tawasuyu

tawasuyu/brahman

Rust 483TOML 191Markdown 50JSON 176777 Definitions

⎇ 28ee1ae2602662b216549ba4db0d34a9b55b8deb crates/modules/charka/charka-codegen/src/expr.rs Rust · 180 lines · 6.3 KB History Who touched what raw

feat(charka): nivel 88 + modelo de datos compartido en charka-ir 28ee1ae26 · sergio ·

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) => match sym.condition(name) {
118            // Un nombre de condición (88) equivale a comparar su dato
119            // padre con el valor que la hace verdadera.
120            Some(cn) => emit_cond(
121                sym,
122                &Cond::Compare {
123                    lhs: Operand::Data(cn.parent.clone()),
124                    op: CmpOp::Eq,
125                    rhs: cn.value.clone(),
126                },
127            ),
128            None => format!("false /* charka: condición 88 no resuelta: {name} */"),
129        },
130        Cond::Not(inner) => format!("!({})", emit_cond(sym, inner)),
131        Cond::And(a, b) => format!("({}) && ({})", emit_cond(sym, a), emit_cond(sym, b)),
132        Cond::Or(a, b) => format!("({}) || ({})", emit_cond(sym, a), emit_cond(sym, b)),
133    }
134}
135
136/// Emite una comparación: numérica si ambos lados lo son, alfanumérica
137/// si alguno es texto.
138fn emit_compare(sym: &Symbols, lhs: &Operand, op: CmpOp, rhs: &Operand) -> String {
139    if is_text_operand(sym, lhs) || is_text_operand(sym, rhs) {
140        let method = match op {
141            CmpOp::Eq => "is_eq",
142            CmpOp::Ne => "is_ne",
143            CmpOp::Lt => "is_lt",
144            CmpOp::Gt => "is_gt",
145            CmpOp::Le => "is_le",
146            CmpOp::Ge => "is_ge",
147        };
148        format!(
149            "cobol_text_cmp({}, {}).{method}()",
150            operand_str(sym, lhs),
151            operand_str(sym, rhs)
152        )
153    } else {
154        let rust_op = match op {
155            CmpOp::Eq => "==",
156            CmpOp::Ne => "!=",
157            CmpOp::Lt => "<",
158            CmpOp::Gt => ">",
159            CmpOp::Le => "<=",
160            CmpOp::Ge => ">=",
161        };
162        format!(
163            "({}) {rust_op} ({})",
164            operand_decimal(sym, lhs),
165            operand_decimal(sym, rhs)
166        )
167    }
168}
169
170/// ¿El operando es alfanumérico?
171fn is_text_operand(sym: &Symbols, op: &Operand) -> bool {
172    match op {
173        Operand::Str(_) => true,
174        Operand::Data(name) => matches!(
175            sym.lookup(name).map(|f| &f.kind),
176            Some(FieldKind::Text { .. })
177        ),
178        _ => false,
179    }
180}