Skip to main content

weavatrix_parse/token/
scanner.rs

1use super::{Mode, Token, TokenKind, Tokenizer};
2
3impl Iterator for Tokenizer<'_> {
4    type Item = Token;
5
6    fn next(&mut self) -> Option<Token> {
7        loop {
8            let token = self.scan()?;
9            if self.mode == Mode::Lite && token.is_trivia() {
10                continue;
11            }
12            return Some(token);
13        }
14    }
15}
16
17impl Tokenizer<'_> {
18    pub(super) fn scan(&mut self) -> Option<Token> {
19        if self.offset >= self.bytes.len() {
20            return None;
21        }
22        let start = self.offset;
23        let line = self.line;
24        let column = self.column;
25        let current = self.peek(0)?;
26
27        if let Some(token) = self.scan_layout(current, start, line, column) {
28            return Some(token);
29        }
30        self.at_line_start = false;
31        if let Some(token) = self.scan_comment(start, line, column) {
32            return Some(token);
33        }
34        if let Some(token) = self.scan_literal(current, start, line, column) {
35            return Some(token);
36        }
37        if let Some(token) = self.scan_number(current, start, line, column) {
38            return Some(token);
39        }
40
41        let character = self.source[self.offset..].chars().next()?;
42        if let Some(token) = self.scan_identifier(character, start, line, column) {
43            return Some(token);
44        }
45
46        // A closing bracket or paren ends a value, so a following `/` divides.
47        self.value_before = matches!(current, b')' | b']' | b'}');
48        self.advance(character.len_utf8());
49        Some(self.emit(TokenKind::Punctuation, start, line, column))
50    }
51
52    fn scan_layout(&mut self, current: u8, start: usize, line: u32, column: u32) -> Option<Token> {
53        if current == b'\n' || (current == b'\r' && self.peek(1) == Some(b'\n')) {
54            self.advance(if current == b'\r' { 2 } else { 1 });
55            self.value_before = false;
56            self.at_line_start = true;
57            return Some(self.emit(TokenKind::Newline, start, line, column));
58        }
59
60        if current == b' ' || current == b'\t' || current == b'\r' {
61            let indent = self.at_line_start && self.syntax.significant_indentation;
62            while matches!(self.peek(0), Some(b' ' | b'\t' | b'\r')) {
63                self.advance(1);
64            }
65            self.at_line_start = false;
66            let kind = if indent {
67                TokenKind::Indent
68            } else {
69                TokenKind::Whitespace
70            };
71            return Some(self.emit(kind, start, line, column));
72        }
73        None
74    }
75
76    fn scan_comment(&mut self, start: usize, line: u32, column: u32) -> Option<Token> {
77        for marker in self.syntax.line_comments {
78            if self.starts_with(marker) {
79                while !matches!(self.peek(0), None | Some(b'\n')) {
80                    self.advance(1);
81                }
82                return Some(self.emit(TokenKind::LineComment, start, line, column));
83            }
84        }
85
86        if let Some((open, close)) = self.syntax.block_comment
87            && self.starts_with(open)
88        {
89            let terminated = self.scan_block_comment(open, close);
90            let kind = if terminated {
91                TokenKind::BlockComment
92            } else {
93                TokenKind::Unterminated
94            };
95            return Some(self.emit(kind, start, line, column));
96        }
97        None
98    }
99
100    fn scan_literal(&mut self, current: u8, start: usize, line: u32, column: u32) -> Option<Token> {
101        if self.syntax.raw_strings
102            && (current == b'r' || current == b'b')
103            && matches!(self.peek(1), Some(b'"' | b'#'))
104        {
105            let terminated = self.scan_raw_string();
106            let kind = if terminated {
107                TokenKind::String
108            } else {
109                TokenKind::Unterminated
110            };
111            self.value_before = true;
112            return Some(self.emit(kind, start, line, column));
113        }
114
115        if self.syntax.char_literals
116            && current == b'\''
117            && let Some(length) = self.char_literal_length()
118        {
119            self.advance(length);
120            self.value_before = true;
121            return Some(self.emit(TokenKind::String, start, line, column));
122        }
123
124        if self.syntax.quotes.contains(&(current as char)) {
125            let terminated = self.scan_string(current);
126            let kind = if terminated {
127                TokenKind::String
128            } else {
129                TokenKind::Unterminated
130            };
131            self.value_before = true;
132            return Some(self.emit(kind, start, line, column));
133        }
134
135        if self.syntax.regex_literals && current == b'/' && !self.value_before {
136            let terminated = self.scan_regex();
137            if terminated {
138                self.value_before = true;
139                return Some(self.emit(TokenKind::Regex, start, line, column));
140            }
141            // Not a regular expression after all: fall through as punctuation.
142            self.offset = start;
143            self.line = line;
144            self.column = column;
145        }
146        None
147    }
148
149    fn scan_number(&mut self, current: u8, start: usize, line: u32, column: u32) -> Option<Token> {
150        if current.is_ascii_digit() {
151            while self
152                .peek(0)
153                .is_some_and(|byte| byte.is_ascii_alphanumeric() || byte == b'.' || byte == b'_')
154            {
155                self.advance(1);
156            }
157            self.value_before = true;
158            return Some(self.emit(TokenKind::Number, start, line, column));
159        }
160        None
161    }
162
163    fn scan_identifier(
164        &mut self,
165        character: char,
166        start: usize,
167        line: u32,
168        column: u32,
169    ) -> Option<Token> {
170        if self.is_identifier_start(character) {
171            while self.source[self.offset..]
172                .chars()
173                .next()
174                .is_some_and(|value| self.is_identifier_part(value))
175            {
176                let width = self.source[self.offset..]
177                    .chars()
178                    .next()
179                    .map_or(1, char::len_utf8);
180                self.advance(width);
181            }
182            let identifier = &self.source[start..self.offset];
183            self.value_before = !self.syntax.regex_literals
184                || !matches!(
185                    identifier,
186                    "await"
187                        | "case"
188                        | "delete"
189                        | "do"
190                        | "else"
191                        | "in"
192                        | "instanceof"
193                        | "new"
194                        | "of"
195                        | "return"
196                        | "throw"
197                        | "typeof"
198                        | "void"
199                        | "yield"
200                );
201            return Some(self.emit(TokenKind::Identifier, start, line, column));
202        }
203        None
204    }
205}