1use std::cmp::Ordering;
14
15use crate::error::SyntaxError;
16use crate::kind::SyntaxKind;
17use crate::text::TextRange;
18
19const TAB_WIDTH: u32 = 4;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct Token {
29 pub kind: SyntaxKind,
30 pub range: TextRange,
31}
32
33impl Token {
34 #[must_use]
35 pub fn text(self, source: &str) -> &str {
36 self.range.slice(source)
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct LexResult {
43 pub tokens: Vec<Token>,
44 pub errors: Vec<SyntaxError>,
45}
46
47#[derive(Debug, Clone, Copy)]
63struct LambdaContext {
64 bracket_depth: u32,
67 body_column: Option<u32>,
75 indent_len: usize,
78 inline: bool,
92}
93
94#[must_use]
99pub fn tokenize(source: &str) -> LexResult {
100 Lexer::new(source).run()
101}
102
103struct Lexer<'a> {
104 source: &'a str,
105 bytes: &'a [u8],
106 pos: u32,
107 tokens: Vec<Token>,
108 errors: Vec<SyntaxError>,
109 indents: Vec<u32>,
111 bracket_depth: u32,
114 lambda_stack: Vec<LambdaContext>,
116 pending_lambda: Option<u32>,
119 at_line_start: bool,
121}
122
123impl<'a> Lexer<'a> {
124 fn new(source: &'a str) -> Self {
125 Self {
126 source,
127 bytes: source.as_bytes(),
128 pos: 0,
129 tokens: Vec::with_capacity(source.len() / 4),
131 errors: Vec::new(),
132 indents: vec![0],
133 bracket_depth: 0,
134 lambda_stack: Vec::new(),
135 pending_lambda: None,
136 at_line_start: true,
137 }
138 }
139
140 fn run(mut self) -> LexResult {
141 while !self.at_eof() {
142 if self.at_line_start && self.indent_significant() {
143 self.lex_line_start();
144 } else {
145 self.lex_token();
146 }
147 }
148
149 let end = self.pos;
151 while !self.lambda_stack.is_empty() {
152 self.close_top_lambda(end);
153 }
154 while self.indents.len() > 1 {
155 self.indents.pop();
156 self.push(SyntaxKind::Dedent, TextRange::empty(end));
157 }
158 self.push(SyntaxKind::Eof, TextRange::empty(end));
159
160 LexResult {
161 tokens: self.tokens,
162 errors: self.errors,
163 }
164 }
165
166 fn lex_line_start(&mut self) {
174 let start = self.pos;
175 let mut column = 0;
176 let mut cursor = self.pos;
177 while let Some(byte) = self.byte_at(cursor) {
178 match byte {
179 b' ' => column += 1,
180 b'\t' => column = (column / TAB_WIDTH + 1) * TAB_WIDTH,
182 _ => break,
183 }
184 cursor += 1;
185 }
186
187 let indent_range = TextRange::new(start, cursor);
188 let blank_line = matches!(
191 self.byte_at(cursor),
192 None | Some(b'\n' | b'\r' | b'#' | b'\\')
193 );
194
195 if blank_line {
196 if !indent_range.is_empty() {
199 self.pos = cursor;
200 self.push(SyntaxKind::Whitespace, indent_range);
201 }
202 self.at_line_start = false;
203 self.lex_token();
205 return;
206 }
207
208 while let Some(context) = self.lambda_stack.last() {
212 match context.body_column {
213 None if context.inline => self.close_top_lambda(start),
215 None => {
216 self.lambda_stack
217 .last_mut()
218 .expect("checked immediately above")
219 .body_column = Some(column);
220 break;
221 }
222 Some(body_column) if column < body_column => self.close_top_lambda(start),
223 Some(_) => break,
224 }
225 }
226
227 if !self.indent_significant() {
230 if !indent_range.is_empty() {
231 self.pos = cursor;
232 self.push(SyntaxKind::Whitespace, indent_range);
233 }
234 self.at_line_start = false;
235 return;
236 }
237
238 let current = *self.indents.last().expect("indent stack is never empty");
241 match column.cmp(¤t) {
242 Ordering::Greater => {
243 self.pos = cursor;
244 self.emit_indent_whitespace(indent_range);
245 self.indents.push(column);
246 self.push(SyntaxKind::Indent, TextRange::empty(cursor));
247 }
248 Ordering::Less => {
249 while *self.indents.last().expect("indent stack is never empty") > column {
250 self.indents.pop();
251 self.push(SyntaxKind::Dedent, TextRange::empty(start));
252 }
253 if *self.indents.last().expect("indent stack is never empty") != column {
254 self.errors.push(SyntaxError::new(
255 indent_range,
256 "unindent does not match any outer indentation level",
257 ));
258 self.indents.push(column);
260 }
261 self.pos = cursor;
262 self.emit_indent_whitespace(indent_range);
263 }
264 Ordering::Equal => {
265 self.pos = cursor;
266 self.emit_indent_whitespace(indent_range);
267 }
268 }
269
270 self.at_line_start = false;
271 }
272
273 fn emit_indent_whitespace(&mut self, indent_range: TextRange) {
275 if !indent_range.is_empty() {
276 self.push(SyntaxKind::Whitespace, indent_range);
277 }
278 }
279
280 fn lex_token(&mut self) {
283 let start = self.pos;
284 let Some(byte) = self.byte_at(self.pos) else {
285 return;
286 };
287
288 let kind = match byte {
289 b' ' | b'\t' => {
290 self.bump_while(|b| b == b' ' || b == b'\t');
291 SyntaxKind::Whitespace
292 }
293 b'\n' => {
294 self.pos += 1;
295 self.finish_line();
296 SyntaxKind::Newline
297 }
298 b'\r' => {
299 self.pos += 1;
300 if self.byte_at(self.pos) == Some(b'\n') {
301 self.pos += 1;
302 }
303 self.finish_line();
304 SyntaxKind::Newline
305 }
306 b'\\' => self.lex_backslash(),
307 b'#' => self.lex_comment(),
308 b'0'..=b'9' => self.lex_number(),
309 b'.' if matches!(self.byte_at(self.pos + 1), Some(b'0'..=b'9')) => self.lex_number(),
310 b'"' | b'\'' => self.lex_string(),
311 b'$' => self.lex_node_path(SyntaxKind::GetNode),
312 b'&' | b'^' if self.starts_annotated_string() => self.lex_annotated_string(byte),
313 b'%' if self.starts_unique_node() => self.lex_node_path(SyntaxKind::UniqueNode),
314 b if is_ident_start(b) => self.lex_ident_or_keyword(),
315 _ => self.lex_operator(),
316 };
317
318 if self.pos == start && kind == SyntaxKind::Unknown {
320 self.pos += 1;
321 }
322
323 if kind == SyntaxKind::FuncKw && self.bracket_depth > 0 {
326 self.pending_lambda = Some(self.bracket_depth);
327 } else if kind == SyntaxKind::Colon
328 && self.bracket_depth > 0
329 && self.pending_lambda == Some(self.bracket_depth)
330 {
331 self.pending_lambda = None;
332 self.lambda_stack.push(LambdaContext {
333 bracket_depth: self.bracket_depth,
334 body_column: None,
335 indent_len: self.indents.len(),
336 inline: false,
337 });
338 } else if kind != SyntaxKind::Newline
339 && let Some(context) = self.lambda_stack.last_mut()
340 && context.body_column.is_none()
341 {
342 context.inline = true;
345 }
346
347 if kind != SyntaxKind::Newline {
348 self.at_line_start = false;
349 }
350
351 self.push(kind, TextRange::new(start, self.pos));
352 }
353
354 fn indent_significant(&self) -> bool {
359 match self.lambda_stack.last() {
360 Some(context) => self.bracket_depth == context.bracket_depth,
361 None => self.bracket_depth == 0,
362 }
363 }
364
365 fn close_top_lambda(&mut self, offset: u32) {
367 let Some(context) = self.lambda_stack.pop() else {
368 return;
369 };
370 while self.indents.len() > context.indent_len {
371 self.indents.pop();
372 self.push(SyntaxKind::Dedent, TextRange::empty(offset));
373 }
374 }
375
376 fn finish_line(&mut self) {
378 self.at_line_start = true;
379 }
380
381 fn lex_backslash(&mut self) -> SyntaxKind {
382 let start = self.pos;
383 let mut cursor = self.pos + 1;
384 while matches!(self.byte_at(cursor), Some(b' ' | b'\t')) {
387 cursor += 1;
388 }
389 match self.byte_at(cursor) {
390 Some(b'\n') => {
391 self.pos = cursor + 1;
392 SyntaxKind::LineContinuation
393 }
394 Some(b'\r') => {
395 cursor += 1;
396 if self.byte_at(cursor) == Some(b'\n') {
397 cursor += 1;
398 }
399 self.pos = cursor;
400 SyntaxKind::LineContinuation
401 }
402 _ => {
403 self.pos = start + 1;
404 self.errors.push(SyntaxError::new(
405 TextRange::new(start, self.pos),
406 "stray `\\` outside a line continuation",
407 ));
408 SyntaxKind::Unknown
409 }
410 }
411 }
412
413 fn lex_comment(&mut self) -> SyntaxKind {
414 let doc = self.byte_at(self.pos + 1) == Some(b'#');
415 self.bump_while(|b| b != b'\n' && b != b'\r');
416 if doc {
417 SyntaxKind::DocComment
418 } else {
419 SyntaxKind::Comment
420 }
421 }
422
423 fn lex_number(&mut self) -> SyntaxKind {
426 let mut is_float = false;
427
428 if self.byte_at(self.pos) == Some(b'0')
429 && matches!(self.byte_at(self.pos + 1), Some(b'x' | b'X' | b'b' | b'B'))
430 {
431 let radix_marker = self.byte_at(self.pos + 1).expect("checked above");
432 self.pos += 2;
433 if radix_marker == b'x' || radix_marker == b'X' {
434 self.bump_while(|b| b.is_ascii_hexdigit() || b == b'_');
435 } else {
436 self.bump_while(|b| matches!(b, b'0' | b'1' | b'_'));
437 }
438 return SyntaxKind::Int;
439 }
440
441 self.bump_while(|b| b.is_ascii_digit() || b == b'_');
442
443 if self.byte_at(self.pos) == Some(b'.')
446 && matches!(self.byte_at(self.pos + 1), Some(b'0'..=b'9'))
447 {
448 is_float = true;
449 self.pos += 1;
450 self.bump_while(|b| b.is_ascii_digit() || b == b'_');
451 } else if self.byte_at(self.pos) == Some(b'.')
452 && !matches!(self.byte_at(self.pos + 1), Some(b'.'))
453 && !matches!(self.byte_at(self.pos + 1), Some(b) if is_ident_start(b))
454 {
455 is_float = true;
457 self.pos += 1;
458 }
459
460 if matches!(self.byte_at(self.pos), Some(b'e' | b'E')) {
461 let mut cursor = self.pos + 1;
462 if matches!(self.byte_at(cursor), Some(b'+' | b'-')) {
463 cursor += 1;
464 }
465 if matches!(self.byte_at(cursor), Some(b'0'..=b'9')) {
466 is_float = true;
467 self.pos = cursor;
468 self.bump_while(|b| b.is_ascii_digit() || b == b'_');
469 }
470 }
471
472 if is_float {
473 SyntaxKind::Float
474 } else {
475 SyntaxKind::Int
476 }
477 }
478
479 fn starts_annotated_string(&self) -> bool {
482 matches!(self.byte_at(self.pos + 1), Some(b'"' | b'\'')) && !self.prev_can_end_expr()
483 }
484
485 fn lex_annotated_string(&mut self, sigil: u8) -> SyntaxKind {
486 self.pos += 1;
487 self.lex_string();
488 if sigil == b'&' {
489 SyntaxKind::StringName
490 } else {
491 SyntaxKind::NodePath
492 }
493 }
494
495 fn starts_unique_node(&self) -> bool {
498 if self.prev_can_end_expr() {
499 return false;
500 }
501 matches!(self.byte_at(self.pos + 1), Some(b'"' | b'\''))
502 || matches!(self.byte_at(self.pos + 1), Some(b) if is_ident_start(b))
503 }
504
505 fn lex_node_path(&mut self, kind: SyntaxKind) -> SyntaxKind {
510 let sigil_start = self.pos;
511 self.pos += 1;
512
513 if matches!(self.byte_at(self.pos), Some(b'"' | b'\'')) {
514 self.lex_string();
515 return kind;
516 }
517
518 let mut matched_any = false;
519
520 if self.byte_at(self.pos) == Some(b'/') {
522 self.pos += 1;
523 matched_any = true;
524 }
525
526 loop {
527 if self.byte_at(self.pos) == Some(b'.') && self.byte_at(self.pos + 1) == Some(b'.') {
529 self.pos += 2;
530 matched_any = true;
531 } else {
532 if self.byte_at(self.pos) == Some(b'%') {
533 self.pos += 1;
534 matched_any = true;
535 }
536 if matches!(self.byte_at(self.pos), Some(b) if is_ident_start(b)) {
537 self.bump_while(is_ident_continue);
538 matched_any = true;
539 } else {
540 break;
541 }
542 }
543
544 if self.byte_at(self.pos) == Some(b'/') {
545 self.pos += 1;
546 } else {
547 break;
548 }
549 }
550
551 if !matched_any {
552 self.errors.push(SyntaxError::new(
553 TextRange::new(sigil_start, self.pos),
554 "expected a node path after the sigil",
555 ));
556 }
557 kind
558 }
559
560 fn lex_string(&mut self) -> SyntaxKind {
561 let start = self.pos;
562 let quote = self.byte_at(self.pos).expect("caller checked for a quote");
563
564 let triple =
566 self.byte_at(self.pos + 1) == Some(quote) && self.byte_at(self.pos + 2) == Some(quote);
567 let delim_len = if triple { 3 } else { 1 };
568 self.pos += delim_len;
569
570 loop {
571 let Some(byte) = self.byte_at(self.pos) else {
572 self.errors.push(SyntaxError::new(
573 TextRange::new(start, self.pos),
574 "unterminated string literal",
575 ));
576 break;
577 };
578
579 if byte == b'\\' {
582 self.pos += 1;
583 if self.pos < self.len() {
584 self.pos += 1;
585 }
586 continue;
587 }
588
589 if !triple && matches!(byte, b'\n' | b'\r') {
590 self.errors.push(SyntaxError::new(
591 TextRange::new(start, self.pos),
592 "unterminated string literal",
593 ));
594 break;
595 }
596
597 if byte == quote {
598 if triple {
599 if self.byte_at(self.pos + 1) == Some(quote)
600 && self.byte_at(self.pos + 2) == Some(quote)
601 {
602 self.pos += 3;
603 break;
604 }
605 self.pos += 1;
606 continue;
607 }
608 self.pos += 1;
609 break;
610 }
611
612 self.pos += 1;
613 }
614
615 SyntaxKind::Str
616 }
617
618 fn lex_ident_or_keyword(&mut self) -> SyntaxKind {
619 let start = self.pos;
620 self.bump_while(is_ident_continue);
621 let text = TextRange::new(start, self.pos).slice(self.source);
622
623 if text == "r" && matches!(self.byte_at(self.pos), Some(b'"' | b'\'')) {
625 self.lex_string();
626 return SyntaxKind::Str;
627 }
628
629 SyntaxKind::from_keyword(text).unwrap_or(SyntaxKind::Ident)
630 }
631
632 #[allow(clippy::too_many_lines)]
635 fn lex_operator(&mut self) -> SyntaxKind {
636 let byte = self.byte_at(self.pos).expect("caller checked for a byte");
637 let next = self.byte_at(self.pos + 1);
638 let after = self.byte_at(self.pos + 2);
639
640 match byte {
644 b')' | b']' | b'}' => {
645 while self
646 .lambda_stack
647 .last()
648 .is_some_and(|context| context.bracket_depth >= self.bracket_depth)
649 {
650 self.close_top_lambda(self.pos);
651 }
652 }
653 b',' => {
654 while self
655 .lambda_stack
656 .last()
657 .is_some_and(|context| context.bracket_depth == self.bracket_depth)
658 {
659 self.close_top_lambda(self.pos);
660 }
661 }
662 _ => {}
663 }
664
665 self.pos += 1;
666
667 macro_rules! two {
668 ($kind:expr) => {{
669 self.pos += 1;
670 $kind
671 }};
672 }
673 macro_rules! three {
674 ($kind:expr) => {{
675 self.pos += 2;
676 $kind
677 }};
678 }
679
680 match byte {
681 b'+' if next == Some(b'=') => two!(SyntaxKind::PlusEq),
682 b'+' => SyntaxKind::Plus,
683 b'-' if next == Some(b'=') => two!(SyntaxKind::MinusEq),
684 b'-' if next == Some(b'>') => two!(SyntaxKind::Arrow),
685 b'-' => SyntaxKind::Minus,
686 b'*' if next == Some(b'*') && after == Some(b'=') => three!(SyntaxKind::StarStarEq),
687 b'*' if next == Some(b'*') => two!(SyntaxKind::StarStar),
688 b'*' if next == Some(b'=') => two!(SyntaxKind::StarEq),
689 b'*' => SyntaxKind::Star,
690 b'/' if next == Some(b'=') => two!(SyntaxKind::SlashEq),
691 b'/' => SyntaxKind::Slash,
692 b'%' if next == Some(b'=') => two!(SyntaxKind::PercentEq),
693 b'%' => SyntaxKind::Percent,
694 b'=' if next == Some(b'=') => two!(SyntaxKind::EqEq),
695 b'=' => SyntaxKind::Eq,
696 b'!' if next == Some(b'=') => two!(SyntaxKind::BangEq),
697 b'!' => SyntaxKind::Bang,
698 b'<' if next == Some(b'<') && after == Some(b'=') => three!(SyntaxKind::ShlEq),
699 b'<' if next == Some(b'<') => two!(SyntaxKind::Shl),
700 b'<' if next == Some(b'=') => two!(SyntaxKind::LtEq),
701 b'<' => SyntaxKind::Lt,
702 b'>' if next == Some(b'>') && after == Some(b'=') => three!(SyntaxKind::ShrEq),
703 b'>' if next == Some(b'>') => two!(SyntaxKind::Shr),
704 b'>' if next == Some(b'=') => two!(SyntaxKind::GtEq),
705 b'>' => SyntaxKind::Gt,
706 b'&' if next == Some(b'&') => two!(SyntaxKind::AmpAmp),
707 b'&' if next == Some(b'=') => two!(SyntaxKind::AmpEq),
708 b'&' => SyntaxKind::Amp,
709 b'|' if next == Some(b'|') => two!(SyntaxKind::PipePipe),
710 b'|' if next == Some(b'=') => two!(SyntaxKind::PipeEq),
711 b'|' => SyntaxKind::Pipe,
712 b'^' if next == Some(b'=') => two!(SyntaxKind::CaretEq),
713 b'^' => SyntaxKind::Caret,
714 b'~' => SyntaxKind::Tilde,
715 b':' if next == Some(b'=') => two!(SyntaxKind::ColonEq),
716 b':' => SyntaxKind::Colon,
717 b';' => SyntaxKind::Semicolon,
718 b',' => SyntaxKind::Comma,
719 b'.' if next == Some(b'.') && after == Some(b'.') => three!(SyntaxKind::Ellipsis),
720 b'.' if next == Some(b'.') => two!(SyntaxKind::DotDot),
721 b'.' => SyntaxKind::Dot,
722 b'@' => SyntaxKind::At,
723 b'$' => SyntaxKind::Dollar,
724 b'(' => {
725 self.bracket_depth += 1;
726 SyntaxKind::LParen
727 }
728 b')' => {
729 self.bracket_depth = self.bracket_depth.saturating_sub(1);
730 SyntaxKind::RParen
731 }
732 b'[' => {
733 self.bracket_depth += 1;
734 SyntaxKind::LBracket
735 }
736 b']' => {
737 self.bracket_depth = self.bracket_depth.saturating_sub(1);
738 SyntaxKind::RBracket
739 }
740 b'{' => {
741 self.bracket_depth += 1;
742 SyntaxKind::LBrace
743 }
744 b'}' => {
745 self.bracket_depth = self.bracket_depth.saturating_sub(1);
746 SyntaxKind::RBrace
747 }
748 _ => {
749 while self.pos < self.len() && !self.source.is_char_boundary(self.pos as usize) {
752 self.pos += 1;
753 }
754 self.errors.push(SyntaxError::new(
755 TextRange::new(self.pos - 1, self.pos),
756 "unexpected character",
757 ));
758 SyntaxKind::Unknown
759 }
760 }
761 }
762
763 fn prev_can_end_expr(&self) -> bool {
776 for token in self.tokens.iter().rev() {
777 if token.kind == SyntaxKind::Newline {
780 return false;
781 }
782 if token.kind.is_trivia() {
783 continue;
784 }
785 return matches!(
786 token.kind,
787 SyntaxKind::Ident
788 | SyntaxKind::Int
789 | SyntaxKind::Float
790 | SyntaxKind::Str
791 | SyntaxKind::StringName
792 | SyntaxKind::NodePath
793 | SyntaxKind::GetNode
794 | SyntaxKind::UniqueNode
795 | SyntaxKind::RParen
796 | SyntaxKind::RBracket
797 | SyntaxKind::RBrace
798 | SyntaxKind::SelfKw
799 | SyntaxKind::SuperKw
800 | SyntaxKind::TrueKw
801 | SyntaxKind::FalseKw
802 | SyntaxKind::NullKw
803 );
804 }
805 false
806 }
807
808 fn push(&mut self, kind: SyntaxKind, range: TextRange) {
809 self.tokens.push(Token { kind, range });
810 }
811
812 fn bump_while(&mut self, predicate: impl Fn(u8) -> bool) {
813 while let Some(byte) = self.byte_at(self.pos) {
814 if !predicate(byte) {
815 break;
816 }
817 self.pos += 1;
818 }
819 }
820
821 fn byte_at(&self, pos: u32) -> Option<u8> {
822 self.bytes.get(pos as usize).copied()
823 }
824
825 fn len(&self) -> u32 {
826 self.bytes.len() as u32
827 }
828
829 fn at_eof(&self) -> bool {
830 self.pos >= self.len()
831 }
832}
833
834fn is_ident_start(byte: u8) -> bool {
835 byte.is_ascii_alphabetic() || byte == b'_' || byte >= 0x80
836}
837
838fn is_ident_continue(byte: u8) -> bool {
839 byte.is_ascii_alphanumeric() || byte == b'_' || byte >= 0x80
840}
841
842#[cfg(test)]
843mod tests {
844 use super::*;
845
846 fn assert_lossless(source: &str) {
848 let lexed = tokenize(source);
849 let rebuilt: String = lexed
850 .tokens
851 .iter()
852 .map(|token| token.text(source))
853 .collect();
854 assert_eq!(rebuilt, source, "token spans must cover the source exactly");
855 }
856
857 fn kinds(source: &str) -> Vec<SyntaxKind> {
858 tokenize(source)
859 .tokens
860 .into_iter()
861 .map(|token| token.kind)
862 .filter(|kind| !kind.is_trivia() && *kind != SyntaxKind::Eof)
863 .collect()
864 }
865
866 #[test]
867 fn round_trips_a_realistic_script() {
868 assert_lossless(
869 "@tool\nclass_name Player\nextends CharacterBody2D\n\n## Docs.\nsignal died\n\nconst MAX := 100\n\n\nfunc _ready() -> void:\n\tvar x := [1, 2, {\"a\": 1}] # trailing\n\tif x and true:\n\t\tprint($Sprite2D/Label)\n",
870 );
871 }
872
873 #[test]
874 fn round_trips_edge_cases() {
875 for source in [
876 "",
877 "\n",
878 "\n\n\n",
879 "pass",
880 "\tpass\n",
881 "a\r\nb\r\n",
882 "var s = \"unterminated\n",
883 "if a \\\n\tand b:\n\tpass\n",
884 "x = 1 § 2\n",
885 "func f():\n\t\t\tpass\n\treturn\n",
886 ] {
887 assert_lossless(source);
888 }
889 }
890
891 #[test]
892 fn emits_indent_and_dedent_around_blocks() {
893 let kinds = kinds("func f():\n\tpass\nvar x = 1\n");
894 assert_eq!(
895 kinds,
896 vec![
897 SyntaxKind::FuncKw,
898 SyntaxKind::Ident,
899 SyntaxKind::LParen,
900 SyntaxKind::RParen,
901 SyntaxKind::Colon,
902 SyntaxKind::Indent,
903 SyntaxKind::PassKw,
904 SyntaxKind::Dedent,
905 SyntaxKind::VarKw,
906 SyntaxKind::Ident,
907 SyntaxKind::Eq,
908 SyntaxKind::Int,
909 ]
910 );
911 }
912
913 #[test]
914 fn blank_and_comment_lines_do_not_shift_indentation() {
915 let kinds = kinds("func f():\n\tvar a = 1\n\n# note\n\tvar b = 2\n");
917 assert_eq!(
918 kinds.iter().filter(|k| **k == SyntaxKind::Indent).count(),
919 1
920 );
921 assert_eq!(
922 kinds.iter().filter(|k| **k == SyntaxKind::Dedent).count(),
923 1
924 );
925 }
926
927 #[test]
928 fn newlines_inside_brackets_are_not_line_breaks() {
929 let kinds = kinds("var a = [\n\t1,\n\t2,\n]\n");
931 assert!(!kinds.contains(&SyntaxKind::Indent));
932 assert!(!kinds.contains(&SyntaxKind::Dedent));
933 }
934
935 #[test]
936 fn distinguishes_modulo_from_unique_node() {
937 assert_eq!(
938 kinds("var a = b % c\n"),
939 vec![
940 SyntaxKind::VarKw,
941 SyntaxKind::Ident,
942 SyntaxKind::Eq,
943 SyntaxKind::Ident,
944 SyntaxKind::Percent,
945 SyntaxKind::Ident,
946 ]
947 );
948 assert_eq!(
949 kinds("var a = %HealthBar\n"),
950 vec![
951 SyntaxKind::VarKw,
952 SyntaxKind::Ident,
953 SyntaxKind::Eq,
954 SyntaxKind::UniqueNode,
955 ]
956 );
957 assert_eq!(
958 kinds("print(%Bar, a % 2)\n"),
959 vec![
960 SyntaxKind::Ident,
961 SyntaxKind::LParen,
962 SyntaxKind::UniqueNode,
963 SyntaxKind::Comma,
964 SyntaxKind::Ident,
965 SyntaxKind::Percent,
966 SyntaxKind::Int,
967 SyntaxKind::RParen,
968 ]
969 );
970 }
971
972 #[test]
973 fn lexes_node_paths_as_single_tokens() {
974 assert_eq!(kinds("$Sprite2D\n"), vec![SyntaxKind::GetNode]);
975 assert_eq!(kinds("$../Sibling/%Unique\n"), vec![SyntaxKind::GetNode]);
976 assert_eq!(kinds("$\"quoted/path\"\n"), vec![SyntaxKind::GetNode]);
977 assert_eq!(
979 kinds("$Sprite2D.position\n"),
980 vec![SyntaxKind::GetNode, SyntaxKind::Dot, SyntaxKind::Ident]
981 );
982 }
983
984 #[test]
985 fn lexes_string_name_and_node_path_literals() {
986 assert_eq!(kinds("emit(&\"died\")\n")[2], SyntaxKind::StringName);
987 assert_eq!(kinds("var p = ^\"a/b\"\n")[3], SyntaxKind::NodePath);
988 assert_eq!(kinds("var x = a & b\n")[4], SyntaxKind::Amp);
990 }
991
992 #[test]
993 fn lexes_number_forms() {
994 assert_eq!(kinds("1_000_000"), vec![SyntaxKind::Int]);
995 assert_eq!(kinds("0xfb8c0b"), vec![SyntaxKind::Int]);
996 assert_eq!(kinds("0b1010_1010"), vec![SyntaxKind::Int]);
997 assert_eq!(kinds("0.234"), vec![SyntaxKind::Float]);
998 assert_eq!(kinds("1e-5"), vec![SyntaxKind::Float]);
999 assert_eq!(kinds("1.5e10"), vec![SyntaxKind::Float]);
1000 assert_eq!(
1002 kinds("1..2"),
1003 vec![SyntaxKind::Int, SyntaxKind::DotDot, SyntaxKind::Int]
1004 );
1005 assert_eq!(
1007 kinds("1.max(2)"),
1008 vec![
1009 SyntaxKind::Int,
1010 SyntaxKind::Dot,
1011 SyntaxKind::Ident,
1012 SyntaxKind::LParen,
1013 SyntaxKind::Int,
1014 SyntaxKind::RParen
1015 ]
1016 );
1017 }
1018
1019 #[test]
1020 fn lexes_string_forms() {
1021 assert_eq!(kinds("\"double\""), vec![SyntaxKind::Str]);
1022 assert_eq!(kinds("'single'"), vec![SyntaxKind::Str]);
1023 assert_eq!(kinds("\"\"\"triple\nspanning\"\"\""), vec![SyntaxKind::Str]);
1024 assert_eq!(kinds("r\"raw\\n\""), vec![SyntaxKind::Str]);
1025 assert_eq!(kinds("\"esc\\\"aped\""), vec![SyntaxKind::Str]);
1026 }
1027
1028 #[test]
1029 fn separates_doc_comments_from_plain_comments() {
1030 let lexed = tokenize("## doc\n# plain\n");
1031 let comments: Vec<_> = lexed
1032 .tokens
1033 .iter()
1034 .filter(|t| t.kind.is_comment())
1035 .map(|t| t.kind)
1036 .collect();
1037 assert_eq!(comments, vec![SyntaxKind::DocComment, SyntaxKind::Comment]);
1038 }
1039
1040 #[test]
1041 fn line_continuation_joins_lines() {
1042 let kinds = kinds("var a = 1 + \\\n\t2\n");
1043 assert!(!kinds.contains(&SyntaxKind::Indent));
1044 assert_eq!(kinds.last(), Some(&SyntaxKind::Int));
1045 }
1046
1047 #[test]
1048 fn reports_inconsistent_dedent() {
1049 let lexed = tokenize("func f():\n\t\tpass\n\treturn\n");
1050 assert!(
1051 lexed
1052 .errors
1053 .iter()
1054 .any(|e| e.message().contains("unindent")),
1055 "expected an unindent diagnostic, got {:?}",
1056 lexed.errors
1057 );
1058 }
1059
1060 #[test]
1061 fn reports_unterminated_string() {
1062 let lexed = tokenize("var s = \"oops\n");
1063 assert!(
1064 lexed
1065 .errors
1066 .iter()
1067 .any(|e| e.message().contains("unterminated"))
1068 );
1069 }
1070
1071 #[test]
1072 fn closes_open_blocks_at_eof() {
1073 let lexed = tokenize("func f():\n\tif a:\n\t\tpass");
1074 let dedents = lexed
1075 .tokens
1076 .iter()
1077 .filter(|t| t.kind == SyntaxKind::Dedent)
1078 .count();
1079 assert_eq!(dedents, 2);
1080 assert_eq!(lexed.tokens.last().map(|t| t.kind), Some(SyntaxKind::Eof));
1081 }
1082}