tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Sutichaykuna

⎇ f250fd0765d4b7b91b1bf9f58efa17c429924abf crates/modules/charka/charka-codegen/src/stmt.rs Rust · 596 siq'ikuna · 18.6 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(charka): PERFORM ... THRU como rango real de párrafos f250fd076 · 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::Initialize { targets } => emit_initialize(em, sym, targets),
90        Stmt::SetTrue { conditions } => emit_set_true(em, sym, conditions),
91        Stmt::Perform(p) => emit_perform(em, sym, p),
92        Stmt::GoTo { target } => {
93            em.line(&format!(
94                "self.{}(); return; // charka: GO TO (aproximado)",
95                paragraph_method(target)
96            ));
97        }
98        Stmt::StopRun | Stmt::Goback => em.line("std::process::exit(0);"),
99        Stmt::Exit => em.line("return;"),
100        Stmt::Continue => em.line("// CONTINUE"),
101        Stmt::Unknown { verb, .. } => {
102            em.line(&format!("// charka: verbo no transpilado — {verb}"));
103        }
104    }
105}
106
107/// Emite una secuencia de statements (un cuerpo de bloque).
108fn emit_block(em: &mut Emitter, sym: &Symbols, stmts: &[Stmt]) {
109    for s in stmts {
110        emit_stmt(em, sym, s);
111    }
112}
113
114/// Almacena un valor `Decimal` (texto de expresión) en un destino —
115/// un dato escalar o un elemento de tabla.
116fn emit_store(em: &mut Emitter, sym: &Symbols, target: &Operand, value: &str, rounded: bool) {
117    match field_ref(sym, target) {
118        Some((lref, FieldKind::Num { .. })) => {
119            let method = if rounded { "store_rounded" } else { "store" };
120            em.line(&format!("{lref}.{method}({value});"));
121        }
122        Some((lref, FieldKind::Text { .. })) => {
123            em.line(&format!("{lref}.store(({value}).to_string().as_str());"));
124        }
125        None => em.line("// charka: destino no resuelto"),
126    }
127}
128
129fn emit_move(em: &mut Emitter, sym: &Symbols, from: &Operand, to: &[Operand]) {
130    for t in to {
131        match field_ref(sym, t) {
132            Some((lref, FieldKind::Num { .. })) => {
133                em.line(&format!("{lref}.store({});", operand_decimal(sym, from)));
134            }
135            Some((lref, FieldKind::Text { .. })) => {
136                if let Operand::Figurative(fig) = from {
137                    em.line(&format!("{lref}.fill('{}');", figurative_fill(*fig)));
138                } else {
139                    em.line(&format!("{lref}.store({});", operand_str(sym, from)));
140                }
141            }
142            None => em.line("// charka: destino MOVE no resuelto"),
143        }
144    }
145}
146
147fn emit_display(em: &mut Emitter, sym: &Symbols, items: &[Operand]) {
148    if items.is_empty() {
149        em.line("println!();");
150        return;
151    }
152    let placeholders = "{}".repeat(items.len());
153    let args: Vec<String> = items.iter().map(|o| operand_display(sym, o)).collect();
154    em.line(&format!(
155        "println!(\"{placeholders}\", {});",
156        args.join(", ")
157    ));
158}
159
160/// La suma de una lista de operandos, encadenando `.add`.
161fn fold_sum(sym: &Symbols, ops: &[Operand]) -> String {
162    let mut it = ops.iter();
163    let Some(first) = it.next() else {
164        return "Decimal::zero()".to_string();
165    };
166    let mut acc = operand_decimal(sym, first);
167    for o in it {
168        acc = format!("({acc}).add(&({}))", operand_decimal(sym, o));
169    }
170    acc
171}
172
173fn emit_add(
174    em: &mut Emitter,
175    sym: &Symbols,
176    addends: &[Operand],
177    to: &[Operand],
178    giving: &[Operand],
179    rounded: bool,
180) {
181    let sum = fold_sum(sym, addends);
182    if !giving.is_empty() {
183        let base = match to.first() {
184            Some(first) => format!("({sum}).add(&({}))", operand_decimal(sym, first)),
185            None => sum,
186        };
187        for g in giving {
188            emit_store(em, sym, g, &base, rounded);
189        }
190    } else {
191        for t in to {
192            emit_inplace(em, sym, t, "add", &sum, rounded);
193        }
194    }
195}
196
197fn emit_subtract(
198    em: &mut Emitter,
199    sym: &Symbols,
200    amounts: &[Operand],
201    from: &[Operand],
202    giving: &[Operand],
203    rounded: bool,
204) {
205    let sum = fold_sum(sym, amounts);
206    if !giving.is_empty() {
207        let minuend = from
208            .first()
209            .map(|f| operand_decimal(sym, f))
210            .unwrap_or_else(|| "Decimal::zero()".to_string());
211        let value = format!("({minuend}).sub(&({sum}))");
212        for g in giving {
213            emit_store(em, sym, g, &value, rounded);
214        }
215    } else {
216        for t in from {
217            emit_inplace(em, sym, t, "sub", &sum, rounded);
218        }
219    }
220}
221
222fn emit_multiply(
223    em: &mut Emitter,
224    sym: &Symbols,
225    left: &Operand,
226    by: &Operand,
227    giving: &[Operand],
228    rounded: bool,
229) {
230    let l = operand_decimal(sym, left);
231    if giving.is_empty() {
232        // `MULTIPLY a BY b` sin GIVING: b queda con a*b.
233        emit_inplace(em, sym, by, "mul", &l, rounded);
234    } else {
235        let value = format!("({l}).mul(&({}))", operand_decimal(sym, by));
236        for g in giving {
237            emit_store(em, sym, g, &value, rounded);
238        }
239    }
240}
241
242fn emit_divide(
243    em: &mut Emitter,
244    sym: &Symbols,
245    left: &Operand,
246    right: &Operand,
247    by_form: bool,
248    giving: &[Operand],
249    rounded: bool,
250) {
251    // `a BY b` → a/b; `a INTO b` → b/a.
252    let (num, den) = if by_form {
253        (operand_decimal(sym, left), operand_decimal(sym, right))
254    } else {
255        (operand_decimal(sym, right), operand_decimal(sym, left))
256    };
257    let div = |scale: u8| {
258        format!(
259            "({num}).div(&({den}), {scale}, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())"
260        )
261    };
262    if giving.is_empty() {
263        // `DIVIDE a INTO b` sin GIVING: b queda con b/a.
264        let value = div(target_scale(sym, right));
265        emit_store(em, sym, right, &value, rounded);
266    } else {
267        for g in giving {
268            let value = div(target_scale(sym, g));
269            emit_store(em, sym, g, &value, rounded);
270        }
271    }
272}
273
274/// Emite una operación aritmética en el lugar: `target = target <op> rhs`.
275fn emit_inplace(
276    em: &mut Emitter,
277    sym: &Symbols,
278    target: &Operand,
279    op: &str,
280    rhs: &str,
281    rounded: bool,
282) {
283    match field_ref(sym, target) {
284        Some((lref, FieldKind::Num { .. })) => {
285            let method = if rounded { "store_rounded" } else { "store" };
286            em.line(&format!("{lref}.{method}({lref}.value().{op}(&({rhs})));"));
287        }
288        _ => em.line("// charka: destino aritmético no resuelto"),
289    }
290}
291
292/// La escala de redondeo de un destino numérico (sus dígitos
293/// fraccionarios), o 4 por defecto.
294fn target_scale(sym: &Symbols, op: &Operand) -> u8 {
295    match field_ref(sym, op).map(|(_, k)| k) {
296        Some(FieldKind::Num { frac, .. }) => frac,
297        _ => 4,
298    }
299}
300
301/// Una expresión `usize` para el número de repeticiones de un `PERFORM`.
302fn count_expr(sym: &Symbols, op: &Operand) -> String {
303    match op {
304        Operand::Num(n) => match n.trim_start_matches('+').parse::<i128>() {
305            Ok(v) if v >= 0 => format!("{v}usize"),
306            _ => "0usize".to_string(),
307        },
308        _ => format!(
309            "(({}).rescale(0, Rounding::Truncate).mantissa().max(0) as usize)",
310            operand_decimal(sym, op)
311        ),
312    }
313}
314
315/// Emite un `EVALUATE` como una cadena `if / else if / else`.
316fn emit_evaluate(
317    em: &mut Emitter,
318    sym: &Symbols,
319    subject: &Operand,
320    whens: &[WhenBranch],
321    other: &[Stmt],
322) {
323    if whens.is_empty() {
324        if !other.is_empty() {
325            em.line("{");
326            em.indent();
327            emit_block(em, sym, other);
328            em.dedent();
329            em.line("}");
330        }
331        return;
332    }
333    for (i, branch) in whens.iter().enumerate() {
334        let cond = branch_condition(sym, subject, branch);
335        if i == 0 {
336            em.line(&format!("if {cond} {{"));
337        } else {
338            em.line(&format!("}} else if {cond} {{"));
339        }
340        em.indent();
341        emit_block(em, sym, &branch.body);
342        em.dedent();
343    }
344    if other.is_empty() {
345        em.line("}");
346    } else {
347        em.line("} else {");
348        em.indent();
349        emit_block(em, sym, other);
350        em.dedent();
351        em.line("}");
352    }
353}
354
355/// La condición de una rama `WHEN`: pasa si **alguna** de sus pruebas
356/// se cumple.
357fn branch_condition(sym: &Symbols, subject: &Operand, branch: &WhenBranch) -> String {
358    if branch.tests.is_empty() {
359        return "false".to_string();
360    }
361    branch
362        .tests
363        .iter()
364        .map(|t| format!("({})", test_condition(sym, subject, t)))
365        .collect::<Vec<_>>()
366        .join(" || ")
367}
368
369/// Traduce una prueba `WHEN` a una expresión Rust de tipo `bool`.
370fn test_condition(sym: &Symbols, subject: &Operand, test: &WhenTest) -> String {
371    let compare = |op: CmpOp, rhs: &Operand| {
372        emit_cond(
373            sym,
374            &Cond::Compare {
375                lhs: subject.clone(),
376                op,
377                rhs: rhs.clone(),
378            },
379        )
380    };
381    match test {
382        WhenTest::Value(v) => compare(CmpOp::Eq, v),
383        WhenTest::Range(lo, hi) => {
384            format!(
385                "({}) && ({})",
386                compare(CmpOp::Ge, lo),
387                compare(CmpOp::Le, hi)
388            )
389        }
390        WhenTest::Cond(cond) => emit_cond(sym, cond),
391    }
392}
393
394/// Almacena una expresión `&str` en un destino: directo si es de
395/// texto, parseado a `Decimal` si es numérico.
396fn emit_store_text(em: &mut Emitter, sym: &Symbols, target: &Operand, text: &str) {
397    match field_ref(sym, target) {
398        Some((lref, FieldKind::Text { .. })) => {
399            em.line(&format!("{lref}.store({text});"));
400        }
401        Some((lref, FieldKind::Num { .. })) => {
402            em.line(&format!(
403                "{lref}.store(Decimal::parse(({text}).trim())\
404                 .unwrap_or_else(|_| Decimal::zero()));"
405            ));
406        }
407        None => em.line("// charka: destino no resuelto"),
408    }
409}
410
411/// `STRING` — concatena el texto de las fuentes en el destino.
412fn emit_string(em: &mut Emitter, sym: &Symbols, sources: &[Operand], into: &Operand) {
413    let fmt = "{}".repeat(sources.len());
414    let args: Vec<String> = sources.iter().map(|s| operand_display(sym, s)).collect();
415    let concat = format!("&format!(\"{fmt}\", {})", args.join(", "));
416    emit_store_text(em, sym, into, &concat);
417}
418
419/// `UNSTRING` — parte el texto de la fuente y reparte los trozos.
420fn emit_unstring(
421    em: &mut Emitter,
422    sym: &Symbols,
423    source: &Operand,
424    delimiter: &Operand,
425    into: &[Operand],
426) {
427    em.line("{");
428    em.indent();
429    em.line(&format!(
430        "let __src = ({}).to_string();",
431        operand_display(sym, source)
432    ));
433    em.line(&format!(
434        "let __delim = ({}).to_string();",
435        operand_display(sym, delimiter)
436    ));
437    em.line("let mut __it = __src.split(__delim.as_str());");
438    for t in into {
439        emit_store_text(em, sym, t, "__it.next().unwrap_or(\"\")");
440    }
441    em.dedent();
442    em.line("}");
443}
444
445/// `INSPECT` — cuenta (`TALLYING`) o reemplaza (`REPLACING`).
446fn emit_inspect(em: &mut Emitter, sym: &Symbols, target: &Operand, op: &InspectOp) {
447    match op {
448        InspectOp::TallyingForAll { counter, search } => {
449            em.line("{");
450            em.indent();
451            em.line(&format!(
452                "let __n = ({}).matches({}).count() as i128;",
453                operand_display(sym, target),
454                operand_str(sym, search)
455            ));
456            match field_ref(sym, counter) {
457                Some((lref, FieldKind::Num { .. })) => em.line(&format!(
458                    "{lref}.store({lref}.value().add(&Decimal::from_integer(__n)));"
459                )),
460                _ => em.line("// charka: contador INSPECT no resuelto"),
461            }
462            em.dedent();
463            em.line("}");
464        }
465        InspectOp::ReplacingAll { from, to } => {
466            let replaced = format!(
467                "({}).replace({}, {})",
468                operand_display(sym, target),
469                operand_str(sym, from),
470                operand_str(sym, to)
471            );
472            emit_store_text(em, sym, target, &format!("{replaced}.as_str()"));
473        }
474    }
475}
476
477/// `INITIALIZE` — pone cada destino (o los miembros de un grupo) en su
478/// valor por defecto.
479fn emit_initialize(em: &mut Emitter, sym: &Symbols, targets: &[Operand]) {
480    for t in targets {
481        match t {
482            Operand::Data(name) => match sym.group(name) {
483                Some(members) => {
484                    for m in members {
485                        emit_reset(em, sym, m);
486                    }
487                }
488                None => emit_reset(em, sym, name),
489            },
490            Operand::Indexed { .. } => emit_reset_element(em, sym, t),
491            _ => {}
492        }
493    }
494}
495
496/// `SET cond... TO TRUE` — asigna a cada dato padre el valor que hace
497/// verdadero su nombre de condición (nivel 88).
498fn emit_set_true(em: &mut Emitter, sym: &Symbols, conditions: &[String]) {
499    for name in conditions {
500        match sym.condition(name) {
501            Some(cn) => {
502                let target = Operand::Data(cn.parent.clone());
503                let value = cn.value.clone();
504                emit_move(em, sym, &value, std::slice::from_ref(&target));
505            }
506            None => em.line(&format!("// charka: condición 88 no resuelta — {name}")),
507        }
508    }
509}
510
511/// Resetea un campo completo (escalar o tabla entera).
512fn emit_reset(em: &mut Emitter, sym: &Symbols, name: &str) {
513    let Some(f) = sym.lookup(name) else {
514        em.line(&format!("// charka: INITIALIZE de {name} no resuelto"));
515        return;
516    };
517    let reset = match f.kind {
518        FieldKind::Num { .. } => "store(Decimal::zero())",
519        FieldKind::Text { .. } => "fill(' ')",
520    };
521    match f.occurs {
522        None => em.line(&format!("self.{}.{reset};", f.ident)),
523        Some(_) => {
524            em.line(&format!("for __e in self.{}.iter_mut() {{", f.ident));
525            em.indent();
526            em.line(&format!("__e.{reset};"));
527            em.dedent();
528            em.line("}");
529        }
530    }
531}
532
533/// Resetea un solo elemento de tabla (`INITIALIZE ELEM(I)`).
534fn emit_reset_element(em: &mut Emitter, sym: &Symbols, op: &Operand) {
535    match field_ref(sym, op) {
536        Some((lref, FieldKind::Num { .. })) => em.line(&format!("{lref}.store(Decimal::zero());")),
537        Some((lref, FieldKind::Text { .. })) => em.line(&format!("{lref}.fill(' ');")),
538        None => em.line("// charka: INITIALIZE no resuelto"),
539    }
540}
541
542fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
543    // Emite el "cuerpo": la llamada al párrafo o el bloque en línea.
544    let emit_body = |em: &mut Emitter, sym: &Symbols| match &p.target {
545        PerformTarget::Paragraph { name, thru } => {
546            for m in sym.paragraph_range(name, thru.as_deref()) {
547                em.line(&format!("self.{m}();"));
548            }
549        }
550        PerformTarget::Inline(body) => emit_block(em, sym, body),
551    };
552
553    match &p.control {
554        PerformControl::Once => {
555            if matches!(p.target, PerformTarget::Inline(_)) {
556                em.line("{");
557                em.indent();
558                emit_body(em, sym);
559                em.dedent();
560                em.line("}");
561            } else {
562                emit_body(em, sym);
563            }
564        }
565        PerformControl::Times(n) => {
566            em.line(&format!("for _ in 0..{} {{", count_expr(sym, n)));
567            em.indent();
568            emit_body(em, sym);
569            em.dedent();
570            em.line("}");
571        }
572        PerformControl::Until(cond) => {
573            em.line(&format!("while !({}) {{", emit_cond(sym, cond)));
574            em.indent();
575            emit_body(em, sym);
576            em.dedent();
577            em.line("}");
578        }
579        PerformControl::Varying {
580            var,
581            from,
582            by,
583            until,
584        } => {
585            // var = from; mientras no se cumpla `until`: cuerpo; var += by.
586            let var_op = Operand::Data(var.clone());
587            emit_store(em, sym, &var_op, &operand_decimal(sym, from), false);
588            em.line(&format!("while !({}) {{", emit_cond(sym, until)));
589            em.indent();
590            emit_body(em, sym);
591            emit_inplace(em, sym, &var_op, "add", &operand_decimal(sym, by), false);
592            em.dedent();
593            em.line("}");
594        }
595    }
596}