⎇ 71a4068d12e81d0be7b08d0717114dd5b10868fe
crates/modules/charka/charka-ir/src/expr.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(charka): charka-ir — representación intermedia con statements tipados
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}