⎇ e52b3fb572cec07d35c55d87519ecc8e0bd0505a
crates/modules/charka/charka-codegen/src/expr.rs
Historia
Quién tocó qué
crudo
feat(charka): charka-codegen — emisión de Rust desde el IR
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/// Un operando como expresión de tipo `Decimal`.
45pub(crate) fn operand_decimal(sym: &Symbols, op: &Operand) -> String {
46 match op {
47 Operand::Num(n) => format!("dec({})", rust_str(n)),
48 Operand::Str(s) => format!(
49 "Decimal::parse({}).unwrap_or_else(|_| Decimal::zero())",
50 rust_str(s)
51 ),
52 Operand::Figurative(_) => "Decimal::zero()".to_string(),
53 Operand::Data(name) => match sym.lookup(name) {
54 Some(f) => match f.kind {
55 FieldKind::Num { .. } => format!("self.{}.value()", f.ident),
56 FieldKind::Text { .. } => format!(
57 "Decimal::parse(self.{}.display().trim()).unwrap_or_else(|_| Decimal::zero())",
58 f.ident
59 ),
60 },
61 None => format!("Decimal::zero() /* charka: dato no resuelto {name} */"),
62 },
63 }
64}
65
66/// Un operando como expresión de tipo `&str` (para texto).
67pub(crate) fn operand_str(sym: &Symbols, op: &Operand) -> String {
68 match op {
69 Operand::Str(s) => rust_str(s),
70 Operand::Num(n) => rust_str(n),
71 Operand::Figurative(f) => rust_str(figurative_text(*f)),
72 Operand::Data(name) => match sym.lookup(name) {
73 Some(f) => format!("self.{}.display().as_str()", f.ident),
74 None => format!("\"\" /* charka: dato no resuelto {name} */"),
75 },
76 }
77}
78
79/// Un operando como expresión que implementa `Display` (para `DISPLAY`).
80pub(crate) fn operand_display(sym: &Symbols, op: &Operand) -> String {
81 match op {
82 Operand::Str(s) => rust_str(s),
83 Operand::Num(n) => rust_str(n),
84 Operand::Figurative(f) => rust_str(figurative_text(*f)),
85 Operand::Data(name) => match sym.lookup(name) {
86 Some(f) => format!("self.{}.display()", f.ident),
87 None => format!("\"\" /* charka: dato no resuelto {name} */"),
88 },
89 }
90}
91
92/// Emite una expresión aritmética como código Rust de tipo `Decimal`.
93pub(crate) fn emit_expr(sym: &Symbols, e: &Expr) -> String {
94 match e {
95 Expr::Operand(op) => operand_decimal(sym, op),
96 Expr::Neg(inner) => format!("Decimal::zero().sub(&({}))", emit_expr(sym, inner)),
97 Expr::Binary { op, lhs, rhs } => {
98 let l = emit_expr(sym, lhs);
99 let r = emit_expr(sym, rhs);
100 match op {
101 BinOp::Add => format!("({l}).add(&({r}))"),
102 BinOp::Sub => format!("({l}).sub(&({r}))"),
103 BinOp::Mul => format!("({l}).mul(&({r}))"),
104 BinOp::Div => format!(
105 "({l}).div(&({r}), 9, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())"
106 ),
107 BinOp::Pow => "Decimal::zero() /* charka: ** no soportado */".to_string(),
108 }
109 }
110 }
111}
112
113/// Emite una condición como código Rust de tipo `bool`.
114pub(crate) fn emit_cond(sym: &Symbols, c: &Cond) -> String {
115 match c {
116 Cond::Compare { lhs, op, rhs } => emit_compare(sym, lhs, *op, rhs),
117 Cond::Named(name) => {
118 format!("false /* charka: condición 88 no soportada: {name} */")
119 }
120 Cond::Not(inner) => format!("!({})", emit_cond(sym, inner)),
121 Cond::And(a, b) => format!("({}) && ({})", emit_cond(sym, a), emit_cond(sym, b)),
122 Cond::Or(a, b) => format!("({}) || ({})", emit_cond(sym, a), emit_cond(sym, b)),
123 }
124}
125
126/// Emite una comparación: numérica si ambos lados lo son, alfanumérica
127/// si alguno es texto.
128fn emit_compare(sym: &Symbols, lhs: &Operand, op: CmpOp, rhs: &Operand) -> String {
129 if is_text_operand(sym, lhs) || is_text_operand(sym, rhs) {
130 let method = match op {
131 CmpOp::Eq => "is_eq",
132 CmpOp::Ne => "is_ne",
133 CmpOp::Lt => "is_lt",
134 CmpOp::Gt => "is_gt",
135 CmpOp::Le => "is_le",
136 CmpOp::Ge => "is_ge",
137 };
138 format!(
139 "cobol_text_cmp({}, {}).{method}()",
140 operand_str(sym, lhs),
141 operand_str(sym, rhs)
142 )
143 } else {
144 let rust_op = match op {
145 CmpOp::Eq => "==",
146 CmpOp::Ne => "!=",
147 CmpOp::Lt => "<",
148 CmpOp::Gt => ">",
149 CmpOp::Le => "<=",
150 CmpOp::Ge => ">=",
151 };
152 format!(
153 "({}) {rust_op} ({})",
154 operand_decimal(sym, lhs),
155 operand_decimal(sym, rhs)
156 )
157 }
158}
159
160/// ¿El operando es alfanumérico?
161fn is_text_operand(sym: &Symbols, op: &Operand) -> bool {
162 match op {
163 Operand::Str(_) => true,
164 Operand::Data(name) => matches!(
165 sym.lookup(name).map(|f| &f.kind),
166 Some(FieldKind::Text { .. })
167 ),
168 _ => false,
169 }
170}