⎇ 7867d6830e128b8a2afe95cb752093e12a184988
crates/modules/charka/charka-codegen/src/stmt.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(charka): EVALUATE TRUE y rangos WHEN ... THRU
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}