weavatrix_parse/token/
scanner.rs1use 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 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 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}