1use 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#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
14pub enum PositionError {
15 #[error("No matching position found")]
17 NoMatchingPosition,
18 #[error("The first character should be asterisk in this position")]
20 MissingPrefixAsterisk,
21 #[error("The last character should be asterisk in this position")]
23 MissingSuffixAsterisk,
24 #[error("Prefix has unknown sequence")]
26 PrefixVerifyError,
27 #[error("Suffix has unknown sequence")]
29 SuffixVerifyError,
30 #[error("Range is empty")]
32 EmptyRange,
33}
34
35pub(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 let mut prefix = pattern
59 .bytes()
60 .position(|b| "!#%&+-=@^_|:".contains(b as char))
61 .map(|i| i + 1)
62 .unwrap_or(0);
63
64 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 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 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 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 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 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 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}