Skip to main content

jlabel_question/
parse_position.rs

1//! Estimate the position from pattern
2
3use crate::position::AllPosition;
4use crate::position::BooleanPosition::*;
5use crate::position::CategoryPosition::*;
6use crate::position::PhonePosition::*;
7use crate::position::SignedRangePosition::*;
8use crate::position::UndefinedPotision::*;
9use crate::position::UnsignedRangePosition::*;
10use AllPosition::*;
11
12/// Errors from position parser.
13#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
14pub enum PositionError {
15    /// Could not determine the position uniquely.
16    #[error("No matching position found")]
17    NoMatchingPosition,
18    /// The position is not `P1`, so it requires an asterisk as the first character of the pattern.
19    #[error("The first character should be asterisk in this position")]
20    MissingPrefixAsterisk,
21    /// The position is not `K3`, so it requires an asterisk as the last character of the pattern.
22    #[error("The last character should be asterisk in this position")]
23    MissingSuffixAsterisk,
24    /// The prefix (string before the range section) conflicts with the estimated position.
25    #[error("Prefix has unknown sequence")]
26    PrefixVerifyError,
27    /// The suffix (string after the range section) conflicts with the estimated position.
28    #[error("Suffix has unknown sequence")]
29    SuffixVerifyError,
30    /// An asterisk(s) was found in the range section. This implies the pattern matches across multiple fields.
31    #[error("Asterisk is not allowed in range section")]
32    AsteriskInRange,
33    /// Range section is empty. This pattern does not match any label.
34    #[error("Range is empty")]
35    EmptyRange,
36}
37
38/// Estimates the position the pattern is pointing at.
39pub(crate) fn estimate_position(pattern: &str) -> Result<(AllPosition, &str), PositionError> {
40    let split = PositionSplit::new(pattern);
41    let position = split.match_position()?;
42    split.verify(position)?;
43
44    Ok((position, split.into_range()?))
45}
46
47struct PositionSplit<'a> {
48    prefix: &'a str,
49    range: &'a str,
50    suffix: &'a str,
51    asterisks: (bool, bool),
52}
53
54impl<'a> PositionSplit<'a> {
55    pub fn new(pattern: &'a str) -> Self {
56        let (pattern, asterisks) = Self::trim_asterisk(pattern);
57
58        // Match to the next char of prefix
59        // /A:
60        //    ^
61        let mut prefix = pattern
62            .bytes()
63            .position(|b| "!#%&+-=@^_|:".contains(b as char))
64            .map(|i| i + 1)
65            .unwrap_or(0);
66
67        // Match to the first char of suffix
68        // /A:
69        // ^
70        let mut suffix = pattern
71            .bytes()
72            .rev()
73            .position(|b| "!#%&+-=@^_|/".contains(b as char))
74            .map(|i| pattern.len() - i - 1)
75            .unwrap_or(pattern.len());
76
77        // If there is only one prefix/suffix delimiter:
78        // /A:
79        // ^s ^p
80        if prefix > suffix {
81            if prefix == pattern.len() {
82                prefix = 0;
83            } else {
84                suffix = pattern.len();
85            }
86        }
87
88        Self {
89            prefix: &pattern[..prefix],
90            range: &pattern[prefix..suffix],
91            suffix: &pattern[suffix..],
92            asterisks,
93        }
94    }
95
96    fn trim_asterisk(mut pattern: &str) -> (&str, (bool, bool)) {
97        let mut stars = (false, false);
98        if pattern.starts_with('*') {
99            pattern = &pattern[1..];
100            stars.0 = true;
101        }
102        if pattern.ends_with('*') {
103            pattern = &pattern[..pattern.len() - 1];
104            stars.1 = true;
105        }
106        (pattern, stars)
107    }
108
109    pub fn match_position(&self) -> Result<AllPosition, PositionError> {
110        if self.suffix.is_empty() && !self.asterisks.1 {
111            // no suffix and no `*` at the end of pattern
112            return Ok(UnsignedRange(K3));
113        }
114
115        if let Some(position) = prefix_match(self.prefix) {
116            return Ok(position);
117        }
118
119        if let Some(position) = suffix_match(self.suffix) {
120            return Ok(position);
121        }
122
123        if let (Some(pchar), Some(schar)) =
124            (self.prefix.bytes().next_back(), self.suffix.bytes().next())
125        {
126            if let Some(position) = combination_match(pchar, schar) {
127                return Ok(position);
128            }
129        }
130
131        Err(PositionError::NoMatchingPosition)
132    }
133
134    pub fn verify(&self, position: AllPosition) -> Result<(), PositionError> {
135        // Check asterisk
136        if position != Phone(P1) && !self.asterisks.0 {
137            return Err(PositionError::MissingPrefixAsterisk);
138        }
139        if position != UnsignedRange(K3) && !self.asterisks.1 {
140            return Err(PositionError::MissingSuffixAsterisk);
141        }
142
143        // Check prefix and suffix
144        let (rprefix, rsuffix) = reverse_hint(position);
145        if !rprefix.ends_with(self.prefix) {
146            return Err(PositionError::PrefixVerifyError);
147        }
148        if !rsuffix.starts_with(self.suffix) {
149            return Err(PositionError::SuffixVerifyError);
150        }
151
152        Ok(())
153    }
154
155    pub fn into_range(self) -> Result<&'a str, PositionError> {
156        if self.range.is_empty() {
157            return Err(PositionError::EmptyRange);
158        }
159        if self.range.contains('*') {
160            return Err(PositionError::AsteriskInRange);
161        }
162        Ok(self.range)
163    }
164}
165
166fn prefix_match(prefix: &str) -> Option<AllPosition> {
167    let mut bytes = prefix.bytes();
168    match bytes.next_back()? {
169        b'^' => Some(Phone(P2)),
170        b'=' => Some(Phone(P5)),
171        b'!' => Some(Boolean(E3)),
172        b'#' => Some(Boolean(F3)),
173        b'%' => Some(Boolean(G3)),
174        b'&' => Some(UnsignedRange(I5)),
175        b':' => match bytes.next_back()? {
176            b'A' => Some(SignedRange(A1)),
177            b'B' => Some(Category(B1)),
178            b'C' => Some(Category(C1)),
179            b'D' => Some(Category(D1)),
180            b'E' => Some(UnsignedRange(E1)),
181            b'F' => Some(UnsignedRange(F1)),
182            b'G' => Some(UnsignedRange(G1)),
183            b'H' => Some(UnsignedRange(H1)),
184            b'I' => Some(UnsignedRange(I1)),
185            b'J' => Some(UnsignedRange(J1)),
186            b'K' => Some(UnsignedRange(K1)),
187            _ => None,
188        },
189        _ => None,
190    }
191}
192fn suffix_match(suffix: &str) -> Option<AllPosition> {
193    let mut bytes = suffix.bytes();
194    match bytes.next()? {
195        b'^' => Some(Phone(P1)),
196        b'=' => Some(Phone(P4)),
197        b'!' => Some(UnsignedRange(E2)),
198        b'#' => Some(UnsignedRange(F2)),
199        b'%' => Some(UnsignedRange(G2)),
200        b'&' => Some(UnsignedRange(I4)),
201        b'/' => match bytes.next()? {
202            b'A' => Some(Phone(P5)),
203            b'B' => Some(UnsignedRange(A3)),
204            b'C' => Some(Category(B3)),
205            b'D' => Some(Category(C3)),
206            b'E' => Some(Category(D3)),
207            b'F' => Some(Boolean(E5)),
208            b'G' => Some(UnsignedRange(F8)),
209            b'H' => Some(Boolean(G5)),
210            b'I' => Some(UnsignedRange(H2)),
211            b'J' => Some(UnsignedRange(I8)),
212            b'K' => Some(UnsignedRange(J2)),
213            _ => None,
214        },
215        _ => None,
216    }
217}
218fn combination_match(prefix: u8, suffix: u8) -> Option<AllPosition> {
219    // The following conditions were removed:
220    // - Conditions that are matched by prefix_match or suffix_match
221    // - Conditions that cannot uniquely determine the position
222    match (prefix, suffix) {
223        (b'-', b'+') => Some(Phone(P3)),
224
225        (b'+', b'+') => Some(UnsignedRange(A2)),
226
227        (b'-', b'_') => Some(Category(B2)),
228
229        (b'_', b'+') => Some(Category(C2)),
230
231        (b'+', b'_') => Some(Category(D2)),
232
233        (b'_', b'-') => Some(Undefined(E4)),
234        (b'-', b'/') => Some(Boolean(E5)),
235
236        (b'_', b'@') => Some(Undefined(F4)),
237        (b'@', b'_') => Some(UnsignedRange(F5)),
238        (b'_', b'|') => Some(UnsignedRange(F6)),
239        (b'|', b'_') => Some(UnsignedRange(F7)),
240
241        (b'_', b'_') => Some(Undefined(G4)),
242
243        (b'-', b'@') => Some(UnsignedRange(I2)),
244        (b'@', b'+') => Some(UnsignedRange(I3)),
245        (b'-', b'|') => Some(UnsignedRange(I6)),
246        (b'|', b'+') => Some(UnsignedRange(I7)),
247
248        (b'+', b'-') => Some(UnsignedRange(K2)),
249
250        _ => None,
251    }
252}
253
254fn reverse_hint(position: AllPosition) -> (&'static str, &'static str) {
255    match position {
256        Phone(P1) => ("", "^"),
257        Phone(P2) => ("^", "-"),
258        Phone(P3) => ("-", "+"),
259        Phone(P4) => ("+", "="),
260        Phone(P5) => ("=", "/A:"),
261
262        SignedRange(A1) => ("/A:", "+"),
263        UnsignedRange(A2) => ("+", "+"),
264        UnsignedRange(A3) => ("+", "/B:"),
265
266        Category(B1) => ("/B:", "-"),
267        Category(B2) => ("-", "_"),
268        Category(B3) => ("_", "/C:"),
269
270        Category(C1) => ("/C:", "_"),
271        Category(C2) => ("_", "+"),
272        Category(C3) => ("+", "/D:"),
273
274        Category(D1) => ("/D:", "+"),
275        Category(D2) => ("+", "_"),
276        Category(D3) => ("_", "/E:"),
277
278        UnsignedRange(E1) => ("/E:", "_"),
279        UnsignedRange(E2) => ("_", "!"),
280        Boolean(E3) => ("!", "_"),
281        Undefined(E4) => ("_", "-"),
282        Boolean(E5) => ("-", "/F:"),
283
284        UnsignedRange(F1) => ("/F:", "_"),
285        UnsignedRange(F2) => ("_", "#"),
286        Boolean(F3) => ("#", "_"),
287        Undefined(F4) => ("_", "@"),
288        UnsignedRange(F5) => ("@", "_"),
289        UnsignedRange(F6) => ("_", "|"),
290        UnsignedRange(F7) => ("|", "_"),
291        UnsignedRange(F8) => ("_", "/G:"),
292
293        UnsignedRange(G1) => ("/G:", "_"),
294        UnsignedRange(G2) => ("_", "%"),
295        Boolean(G3) => ("%", "_"),
296        Undefined(G4) => ("_", "_"),
297        Boolean(G5) => ("_", "/H:"),
298
299        UnsignedRange(H1) => ("/H:", "_"),
300        UnsignedRange(H2) => ("_", "/I:"),
301
302        UnsignedRange(I1) => ("/I:", "-"),
303        UnsignedRange(I2) => ("-", "@"),
304        UnsignedRange(I3) => ("@", "+"),
305        UnsignedRange(I4) => ("+", "&"),
306        UnsignedRange(I5) => ("&", "-"),
307        UnsignedRange(I6) => ("-", "|"),
308        UnsignedRange(I7) => ("|", "+"),
309        UnsignedRange(I8) => ("+", "/J:"),
310
311        UnsignedRange(J1) => ("/J:", "_"),
312        UnsignedRange(J2) => ("_", "/K:"),
313
314        UnsignedRange(K1) => ("/K:", "+"),
315        UnsignedRange(K2) => ("+", "-"),
316        UnsignedRange(K3) => ("-", ""),
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use crate::{
323        parse_position::{PositionError, estimate_position},
324        position::{
325            AllPosition::*, BooleanPosition::*, CategoryPosition::*, PhonePosition::*,
326            SignedRangePosition::*, UndefinedPotision::*, UnsignedRangePosition::*,
327        },
328    };
329
330    #[test]
331    fn basic() {
332        assert_eq!(estimate_position("a^*"), Ok((Phone(P1), "a")));
333        assert_eq!(estimate_position("*/A:-1+*"), Ok((SignedRange(A1), "-1")));
334        assert_eq!(estimate_position("*/A:-??+*"), Ok((SignedRange(A1), "-??")));
335        assert_eq!(estimate_position("*|?+*"), Ok((UnsignedRange(I7), "?")));
336        assert_eq!(estimate_position("*-1"), Ok((UnsignedRange(K3), "1")));
337        assert_eq!(estimate_position("*_42/I:*"), Ok((UnsignedRange(H2), "42")));
338        assert_eq!(estimate_position("*/B:17-*"), Ok((Category(B1), "17")));
339        assert_eq!(estimate_position("*_xx-*"), Ok((Undefined(E4), "xx")));
340        assert_eq!(estimate_position("*_xx@*"), Ok((Undefined(F4), "xx")));
341        assert_eq!(estimate_position("*_xx_*"), Ok((Undefined(G4), "xx")));
342        assert_eq!(estimate_position("*/A:-1?+*"), Ok((SignedRange(A1), "-1?")));
343    }
344
345    #[test]
346    fn basic_fail() {
347        assert_eq!(estimate_position("*"), Err(PositionError::EmptyRange));
348        assert_eq!(
349            estimate_position(":*"),
350            Err(PositionError::NoMatchingPosition)
351        );
352        assert_eq!(estimate_position("*/A:*"), Err(PositionError::EmptyRange));
353        assert_eq!(
354            estimate_position("*/A:0/B:*"),
355            Err(PositionError::SuffixVerifyError)
356        );
357        assert_eq!(
358            estimate_position("*/B:0+*"),
359            Err(PositionError::SuffixVerifyError)
360        );
361
362        assert_eq!(
363            estimate_position("*/A:0+*/B:+*"),
364            Err(PositionError::AsteriskInRange)
365        );
366
367        assert_eq!(
368            estimate_position("*/B :0+*"),
369            Err(PositionError::NoMatchingPosition)
370        );
371        assert_eq!(
372            estimate_position("*_0/Z:*"),
373            Err(PositionError::NoMatchingPosition)
374        );
375
376        assert_eq!(
377            estimate_position("a^"),
378            Err(PositionError::MissingSuffixAsterisk)
379        );
380        assert_eq!(
381            estimate_position("/B:17-*"),
382            Err(PositionError::MissingPrefixAsterisk)
383        );
384        assert_eq!(
385            // K3
386            estimate_position("-1"),
387            Err(PositionError::MissingPrefixAsterisk)
388        );
389    }
390
391    #[test]
392    fn advanced() {
393        assert_eq!(estimate_position("*#1*"), Ok((Boolean(F3), "1")));
394        assert_eq!(estimate_position("*%1*"), Ok((Boolean(G3), "1")));
395        assert_eq!(estimate_position("*_01/C*"), Ok((Category(B3), "01")));
396        assert_eq!(estimate_position("*-1/*"), Ok((Boolean(E5), "1")));
397
398        assert_eq!(
399            estimate_position("*-1/H:*"),
400            Err(PositionError::PrefixVerifyError)
401        );
402    }
403}