//! `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 { 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 { word_of(self.peek()) } /// La palabra `n` posiciones adelante, en mayúsculas. pub(crate) fn word_at(&self, n: usize) -> Option { 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 { 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 { 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, }) }