tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Definiciones

⎇ 82ba0b7a1a04b7ff2d7e2175ac74766da0d16066 crates/modules/charka/charka-shadow/src/interp.rs Rust · 738 líneas · 25.6 KB Historia Quién tocó qué crudo

feat(charka): SET ... TO TRUE — escribir nombres de condición (88) 82ba0b7a1 · 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, 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}