⎇ 4d9ce11b1e444430e6e2bcc0a72baa1d40a4527c
crates/modules/charka/charka-shadow/src/interp.rs
Historia
Quién tocó qué
crudo
feat(charka): charka-shadow — validador en sombra + corpus COBOL
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, Expr, Figurative, Ir, Operand, Perform, PerformControl, PerformTarget, Stmt,
12};
13use charka_runtime::{cobol_text_cmp, Decimal, Rounding};
14
15use crate::field::{build_fields, Cell};
16
17/// Tope de pasos: corta los bucles que no terminan (un `PERFORM UNTIL`
18/// con una condición que nunca se cumple) en vez de colgarse.
19const STEP_BUDGET: u64 = 5_000_000;
20
21/// Escala intermedia de la división dentro de una expresión.
22const DIV_SCALE: u8 = 9;
23
24/// El resultado de ejecutar un statement: cómo sigue el control.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26enum Flow {
27 /// Sigue con el statement siguiente.
28 Normal,
29 /// Sale del párrafo actual (`EXIT`).
30 Exit,
31 /// Termina el programa (`STOP RUN`, `GOBACK`, tope de pasos).
32 Stop,
33}
34
35/// La máquina sombra: el estado y el motor de ejecución.
36pub(crate) struct Machine<'a> {
37 ir: &'a Ir,
38 fields: HashMap<String, Cell>,
39 para_index: HashMap<String, usize>,
40 pub output: Vec<String>,
41 budget: u64,
42 pub step_limit_hit: bool,
43 pub stopped: bool,
44}
45
46impl<'a> Machine<'a> {
47 /// Prepara la máquina: aplana los datos e indexa los párrafos.
48 pub(crate) fn new(ir: &'a Ir) -> Self {
49 let mut para_index = HashMap::new();
50 for (i, proc) in ir.procedures.iter().enumerate() {
51 para_index.entry(proc.name.to_uppercase()).or_insert(i);
52 }
53 Self {
54 ir,
55 fields: build_fields(&ir.data),
56 para_index,
57 output: Vec::new(),
58 budget: STEP_BUDGET,
59 step_limit_hit: false,
60 stopped: false,
61 }
62 }
63
64 /// Corre el programa: encadena los párrafos en orden (el «caer» de
65 /// COBOL) hasta un `STOP RUN` o el final.
66 pub(crate) fn run(&mut self) {
67 let ir = self.ir;
68 for i in 0..ir.procedures.len() {
69 if let Flow::Stop = self.exec_block(&ir.procedures[i].body) {
70 self.stopped = true;
71 break;
72 }
73 }
74 }
75
76 // ── Ejecución ─────────────────────────────────────────────────
77
78 /// Consume un paso del presupuesto. `true` si se agotó.
79 fn tick(&mut self) -> bool {
80 if self.budget == 0 {
81 self.step_limit_hit = true;
82 return true;
83 }
84 self.budget -= 1;
85 false
86 }
87
88 fn exec_block(&mut self, stmts: &'a [Stmt]) -> Flow {
89 for s in stmts {
90 match self.exec_stmt(s) {
91 Flow::Normal => {}
92 other => return other,
93 }
94 }
95 Flow::Normal
96 }
97
98 fn exec_stmt(&mut self, stmt: &'a Stmt) -> Flow {
99 if self.tick() {
100 return Flow::Stop;
101 }
102 match stmt {
103 Stmt::Move { from, to } => {
104 for t in to {
105 self.do_move(from, t);
106 }
107 Flow::Normal
108 }
109 Stmt::Display { items } => {
110 let line: String = items.iter().map(|o| self.eval_text(o)).collect();
111 self.output.push(line);
112 Flow::Normal
113 }
114 Stmt::Accept { .. } => Flow::Normal, // sin entrada: deja el campo igual
115 Stmt::Compute {
116 targets,
117 rounded,
118 expr,
119 } => {
120 let value = self.eval_expr(expr);
121 for t in targets {
122 self.store(t, value, *rounded);
123 }
124 Flow::Normal
125 }
126 Stmt::Add {
127 addends,
128 to,
129 giving,
130 rounded,
131 } => {
132 let sum = self.fold_sum(addends);
133 if giving.is_empty() {
134 for t in to {
135 let cur = self.field_value(t);
136 self.store(t, cur.add(&sum), *rounded);
137 }
138 } else {
139 let base = match to.first() {
140 Some(first) => sum.add(&self.field_value(first)),
141 None => sum,
142 };
143 for g in giving {
144 self.store(g, base, *rounded);
145 }
146 }
147 Flow::Normal
148 }
149 Stmt::Subtract {
150 amounts,
151 from,
152 giving,
153 rounded,
154 } => {
155 let sum = self.fold_sum(amounts);
156 if giving.is_empty() {
157 for t in from {
158 let cur = self.field_value(t);
159 self.store(t, cur.sub(&sum), *rounded);
160 }
161 } else {
162 let minuend = from
163 .first()
164 .map(|f| self.field_value(f))
165 .unwrap_or_else(Decimal::zero);
166 let value = minuend.sub(&sum);
167 for g in giving {
168 self.store(g, value, *rounded);
169 }
170 }
171 Flow::Normal
172 }
173 Stmt::Multiply {
174 left,
175 by,
176 giving,
177 rounded,
178 } => {
179 let value = self.eval_decimal(left).mul(&self.eval_decimal(by));
180 if giving.is_empty() {
181 if let Operand::Data(name) = by {
182 self.store(name, value, *rounded);
183 }
184 } else {
185 for g in giving {
186 self.store(g, value, *rounded);
187 }
188 }
189 Flow::Normal
190 }
191 Stmt::Divide {
192 left,
193 right,
194 by_form,
195 giving,
196 rounded,
197 } => {
198 let (num, den) = if *by_form {
199 (self.eval_decimal(left), self.eval_decimal(right))
200 } else {
201 (self.eval_decimal(right), self.eval_decimal(left))
202 };
203 if giving.is_empty() {
204 if let Operand::Data(name) = right {
205 let v = divide(num, den, self.target_scale(name));
206 self.store(name, v, *rounded);
207 }
208 } else {
209 for g in giving {
210 let v = divide(num, den, self.target_scale(g));
211 self.store(g, v, *rounded);
212 }
213 }
214 Flow::Normal
215 }
216 Stmt::If {
217 cond,
218 then_branch,
219 else_branch,
220 } => {
221 if self.eval_cond(cond) {
222 self.exec_block(then_branch)
223 } else {
224 self.exec_block(else_branch)
225 }
226 }
227 Stmt::Perform(p) => self.exec_perform(p),
228 Stmt::GoTo { target } => {
229 // Aproximación: ejecuta el destino y sale del párrafo.
230 match self.run_paragraph(target) {
231 Flow::Stop => Flow::Stop,
232 _ => Flow::Exit,
233 }
234 }
235 Stmt::StopRun | Stmt::Goback => Flow::Stop,
236 Stmt::Exit => Flow::Exit,
237 Stmt::Continue => Flow::Normal,
238 Stmt::Unknown { .. } => Flow::Normal, // verbo no soportado: se omite
239 }
240 }
241
242 fn exec_perform(&mut self, p: &'a Perform) -> Flow {
243 match &p.control {
244 PerformControl::Once => self.run_target(&p.target),
245 PerformControl::Times(n) => {
246 let count = self.count_of(n);
247 for _ in 0..count {
248 if self.tick() {
249 return Flow::Stop;
250 }
251 if let Flow::Stop = self.run_target(&p.target) {
252 return Flow::Stop;
253 }
254 }
255 Flow::Normal
256 }
257 PerformControl::Until(cond) => loop {
258 if self.tick() {
259 return Flow::Stop;
260 }
261 if self.eval_cond(cond) {
262 return Flow::Normal;
263 }
264 if let Flow::Stop = self.run_target(&p.target) {
265 return Flow::Stop;
266 }
267 },
268 }
269 }
270
271 /// Ejecuta una vez el cuerpo de un `PERFORM`. Un `EXIT` dentro de
272 /// él termina esa pasada, no el programa.
273 fn run_target(&mut self, target: &'a PerformTarget) -> Flow {
274 let flow = match target {
275 PerformTarget::Paragraph { name, .. } => self.run_paragraph(name),
276 PerformTarget::Inline(body) => self.exec_block(body),
277 };
278 match flow {
279 Flow::Stop => Flow::Stop,
280 _ => Flow::Normal,
281 }
282 }
283
284 fn run_paragraph(&mut self, name: &str) -> Flow {
285 let Some(&idx) = self.para_index.get(&name.to_uppercase()) else {
286 return Flow::Normal;
287 };
288 let ir = self.ir;
289 match self.exec_block(&ir.procedures[idx].body) {
290 Flow::Stop => Flow::Stop,
291 _ => Flow::Normal,
292 }
293 }
294
295 /// `MOVE from` a un solo campo destino.
296 fn do_move(&mut self, from: &Operand, target: &str) {
297 let key = target.to_uppercase();
298 match self.fields.get(&key) {
299 Some(Cell::Num(_)) => {
300 let v = self.eval_decimal(from);
301 if let Some(Cell::Num(n)) = self.fields.get_mut(&key) {
302 n.store(v);
303 }
304 }
305 Some(Cell::Text(_)) => {
306 if let Operand::Figurative(fig) = from {
307 let ch = figurative_fill(*fig);
308 if let Some(Cell::Text(t)) = self.fields.get_mut(&key) {
309 t.fill(ch);
310 }
311 } else {
312 let s = self.eval_text(from);
313 if let Some(Cell::Text(t)) = self.fields.get_mut(&key) {
314 t.store(&s);
315 }
316 }
317 }
318 None => {}
319 }
320 }
321
322 /// Almacena un valor en un campo, conformándolo a su tipo.
323 fn store(&mut self, name: &str, value: Decimal, rounded: bool) {
324 match self.fields.get_mut(&name.to_uppercase()) {
325 Some(Cell::Num(n)) => {
326 if rounded {
327 n.store_rounded(value);
328 } else {
329 n.store(value);
330 }
331 }
332 Some(Cell::Text(t)) => t.store(&value.to_string()),
333 None => {}
334 }
335 }
336
337 // ── Evaluación ────────────────────────────────────────────────
338
339 fn eval_decimal(&self, op: &Operand) -> Decimal {
340 match op {
341 Operand::Num(n) => Decimal::parse(n).unwrap_or_else(|_| Decimal::zero()),
342 Operand::Str(s) => Decimal::parse(s).unwrap_or_else(|_| Decimal::zero()),
343 Operand::Figurative(_) => Decimal::zero(),
344 Operand::Data(name) => match self.fields.get(&name.to_uppercase()) {
345 Some(Cell::Num(n)) => n.value(),
346 Some(Cell::Text(t)) => {
347 Decimal::parse(t.as_str().trim()).unwrap_or_else(|_| Decimal::zero())
348 }
349 None => Decimal::zero(),
350 },
351 }
352 }
353
354 fn eval_text(&self, op: &Operand) -> String {
355 match op {
356 Operand::Str(s) => s.clone(),
357 Operand::Num(n) => n.clone(),
358 Operand::Figurative(f) => figurative_text(*f).to_string(),
359 Operand::Data(name) => match self.fields.get(&name.to_uppercase()) {
360 Some(Cell::Num(n)) => n.display(),
361 Some(Cell::Text(t)) => t.display(),
362 None => String::new(),
363 },
364 }
365 }
366
367 fn eval_expr(&self, e: &Expr) -> Decimal {
368 match e {
369 Expr::Operand(op) => self.eval_decimal(op),
370 Expr::Neg(inner) => Decimal::zero().sub(&self.eval_expr(inner)),
371 Expr::Binary { op, lhs, rhs } => {
372 let l = self.eval_expr(lhs);
373 let r = self.eval_expr(rhs);
374 match op {
375 BinOp::Add => l.add(&r),
376 BinOp::Sub => l.sub(&r),
377 BinOp::Mul => l.mul(&r),
378 BinOp::Div => divide(l, r, DIV_SCALE),
379 BinOp::Pow => pow(&l, &r),
380 }
381 }
382 }
383 }
384
385 fn eval_cond(&self, c: &Cond) -> bool {
386 match c {
387 Cond::Compare { lhs, op, rhs } => {
388 let ord = if self.is_text(lhs) || self.is_text(rhs) {
389 cobol_text_cmp(&self.eval_text(lhs), &self.eval_text(rhs))
390 } else {
391 self.eval_decimal(lhs).cmp(&self.eval_decimal(rhs))
392 };
393 match op {
394 CmpOp::Eq => ord.is_eq(),
395 CmpOp::Ne => ord.is_ne(),
396 CmpOp::Lt => ord.is_lt(),
397 CmpOp::Gt => ord.is_gt(),
398 CmpOp::Le => ord.is_le(),
399 CmpOp::Ge => ord.is_ge(),
400 }
401 }
402 Cond::Named(_) => false, // nombres de condición (88): no soportado
403 Cond::Not(inner) => !self.eval_cond(inner),
404 Cond::And(a, b) => self.eval_cond(a) && self.eval_cond(b),
405 Cond::Or(a, b) => self.eval_cond(a) || self.eval_cond(b),
406 }
407 }
408
409 fn is_text(&self, op: &Operand) -> bool {
410 match op {
411 Operand::Str(_) => true,
412 Operand::Data(name) => {
413 matches!(self.fields.get(&name.to_uppercase()), Some(Cell::Text(_)))
414 }
415 _ => false,
416 }
417 }
418
419 /// La suma de una lista de operandos.
420 fn fold_sum(&self, ops: &[Operand]) -> Decimal {
421 let mut acc = Decimal::zero();
422 for o in ops {
423 acc = acc.add(&self.eval_decimal(o));
424 }
425 acc
426 }
427
428 /// El valor actual de un campo por nombre.
429 fn field_value(&self, name: &str) -> Decimal {
430 match self.fields.get(&name.to_uppercase()) {
431 Some(Cell::Num(n)) => n.value(),
432 Some(Cell::Text(t)) => {
433 Decimal::parse(t.as_str().trim()).unwrap_or_else(|_| Decimal::zero())
434 }
435 None => Decimal::zero(),
436 }
437 }
438
439 /// Los dígitos fraccionarios de un campo numérico destino.
440 fn target_scale(&self, name: &str) -> u8 {
441 match self.fields.get(&name.to_uppercase()) {
442 Some(Cell::Num(n)) => n.picture().fraction_digits,
443 _ => 4,
444 }
445 }
446
447 /// El número de repeticiones de un `PERFORM ... TIMES`.
448 fn count_of(&self, op: &Operand) -> usize {
449 let m = self
450 .eval_decimal(op)
451 .rescale(0, Rounding::Truncate)
452 .mantissa();
453 if m < 0 {
454 0
455 } else {
456 m as usize
457 }
458 }
459}
460
461/// División con escala fija; una división por cero da cero.
462fn divide(num: Decimal, den: Decimal, scale: u8) -> Decimal {
463 num.div(&den, scale, Rounding::Truncate)
464 .unwrap_or_else(|_| Decimal::zero())
465}
466
467/// Potencia con exponente entero no negativo; en otro caso da 1.
468fn pow(base: &Decimal, exp: &Decimal) -> Decimal {
469 let e = exp.rescale(0, Rounding::Truncate).mantissa();
470 if !(0..=256).contains(&e) {
471 return Decimal::from_integer(1);
472 }
473 let mut acc = Decimal::from_integer(1);
474 for _ in 0..e {
475 acc = acc.mul(base);
476 }
477 acc
478}
479
480/// El texto que representa una constante figurativa.
481fn figurative_text(f: Figurative) -> &'static str {
482 match f {
483 Figurative::Zero => "0",
484 Figurative::Space => " ",
485 Figurative::Quote => "\"",
486 Figurative::HighValue | Figurative::LowValue | Figurative::Null => "",
487 }
488}
489
490/// El carácter de relleno de una figurativa, para `Text::fill`.
491fn figurative_fill(f: Figurative) -> char {
492 match f {
493 Figurative::Zero => '0',
494 Figurative::Quote => '"',
495 _ => ' ',
496 }
497}