⎇ e52b3fb572cec07d35c55d87519ecc8e0bd0505a
crates/modules/charka/charka-codegen/src/stmt.rs
History
Who touched what
raw
feat(charka): charka-codegen — emisión de Rust desde el IR
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::{Operand, Perform, PerformControl, PerformTarget, Stmt};
5
6use crate::emit::Emitter;
7use crate::expr::{
8 emit_cond, emit_expr, 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 { into } => {
18 em.line(&format!(
19 "// charka: ACCEPT {into} — entrada interactiva no soportada en v1"
20 ));
21 }
22 Stmt::Compute {
23 targets,
24 rounded,
25 expr,
26 } => {
27 let value = emit_expr(sym, expr);
28 for t in targets {
29 emit_store(em, sym, t, &value, *rounded);
30 }
31 }
32 Stmt::Add {
33 addends,
34 to,
35 giving,
36 rounded,
37 } => emit_add(em, sym, addends, to, giving, *rounded),
38 Stmt::Subtract {
39 amounts,
40 from,
41 giving,
42 rounded,
43 } => emit_subtract(em, sym, amounts, from, giving, *rounded),
44 Stmt::Multiply {
45 left,
46 by,
47 giving,
48 rounded,
49 } => emit_multiply(em, sym, left, by, giving, *rounded),
50 Stmt::Divide {
51 left,
52 right,
53 by_form,
54 giving,
55 rounded,
56 } => emit_divide(em, sym, left, right, *by_form, giving, *rounded),
57 Stmt::If {
58 cond,
59 then_branch,
60 else_branch,
61 } => {
62 em.line(&format!("if {} {{", emit_cond(sym, cond)));
63 em.indent();
64 emit_block(em, sym, then_branch);
65 em.dedent();
66 if else_branch.is_empty() {
67 em.line("}");
68 } else {
69 em.line("} else {");
70 em.indent();
71 emit_block(em, sym, else_branch);
72 em.dedent();
73 em.line("}");
74 }
75 }
76 Stmt::Perform(p) => emit_perform(em, sym, p),
77 Stmt::GoTo { target } => {
78 em.line(&format!(
79 "self.{}(); return; // charka: GO TO (aproximado)",
80 paragraph_method(target)
81 ));
82 }
83 Stmt::StopRun | Stmt::Goback => em.line("std::process::exit(0);"),
84 Stmt::Exit => em.line("return;"),
85 Stmt::Continue => em.line("// CONTINUE"),
86 Stmt::Unknown { verb, .. } => {
87 em.line(&format!("// charka: verbo no transpilado — {verb}"));
88 }
89 }
90}
91
92/// Emite una secuencia de statements (un cuerpo de bloque).
93fn emit_block(em: &mut Emitter, sym: &Symbols, stmts: &[Stmt]) {
94 for s in stmts {
95 emit_stmt(em, sym, s);
96 }
97}
98
99/// Almacena un valor `Decimal` (texto de expresión) en un campo.
100fn emit_store(em: &mut Emitter, sym: &Symbols, name: &str, value: &str, rounded: bool) {
101 match sym.lookup(name) {
102 Some(f) => match f.kind {
103 FieldKind::Num { .. } => {
104 let method = if rounded { "store_rounded" } else { "store" };
105 em.line(&format!("self.{}.{method}({value});", f.ident));
106 }
107 FieldKind::Text { .. } => {
108 em.line(&format!(
109 "self.{}.store(({value}).to_string().as_str());",
110 f.ident
111 ));
112 }
113 },
114 None => em.line(&format!("// charka: destino no resuelto — {name}")),
115 }
116}
117
118fn emit_move(em: &mut Emitter, sym: &Symbols, from: &Operand, to: &[String]) {
119 for t in to {
120 match sym.lookup(t) {
121 Some(f) => match f.kind {
122 FieldKind::Num { .. } => {
123 em.line(&format!(
124 "self.{}.store({});",
125 f.ident,
126 operand_decimal(sym, from)
127 ));
128 }
129 FieldKind::Text { .. } => {
130 if let Operand::Figurative(fig) = from {
131 em.line(&format!(
132 "self.{}.fill('{}');",
133 f.ident,
134 figurative_fill(*fig)
135 ));
136 } else {
137 em.line(&format!(
138 "self.{}.store({});",
139 f.ident,
140 operand_str(sym, from)
141 ));
142 }
143 }
144 },
145 None => em.line(&format!("// charka: destino MOVE no resuelto — {t}")),
146 }
147 }
148}
149
150fn emit_display(em: &mut Emitter, sym: &Symbols, items: &[Operand]) {
151 if items.is_empty() {
152 em.line("println!();");
153 return;
154 }
155 let placeholders = "{}".repeat(items.len());
156 let args: Vec<String> = items.iter().map(|o| operand_display(sym, o)).collect();
157 em.line(&format!(
158 "println!(\"{placeholders}\", {});",
159 args.join(", ")
160 ));
161}
162
163/// La suma de una lista de operandos, encadenando `.add`.
164fn fold_sum(sym: &Symbols, ops: &[Operand]) -> String {
165 let mut it = ops.iter();
166 let Some(first) = it.next() else {
167 return "Decimal::zero()".to_string();
168 };
169 let mut acc = operand_decimal(sym, first);
170 for o in it {
171 acc = format!("({acc}).add(&({}))", operand_decimal(sym, o));
172 }
173 acc
174}
175
176fn emit_add(
177 em: &mut Emitter,
178 sym: &Symbols,
179 addends: &[Operand],
180 to: &[String],
181 giving: &[String],
182 rounded: bool,
183) {
184 let sum = fold_sum(sym, addends);
185 if !giving.is_empty() {
186 let base = match to.first() {
187 Some(first) => format!(
188 "({sum}).add(&({}))",
189 operand_decimal(sym, &Operand::Data(first.clone()))
190 ),
191 None => sum,
192 };
193 for g in giving {
194 emit_store(em, sym, g, &base, rounded);
195 }
196 } else {
197 for t in to {
198 emit_inplace(em, sym, t, "add", &sum, rounded);
199 }
200 }
201}
202
203fn emit_subtract(
204 em: &mut Emitter,
205 sym: &Symbols,
206 amounts: &[Operand],
207 from: &[String],
208 giving: &[String],
209 rounded: bool,
210) {
211 let sum = fold_sum(sym, amounts);
212 if !giving.is_empty() {
213 let minuend = from
214 .first()
215 .map(|f| operand_decimal(sym, &Operand::Data(f.clone())))
216 .unwrap_or_else(|| "Decimal::zero()".to_string());
217 let value = format!("({minuend}).sub(&({sum}))");
218 for g in giving {
219 emit_store(em, sym, g, &value, rounded);
220 }
221 } else {
222 for t in from {
223 emit_inplace(em, sym, t, "sub", &sum, rounded);
224 }
225 }
226}
227
228fn emit_multiply(
229 em: &mut Emitter,
230 sym: &Symbols,
231 left: &Operand,
232 by: &Operand,
233 giving: &[String],
234 rounded: bool,
235) {
236 let l = operand_decimal(sym, left);
237 if !giving.is_empty() {
238 let value = format!("({l}).mul(&({}))", operand_decimal(sym, by));
239 for g in giving {
240 emit_store(em, sym, g, &value, rounded);
241 }
242 } else if let Operand::Data(name) = by {
243 // `MULTIPLY a BY b` sin GIVING: b queda con a*b.
244 emit_inplace(em, sym, name, "mul", &l, rounded);
245 } else {
246 em.line("// charka: MULTIPLY sin destino claro");
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: &[String],
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 if !giving.is_empty() {
266 for g in giving {
267 let value = format!(
268 "({num}).div(&({den}), {}, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())",
269 target_scale(sym, g)
270 );
271 emit_store(em, sym, g, &value, rounded);
272 }
273 } else if let Operand::Data(name) = right {
274 // `DIVIDE a INTO b` sin GIVING: b queda con b/a.
275 let value = format!(
276 "({num}).div(&({den}), {}, Rounding::Truncate).unwrap_or_else(|_| Decimal::zero())",
277 target_scale(sym, name)
278 );
279 emit_store(em, sym, name, &value, rounded);
280 } else {
281 em.line("// charka: DIVIDE sin destino claro");
282 }
283}
284
285/// Emite una operación aritmética en el lugar: `t = t <op> rhs`.
286fn emit_inplace(em: &mut Emitter, sym: &Symbols, name: &str, op: &str, rhs: &str, rounded: bool) {
287 match sym.lookup(name) {
288 Some(f) if matches!(f.kind, FieldKind::Num { .. }) => {
289 let method = if rounded { "store_rounded" } else { "store" };
290 em.line(&format!(
291 "self.{0}.{method}(self.{0}.value().{op}(&({rhs})));",
292 f.ident
293 ));
294 }
295 _ => em.line(&format!(
296 "// charka: destino aritmético no resuelto — {name}"
297 )),
298 }
299}
300
301/// La escala de redondeo de un destino numérico (sus dígitos
302/// fraccionarios), o 4 por defecto.
303fn target_scale(sym: &Symbols, name: &str) -> u8 {
304 match sym.lookup(name).map(|f| &f.kind) {
305 Some(FieldKind::Num { frac, .. }) => *frac,
306 _ => 4,
307 }
308}
309
310/// Una expresión `usize` para el número de repeticiones de un `PERFORM`.
311fn count_expr(sym: &Symbols, op: &Operand) -> String {
312 match op {
313 Operand::Num(n) => match n.trim_start_matches('+').parse::<i128>() {
314 Ok(v) if v >= 0 => format!("{v}usize"),
315 _ => "0usize".to_string(),
316 },
317 _ => format!(
318 "(({}).rescale(0, Rounding::Truncate).mantissa().max(0) as usize)",
319 operand_decimal(sym, op)
320 ),
321 }
322}
323
324fn emit_perform(em: &mut Emitter, sym: &Symbols, p: &Perform) {
325 // Emite el "cuerpo": la llamada al párrafo o el bloque en línea.
326 let emit_body = |em: &mut Emitter, sym: &Symbols| match &p.target {
327 PerformTarget::Paragraph { name, thru } => {
328 let note = thru
329 .as_ref()
330 .map(|t| format!(" // charka: THRU {t} — rango no soportado"))
331 .unwrap_or_default();
332 em.line(&format!("self.{}();{note}", paragraph_method(name)));
333 }
334 PerformTarget::Inline(body) => emit_block(em, sym, body),
335 };
336
337 match &p.control {
338 PerformControl::Once => {
339 if matches!(p.target, PerformTarget::Inline(_)) {
340 em.line("{");
341 em.indent();
342 emit_body(em, sym);
343 em.dedent();
344 em.line("}");
345 } else {
346 emit_body(em, sym);
347 }
348 }
349 PerformControl::Times(n) => {
350 em.line(&format!("for _ in 0..{} {{", count_expr(sym, n)));
351 em.indent();
352 emit_body(em, sym);
353 em.dedent();
354 em.line("}");
355 }
356 PerformControl::Until(cond) => {
357 em.line(&format!("while !({}) {{", emit_cond(sym, cond)));
358 em.indent();
359 emit_body(em, sym);
360 em.dedent();
361 em.line("}");
362 }
363 }
364}