gioser

tawasuyu/brahman

Rust 231TOML 70JSON 14HTML 12905 Sutichaykuna

⎇ 7867d6830e128b8a2afe95cb752093e12a184988 crates/modules/charka/charka-codegen/src/stmt.rs Rust · 531 siq'ikuna · 16.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(charka): EVALUATE TRUE y rangos WHEN ... THRU 7867d6830 · 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::{
5    CmpOp, Cond, InspectOp, Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch,
6    WhenTest,
7};
8
9use crate::emit::Emitter;
10use crate::expr::{
11    emit_cond, emit_expr, field_ref, figurative_fill, operand_decimal, operand_display, operand_str,
12};
13use crate::sym::{paragraph_method, FieldKind, Symbols};
14
15/// Emite un statement.
16pub(crate) fn emit_stmt(em: &mut Emitter, sym: &Symbols, stmt: &Stmt) {
17    match stmt {
18        Stmt::Move { from, to } => emit_move(em, sym, from, to),
19        Stmt::Display { items } => emit_display(em, sym, items),
20        Stmt::Accept { .. } => {
21            em.line("// charka: ACCEPT — entrada interactiva no soportada en v1");
22        }
23        Stmt::Compute {
24            targets,
25            rounded,
26            expr,
27        } => {
28            let value = emit_expr(sym, expr);
29            for t in targets {
30                emit_store(em, sym, t, &value, *rounded);
31            }
32        }
33        Stmt::Add {
34            addends,
35            to,
36            giving,
37            rounded,
38        } => emit_add(em, sym, addends, to, giving, *rounded),
39        Stmt::Subtract {
40            amounts,
41            from,
42            giving,
43            rounded,
44        } => emit_subtract(em, sym, amounts, from, giving, *rounded),
45        Stmt::Multiply {
46            left,
47            by,
48            giving,
49            rounded,
50        } => emit_multiply(em, sym, left, by, giving, *rounded),
51        Stmt::Divide {
52            left,
53            right,
54            by_form,
55            giving,
56            rounded,
57        } => emit_divide(em, sym, left, right, *by_form, giving, *rounded),
58        Stmt::If {
59            cond,
60            then_branch,
61            else_branch,
62        } => {
63            em.line(&format!("if {} {{", emit_cond(sym, cond)));
64            em.indent();
65            emit_block(em, sym, then_branch);
66            em.dedent();
67            if else_branch.is_empty() {
68                em.line("}");
69            } else {
70                em.line("} else {");
71                em.indent();
72                emit_block(em, sym, else_branch);
73                em.dedent();
74                em.line("}");
75            }
76        }
77        Stmt::Evaluate {
78            subject,
79            whens,
80            other,
81        } => emit_evaluate(em, sym, subject, whens, other),
82        Stmt::StringConcat { sources, into } => emit_string(em, sym, sources, into),
83        Stmt::Unstring {
84            source,
85            delimiter,
86            into,
87        } => emit_unstring(em, sym, source, delimiter, into),
88        Stmt::Inspect { target, op } => emit_inspect(em, sym, target, op),
89        Stmt::Perform(p) => emit_perform(em, sym, p),
90        Stmt::GoTo { target } => {
91            em.line(&format!(
92                "self.{}(); return; // charka: GO TO (aproximado)",
93                paragraph_method(target)
94            ));
95        }
96        Stmt::StopRun | Stmt::Goback => em.line("std::process::exit(0);"),
97        Stmt::Exit => em.line("return;"),
98        Stmt::Continue => em.line("// CONTINUE"),
99        Stmt::Unknown { verb, .. } => {
100            em.line(&format!("// charka: verbo no transpilado — {verb}"));
101        }
102    }
103}
104
105/// Emite una secuencia de statements (un cuerpo de bloque).
106fn emit_block(em: &mut Emitter, sym: &Symbols, stmts: &[Stmt]) {
107    for s in stmts {
108        emit_stmt(em, sym, s);
109    }
110}
111
112/// Almacena un valor `Decimal` (texto de expresión) en un destino —
113/// un dato escalar o un elemento de tabla.
114fn emit_store(em: &mut Emitter, sym: &Symbols, target: &Operand, value: &str, rounded: bool) {
115    match field_ref(sym, target) {
116        Some((lref, FieldKind::Num { .. })) => {
117            let method = if rounded { "store_rounded" } else { "store" };
118            em.line(&format!("{lref}.{method}({value});"));
119        }
120        Some((lref, FieldKind::Text { .. })) => {
121            em.line(&format!("{lref}.store(({value}).to_string().as_str());"));
122        }
123        None => em.line("// charka: destino no resuelto"),
124    }
125}
126
127fn emit_move(em: &mut Emitter, sym: &Symbols, from: &Operand, to: &[Operand]) {
128    for t in to {
129        match field_ref(sym, t) {
130            Some((lref, FieldKind::Num { .. })) => {
131                em.line(&format!("{lref}.store({});", operand_decimal(sym, from)));
132            }
133            Some((lref, FieldKind::Text { .. })) => {
134                if let Operand::Figurative(fig) = from {
135                    em.line(&format!("{lref}.fill('{}');", figurative_fill(*fig)));
136                } else {
137                    em.line(&format!("{lref}.store({});", operand_str(sym, from)));
138                }
139            }
140            None => em.line("// charka: destino MOVE no resuelto"),
141        }
142    }
143}
144
145fn emit_display(em: &mut Emitter, sym: &Symbols, items: &[Operand]) {
146    if items.is_empty() {
147        em.line("println!();");
148        return;
149    }
150    let placeholders = "{}".repeat(items.len());
151    let args: Vec<String> = items.iter().map(|o| operand_display(sym, o)).collect();
152    em.line(&format!(
153        "println!(\"{placeholders}\", {});",
154        args.join(", ")
155    ));
156}
157
158/// La suma de una lista de operandos, encadenando `.add`.
159fn fold_sum(sym: &Symbols, ops: &[Operand]) -> String {
160    let mut it = ops.iter();
161    let Some(first) = it.next() else {
162        return "Decimal::zero()".to_string();
163    };
164    let mut acc = operand_decimal(sym, first);
165    for o in it {
166        acc = format!("({acc}).add(&({}))", operand_decimal(sym, o));
167    }
168    acc
169}
170
171fn emit_add(
172    em: &mut Emitter,
173    sym: &Symbols,
174    addends: &[Operand],
175    to: &[Operand],
176    giving: &[Operand],
177    rounded: bool,
178) {
179    let sum = fold_sum(sym, addends);
180    if !giving.is_empty() {
181        let base = match to.first() {
182            Some(first) => format!("({sum}).add(&({}))", operand_decimal(sym, first)),
183            None => sum,
184        };
185        for g in giving {
186            emit_store(em, sym, g, &base, rounded);
187        }
188    } else {
189        for t in to {
190            emit_inplace(em, sym, t, "add", &sum, rounded);
191        }
192    }
193}
194
195fn emit_subtract(
196    em: &mut Emitter,
197    sym: &Symbols,
198    amounts: &[Operand],
199    from: &[Operand],
200    giving: &[Operand],
201    rounded: bool,
202) {
203    let sum = fold_sum(sym, amounts);
204    if !giving.is_empty() {
205        let minuend = from
206            .first()
207            .map(|f| operand_decimal(sym, f))
208            .unwrap_or_else(|| "Decimal::zero()".to_string());
209        let value = format!("({minuend}).sub(&({sum}))");
210        for g in giving {
211            emit_store(em, sym, g, &value, rounded);
212        }
213    } else {
214        for t in from {
215            emit_inplace(em, sym, t, "sub", &sum, rounded);
216        }
217    }
218}
219
220fn emit_multiply(
221    em: &mut Emitter,
222    sym: &Symbols,
223    left: &Operand,
224    by: &Operand,
225    giving: &[Operand],
226    rounded: bool,
227) {
228    let l = operand_decimal(sym, left);
229    if giving.is_empty() {
230        // `MULTIPLY a BY b` sin GIVING: b queda con a*b.
231        emit_inplace(em, sym, by, "mul", &l, rounded);
232    } else {
233        let value = format!("({l}).mul(&({}))", operand_decimal(sym, by));
234        for g in giving {
235            emit_store(em, sym, g, &value, rounded);
236        }
237    }
238}
239
240fn emit_divide(
241    em: &mut Emitter,
242    sym: &Symbols,
243    left: &Operand,
244    right: &Operand,
245    by_form: bool,
246    giving: &[Operand],
247    rounded: bool,
248) {
249    // `a BY b` → a/b; `a INTO b` → b/a.
250    let (num, den) = if by_form {
251        (operand_decimal(sym, left), operand_decimal(sym, right))
252    } else {
253        (operand_decimal(sym, right), operand_decimal(sym, left))
254    };
255    let div = |scale: u8| {
256        format!(
257            "({num}).div(&({den}), {scale}, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())"
258        )
259    };
260    if giving.is_empty() {
261        // `DIVIDE a INTO b` sin GIVING: b queda con b/a.
262        let value = div(target_scale(sym, right));
263        emit_store(em, sym, right, &value, rounded);
264    } else {
265        for g in giving {
266            let value = div(target_scale(sym, g));
267            emit_store(em, sym, g, &value, rounded);
268        }
269    }
270}
271
272/// Emite una operación aritmética en el lugar: `target = target <op> rhs`.
273fn emit_inplace(
274    em: &mut Emitter,
275    sym: &Symbols,
276    target: &Operand,
277    op: &str,
278    rhs: &str,
279    rounded: bool,
280) {
281    match field_ref(sym, target) {
282        Some((lref, FieldKind::Num { .. })) => {
283            let method = if rounded { "store_rounded" } else { "store" };
284            em.line(&format!("{lref}.{method}({lref}.value().{op}(&({rhs})));"));
285        }
286        _ => em.line("// charka: destino aritmético no resuelto"),
287    }
288}
289
290/// La escala de redondeo de un destino numérico (sus dígitos
291/// fraccionarios), o 4 por defecto.
292fn target_scale(sym: &Symbols, op: &Operand) -> u8 {
293    match field_ref(sym, op).map(|(_, k)| k) {
294        Some(FieldKind::Num { frac, .. }) => frac,
295        _ => 4,
296    }
297}
298
299/// Una expresión `usize` para el número de repeticiones de un `PERFORM`.
300fn count_expr(sym: &Symbols, op: &Operand) -> String {
301    match op {
302        Operand::Num(n) => match n.trim_start_matches('+').parse::<i128>() {
303            Ok(v) if v >= 0 => format!("{v}usize"),
304            _ => "0usize".to_string(),
305        },
306        _ => format!(
307            "(({}).rescale(0, Rounding::Truncate).mantissa().max(0) as usize)",
308            operand_decimal(sym, op)
309        ),
310    }
311}
312
313/// Emite un `EVALUATE` como una cadena `if / else if / else`.
314fn emit_evaluate(
315    em: &mut Emitter,
316    sym: &Symbols,
317    subject: &Operand,
318    whens: &[WhenBranch],
319    other: &[Stmt],
320) {
321    if whens.is_empty() {
322        if !other.is_empty() {
323            em.line("{");
324            em.indent();
325            emit_block(em, sym, other);
326            em.dedent();
327            em.line("}");
328        }
329        return;
330    }
331    for (i, branch) in whens.iter().enumerate() {
332        let cond = branch_condition(sym, subject, branch);
333        if i == 0 {
334            em.line(&format!("if {cond} {{"));
335        } else {
336            em.line(&format!("}} else if {cond} {{"));
337        }
338        em.indent();
339        emit_block(em, sym, &branch.body);
340        em.dedent();
341    }
342    if other.is_empty() {
343        em.line("}");
344    } else {
345        em.line("} else {");
346        em.indent();
347        emit_block(em, sym, other);
348        em.dedent();
349        em.line("}");
350    }
351}
352
353/// La condición de una rama `WHEN`: pasa si **alguna** de sus pruebas
354/// se cumple.
355fn branch_condition(sym: &Symbols, subject: &Operand, branch: &WhenBranch) -> String {
356    if branch.tests.is_empty() {
357        return "false".to_string();
358    }
359    branch
360        .tests
361        .iter()
362        .map(|t| format!("({})", test_condition(sym, subject, t)))
363        .collect::<Vec<_>>()
364        .join(" || ")
365}
366
367/// Traduce una prueba `WHEN` a una expresión Rust de tipo `bool`.
368fn test_condition(sym: &Symbols, subject: &Operand, test: &WhenTest) -> String {
369    let compare = |op: CmpOp, rhs: &Operand| {
370        emit_cond(
371            sym,
372            &Cond::Compare {
373                lhs: subject.clone(),
374                op,
375                rhs: rhs.clone(),
376            },
377        )
378    };
379    match test {
380        WhenTest::Value(v) => compare(CmpOp::Eq, v),
381        WhenTest::Range(lo, hi) => {
382            format!(
383                "({}) && ({})",
384                compare(CmpOp::Ge, lo),
385                compare(CmpOp::Le, hi)
386            )
387        }
388        WhenTest::Cond(cond) => emit_cond(sym, cond),
389    }
390}
391
392/// Almacena una expresión `&str` en un destino: directo si es de
393/// texto, parseado a `Decimal` si es numérico.
394fn emit_store_text(em: &mut Emitter, sym: &Symbols, target: &Operand, text: &str) {
395    match field_ref(sym, target) {
396        Some((lref, FieldKind::Text { .. })) => {
397            em.line(&format!("{lref}.store({text});"));
398        }
399        Some((lref, FieldKind::Num { .. })) => {
400            em.line(&format!(
401                "{lref}.store(Decimal::parse(({text}).trim())\
402                 .unwrap_or_else(|_| Decimal::zero()));"
403            ));
404        }
405        None => em.line("// charka: destino no resuelto"),
406    }
407}
408
409/// `STRING` — concatena el texto de las fuentes en el destino.
410fn emit_string(em: &mut Emitter, sym: &Symbols, sources: &[Operand], into: &Operand) {
411    let fmt = "{}".repeat(sources.len());
412    let args: Vec<String> = sources.iter().map(|s| operand_display(sym, s)).collect();
413    let concat = format!("&format!(\"{fmt}\", {})", args.join(", "));
414    emit_store_text(em, sym, into, &concat);
415}
416
417/// `UNSTRING` — parte el texto de la fuente y reparte los trozos.
418fn emit_unstring(
419    em: &mut Emitter,
420    sym: &Symbols,
421    source: &Operand,
422    delimiter: &Operand,
423    into: &[Operand],
424) {
425    em.line("{");
426    em.indent();
427    em.line(&format!(
428        "let __src = ({}).to_string();",
429        operand_display(sym, source)
430    ));
431    em.line(&format!(
432        "let __delim = ({}).to_string();",
433        operand_display(sym, delimiter)
434    ));
435    em.line("let mut __it = __src.split(__delim.as_str());");
436    for t in into {
437        emit_store_text(em, sym, t, "__it.next().unwrap_or(\"\")");
438    }
439    em.dedent();
440    em.line("}");
441}
442
443/// `INSPECT` — cuenta (`TALLYING`) o reemplaza (`REPLACING`).
444fn emit_inspect(em: &mut Emitter, sym: &Symbols, target: &Operand, op: &InspectOp) {
445    match op {
446        InspectOp::TallyingForAll { counter, search } => {
447            em.line("{");
448            em.indent();
449            em.line(&format!(
450                "let __n = ({}).matches({}).count() as i128;",
451                operand_display(sym, target),
452                operand_str(sym, search)
453            ));
454            match field_ref(sym, counter) {
455                Some((lref, FieldKind::Num { .. })) => em.line(&format!(
456                    "{lref}.store({lref}.value().add(&Decimal::from_integer(__n)));"
457                )),
458                _ => em.line("// charka: contador INSPECT no resuelto"),
459            }
460            em.dedent();
461            em.line("}");
462        }
463        InspectOp::ReplacingAll { from, to } => {
464            let replaced = format!(
465                "({}).replace({}, {})",
466                operand_display(sym, target),
467                operand_str(sym, from),
468                operand_str(sym, to)
469            );
470            emit_store_text(em, sym, target, &format!("{replaced}.as_str()"));
471        }
472    }
473}
474
475fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
476    // Emite el "cuerpo": la llamada al párrafo o el bloque en línea.
477    let emit_body = |em: &mut Emitter, sym: &Symbols| match &p.target {
478        PerformTarget::Paragraph { name, thru } => {
479            let note = thru
480                .as_ref()
481                .map(|t| format!(" // charka: THRU {t} — rango no soportado"))
482                .unwrap_or_default();
483            em.line(&format!("self.{}();{note}", paragraph_method(name)));
484        }
485        PerformTarget::Inline(body) => emit_block(em, sym, body),
486    };
487
488    match &p.control {
489        PerformControl::Once => {
490            if matches!(p.target, PerformTarget::Inline(_)) {
491                em.line("{");
492                em.indent();
493                emit_body(em, sym);
494                em.dedent();
495                em.line("}");
496            } else {
497                emit_body(em, sym);
498            }
499        }
500        PerformControl::Times(n) => {
501            em.line(&format!("for _ in 0..{} {{", count_expr(sym, n)));
502            em.indent();
503            emit_body(em, sym);
504            em.dedent();
505            em.line("}");
506        }
507        PerformControl::Until(cond) => {
508            em.line(&format!("while !({}) {{", emit_cond(sym, cond)));
509            em.indent();
510            emit_body(em, sym);
511            em.dedent();
512            em.line("}");
513        }
514        PerformControl::Varying {
515            var,
516            from,
517            by,
518            until,
519        } => {
520            // var = from; mientras no se cumpla `until`: cuerpo; var += by.
521            let var_op = Operand::Data(var.clone());
522            emit_store(em, sym, &var_op, &operand_decimal(sym, from), false);
523            em.line(&format!("while !({}) {{", emit_cond(sym, until)));
524            em.indent();
525            emit_body(em, sym);
526            emit_inplace(em, sym, &var_op, "add", &operand_decimal(sym, by), false);
527            em.dedent();
528            em.line("}");
529        }
530    }
531}