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