tawasuyu

tawasuyu/brahman

Rust 521TOML 202Markdown 57JSON 197505 Definiciones

⎇ b3278bdb0cccadf52a05c09cf2975027e498cbc4 crates/modules/charka/charka-codegen/src/stmt.rs Rust · 675 líneas · 21.2 KB Historia Quién tocó qué crudo

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