1mod cursor;
2mod lookup;
3mod token;
4mod token_kind;
5
6use crate::Error;
7use crate::LimitTracker;
8use crate::lexer::cursor::Cursor;
9use crate::lexer::lookup::ByteClass;
10pub use token::Token;
11pub use token_kind::TokenKind;
12
13#[derive(Debug)]
32pub struct Lexer<'a> {
33 finished: bool,
34 cursor: Cursor<'a>,
35 pub(crate) limit_tracker: LimitTracker,
36}
37
38enum NumberState {
40 MinusSign,
41 LeadingZero,
42 IntegerPart,
43 DecimalPoint,
44 FractionalPart,
45 ExponentIndicator,
46 ExponentSign,
47 ExponentDigit,
48}
49
50impl<'a> Lexer<'a> {
51 pub fn new(input: &'a str) -> Self {
69 Self {
70 cursor: Cursor::new(input),
71 finished: false,
72 limit_tracker: LimitTracker::new(usize::MAX),
73 }
74 }
75
76 pub fn with_limit(mut self, limit: usize) -> Self {
77 self.limit_tracker = LimitTracker::new(limit);
78 self
79 }
80
81 pub fn lex(self) -> (Vec<Token<'a>>, Vec<Error>) {
83 let mut tokens = vec![];
84 let mut errors = vec![];
85
86 for item in self {
87 match item {
88 Ok(token) => tokens.push(token),
89 Err(error) => errors.push(error),
90 }
91 }
92
93 (tokens, errors)
94 }
95
96 pub(crate) fn next_significant(&mut self) -> Option<Result<Token<'a>, Error>> {
103 if self.finished {
104 return None;
105 }
106
107 loop {
108 if self.limit_tracker.check_and_increment() {
109 self.finished = true;
110 return Some(Err(Error::limit(
111 "token limit reached, aborting lexing",
112 self.cursor.index(),
113 )));
114 }
115
116 if self.cursor.skip_trivia() {
117 continue;
118 }
119
120 return match self.cursor.advance() {
121 Ok(token) => {
122 if matches!(token.kind(), TokenKind::Eof) {
123 self.finished = true;
124 }
125
126 Some(Ok(token))
127 }
128 Err(err) => Some(Err(err)),
129 };
130 }
131 }
132}
133
134impl<'a> Iterator for Lexer<'a> {
135 type Item = Result<Token<'a>, Error>;
136
137 #[inline]
138 fn next(&mut self) -> Option<Self::Item> {
139 if self.finished {
140 return None;
141 }
142
143 if self.limit_tracker.check_and_increment() {
144 self.finished = true;
145 return Some(Err(Error::limit(
146 "token limit reached, aborting lexing",
147 self.cursor.index(),
148 )));
149 }
150
151 match self.cursor.advance() {
152 Ok(token) => {
153 if matches!(token.kind(), TokenKind::Eof) {
154 self.finished = true;
155 }
156
157 Some(Ok(token))
158 }
159 Err(err) => Some(Err(err)),
160 }
161 }
162}
163
164impl<'a> Cursor<'a> {
165 fn advance(&mut self) -> Result<Token<'a>, Error> {
166 debug_assert!(self.err.is_none());
169
170 let mut token = Token { kind: TokenKind::Eof, data: "", index: self.index() };
171
172 let Some(c) = self.bump() else {
173 let end = self.source.len();
175 self.offset = end;
176 token.index = end;
177 return Ok(token);
178 };
179
180 match lookup::byte_class(c) {
181 ByteClass::Bang => self.punctuation(token, TokenKind::Bang),
182 ByteClass::Dollar => self.punctuation(token, TokenKind::Dollar),
183 ByteClass::Amp => self.punctuation(token, TokenKind::Amp),
184 ByteClass::LParen => self.punctuation(token, TokenKind::LParen),
185 ByteClass::RParen => self.punctuation(token, TokenKind::RParen),
186 ByteClass::Comma => self.punctuation(token, TokenKind::Comma),
187 ByteClass::Colon => self.punctuation(token, TokenKind::Colon),
188 ByteClass::Eq => self.punctuation(token, TokenKind::Eq),
189 ByteClass::At => self.punctuation(token, TokenKind::At),
190 ByteClass::LBracket => self.punctuation(token, TokenKind::LBracket),
191 ByteClass::RBracket => self.punctuation(token, TokenKind::RBracket),
192 ByteClass::LCurly => self.punctuation(token, TokenKind::LCurly),
193 ByteClass::RCurly => self.punctuation(token, TokenKind::RCurly),
194 ByteClass::Pipe => self.punctuation(token, TokenKind::Pipe),
195 ByteClass::Name => {
196 token.kind = TokenKind::Name;
197 token.data = self.consume_name();
198 Ok(token)
199 }
200 ByteClass::Whitespace => {
201 token.kind = TokenKind::Whitespace;
202 token.data = self.consume_whitespace();
203 Ok(token)
204 }
205 ByteClass::Bom => {
206 if self.eat_bom() {
207 token.kind = TokenKind::Whitespace;
208 token.data = self.consume_whitespace();
209 Ok(token)
210 } else {
211 self.unexpected_character(c, &token)
212 }
213 }
214 ByteClass::Quote => self.lex_string_start(token),
215 ByteClass::Hash => self.lex_comment(token),
216 ByteClass::Dot => self.lex_spread(token),
217 ByteClass::Zero => self.lex_number(NumberState::LeadingZero, token),
218 ByteClass::Digit => self.lex_number(NumberState::IntegerPart, token),
219 ByteClass::Minus => self.lex_number(NumberState::MinusSign, token),
220 ByteClass::Other => self.unexpected_character(c, &token),
221 }
222 }
223
224 fn skip_trivia(&mut self) -> bool {
228 let Some(&c) = self.bytes.get(self.next) else {
229 return false;
230 };
231 match lookup::byte_class(c) {
232 ByteClass::Whitespace => {
233 self.bump();
234 self.consume_whitespace();
235 true
236 }
237 ByteClass::Comma => {
238 self.bump();
239 let _ = self.current_str();
241 true
242 }
243 ByteClass::Bom if self.at_bom() => {
244 self.bump();
245 self.eat_bom();
246 self.consume_whitespace();
247 true
248 }
249 _ => false,
250 }
251 }
252
253 #[inline]
254 fn punctuation(&mut self, mut token: Token<'a>, kind: TokenKind) -> Result<Token<'a>, Error> {
255 token.kind = kind;
256 token.data = self.current_str();
257 Ok(token)
258 }
259
260 fn lex_comment(&mut self, mut token: Token<'a>) -> Result<Token<'a>, Error> {
261 token.kind = TokenKind::Comment;
262 let start = self.index;
263 let end = self.seek_line_end();
264 token.data = &self.source[start..end];
265 Ok(token)
266 }
267
268 fn lex_spread(&mut self, mut token: Token<'a>) -> Result<Token<'a>, Error> {
269 token.kind = TokenKind::Spread;
270 if let Some(c) = self.bump() {
271 if c == b'.' {
272 if self.eatc(b'.') {
273 token.data = self.current_str();
274 return Ok(token);
275 }
276 } else if !c.is_ascii() {
277 self.consume_current_char();
280 }
281 }
282 let data = self.current_str();
283 Err(Error::with_loc("Unterminated spread operator", data.to_string(), token.index))
284 }
285
286 fn lex_string_start(&mut self, mut token: Token<'a>) -> Result<Token<'a>, Error> {
287 token.kind = TokenKind::StringValue;
288
289 if self.eatc(b'"') {
290 if self.eatc(b'"') {
291 return self.lex_block_string(token);
292 }
293
294 token.data = self.current_str();
296 return Ok(token);
297 }
298
299 if self.next == self.bytes.len() {
300 return Err(Error::with_loc(
302 "unexpected end of data while lexing string value",
303 self.current_str().to_string(),
304 token.index,
305 ));
306 }
307
308 self.lex_string(token)
309 }
310
311 fn lex_string(&mut self, mut token: Token<'a>) -> Result<Token<'a>, Error> {
312 loop {
313 let Some(found) = memchr::memchr2(b'"', b'\\', &self.bytes[self.next..]) else {
314 return self.unterminated_string(&token);
315 };
316 let stop = self.next + found;
317
318 if memchr::memchr2(b'\n', b'\r', &self.bytes[self.next..stop]).is_some() {
319 self.add_err(Error::with_loc("unexpected line terminator", String::new(), 0));
320 }
321
322 self.offset = stop;
324 self.next = stop + 1;
325
326 if self.bytes[stop] == b'"' {
327 token.data = self.current_str();
328 return self.done(token);
329 }
330
331 let Some(c) = self.bump() else {
333 return self.unterminated_string(&token);
334 };
335 if c == b'u' {
336 for remaining in (1..=4usize).rev() {
339 let Some(c) = self.bump() else {
340 return self.unterminated_string(&token);
341 };
342 if c == b'"' {
343 self.add_err(Error::with_loc(
344 "incomplete unicode escape sequence",
345 char::from(c).to_string(),
346 token.index,
347 ));
348 token.data = self.current_str();
349 return self.done(token);
350 }
351 if !c.is_ascii_hexdigit() {
352 self.add_err(Error::with_loc(
353 "invalid unicode escape sequence",
354 c.to_string(),
355 0,
356 ));
357 break;
358 }
359 if remaining == 1 {
360 let hex_end = self.offset + 1;
361 let hex_start = hex_end - 4;
362 let hex = &self.source[hex_start..hex_end];
363 let code_point = u32::from_str_radix(hex, 16).unwrap();
366 if char::from_u32(code_point).is_none() {
367 let escape_sequence_start = hex_start - 2; let escape_sequence = &self.source[escape_sequence_start..hex_end];
371 self.add_err(Error::with_loc(
372 "surrogate code point is invalid in unicode escape sequence \
373 (paired surrogate not supported yet: \
374 https://github.com/oxc-project/oxc-graphql-parser/issues/657)",
375 escape_sequence.to_owned(),
376 0,
377 ));
378 }
379 }
380 }
381 } else if !is_escaped_char(c) {
382 let c = self.char_for_error(c);
383 self.add_err(Error::with_loc("unexpected escaped character", c.to_string(), 0));
384 }
385 }
386 }
387
388 fn lex_block_string(&mut self, mut token: Token<'a>) -> Result<Token<'a>, Error> {
389 loop {
390 let Some(found) = memchr::memchr2(b'"', b'\\', &self.bytes[self.next..]) else {
391 return self.unterminated_string(&token);
392 };
393 let stop = self.next + found;
394
395 self.offset = stop;
397 self.next = stop + 1;
398
399 if self.bytes[stop] == b'"' {
400 if self.eatc(b'"') && self.eatc(b'"') {
403 token.data = self.current_str();
404 return self.done(token);
405 }
406 continue;
407 }
408
409 loop {
415 let Some(c) = self.bump() else {
416 return self.unterminated_string(&token);
417 };
418 match c {
419 b'\\' => {}
420 b'"' => {
421 if self.eatc(b'"') {
422 self.eatc(b'"');
423 }
424 break;
425 }
426 _ => break,
427 }
428 }
429 }
430 }
431
432 fn lex_number(
433 &mut self,
434 mut state: NumberState,
435 mut token: Token<'a>,
436 ) -> Result<Token<'a>, Error> {
437 token.kind = TokenKind::Int;
438
439 loop {
440 let Some(c) = self.bump() else {
441 return match state {
442 NumberState::MinusSign => Err(Error::with_loc(
443 "Unexpected character \"-\"",
444 self.current_str().to_string(),
445 token.index,
446 )),
447 NumberState::DecimalPoint
448 | NumberState::ExponentIndicator
449 | NumberState::ExponentSign => Err(Error::with_loc(
450 "Unexpected EOF in float value",
451 self.current_str().to_string(),
452 token.index,
453 )),
454 NumberState::LeadingZero
455 | NumberState::IntegerPart
456 | NumberState::FractionalPart
457 | NumberState::ExponentDigit => {
458 token.data = self.current_str();
459 Ok(token)
460 }
461 };
462 };
463
464 match state {
465 NumberState::MinusSign => match c {
466 b'0' => {
467 state = NumberState::LeadingZero;
468 }
469 curr if curr.is_ascii_digit() => {
470 state = NumberState::IntegerPart;
471 }
472 _ => {
473 let c = self.char_for_error(c);
474 return Err(Error::with_loc(
475 format!("Unexpected character `{c}`"),
476 self.current_str().to_string(),
477 token.index,
478 ));
479 }
480 },
481 NumberState::LeadingZero => match c {
482 b'.' => {
483 token.kind = TokenKind::Float;
484 state = NumberState::DecimalPoint;
485 }
486 b'e' | b'E' => {
487 token.kind = TokenKind::Float;
488 state = NumberState::ExponentIndicator;
489 }
490 _ if c.is_ascii_digit() => {
491 return Err(Error::with_loc(
492 "Numbers must not have non-significant leading zeroes",
493 self.current_str().to_string(),
494 token.index,
495 ));
496 }
497 _ if lookup::is_namestart(c) => {
498 let c = char::from(c);
499 return Err(Error::with_loc(
500 format!("Unexpected character `{c}` as integer suffix"),
501 self.current_str().to_string(),
502 token.index,
503 ));
504 }
505 _ => {
506 token.data = self.prev_str();
507 return Ok(token);
508 }
509 },
510 NumberState::IntegerPart => match c {
511 curr if curr.is_ascii_digit() => {}
512 b'.' => {
513 token.kind = TokenKind::Float;
514 state = NumberState::DecimalPoint;
515 }
516 b'e' | b'E' => {
517 token.kind = TokenKind::Float;
518 state = NumberState::ExponentIndicator;
519 }
520 _ if lookup::is_namestart(c) => {
521 let c = char::from(c);
522 return Err(Error::with_loc(
523 format!("Unexpected character `{c}` as integer suffix"),
524 self.current_str().to_string(),
525 token.index,
526 ));
527 }
528 _ => {
529 token.data = self.prev_str();
530 return Ok(token);
531 }
532 },
533 NumberState::DecimalPoint => match c {
534 curr if curr.is_ascii_digit() => {
535 state = NumberState::FractionalPart;
536 }
537 _ => {
538 let c = self.char_for_error(c);
539 return Err(Error::with_loc(
540 format!("Unexpected character `{c}`, expected fractional digit"),
541 self.current_str().to_string(),
542 token.index,
543 ));
544 }
545 },
546 NumberState::FractionalPart => match c {
547 curr if curr.is_ascii_digit() => {}
548 b'e' | b'E' => {
549 state = NumberState::ExponentIndicator;
550 }
551 _ if c == b'.' || lookup::is_namestart(c) => {
552 let c = char::from(c);
553 return Err(Error::with_loc(
554 format!("Unexpected character `{c}` as float suffix"),
555 self.current_str().to_string(),
556 token.index,
557 ));
558 }
559 _ => {
560 token.data = self.prev_str();
561 return Ok(token);
562 }
563 },
564 NumberState::ExponentIndicator => match c {
565 _ if c.is_ascii_digit() => {
566 state = NumberState::ExponentDigit;
567 }
568 b'+' | b'-' => {
569 state = NumberState::ExponentSign;
570 }
571 _ => {
572 let c = self.char_for_error(c);
573 return Err(Error::with_loc(
574 format!("Unexpected character `{c}`, expected exponent digit or sign"),
575 self.current_str().to_string(),
576 token.index,
577 ));
578 }
579 },
580 NumberState::ExponentSign => match c {
581 _ if c.is_ascii_digit() => {
582 state = NumberState::ExponentDigit;
583 }
584 _ => {
585 let c = self.char_for_error(c);
586 return Err(Error::with_loc(
587 format!("Unexpected character `{c}`, expected exponent digit"),
588 self.current_str().to_string(),
589 token.index,
590 ));
591 }
592 },
593 NumberState::ExponentDigit => match c {
594 _ if c.is_ascii_digit() => {}
595 _ if c == b'.' || lookup::is_namestart(c) => {
596 let c = char::from(c);
597 return Err(Error::with_loc(
598 format!("Unexpected character `{c}` as float suffix"),
599 self.current_str().to_string(),
600 token.index,
601 ));
602 }
603 _ => {
604 token.data = self.prev_str();
605 return Ok(token);
606 }
607 },
608 }
609 }
610 }
611
612 fn unexpected_character(&mut self, c: u8, token: &Token<'a>) -> Result<Token<'a>, Error> {
613 let c = self.char_for_error(c);
614 Err(Error::with_loc(
615 format!(r#"Unexpected character "{c}""#),
616 self.current_str().to_string(),
617 token.index,
618 ))
619 }
620
621 fn unterminated_string(&mut self, token: &Token<'a>) -> Result<Token<'a>, Error> {
622 self.err = None;
625 Err(Error::with_loc("unterminated string value", self.drain().to_string(), token.index))
626 }
627
628 fn char_for_error(&mut self, c: u8) -> char {
629 if c.is_ascii() { char::from(c) } else { self.consume_current_char() }
630 }
631
632 #[inline]
633 fn done(&mut self, token: Token<'a>) -> Result<Token<'a>, Error> {
634 if let Some(mut err) = self.err.take() {
635 err.set_data(token.data.to_string());
636 err.index = token.index;
637 return Err(err);
638 }
639 Ok(token)
640 }
641}
642
643fn is_whitespace_assimilated(c: u8) -> bool {
646 matches!(
647 c,
648 b'\t'
650 | b' '
651 | b'\n'
653 | b'\r'
654 )
655}
656
657fn is_name_continue(c: u8) -> bool {
659 matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_')
660}
661
662fn is_escaped_char(c: u8) -> bool {
665 matches!(c, b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't')
666}