tawasuyu

tawasuyu/brahman

Rust 230TOML 69JSON 14HTML 12901 Definiciones

⎇ 634a43006ac4257288af40320b5360e9f157ab09 crates/modules/charka/charka-shadow/src/interp.rs Rust · 847 líneas · 29.5 KB Historia Quién tocó qué crudo

feat(charka): PICTURE de edición — Z, coma de millares y punto decimal 634a43006 · sergio ·

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}