Files
brahman/crates/modules/charka/charka-ir/src/expr.rs
T
sergio 71a4068d12 feat(charka): charka-ir — representación intermedia con statements tipados
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>
2026-05-21 20:23:19 +00:00

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,
}
}