Skip to main content

ironwork_rt/
edit.rs

1//! Editing: a numeric value laid into a numeric-edited PICTURE, alphanumeric data into an
2//! alphanumeric-edited one, DBCS data into a DBCS one with B, and de-editing back to a number.
3
4use crate::picture::Sym;
5use zarch::ebcdic;
6
7/// The characters a numeric-edited item holds for a value. `magnitude` is already aligned to the
8/// PICTURE's decimal places and within its digit positions. `point` is what a decimal point
9/// position shows: a period, or a comma under DECIMAL-POINT IS COMMA. `currency` is the currency
10/// sign value, which the first currency position holds in full.
11pub fn numeric(syms: &[Sym], digits: u32, negative: bool, magnitude: u128, blank_when_zero: bool, point: char, currency: &str) -> String {
12    let width = |s: &Sym| width(s, currency);
13    let size: usize = syms.iter().map(width).sum();
14    let digit_syms: Vec<&Sym> = syms.iter().filter(|s| s.is_digit()).collect();
15    if magnitude == 0 && (blank_when_zero || digit_syms.iter().all(|s| matches!(s, Sym::Z | Sym::Float(_)))) {
16        return " ".repeat(size);
17    }
18    if magnitude == 0 && digit_syms.iter().all(|s| matches!(s, Sym::Star)) {
19        return syms.iter().flat_map(|s| std::iter::repeat_n(if *s == Sym::Point { point } else { '*' }, width(s))).collect();
20    }
21    let text = format!("{magnitude:0width$}", width = digits as usize);
22    let mut digit_chars = text.chars().skip(text.len() - digits as usize);
23    let mut out: Vec<char> = Vec::with_capacity(size);
24    let (mut significant, mut fill) = (false, None);
25    let (mut float_char, mut float_slot) = (None, None);
26    for s in syms {
27        match *s {
28            Sym::Nine => {
29                significant = true;
30                out.push(digit_chars.next().unwrap_or('0'));
31            }
32            Sym::Z | Sym::Star => {
33                let blank = if *s == Sym::Star { '*' } else { ' ' };
34                fill = Some(blank);
35                let d = digit_chars.next().unwrap_or('0');
36                significant |= d != '0';
37                out.push(if significant { d } else { blank });
38            }
39            Sym::FloatLead(c) => {
40                float_char = Some(c);
41                out.extend(std::iter::repeat_n(' ', width(s)));
42                float_slot = Some(out.len() - 1);
43            }
44            Sym::Float(_) => {
45                let d = digit_chars.next().unwrap_or('0');
46                significant |= d != '0';
47                if significant {
48                    out.push(d);
49                } else {
50                    float_slot = Some(out.len());
51                    out.push(' ');
52                }
53            }
54            Sym::Point | Sym::Implied => {
55                significant = true;
56                if *s == Sym::Point {
57                    out.push(point);
58                }
59            }
60            Sym::Insert(c) => {
61                if significant {
62                    out.push(c);
63                } else if float_char.is_some() {
64                    float_slot = Some(out.len());
65                    out.push(' ');
66                } else {
67                    out.push(fill.unwrap_or(c));
68                }
69            }
70            Sym::Sign(c) => out.push(match (c, negative) {
71                ('+', false) => '+',
72                (_, true) => '-',
73                _ => ' ',
74            }),
75            Sym::Currency => out.extend(currency.chars()),
76            Sym::Cr => out.extend(if negative { ['C', 'R'] } else { [' ', ' '] }),
77            Sym::Db => out.extend(if negative { ['D', 'B'] } else { [' ', ' '] }),
78            Sym::Char => out.push(' '),
79        }
80    }
81    match (float_char, float_slot) {
82        (Some('$'), Some(slot)) => {
83            let value: Vec<char> = currency.chars().collect();
84            out[slot + 1 - value.len()..=slot].copy_from_slice(&value);
85        }
86        (Some(c), Some(slot)) => {
87            out[slot] = match (c, negative) {
88                ('+', false) => '+',
89                (_, true) => '-',
90                _ => ' ',
91            }
92        }
93        _ => {}
94    }
95    out.into_iter().collect()
96}
97
98/// Alphanumeric data laid into an alphanumeric-edited PICTURE: characters fill the X, A and 9
99/// positions in order; insertion characters stand where they are written.
100pub fn alphanumeric(syms: &[Sym], data: &[u8], space: u8, encode: impl Fn(char) -> u8) -> Vec<u8> {
101    let mut source = data.iter();
102    syms.iter()
103        .map(|s| match s {
104            Sym::Insert(c) => encode(*c),
105            _ => source.next().copied().unwrap_or(space),
106        })
107        .collect()
108}
109
110/// DBCS data laid into a DBCS PICTURE with B: two bytes of the data fill each G or N position in
111/// order, a DBCS space each position the data does not reach, and each B is a DBCS space.
112pub fn dbcs(syms: &[Sym], data: &[u8]) -> Vec<u8> {
113    let mut source = data.as_chunks::<2>().0.iter();
114    syms.iter()
115        .flat_map(|s| match s {
116            Sym::Insert(_) => [ebcdic::SPACE; 2],
117            _ => source.next().copied().unwrap_or([ebcdic::SPACE; 2]),
118        })
119        .collect()
120}
121
122/// A numeric-edited item's value: the digits in its digit positions, negative when it shows a
123/// minus sign, CR or DB. `currency` is the currency sign value, as for [`numeric`].
124pub fn de_edit(syms: &[Sym], text: &str, currency: &str) -> (bool, u128) {
125    let chars: Vec<char> = text.chars().collect();
126    let (mut at, mut magnitude, mut negative) = (0usize, 0u128, false);
127    for s in syms {
128        let c = chars.get(at).copied().unwrap_or(' ');
129        match s {
130            Sym::Implied => continue,
131            Sym::Cr | Sym::Db => {
132                negative |= c != ' ';
133                at += 2;
134                continue;
135            }
136            _ if s.is_digit() => magnitude = magnitude * 10 + c.to_digit(10).unwrap_or(0) as u128,
137            Sym::Sign(_) | Sym::FloatLead(_) | Sym::Float(_) => negative |= c == '-',
138            _ => {}
139        }
140        if matches!(s, Sym::Float(_)) {
141            negative |= c == '-';
142        }
143        at += width(s, currency);
144    }
145    (negative, magnitude)
146}
147
148/// Character positions `s` takes: the first currency position holds the whole currency sign value.
149fn width(s: &Sym, currency: &str) -> usize {
150    match s {
151        Sym::Currency | Sym::FloatLead('$') => currency.chars().count().max(1),
152        _ => s.width(),
153    }
154}