Skip to main content

ironwork_exec/
strings.rs

1//! The byte-level work of STRING, UNSTRING and INSPECT, apart from storage and operands.
2
3use syntax::ast::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).
18pub fn region(data: &[u8], before: Option<&[u8]>, after: Option<&[u8]>) -> (usize, usize) {
19    let find = |needle: &[u8]| (!needle.is_empty()).then(|| data.windows(needle.len()).position(|w| w == 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.
34pub fn inspect(data: &mut [u8], 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 { 1 } 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(1);
73    }
74    counts
75}
76
77/// Where the next UNSTRING field ends and which delimiter ended it: the earliest position at or
78/// after `from` where any delimiter matches, the first listed winning at a tie.
79pub fn next_delimiter(source: &[u8], from: usize, delimiters: &[(bool, Vec<u8>)]) -> Option<(usize, usize)> {
80    (from..source.len()).find_map(|p| delimiters.iter().position(|(_, d)| !d.is_empty() && source[p..].starts_with(d)).map(|k| (p, k)))
81}
82
83/// The end of a delimiter at `at`, taking every repetition when it is DELIMITED BY ALL.
84pub fn past_delimiter(source: &[u8], at: usize, delimiter: &[u8], all: bool) -> usize {
85    let mut end = at + delimiter.len();
86    while all && !delimiter.is_empty() && source[end..].starts_with(delimiter) {
87        end += delimiter.len();
88    }
89    end
90}
91
92/// STRING's view of a sending item: all of it for DELIMITED BY SIZE, or up to the delimiter.
93pub fn delimited(bytes: &[u8], delimiter: Option<&[u8]>) -> Vec<u8> {
94    match delimiter {
95        Some(d) if !d.is_empty() => {
96            let end = bytes.windows(d.len()).position(|w| w == d).unwrap_or(bytes.len());
97            bytes[..end].to_vec()
98        }
99        _ => bytes.to_vec(),
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    fn phrase(mode: InspectMode, pattern: &str, by: Option<&str>, region: (usize, usize)) -> Phrase {
108        Phrase { mode, pattern: pattern.as_bytes().to_vec(), by: by.map(|b| b.as_bytes().to_vec()), start: region.0, end: region.1 }
109    }
110
111    #[test]
112    fn tallying_all_leading_and_characters() {
113        let mut data = b"  AABA  ".to_vec();
114        let n = data.len();
115        let counts = inspect(&mut data, &[phrase(InspectMode::Leading, " ", None, (0, n)), phrase(InspectMode::All, "A", None, (0, n))]);
116        assert_eq!(counts, [2, 3]);
117        let counts = inspect(&mut data, &[phrase(InspectMode::Characters, "", None, (0, n))]);
118        assert_eq!(counts, [8]);
119    }
120
121    #[test]
122    fn replacing_first_all_and_within_bounds() {
123        let mut data = b"A,B,C.D,E".to_vec();
124        let (start, end) = region(&data, Some(b"."), None);
125        inspect(&mut data, &[phrase(InspectMode::All, ",", Some(";"), (start, end))]);
126        assert_eq!(data, b"A;B;C.D,E");
127        let mut data = b"XAXAX".to_vec();
128        inspect(&mut data, &[phrase(InspectMode::First, "X", Some("Y"), (0, 5))]);
129        assert_eq!(data, b"YAXAX");
130        let mut data = b"00012".to_vec();
131        inspect(&mut data, &[phrase(InspectMode::Leading, "0", Some(" "), (0, 5))]);
132        assert_eq!(data, b"   12");
133    }
134
135    #[test]
136    fn after_initial_with_no_occurrence_leaves_nothing() {
137        assert_eq!(region(b"ABC", None, Some(b"Z")), (3, 3));
138        assert_eq!(region(b"ABCD", Some(b"D"), Some(b"A")), (1, 3));
139    }
140
141    #[test]
142    fn a_leading_run_ends_at_the_first_other_character() {
143        let mut data = b"**A**".to_vec();
144        assert_eq!(inspect(&mut data, &[phrase(InspectMode::Leading, "*", None, (0, 5))]), [2]);
145    }
146
147    #[test]
148    fn delimiters() {
149        let d = vec![(true, b" ".to_vec()), (false, b",".to_vec())];
150        assert_eq!(next_delimiter(b"AB  C,D", 0, &d), Some((2, 0)));
151        assert_eq!(past_delimiter(b"AB  C,D", 2, b" ", true), 4);
152        assert_eq!(next_delimiter(b"AB  C,D", 4, &d), Some((5, 1)));
153        assert_eq!(delimited(b"JOHN  SMITH", Some(b" ")), b"JOHN");
154    }
155}