⎇ 82ba0b7a1a04b7ff2d7e2175ac74766da0d16066
crates/modules/charka/charka-shadow/src/interp.rs
Historia
Quién tocó qué
crudo
feat(charka): SET ... TO TRUE — escribir nombres de condición (88)
1//! El intérprete del IR: la ejecución «sombra» del programa COBOL.
2//!
3//! Ejecuta el [`Ir`] directamente sobre los tipos de `charka-runtime`,
4//! sin compilar nada. Es una segunda ruta de ejecución, independiente
5//! del código que emite `charka-codegen` — eso es lo que lo hace un
6//! validador: si el intérprete y el transpilado divergen, hay un bug.
7
8use std::collections::HashMap;
9
10use charka_ir::{
11 BinOp, CmpOp, Cond, ConditionName, Expr, Figurative, InspectOp, Ir, Operand, Perform,
12 PerformControl, PerformTarget, Stmt, WhenTest,
13};
14use charka_runtime::{cobol_text_cmp, Decimal, Num, Rounding, Text};
15
16use crate::field::{build_fields, Cell};
17
18/// Tope de pasos: corta los bucles que no terminan (un `PERFORM UNTIL`
19/// con una condición que nunca se cumple) en vez de colgarse.
20const STEP_BUDGET: u64 = 5_000_000;
21
22/// Escala intermedia de la división dentro de una expresión.
23const DIV_SCALE: u8 = 9;
24
25/// El resultado de ejecutar un statement: cómo sigue el control.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27enum Flow {
28 /// Sigue con el statement siguiente.
29 Normal,
30 /// Sale del párrafo actual (`EXIT`).
31 Exit,
32 /// Termina el programa (`STOP RUN`, `GOBACK`, tope de pasos).
33 Stop,
34}
35
36/// La máquina sombra: el estado y el motor de ejecución.
37pub(crate) struct Machine<'a> {
38 ir: &'a Ir,
39 fields: HashMap<String, Cell>,
40 para_index: HashMap<String, usize>,
41 conditions: HashMap<String, ConditionName>,
42 pub output: Vec<String>,
43 budget: u64,
44 pub step_limit_hit: bool,
45 pub stopped: bool,
46}
47
48impl<'a> Machine<'a> {
49 /// Prepara la máquina: aplana los datos e indexa los párrafos.
50 pub(crate) fn new(ir: &'a Ir) -> Self {
51 let mut para_index = HashMap::new();
52 for (i, proc) in ir.procedures.iter().enumerate() {
53 para_index.entry(proc.name.to_uppercase()).or_insert(i);
54 }
55 let conditions = ir
56 .model
57 .conditions
58 .iter()
59 .map(|c| (c.name.clone(), c.clone()))
60 .collect();
61 Self {
62 ir,
63 fields: build_fields(&ir.model),
64 para_index,
65 conditions,
66 output: Vec::new(),
67 budget: STEP_BUDGET,
68 step_limit_hit: false,
69 stopped: false,
70 }
71 }
72
73 /// Corre el programa: encadena los párrafos en orden (el «caer» de
74 /// COBOL) hasta un `STOP RUN` o el final.
75 pub(crate) fn run(&mut self) {
76 let ir = self.ir;
77 for i in 0..ir.procedures.len() {
78 if let Flow::Stop = self.exec_block(&ir.procedures[i].body) {
79 self.stopped = true;
80 break;
81 }
82 }
83 }
84
85 // ── Ejecución ─────────────────────────────────────────────────
86
87 /// Consume un paso del presupuesto. `true` si se agotó.
88 fn tick(&mut self) -> bool {
89 if self.budget == 0 {
90 self.step_limit_hit = true;
91 return true;
92 }
93 self.budget -= 1;
94 false
95 }
96
97 fn exec_block(&mut self, stmts: &'a [Stmt]) -> Flow {
98 for s in stmts {
99 match self.exec_stmt(s) {
100 Flow::Normal => {}
101 other => return other,
102 }
103 }
104 Flow::Normal
105 }
106
107 fn exec_stmt(&mut self, stmt: &'a Stmt) -> Flow {
108 if self.tick() {
109 return Flow::Stop;
110 }
111 match stmt {
112 Stmt::Move { from, to } => {
113 for t in to {
114 self.do_move(from, t);
115 }
116 Flow::Normal
117 }
118 Stmt::Display { items } => {
119 let line: String = items.iter().map(|o| self.eval_text(o)).collect();
120 self.output.push(line);
121 Flow::Normal
122 }
123 Stmt::Accept { .. } => Flow::Normal, // sin entrada: deja el campo igual
124 Stmt::Compute {
125 targets,
126 rounded,
127 expr,
128 } => {
129 let value = self.eval_expr(expr);
130 for t in targets {
131 self.store(t, value, *rounded);
132 }
133 Flow::Normal
134 }
135 Stmt::Add {
136 addends,
137 to,
138 giving,
139 rounded,
140 } => {
141 let sum = self.fold_sum(addends);
142 if giving.is_empty() {
143 for t in to {
144 let cur = self.eval_decimal(t);
145 self.store(t, cur.add(&sum), *rounded);
146 }
147 } else {
148 let base = match to.first() {
149 Some(first) => sum.add(&self.eval_decimal(first)),
150 None => sum,
151 };
152 for g in giving {
153 self.store(g, base, *rounded);
154 }
155 }
156 Flow::Normal
157 }
158 Stmt::Subtract {
159 amounts,
160 from,
161 giving,
162 rounded,
163 } => {
164 let sum = self.fold_sum(amounts);
165 if giving.is_empty() {
166 for t in from {
167 let cur = self.eval_decimal(t);
168 self.store(t, cur.sub(&sum), *rounded);
169 }
170 } else {
171 let minuend = from
172 .first()
173 .map(|f| self.eval_decimal(f))
174 .unwrap_or_else(Decimal::zero);
175 let value = minuend.sub(&sum);
176 for g in giving {
177 self.store(g, value, *rounded);
178 }
179 }
180 Flow::Normal
181 }
182 Stmt::Multiply {
183 left,
184 by,
185 giving,
186 rounded,
187 } => {
188 let value = self.eval_decimal(left).mul(&self.eval_decimal(by));
189 if giving.is_empty() {
190 // `MULTIPLY a BY b` sin GIVING: b queda con a*b.
191 self.store(by, value, *rounded);
192 } else {
193 for g in giving {
194 self.store(g, value, *rounded);
195 }
196 }
197 Flow::Normal
198 }
199 Stmt::Divide {
200 left,
201 right,
202 by_form,
203 giving,
204 rounded,
205 } => {
206 let (num, den) = if *by_form {
207 (self.eval_decimal(left), self.eval_decimal(right))
208 } else {
209 (self.eval_decimal(right), self.eval_decimal(left))
210 };
211 if giving.is_empty() {
212 // `DIVIDE a INTO b` sin GIVING: b queda con b/a.
213 let v = divide(num, den, self.target_scale(right));
214 self.store(right, v, *rounded);
215 } else {
216 for g in giving {
217 let v = divide(num, den, self.target_scale(g));
218 self.store(g, v, *rounded);
219 }
220 }
221 Flow::Normal
222 }
223 Stmt::If {
224 cond,
225 then_branch,
226 else_branch,
227 } => {
228 if self.eval_cond(cond) {
229 self.exec_block(then_branch)
230 } else {
231 self.exec_block(else_branch)
232 }
233 }
234 Stmt::Evaluate {
235 subject,
236 whens,
237 other,
238 } => {
239 for branch in whens {
240 if branch.tests.iter().any(|t| self.when_test(subject, t)) {
241 return self.exec_block(&branch.body);
242 }
243 }
244 self.exec_block(other)
245 }
246 Stmt::StringConcat { sources, into } => {
247 let s: String = sources.iter().map(|o| self.eval_text(o)).collect();
248 self.store_text(into, &s);
249 Flow::Normal
250 }
251 Stmt::Unstring {
252 source,
253 delimiter,
254 into,
255 } => {
256 let src = self.eval_text(source);
257 let delim = self.eval_text(delimiter);
258 let parts: Vec<String> = if delim.is_empty() {
259 vec![src]
260 } else {
261 src.split(delim.as_str()).map(|p| p.to_string()).collect()
262 };
263 for (i, target) in into.iter().enumerate() {
264 let piece = parts.get(i).cloned().unwrap_or_default();
265 self.store_text(target, &piece);
266 }
267 Flow::Normal
268 }
269 Stmt::Inspect { target, op } => {
270 match op {
271 InspectOp::TallyingForAll { counter, search } => {
272 let hay = self.eval_text(target);
273 let needle = self.eval_text(search);
274 let n = if needle.is_empty() {
275 0
276 } else {
277 hay.matches(needle.as_str()).count()
278 };
279 let cur = self.eval_decimal(counter);
280 self.store(counter, cur.add(&Decimal::from_integer(n as i128)), false);
281 }
282 InspectOp::ReplacingAll { from, to } => {
283 let hay = self.eval_text(target);
284 let f = self.eval_text(from);
285 let t = self.eval_text(to);
286 let new = if f.is_empty() {
287 hay
288 } else {
289 hay.replace(f.as_str(), t.as_str())
290 };
291 self.store_text(target, &new);
292 }
293 }
294 Flow::Normal
295 }
296 Stmt::Initialize { targets } => {
297 for t in targets {
298 match t {
299 Operand::Data(name) => {
300 match self.ir.model.group(name).map(|g| g.members.clone()) {
301 Some(members) => {
302 for m in &members {
303 self.reset_field(m);
304 }
305 }
306 None => self.reset_field(name),
307 }
308 }
309 Operand::Indexed { .. } => self.reset_element(t),
310 _ => {}
311 }
312 }
313 Flow::Normal
314 }
315 Stmt::SetTrue { conditions } => {
316 for name in conditions {
317 if let Some(cn) = self.conditions.get(&name.to_uppercase()).cloned() {
318 self.do_move(&cn.value, &Operand::Data(cn.parent));
319 }
320 }
321 Flow::Normal
322 }
323 Stmt::Perform(p) => self.exec_perform(p),
324 Stmt::GoTo { target } => {
325 // Aproximación: ejecuta el destino y sale del párrafo.
326 match self.run_paragraph(target) {
327 Flow::Stop => Flow::Stop,
328 _ => Flow::Exit,
329 }
330 }
331 Stmt::StopRun | Stmt::Goback => Flow::Stop,
332 Stmt::Exit => Flow::Exit,
333 Stmt::Continue => Flow::Normal,
334 Stmt::Unknown { .. } => Flow::Normal, // verbo no soportado: se omite
335 }
336 }
337
338 fn exec_perform(&mut self, p: &'a Perform) -> Flow {
339 match &p.control {
340 PerformControl::Once => self.run_target(&p.target),
341 PerformControl::Times(n) => {
342 let count = self.count_of(n);
343 for _ in 0..count {
344 if self.tick() {
345 return Flow::Stop;
346 }
347 if let Flow::Stop = self.run_target(&p.target) {
348 return Flow::Stop;
349 }
350 }
351 Flow::Normal
352 }
353 PerformControl::Until(cond) => loop {
354 if self.tick() {
355 return Flow::Stop;
356 }
357 if self.eval_cond(cond) {
358 return Flow::Normal;
359 }
360 if let Flow::Stop = self.run_target(&p.target) {
361 return Flow::Stop;
362 }
363 },
364 PerformControl::Varying {
365 var,
366 from,
367 by,
368 until,
369 } => {
370 let var_op = Operand::Data(var.clone());
371 let start = self.eval_decimal(from);
372 self.store(&var_op, start, false);
373 loop {
374 if self.tick() {
375 return Flow::Stop;
376 }
377 if self.eval_cond(until) {
378 return Flow::Normal;
379 }
380 if let Flow::Stop = self.run_target(&p.target) {
381 return Flow::Stop;
382 }
383 let next = self.eval_decimal(&var_op).add(&self.eval_decimal(by));
384 self.store(&var_op, next, false);
385 }
386 }
387 }
388 }
389
390 /// Ejecuta una vez el cuerpo de un `PERFORM`. Un `EXIT` dentro de
391 /// él termina esa pasada, no el programa.
392 fn run_target(&mut self, target: &'a PerformTarget) -> Flow {
393 let flow = match target {
394 PerformTarget::Paragraph { name, .. } => self.run_paragraph(name),
395 PerformTarget::Inline(body) => self.exec_block(body),
396 };
397 match flow {
398 Flow::Stop => Flow::Stop,
399 _ => Flow::Normal,
400 }
401 }
402
403 fn run_paragraph(&mut self, name: &str) -> Flow {
404 let Some(&idx) = self.para_index.get(&name.to_uppercase()) else {
405 return Flow::Normal;
406 };
407 let ir = self.ir;
408 match self.exec_block(&ir.procedures[idx].body) {
409 Flow::Stop => Flow::Stop,
410 _ => Flow::Normal,
411 }
412 }
413
414 /// Resuelve una referencia a dato (escalar o elemento de tabla) a
415 /// su nombre y un índice 0-based. `None` si no es una referencia.
416 fn resolve(&self, op: &Operand) -> Option<(String, usize)> {
417 match op {
418 Operand::Data(name) => Some((name.to_uppercase(), 0)),
419 Operand::Indexed { name, index } => {
420 // El subíndice de COBOL es 1-based.
421 let i = self
422 .eval_decimal(index)
423 .rescale(0, Rounding::Truncate)
424 .mantissa();
425 let idx = if i < 1 { 0 } else { (i - 1) as usize };
426 Some((name.to_uppercase(), idx))
427 }
428 _ => None,
429 }
430 }
431
432 /// `MOVE from` a un solo destino (escalar o elemento de tabla).
433 fn do_move(&mut self, from: &Operand, target: &Operand) {
434 let Some((key, idx)) = self.resolve(target) else {
435 return;
436 };
437 let is_num = matches!(self.fields.get(&key), Some(Cell::Num(_)));
438 if is_num {
439 let v = self.eval_decimal(from);
440 if let Some(Cell::Num(arr)) = self.fields.get_mut(&key) {
441 if let Some(n) = arr.get_mut(idx) {
442 n.store(v);
443 }
444 }
445 } else if let Operand::Figurative(fig) = from {
446 let ch = figurative_fill(*fig);
447 if let Some(Cell::Text(arr)) = self.fields.get_mut(&key) {
448 if let Some(t) = arr.get_mut(idx) {
449 t.fill(ch);
450 }
451 }
452 } else {
453 let s = self.eval_text(from);
454 if let Some(Cell::Text(arr)) = self.fields.get_mut(&key) {
455 if let Some(t) = arr.get_mut(idx) {
456 t.store(&s);
457 }
458 }
459 }
460 }
461
462 /// Almacena un valor en un destino, conformándolo a su tipo.
463 fn store(&mut self, target: &Operand, value: Decimal, rounded: bool) {
464 let Some((key, idx)) = self.resolve(target) else {
465 return;
466 };
467 match self.fields.get_mut(&key) {
468 Some(Cell::Num(arr)) => {
469 if let Some(n) = arr.get_mut(idx) {
470 if rounded {
471 n.store_rounded(value);
472 } else {
473 n.store(value);
474 }
475 }
476 }
477 Some(Cell::Text(arr)) => {
478 if let Some(t) = arr.get_mut(idx) {
479 t.store(&value.to_string());
480 }
481 }
482 None => {}
483 }
484 }
485
486 /// Resetea un campo completo (escalar o tabla) a su valor por
487 /// defecto: 0 si es numérico, espacios si es alfanumérico.
488 fn reset_field(&mut self, name: &str) {
489 match self.fields.get_mut(&name.to_uppercase()) {
490 Some(Cell::Num(arr)) => {
491 for n in arr.iter_mut() {
492 *n = Num::new(n.picture());
493 }
494 }
495 Some(Cell::Text(arr)) => {
496 for t in arr.iter_mut() {
497 *t = Text::new(t.len());
498 }
499 }
500 None => {}
501 }
502 }
503
504 /// Resetea un solo elemento de tabla a su valor por defecto.
505 fn reset_element(&mut self, op: &Operand) {
506 let Some((key, idx)) = self.resolve(op) else {
507 return;
508 };
509 match self.fields.get_mut(&key) {
510 Some(Cell::Num(arr)) => {
511 if let Some(n) = arr.get_mut(idx) {
512 *n = Num::new(n.picture());
513 }
514 }
515 Some(Cell::Text(arr)) => {
516 if let Some(t) = arr.get_mut(idx) {
517 *t = Text::new(t.len());
518 }
519 }
520 None => {}
521 }
522 }
523
524 /// Almacena un texto en un destino, conformándolo a su tipo.
525 fn store_text(&mut self, target: &Operand, text: &str) {
526 let Some((key, idx)) = self.resolve(target) else {
527 return;
528 };
529 match self.fields.get_mut(&key) {
530 Some(Cell::Text(arr)) => {
531 if let Some(t) = arr.get_mut(idx) {
532 t.store(text);
533 }
534 }
535 Some(Cell::Num(arr)) => {
536 if let Some(n) = arr.get_mut(idx) {
537 n.store(Decimal::parse(text.trim()).unwrap_or_else(|_| Decimal::zero()));
538 }
539 }
540 None => {}
541 }
542 }
543
544 // ── Evaluación ────────────────────────────────────────────────
545
546 fn eval_decimal(&self, op: &Operand) -> Decimal {
547 match op {
548 Operand::Num(n) => Decimal::parse(n).unwrap_or_else(|_| Decimal::zero()),
549 Operand::Str(s) => Decimal::parse(s).unwrap_or_else(|_| Decimal::zero()),
550 Operand::Figurative(_) => Decimal::zero(),
551 Operand::Data(_) | Operand::Indexed { .. } => {
552 let Some((key, idx)) = self.resolve(op) else {
553 return Decimal::zero();
554 };
555 match self.fields.get(&key) {
556 Some(Cell::Num(arr)) => arr
557 .get(idx)
558 .map(|n| n.value())
559 .unwrap_or_else(Decimal::zero),
560 Some(Cell::Text(arr)) => arr
561 .get(idx)
562 .and_then(|t| Decimal::parse(t.as_str().trim()).ok())
563 .unwrap_or_else(Decimal::zero),
564 None => Decimal::zero(),
565 }
566 }
567 }
568 }
569
570 fn eval_text(&self, op: &Operand) -> String {
571 match op {
572 Operand::Str(s) => s.clone(),
573 Operand::Num(n) => n.clone(),
574 Operand::Figurative(f) => figurative_text(*f).to_string(),
575 Operand::Data(_) | Operand::Indexed { .. } => {
576 let Some((key, idx)) = self.resolve(op) else {
577 return String::new();
578 };
579 match self.fields.get(&key) {
580 Some(Cell::Num(arr)) => arr.get(idx).map(|n| n.display()).unwrap_or_default(),
581 Some(Cell::Text(arr)) => arr.get(idx).map(|t| t.display()).unwrap_or_default(),
582 None => String::new(),
583 }
584 }
585 }
586 }
587
588 fn eval_expr(&self, e: &Expr) -> Decimal {
589 match e {
590 Expr::Operand(op) => self.eval_decimal(op),
591 Expr::Neg(inner) => Decimal::zero().sub(&self.eval_expr(inner)),
592 Expr::Binary { op, lhs, rhs } => {
593 let l = self.eval_expr(lhs);
594 let r = self.eval_expr(rhs);
595 match op {
596 BinOp::Add => l.add(&r),
597 BinOp::Sub => l.sub(&r),
598 BinOp::Mul => l.mul(&r),
599 BinOp::Div => divide(l, r, DIV_SCALE),
600 BinOp::Pow => pow(&l, &r),
601 }
602 }
603 }
604 }
605
606 fn eval_cond(&self, c: &Cond) -> bool {
607 match c {
608 Cond::Compare { lhs, op, rhs } => {
609 let ord = if self.is_text(lhs) || self.is_text(rhs) {
610 cobol_text_cmp(&self.eval_text(lhs), &self.eval_text(rhs))
611 } else {
612 self.eval_decimal(lhs).cmp(&self.eval_decimal(rhs))
613 };
614 match op {
615 CmpOp::Eq => ord.is_eq(),
616 CmpOp::Ne => ord.is_ne(),
617 CmpOp::Lt => ord.is_lt(),
618 CmpOp::Gt => ord.is_gt(),
619 CmpOp::Le => ord.is_le(),
620 CmpOp::Ge => ord.is_ge(),
621 }
622 }
623 Cond::Named(name) => match self.conditions.get(&name.to_uppercase()) {
624 // Un nombre de condición (88): el dato padre igual al
625 // valor que la hace verdadera.
626 Some(cn) => self.operands_equal(&Operand::Data(cn.parent.clone()), &cn.value),
627 None => false,
628 },
629 Cond::Not(inner) => !self.eval_cond(inner),
630 Cond::And(a, b) => self.eval_cond(a) && self.eval_cond(b),
631 Cond::Or(a, b) => self.eval_cond(a) || self.eval_cond(b),
632 }
633 }
634
635 fn is_text(&self, op: &Operand) -> bool {
636 match op {
637 Operand::Str(_) => true,
638 Operand::Data(_) | Operand::Indexed { .. } => match self.resolve(op) {
639 Some((key, _)) => matches!(self.fields.get(&key), Some(Cell::Text(_))),
640 None => false,
641 },
642 _ => false,
643 }
644 }
645
646 /// ¿Se cumple una prueba `WHEN` para el sujeto dado?
647 fn when_test(&self, subject: &Operand, test: &WhenTest) -> bool {
648 match test {
649 WhenTest::Value(v) => self.operands_equal(subject, v),
650 WhenTest::Range(lo, hi) => {
651 let s = self.eval_decimal(subject);
652 s >= self.eval_decimal(lo) && s <= self.eval_decimal(hi)
653 }
654 WhenTest::Cond(cond) => self.eval_cond(cond),
655 }
656 }
657
658 /// ¿Son iguales dos operandos? (Para las ramas `WHEN` del `EVALUATE`.)
659 fn operands_equal(&self, a: &Operand, b: &Operand) -> bool {
660 if self.is_text(a) || self.is_text(b) {
661 cobol_text_cmp(&self.eval_text(a), &self.eval_text(b)).is_eq()
662 } else {
663 self.eval_decimal(a) == self.eval_decimal(b)
664 }
665 }
666
667 /// La suma de una lista de operandos.
668 fn fold_sum(&self, ops: &[Operand]) -> Decimal {
669 let mut acc = Decimal::zero();
670 for o in ops {
671 acc = acc.add(&self.eval_decimal(o));
672 }
673 acc
674 }
675
676 /// Los dígitos fraccionarios de un destino numérico.
677 fn target_scale(&self, op: &Operand) -> u8 {
678 if let Some((key, idx)) = self.resolve(op) {
679 if let Some(Cell::Num(arr)) = self.fields.get(&key) {
680 if let Some(n) = arr.get(idx) {
681 return n.picture().fraction_digits;
682 }
683 }
684 }
685 4
686 }
687
688 /// El número de repeticiones de un `PERFORM ... TIMES`.
689 fn count_of(&self, op: &Operand) -> usize {
690 let m = self
691 .eval_decimal(op)
692 .rescale(0, Rounding::Truncate)
693 .mantissa();
694 if m < 0 {
695 0
696 } else {
697 m as usize
698 }
699 }
700}
701
702/// División con escala fija; una división por cero da cero.
703fn divide(num: Decimal, den: Decimal, scale: u8) -> Decimal {
704 num.div(&den, scale, Rounding::Truncate)
705 .unwrap_or_else(|_| Decimal::zero())
706}
707
708/// Potencia con exponente entero no negativo; en otro caso da 1.
709fn pow(base: &Decimal, exp: &Decimal) -> Decimal {
710 let e = exp.rescale(0, Rounding::Truncate).mantissa();
711 if !(0..=256).contains(&e) {
712 return Decimal::from_integer(1);
713 }
714 let mut acc = Decimal::from_integer(1);
715 for _ in 0..e {
716 acc = acc.mul(base);
717 }
718 acc
719}
720
721/// El texto que representa una constante figurativa.
722fn figurative_text(f: Figurative) -> &'static str {
723 match f {
724 Figurative::Zero => "0",
725 Figurative::Space => " ",
726 Figurative::Quote => "\"",
727 Figurative::HighValue | Figurative::LowValue | Figurative::Null => "",
728 }
729}
730
731/// El carácter de relleno de una figurativa, para `Text::fill`.
732fn figurative_fill(f: Figurative) -> char {
733 match f {
734 Figurative::Zero => '0',
735 Figurative::Quote => '"',
736 _ => ' ',
737 }
738}