⎇ b3278bdb0cccadf52a05c09cf2975027e498cbc4
crates/modules/charka/charka-codegen/src/stmt.rs
Historia
Quién tocó qué
crudo
feat(charka): E/S de ficheros — SELECT/FD/OPEN/READ/WRITE/CLOSE
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}