⎇ 82ba0b7a1a04b7ff2d7e2175ac74766da0d16066
crates/modules/charka/charka-codegen/src/expr.rs
History
Who touched what
raw
feat(charka): OCCURS — tablas y referencias con subíndice
1//! Emisión de expresiones y condiciones: cada nodo del IR se convierte
2//! en un fragmento de código Rust (un `String`).
3
4use charka_ir::{BinOp, CmpOp, Cond, Expr, Figurative, Operand};
5
6use crate::sym::{FieldKind, Symbols};
7
8/// Un literal de texto Rust con las comillas y los escapes adecuados.
9pub(crate) fn rust_str(s: &str) -> String {
10 let mut out = String::from("\"");
11 for c in s.chars() {
12 match c {
13 '"' => out.push_str("\\\""),
14 '\\' => out.push_str("\\\\"),
15 '\n' => out.push_str("\\n"),
16 '\r' => out.push_str("\\r"),
17 '\t' => out.push_str("\\t"),
18 _ => out.push(c),
19 }
20 }
21 out.push('"');
22 out
23}
24
25/// El texto que representa una constante figurativa.
26pub(crate) fn figurative_text(f: Figurative) -> &'static str {
27 match f {
28 Figurative::Zero => "0",
29 Figurative::Space => " ",
30 Figurative::Quote => "\"",
31 Figurative::HighValue | Figurative::LowValue | Figurative::Null => "",
32 }
33}
34
35/// El carácter de relleno de una figurativa, para `Text::fill`.
36pub(crate) fn figurative_fill(f: Figurative) -> char {
37 match f {
38 Figurative::Zero => '0',
39 Figurative::Quote => '"',
40 _ => ' ',
41 }
42}
43
44/// La referencia Rust a un campo (un dato escalar `self.x` o un
45/// elemento de tabla `self.x[idx]`) y el tipo del campo. `None` si el
46/// operando no es una referencia a dato.
47pub(crate) fn field_ref(sym: &Symbols, op: &Operand) -> Option<(String, FieldKind)> {
48 match op {
49 Operand::Data(name) => sym
50 .lookup(name)
51 .map(|f| (format!("self.{}", f.ident), f.kind)),
52 Operand::Indexed { name, index } => sym.lookup(name).map(|f| {
53 (
54 format!("self.{}[{}]", f.ident, subscript(sym, index)),
55 f.kind,
56 )
57 }),
58 _ => None,
59 }
60}
61
62/// Un subíndice de tabla como expresión `usize`. COBOL es 1-based;
63/// Rust 0-based — de ahí el `saturating_sub(1)`.
64fn subscript(sym: &Symbols, index: &Operand) -> String {
65 format!(
66 "(({}).rescale(0, Rounding::Truncate).mantissa() as usize).saturating_sub(1)",
67 operand_decimal(sym, index)
68 )
69}
70
71/// Un operando como expresión de tipo `Decimal`.
72pub(crate) fn operand_decimal(sym: &Symbols, op: &Operand) -> String {
73 match op {
74 Operand::Num(n) => format!("dec({})", rust_str(n)),
75 Operand::Str(s) => format!(
76 "Decimal::parse({}).unwrap_or_else(|_| Decimal::zero())",
77 rust_str(s)
78 ),
79 Operand::Figurative(_) => "Decimal::zero()".to_string(),
80 Operand::Data(_) | Operand::Indexed { .. } => match field_ref(sym, op) {
81 Some((lref, FieldKind::Num { .. })) => format!("{lref}.value()"),
82 Some((lref, FieldKind::Text { .. })) => format!(
83 "Decimal::parse({lref}.display().trim()).unwrap_or_else(|_| Decimal::zero())"
84 ),
85 None => "Decimal::zero() /* charka: dato no resuelto */".to_string(),
86 },
87 }
88}
89
90/// Un operando como expresión de tipo `&str` (para texto).
91pub(crate) fn operand_str(sym: &Symbols, op: &Operand) -> String {
92 match op {
93 Operand::Str(s) => rust_str(s),
94 Operand::Num(n) => rust_str(n),
95 Operand::Figurative(f) => rust_str(figurative_text(*f)),
96 Operand::Data(_) | Operand::Indexed { .. } => match field_ref(sym, op) {
97 Some((lref, _)) => format!("{lref}.display().as_str()"),
98 None => "\"\" /* charka: dato no resuelto */".to_string(),
99 },
100 }
101}
102
103/// Un operando como expresión que implementa `Display` (para `DISPLAY`).
104pub(crate) fn operand_display(sym: &Symbols, op: &Operand) -> String {
105 match op {
106 Operand::Str(s) => rust_str(s),
107 Operand::Num(n) => rust_str(n),
108 Operand::Figurative(f) => rust_str(figurative_text(*f)),
109 Operand::Data(_) | Operand::Indexed { .. } => match field_ref(sym, op) {
110 Some((lref, _)) => format!("{lref}.display()"),
111 None => "\"\" /* charka: dato no resuelto */".to_string(),
112 },
113 }
114}
115
116/// Emite una expresión aritmética como código Rust de tipo `Decimal`.
117pub(crate) fn emit_expr(sym: &Symbols, e: &Expr) -> String {
118 match e {
119 Expr::Operand(op) => operand_decimal(sym, op),
120 Expr::Neg(inner) => format!("Decimal::zero().sub(&({}))", emit_expr(sym, inner)),
121 Expr::Binary { op, lhs, rhs } => {
122 let l = emit_expr(sym, lhs);
123 let r = emit_expr(sym, rhs);
124 match op {
125 BinOp::Add => format!("({l}).add(&({r}))"),
126 BinOp::Sub => format!("({l}).sub(&({r}))"),
127 BinOp::Mul => format!("({l}).mul(&({r}))"),
128 BinOp::Div => format!(
129 "({l}).div(&({r}), 9, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())"
130 ),
131 BinOp::Pow => "Decimal::zero() /* charka: ** no soportado */".to_string(),
132 }
133 }
134 }
135}
136
137/// Emite una condición como código Rust de tipo `bool`.
138pub(crate) fn emit_cond(sym: &Symbols, c: &Cond) -> String {
139 match c {
140 Cond::Compare { lhs, op, rhs } => emit_compare(sym, lhs, *op, rhs),
141 Cond::Named(name) => match sym.condition(name) {
142 // Un nombre de condición (88) equivale a comparar su dato
143 // padre con el valor que la hace verdadera.
144 Some(cn) => emit_cond(
145 sym,
146 &Cond::Compare {
147 lhs: Operand::Data(cn.parent.clone()),
148 op: CmpOp::Eq,
149 rhs: cn.value.clone(),
150 },
151 ),
152 None => format!("false /* charka: condición 88 no resuelta: {name} */"),
153 },
154 Cond::Not(inner) => format!("!({})", emit_cond(sym, inner)),
155 Cond::And(a, b) => format!("({}) && ({})", emit_cond(sym, a), emit_cond(sym, b)),
156 Cond::Or(a, b) => format!("({}) || ({})", emit_cond(sym, a), emit_cond(sym, b)),
157 }
158}
159
160/// Emite una comparación: numérica si ambos lados lo son, alfanumérica
161/// si alguno es texto.
162fn emit_compare(sym: &Symbols, lhs: &Operand, op: CmpOp, rhs: &Operand) -> String {
163 if is_text_operand(sym, lhs) || is_text_operand(sym, rhs) {
164 let method = match op {
165 CmpOp::Eq => "is_eq",
166 CmpOp::Ne => "is_ne",
167 CmpOp::Lt => "is_lt",
168 CmpOp::Gt => "is_gt",
169 CmpOp::Le => "is_le",
170 CmpOp::Ge => "is_ge",
171 };
172 format!(
173 "cobol_text_cmp({}, {}).{method}()",
174 operand_str(sym, lhs),
175 operand_str(sym, rhs)
176 )
177 } else {
178 let rust_op = match op {
179 CmpOp::Eq => "==",
180 CmpOp::Ne => "!=",
181 CmpOp::Lt => "<",
182 CmpOp::Gt => ">",
183 CmpOp::Le => "<=",
184 CmpOp::Ge => ">=",
185 };
186 format!(
187 "({}) {rust_op} ({})",
188 operand_decimal(sym, lhs),
189 operand_decimal(sym, rhs)
190 )
191 }
192}
193
194/// ¿El operando es alfanumérico?
195fn is_text_operand(sym: &Symbols, op: &Operand) -> bool {
196 match op {
197 Operand::Str(_) => true,
198 Operand::Data(_) | Operand::Indexed { .. } => matches!(
199 field_ref(sym, op).map(|(_, k)| k),
200 Some(FieldKind::Text { .. })
201 ),
202 _ => false,
203 }
204}