1mod keyword;
2
3use mago_database::file::FileId;
4use mago_database::file::HasFileId;
5use mago_span::Position;
6use mago_syntax_core::float_exponent;
7use mago_syntax_core::float_separator;
8use mago_syntax_core::input::Input;
9use mago_syntax_core::number_sign;
10use mago_syntax_core::part_of_identifier;
11use mago_syntax_core::start_of_binary_number;
12use mago_syntax_core::start_of_float_number;
13use mago_syntax_core::start_of_hexadecimal_number;
14use mago_syntax_core::start_of_identifier;
15use mago_syntax_core::start_of_octal_number;
16use mago_syntax_core::start_of_octal_or_float_number;
17use mago_syntax_core::utils::read_digits_of_base;
18
19use crate::error::SyntaxError;
20use crate::token::TypeToken;
21use crate::token::TypeTokenKind;
22
23#[derive(Debug)]
24pub struct TypeLexer<'arena> {
25 input: Input<'arena>,
26}
27
28impl<'arena> TypeLexer<'arena> {
29 #[inline]
30 #[must_use]
31 pub fn new(input: Input<'arena>) -> TypeLexer<'arena> {
32 TypeLexer { input }
33 }
34
35 #[inline]
36 #[must_use]
37 pub fn has_reached_eof(&self) -> bool {
38 self.input.has_reached_eof()
39 }
40
41 #[inline]
42 #[must_use]
43 pub fn current_position(&self) -> Position {
44 self.input.current_position()
45 }
46
47 #[inline]
48 #[must_use]
49 pub fn slice_in_range(&self, from: u32, to: u32) -> &'arena [u8] {
50 self.input.slice_in_range(from, to)
51 }
52
53 #[inline]
54 pub fn advance(&mut self) -> Option<Result<TypeToken<'arena>, SyntaxError>> {
55 if self.input.has_reached_eof() {
56 return None;
57 }
58
59 let start = self.input.current_position();
60 let whitespaces = self.input.consume_whitespaces();
61 if !whitespaces.is_empty() {
62 let end = self.input.current_position();
63 return Some(Ok(self.token(TypeTokenKind::Whitespace, whitespaces, start, end)));
64 }
65
66 let remaining = self.input.read_remaining();
67 let first = unsafe { *remaining.get_unchecked(0) };
69 let second = remaining.get(1).copied();
70
71 let (kind, length) = match first {
72 b'*' => (TypeTokenKind::Asterisk, 1),
73 b':' => {
74 if second == Some(b':') {
75 (TypeTokenKind::ColonColon, 2)
76 } else {
77 (TypeTokenKind::Colon, 1)
78 }
79 }
80 b'=' => (TypeTokenKind::Equals, 1),
81 b'?' => (TypeTokenKind::Question, 1),
82 b'!' => (TypeTokenKind::Exclamation, 1),
83 b'&' => (TypeTokenKind::Ampersand, 1),
84 b'|' => (TypeTokenKind::Pipe, 1),
85 b'>' => (TypeTokenKind::GreaterThan, 1),
86 b'<' => (TypeTokenKind::LessThan, 1),
87 b'(' => (TypeTokenKind::LeftParenthesis, 1),
88 b')' => (TypeTokenKind::RightParenthesis, 1),
89 b'[' => (TypeTokenKind::LeftBracket, 1),
90 b']' => (TypeTokenKind::RightBracket, 1),
91 b'{' => (TypeTokenKind::LeftBrace, 1),
92 b'}' => (TypeTokenKind::RightBrace, 1),
93 b',' => (TypeTokenKind::Comma, 1),
94 b'+' => (TypeTokenKind::Plus, 1),
95 b'-' => (TypeTokenKind::Minus, 1),
96 b'.' => match remaining.get(..3) {
97 Some([b'.', b'.', b'.']) => (TypeTokenKind::Ellipsis, 3),
98 _ if matches!(second, Some(b'0'..=b'9')) => self.read_decimal(),
99 _ => {
100 return Some(Err(SyntaxError::UnrecognizedToken(
101 self.file_id(),
102 first,
103 self.input.current_position(),
104 )));
105 }
106 },
107 b'/' if second == Some(b'/') => self.read_single_line_comment(),
108 b'\'' | b'"' => self.read_literal_string(first),
109 b'\\' if second.is_some_and(|b| b.is_ascii_alphabetic() || b == b'_' || b >= 0x80) => {
110 self.read_fully_qualified_identifier()
111 }
112 b'$' if second.is_some_and(|b| b.is_ascii_alphabetic() || b == b'_' || b >= 0x80) => self.read_variable(),
113 b'0'..=b'9' => self.read_number(),
114 b if b.is_ascii_alphabetic() || b == b'_' || b >= 0x80 => self.read_identifier_or_keyword(),
115 _ => {
116 return Some(Err(SyntaxError::UnrecognizedToken(self.file_id(), first, self.input.current_position())));
117 }
118 };
119
120 let buffer = self.input.consume(length);
121 let end = self.input.current_position();
122
123 Some(Ok(self.token(kind, buffer, start, end)))
124 }
125
126 #[inline]
127 fn read_variable(&self) -> (TypeTokenKind, usize) {
128 let mut length = 2;
129 while let [part_of_identifier!(), ..] = self.input.peek(length, 1) {
130 length += 1;
131 }
132 (TypeTokenKind::Variable, length)
133 }
134
135 #[inline]
136 fn read_single_line_comment(&self) -> (TypeTokenKind, usize) {
137 let mut length = 2;
138 loop {
139 match self.input.peek(length, 1) {
140 [b'\n', ..] | [] => break,
141 [_, ..] => length += 1,
142 }
143 }
144 (TypeTokenKind::SingleLineComment, length)
145 }
146
147 #[inline]
148 fn read_decimal(&self) -> (TypeTokenKind, usize) {
149 let mut length = read_digits_of_base(&self.input, 2, 10);
150 if let float_exponent!() = self.input.peek(length, 1) {
151 let mut exp_length = length + 1;
152 if let number_sign!() = self.input.peek(exp_length, 1) {
153 exp_length += 1;
154 }
155
156 let after_exp = read_digits_of_base(&self.input, exp_length, 10);
157 if after_exp > exp_length {
158 length = after_exp;
159 }
160 }
161 (TypeTokenKind::LiteralFloat, length)
162 }
163
164 #[inline]
165 fn read_number(&self) -> (TypeTokenKind, usize) {
166 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
167 enum NumberKind {
168 Integer,
169 Float,
170 OctalOrFloat,
171 IntegerOrFloat,
172 }
173
174 let mut length = 1;
175 let (base, kind): (u8, NumberKind) = match self.input.read(3) {
176 start_of_binary_number!() => {
177 length += 1;
178 (2, NumberKind::Integer)
179 }
180 start_of_octal_number!() => {
181 length += 1;
182 (8, NumberKind::Integer)
183 }
184 start_of_hexadecimal_number!() => {
185 length += 1;
186 (16, NumberKind::Integer)
187 }
188 start_of_octal_or_float_number!() => (10, NumberKind::OctalOrFloat),
189 start_of_float_number!() => (10, NumberKind::Float),
190 _ => (10, NumberKind::IntegerOrFloat),
191 };
192
193 if kind != NumberKind::Float {
194 length = read_digits_of_base(&self.input, length, base);
195 if kind == NumberKind::Integer {
196 return (TypeTokenKind::LiteralInteger, length);
197 }
198 }
199
200 let is_float = matches!(self.input.peek(length, 3), float_separator!());
201 if !is_float {
202 return (TypeTokenKind::LiteralInteger, length);
203 }
204
205 if let [b'.'] = self.input.peek(length, 1) {
206 length += 1;
207 length = read_digits_of_base(&self.input, length, 10);
208 }
209
210 if let float_exponent!() = self.input.peek(length, 1) {
211 let mut exp_length = length + 1;
212 if let number_sign!() = self.input.peek(exp_length, 1) {
213 exp_length += 1;
214 }
215
216 let after_exp = read_digits_of_base(&self.input, exp_length, 10);
217 if after_exp > exp_length {
218 length = after_exp;
219 }
220 }
221
222 (TypeTokenKind::LiteralFloat, length)
223 }
224
225 #[inline]
226 fn read_literal_string(&self, quote: u8) -> (TypeTokenKind, usize) {
227 let total = self.input.len();
228 let start = self.input.current_offset();
229 let mut length = 1;
230 let mut last_was_backslash = false;
231 let mut partial = false;
232
233 loop {
234 let pos = start + length;
235 if pos >= total {
236 partial = true;
237 break;
238 }
239
240 let byte = self.input.read_at(pos);
241 if *byte == b'\\' {
242 last_was_backslash = !last_was_backslash;
243 length += 1;
244 } else {
245 if byte == "e && !last_was_backslash {
246 length += 1;
247 break;
248 }
249 length += 1;
250 last_was_backslash = false;
251 }
252 }
253
254 if partial { (TypeTokenKind::PartialLiteralString, length) } else { (TypeTokenKind::LiteralString, length) }
255 }
256
257 #[inline]
258 fn read_fully_qualified_identifier(&self) -> (TypeTokenKind, usize) {
259 let mut length = 2;
260 let mut last_was_slash = false;
261 loop {
262 match self.input.peek(length, 1) {
263 [start_of_identifier!(), ..] if last_was_slash => {
264 length += 1;
265 last_was_slash = false;
266 }
267 [part_of_identifier!(), ..] if !last_was_slash => {
268 length += 1;
269 }
270 [b'\\', ..] => {
271 if last_was_slash {
272 length -= 1;
273 break;
274 }
275 length += 1;
276 last_was_slash = true;
277 }
278 _ => break,
279 }
280 }
281 (TypeTokenKind::FullyQualifiedIdentifier, length)
282 }
283
284 #[inline]
287 fn read_identifier_or_keyword(&self) -> (TypeTokenKind, usize) {
288 let remaining = self.input.read_remaining();
289 let total = remaining.len();
290 let mut length = 1;
291 let mut next_is_hyphen = false;
292 let mut next_is_backslash = false;
293
294 while length < total {
298 let b = unsafe { *remaining.get_unchecked(length) };
300 if mago_syntax_core::utils::is_part_of_identifier(&b) {
301 length += 1;
302 continue;
303 }
304
305 if b == b'-' && length + 1 < total {
306 let b2 = unsafe { *remaining.get_unchecked(length + 1) };
308 if mago_syntax_core::utils::is_part_of_identifier(&b2) {
309 next_is_hyphen = true;
310 }
311 } else if b == b'\\' && length + 1 < total {
312 let b2 = unsafe { *remaining.get_unchecked(length + 1) };
314 if mago_syntax_core::utils::is_start_of_identifier(&b2) {
315 next_is_backslash = true;
316 }
317 } else {
318 }
320
321 break;
322 }
323
324 if next_is_backslash {
325 return self.finish_qualified_identifier(length);
326 }
327
328 if !next_is_hyphen {
329 let bytes = unsafe { remaining.get_unchecked(..length) };
331 if let Some(kind) = keyword::lookup_keyword(bytes) {
332 return (kind, length);
333 }
334 return (TypeTokenKind::Identifier, length);
335 }
336
337 let base_len = length;
338 while length < total {
339 let b = unsafe { *remaining.get_unchecked(length) };
341 if mago_syntax_core::utils::is_part_of_identifier(&b) {
342 length += 1;
343 continue;
344 }
345
346 if b == b'-' && length + 1 < total {
347 let b2 = unsafe { *remaining.get_unchecked(length + 1) };
349 if mago_syntax_core::utils::is_part_of_identifier(&b2) {
350 length += 1;
351 continue;
352 }
353 }
354
355 break;
356 }
357
358 let bytes = unsafe { remaining.get_unchecked(..length) };
360 if let Some(kind) = keyword::lookup_keyword(bytes) {
361 return (kind, length);
362 }
363
364 let base_bytes = unsafe { remaining.get_unchecked(..base_len) };
366 if let Some(kind) = keyword::lookup_keyword(base_bytes) {
367 return (kind, base_len);
368 }
369
370 (TypeTokenKind::Identifier, base_len)
371 }
372
373 #[inline]
375 fn finish_qualified_identifier(&self, start_len: usize) -> (TypeTokenKind, usize) {
376 let mut length = start_len;
377 let mut slashes = 0;
378 let mut last_was_slash = false;
379
380 loop {
381 match self.input.peek(length, 1) {
382 [start_of_identifier!(), ..] if last_was_slash => {
383 length += 1;
384 last_was_slash = false;
385 }
386 [part_of_identifier!(), ..] if !last_was_slash => {
387 length += 1;
388 }
389 [b'\\', ..] => {
390 if last_was_slash {
391 length -= 1;
392 slashes -= 1;
393 break;
394 }
395 length += 1;
396 slashes += 1;
397 last_was_slash = true;
398 }
399 _ => break,
400 }
401 }
402
403 if last_was_slash {
404 length -= 1;
405 slashes -= 1;
406 }
407
408 if slashes > 0 { (TypeTokenKind::QualifiedIdentifier, length) } else { (TypeTokenKind::Identifier, length) }
409 }
410
411 #[inline]
412 fn token(&self, kind: TypeTokenKind, value: &'arena [u8], start: Position, _end: Position) -> TypeToken<'arena> {
413 TypeToken { kind, start, value }
414 }
415}
416
417impl HasFileId for TypeLexer<'_> {
418 #[inline]
419 fn file_id(&self) -> FileId {
420 self.input.file_id()
421 }
422}