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