1pub struct Lexer {
2 position: usize,
3 len: usize,
4 chars: Vec<char>,
5 error_message: String,
6 error_position: usize,
7}
8
9#[derive(PartialEq, Debug)]
10pub enum Token {
11 Color(i32), Condition(Compare, f64), Currency(char), Literal(char), Spacer(char), Ghost(char), Text(String), Separator, Raw, Percent, Comma, Period, Sharp, Zero, QuestionMark, Scientific, ScientificMinus, General, Day, DayPadded, DayNameShort, DayName, Month, MonthPadded, MonthNameShort, MonthName, MonthLetter, YearShort, Year, Hour, HourPadded, Second, SecondPadded, ElapsedHour, ElapsedMinute, ElapsedSecond, ElapsedHourPadded, ElapsedMinutePadded, ElapsedSecondPadded, AMPM, ILLEGAL,
53 EOF,
54}
55
56#[derive(PartialEq, Eq, Debug)]
57pub enum Compare {
58 Equal,
59 LessThan,
60 GreaterThan,
61 LessOrEqualThan,
62 GreaterOrEqualThan,
63}
64
65impl Token {
66 pub fn is_digit(&self) -> bool {
67 (self == &Token::Zero) || (self == &Token::Sharp) || (self == &Token::QuestionMark)
68 }
69
70 pub fn is_date(&self) -> bool {
71 self == &Token::Day
72 || self == &Token::DayPadded
73 || self == &Token::DayNameShort
74 || self == &Token::DayName
75 || self == &Token::MonthName
76 || self == &Token::MonthNameShort
77 || self == &Token::Month
78 || self == &Token::MonthPadded
79 || self == &Token::MonthLetter
80 || self == &Token::YearShort
81 || self == &Token::Year
82 }
83}
84
85impl Lexer {
86 pub fn new(format: &str) -> Lexer {
87 let chars: Vec<char> = format.chars().collect();
88 let len = chars.len();
89 Lexer {
90 chars,
91 position: 0,
92 len,
93 error_message: "".to_string(),
94 error_position: 0,
95 }
96 }
97
98 fn peek_char(&self) -> Option<char> {
99 let position = self.position;
100 if position < self.len {
101 Some(self.chars[position])
102 } else {
103 None
104 }
105 }
106
107 fn read_next_char(&mut self) -> Option<char> {
108 let position = self.position;
109 if position < self.len {
110 self.position = position + 1;
111 Some(self.chars[position])
112 } else {
113 None
114 }
115 }
116
117 fn set_error(&mut self, error: &str) {
118 self.error_message = error.to_string();
119 self.error_position = self.position;
120 self.position = self.len;
121 }
122
123 fn consume_string(&mut self) -> Option<String> {
124 let mut position = self.position;
125 let len = self.len;
126 let mut chars = "".to_string();
127 while position < len {
128 let x = self.chars[position];
129 position += 1;
130 if x != '"' {
131 chars.push(x);
132 } else if position < len && self.chars[position] == '"' {
133 chars.push(x);
134 position += 1;
135 } else {
136 self.position = position;
137 return Some(chars);
138 }
139 }
140 None
141 }
142
143 fn consume_number(&mut self) -> Option<f64> {
144 let mut position = self.position;
145 let len = self.len;
146 let mut chars = "".to_string();
147 if position < len && self.chars[position] == '-' {
149 chars.push('-');
150 position += 1;
151 }
152 while position < len {
154 let x = self.chars[position];
155 if x.is_ascii_digit() {
156 chars.push(x);
157 } else {
158 break;
159 }
160 position += 1;
161 }
162 if position < len && self.chars[position] == '.' {
163 chars.push('.');
165 position += 1;
166 while position < len {
167 let x = self.chars[position];
168 if x.is_ascii_digit() {
169 chars.push(x);
170 } else {
171 break;
172 }
173 position += 1;
174 }
175 }
176 if position + 1 < len && self.chars[position].eq_ignore_ascii_case(&'e') {
177 let x = self.chars[position + 1];
179 if x == '-' || x == '+' || x.is_ascii_digit() {
180 chars.push('e');
181 chars.push(x);
182 position += 2;
183 while position < len {
184 let x = self.chars[position];
185 if x.is_ascii_digit() {
186 chars.push(x);
187 } else {
188 break;
189 }
190 position += 1;
191 }
192 }
193 }
194 self.position = position;
195 chars.parse::<f64>().ok()
196 }
197
198 fn consume_condition(&mut self) -> Option<(Compare, f64)> {
199 let cmp;
200 match self.read_next_char() {
201 Some('<') => {
202 if let Some('=') = self.peek_char() {
203 self.read_next_char();
204 cmp = Compare::LessOrEqualThan;
205 } else {
206 cmp = Compare::LessThan;
207 }
208 }
209 Some('>') => {
210 if let Some('=') = self.peek_char() {
211 self.read_next_char();
212 cmp = Compare::GreaterOrEqualThan;
213 } else {
214 cmp = Compare::GreaterThan;
215 }
216 }
217 Some('=') => {
218 cmp = Compare::Equal;
219 }
220 _ => {
221 return None;
222 }
223 }
224
225 if let Some(v) = self.consume_number() {
226 if self.read_next_char() == Some(']') {
227 return Some((cmp, v));
228 }
229 } else if self.read_next_char() == Some(']') {
230 return Some((cmp, 0.0));
231 }
232 None
233 }
234
235 fn consume_elapsed_time(&mut self) -> Option<Token> {
237 let start_pos = self.position;
239
240 let unit = match self.read_next_char() {
242 Some(c) if ['h', 'H', 'm', 'M', 's', 'S'].contains(&c) => c.to_ascii_lowercase(),
243 _ => {
244 self.position = start_pos;
246 return None;
247 }
248 };
249
250 let mut count = 1;
252 while let Some(ch) = self.peek_char() {
253 if ch.to_ascii_lowercase() == unit {
254 count += 1;
255 self.read_next_char();
256 } else {
257 break;
258 }
259 }
260
261 if self.read_next_char() != Some(']') {
263 self.position = start_pos;
265 return None;
266 }
267
268 let token = match (unit, count) {
270 ('h', 1) => Token::ElapsedHour,
271 ('h', 2) => Token::ElapsedHourPadded,
272 ('m', 1) => Token::ElapsedMinute,
273 ('m', 2) => Token::ElapsedMinutePadded,
274 ('s', 1) => Token::ElapsedSecond,
275 ('s', 2) => Token::ElapsedSecondPadded,
276 _ => {
278 self.position = start_pos;
279 return None;
280 }
281 };
282
283 Some(token)
284 }
285
286 fn consume_color(&mut self) -> Option<i32> {
287 let colors = [
288 "black", "white", "red", "green", "blue", "yellow", "magenta",
289 ];
290 let mut chars = "".to_string();
291 while let Some(ch) = self.read_next_char() {
292 if ch == ']' {
293 let lc = chars.to_lowercase();
294 if let Some(index) = colors.iter().position(|&x| x == lc) {
295 return Some(index as i32);
296 }
297 if !chars.starts_with("Color") {
298 return None;
299 }
300 if let Ok(index) = chars[5..].trim().parse::<i32>() {
301 if index < 57 && index > 0 {
302 return Some(index);
303 } else {
304 return None;
305 }
306 }
307 return None;
308 } else {
309 chars.push(ch);
310 }
311 }
312 None
313 }
314
315 pub fn peek_token(&mut self) -> Token {
316 let position = self.position;
317 let token = self.next_token();
318 self.position = position;
319 token
320 }
321
322 pub fn next_token(&mut self) -> Token {
323 let ch = self.read_next_char();
324 match ch {
325 Some(x) => match x {
326 '$' | '€' | '(' | ')' | '/' | ':' | '+' | '-' | '^' | '\'' | '{' | '}' | '<'
327 | '=' | '!' | '~' | '>' | ' ' => Token::Literal(x),
328 '?' => Token::QuestionMark,
329 ';' => Token::Separator,
330 '#' => Token::Sharp,
331 ',' => Token::Comma,
332 '.' => Token::Period,
333 '0' => Token::Zero,
334 '@' => Token::Raw,
335 '%' => Token::Percent,
336 '[' => {
337 if let Some(c) = self.peek_char() {
338 if c == '<' || c == '>' || c == '=' {
339 if let Some((cmp, value)) = self.consume_condition() {
341 Token::Condition(cmp, value)
342 } else {
343 self.set_error("Failed to parse condition");
344 Token::ILLEGAL
345 }
346 } else if c == '$' {
347 self.read_next_char();
355 if let Some(currency) = self.read_next_char() {
356 if Some('-') == self.peek_char() {
358 self.read_next_char();
359
360 while let Some(c) = self.peek_char() {
361 if c == ']' {
362 break;
363 }
364 self.read_next_char();
365 }
366 }
367 if self.read_next_char() == Some(']') {
368 return Token::Currency(currency);
369 }
370 }
371 self.set_error("Failed to parse currency");
372 Token::ILLEGAL
373 } else {
374 if let Some(token) = self.consume_elapsed_time() {
376 return token;
377 }
378 if let Some(index) = self.consume_color() {
380 return Token::Color(index);
381 }
382 self.set_error("Failed to parse color");
383 Token::ILLEGAL
384 }
385 } else {
386 self.set_error("Unexpected end of input");
387 Token::ILLEGAL
388 }
389 }
390 '_' => {
391 if let Some(y) = self.read_next_char() {
392 Token::Ghost(y)
393 } else {
394 self.set_error("Unexpected end of input");
395 Token::ILLEGAL
396 }
397 }
398 '*' => {
399 if let Some(y) = self.read_next_char() {
400 Token::Spacer(y)
401 } else {
402 self.set_error("Unexpected end of input");
403 Token::ILLEGAL
404 }
405 }
406 '\\' => {
407 if let Some(y) = self.read_next_char() {
408 Token::Literal(y)
409 } else {
410 self.set_error("Unexpected end of input");
411 Token::ILLEGAL
412 }
413 }
414 '"' => {
415 if let Some(s) = self.consume_string() {
416 Token::Text(s)
417 } else {
418 self.set_error("Did not find end of text string");
419 Token::ILLEGAL
420 }
421 }
422 'E' => {
423 if let Some(s) = self.read_next_char() {
424 if s == '+' {
425 Token::Scientific
426 } else if s == '-' {
427 Token::ScientificMinus
428 } else {
429 self.set_error(&format!("Unexpected char: {s}. Expected + or -"));
430 Token::ILLEGAL
431 }
432 } else {
433 self.set_error("Unexpected end of input");
434 Token::ILLEGAL
435 }
436 }
437 'd' => {
438 let mut d = 1;
439 while let Some('d') = self.peek_char() {
440 d += 1;
441 self.read_next_char();
442 }
443 match d {
444 1 => Token::Day,
445 2 => Token::DayPadded,
446 3 => Token::DayNameShort,
447 _ => Token::DayName,
448 }
449 }
450 'm' => {
451 let mut m = 1;
452 while let Some('m') = self.peek_char() {
453 m += 1;
454 self.read_next_char();
455 }
456 match m {
457 1 => Token::Month, 2 => Token::MonthPadded, 3 => Token::MonthNameShort,
460 4 => Token::MonthName,
461 5 => Token::MonthLetter,
462 _ => Token::MonthName,
463 }
464 }
465 'y' => {
466 let mut y = 1;
467 while let Some('y') = self.peek_char() {
468 y += 1;
469 self.read_next_char();
470 }
471 if y == 1 || y == 2 {
472 Token::YearShort
473 } else {
474 Token::Year
475 }
476 }
477 'h' | 'H' => {
478 let mut h = 1;
479 while let Some(c) = self.peek_char() {
480 if c.eq_ignore_ascii_case(&'h') {
481 h += 1;
482 self.read_next_char();
483 } else {
484 break;
485 }
486 }
487 if h == 1 {
488 Token::Hour
489 } else if h == 2 {
490 Token::HourPadded
491 } else {
492 self.set_error("Unexpected character after 'h'");
493 Token::ILLEGAL
494 }
495 }
496 's' => {
497 let mut s = 1;
498 while let Some('s') = self.peek_char() {
499 s += 1;
500 self.read_next_char();
501 }
502 if s == 1 {
503 Token::Second
504 } else if s == 2 {
505 Token::SecondPadded
506 } else {
507 self.set_error("Unexpected character after 's'");
508 Token::ILLEGAL
509 }
510 }
511 'A' | 'a' => {
512 if let Some('M') | Some('m') = self.peek_char() {
513 self.read_next_char();
514 } else {
515 self.set_error("Unexpected character after 'A'");
516 return Token::ILLEGAL;
517 }
518 if let Some('/') = self.peek_char() {
519 self.read_next_char();
520 } else {
521 self.set_error("Unexpected character after 'AM'");
522 return Token::ILLEGAL;
523 }
524 if let Some('P') | Some('p') = self.peek_char() {
525 self.read_next_char();
526 } else {
527 self.set_error("Unexpected character after 'AM'");
528 return Token::ILLEGAL;
529 }
530 if let Some('M') | Some('m') = self.peek_char() {
531 self.read_next_char();
532 } else {
533 self.set_error("Unexpected character after 'AMP'");
534 return Token::ILLEGAL;
535 }
536 Token::AMPM
537 }
538 'g' | 'G' => {
539 for c in "eneral".chars() {
540 let cc = self.read_next_char();
541 if Some(c) != cc {
542 self.set_error(&format!("Unexpected character: {x}"));
543 return Token::ILLEGAL;
544 }
545 }
546 Token::General
547 }
548 _ => {
549 self.set_error(&format!("Unexpected character: {x}"));
550 Token::ILLEGAL
551 }
552 },
553 None => Token::EOF,
554 }
555 }
556}
557
558pub fn is_likely_date_number_format(format: &str) -> bool {
559 let mut lexer = Lexer::new(format);
560 loop {
561 let token = lexer.next_token();
562 if token == Token::EOF {
563 return false;
564 }
565 if token.is_date() {
566 return true;
567 }
568 }
569}