Skip to main content

rl_lang/lexer/
scanner.rs

1//! Top-level scan driver.
2//!
3//! Exposes the single entry point that the pipeline calls to turn a source
4//! string into a token stream. Delegates all real work to [`Tokenizer`].
5use crate::lexer::{tokenizer::Tokenizer, tokentypes::TokenType};
6use crate::utils::errors::Error;
7
8impl Tokenizer {
9    /// Scans the source file and return token stream
10    ///
11    /// Iterates over every character in the source file, identifies lexemes,
12    /// and returns a flat list of [`Token`]s terminated by [`TokenType::EOF`].
13    ///
14    /// For multi-character tokens (e.g. `==`, `!=`, `<=`) the scanner peeks
15    /// ahead one character to decide which token to emit.
16    ///
17    /// # Tokens
18    ///
19    /// | category       | examples                                      |
20    /// |----------------|-----------------------------------------------|
21    /// | literals       | `1`, `3.14`, `'a'`, `"hello"`                 |
22    /// | identifiers    | `foo`, `my_var`, `_private`                   |
23    /// | arithmetic     | `+`, `-`, `*`, `/`                            |
24    /// | comparison     | `==`, `!=`, `<`, `>`, `<=`, `>=`              |
25    /// | assignment     | `=`, `+=`, `-=`, `*=`, `/=`                   |
26    /// | delimiters     | `(`, `)`, `{`, `}`, `[`, `]`                  |
27    /// | punctuation    | `,`, `;`, `.`, `..`, `:`, `::`                |
28    /// | special        | `#`, `!#`, `->`, `//` (comment)               |
29    /// | whitespace     | newlines (emitted), spaces/tabs (skipped)     |
30    ///
31    ///
32    /// # Errors
33    ///
34    /// - unterminated character literal
35    /// - unknown escape sequence
36    /// - unterminated string
37    /// - unexpected character
38    pub fn scan_tokens(&mut self) -> Result<(), Error> {
39        let current_character = self.advance();
40        match current_character {
41            // single character
42            '{' => self.add_token(TokenType::LeftBrace),
43            '}' => self.add_token(TokenType::RightBrace),
44            '[' => self.add_token(TokenType::LeftBracket),
45            ']' => self.add_token(TokenType::RightBracket),
46            '(' => self.add_token(TokenType::LeftParen),
47            ')' => self.add_token(TokenType::RightParen),
48            '#' => self.add_token(TokenType::Hash),
49            ',' => self.add_token(TokenType::Comma),
50            ';' => self.add_token(TokenType::Semicolon),
51            '?' => self.add_token(TokenType::Question),
52
53            // multi character
54            ':' => {
55                if self.peek() == ':' {
56                    self.advance();
57                    self.add_token(TokenType::ColonColon);
58                } else {
59                    self.add_token(TokenType::Colon);
60                }
61            }
62
63            '.' => {
64                if self.peek() == '.' {
65                    self.advance();
66                    self.add_token(TokenType::DotDot);
67                } else {
68                    self.add_token(TokenType::Dot);
69                }
70            }
71
72            '/' => {
73                if self.peek() == '/' {
74                    while self.peek() != '\n' {
75                        self.advance();
76                    }
77                } else if self.peek() == '=' {
78                    self.advance();
79                    self.add_token(TokenType::SlashEqual);
80                } else {
81                    self.add_token(TokenType::Slash);
82                }
83            }
84
85            '=' => {
86                if self.peek() == '=' {
87                    self.advance();
88                    self.add_token(TokenType::Compare);
89                } else if self.peek() == '>' {
90                    self.advance();
91                    self.add_token(TokenType::FatArrow);
92                } else {
93                    self.add_token(TokenType::Assign);
94                }
95            }
96
97            '!' => {
98                if self.peek() == '=' {
99                    self.advance();
100                    self.add_token(TokenType::BangEqual);
101                } else if self.peek() == '#' {
102                    self.advance();
103                    self.add_token(TokenType::BangHash);
104                } else {
105                    self.add_token(TokenType::Bang);
106                }
107            }
108
109            '>' => {
110                if self.peek() == '=' {
111                    self.advance();
112                    self.add_token(TokenType::GreaterEqual);
113                } else {
114                    self.add_token(TokenType::Greater);
115                }
116            }
117
118            '<' => {
119                if self.peek() == '=' {
120                    self.advance();
121                    self.add_token(TokenType::LessEqual);
122                } else {
123                    self.add_token(TokenType::Less);
124                }
125            }
126
127            '+' => {
128                if self.peek() == '=' {
129                    self.advance();
130                    self.add_token(TokenType::PlusEqual);
131                } else {
132                    self.add_token(TokenType::Plus);
133                }
134            }
135
136            '-' => {
137                if self.peek() == '=' {
138                    self.advance();
139                    self.add_token(TokenType::MinusEqual);
140                } else if self.peek() == '>' {
141                    self.advance();
142                    self.add_token(TokenType::Arrow);
143                } else {
144                    self.add_token(TokenType::Minus);
145                }
146            }
147
148            '*' => {
149                if self.peek() == '=' {
150                    self.advance();
151                    self.add_token(TokenType::StarEqual);
152                } else {
153                    self.add_token(TokenType::Star);
154                }
155            }
156
157            // whitespaces
158            ' ' | '\t' | '\r' => {}
159
160            '\n' => {
161                self.line += 1;
162                self.add_token(TokenType::Newline)
163            }
164
165            // literals
166            '\'' => self.character_literal()?,
167            '"' => self.string_literal()?,
168
169            '0'..='9' => self.number_literal(),
170
171            '_' | 'a'..='z' | 'A'..='Z' => self.identifier(),
172
173            other => {
174                return Err(self.err(
175                    format!("unexpected character `{}`", other),
176                    self.current_span(),
177                ));
178            }
179        }
180        Ok(())
181    }
182}