tawasuyu

tawasuyu/brahman

Rust 483TOML 191Markdown 50JSON 176798 Definitions

⎇ 82ba0b7a1a04b7ff2d7e2175ac74766da0d16066 crates/modules/charka/charka-codegen/src/expr.rs Rust · 204 lines · 7.2 KB History Who touched what raw

feat(charka): OCCURS — tablas y referencias con subíndice 3902763da · 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/// 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}