1use crate::keyword::{self, Keyword};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
18pub struct Span {
19 pub start: u32,
21 pub end: u32,
23}
24
25impl Span {
26 pub fn new(start: usize, end: usize) -> Span {
28 Span {
29 start: start.min(u32::MAX as usize) as u32,
30 end: end.min(u32::MAX as usize) as u32,
31 }
32 }
33
34 pub fn at(offset: usize) -> Span {
36 Span::new(offset, offset)
37 }
38
39 pub fn to(self, other: Span) -> Span {
41 Span {
42 start: self.start.min(other.start),
43 end: self.end.max(other.end),
44 }
45 }
46
47 pub fn len(self) -> usize {
49 self.end.saturating_sub(self.start) as usize
50 }
51
52 pub fn is_empty(self) -> bool {
54 self.end <= self.start
55 }
56
57 pub fn slice(self, source: &[u8]) -> &[u8] {
59 source
60 .get(self.start as usize..self.end as usize)
61 .unwrap_or(&[])
62 }
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
71pub enum QuoteForm {
72 Bare,
74 Double,
76 Bracket,
78 Backtick,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum TokenKind {
85 Identifier {
87 keyword: Option<Keyword>,
89 quote: QuoteForm,
91 },
92 String,
94 Blob,
96 Integer,
98 Float,
100 Parameter,
102 Punctuator(Punctuator),
104 EndOfInput,
106}
107
108#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110pub enum Punctuator {
111 LeftParen,
113 RightParen,
115 Comma,
117 Semicolon,
119 Dot,
121 Plus,
123 Minus,
125 Star,
127 Slash,
129 Percent,
131 Equal,
133 NotEqual,
135 Less,
137 LessEqual,
139 Greater,
141 GreaterEqual,
143 ShiftLeft,
145 ShiftRight,
147 BitAnd,
149 BitOr,
151 BitNot,
153 Concat,
155 Arrow,
157 DoubleArrow,
159 L2Distance,
161 CosineDistance,
163 NegativeInnerProduct,
165 L1Distance,
167 HammingDistance,
169 JaccardDistance,
171}
172
173impl Punctuator {
174 pub fn as_str(self) -> &'static str {
176 match self {
177 Punctuator::LeftParen => "(",
178 Punctuator::RightParen => ")",
179 Punctuator::Comma => ",",
180 Punctuator::Semicolon => ";",
181 Punctuator::Dot => ".",
182 Punctuator::Plus => "+",
183 Punctuator::Minus => "-",
184 Punctuator::Star => "*",
185 Punctuator::Slash => "/",
186 Punctuator::Percent => "%",
187 Punctuator::Equal => "=",
188 Punctuator::NotEqual => "<>",
189 Punctuator::Less => "<",
190 Punctuator::LessEqual => "<=",
191 Punctuator::Greater => ">",
192 Punctuator::GreaterEqual => ">=",
193 Punctuator::ShiftLeft => "<<",
194 Punctuator::ShiftRight => ">>",
195 Punctuator::BitAnd => "&",
196 Punctuator::BitOr => "|",
197 Punctuator::BitNot => "~",
198 Punctuator::Concat => "||",
199 Punctuator::Arrow => "->",
200 Punctuator::L2Distance => "<->",
201 Punctuator::CosineDistance => "<=>",
202 Punctuator::NegativeInnerProduct => "<#>",
203 Punctuator::L1Distance => "<+>",
204 Punctuator::HammingDistance => "<~>",
205 Punctuator::JaccardDistance => "<%>",
206 Punctuator::DoubleArrow => "->>",
207 }
208 }
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213pub struct Token {
214 pub kind: TokenKind,
216 pub span: Span,
218}
219
220impl Token {
221 pub fn text(self, source: &[u8]) -> &[u8] {
223 self.span.slice(source)
224 }
225
226 pub fn keyword(self) -> Option<Keyword> {
228 match self.kind {
229 TokenKind::Identifier { keyword, .. } => keyword,
230 _ => None,
231 }
232 }
233
234 pub fn is(self, punctuator: Punctuator) -> bool {
236 self.kind == TokenKind::Punctuator(punctuator)
237 }
238}
239
240#[derive(Clone, Copy, Debug, PartialEq, Eq)]
242pub enum LexErrorKind {
243 UnterminatedQuote,
245 UnterminatedComment,
247 UnrecognisedByte,
249 MalformedBlob,
251 MalformedNumber,
253 MalformedParameter,
255}
256
257impl LexErrorKind {
258 pub fn message(self) -> &'static str {
260 match self {
261 LexErrorKind::UnterminatedQuote => "unrecognized token: unterminated quoted name",
262 LexErrorKind::UnterminatedComment => "unrecognized token: unterminated comment",
263 LexErrorKind::UnrecognisedByte => "unrecognized token",
264 LexErrorKind::MalformedBlob => "unrecognized token: malformed blob literal",
265 LexErrorKind::MalformedNumber => "unrecognized token: malformed numeric literal",
266 LexErrorKind::MalformedParameter => "unrecognized token: malformed parameter",
267 }
268 }
269}
270
271#[derive(Clone, Copy, Debug, PartialEq, Eq)]
273pub struct LexError {
274 pub kind: LexErrorKind,
276 pub offset: u32,
278}
279
280#[derive(Clone, Debug)]
282pub struct Lexer<'a> {
283 source: &'a [u8],
284 offset: usize,
285}
286
287impl<'a> Lexer<'a> {
288 pub fn new(source: &'a [u8]) -> Lexer<'a> {
290 Lexer { source, offset: 0 }
291 }
292
293 pub fn at(source: &'a [u8], offset: usize) -> Lexer<'a> {
295 Lexer {
296 source,
297 offset: offset.min(source.len()),
298 }
299 }
300
301 pub fn offset(&self) -> usize {
303 self.offset
304 }
305
306 pub fn source(&self) -> &'a [u8] {
308 self.source
309 }
310
311 fn byte(&self, offset: usize) -> Option<u8> {
313 self.source.get(offset).copied()
314 }
315
316 fn skip_trivia(&mut self) -> Result<(), LexError> {
319 loop {
320 match self.byte(self.offset) {
321 Some(byte) if is_space(byte) => self.offset += 1,
322 Some(b'-') if self.byte(self.offset + 1) == Some(b'-') => {
323 self.offset += 2;
324 while let Some(byte) = self.byte(self.offset) {
325 self.offset += 1;
326 if byte == b'\n' {
327 break;
328 }
329 }
330 }
331 Some(b'/') if self.byte(self.offset + 1) == Some(b'*') => {
332 let opened = self.offset;
333 self.offset += 2;
334 loop {
335 match self.byte(self.offset) {
336 None => {
337 return Ok(());
342 }
343 Some(b'*') if self.byte(self.offset + 1) == Some(b'/') => {
344 self.offset += 2;
345 break;
346 }
347 Some(_) => self.offset += 1,
348 }
349 }
350 let _ = opened;
351 }
352 _ => return Ok(()),
353 }
354 }
355 }
356
357 pub fn next_token(&mut self) -> Result<Token, LexError> {
359 self.skip_trivia()?;
360 let start = self.offset;
361 let Some(byte) = self.byte(start) else {
362 return Ok(Token {
363 kind: TokenKind::EndOfInput,
364 span: Span::at(start),
365 });
366 };
367 match byte {
368 b'\'' => self.scan_quoted(start, b'\'', TokenKind::String),
369 b'"' => self.scan_quoted(
370 start,
371 b'"',
372 TokenKind::Identifier {
373 keyword: None,
374 quote: QuoteForm::Double,
375 },
376 ),
377 b'`' => self.scan_quoted(
378 start,
379 b'`',
380 TokenKind::Identifier {
381 keyword: None,
382 quote: QuoteForm::Backtick,
383 },
384 ),
385 b'[' => self.scan_bracket(start),
386 b'0'..=b'9' => self.scan_number(start),
387 b'.' if self.byte(start + 1).is_some_and(is_digit) => self.scan_number(start),
388 b'?' | b':' | b'@' | b'$' => self.scan_parameter(start),
389 byte if is_identifier_start(byte) => self.scan_word(start),
390 _ => self.scan_punctuator(start),
391 }
392 }
393
394 fn scan_quoted(
397 &mut self,
398 start: usize,
399 delimiter: u8,
400 kind: TokenKind,
401 ) -> Result<Token, LexError> {
402 let mut cursor = start + 1;
403 loop {
404 match self.byte(cursor) {
405 None => {
406 return Err(LexError {
407 kind: LexErrorKind::UnterminatedQuote,
408 offset: start as u32,
409 })
410 }
411 Some(byte) if byte == delimiter => {
412 if self.byte(cursor + 1) == Some(delimiter) {
413 cursor += 2;
414 continue;
415 }
416 cursor += 1;
417 break;
418 }
419 Some(_) => cursor += 1,
420 }
421 }
422 self.offset = cursor;
423 Ok(Token {
424 kind,
425 span: Span::new(start, cursor),
426 })
427 }
428
429 fn scan_bracket(&mut self, start: usize) -> Result<Token, LexError> {
431 let mut cursor = start + 1;
432 loop {
433 match self.byte(cursor) {
434 None => {
435 return Err(LexError {
436 kind: LexErrorKind::UnterminatedQuote,
437 offset: start as u32,
438 })
439 }
440 Some(b']') => {
441 cursor += 1;
442 break;
443 }
444 Some(_) => cursor += 1,
445 }
446 }
447 self.offset = cursor;
448 Ok(Token {
449 kind: TokenKind::Identifier {
450 keyword: None,
451 quote: QuoteForm::Bracket,
452 },
453 span: Span::new(start, cursor),
454 })
455 }
456
457 fn scan_word(&mut self, start: usize) -> Result<Token, LexError> {
459 let mut cursor = start;
460 while self.byte(cursor).is_some_and(is_identifier_part) {
461 cursor += 1;
462 }
463 let word = self.source.get(start..cursor).unwrap_or(&[]);
464 if word.len() == 1
465 && word
466 .first()
467 .is_some_and(|byte| byte.eq_ignore_ascii_case(&b'x'))
468 && self.byte(cursor) == Some(b'\'')
469 {
470 return self.scan_blob(start, cursor);
471 }
472 self.offset = cursor;
473 Ok(Token {
474 kind: TokenKind::Identifier {
475 keyword: keyword::lookup(word),
476 quote: QuoteForm::Bare,
477 },
478 span: Span::new(start, cursor),
479 })
480 }
481
482 fn scan_blob(&mut self, start: usize, quote: usize) -> Result<Token, LexError> {
485 let mut cursor = quote + 1;
486 let body = cursor;
487 loop {
488 match self.byte(cursor) {
489 None => {
490 return Err(LexError {
491 kind: LexErrorKind::UnterminatedQuote,
492 offset: start as u32,
493 })
494 }
495 Some(b'\'') => break,
496 Some(byte) if byte.is_ascii_hexdigit() => cursor += 1,
497 Some(_) => {
498 return Err(LexError {
499 kind: LexErrorKind::MalformedBlob,
500 offset: start as u32,
501 })
502 }
503 }
504 }
505 if !(cursor - body).is_multiple_of(2) {
506 return Err(LexError {
507 kind: LexErrorKind::MalformedBlob,
508 offset: start as u32,
509 });
510 }
511 self.offset = cursor + 1;
512 Ok(Token {
513 kind: TokenKind::Blob,
514 span: Span::new(start, cursor + 1),
515 })
516 }
517
518 fn scan_number(&mut self, start: usize) -> Result<Token, LexError> {
520 if self.byte(start) == Some(b'0')
521 && self
522 .byte(start + 1)
523 .is_some_and(|byte| byte.eq_ignore_ascii_case(&b'x'))
524 {
525 return self.scan_hex_number(start);
526 }
527 let mut cursor = start;
528 let mut float = false;
529 cursor = self.scan_digits(cursor);
530 if self.byte(cursor) == Some(b'.') {
531 float = true;
532 cursor = self.scan_digits(cursor + 1);
533 }
534 if self
535 .byte(cursor)
536 .is_some_and(|byte| byte.eq_ignore_ascii_case(&b'e'))
537 {
538 let mut lookahead = cursor + 1;
539 if matches!(self.byte(lookahead), Some(b'+') | Some(b'-')) {
540 lookahead += 1;
541 }
542 if self.byte(lookahead).is_some_and(is_digit) {
543 float = true;
544 cursor = self.scan_digits(lookahead);
545 }
546 }
547 if self.byte(cursor).is_some_and(is_identifier_part) {
550 return Err(LexError {
551 kind: LexErrorKind::MalformedNumber,
552 offset: start as u32,
553 });
554 }
555 self.offset = cursor;
556 Ok(Token {
557 kind: if float {
558 TokenKind::Float
559 } else {
560 TokenKind::Integer
561 },
562 span: Span::new(start, cursor),
563 })
564 }
565
566 fn scan_hex_number(&mut self, start: usize) -> Result<Token, LexError> {
568 let mut cursor = start + 2;
569 let digits = cursor;
570 loop {
571 let Some(byte) = self.byte(cursor) else { break };
572 let separator = byte == b'_'
574 && cursor > digits
575 && self
576 .byte(cursor.wrapping_sub(1))
577 .is_some_and(|b| b.is_ascii_hexdigit())
578 && self.byte(cursor + 1).is_some_and(|b| b.is_ascii_hexdigit());
579 if !(byte.is_ascii_hexdigit() || separator) {
580 break;
581 }
582 cursor += 1;
583 }
584 if cursor == digits || self.byte(cursor).is_some_and(is_identifier_part) {
585 return Err(LexError {
586 kind: LexErrorKind::MalformedNumber,
587 offset: start as u32,
588 });
589 }
590 self.offset = cursor;
591 Ok(Token {
592 kind: TokenKind::Integer,
593 span: Span::new(start, cursor),
594 })
595 }
596
597 fn scan_digits(&mut self, from: usize) -> usize {
599 let mut cursor = from;
600 while let Some(byte) = self.byte(cursor) {
601 if is_digit(byte) {
602 cursor += 1;
603 continue;
604 }
605 if byte == b'_'
607 && cursor > from
608 && self.byte(cursor + 1).is_some_and(is_digit)
609 && self.byte(cursor.wrapping_sub(1)).is_some_and(is_digit)
610 {
611 cursor += 1;
612 continue;
613 }
614 break;
615 }
616 cursor
617 }
618
619 fn scan_parameter(&mut self, start: usize) -> Result<Token, LexError> {
621 let sigil = self.byte(start).unwrap_or(b'?');
622 let mut cursor = start + 1;
623 if sigil == b'?' {
624 cursor = self.scan_digits(cursor);
625 self.offset = cursor;
626 return Ok(Token {
627 kind: TokenKind::Parameter,
628 span: Span::new(start, cursor),
629 });
630 }
631 while self.byte(cursor).is_some_and(is_identifier_part) {
632 cursor += 1;
633 }
634 if sigil == b'$' && self.byte(cursor) == Some(b'(') {
637 while let Some(byte) = self.byte(cursor) {
638 cursor += 1;
639 if byte == b')' {
640 break;
641 }
642 }
643 }
644 if cursor == start + 1 {
645 return Err(LexError {
648 kind: LexErrorKind::MalformedParameter,
649 offset: start as u32,
650 });
651 }
652 self.offset = cursor;
653 Ok(Token {
654 kind: TokenKind::Parameter,
655 span: Span::new(start, cursor),
656 })
657 }
658
659 fn scan_punctuator(&mut self, start: usize) -> Result<Token, LexError> {
661 let one = self.byte(start).unwrap_or(0);
662 let two = self.byte(start + 1);
663 let three = self.byte(start + 2);
664 let (punctuator, length) = match (one, two, three) {
665 (b'-', Some(b'>'), Some(b'>')) => (Punctuator::DoubleArrow, 3),
666 (b'<', Some(b'-'), Some(b'>')) => (Punctuator::L2Distance, 3),
671 (b'<', Some(b'='), Some(b'>')) => (Punctuator::CosineDistance, 3),
672 (b'<', Some(b'#'), Some(b'>')) => (Punctuator::NegativeInnerProduct, 3),
673 (b'<', Some(b'+'), Some(b'>')) => (Punctuator::L1Distance, 3),
674 (b'<', Some(b'~'), Some(b'>')) => (Punctuator::HammingDistance, 3),
675 (b'<', Some(b'%'), Some(b'>')) => (Punctuator::JaccardDistance, 3),
676 (b'-', Some(b'>'), _) => (Punctuator::Arrow, 2),
677 (b'|', Some(b'|'), _) => (Punctuator::Concat, 2),
678 (b'<', Some(b'<'), _) => (Punctuator::ShiftLeft, 2),
679 (b'>', Some(b'>'), _) => (Punctuator::ShiftRight, 2),
680 (b'<', Some(b'='), _) => (Punctuator::LessEqual, 2),
681 (b'>', Some(b'='), _) => (Punctuator::GreaterEqual, 2),
682 (b'<', Some(b'>'), _) => (Punctuator::NotEqual, 2),
683 (b'!', Some(b'='), _) => (Punctuator::NotEqual, 2),
684 (b'=', Some(b'='), _) => (Punctuator::Equal, 2),
685 (b'(', _, _) => (Punctuator::LeftParen, 1),
686 (b')', _, _) => (Punctuator::RightParen, 1),
687 (b',', _, _) => (Punctuator::Comma, 1),
688 (b';', _, _) => (Punctuator::Semicolon, 1),
689 (b'.', _, _) => (Punctuator::Dot, 1),
690 (b'+', _, _) => (Punctuator::Plus, 1),
691 (b'-', _, _) => (Punctuator::Minus, 1),
692 (b'*', _, _) => (Punctuator::Star, 1),
693 (b'/', _, _) => (Punctuator::Slash, 1),
694 (b'%', _, _) => (Punctuator::Percent, 1),
695 (b'=', _, _) => (Punctuator::Equal, 1),
696 (b'<', _, _) => (Punctuator::Less, 1),
697 (b'>', _, _) => (Punctuator::Greater, 1),
698 (b'&', _, _) => (Punctuator::BitAnd, 1),
699 (b'|', _, _) => (Punctuator::BitOr, 1),
700 (b'~', _, _) => (Punctuator::BitNot, 1),
701 _ => {
702 return Err(LexError {
703 kind: LexErrorKind::UnrecognisedByte,
704 offset: start as u32,
705 })
706 }
707 };
708 self.offset = start + length;
709 Ok(Token {
710 kind: TokenKind::Punctuator(punctuator),
711 span: Span::new(start, start + length),
712 })
713 }
714}
715
716pub fn is_space(byte: u8) -> bool {
718 matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | 0x0b | 0x0c)
719}
720
721pub fn is_digit(byte: u8) -> bool {
723 byte.is_ascii_digit()
724}
725
726pub fn is_identifier_start(byte: u8) -> bool {
731 byte.is_ascii_alphabetic() || byte == b'_' || byte >= 0x80
732}
733
734pub fn is_identifier_part(byte: u8) -> bool {
736 is_identifier_start(byte) || byte.is_ascii_digit() || byte == b'$'
737}
738
739pub fn illegal_token_text(source: &[u8], error: LexError) -> &[u8] {
752 let start = (error.offset as usize).min(source.len());
753 let end = match error.kind {
754 LexErrorKind::UnterminatedQuote | LexErrorKind::UnterminatedComment => source.len(),
755 LexErrorKind::MalformedBlob => blob_token_end(source, start),
756 LexErrorKind::MalformedNumber => number_token_end(source, start),
757 LexErrorKind::MalformedParameter | LexErrorKind::UnrecognisedByte => start + 1,
758 };
759 source.get(start..end.min(source.len())).unwrap_or(&[])
760}
761
762fn blob_token_end(source: &[u8], start: usize) -> usize {
767 let mut cursor = start + 2;
768 while source.get(cursor).is_some_and(u8::is_ascii_hexdigit) {
769 cursor += 1;
770 }
771 while source.get(cursor).is_some_and(|byte| *byte != b'\'') {
772 cursor += 1;
773 }
774 (cursor + 1).min(source.len())
775}
776
777fn number_token_end(source: &[u8], start: usize) -> usize {
785 let at = |index: usize| source.get(index).copied();
786 let mut cursor = start;
787 let hex = at(start) == Some(b'0')
788 && at(start + 1).is_some_and(|byte| byte.eq_ignore_ascii_case(&b'x'))
789 && at(start + 2).is_some_and(|byte| byte.is_ascii_hexdigit());
790 if hex {
791 cursor += 2;
792 while at(cursor).is_some_and(|byte| byte.is_ascii_hexdigit()) {
793 cursor += 1;
794 }
795 } else {
796 cursor = skip_digits_and_separators(source, cursor);
797 if at(cursor) == Some(b'.') {
798 cursor = skip_digits_and_separators(source, cursor + 1);
799 }
800 let sign = usize::from(matches!(at(cursor + 1), Some(b'+') | Some(b'-')));
801 let exponent = at(cursor).is_some_and(|byte| byte.eq_ignore_ascii_case(&b'e'))
802 && at(cursor + 1 + sign).is_some_and(|byte| byte.is_ascii_digit());
803 if exponent {
804 cursor = skip_digits_and_separators(source, cursor + 1 + sign);
805 }
806 }
807 while at(cursor).is_some_and(is_identifier_part) {
808 cursor += 1;
809 }
810 cursor
811}
812
813fn skip_digits_and_separators(source: &[u8], from: usize) -> usize {
818 let mut cursor = from;
819 loop {
820 let byte = source.get(cursor).copied();
821 let between = byte == Some(b'_')
822 && cursor > from
823 && source.get(cursor + 1).is_some_and(u8::is_ascii_digit)
824 && source.get(cursor - 1).is_some_and(u8::is_ascii_digit);
825 if byte.is_some_and(|byte| byte.is_ascii_digit()) || between {
826 cursor += 1;
827 } else {
828 return cursor;
829 }
830 }
831}
832
833pub fn identifier_text<'a>(source: &'a [u8], token: Token) -> std::borrow::Cow<'a, [u8]> {
838 let raw = token.text(source);
839 let TokenKind::Identifier { quote, .. } = token.kind else {
840 return std::borrow::Cow::Borrowed(raw);
841 };
842 match quote {
843 QuoteForm::Bare => std::borrow::Cow::Borrowed(raw),
844 QuoteForm::Bracket => {
845 std::borrow::Cow::Borrowed(raw.get(1..raw.len().saturating_sub(1)).unwrap_or(&[]))
846 }
847 QuoteForm::Double => unquote(raw, b'"'),
848 QuoteForm::Backtick => unquote(raw, b'`'),
849 }
850}
851
852pub fn string_text(source: &[u8], token: Token) -> std::borrow::Cow<'_, [u8]> {
854 unquote(token.text(source), b'\'')
855}
856
857fn unquote(raw: &[u8], delimiter: u8) -> std::borrow::Cow<'_, [u8]> {
859 let body = raw.get(1..raw.len().saturating_sub(1)).unwrap_or(&[]);
860 if !body.contains(&delimiter) {
861 return std::borrow::Cow::Borrowed(body);
862 }
863 let mut out = Vec::with_capacity(body.len());
864 let mut index = 0;
865 while let Some(byte) = body.get(index).copied() {
866 out.push(byte);
867 index += if byte == delimiter && body.get(index + 1) == Some(&delimiter) {
868 2
869 } else {
870 1
871 };
872 }
873 std::borrow::Cow::Owned(out)
874}
875
876pub fn blob_bytes(source: &[u8], token: Token) -> Vec<u8> {
878 let raw = token.text(source);
879 let body = raw.get(2..raw.len().saturating_sub(1)).unwrap_or(&[]);
880 let mut out = Vec::with_capacity(body.len() / 2);
881 let mut index = 0;
882 while let (Some(high), Some(low)) = (body.get(index), body.get(index + 1)) {
883 let high = (*high as char).to_digit(16).unwrap_or(0) as u8;
884 let low = (*low as char).to_digit(16).unwrap_or(0) as u8;
885 out.push((high << 4) | low);
886 index += 2;
887 }
888 out
889}
890
891pub fn line_and_column(source: &[u8], offset: u32) -> (u32, u32) {
893 let limit = (offset as usize).min(source.len());
894 let mut line = 1u32;
895 let mut column = 1u32;
896 for byte in source.get(..limit).unwrap_or(&[]) {
897 if *byte == b'\n' {
898 line = line.saturating_add(1);
899 column = 1;
900 } else {
901 column = column.saturating_add(1);
902 }
903 }
904 (line, column)
905}
906
907#[cfg(test)]
908mod tests {
909 use super::*;
910
911 fn tokens(source: &str) -> Result<Vec<Token>, LexError> {
913 let bytes = source.as_bytes();
914 let mut lexer = Lexer::new(bytes);
915 let mut out = Vec::new();
916 loop {
917 let token = lexer.next_token()?;
918 if token.kind == TokenKind::EndOfInput {
919 return Ok(out);
920 }
921 out.push(token);
922 }
923 }
924
925 #[test]
929 fn spans_are_ordered_disjoint_and_inside_the_source() {
930 let source = "SELECT a, 'x' /* c */ FROM t -- tail\nWHERE b=1;";
931 let found = tokens(source).expect("it lexes");
932 let mut previous_end = 0u32;
933 for token in &found {
934 assert!(token.span.start >= previous_end, "{token:?}");
935 assert!(token.span.end <= source.len() as u32, "{token:?}");
936 assert!(token.span.end > token.span.start, "{token:?}");
937 previous_end = token.span.end;
938 }
939 }
940
941 #[test]
944 fn a_keyword_is_an_identifier_carrying_a_keyword() {
945 let found = tokens("select key").expect("it lexes");
946 assert_eq!(
947 found.first().and_then(|t| t.keyword()),
948 Some(Keyword::SELECT)
949 );
950 assert_eq!(found.get(1).and_then(|t| t.keyword()), Some(Keyword::KEY));
951 assert!(found
952 .get(1)
953 .and_then(|t| t.keyword())
954 .is_some_and(Keyword::may_fall_back));
955 }
956
957 #[test]
960 fn the_four_identifier_quote_forms_are_distinguished() {
961 let found = tokens("a \"b\" [c] `d`").expect("it lexes");
962 let forms: Vec<QuoteForm> = found
963 .iter()
964 .filter_map(|token| match token.kind {
965 TokenKind::Identifier { quote, .. } => Some(quote),
966 _ => None,
967 })
968 .collect();
969 assert_eq!(
970 forms,
971 vec![
972 QuoteForm::Bare,
973 QuoteForm::Double,
974 QuoteForm::Bracket,
975 QuoteForm::Backtick
976 ]
977 );
978 }
979
980 #[test]
983 fn doubled_quotes_are_undoubled() {
984 let source = b"'it''s'";
985 let mut lexer = Lexer::new(source);
986 let token = lexer.next_token().expect("it lexes");
987 assert_eq!(token.kind, TokenKind::String);
988 assert_eq!(string_text(source, token).as_ref(), b"it's");
989
990 let plain = b"'plain'";
991 let mut lexer = Lexer::new(plain);
992 let token = lexer.next_token().expect("it lexes");
993 assert!(matches!(
994 string_text(plain, token),
995 std::borrow::Cow::Borrowed(_)
996 ));
997 }
998
999 #[test]
1002 fn an_unterminated_quote_reports_its_opening_byte() {
1003 let mut lexer = Lexer::new(b"SELECT 'abc");
1004 assert_eq!(
1005 lexer.next_token().map(|t| t.kind),
1006 Ok(TokenKind::Identifier {
1007 keyword: Some(Keyword::SELECT),
1008 quote: QuoteForm::Bare
1009 })
1010 );
1011 assert_eq!(
1012 lexer.next_token(),
1013 Err(LexError {
1014 kind: LexErrorKind::UnterminatedQuote,
1015 offset: 7
1016 })
1017 );
1018 }
1019
1020 #[test]
1023 fn every_numeric_form_lexes() {
1024 for (source, kind) in [
1025 ("1", TokenKind::Integer),
1026 ("1_000", TokenKind::Integer),
1027 ("0x1f", TokenKind::Integer),
1028 ("0XFF", TokenKind::Integer),
1029 ("1.5", TokenKind::Float),
1030 (".5", TokenKind::Float),
1031 ("1.", TokenKind::Float),
1032 ("1e10", TokenKind::Float),
1033 ("1E+10", TokenKind::Float),
1034 ("1.5e-3", TokenKind::Float),
1035 ] {
1036 let found = tokens(source).expect(source);
1037 assert_eq!(found.first().map(|t| t.kind), Some(kind), "{source}");
1038 assert_eq!(found.len(), 1, "{source}");
1039 }
1040 }
1041
1042 #[test]
1044 fn a_number_glued_to_a_word_is_rejected() {
1045 assert_eq!(
1046 tokens("123abc").map(|_| ()),
1047 Err(LexError {
1048 kind: LexErrorKind::MalformedNumber,
1049 offset: 0
1050 })
1051 );
1052 }
1053
1054 #[test]
1057 fn blob_literals_decode() {
1058 let source = b"X'48690a'";
1059 let mut lexer = Lexer::new(source);
1060 let token = lexer.next_token().expect("it lexes");
1061 assert_eq!(token.kind, TokenKind::Blob);
1062 assert_eq!(blob_bytes(source, token), vec![0x48, 0x69, 0x0a]);
1063 assert!(tokens("x'abc'").is_err());
1064 assert!(tokens("x'zz'").is_err());
1065 }
1066
1067 #[test]
1069 fn every_parameter_form_lexes() {
1070 for source in ["?", "?12", ":name", "@name", "$name"] {
1071 let found = tokens(source).expect(source);
1072 assert_eq!(found.len(), 1, "{source}");
1073 assert_eq!(found.first().map(|t| t.kind), Some(TokenKind::Parameter));
1074 }
1075 assert!(tokens(":").is_err());
1076 }
1077
1078 #[test]
1080 fn comments_are_trivia() {
1081 let found = tokens("1 -- comment\n+ /* block */ 2").expect("it lexes");
1082 assert_eq!(found.len(), 3);
1083 assert!(found.get(1).is_some_and(|t| t.is(Punctuator::Plus)));
1084 }
1085
1086 #[test]
1089 fn an_unterminated_block_comment_at_end_of_input_is_accepted() {
1090 let found = tokens("SELECT 1 /* trailing").expect("it lexes");
1091 assert_eq!(found.len(), 2);
1092 }
1093
1094 #[test]
1096 fn operators_lex_longest_first() {
1097 let found = tokens("a->>b->c||d<<e").expect("it lexes");
1098 let punctuators: Vec<&'static str> = found
1099 .iter()
1100 .filter_map(|token| match token.kind {
1101 TokenKind::Punctuator(punctuator) => Some(punctuator.as_str()),
1102 _ => None,
1103 })
1104 .collect();
1105 assert_eq!(punctuators, vec!["->>", "->", "||", "<<"]);
1106 }
1107
1108 #[test]
1111 fn line_and_column_count_from_one() {
1112 let source = b"SELECT\n 1";
1113 assert_eq!(line_and_column(source, 0), (1, 1));
1114 assert_eq!(line_and_column(source, 9), (2, 3));
1115 }
1116
1117 #[test]
1120 fn high_bytes_are_identifier_characters() {
1121 let found = tokens("naïve").expect("it lexes");
1122 assert_eq!(found.len(), 1);
1123 }
1124}