1use alloc::borrow::Cow;
9use alloc::string::String;
10use alloc::vec::Vec;
11use core::{char, iter::Peekable};
12
13use crate::serialize::txt::errors::{LexerError, LexerResult};
14
15pub(crate) struct Lexer<'a> {
17 txt: Peekable<CowChars<'a>>,
18 state: State,
19}
20
21impl<'a> Lexer<'a> {
22 pub(crate) fn new(txt: impl Into<Cow<'a, str>>) -> Self {
24 Lexer {
25 txt: CowChars {
26 data: txt.into(),
27 offset: 0,
28 }
29 .peekable(),
30 state: State::StartLine,
31 }
32 }
33
34 pub(crate) fn next_token(&mut self) -> LexerResult<Option<Token>> {
36 let mut char_data_vec: Option<Vec<String>> = None;
37 let mut char_data: Option<String> = None;
38
39 loop {
40 let ch: Option<char> = self.peek();
42
43 match self.state {
50 State::StartLine => {
51 match ch {
52 Some('\r') | Some('\n') => {
53 self.state = State::EOL;
54 }
55 Some(ch) if ch.is_whitespace() => self.state = State::Blank,
57 Some(_) => self.state = State::RestOfLine,
58 None => {
59 self.state = State::EOF;
60 }
61 }
62 }
63 State::RestOfLine => {
64 match ch {
65 Some('@') => self.state = State::At,
66 Some('(') => {
67 self.txt.next();
68 char_data_vec = Some(Vec::new());
69 self.state = State::List;
70 }
71 Some(ch @ ')') => return Err(LexerError::IllegalCharacter(ch)),
72 Some('$') => {
73 self.txt.next();
74 char_data = Some(String::new());
75 self.state = State::Dollar;
76 }
77 Some('\r') | Some('\n') => {
78 self.state = State::EOL;
79 }
80 Some('"') => {
81 self.txt.next();
82 char_data = Some(String::new());
83 self.state = State::Quote;
84 }
85 Some(';') => self.state = State::Comment { is_list: false },
86 Some(ch) if ch.is_whitespace() => {
87 self.txt.next();
88 } Some(ch) if !ch.is_control() && !ch.is_whitespace() => {
90 char_data = Some(String::new());
91 self.state = State::CharData { is_list: false };
92 }
93 Some(ch) => return Err(LexerError::UnrecognizedChar(ch)),
94 None => {
95 self.state = State::EOF;
96 }
97 }
98 }
99 State::Blank => {
100 self.txt.next();
102 self.state = State::RestOfLine;
103 return Ok(Some(Token::Blank));
104 }
105 State::Comment { is_list } => {
106 match ch {
107 Some('\r') | Some('\n') => {
108 self.state = if is_list { State::List } else { State::EOL };
109 } Some(_) => {
111 self.txt.next();
112 } None => {
114 self.state = State::EOF;
115 }
116 }
117 }
118 State::Quote => {
119 match ch {
120 Some('"') => {
122 self.state = State::RestOfLine;
123 self.txt.next();
124 return Ok(Some(Token::CharData(
125 char_data.take().unwrap_or_else(|| "".into()),
126 )));
127 }
128 Some('\\') => {
129 Self::push_to_str(&mut char_data, self.escape_seq()?)?;
130 }
131 Some(ch) => {
132 self.txt.next();
133 Self::push_to_str(&mut char_data, ch)?;
134 }
135 None => return Err(LexerError::UnclosedQuotedString),
136 }
137 }
138 State::Dollar => {
139 match ch {
140 Some(ch @ 'A'..='Z') => {
142 self.txt.next();
143 Self::push_to_str(&mut char_data, ch)?;
144 }
145 Some(_) | None => {
147 self.state = State::RestOfLine;
148 let dollar: String = char_data.take().ok_or({
149 LexerError::IllegalState(
150 "char_data \
151 is None",
152 )
153 })?;
154
155 return Ok(Some(match dollar.as_str() {
156 "INCLUDE" => Token::Include,
157 "ORIGIN" => Token::Origin,
158 "TTL" => Token::Ttl,
159 _ => {
160 return Err(LexerError::UnrecognizedDollar(
161 char_data.take().unwrap_or_else(|| "".into()),
162 ));
163 }
164 }));
165 }
166 }
167 }
168 State::List => match ch {
169 Some(';') => {
170 self.txt.next();
171 self.state = State::Comment { is_list: true }
172 }
173 Some(')') => {
174 self.txt.next();
175 self.state = State::RestOfLine;
176 return match char_data_vec.take() {
177 Some(v) => Ok(Some(Token::List(v))),
178 None => Err(LexerError::IllegalState("char_data_vec is None")),
179 };
180 }
181 Some(ch) if ch.is_whitespace() => {
182 self.txt.next();
183 }
184 Some(ch) if !ch.is_control() && !ch.is_whitespace() => {
185 char_data = Some(String::new());
186 self.state = State::CharData { is_list: true }
187 }
188 Some(ch) => return Err(LexerError::UnrecognizedChar(ch)),
189 None => return Err(LexerError::UnclosedList),
190 },
191 State::CharData { is_list } => {
192 match ch {
193 Some(ch @ ')') if !is_list => {
194 return Err(LexerError::IllegalCharacter(ch));
195 }
196 Some(ch) if ch.is_whitespace() || ch == ')' || ch == ';' => {
197 if is_list {
198 char_data_vec
199 .as_mut()
200 .ok_or(LexerError::IllegalState("char_data_vec is None"))
201 .and_then(|v| {
202 let char_data = char_data
203 .take()
204 .ok_or(LexerError::IllegalState("char_data is None"))?;
205
206 v.push(char_data);
207 Ok(())
208 })?;
209 self.state = State::List;
210 } else {
211 self.state = State::RestOfLine;
212 return match char_data.take() {
213 Some(s) => Ok(Some(Token::CharData(s))),
214 None => Err(LexerError::IllegalState("char_data is None")),
215 };
216 }
217 }
218 Some(ch) if !ch.is_control() && !ch.is_whitespace() => {
221 self.txt.next();
222 Self::push_to_str(&mut char_data, ch)?;
223 }
224 Some(ch) => return Err(LexerError::UnrecognizedChar(ch)),
225 None => {
226 self.state = State::EOF;
227 return char_data
228 .take()
229 .ok_or(LexerError::IllegalState("char_data is None"))
230 .map(|s| Some(Token::CharData(s)));
231 }
232 }
233 }
234 State::At => {
235 self.txt.next();
236 self.state = State::RestOfLine;
237 return Ok(Some(Token::At));
238 }
239 State::EOL => match ch {
240 Some('\r') => {
241 self.txt.next();
242 }
243 Some('\n') => {
244 self.txt.next();
245 self.state = State::StartLine;
246 return Ok(Some(Token::EOL));
247 }
248 Some(ch) => return Err(LexerError::IllegalCharacter(ch)),
249 None => return Err(LexerError::EOF),
250 },
251 State::EOF => {
253 self.txt.next(); return Ok(None);
255 }
256 }
257 }
258 }
259
260 fn push_to_str(collect: &mut Option<String>, ch: char) -> LexerResult<()> {
261 let Some(s) = collect.as_mut() else {
262 return Err(LexerError::IllegalState("collect is None"));
263 };
264
265 s.push(ch);
266 Ok(())
267 }
268
269 fn escape_seq(&mut self) -> LexerResult<char> {
270 self.txt.next(); let ch = self.peek().ok_or(LexerError::EOF)?;
273
274 if !ch.is_control() {
275 if ch.is_numeric() {
276 let d1: u32 = self
278 .txt
279 .next()
280 .ok_or(LexerError::EOF)
281 .map(|c| c.to_digit(10).ok_or(LexerError::IllegalCharacter(c)))??; let d2: u32 = self
283 .txt
284 .next()
285 .ok_or(LexerError::EOF)
286 .map(|c| c.to_digit(10).ok_or(LexerError::IllegalCharacter(c)))??; let d3: u32 = self
288 .txt
289 .next()
290 .ok_or(LexerError::EOF)
291 .map(|c| c.to_digit(10).ok_or(LexerError::IllegalCharacter(c)))??; let val: u32 = (d1 << 16) + (d2 << 8) + d3;
294 let ch: char = char::from_u32(val).ok_or(LexerError::UnrecognizedOctet(val))?;
295
296 Ok(ch)
297 } else {
298 self.txt.next(); Ok(ch)
301 }
302 } else {
303 Err(LexerError::IllegalCharacter(ch))
304 }
305 }
306
307 fn peek(&mut self) -> Option<char> {
308 self.txt.peek().copied()
309 }
310}
311
312struct CowChars<'a> {
313 data: Cow<'a, str>,
314 offset: usize,
315}
316
317impl Iterator for CowChars<'_> {
318 type Item = char;
319
320 fn next(&mut self) -> Option<char> {
321 let mut iter = self.data[self.offset..].char_indices();
322 let (_, ch) = iter.next()?; match iter.next() {
324 Some((idx, _)) => self.offset += idx,
325 None => self.offset = self.data.len(),
326 }
327
328 Some(ch)
329 }
330}
331
332#[doc(hidden)]
333#[derive(Copy, Clone, PartialEq, Debug)]
334pub(crate) enum State {
335 StartLine,
336 RestOfLine,
337 Blank, List, CharData { is_list: bool }, Comment { is_list: bool }, At, Quote, Dollar, EOL, EOF,
347}
348
349#[derive(Eq, PartialEq, Debug, Clone)]
351pub enum Token {
352 Blank,
354 List(Vec<String>),
356 CharData(String),
358 At,
360 Include,
362 Origin,
364 Ttl,
366 EOL,
368}
369
370#[cfg(test)]
371mod lex_test {
372 use alloc::string::ToString;
373
374 use super::*;
375
376 #[allow(clippy::uninlined_format_args)]
377 fn next_token(lexer: &mut Lexer<'_>) -> Option<Token> {
378 let result = lexer.next_token();
379 assert!(result.is_ok(), "{:?}", result);
380 result.unwrap()
381 }
382
383 #[test]
384 fn blank() {
385 let mut lexer = Lexer::new(" dead beef");
387 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
388 assert_eq!(
389 next_token(&mut lexer).unwrap(),
390 Token::CharData("dead".to_string())
391 );
392 assert_eq!(
393 next_token(&mut lexer).unwrap(),
394 Token::CharData("beef".to_string())
395 );
396
397 let mut lexer = Lexer::new("dead beef");
399 assert_eq!(
400 next_token(&mut lexer).unwrap(),
401 Token::CharData("dead".to_string())
402 );
403 assert_eq!(
404 next_token(&mut lexer).unwrap(),
405 Token::CharData("beef".to_string())
406 );
407
408 let mut lexer = Lexer::new("dead beef\r\n after");
409 assert_eq!(
410 next_token(&mut lexer).unwrap(),
411 Token::CharData("dead".to_string())
412 );
413 assert_eq!(
414 next_token(&mut lexer).unwrap(),
415 Token::CharData("beef".to_string())
416 );
417 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
418 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
419 assert_eq!(
420 next_token(&mut lexer).unwrap(),
421 Token::CharData("after".to_string())
422 );
423
424 let mut lexer = Lexer::new(
425 "dead beef ();comment
426 after",
427 );
428 assert_eq!(
429 next_token(&mut lexer).unwrap(),
430 Token::CharData("dead".to_string())
431 );
432 assert_eq!(
433 next_token(&mut lexer).unwrap(),
434 Token::CharData("beef".to_string())
435 );
436 assert_eq!(next_token(&mut lexer).unwrap(), Token::List(vec![]));
437 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
438 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
439 assert_eq!(
440 next_token(&mut lexer).unwrap(),
441 Token::CharData("after".to_string())
442 );
443 }
444
445 #[test]
446 fn escape() {
447 assert_eq!(
448 Lexer::new("a\\Aa").next_token().unwrap().unwrap(),
449 Token::CharData("a\\Aa".to_string())
450 );
451 assert_eq!(
452 Lexer::new("a\\$").next_token().unwrap().unwrap(),
453 Token::CharData("a\\$".to_string())
454 );
455 assert_eq!(
456 Lexer::new("a\\077").next_token().unwrap().unwrap(),
457 Token::CharData("a\\077".to_string())
458 );
459 }
460
461 #[test]
462 fn quoted_txt() {
463 assert_eq!(
464 Lexer::new("\"Quoted\"").next_token().unwrap().unwrap(),
465 Token::CharData("Quoted".to_string())
466 );
467 assert_eq!(
468 Lexer::new("\";@$\"").next_token().unwrap().unwrap(),
469 Token::CharData(";@$".to_string())
470 );
471 assert_eq!(
472 Lexer::new("\"some \\A\"").next_token().unwrap().unwrap(),
473 Token::CharData("some A".to_string())
474 );
475 assert_eq!(
476 Lexer::new("\"a\\Aa\"").next_token().unwrap().unwrap(),
477 Token::CharData("aAa".to_string())
478 );
479 assert_eq!(
480 Lexer::new("\"a\\$\"").next_token().unwrap().unwrap(),
481 Token::CharData("a$".to_string())
482 );
483 assert_eq!(
484 Lexer::new("\"a\\077\"").next_token().unwrap().unwrap(),
485 Token::CharData("a\u{707}".to_string())
486 );
487
488 assert!(Lexer::new("\"a\\\"").next_token().is_err());
489 assert!(Lexer::new("\"a\\0\"").next_token().is_err());
490 assert!(Lexer::new("\"a\\07\"").next_token().is_err());
491
492 let mut lexer = Lexer::new("\"multi\nline\ntext\"");
493
494 assert_eq!(
495 next_token(&mut lexer).unwrap(),
496 Token::CharData("multi\nline\ntext".to_string())
497 );
498 assert_eq!(next_token(&mut lexer), None);
499
500 let mut lexer = Lexer::new("\"multi\r\nline\r\ntext\"\r\n");
501
502 assert_eq!(
503 next_token(&mut lexer).unwrap(),
504 Token::CharData("multi\r\nline\r\ntext".to_string())
505 );
506 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
507 assert_eq!(next_token(&mut lexer), None);
508
509 assert!(Lexer::new("\"multi").next_token().is_err());
510 }
511
512 #[test]
513 fn unicode() {
514 assert_eq!(
515 Lexer::new("♥").next_token().unwrap().unwrap(),
516 Token::CharData("♥".to_string())
517 );
518 }
519
520 #[test]
522 fn lex() {
523 assert_eq!(
524 next_token(&mut Lexer::new(".")).unwrap(),
525 Token::CharData(".".to_string())
526 );
527 assert_eq!(
528 next_token(&mut Lexer::new(" .")).unwrap(),
529 Token::Blank
530 );
531 assert_eq!(
532 next_token(&mut Lexer::new("abc")).unwrap(),
533 Token::CharData("abc".to_string())
534 );
535 assert_eq!(
536 next_token(&mut Lexer::new("abc.")).unwrap(),
537 Token::CharData("abc.".to_string())
538 );
539 assert_eq!(next_token(&mut Lexer::new(";abc")), None);
540 assert_eq!(next_token(&mut Lexer::new(";;@$-\"")), None);
541 assert_eq!(next_token(&mut Lexer::new("@")).unwrap(), Token::At);
542 assert_eq!(
543 next_token(&mut Lexer::new("123")).unwrap(),
544 Token::CharData("123".to_string())
545 );
546 assert_eq!(
547 next_token(&mut Lexer::new("$INCLUDE")).unwrap(),
548 Token::Include
549 );
550 assert_eq!(
551 next_token(&mut Lexer::new("$ORIGIN")).unwrap(),
552 Token::Origin
553 );
554 assert_eq!(next_token(&mut Lexer::new("$TTL")).unwrap(), Token::Ttl);
555 assert_eq!(next_token(&mut Lexer::new("\n")), Some(Token::EOL));
556 assert_eq!(next_token(&mut Lexer::new("\r\n")), Some(Token::EOL));
557 }
558
559 #[test]
560 fn list() {
561 let mut lexer = Lexer::new("(");
562 assert!(lexer.next_token().is_err());
563
564 assert!(Lexer::new(")").next_token().is_err());
565
566 let mut lexer = Lexer::new("()");
567 assert_eq!(next_token(&mut lexer).unwrap(), Token::List(vec![]));
568 assert_eq!(next_token(&mut lexer), None);
569
570 let mut lexer = Lexer::new("(abc)");
571 assert_eq!(
572 next_token(&mut lexer).unwrap(),
573 Token::List(vec!["abc".to_string()])
574 );
575 assert_eq!(next_token(&mut lexer), None);
576
577 let mut lexer = Lexer::new("(\nabc\n)");
578 assert_eq!(
579 next_token(&mut lexer).unwrap(),
580 Token::List(vec!["abc".to_string()])
581 );
582 assert_eq!(next_token(&mut lexer), None);
583
584 let mut lexer = Lexer::new("(\nabc\nabc)");
585 assert_eq!(
586 next_token(&mut lexer).unwrap(),
587 Token::List(vec!["abc".to_string(), "abc".to_string()])
588 );
589 assert_eq!(next_token(&mut lexer), None);
590
591 let mut lexer = Lexer::new("(\nabc;comment\n)");
592 assert_eq!(
593 next_token(&mut lexer).unwrap(),
594 Token::List(vec!["abc".to_string()])
595 );
596 assert_eq!(next_token(&mut lexer), None);
597 }
598
599 #[test]
600 #[allow(clippy::cognitive_complexity)]
601 fn soa() {
602 let mut lexer = Lexer::new(
603 "@ IN SOA VENERA Action\\.domains (
604 \
605 20 ; SERIAL
606 7200 ; REFRESH
607 \
608 600 ; RETRY
609 3600000; EXPIRE
610 \
611 60) ; MINIMUM
612
613 NS A.ISI.EDU.
614 NS VENERA
615 \
616 NS VAXA
617 MX 10 VENERA
618 MX 20 VAXA
619
620\
621 A A 26.3.0.103
622
623VENERA A 10.1.0.52
624 A \
625 128.9.0.32
626
627$INCLUDE <SUBSYS>ISI-MAILBOXES.TXT",
628 );
629
630 assert_eq!(next_token(&mut lexer).unwrap(), Token::At);
631 assert_eq!(
632 next_token(&mut lexer).unwrap(),
633 Token::CharData("IN".to_string())
634 );
635 assert_eq!(
636 next_token(&mut lexer).unwrap(),
637 Token::CharData("SOA".to_string())
638 );
639 assert_eq!(
640 next_token(&mut lexer).unwrap(),
641 Token::CharData("VENERA".to_string())
642 );
643 assert_eq!(
644 next_token(&mut lexer).unwrap(),
645 Token::CharData("Action\\.domains".to_string())
646 );
647 assert_eq!(
648 next_token(&mut lexer).unwrap(),
649 Token::List(vec![
650 "20".to_string(),
651 "7200".to_string(),
652 "600".to_string(),
653 "3600000".to_string(),
654 "60".to_string(),
655 ])
656 );
657 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
658 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
659 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
660 assert_eq!(
661 next_token(&mut lexer).unwrap(),
662 Token::CharData("NS".to_string())
663 );
664 assert_eq!(
665 next_token(&mut lexer).unwrap(),
666 Token::CharData("A.ISI.EDU.".to_string())
667 );
668 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
669 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
670 assert_eq!(
671 next_token(&mut lexer).unwrap(),
672 Token::CharData("NS".to_string())
673 );
674 assert_eq!(
675 next_token(&mut lexer).unwrap(),
676 Token::CharData("VENERA".to_string())
677 );
678 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
679 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
680 assert_eq!(
681 next_token(&mut lexer).unwrap(),
682 Token::CharData("NS".to_string())
683 );
684 assert_eq!(
685 next_token(&mut lexer).unwrap(),
686 Token::CharData("VAXA".to_string())
687 );
688 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
689 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
690 assert_eq!(
691 next_token(&mut lexer).unwrap(),
692 Token::CharData("MX".to_string())
693 );
694 assert_eq!(
695 next_token(&mut lexer).unwrap(),
696 Token::CharData("10".to_string())
697 );
698 assert_eq!(
699 next_token(&mut lexer).unwrap(),
700 Token::CharData("VENERA".to_string())
701 );
702 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
703 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
704 assert_eq!(
705 next_token(&mut lexer).unwrap(),
706 Token::CharData("MX".to_string())
707 );
708 assert_eq!(
709 next_token(&mut lexer).unwrap(),
710 Token::CharData("20".to_string())
711 );
712 assert_eq!(
713 next_token(&mut lexer).unwrap(),
714 Token::CharData("VAXA".to_string())
715 );
716 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
717 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
718 assert_eq!(
719 next_token(&mut lexer).unwrap(),
720 Token::CharData("A".to_string())
721 );
722 assert_eq!(
723 next_token(&mut lexer).unwrap(),
724 Token::CharData("A".to_string())
725 );
726 assert_eq!(
727 next_token(&mut lexer).unwrap(),
728 Token::CharData("26.3.0.103".to_string())
729 );
730 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
731 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
732 assert_eq!(
733 next_token(&mut lexer).unwrap(),
734 Token::CharData("VENERA".to_string())
735 );
736 assert_eq!(
737 next_token(&mut lexer).unwrap(),
738 Token::CharData("A".to_string())
739 );
740 assert_eq!(
741 next_token(&mut lexer).unwrap(),
742 Token::CharData("10.1.0.52".to_string())
743 );
744 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
745 assert_eq!(next_token(&mut lexer).unwrap(), Token::Blank);
746 assert_eq!(
747 next_token(&mut lexer).unwrap(),
748 Token::CharData("A".to_string())
749 );
750 assert_eq!(
751 next_token(&mut lexer).unwrap(),
752 Token::CharData("128.9.0.32".to_string())
753 );
754 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
755 assert_eq!(next_token(&mut lexer).unwrap(), Token::EOL);
756 assert_eq!(next_token(&mut lexer).unwrap(), Token::Include);
757 assert_eq!(
758 next_token(&mut lexer).unwrap(),
759 Token::CharData("<SUBSYS>ISI-MAILBOXES.TXT".to_string())
760 );
761 assert!(next_token(&mut lexer).is_none());
762 }
763}