tawasuyu

tawasuyu/brahman

Rust 459TOML 177Markdown 49JSON 176207 Definiciones

⎇ 3902763daa94397756ff2394116daef97814090a crates/modules/charka/charka-codegen/src/stmt.rs Rust · 425 líneas · 12.6 KB Historia Quién tocó qué crudo

feat(charka): OCCURS — tablas y referencias con subíndice 3902763da · sergio ·

1//! Emisión de los statements del PROCEDURE: cada [`Stmt`] se traduce a
2//! una o varias líneas de código Rust sobre `charka-runtime`.
3
4use charka_ir::{CmpOp, Cond, Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch};
5
6use crate::emit::Emitter;
7use crate::expr::{
8    emit_cond, emit_expr, field_ref, figurative_fill, operand_decimal, operand_display, operand_str,
9};
10use crate::sym::{paragraph_method, FieldKind, Symbols};
11
12/// Emite un statement.
13pub(crate) fn emit_stmt(em: &mut Emitter, sym: &Symbols, stmt: &Stmt) {
14    match stmt {
15        Stmt::Move { from, to } => emit_move(em, sym, from, to),
16        Stmt::Display { items } => emit_display(em, sym, items),
17        Stmt::Accept { .. } => {
18            em.line("// charka: ACCEPT — entrada interactiva no soportada en v1");
19        }
20        Stmt::Compute {
21            targets,
22            rounded,
23            expr,
24        } => {
25            let value = emit_expr(sym, expr);
26            for t in targets {
27                emit_store(em, sym, t, &value, *rounded);
28            }
29        }
30        Stmt::Add {
31            addends,
32            to,
33            giving,
34            rounded,
35        } => emit_add(em, sym, addends, to, giving, *rounded),
36        Stmt::Subtract {
37            amounts,
38            from,
39            giving,
40            rounded,
41        } => emit_subtract(em, sym, amounts, from, giving, *rounded),
42        Stmt::Multiply {
43            left,
44            by,
45            giving,
46            rounded,
47        } => emit_multiply(em, sym, left, by, giving, *rounded),
48        Stmt::Divide {
49            left,
50            right,
51            by_form,
52            giving,
53            rounded,
54        } => emit_divide(em, sym, left, right, *by_form, giving, *rounded),
55        Stmt::If {
56            cond,
57            then_branch,
58            else_branch,
59        } => {
60            em.line(&format!("if {} {{", emit_cond(sym, cond)));
61            em.indent();
62            emit_block(em, sym, then_branch);
63            em.dedent();
64            if else_branch.is_empty() {
65                em.line("}");
66            } else {
67                em.line("} else {");
68                em.indent();
69                emit_block(em, sym, else_branch);
70                em.dedent();
71                em.line("}");
72            }
73        }
74        Stmt::Evaluate {
75            subject,
76            whens,
77            other,
78        } => emit_evaluate(em, sym, subject, whens, other),
79        Stmt::Perform(p) => emit_perform(em, sym, p),
80        Stmt::GoTo { target } => {
81            em.line(&format!(
82                "self.{}(); return; // charka: GO TO (aproximado)",
83                paragraph_method(target)
84            ));
85        }
86        Stmt::StopRun | Stmt::Goback => em.line("std::process::exit(0);"),
87        Stmt::Exit => em.line("return;"),
88        Stmt::Continue => em.line("// CONTINUE"),
89        Stmt::Unknown { verb, .. } => {
90            em.line(&format!("// charka: verbo no transpilado — {verb}"));
91        }
92    }
93}
94
95/// Emite una secuencia de statements (un cuerpo de bloque).
96fn emit_block(em: &mut Emitter, sym: &Symbols, stmts: &[Stmt]) {
97    for s in stmts {
98        emit_stmt(em, sym, s);
99    }
100}
101
102/// Almacena un valor `Decimal` (texto de expresión) en un destino —
103/// un dato escalar o un elemento de tabla.
104fn emit_store(em: &mut Emitter, sym: &Symbols, target: &Operand, value: &str, rounded: bool) {
105    match field_ref(sym, target) {
106        Some((lref, FieldKind::Num { .. })) => {
107            let method = if rounded { "store_rounded" } else { "store" };
108            em.line(&format!("{lref}.{method}({value});"));
109        }
110        Some((lref, FieldKind::Text { .. })) => {
111            em.line(&format!("{lref}.store(({value}).to_string().as_str());"));
112        }
113        None => em.line("// charka: destino no resuelto"),
114    }
115}
116
117fn emit_move(em: &mut Emitter, sym: &Symbols, from: &Operand, to: &[Operand]) {
118    for t in to {
119        match field_ref(sym, t) {
120            Some((lref, FieldKind::Num { .. })) => {
121                em.line(&format!("{lref}.store({});", operand_decimal(sym, from)));
122            }
123            Some((lref, FieldKind::Text { .. })) => {
124                if let Operand::Figurative(fig) = from {
125                    em.line(&format!("{lref}.fill('{}');", figurative_fill(*fig)));
126                } else {
127                    em.line(&format!("{lref}.store({});", operand_str(sym, from)));
128                }
129            }
130            None => em.line("// charka: destino MOVE no resuelto"),
131        }
132    }
133}
134
135fn emit_display(em: &mut Emitter, sym: &Symbols, items: &[Operand]) {
136    if items.is_empty() {
137        em.line("println!();");
138        return;
139    }
140    let placeholders = "{}".repeat(items.len());
141    let args: Vec<String> = items.iter().map(|o| operand_display(sym, o)).collect();
142    em.line(&format!(
143        "println!(\"{placeholders}\", {});",
144        args.join(", ")
145    ));
146}
147
148/// La suma de una lista de operandos, encadenando `.add`.
149fn fold_sum(sym: &Symbols, ops: &[Operand]) -> String {
150    let mut it = ops.iter();
151    let Some(first) = it.next() else {
152        return "Decimal::zero()".to_string();
153    };
154    let mut acc = operand_decimal(sym, first);
155    for o in it {
156        acc = format!("({acc}).add(&({}))", operand_decimal(sym, o));
157    }
158    acc
159}
160
161fn emit_add(
162    em: &mut Emitter,
163    sym: &Symbols,
164    addends: &[Operand],
165    to: &[Operand],
166    giving: &[Operand],
167    rounded: bool,
168) {
169    let sum = fold_sum(sym, addends);
170    if !giving.is_empty() {
171        let base = match to.first() {
172            Some(first) => format!("({sum}).add(&({}))", operand_decimal(sym, first)),
173            None => sum,
174        };
175        for g in giving {
176            emit_store(em, sym, g, &base, rounded);
177        }
178    } else {
179        for t in to {
180            emit_inplace(em, sym, t, "add", &sum, rounded);
181        }
182    }
183}
184
185fn emit_subtract(
186    em: &mut Emitter,
187    sym: &Symbols,
188    amounts: &[Operand],
189    from: &[Operand],
190    giving: &[Operand],
191    rounded: bool,
192) {
193    let sum = fold_sum(sym, amounts);
194    if !giving.is_empty() {
195        let minuend = from
196            .first()
197            .map(|f| operand_decimal(sym, f))
198            .unwrap_or_else(|| "Decimal::zero()".to_string());
199        let value = format!("({minuend}).sub(&({sum}))");
200        for g in giving {
201            emit_store(em, sym, g, &value, rounded);
202        }
203    } else {
204        for t in from {
205            emit_inplace(em, sym, t, "sub", &sum, rounded);
206        }
207    }
208}
209
210fn emit_multiply(
211    em: &mut Emitter,
212    sym: &Symbols,
213    left: &Operand,
214    by: &Operand,
215    giving: &[Operand],
216    rounded: bool,
217) {
218    let l = operand_decimal(sym, left);
219    if giving.is_empty() {
220        // `MULTIPLY a BY b` sin GIVING: b queda con a*b.
221        emit_inplace(em, sym, by, "mul", &l, rounded);
222    } else {
223        let value = format!("({l}).mul(&({}))", operand_decimal(sym, by));
224        for g in giving {
225            emit_store(em, sym, g, &value, rounded);
226        }
227    }
228}
229
230fn emit_divide(
231    em: &mut Emitter,
232    sym: &Symbols,
233    left: &Operand,
234    right: &Operand,
235    by_form: bool,
236    giving: &[Operand],
237    rounded: bool,
238) {
239    // `a BY b` → a/b; `a INTO b` → b/a.
240    let (num, den) = if by_form {
241        (operand_decimal(sym, left), operand_decimal(sym, right))
242    } else {
243        (operand_decimal(sym, right), operand_decimal(sym, left))
244    };
245    let div = |scale: u8| {
246        format!(
247            "({num}).div(&({den}), {scale}, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())"
248        )
249    };
250    if giving.is_empty() {
251        // `DIVIDE a INTO b` sin GIVING: b queda con b/a.
252        let value = div(target_scale(sym, right));
253        emit_store(em, sym, right, &value, rounded);
254    } else {
255        for g in giving {
256            let value = div(target_scale(sym, g));
257            emit_store(em, sym, g, &value, rounded);
258        }
259    }
260}
261
262/// Emite una operación aritmética en el lugar: `target = target <op> rhs`.
263fn emit_inplace(
264    em: &mut Emitter,
265    sym: &Symbols,
266    target: &Operand,
267    op: &str,
268    rhs: &str,
269    rounded: bool,
270) {
271    match field_ref(sym, target) {
272        Some((lref, FieldKind::Num { .. })) => {
273            let method = if rounded { "store_rounded" } else { "store" };
274            em.line(&format!("{lref}.{method}({lref}.value().{op}(&({rhs})));"));
275        }
276        _ => em.line("// charka: destino aritmético no resuelto"),
277    }
278}
279
280/// La escala de redondeo de un destino numérico (sus dígitos
281/// fraccionarios), o 4 por defecto.
282fn target_scale(sym: &Symbols, op: &Operand) -> u8 {
283    match field_ref(sym, op).map(|(_, k)| k) {
284        Some(FieldKind::Num { frac, .. }) => frac,
285        _ => 4,
286    }
287}
288
289/// Una expresión `usize` para el número de repeticiones de un `PERFORM`.
290fn count_expr(sym: &Symbols, op: &Operand) -> String {
291    match op {
292        Operand::Num(n) => match n.trim_start_matches('+').parse::<i128>() {
293            Ok(v) if v >= 0 => format!("{v}usize"),
294            _ => "0usize".to_string(),
295        },
296        _ => format!(
297            "(({}).rescale(0, Rounding::Truncate).mantissa().max(0) as usize)",
298            operand_decimal(sym, op)
299        ),
300    }
301}
302
303/// Emite un `EVALUATE` como una cadena `if / else if / else`.
304fn emit_evaluate(
305    em: &mut Emitter,
306    sym: &Symbols,
307    subject: &Operand,
308    whens: &[WhenBranch],
309    other: &[Stmt],
310) {
311    if whens.is_empty() {
312        if !other.is_empty() {
313            em.line("{");
314            em.indent();
315            emit_block(em, sym, other);
316            em.dedent();
317            em.line("}");
318        }
319        return;
320    }
321    for (i, branch) in whens.iter().enumerate() {
322        let cond = branch_condition(sym, subject, branch);
323        if i == 0 {
324            em.line(&format!("if {cond} {{"));
325        } else {
326            em.line(&format!("}} else if {cond} {{"));
327        }
328        em.indent();
329        emit_block(em, sym, &branch.body);
330        em.dedent();
331    }
332    if other.is_empty() {
333        em.line("}");
334    } else {
335        em.line("} else {");
336        em.indent();
337        emit_block(em, sym, other);
338        em.dedent();
339        em.line("}");
340    }
341}
342
343/// La condición de una rama `WHEN`: el sujeto igual a cualquiera de
344/// sus valores.
345fn branch_condition(sym: &Symbols, subject: &Operand, branch: &WhenBranch) -> String {
346    if branch.values.is_empty() {
347        return "false".to_string();
348    }
349    branch
350        .values
351        .iter()
352        .map(|v| {
353            format!(
354                "({})",
355                emit_cond(
356                    sym,
357                    &Cond::Compare {
358                        lhs: subject.clone(),
359                        op: CmpOp::Eq,
360                        rhs: v.clone(),
361                    },
362                )
363            )
364        })
365        .collect::<Vec<_>>()
366        .join(" || ")
367}
368
369fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
370    // Emite el "cuerpo": la llamada al párrafo o el bloque en línea.
371    let emit_body = |em: &mut Emitter, sym: &Symbols| match &p.target {
372        PerformTarget::Paragraph { name, thru } => {
373            let note = thru
374                .as_ref()
375                .map(|t| format!(" // charka: THRU {t} — rango no soportado"))
376                .unwrap_or_default();
377            em.line(&format!("self.{}();{note}", paragraph_method(name)));
378        }
379        PerformTarget::Inline(body) => emit_block(em, sym, body),
380    };
381
382    match &p.control {
383        PerformControl::Once => {
384            if matches!(p.target, PerformTarget::Inline(_)) {
385                em.line("{");
386                em.indent();
387                emit_body(em, sym);
388                em.dedent();
389                em.line("}");
390            } else {
391                emit_body(em, sym);
392            }
393        }
394        PerformControl::Times(n) => {
395            em.line(&format!("for _ in 0..{} {{", count_expr(sym, n)));
396            em.indent();
397            emit_body(em, sym);
398            em.dedent();
399            em.line("}");
400        }
401        PerformControl::Until(cond) => {
402            em.line(&format!("while !({}) {{", emit_cond(sym, cond)));
403            em.indent();
404            emit_body(em, sym);
405            em.dedent();
406            em.line("}");
407        }
408        PerformControl::Varying {
409            var,
410            from,
411            by,
412            until,
413        } => {
414            // var = from; mientras no se cumpla `until`: cuerpo; var += by.
415            let var_op = Operand::Data(var.clone());
416            emit_store(em, sym, &var_op, &operand_decimal(sym, from), false);
417            em.line(&format!("while !({}) {{", emit_cond(sym, until)));
418            em.indent();
419            emit_body(em, sym);
420            emit_inplace(em, sym, &var_op, "add", &operand_decimal(sym, by), false);
421            em.dedent();
422            em.line("}");
423        }
424    }
425}