1use crate::error::{QqlError, Span};
2use crate::token::{Token, TokenKind, lookup_keyword};
3
4#[derive(Debug, Clone)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize))]
7pub struct Lexer<'a> {
8 input: &'a str,
9 pos: usize,
10 halted: bool,
14}
15
16impl<'a> Lexer<'a> {
17 pub fn new(input: &'a str) -> Self {
19 Lexer {
20 input,
21 pos: 0,
22 halted: false,
23 }
24 }
25
26 pub fn next_token(&mut self) -> Result<Token<'a>, QqlError> {
31 if self.halted {
32 return Ok(Token::eof(self.pos));
33 }
34 let result = self.lex_one();
35 if result.is_err() {
36 self.halted = true;
37 }
38 result
39 }
40
41 fn lex_one(&mut self) -> Result<Token<'a>, QqlError> {
42 self.skip_whitespace();
43
44 if self.pos >= self.input.len() {
45 return Ok(Token::new(TokenKind::Eof, "", Span::point(self.pos)));
46 }
47
48 let bytes = self.input.as_bytes();
49 let ch = bytes[self.pos];
50
51 match ch {
52 b'{' => self.single_char(TokenKind::Lbrace),
53 b'}' => self.single_char(TokenKind::Rbrace),
54 b'[' => self.single_char(TokenKind::Lbracket),
55 b']' => self.single_char(TokenKind::Rbracket),
56 b'(' => self.single_char(TokenKind::Lparen),
57 b')' => self.single_char(TokenKind::Rparen),
58 b'*' => self.single_char(TokenKind::Star),
59 b':' => self.single_char(TokenKind::Colon),
60 b',' => self.single_char(TokenKind::Comma),
61 b'=' => self.single_char(TokenKind::Equals),
62 b'!' => self.read_not_equals(),
63 b'>' => self.read_gt_or_gte(),
64 b'<' => self.read_lt_or_lte(),
65 b'+' => self.single_char(TokenKind::Plus),
66 b'/' => self.single_char(TokenKind::Slash),
67 b';' => self.single_char(TokenKind::Semicolon),
68 b'?' => self.single_char(TokenKind::Question),
69 b'-' => self.read_minus_or_number(),
70 b'`' => self.read_backtick_string(),
71 b'"' | b'\'' => self.read_string(ch),
72 _ => {
73 if ch == b'r'
77 && self.pos + 1 < self.input.len()
78 && (bytes[self.pos + 1] == b'\'' || bytes[self.pos + 1] == b'"')
79 {
80 self.read_raw_string(bytes[self.pos + 1])
81 } else if self.input[self.pos..].starts_with('≥') {
82 let pos = self.pos;
83 self.pos += '≥'.len_utf8();
84 Ok(Token::new(
85 TokenKind::Gte,
86 &self.input[pos..self.pos],
87 Span::new(pos, self.pos),
88 ))
89 } else if self.input[self.pos..].starts_with('≤') {
90 let pos = self.pos;
91 self.pos += '≤'.len_utf8();
92 Ok(Token::new(
93 TokenKind::Lte,
94 &self.input[pos..self.pos],
95 Span::new(pos, self.pos),
96 ))
97 } else if self.input[self.pos..].starts_with('≠') {
98 let pos = self.pos;
99 self.pos += '≠'.len_utf8();
100 Ok(Token::new(
101 TokenKind::NotEquals,
102 &self.input[pos..self.pos],
103 Span::new(pos, self.pos),
104 ))
105 } else if is_digit(ch) {
106 self.read_number()
107 } else if is_ident_start(ch) {
108 self.read_identifier()
109 } else {
110 let c = self.input[self.pos..].chars().next().unwrap_or('?');
111 let len = c.len_utf8();
112 let err_msg = alloc::format!("Unexpected character '{}'", c);
113 Err(QqlError::lex(
114 "QQL-LEX-CHAR",
115 err_msg,
116 Span::new(self.pos, self.pos + len),
117 ))
118 }
119 }
120 }
121 }
122
123 fn single_char(&mut self, kind: TokenKind) -> Result<Token<'a>, QqlError> {
124 let pos = self.pos;
125 self.pos += 1;
126 Ok(Token::new(
127 kind,
128 &self.input[pos..pos + 1],
129 Span::new(pos, pos + 1),
130 ))
131 }
132
133 fn read_not_equals(&mut self) -> Result<Token<'a>, QqlError> {
134 let bytes = self.input.as_bytes();
135 if self.pos + 1 < self.input.len() && bytes[self.pos + 1] == b'=' {
136 let pos = self.pos;
137 self.pos += 2;
138 Ok(Token::new(
139 TokenKind::NotEquals,
140 &self.input[pos..pos + 2],
141 Span::new(pos, pos + 2),
142 ))
143 } else {
144 Err(QqlError::lex(
145 "QQL-LEX-CHAR",
146 "Unexpected character '!'",
147 Span::new(self.pos, self.pos + 1),
148 ))
149 }
150 }
151
152 fn read_gt_or_gte(&mut self) -> Result<Token<'a>, QqlError> {
153 let bytes = self.input.as_bytes();
154 if self.pos + 1 < self.input.len() && bytes[self.pos + 1] == b'=' {
155 let pos = self.pos;
156 self.pos += 2;
157 Ok(Token::new(
158 TokenKind::Gte,
159 &self.input[pos..pos + 2],
160 Span::new(pos, pos + 2),
161 ))
162 } else {
163 self.single_char(TokenKind::Gt)
164 }
165 }
166
167 fn read_lt_or_lte(&mut self) -> Result<Token<'a>, QqlError> {
168 let bytes = self.input.as_bytes();
169 if self.pos + 1 < self.input.len() && bytes[self.pos + 1] == b'=' {
170 let pos = self.pos;
171 self.pos += 2;
172 Ok(Token::new(
173 TokenKind::Lte,
174 &self.input[pos..pos + 2],
175 Span::new(pos, pos + 2),
176 ))
177 } else {
178 self.single_char(TokenKind::Lt)
179 }
180 }
181
182 fn read_minus_or_number(&mut self) -> Result<Token<'a>, QqlError> {
183 let bytes = self.input.as_bytes();
184 if self.pos + 1 < self.input.len() && is_digit(bytes[self.pos + 1]) {
185 self.read_number()
186 } else if self.pos + 1 < self.input.len() && bytes[self.pos + 1] == b'.' {
187 Err(QqlError::lex(
191 "QQL-LEX-NUMBER",
192 "malformed numeric literal: '-' must be followed by a digit",
193 Span::new(self.pos, self.pos + 2),
194 ))
195 } else {
196 self.single_char(TokenKind::Minus)
197 }
198 }
199
200 fn read_string(&mut self, quote: u8) -> Result<Token<'a>, QqlError> {
201 let start = self.pos;
202 let triple = if quote == b'\'' { "'''" } else { "\"\"\"" };
208 if self.input[start..].starts_with(triple)
209 && self.input[start + triple.len()..].find(triple).is_some()
210 {
211 return self.read_triple_quoted_string(quote);
212 }
213
214 self.pos += 1;
215 let content_start = self.pos;
216
217 while self.pos < self.input.len() {
218 let bytes = self.input.as_bytes();
219 if bytes[self.pos] == b'\\' {
220 if self.pos + 1 >= self.input.len() {
221 return Err(QqlError::lex(
222 "QQL-LEX-STRING",
223 "unterminated string literal",
224 Span::new(start, self.input.len()),
225 ));
226 }
227 self.pos += 2;
228 continue;
229 }
230 if bytes[self.pos] == quote {
231 if quote == b'\'' && self.pos + 1 < self.input.len() && bytes[self.pos + 1] == b'\''
233 {
234 self.pos += 2;
235 continue;
236 }
237 let text = &self.input[content_start..self.pos];
238 self.pos += 1;
239 return Ok(Token::new(
240 TokenKind::String,
241 text,
242 Span::new(start, self.pos),
243 ));
244 }
245 self.pos += 1;
246 }
247
248 Err(QqlError::lex(
249 "QQL-LEX-STRING",
250 "unterminated string literal",
251 Span::new(start, self.input.len()),
252 ))
253 }
254
255 fn read_triple_quoted_string(&mut self, quote: u8) -> Result<Token<'a>, QqlError> {
256 let start = self.pos;
257 self.pos += 3;
258 let content_start = self.pos;
259 let delimiter = if quote == b'\'' { "'''" } else { "\"\"\"" };
260
261 if let Some(rel_pos) = self.input[self.pos..].find(delimiter) {
262 let content_end = self.pos + rel_pos;
263 let text = &self.input[content_start..content_end];
264 self.pos = content_end + 3;
265 return Ok(Token::new(
266 TokenKind::String,
267 text,
268 Span::new(start, self.pos),
269 ));
270 }
271
272 Err(QqlError::lex(
273 "QQL-LEX-STRING",
274 "unterminated triple-quoted string literal",
275 Span::new(start, self.input.len()),
276 ))
277 }
278
279 fn read_raw_string(&mut self, quote: u8) -> Result<Token<'a>, QqlError> {
280 let start = self.pos;
281 self.pos += 2;
282 let content_start = self.pos;
283
284 while self.pos < self.input.len() {
285 if self.input.as_bytes()[self.pos] == quote {
286 let text = &self.input[content_start..self.pos];
287 self.pos += 1;
288 return Ok(Token::new(
289 TokenKind::String,
290 text,
291 Span::new(start, self.pos),
292 ));
293 }
294 self.pos += 1;
295 }
296
297 Err(QqlError::lex(
298 "QQL-LEX-STRING",
299 "unterminated raw string literal",
300 Span::new(start, self.input.len()),
301 ))
302 }
303
304 fn read_backtick_string(&mut self) -> Result<Token<'a>, QqlError> {
305 let start = self.pos;
306 self.pos += 1;
307 let content_start = self.pos;
308
309 while self.pos < self.input.len() {
310 if self.input.as_bytes()[self.pos] == b'`' {
311 let text = &self.input[content_start..self.pos];
312 self.pos += 1;
313 return Ok(Token::new(
314 TokenKind::String,
315 text,
316 Span::new(start, self.pos),
317 ));
318 }
319 self.pos += 1;
320 }
321
322 Err(QqlError::lex(
323 "QQL-LEX-STRING",
324 "unterminated backtick string literal",
325 Span::new(start, self.input.len()),
326 ))
327 }
328
329 fn read_number(&mut self) -> Result<Token<'a>, QqlError> {
330 let start = self.pos;
331 if self.input.as_bytes()[self.pos] == b'-' {
332 self.pos += 1;
333 }
334
335 while self.pos < self.input.len() && is_digit(self.input.as_bytes()[self.pos]) {
336 self.pos += 1;
337 }
338
339 let mut is_float = false;
340 if self.pos < self.input.len()
341 && self.input.as_bytes()[self.pos] == b'.'
342 && self.pos + 1 < self.input.len()
343 && is_digit(self.input.as_bytes()[self.pos + 1])
344 {
345 is_float = true;
346 self.pos += 1;
347 while self.pos < self.input.len() && is_digit(self.input.as_bytes()[self.pos]) {
348 self.pos += 1;
349 }
350 }
351
352 if self.pos < self.input.len()
358 && (self.input.as_bytes()[self.pos] == b'e' || self.input.as_bytes()[self.pos] == b'E')
359 {
360 let mut cursor = self.pos + 1;
361 if cursor < self.input.len()
362 && (self.input.as_bytes()[cursor] == b'+' || self.input.as_bytes()[cursor] == b'-')
363 {
364 cursor += 1;
365 }
366 if cursor >= self.input.len() || !is_digit(self.input.as_bytes()[cursor]) {
367 return Err(QqlError::lex(
368 "QQL-LEX-NUMBER",
369 "malformed numeric literal: exponent requires at least one digit",
370 Span::new(start, cursor),
371 ));
372 }
373 is_float = true;
374 self.pos = cursor;
375 while self.pos < self.input.len() && is_digit(self.input.as_bytes()[self.pos]) {
376 self.pos += 1;
377 }
378 }
379
380 if self.pos < self.input.len() && self.input.as_bytes()[self.pos] == b'.' {
383 return Err(QqlError::lex(
384 "QQL-LEX-NUMBER",
385 "malformed numeric literal: unexpected '.'",
386 Span::new(start, self.pos + 1),
387 ));
388 }
389
390 if is_float {
391 Ok(Token::new(
392 TokenKind::Float,
393 &self.input[start..self.pos],
394 Span::new(start, self.pos),
395 ))
396 } else {
397 Ok(Token::new(
398 TokenKind::Integer,
399 &self.input[start..self.pos],
400 Span::new(start, self.pos),
401 ))
402 }
403 }
404
405 fn read_identifier(&mut self) -> Result<Token<'a>, QqlError> {
406 let start = self.pos;
407 let bytes = self.input.as_bytes();
408
409 while self.pos < self.input.len() && is_ident_continue(bytes[self.pos]) {
410 self.pos += 1;
411 }
412
413 loop {
414 if self.pos >= self.input.len() {
415 break;
416 }
417 if self.input[self.pos..].starts_with('.') {
418 let rest = &self.input[self.pos + 1..];
419 let first_byte = rest.as_bytes().first().copied().unwrap_or(0);
420 if first_byte.is_ascii_alphabetic() || first_byte == b'_' {
423 self.pos += 1;
424 while self.pos < self.input.len()
425 && is_ident_continue(self.input.as_bytes()[self.pos])
426 {
427 self.pos += 1;
428 }
429 } else {
430 break;
431 }
432 } else if self.input[self.pos..].starts_with("[].") {
433 let rest = &self.input[self.pos + 3..];
434 let first_byte = rest.as_bytes().first().copied().unwrap_or(0);
435 if first_byte.is_ascii_alphabetic() || first_byte == b'_' {
436 self.pos += 3;
437 while self.pos < self.input.len()
438 && is_ident_continue(self.input.as_bytes()[self.pos])
439 {
440 self.pos += 1;
441 }
442 } else {
443 break;
444 }
445 } else {
446 break;
447 }
448 }
449
450 let word = &self.input[start..self.pos];
451
452 if !word.contains('.')
453 && let Some(kind) = lookup_keyword(word)
454 {
455 return Ok(Token::new(kind, word, Span::new(start, self.pos)));
456 }
457
458 Ok(Token::new(
459 TokenKind::Identifier,
460 word,
461 Span::new(start, self.pos),
462 ))
463 }
464
465 fn skip_whitespace(&mut self) {
466 let bytes = self.input.as_bytes();
467 loop {
468 while self.pos < self.input.len() && is_whitespace(bytes[self.pos]) {
469 self.pos += 1;
470 }
471 if self.pos + 1 < self.input.len()
473 && bytes[self.pos] == b'-'
474 && bytes[self.pos + 1] == b'-'
475 {
476 self.pos += 2;
477 while self.pos < self.input.len() && bytes[self.pos] != b'\n' {
478 self.pos += 1;
479 }
480 continue;
481 }
482 break;
483 }
484 }
485}
486
487impl<'a> Iterator for Lexer<'a> {
488 type Item = Result<Token<'a>, QqlError>;
489
490 fn next(&mut self) -> Option<Self::Item> {
491 if self.halted || self.pos >= self.input.len() {
492 return None;
493 }
494 let result = self.next_token();
495 match &result {
496 Ok(t) if t.kind == TokenKind::Eof => None,
497 Err(_) => {
501 self.halted = true;
502 Some(result)
503 }
504 _ => Some(result),
505 }
506 }
507}
508
509fn is_whitespace(ch: u8) -> bool {
510 ch == b' ' || ch == b'\t' || ch == b'\n' || ch == b'\r'
511}
512
513fn is_digit(ch: u8) -> bool {
514 ch.is_ascii_digit()
515}
516
517fn is_ident_start(ch: u8) -> bool {
518 ch == b'$' || ch == b'_' || ch.is_ascii_alphabetic()
519}
520
521fn is_ident_continue(ch: u8) -> bool {
522 is_ident_start(ch) || is_digit(ch)
523}