Skip to main content

hickory_proto/serialize/txt/
zone_lex.rs

1// Copyright 2015-2016 Benjamin Fry
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use 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
15/// A Lexer for Zone files
16pub(crate) struct Lexer<'a> {
17    txt: Peekable<CowChars<'a>>,
18    state: State,
19}
20
21impl<'a> Lexer<'a> {
22    /// Creates a new lexer with the given data to parse
23    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    /// Return the next Token in the string
35    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            // This is to get around mutability rules such that we can peek at the iter without moving next...
41            let ch: Option<char> = self.peek();
42
43            // handy line for debugging
44            // debug!("ch = {:?}; state = {:?}(c: {:?}, v: {:?})", ch, self.state, char_data, char_data_vec);
45
46            // continuing states should pass back the state as the last statement,
47            //  terminal states should set the state internally and return the proper Token::*.
48            // TODO: there is some non-ideal copying going on in here...
49            match self.state {
50                State::StartLine => {
51                    match ch {
52                        Some('\r') | Some('\n') => {
53                            self.state = State::EOL;
54                        }
55                        // white space at the start of line is a Blank
56                        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                        } // gobble other whitespace
89                        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                    // consume the whitespace
101                    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                        } // out of the comment
110                        Some(_) => {
111                            self.txt.next();
112                        } // advance the token by default and maintain state
113                        None => {
114                            self.state = State::EOF;
115                        }
116                    }
117                }
118                State::Quote => {
119                    match ch {
120                        // end and gobble the '"'
121                        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                        // even this is a little broad for what's actually possible in a dollar...
141                        Some(ch @ 'A'..='Z') => {
142                            self.txt.next();
143                            Self::push_to_str(&mut char_data, ch)?;
144                        }
145                        // finishes the Dollar...
146                        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                        // TODO: this next one can be removed, but will keep unescaping for quoted strings
219                        //Some('\\') => { try!(Self::push_to_str(&mut char_data, try!(self.escape_seq()))); },
220                        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                // to exhaust all cases, this should never be run...
252                State::EOF => {
253                    self.txt.next(); // making sure we consume the last... it will always return None after.
254                    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        // escaped character, let's decode it.
271        self.txt.next(); // consume the escape
272        let ch = self.peek().ok_or(LexerError::EOF)?;
273
274        if !ch.is_control() {
275            if ch.is_numeric() {
276                // in this case it's an escaped octal: \DDD
277                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)))??; // gobble
282                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)))??; // gobble
287                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)))??; // gobble
292
293                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                // this is an escaped char: \X
299                self.txt.next(); // gobble the char
300                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()?; // The returned index is always `0`
323        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,                      // only if the first part of the line
338    List,                       // (..)
339    CharData { is_list: bool }, // [a-zA-Z, non-control utf8]+
340    //  Name,              // CharData + '.' + CharData
341    Comment { is_list: bool }, // ;.*
342    At,                        // @
343    Quote,                     // ".*"
344    Dollar,                    // $
345    EOL,                       // \n or \r\n
346    EOF,
347}
348
349/// Tokens emited from each Lexer pass
350#[derive(Eq, PartialEq, Debug, Clone)]
351pub enum Token {
352    /// only if the first part of the line
353    Blank,
354    /// (..) TODO, this is probably wrong, List maybe should just skip line endings
355    List(Vec<String>),
356    /// [a-zA-Z, non-control utf8, ., -, 0-9]+, ".*"
357    CharData(String),
358    /// @
359    At,
360    /// $INCLUDE
361    Include,
362    /// $ORIGIN
363    Origin,
364    /// $TTL
365    Ttl,
366    /// \n or \r\n
367    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        // first blank
386        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        // not the second blank
398        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    // fun with tests!!! lots of options
521    #[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}