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("Asterisk is not allowed in range section")]
32 AsteriskInRange,
33 #[error("Range is empty")]
35 EmptyRange,
36}
37
38pub(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 let mut prefix = pattern
62 .bytes()
63 .position(|b| "!#%&+-=@^_|:".contains(b as char))
64 .map(|i| i + 1)
65 .unwrap_or(0);
66
67 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 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 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 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 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 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 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}