71a4068d12
Tercera etapa del transpilador: Program -> Ir. El PROCEDURE division pasa de sentencias con tokens crudos a un árbol de instrucciones tipadas. - lower(&Program) -> Ir: total y tolerante, nunca falla. La DATA division pasa tal cual y sirve de tabla de símbolos. - Stmt cubre MOVE, DISPLAY, ACCEPT, COMPUTE, ADD, SUBTRACT, MULTIPLY, DIVIDE, IF/ELSE/END-IF, PERFORM (fuera de línea, en línea, TIMES, UNTIL), GO TO, STOP RUN, GOBACK, EXIT, CONTINUE. - Expresiones de COMPUTE con precedencia y paréntesis (Pratt). Condiciones con comparadores símbolo/palabra, AND/OR/NOT y nombres de condición (nivel 88). - Delimita statements por palabras frontera (COBOL no los separa con un símbolo). Verbo no soportado -> Stmt::Unknown con tokens crudos. - Módulos: ast / kw / cursor / expr / stmt. 17 tests; fmt + clippy limpios. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
209 lines
5.9 KiB
Rust
209 lines
5.9 KiB
Rust
//! Parseo de expresiones aritméticas (`COMPUTE`) y de condiciones
|
|
//! (`IF`, `PERFORM UNTIL`).
|
|
|
|
use charka_parser::TokenKind;
|
|
|
|
use crate::ast::{BinOp, CmpOp, Cond, Expr, Operand};
|
|
use crate::cursor::{parse_operand, Cursor};
|
|
use crate::kw::is_boundary;
|
|
|
|
// ── Expresiones ───────────────────────────────────────────────────
|
|
|
|
/// Parsea una expresión aritmética con precedencia y paréntesis.
|
|
pub(crate) fn parse_expr(c: &mut Cursor) -> Expr {
|
|
parse_bin(c, 0)
|
|
}
|
|
|
|
/// Trepa por precedencia: `min_prec` es la mínima precedencia que este
|
|
/// nivel acepta seguir consumiendo.
|
|
fn parse_bin(c: &mut Cursor, min_prec: u8) -> Expr {
|
|
let mut lhs = parse_unary(c);
|
|
while let Some((op, prec, right_assoc)) = peek_binop(c) {
|
|
if prec < min_prec {
|
|
break;
|
|
}
|
|
c.bump();
|
|
let next_min = if right_assoc { prec } else { prec + 1 };
|
|
let rhs = parse_bin(c, next_min);
|
|
lhs = Expr::Binary {
|
|
op,
|
|
lhs: Box::new(lhs),
|
|
rhs: Box::new(rhs),
|
|
};
|
|
}
|
|
lhs
|
|
}
|
|
|
|
/// Negación o signo unario delante de un primario.
|
|
fn parse_unary(c: &mut Cursor) -> Expr {
|
|
if c.eat_sym("-") {
|
|
return Expr::Neg(Box::new(parse_unary(c)));
|
|
}
|
|
if c.eat_sym("+") {
|
|
return parse_unary(c);
|
|
}
|
|
parse_primary(c)
|
|
}
|
|
|
|
/// Un primario: un paréntesis o un operando hoja.
|
|
fn parse_primary(c: &mut Cursor) -> Expr {
|
|
if c.eat_sym("(") {
|
|
let e = parse_bin(c, 0);
|
|
c.eat_sym(")");
|
|
return e;
|
|
}
|
|
// No consumir un verbo o conector: la expresión terminó.
|
|
if c.done() || c.peek_word().map(|w| is_boundary(&w)).unwrap_or(false) {
|
|
return Expr::Operand(Operand::Num("0".into()));
|
|
}
|
|
Expr::Operand(parse_operand(c))
|
|
}
|
|
|
|
/// El operador binario en el token actual, con su precedencia y si es
|
|
/// asociativo a derecha. `**` es la única potencia, asociativa a der.
|
|
fn peek_binop(c: &Cursor) -> Option<(BinOp, u8, bool)> {
|
|
let t = c.peek()?;
|
|
if t.kind != TokenKind::Symbol {
|
|
return None;
|
|
}
|
|
match t.text.as_str() {
|
|
"+" => Some((BinOp::Add, 1, false)),
|
|
"-" => Some((BinOp::Sub, 1, false)),
|
|
"*" => Some((BinOp::Mul, 2, false)),
|
|
"/" => Some((BinOp::Div, 2, false)),
|
|
"**" => Some((BinOp::Pow, 3, true)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
// ── Condiciones ───────────────────────────────────────────────────
|
|
|
|
/// Parsea una condición: comparaciones unidas por `AND`/`OR`/`NOT`.
|
|
pub(crate) fn parse_cond(c: &mut Cursor) -> Cond {
|
|
parse_or(c)
|
|
}
|
|
|
|
fn parse_or(c: &mut Cursor) -> Cond {
|
|
let mut lhs = parse_and(c);
|
|
while c.eat_word("OR") {
|
|
let rhs = parse_and(c);
|
|
lhs = Cond::Or(Box::new(lhs), Box::new(rhs));
|
|
}
|
|
lhs
|
|
}
|
|
|
|
fn parse_and(c: &mut Cursor) -> Cond {
|
|
let mut lhs = parse_not(c);
|
|
while c.eat_word("AND") {
|
|
let rhs = parse_not(c);
|
|
lhs = Cond::And(Box::new(lhs), Box::new(rhs));
|
|
}
|
|
lhs
|
|
}
|
|
|
|
fn parse_not(c: &mut Cursor) -> Cond {
|
|
if c.eat_word("NOT") {
|
|
return Cond::Not(Box::new(parse_not(c)));
|
|
}
|
|
parse_cond_primary(c)
|
|
}
|
|
|
|
/// Un primario de condición: un paréntesis, una comparación, o un dato
|
|
/// suelto (un nombre de condición de nivel 88).
|
|
fn parse_cond_primary(c: &mut Cursor) -> Cond {
|
|
if c.eat_sym("(") {
|
|
let inner = parse_or(c);
|
|
c.eat_sym(")");
|
|
return inner;
|
|
}
|
|
if c.done() || c.peek_word().map(|w| is_boundary(&w)).unwrap_or(false) {
|
|
return Cond::Named(String::new());
|
|
}
|
|
let lhs = parse_operand(c);
|
|
match parse_cmp_op(c) {
|
|
Some(op) => {
|
|
let rhs = parse_operand(c);
|
|
Cond::Compare { lhs, op, rhs }
|
|
}
|
|
None => match lhs {
|
|
Operand::Data(n) => Cond::Named(n),
|
|
// Un literal solo como condición es raro; se degrada a "≠ 0".
|
|
other => Cond::Compare {
|
|
lhs: other,
|
|
op: CmpOp::Ne,
|
|
rhs: Operand::Num("0".into()),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Lee un operador relacional (forma símbolo o forma palabra). Si no
|
|
/// hay ninguno, rebobina el cursor y devuelve `None`.
|
|
fn parse_cmp_op(c: &mut Cursor) -> Option<CmpOp> {
|
|
let save = c.pos;
|
|
c.eat_word("IS");
|
|
let negated = c.eat_word("NOT");
|
|
if let Some(op) = cmp_core(c) {
|
|
return Some(if negated { negate(op) } else { op });
|
|
}
|
|
c.pos = save;
|
|
None
|
|
}
|
|
|
|
/// El núcleo del comparador, sin el `IS`/`NOT` opcionales.
|
|
fn cmp_core(c: &mut Cursor) -> Option<CmpOp> {
|
|
if c.eat_sym("<>") {
|
|
return Some(CmpOp::Ne);
|
|
}
|
|
if c.eat_sym("<=") {
|
|
return Some(CmpOp::Le);
|
|
}
|
|
if c.eat_sym(">=") {
|
|
return Some(CmpOp::Ge);
|
|
}
|
|
if c.eat_sym("=") {
|
|
return Some(CmpOp::Eq);
|
|
}
|
|
if c.eat_sym("<") {
|
|
return Some(CmpOp::Lt);
|
|
}
|
|
if c.eat_sym(">") {
|
|
return Some(CmpOp::Gt);
|
|
}
|
|
if c.eat_word("EQUAL") || c.eat_word("EQUALS") {
|
|
c.eat_word("TO");
|
|
return Some(CmpOp::Eq);
|
|
}
|
|
if c.eat_word("GREATER") {
|
|
c.eat_word("THAN");
|
|
if c.eat_word("OR") {
|
|
c.eat_word("EQUAL");
|
|
c.eat_word("TO");
|
|
return Some(CmpOp::Ge);
|
|
}
|
|
return Some(CmpOp::Gt);
|
|
}
|
|
if c.eat_word("LESS") {
|
|
c.eat_word("THAN");
|
|
if c.eat_word("OR") {
|
|
c.eat_word("EQUAL");
|
|
c.eat_word("TO");
|
|
return Some(CmpOp::Le);
|
|
}
|
|
return Some(CmpOp::Lt);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// El comparador opuesto — para resolver `NOT` delante de un relacional.
|
|
fn negate(op: CmpOp) -> CmpOp {
|
|
match op {
|
|
CmpOp::Eq => CmpOp::Ne,
|
|
CmpOp::Ne => CmpOp::Eq,
|
|
CmpOp::Lt => CmpOp::Ge,
|
|
CmpOp::Ge => CmpOp::Lt,
|
|
CmpOp::Gt => CmpOp::Le,
|
|
CmpOp::Le => CmpOp::Gt,
|
|
}
|
|
}
|