Files
brahman/crates/modules/charka/charka-ir/src/cursor.rs
T
sergio 3902763daa feat(charka): OCCURS — tablas y referencias con subíndice
Los arrays de COBOL, que antes el transpilador descartaba en silencio.
Una rebanada vertical amplia que atraviesa el pipeline entero.

- Parser: la cláusula OCCURS n [TIMES] se captura en DataItem.
- IR: Operand::Indexed { name, index } — una referencia ELEM(I), con
  subíndice 1-based. Los destinos de los statements pasan de
  Vec<String> a Vec<Operand>, así que se puede escribir a un elemento
  de tabla (MOVE x TO ELEM(I), COMPUTE ELEM(I) = ...). model::Field
  gana occurs: Option<u32>.
- Codegen: un campo OCCURS se emite como Vec<Num>/Vec<Text>,
  inicializado con vec![..; n]; una referencia con subíndice indexa el
  vector (1-based -> 0-based).
- Shadow: en el intérprete todo campo es un vector — un escalar es de
  longitud 1, una tabla de n; las referencias se resuelven a
  (nombre, índice).
- Corpus: programa nuevo 11-tabla (llena una tabla con cuadrados y los
  suma). Verificado: el intérprete sombra y el crate compilado por
  scaffold dan ambos SUMA DE CUADRADOS = 000055.

Alcance v1: OCCURS elemental, una dimensión, subíndice de un operando.
Fuera: OCCURS de grupo, multidimensional, DEPENDING ON.

Tests: charka-parser 16, charka-ir 24, charka-codegen 18,
charka-shadow 16. fmt + clippy limpios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 22:03:48 +00:00

156 lines
4.6 KiB
Rust

//! `Cursor` — un cursor de avance sobre la lista de tokens de una
//! sentencia, más las primitivas para leer un operando.
use charka_parser::{Token, TokenKind};
use crate::ast::{Figurative, Operand};
/// Cursor sobre los tokens de una sentencia. `pos` es público dentro
/// del crate para que el parser de condiciones pueda rebobinar.
pub(crate) struct Cursor<'a> {
pub(crate) toks: &'a [Token],
pub(crate) pos: usize,
}
impl<'a> Cursor<'a> {
pub(crate) fn new(toks: &'a [Token]) -> Self {
Self { toks, pos: 0 }
}
/// ¿Se agotaron los tokens?
pub(crate) fn done(&self) -> bool {
self.pos >= self.toks.len()
}
/// El token actual, sin consumirlo.
pub(crate) fn peek(&self) -> Option<&Token> {
self.toks.get(self.pos)
}
/// El token `n` posiciones adelante, sin consumirlo.
pub(crate) fn peek_at(&self, n: usize) -> Option<&Token> {
self.toks.get(self.pos + n)
}
/// Consume y devuelve el token actual.
pub(crate) fn bump(&mut self) -> Option<Token> {
let t = self.toks.get(self.pos).cloned();
if t.is_some() {
self.pos += 1;
}
t
}
/// El token actual, si es una palabra, en mayúsculas.
pub(crate) fn peek_word(&self) -> Option<String> {
word_of(self.peek())
}
/// La palabra `n` posiciones adelante, en mayúsculas.
pub(crate) fn word_at(&self, n: usize) -> Option<String> {
word_of(self.peek_at(n))
}
/// ¿El token actual es la palabra `kw`?
pub(crate) fn at_word(&self, kw: &str) -> bool {
self.peek_word().as_deref() == Some(kw)
}
/// Consume el token actual si es la palabra `kw`.
pub(crate) fn eat_word(&mut self, kw: &str) -> bool {
if self.at_word(kw) {
self.pos += 1;
true
} else {
false
}
}
/// ¿El token actual es el símbolo `s`?
pub(crate) fn at_sym(&self, s: &str) -> bool {
matches!(self.peek(), Some(t) if t.kind == TokenKind::Symbol && t.text == s)
}
/// Consume el token actual si es el símbolo `s`.
pub(crate) fn eat_sym(&mut self, s: &str) -> bool {
if self.at_sym(s) {
self.pos += 1;
true
} else {
false
}
}
}
/// Si el token es una palabra, su texto en mayúsculas.
fn word_of(t: Option<&Token>) -> Option<String> {
match t {
Some(t) if t.kind == TokenKind::Word => Some(t.text.to_uppercase()),
_ => None,
}
}
/// Lee un operando: un literal con signo opcional, o un token suelto.
pub(crate) fn parse_operand(c: &mut Cursor) -> Operand {
// Signo delante de un literal numérico (`-5`, `+3`).
if (c.at_sym("-") || c.at_sym("+")) && c.peek_at(1).map(|t| t.kind) == Some(TokenKind::Number) {
let neg = c.at_sym("-");
c.bump();
let num = c.bump().expect("número tras el signo");
return Operand::Num(if neg {
format!("-{}", num.text)
} else {
num.text
});
}
let base = match c.bump() {
Some(t) => token_to_operand(&t),
None => return Operand::Num("0".into()),
};
// Subíndice de tabla: `name(index)`. La v1 toma un solo subíndice;
// lo demás dentro del paréntesis se descarta.
if let Operand::Data(name) = &base {
if c.eat_sym("(") {
let index = parse_operand(c);
while !c.at_sym(")") && !c.done() {
c.bump();
}
c.eat_sym(")");
return Operand::Indexed {
name: name.clone(),
index: Box::new(index),
};
}
}
base
}
/// Clasifica un token suelto como operando.
pub(crate) fn token_to_operand(t: &Token) -> Operand {
match t.kind {
TokenKind::Number => Operand::Num(t.text.clone()),
TokenKind::String => Operand::Str(t.text.clone()),
TokenKind::Word => {
let u = t.text.to_uppercase();
match figurative(&u) {
Some(f) => Operand::Figurative(f),
None => Operand::Data(u),
}
}
TokenKind::Period | TokenKind::Symbol => Operand::Data(t.text.clone()),
}
}
/// Reconoce una constante figurativa por su nombre en mayúsculas.
fn figurative(w: &str) -> Option<Figurative> {
Some(match w {
"ZERO" | "ZEROS" | "ZEROES" => Figurative::Zero,
"SPACE" | "SPACES" => Figurative::Space,
"HIGH-VALUE" | "HIGH-VALUES" => Figurative::HighValue,
"LOW-VALUE" | "LOW-VALUES" => Figurative::LowValue,
"QUOTE" | "QUOTES" => Figurative::Quote,
"NULL" | "NULLS" => Figurative::Null,
_ => return None,
})
}