Skip to main content

ironwork_rt/
strings.rs

1//! The byte-level work of STRING, UNSTRING and INSPECT, apart from storage and operands.
2
3use crate::vocab::InspectMode;
4
5/// One INSPECT phrase, resolved to bytes: `pattern` is empty for CHARACTERS.
6pub struct Phrase {
7    pub mode: InspectMode,
8    pub pattern: Vec<u8>,
9    pub by: Option<Vec<u8>>,
10    /// The region the phrase applies to, from its BEFORE and AFTER INITIAL bounds.
11    pub start: usize,
12    pub end: usize,
13}
14
15/// The region `[start, end)` of `data` that BEFORE and AFTER INITIAL leave: BEFORE ends it at the
16/// first occurrence of its value, AFTER starts it after the first occurrence of its value (or
17/// leaves nothing when the value does not occur). A character position is `unit` bytes.
18pub fn region(data: &[u8], unit: usize, before: Option<&[u8]>, after: Option<&[u8]>) -> (usize, usize) {
19    let find = |needle: &[u8]| (!needle.is_empty()).then(|| (0..=data.len().saturating_sub(needle.len())).step_by(unit).find(|&p| data[p..].starts_with(needle))).flatten();
20    let start = match after {
21        Some(a) => find(a).map_or(data.len(), |p| p + a.len()),
22        None => 0,
23    };
24    let end = match before {
25        Some(b) => find(b).unwrap_or(data.len()),
26        None => data.len(),
27    };
28    (start, end.max(start))
29}
30
31/// Scans `data` left to right; at each position the first phrase that applies and matches takes
32/// the characters it matches. Returns how many times each phrase matched, replacing as it goes
33/// when phrases carry a BY value. A character position is `unit` bytes.
34pub fn inspect(data: &mut [u8], unit: usize, phrases: &[Phrase]) -> Vec<i64> {
35    let mut counts = vec![0i64; phrases.len()];
36    let mut active = vec![true; phrases.len()];
37    let mut next_leading: Vec<usize> = phrases.iter().map(|p| p.start).collect();
38    let mut at = 0;
39    while at < data.len() {
40        let mut taken = 0;
41        for (k, phrase) in phrases.iter().enumerate() {
42            if !active[k] || at < phrase.start || at >= phrase.end {
43                continue;
44            }
45            if phrase.mode == InspectMode::Leading && at != next_leading[k] {
46                active[k] = false;
47                continue;
48            }
49            let len = if phrase.mode == InspectMode::Characters { unit } else { phrase.pattern.len() };
50            let fits = len > 0 && at + len <= phrase.end;
51            let hit = fits && (phrase.mode == InspectMode::Characters || data[at..at + len] == phrase.pattern[..]);
52            if !hit {
53                if phrase.mode == InspectMode::Leading {
54                    active[k] = false;
55                }
56                continue;
57            }
58            counts[k] += 1;
59            if let Some(by) = &phrase.by {
60                for (i, b) in by.iter().cycle().take(len).enumerate() {
61                    data[at + i] = *b;
62                }
63            }
64            match phrase.mode {
65                InspectMode::First => active[k] = false,
66                InspectMode::Leading => next_leading[k] = at + len,
67                _ => {}
68            }
69            taken = len;
70            break;
71        }
72        at += taken.max(unit);
73    }
74    counts
75}
76
77/// Where the next UNSTRING field ends and which delimiter ended it: the earliest character
78/// position, `unit` bytes each, at or after `from` where any delimiter matches, the first listed
79/// winning at a tie.
80pub fn next_delimiter(source: &[u8], from: usize, delimiters: &[(bool, Vec<u8>)], unit: usize) -> Option<(usize, usize)> {
81    (from..source.len()).step_by(unit).find_map(|p| delimiters.iter().position(|(_, d)| !d.is_empty() && source[p..].starts_with(d)).map(|k| (p, k)))
82}
83
84/// The end of a delimiter at `at`, taking every repetition when it is DELIMITED BY ALL.
85pub fn past_delimiter(source: &[u8], at: usize, delimiter: &[u8], all: bool) -> usize {
86    let mut end = at + delimiter.len();
87    while all && !delimiter.is_empty() && source[end..].starts_with(delimiter) {
88        end += delimiter.len();
89    }
90    end
91}
92
93/// STRING's view of a sending item: all of it for DELIMITED BY SIZE, or up to the delimiter.
94/// The delimiter is matched at character positions, `unit` bytes each.
95pub fn delimited(bytes: &[u8], delimiter: Option<&[u8]>, unit: usize) -> Vec<u8> {
96    match delimiter {
97        Some(d) if !d.is_empty() => {
98            let end = (0..=bytes.len().saturating_sub(d.len())).step_by(unit).find(|&p| bytes[p..].starts_with(d)).unwrap_or(bytes.len());
99            bytes[..end.min(bytes.len())].to_vec()
100        }
101        _ => bytes.to_vec(),
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    fn phrase(mode: InspectMode, pattern: &str, by: Option<&str>, region: (usize, usize)) -> Phrase {
110        Phrase { mode, pattern: pattern.as_bytes().to_vec(), by: by.map(|b| b.as_bytes().to_vec()), start: region.0, end: region.1 }
111    }
112
113    #[test]
114    fn tallying_all_leading_and_characters() {
115        let mut data = b"  AABA  ".to_vec();
116        let n = data.len();
117        let counts = inspect(&mut data, 1, &[phrase(InspectMode::Leading, " ", None, (0, n)), phrase(InspectMode::All, "A", None, (0, n))]);
118        assert_eq!(counts, [2, 3]);
119        let counts = inspect(&mut data, 1, &[phrase(InspectMode::Characters, "", None, (0, n))]);
120        assert_eq!(counts, [8]);
121    }
122
123    #[test]
124    fn replacing_first_all_and_within_bounds() {
125        let mut data = b"A,B,C.D,E".to_vec();
126        let (start, end) = region(&data, 1, Some(b"."), None);
127        inspect(&mut data, 1, &[phrase(InspectMode::All, ",", Some(";"), (start, end))]);
128        assert_eq!(data, b"A;B;C.D,E");
129        let mut data = b"XAXAX".to_vec();
130        inspect(&mut data, 1, &[phrase(InspectMode::First, "X", Some("Y"), (0, 5))]);
131        assert_eq!(data, b"YAXAX");
132        let mut data = b"00012".to_vec();
133        inspect(&mut data, 1, &[phrase(InspectMode::Leading, "0", Some(" "), (0, 5))]);
134        assert_eq!(data, b"   12");
135    }
136
137    #[test]
138    fn after_initial_with_no_occurrence_leaves_nothing() {
139        assert_eq!(region(b"ABC", 1, None, Some(b"Z")), (3, 3));
140        assert_eq!(region(b"ABCD", 1, Some(b"D"), Some(b"A")), (1, 3));
141    }
142
143    #[test]
144    fn a_leading_run_ends_at_the_first_other_character() {
145        let mut data = b"**A**".to_vec();
146        assert_eq!(inspect(&mut data, 1, &[phrase(InspectMode::Leading, "*", None, (0, 5))]), [2]);
147    }
148
149    #[test]
150    fn national_characters_are_two_bytes() {
151        let mut data = vec![0x00, 0x41, 0x41, 0x00, 0x20, 0x00, 0x00, 0x20];
152        let n = data.len();
153        let space = Phrase { mode: InspectMode::All, pattern: vec![0x00, 0x20], by: None, start: 0, end: n };
154        let characters = Phrase { mode: InspectMode::Characters, pattern: vec![], by: None, start: 0, end: n };
155        assert_eq!(inspect(&mut data, 2, &[space, characters]), [1, 3]);
156        assert_eq!(region(&data, 2, Some(&[0x00, 0x20]), Some(&[0x00, 0x41])), (2, 6));
157    }
158
159    #[test]
160    fn delimiters() {
161        let d = vec![(true, b" ".to_vec()), (false, b",".to_vec())];
162        assert_eq!(next_delimiter(b"AB  C,D", 0, &d, 1), Some((2, 0)));
163        assert_eq!(past_delimiter(b"AB  C,D", 2, b" ", true), 4);
164        assert_eq!(next_delimiter(b"AB  C,D", 4, &d, 1), Some((5, 1)));
165        assert_eq!(delimited(b"JOHN  SMITH", Some(b" "), 1), b"JOHN");
166    }
167
168    #[test]
169    fn two_byte_characters_match_delimiters_only_at_their_own_positions() {
170        let data = [0x42, 0xC1, 0x42, 0x40, 0x42, 0xC2, 0x40, 0x40];
171        assert_eq!(delimited(&data, Some(&[0x40, 0x42]), 2), data, "X'4042' straddles two characters");
172        assert_eq!(delimited(&data, Some(&[0x40, 0x42]), 1), &data[..3]);
173        assert_eq!(delimited(&data, Some(&[0x40, 0x40]), 2), &data[..6]);
174        assert_eq!(next_delimiter(&data, 0, &[(false, vec![0x40, 0x40])], 2), Some((6, 0)));
175    }
176}