⎇ 28ee1ae2602662b216549ba4db0d34a9b55b8deb
crates/modules/charka/charka-shadow/src/interp.rs
History
Who touched what
raw
feat(charka): nivel 88 + modelo de datos compartido en charka-ir
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, Ir, Operand, Perform, PerformControl,
12 PerformTarget, Stmt,
13};
14use charka_runtime::{cobol_text_cmp, Decimal, Rounding};
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.field_value(t);
145 self.store(t, cur.add(&sum), *rounded);
146 }
147 } else {
148 let base = match to.first() {
149 Some(first) => sum.add(&self.field_value(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.field_value(t);
168 self.store(t, cur.sub(&sum), *rounded);
169 }
170 } else {
171 let minuend = from
172 .first()
173 .map(|f| self.field_value(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 if let Operand::Data(name) = by {
191 self.store(name, value, *rounded);
192 }
193 } else {
194 for g in giving {
195 self.store(g, value, *rounded);
196 }
197 }
198 Flow::Normal
199 }
200 Stmt::Divide {
201 left,
202 right,
203 by_form,
204 giving,
205 rounded,
206 } => {
207 let (num, den) = if *by_form {
208 (self.eval_decimal(left), self.eval_decimal(right))
209 } else {
210 (self.eval_decimal(right), self.eval_decimal(left))
211 };
212 if giving.is_empty() {
213 if let Operand::Data(name) = right {
214 let v = divide(num, den, self.target_scale(name));
215 self.store(name, v, *rounded);
216 }
217 } else {
218 for g in giving {
219 let v = divide(num, den, self.target_scale(g));
220 self.store(g, v, *rounded);
221 }
222 }
223 Flow::Normal
224 }
225 Stmt::If {
226 cond,
227 then_branch,
228 else_branch,
229 } => {
230 if self.eval_cond(cond) {
231 self.exec_block(then_branch)
232 } else {
233 self.exec_block(else_branch)
234 }
235 }
236 Stmt::Evaluate {
237 subject,
238 whens,
239 other,
240 } => {
241 for branch in whens {
242 if branch
243 .values
244 .iter()
245 .any(|v| self.operands_equal(subject, v))
246 {
247 return self.exec_block(&branch.body);
248 }
249 }
250 self.exec_block(other)
251 }
252 Stmt::Perform(p) => self.exec_perform(p),
253 Stmt::GoTo { target } => {
254 // Aproximación: ejecuta el destino y sale del párrafo.
255 match self.run_paragraph(target) {
256 Flow::Stop => Flow::Stop,
257 _ => Flow::Exit,
258 }
259 }
260 Stmt::StopRun | Stmt::Goback => Flow::Stop,
261 Stmt::Exit => Flow::Exit,
262 Stmt::Continue => Flow::Normal,
263 Stmt::Unknown { .. } => Flow::Normal, // verbo no soportado: se omite
264 }
265 }
266
267 fn exec_perform(&mut self, p: &'a Perform) -> Flow {
268 match &p.control {
269 PerformControl::Once => self.run_target(&p.target),
270 PerformControl::Times(n) => {
271 let count = self.count_of(n);
272 for _ in 0..count {
273 if self.tick() {
274 return Flow::Stop;
275 }
276 if let Flow::Stop = self.run_target(&p.target) {
277 return Flow::Stop;
278 }
279 }
280 Flow::Normal
281 }
282 PerformControl::Until(cond) => loop {
283 if self.tick() {
284 return Flow::Stop;
285 }
286 if self.eval_cond(cond) {
287 return Flow::Normal;
288 }
289 if let Flow::Stop = self.run_target(&p.target) {
290 return Flow::Stop;
291 }
292 },
293 PerformControl::Varying {
294 var,
295 from,
296 by,
297 until,
298 } => {
299 let start = self.eval_decimal(from);
300 self.store(var, start, false);
301 loop {
302 if self.tick() {
303 return Flow::Stop;
304 }
305 if self.eval_cond(until) {
306 return Flow::Normal;
307 }
308 if let Flow::Stop = self.run_target(&p.target) {
309 return Flow::Stop;
310 }
311 let next = self.field_value(var).add(&self.eval_decimal(by));
312 self.store(var, next, false);
313 }
314 }
315 }
316 }
317
318 /// Ejecuta una vez el cuerpo de un `PERFORM`. Un `EXIT` dentro de
319 /// él termina esa pasada, no el programa.
320 fn run_target(&mut self, target: &'a PerformTarget) -> Flow {
321 let flow = match target {
322 PerformTarget::Paragraph { name, .. } => self.run_paragraph(name),
323 PerformTarget::Inline(body) => self.exec_block(body),
324 };
325 match flow {
326 Flow::Stop => Flow::Stop,
327 _ => Flow::Normal,
328 }
329 }
330
331 fn run_paragraph(&mut self, name: &str) -> Flow {
332 let Some(&idx) = self.para_index.get(&name.to_uppercase()) else {
333 return Flow::Normal;
334 };
335 let ir = self.ir;
336 match self.exec_block(&ir.procedures[idx].body) {
337 Flow::Stop => Flow::Stop,
338 _ => Flow::Normal,
339 }
340 }
341
342 /// `MOVE from` a un solo campo destino.
343 fn do_move(&mut self, from: &Operand, target: &str) {
344 let key = target.to_uppercase();
345 match self.fields.get(&key) {
346 Some(Cell::Num(_)) => {
347 let v = self.eval_decimal(from);
348 if let Some(Cell::Num(n)) = self.fields.get_mut(&key) {
349 n.store(v);
350 }
351 }
352 Some(Cell::Text(_)) => {
353 if let Operand::Figurative(fig) = from {
354 let ch = figurative_fill(*fig);
355 if let Some(Cell::Text(t)) = self.fields.get_mut(&key) {
356 t.fill(ch);
357 }
358 } else {
359 let s = self.eval_text(from);
360 if let Some(Cell::Text(t)) = self.fields.get_mut(&key) {
361 t.store(&s);
362 }
363 }
364 }
365 None => {}
366 }
367 }
368
369 /// Almacena un valor en un campo, conformándolo a su tipo.
370 fn store(&mut self, name: &str, value: Decimal, rounded: bool) {
371 match self.fields.get_mut(&name.to_uppercase()) {
372 Some(Cell::Num(n)) => {
373 if rounded {
374 n.store_rounded(value);
375 } else {
376 n.store(value);
377 }
378 }
379 Some(Cell::Text(t)) => t.store(&value.to_string()),
380 None => {}
381 }
382 }
383
384 // ── Evaluación ────────────────────────────────────────────────
385
386 fn eval_decimal(&self, op: &Operand) -> Decimal {
387 match op {
388 Operand::Num(n) => Decimal::parse(n).unwrap_or_else(|_| Decimal::zero()),
389 Operand::Str(s) => Decimal::parse(s).unwrap_or_else(|_| Decimal::zero()),
390 Operand::Figurative(_) => Decimal::zero(),
391 Operand::Data(name) => match self.fields.get(&name.to_uppercase()) {
392 Some(Cell::Num(n)) => n.value(),
393 Some(Cell::Text(t)) => {
394 Decimal::parse(t.as_str().trim()).unwrap_or_else(|_| Decimal::zero())
395 }
396 None => Decimal::zero(),
397 },
398 }
399 }
400
401 fn eval_text(&self, op: &Operand) -> String {
402 match op {
403 Operand::Str(s) => s.clone(),
404 Operand::Num(n) => n.clone(),
405 Operand::Figurative(f) => figurative_text(*f).to_string(),
406 Operand::Data(name) => match self.fields.get(&name.to_uppercase()) {
407 Some(Cell::Num(n)) => n.display(),
408 Some(Cell::Text(t)) => t.display(),
409 None => String::new(),
410 },
411 }
412 }
413
414 fn eval_expr(&self, e: &Expr) -> Decimal {
415 match e {
416 Expr::Operand(op) => self.eval_decimal(op),
417 Expr::Neg(inner) => Decimal::zero().sub(&self.eval_expr(inner)),
418 Expr::Binary { op, lhs, rhs } => {
419 let l = self.eval_expr(lhs);
420 let r = self.eval_expr(rhs);
421 match op {
422 BinOp::Add => l.add(&r),
423 BinOp::Sub => l.sub(&r),
424 BinOp::Mul => l.mul(&r),
425 BinOp::Div => divide(l, r, DIV_SCALE),
426 BinOp::Pow => pow(&l, &r),
427 }
428 }
429 }
430 }
431
432 fn eval_cond(&self, c: &Cond) -> bool {
433 match c {
434 Cond::Compare { lhs, op, rhs } => {
435 let ord = if self.is_text(lhs) || self.is_text(rhs) {
436 cobol_text_cmp(&self.eval_text(lhs), &self.eval_text(rhs))
437 } else {
438 self.eval_decimal(lhs).cmp(&self.eval_decimal(rhs))
439 };
440 match op {
441 CmpOp::Eq => ord.is_eq(),
442 CmpOp::Ne => ord.is_ne(),
443 CmpOp::Lt => ord.is_lt(),
444 CmpOp::Gt => ord.is_gt(),
445 CmpOp::Le => ord.is_le(),
446 CmpOp::Ge => ord.is_ge(),
447 }
448 }
449 Cond::Named(name) => match self.conditions.get(&name.to_uppercase()) {
450 // Un nombre de condición (88): el dato padre igual al
451 // valor que la hace verdadera.
452 Some(cn) => self.operands_equal(&Operand::Data(cn.parent.clone()), &cn.value),
453 None => false,
454 },
455 Cond::Not(inner) => !self.eval_cond(inner),
456 Cond::And(a, b) => self.eval_cond(a) && self.eval_cond(b),
457 Cond::Or(a, b) => self.eval_cond(a) || self.eval_cond(b),
458 }
459 }
460
461 fn is_text(&self, op: &Operand) -> bool {
462 match op {
463 Operand::Str(_) => true,
464 Operand::Data(name) => {
465 matches!(self.fields.get(&name.to_uppercase()), Some(Cell::Text(_)))
466 }
467 _ => false,
468 }
469 }
470
471 /// ¿Son iguales dos operandos? (Para las ramas `WHEN` del `EVALUATE`.)
472 fn operands_equal(&self, a: &Operand, b: &Operand) -> bool {
473 if self.is_text(a) || self.is_text(b) {
474 cobol_text_cmp(&self.eval_text(a), &self.eval_text(b)).is_eq()
475 } else {
476 self.eval_decimal(a) == self.eval_decimal(b)
477 }
478 }
479
480 /// La suma de una lista de operandos.
481 fn fold_sum(&self, ops: &[Operand]) -> Decimal {
482 let mut acc = Decimal::zero();
483 for o in ops {
484 acc = acc.add(&self.eval_decimal(o));
485 }
486 acc
487 }
488
489 /// El valor actual de un campo por nombre.
490 fn field_value(&self, name: &str) -> Decimal {
491 match self.fields.get(&name.to_uppercase()) {
492 Some(Cell::Num(n)) => n.value(),
493 Some(Cell::Text(t)) => {
494 Decimal::parse(t.as_str().trim()).unwrap_or_else(|_| Decimal::zero())
495 }
496 None => Decimal::zero(),
497 }
498 }
499
500 /// Los dígitos fraccionarios de un campo numérico destino.
501 fn target_scale(&self, name: &str) -> u8 {
502 match self.fields.get(&name.to_uppercase()) {
503 Some(Cell::Num(n)) => n.picture().fraction_digits,
504 _ => 4,
505 }
506 }
507
508 /// El número de repeticiones de un `PERFORM ... TIMES`.
509 fn count_of(&self, op: &Operand) -> usize {
510 let m = self
511 .eval_decimal(op)
512 .rescale(0, Rounding::Truncate)
513 .mantissa();
514 if m < 0 {
515 0
516 } else {
517 m as usize
518 }
519 }
520}
521
522/// División con escala fija; una división por cero da cero.
523fn divide(num: Decimal, den: Decimal, scale: u8) -> Decimal {
524 num.div(&den, scale, Rounding::Truncate)
525 .unwrap_or_else(|_| Decimal::zero())
526}
527
528/// Potencia con exponente entero no negativo; en otro caso da 1.
529fn pow(base: &Decimal, exp: &Decimal) -> Decimal {
530 let e = exp.rescale(0, Rounding::Truncate).mantissa();
531 if !(0..=256).contains(&e) {
532 return Decimal::from_integer(1);
533 }
534 let mut acc = Decimal::from_integer(1);
535 for _ in 0..e {
536 acc = acc.mul(base);
537 }
538 acc
539}
540
541/// El texto que representa una constante figurativa.
542fn figurative_text(f: Figurative) -> &'static str {
543 match f {
544 Figurative::Zero => "0",
545 Figurative::Space => " ",
546 Figurative::Quote => "\"",
547 Figurative::HighValue | Figurative::LowValue | Figurative::Null => "",
548 }
549}
550
551/// El carácter de relleno de una figurativa, para `Text::fill`.
552fn figurative_fill(f: Figurative) -> char {
553 match f {
554 Figurative::Zero => '0',
555 Figurative::Quote => '"',
556 _ => ' ',
557 }
558}