⎇ 3902763daa94397756ff2394116daef97814090a
crates/modules/charka/charka-codegen/src/stmt.rs
Historia
Quién tocó qué
crudo
feat(charka): OCCURS — tablas y referencias con subíndice
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::{CmpOp, Cond, Operand, Perform, PerformControl, PerformTarget, Stmt, WhenBranch};
5
6use crate::emit::Emitter;
7use crate::expr::{
8 emit_cond, emit_expr, field_ref, figurative_fill, operand_decimal, operand_display, operand_str,
9};
10use crate::sym::{paragraph_method, FieldKind, Symbols};
11
12/// Emite un statement.
13pub(crate) fn emit_stmt(em: &mut Emitter, sym: &Symbols, stmt: &Stmt) {
14 match stmt {
15 Stmt::Move { from, to } => emit_move(em, sym, from, to),
16 Stmt::Display { items } => emit_display(em, sym, items),
17 Stmt::Accept { .. } => {
18 em.line("// charka: ACCEPT — entrada interactiva no soportada en v1");
19 }
20 Stmt::Compute {
21 targets,
22 rounded,
23 expr,
24 } => {
25 let value = emit_expr(sym, expr);
26 for t in targets {
27 emit_store(em, sym, t, &value, *rounded);
28 }
29 }
30 Stmt::Add {
31 addends,
32 to,
33 giving,
34 rounded,
35 } => emit_add(em, sym, addends, to, giving, *rounded),
36 Stmt::Subtract {
37 amounts,
38 from,
39 giving,
40 rounded,
41 } => emit_subtract(em, sym, amounts, from, giving, *rounded),
42 Stmt::Multiply {
43 left,
44 by,
45 giving,
46 rounded,
47 } => emit_multiply(em, sym, left, by, giving, *rounded),
48 Stmt::Divide {
49 left,
50 right,
51 by_form,
52 giving,
53 rounded,
54 } => emit_divide(em, sym, left, right, *by_form, giving, *rounded),
55 Stmt::If {
56 cond,
57 then_branch,
58 else_branch,
59 } => {
60 em.line(&format!("if {} {{", emit_cond(sym, cond)));
61 em.indent();
62 emit_block(em, sym, then_branch);
63 em.dedent();
64 if else_branch.is_empty() {
65 em.line("}");
66 } else {
67 em.line("} else {");
68 em.indent();
69 emit_block(em, sym, else_branch);
70 em.dedent();
71 em.line("}");
72 }
73 }
74 Stmt::Evaluate {
75 subject,
76 whens,
77 other,
78 } => emit_evaluate(em, sym, subject, whens, other),
79 Stmt::Perform(p) => emit_perform(em, sym, p),
80 Stmt::GoTo { target } => {
81 em.line(&format!(
82 "self.{}(); return; // charka: GO TO (aproximado)",
83 paragraph_method(target)
84 ));
85 }
86 Stmt::StopRun | Stmt::Goback => em.line("std::process::exit(0);"),
87 Stmt::Exit => em.line("return;"),
88 Stmt::Continue => em.line("// CONTINUE"),
89 Stmt::Unknown { verb, .. } => {
90 em.line(&format!("// charka: verbo no transpilado — {verb}"));
91 }
92 }
93}
94
95/// Emite una secuencia de statements (un cuerpo de bloque).
96fn emit_block(em: &mut Emitter, sym: &Symbols, stmts: &[Stmt]) {
97 for s in stmts {
98 emit_stmt(em, sym, s);
99 }
100}
101
102/// Almacena un valor `Decimal` (texto de expresión) en un destino —
103/// un dato escalar o un elemento de tabla.
104fn emit_store(em: &mut Emitter, sym: &Symbols, target: &Operand, value: &str, rounded: bool) {
105 match field_ref(sym, target) {
106 Some((lref, FieldKind::Num { .. })) => {
107 let method = if rounded { "store_rounded" } else { "store" };
108 em.line(&format!("{lref}.{method}({value});"));
109 }
110 Some((lref, FieldKind::Text { .. })) => {
111 em.line(&format!("{lref}.store(({value}).to_string().as_str());"));
112 }
113 None => em.line("// charka: destino no resuelto"),
114 }
115}
116
117fn emit_move(em: &mut Emitter, sym: &Symbols, from: &Operand, to: &[Operand]) {
118 for t in to {
119 match field_ref(sym, t) {
120 Some((lref, FieldKind::Num { .. })) => {
121 em.line(&format!("{lref}.store({});", operand_decimal(sym, from)));
122 }
123 Some((lref, FieldKind::Text { .. })) => {
124 if let Operand::Figurative(fig) = from {
125 em.line(&format!("{lref}.fill('{}');", figurative_fill(*fig)));
126 } else {
127 em.line(&format!("{lref}.store({});", operand_str(sym, from)));
128 }
129 }
130 None => em.line("// charka: destino MOVE no resuelto"),
131 }
132 }
133}
134
135fn emit_display(em: &mut Emitter, sym: &Symbols, items: &[Operand]) {
136 if items.is_empty() {
137 em.line("println!();");
138 return;
139 }
140 let placeholders = "{}".repeat(items.len());
141 let args: Vec<String> = items.iter().map(|o| operand_display(sym, o)).collect();
142 em.line(&format!(
143 "println!(\"{placeholders}\", {});",
144 args.join(", ")
145 ));
146}
147
148/// La suma de una lista de operandos, encadenando `.add`.
149fn fold_sum(sym: &Symbols, ops: &[Operand]) -> String {
150 let mut it = ops.iter();
151 let Some(first) = it.next() else {
152 return "Decimal::zero()".to_string();
153 };
154 let mut acc = operand_decimal(sym, first);
155 for o in it {
156 acc = format!("({acc}).add(&({}))", operand_decimal(sym, o));
157 }
158 acc
159}
160
161fn emit_add(
162 em: &mut Emitter,
163 sym: &Symbols,
164 addends: &[Operand],
165 to: &[Operand],
166 giving: &[Operand],
167 rounded: bool,
168) {
169 let sum = fold_sum(sym, addends);
170 if !giving.is_empty() {
171 let base = match to.first() {
172 Some(first) => format!("({sum}).add(&({}))", operand_decimal(sym, first)),
173 None => sum,
174 };
175 for g in giving {
176 emit_store(em, sym, g, &base, rounded);
177 }
178 } else {
179 for t in to {
180 emit_inplace(em, sym, t, "add", &sum, rounded);
181 }
182 }
183}
184
185fn emit_subtract(
186 em: &mut Emitter,
187 sym: &Symbols,
188 amounts: &[Operand],
189 from: &[Operand],
190 giving: &[Operand],
191 rounded: bool,
192) {
193 let sum = fold_sum(sym, amounts);
194 if !giving.is_empty() {
195 let minuend = from
196 .first()
197 .map(|f| operand_decimal(sym, f))
198 .unwrap_or_else(|| "Decimal::zero()".to_string());
199 let value = format!("({minuend}).sub(&({sum}))");
200 for g in giving {
201 emit_store(em, sym, g, &value, rounded);
202 }
203 } else {
204 for t in from {
205 emit_inplace(em, sym, t, "sub", &sum, rounded);
206 }
207 }
208}
209
210fn emit_multiply(
211 em: &mut Emitter,
212 sym: &Symbols,
213 left: &Operand,
214 by: &Operand,
215 giving: &[Operand],
216 rounded: bool,
217) {
218 let l = operand_decimal(sym, left);
219 if giving.is_empty() {
220 // `MULTIPLY a BY b` sin GIVING: b queda con a*b.
221 emit_inplace(em, sym, by, "mul", &l, rounded);
222 } else {
223 let value = format!("({l}).mul(&({}))", operand_decimal(sym, by));
224 for g in giving {
225 emit_store(em, sym, g, &value, rounded);
226 }
227 }
228}
229
230fn emit_divide(
231 em: &mut Emitter,
232 sym: &Symbols,
233 left: &Operand,
234 right: &Operand,
235 by_form: bool,
236 giving: &[Operand],
237 rounded: bool,
238) {
239 // `a BY b` → a/b; `a INTO b` → b/a.
240 let (num, den) = if by_form {
241 (operand_decimal(sym, left), operand_decimal(sym, right))
242 } else {
243 (operand_decimal(sym, right), operand_decimal(sym, left))
244 };
245 let div = |scale: u8| {
246 format!(
247 "({num}).div(&({den}), {scale}, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())"
248 )
249 };
250 if giving.is_empty() {
251 // `DIVIDE a INTO b` sin GIVING: b queda con b/a.
252 let value = div(target_scale(sym, right));
253 emit_store(em, sym, right, &value, rounded);
254 } else {
255 for g in giving {
256 let value = div(target_scale(sym, g));
257 emit_store(em, sym, g, &value, rounded);
258 }
259 }
260}
261
262/// Emite una operación aritmética en el lugar: `target = target <op> rhs`.
263fn emit_inplace(
264 em: &mut Emitter,
265 sym: &Symbols,
266 target: &Operand,
267 op: &str,
268 rhs: &str,
269 rounded: bool,
270) {
271 match field_ref(sym, target) {
272 Some((lref, FieldKind::Num { .. })) => {
273 let method = if rounded { "store_rounded" } else { "store" };
274 em.line(&format!("{lref}.{method}({lref}.value().{op}(&({rhs})));"));
275 }
276 _ => em.line("// charka: destino aritmético no resuelto"),
277 }
278}
279
280/// La escala de redondeo de un destino numérico (sus dígitos
281/// fraccionarios), o 4 por defecto.
282fn target_scale(sym: &Symbols, op: &Operand) -> u8 {
283 match field_ref(sym, op).map(|(_, k)| k) {
284 Some(FieldKind::Num { frac, .. }) => frac,
285 _ => 4,
286 }
287}
288
289/// Una expresión `usize` para el número de repeticiones de un `PERFORM`.
290fn count_expr(sym: &Symbols, op: &Operand) -> String {
291 match op {
292 Operand::Num(n) => match n.trim_start_matches('+').parse::<i128>() {
293 Ok(v) if v >= 0 => format!("{v}usize"),
294 _ => "0usize".to_string(),
295 },
296 _ => format!(
297 "(({}).rescale(0, Rounding::Truncate).mantissa().max(0) as usize)",
298 operand_decimal(sym, op)
299 ),
300 }
301}
302
303/// Emite un `EVALUATE` como una cadena `if / else if / else`.
304fn emit_evaluate(
305 em: &mut Emitter,
306 sym: &Symbols,
307 subject: &Operand,
308 whens: &[WhenBranch],
309 other: &[Stmt],
310) {
311 if whens.is_empty() {
312 if !other.is_empty() {
313 em.line("{");
314 em.indent();
315 emit_block(em, sym, other);
316 em.dedent();
317 em.line("}");
318 }
319 return;
320 }
321 for (i, branch) in whens.iter().enumerate() {
322 let cond = branch_condition(sym, subject, branch);
323 if i == 0 {
324 em.line(&format!("if {cond} {{"));
325 } else {
326 em.line(&format!("}} else if {cond} {{"));
327 }
328 em.indent();
329 emit_block(em, sym, &branch.body);
330 em.dedent();
331 }
332 if other.is_empty() {
333 em.line("}");
334 } else {
335 em.line("} else {");
336 em.indent();
337 emit_block(em, sym, other);
338 em.dedent();
339 em.line("}");
340 }
341}
342
343/// La condición de una rama `WHEN`: el sujeto igual a cualquiera de
344/// sus valores.
345fn branch_condition(sym: &Symbols, subject: &Operand, branch: &WhenBranch) -> String {
346 if branch.values.is_empty() {
347 return "false".to_string();
348 }
349 branch
350 .values
351 .iter()
352 .map(|v| {
353 format!(
354 "({})",
355 emit_cond(
356 sym,
357 &Cond::Compare {
358 lhs: subject.clone(),
359 op: CmpOp::Eq,
360 rhs: v.clone(),
361 },
362 )
363 )
364 })
365 .collect::<Vec<_>>()
366 .join(" || ")
367}
368
369fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
370 // Emite el "cuerpo": la llamada al párrafo o el bloque en línea.
371 let emit_body = |em: &mut Emitter, sym: &Symbols| match &p.target {
372 PerformTarget::Paragraph { name, thru } => {
373 let note = thru
374 .as_ref()
375 .map(|t| format!(" // charka: THRU {t} — rango no soportado"))
376 .unwrap_or_default();
377 em.line(&format!("self.{}();{note}", paragraph_method(name)));
378 }
379 PerformTarget::Inline(body) => emit_block(em, sym, body),
380 };
381
382 match &p.control {
383 PerformControl::Once => {
384 if matches!(p.target, PerformTarget::Inline(_)) {
385 em.line("{");
386 em.indent();
387 emit_body(em, sym);
388 em.dedent();
389 em.line("}");
390 } else {
391 emit_body(em, sym);
392 }
393 }
394 PerformControl::Times(n) => {
395 em.line(&format!("for _ in 0..{} {{", count_expr(sym, n)));
396 em.indent();
397 emit_body(em, sym);
398 em.dedent();
399 em.line("}");
400 }
401 PerformControl::Until(cond) => {
402 em.line(&format!("while !({}) {{", emit_cond(sym, cond)));
403 em.indent();
404 emit_body(em, sym);
405 em.dedent();
406 em.line("}");
407 }
408 PerformControl::Varying {
409 var,
410 from,
411 by,
412 until,
413 } => {
414 // var = from; mientras no se cumpla `until`: cuerpo; var += by.
415 let var_op = Operand::Data(var.clone());
416 emit_store(em, sym, &var_op, &operand_decimal(sym, from), false);
417 em.line(&format!("while !({}) {{", emit_cond(sym, until)));
418 em.indent();
419 emit_body(em, sym);
420 emit_inplace(em, sym, &var_op, "add", &operand_decimal(sym, by), false);
421 em.dedent();
422 em.line("}");
423 }
424 }
425}