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