Skip to main content

ironwork_rt/
text.rs

1//! STRING, UNSTRING and INSPECT (lir.md ยง9.1) over the executor's handles, in the walker's order of
2//! locates, reads and stores. The inputs are `lir::text`'s plans with each receiver's MOVE, store
3//! and step plan left to its `Loc`'s kind, which `store` decides by.
4
5use crate::abend::Abend;
6use crate::host::{self, Host, Values};
7use crate::lir::{Bound, Chars, ConvertTable, Converting, Replacement, StringSource};
8use crate::storage::{Loc, Val};
9use crate::store::{self, ProgramFacts};
10use crate::strings::{self, Phrase};
11use crate::vocab::{Figurative, InspectMode, Pos};
12use numeric::precision::{Fixed, Places};
13
14type R<T> = Result<T, Abend>;
15
16/// `lir::UnstringInto`: the receiver, DELIMITER IN and COUNT IN.
17#[derive(Clone, Copy, Debug)]
18pub struct UnstringField<P> {
19    pub target: P,
20    pub delimiter: Option<P>,
21    pub count: Option<P>,
22}
23
24/// `lir::InspectPhrase`, TALLYING's counter a place.
25#[derive(Clone, Debug)]
26pub struct InspectPhrase<P, O> {
27    pub mode: InspectMode,
28    pub pattern: Option<Chars<P, O>>,
29    pub by: Option<Replacement<P, O>>,
30    pub counter: Option<P>,
31    pub bounds: Vec<Bound<P, O>>,
32}
33
34/// An operand's characters: an item's storage, or a value as `store::natural_bytes` gives it.
35pub fn chars<P: Copy, O>(x: &mut impl Values<P, O>, c: &Chars<P, O>, pos: Pos) -> R<Vec<u8>> {
36    match c {
37        Chars::Literal(bytes) => Ok(bytes.clone()),
38        Chars::Place(p) => {
39            let loc = x.locate(*p, false)?;
40            Ok(store::bytes(x.mem(), loc).to_vec())
41        }
42        Chars::Value(o) => {
43            let val = x.value(o, pos)?;
44            store::natural_bytes(&x.facts(), val, pos)
45        }
46    }
47}
48
49/// STRING: returns whether it overflowed. The pointer is read before the first source and stored
50/// after the last, and each source is read after the ones before it are stored.
51pub fn string<P: Copy, O>(x: &mut impl Values<P, O>, into: P, pointer: Option<P>, sources: &[StringSource<P, O>], pos: Pos) -> R<bool> {
52    let dest = x.locate(into, true)?;
53    let mut at = match pointer {
54        Some(p) => x.integer(p, pos)?,
55        None => 1,
56    };
57    let len = dest.len as i64;
58    let mut overflow = at < 1 || at > len;
59    if !overflow {
60        'sources: for source in sources {
61            let bytes = chars(x, &source.chars, pos)?;
62            let delimiter = match &source.delimiter {
63                None => None,
64                Some(d) => Some(chars(x, d, pos)?),
65            };
66            for b in strings::delimited(&bytes, delimiter.as_deref()) {
67                if at > len {
68                    overflow = true;
69                    break 'sources;
70                }
71                x.mem()[dest.offset + at as usize - 1] = b;
72                at += 1;
73            }
74        }
75    }
76    if let Some(p) = pointer {
77        host::set_integer(x, p, at, pos)?;
78    }
79    Ok(overflow)
80}
81
82/// UNSTRING: returns whether it overflowed. `delimiters` are the DELIMITED BY operands, each true
83/// for ALL; `tallying` is the TALLYING IN item.
84pub fn unstring<P: Copy, O>(
85    x: &mut impl Values<P, O>,
86    source: P,
87    pointer: Option<P>,
88    delimiters: &[(bool, Chars<P, O>)],
89    into: &[UnstringField<P>],
90    tallying: Option<P>,
91    pos: Pos,
92) -> R<bool> {
93    let source_loc = x.locate(source, false)?;
94    let source = store::bytes(x.mem(), source_loc).to_vec();
95    let len = source.len() as i64;
96    let mut at = match pointer {
97        Some(p) => x.integer(p, pos)?,
98        None => 1,
99    };
100    let mut found = Vec::with_capacity(delimiters.len());
101    for (all, d) in delimiters {
102        found.push((*all, chars(x, d, pos)?));
103    }
104    let delimiters = found;
105    let mut overflow = at < 1 || at > len;
106    let mut fields = 0i64;
107    if !overflow {
108        for field in into {
109            if at > len {
110                break;
111            }
112            let start = at as usize - 1;
113            let dest = x.locate(field.target, true)?;
114            let (end, matched) = if delimiters.is_empty() {
115                ((start + dest.len).min(source.len()), None)
116            } else {
117                match strings::next_delimiter(&source, start, &delimiters) {
118                    Some((at, k)) => (at, Some(k)),
119                    None => (source.len(), None),
120                }
121            };
122            x.assign(dest, Val::Bytes(source[start..end].to_vec()), None, pos)?;
123            let delimiter = matched.map(|k| delimiters[k].1.clone());
124            if let Some(p) = field.delimiter {
125                let d = x.locate(p, true)?;
126                x.assign(d, delimiter.clone().map_or(Val::Fig(Figurative::Space), Val::Bytes), None, pos)?;
127            }
128            if let Some(p) = field.count {
129                host::set_integer(x, p, (end - start) as i64, pos)?;
130            }
131            let next = match matched {
132                Some(k) => strings::past_delimiter(&source, end, &delimiters[k].1, delimiters[k].0),
133                None => end,
134            };
135            at = next as i64 + 1;
136            fields += 1;
137        }
138        overflow = at <= len;
139    }
140    if let Some(p) = pointer {
141        host::set_integer(x, p, at, pos)?;
142    }
143    if let Some(p) = tallying {
144        let dest = x.locate(p, false)?;
145        add_count(x, dest, fields, "TALLYING IN needs a numeric item", pos)?;
146    }
147    Ok(overflow)
148}
149
150/// INSPECT: TALLYING counts over the item as it is, then REPLACING and CONVERTING change it.
151pub fn inspect<P: Copy, O>(
152    x: &mut impl Values<P, O>,
153    target: P,
154    tallying: &[InspectPhrase<P, O>],
155    replacing: &[InspectPhrase<P, O>],
156    converting: Option<&Converting<P, O>>,
157    pos: Pos,
158) -> R<()> {
159    let loc = x.locate(target, false)?;
160    let mut data = store::bytes(x.mem(), loc).to_vec();
161    count(x, &mut data, 1, tallying, pos)?;
162    let mut changes = phrases(x, &data, 1, replacing, pos)?;
163    if let Some(c) = converting {
164        let pairs = match &c.table {
165            ConvertTable::Built(pairs) => pairs.clone(),
166            ConvertTable::Operands { from, to } => {
167                let (from, to) = (chars(x, from, pos)?, chars(x, to, pos)?);
168                if from.len() != to.len() {
169                    return Err(Abend::ironwork("CONVERTING needs operands of the same length", pos));
170                }
171                let mut pairs: Vec<(u8, u8)> = Vec::new();
172                for (f, t) in from.into_iter().zip(to) {
173                    if !pairs.iter().any(|&(seen, _)| seen == f) {
174                        pairs.push((f, t));
175                    }
176                }
177                pairs
178            }
179        };
180        let (start, end) = region(x, &data, 1, &c.bounds, pos)?;
181        for (f, t) in pairs {
182            changes.push(Phrase { mode: InspectMode::All, pattern: vec![f], by: Some(vec![t]), start, end });
183        }
184    }
185    strings::inspect(&mut data, 1, &changes);
186    store::write(x.mem(), loc, &data);
187    Ok(())
188}
189
190/// INSPECT TALLYING of a function's value, evaluated once before the phrases' operands. A national
191/// value's character positions are two bytes, and a figurative constant is one national character
192/// (assumption C191).
193pub fn tally<P: Copy, O>(x: &mut impl Values<P, O>, subject: &O, tallying: &[InspectPhrase<P, O>], pos: Pos) -> R<()> {
194    let val = x.value(subject, pos)?;
195    let unit = if matches!(val, Val::National(_)) { 2 } else { 1 };
196    let mut data = store::natural_bytes(&x.facts(), val, pos)?;
197    count(x, &mut data, unit, tallying, pos)
198}
199
200fn count<P: Copy, O>(x: &mut impl Values<P, O>, data: &mut [u8], unit: usize, tallying: &[InspectPhrase<P, O>], pos: Pos) -> R<()> {
201    let tallied = phrases(x, data, unit, tallying, pos)?;
202    let counts = strings::inspect(data, unit, &tallied);
203    for (phrase, count) in tallying.iter().zip(counts) {
204        let Some(counter) = phrase.counter else { continue };
205        let dest = x.locate(counter, false)?;
206        add_count(x, dest, count, "a TALLYING counter must be numeric", pos)?;
207    }
208    Ok(())
209}
210
211/// `chars`, where a figurative constant is a national character when a position is two bytes.
212fn chars_in<P: Copy, O>(x: &mut impl Values<P, O>, c: &Chars<P, O>, unit: usize, pos: Pos) -> R<Vec<u8>> {
213    match c {
214        Chars::Value(o) if unit == 2 => match x.value(o, pos)? {
215            Val::Fig(f) => Ok(store::figurative_unit(f, x.facts().options().quote).to_be_bytes().to_vec()),
216            val => store::natural_bytes(&x.facts(), val, pos),
217        },
218        c => chars(x, c, pos),
219    }
220}
221
222/// TALLYING's add: `n` added to a numeric item, stored with no size error.
223fn add_count<P: Copy>(x: &mut impl Host<P>, dest: Loc, n: i64, not_numeric: &str, pos: Pos) -> R<()> {
224    let Val::Num(current) = host::read(x, dest, pos)? else {
225        return Err(Abend::ironwork(not_numeric, pos));
226    };
227    let arith = x.facts().options().arith;
228    let total = current.add(Fixed::new(n as i128, Places::new(19, 0)), 0, arith).map_err(|_| Abend::ironwork("TALLYING", pos))?;
229    x.store_fixed(dest, &total, pos)
230}
231
232/// The part of `data` BEFORE and AFTER INITIAL leave a phrase: the last of each applies.
233fn region<P: Copy, O>(x: &mut impl Values<P, O>, data: &[u8], unit: usize, bounds: &[Bound<P, O>], pos: Pos) -> R<(usize, usize)> {
234    let (mut before, mut after) = (None, None);
235    for b in bounds {
236        let v = chars_in(x, &b.value, unit, pos)?;
237        if b.after { after = Some(v) } else { before = Some(v) }
238    }
239    Ok(strings::region(data, unit, before.as_deref(), after.as_deref()))
240}
241
242fn phrases<P: Copy, O>(x: &mut impl Values<P, O>, data: &[u8], unit: usize, phrases: &[InspectPhrase<P, O>], pos: Pos) -> R<Vec<Phrase>> {
243    let mut out = Vec::new();
244    for p in phrases {
245        let pattern = match &p.pattern {
246            Some(c) => chars_in(x, c, unit, pos)?,
247            None => Vec::new(),
248        };
249        let len = pattern.len().max(1);
250        let by = match &p.by {
251            Some(Replacement::Fill(b)) => Some(vec![*b; len]),
252            Some(Replacement::Chars(c)) => Some(chars(x, c, pos)?),
253            None => None,
254        };
255        if by.as_ref().is_some_and(|b| b.len() != len) {
256            return Err(Abend::ironwork("a REPLACING value must be as long as what it replaces", pos));
257        }
258        let (start, end) = region(x, data, unit, &p.bounds, pos)?;
259        out.push(Phrase { mode: p.mode, pattern, by, start, end });
260    }
261    Ok(out)
262}