3902763daa
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>
156 lines
4.6 KiB
Rust
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,
|
|
})
|
|
}
|