tawasuyu

tawasuyu/brahman

Rust 307TOML 110JSON 14Markdown 113965 Sutichaykuna

⎇ 71a4068d12e81d0be7b08d0717114dd5b10868fe crates/modules/charka/charka-ir/src/expr.rs Rust · 208 siq'ikuna · 5.9 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(charka): charka-ir — representación intermedia con statements tipados 71a4068d1 · sergio ·

1//! Parseo de expresiones aritméticas (`COMPUTE`) y de condiciones
2//! (`IF`, `PERFORM UNTIL`).
3
4use charka_parser::TokenKind;
5
6use crate::ast::{BinOp, CmpOp, Cond, Expr, Operand};
7use crate::cursor::{parse_operand, Cursor};
8use crate::kw::is_boundary;
9
10// ── Expresiones ───────────────────────────────────────────────────
11
12/// Parsea una expresión aritmética con precedencia y paréntesis.
13pub(crate) fn parse_expr(c: &mut Cursor) -> Expr {
14    parse_bin(c, 0)
15}
16
17/// Trepa por precedencia: `min_prec` es la mínima precedencia que este
18/// nivel acepta seguir consumiendo.
19fn parse_bin(c: &mut Cursor, min_prec: u8) -> Expr {
20    let mut lhs = parse_unary(c);
21    while let Some((op, prec, right_assoc)) = peek_binop(c) {
22        if prec < min_prec {
23            break;
24        }
25        c.bump();
26        let next_min = if right_assoc { prec } else { prec + 1 };
27        let rhs = parse_bin(c, next_min);
28        lhs = Expr::Binary {
29            op,
30            lhs: Box::new(lhs),
31            rhs: Box::new(rhs),
32        };
33    }
34    lhs
35}
36
37/// Negación o signo unario delante de un primario.
38fn parse_unary(c: &mut Cursor) -> Expr {
39    if c.eat_sym("-") {
40        return Expr::Neg(Box::new(parse_unary(c)));
41    }
42    if c.eat_sym("+") {
43        return parse_unary(c);
44    }
45    parse_primary(c)
46}
47
48/// Un primario: un paréntesis o un operando hoja.
49fn parse_primary(c: &mut Cursor) -> Expr {
50    if c.eat_sym("(") {
51        let e = parse_bin(c, 0);
52        c.eat_sym(")");
53        return e;
54    }
55    // No consumir un verbo o conector: la expresión terminó.
56    if c.done() || c.peek_word().map(|w| is_boundary(&w)).unwrap_or(false) {
57        return Expr::Operand(Operand::Num("0".into()));
58    }
59    Expr::Operand(parse_operand(c))
60}
61
62/// El operador binario en el token actual, con su precedencia y si es
63/// asociativo a derecha. `**` es la única potencia, asociativa a der.
64fn peek_binop(c: &Cursor) -> Option<(BinOp, u8, bool)> {
65    let t = c.peek()?;
66    if t.kind != TokenKind::Symbol {
67        return None;
68    }
69    match t.text.as_str() {
70        "+" => Some((BinOp::Add, 1, false)),
71        "-" => Some((BinOp::Sub, 1, false)),
72        "*" => Some((BinOp::Mul, 2, false)),
73        "/" => Some((BinOp::Div, 2, false)),
74        "**" => Some((BinOp::Pow, 3, true)),
75        _ => None,
76    }
77}
78
79// ── Condiciones ───────────────────────────────────────────────────
80
81/// Parsea una condición: comparaciones unidas por `AND`/`OR`/`NOT`.
82pub(crate) fn parse_cond(c: &mut Cursor) -> Cond {
83    parse_or(c)
84}
85
86fn parse_or(c: &mut Cursor) -> Cond {
87    let mut lhs = parse_and(c);
88    while c.eat_word("OR") {
89        let rhs = parse_and(c);
90        lhs = Cond::Or(Box::new(lhs), Box::new(rhs));
91    }
92    lhs
93}
94
95fn parse_and(c: &mut Cursor) -> Cond {
96    let mut lhs = parse_not(c);
97    while c.eat_word("AND") {
98        let rhs = parse_not(c);
99        lhs = Cond::And(Box::new(lhs), Box::new(rhs));
100    }
101    lhs
102}
103
104fn parse_not(c: &mut Cursor) -> Cond {
105    if c.eat_word("NOT") {
106        return Cond::Not(Box::new(parse_not(c)));
107    }
108    parse_cond_primary(c)
109}
110
111/// Un primario de condición: un paréntesis, una comparación, o un dato
112/// suelto (un nombre de condición de nivel 88).
113fn parse_cond_primary(c: &mut Cursor) -> Cond {
114    if c.eat_sym("(") {
115        let inner = parse_or(c);
116        c.eat_sym(")");
117        return inner;
118    }
119    if c.done() || c.peek_word().map(|w| is_boundary(&w)).unwrap_or(false) {
120        return Cond::Named(String::new());
121    }
122    let lhs = parse_operand(c);
123    match parse_cmp_op(c) {
124        Some(op) => {
125            let rhs = parse_operand(c);
126            Cond::Compare { lhs, op, rhs }
127        }
128        None => match lhs {
129            Operand::Data(n) => Cond::Named(n),
130            // Un literal solo como condición es raro; se degrada a "≠ 0".
131            other => Cond::Compare {
132                lhs: other,
133                op: CmpOp::Ne,
134                rhs: Operand::Num("0".into()),
135            },
136        },
137    }
138}
139
140/// Lee un operador relacional (forma símbolo o forma palabra). Si no
141/// hay ninguno, rebobina el cursor y devuelve `None`.
142fn parse_cmp_op(c: &mut Cursor) -> Option<CmpOp> {
143    let save = c.pos;
144    c.eat_word("IS");
145    let negated = c.eat_word("NOT");
146    if let Some(op) = cmp_core(c) {
147        return Some(if negated { negate(op) } else { op });
148    }
149    c.pos = save;
150    None
151}
152
153/// El núcleo del comparador, sin el `IS`/`NOT` opcionales.
154fn cmp_core(c: &mut Cursor) -> Option<CmpOp> {
155    if c.eat_sym("<>") {
156        return Some(CmpOp::Ne);
157    }
158    if c.eat_sym("<=") {
159        return Some(CmpOp::Le);
160    }
161    if c.eat_sym(">=") {
162        return Some(CmpOp::Ge);
163    }
164    if c.eat_sym("=") {
165        return Some(CmpOp::Eq);
166    }
167    if c.eat_sym("<") {
168        return Some(CmpOp::Lt);
169    }
170    if c.eat_sym(">") {
171        return Some(CmpOp::Gt);
172    }
173    if c.eat_word("EQUAL") || c.eat_word("EQUALS") {
174        c.eat_word("TO");
175        return Some(CmpOp::Eq);
176    }
177    if c.eat_word("GREATER") {
178        c.eat_word("THAN");
179        if c.eat_word("OR") {
180            c.eat_word("EQUAL");
181            c.eat_word("TO");
182            return Some(CmpOp::Ge);
183        }
184        return Some(CmpOp::Gt);
185    }
186    if c.eat_word("LESS") {
187        c.eat_word("THAN");
188        if c.eat_word("OR") {
189            c.eat_word("EQUAL");
190            c.eat_word("TO");
191            return Some(CmpOp::Le);
192        }
193        return Some(CmpOp::Lt);
194    }
195    None
196}
197
198/// El comparador opuesto — para resolver `NOT` delante de un relacional.
199fn negate(op: CmpOp) -> CmpOp {
200    match op {
201        CmpOp::Eq => CmpOp::Ne,
202        CmpOp::Ne => CmpOp::Eq,
203        CmpOp::Lt => CmpOp::Ge,
204        CmpOp::Ge => CmpOp::Lt,
205        CmpOp::Gt => CmpOp::Le,
206        CmpOp::Le => CmpOp::Gt,
207    }
208}