⎇ f250fd0765d4b7b91b1bf9f58efa17c429924abf
crates/modules/charka/charka-codegen/src/stmt.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(charka): PERFORM ... THRU como rango real de párrafos
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}