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 if let Some(context) = self.lambda_stack.last_mut() {
340 if context.body_column.is_none() {
341 context.inline = true;
344 }
345 }
346 }
347
348 if kind != SyntaxKind::Newline {
349 self.at_line_start = false;
350 }
351
352 self.push(kind, TextRange::new(start, self.pos));
353 }
354
355 fn indent_significant(&self) -> bool {
360 match self.lambda_stack.last() {
361 Some(context) => self.bracket_depth == context.bracket_depth,
362 None => self.bracket_depth == 0,
363 }
364 }
365
366 fn close_top_lambda(&mut self, offset: u32) {
368 let Some(context) = self.lambda_stack.pop() else {
369 return;
370 };
371 while self.indents.len() > context.indent_len {
372 self.indents.pop();
373 self.push(SyntaxKind::Dedent, TextRange::empty(offset));
374 }
375 }
376
377 fn finish_line(&mut self) {
379 self.at_line_start = true;
380 }
381
382 fn lex_backslash(&mut self) -> SyntaxKind {
383 let start = self.pos;
384 let mut cursor = self.pos + 1;
385 while matches!(self.byte_at(cursor), Some(b' ' | b'\t')) {
388 cursor += 1;
389 }
390 match self.byte_at(cursor) {
391 Some(b'\n') => {
392 self.pos = cursor + 1;
393 SyntaxKind::LineContinuation
394 }
395 Some(b'\r') => {
396 cursor += 1;
397 if self.byte_at(cursor) == Some(b'\n') {
398 cursor += 1;
399 }
400 self.pos = cursor;
401 SyntaxKind::LineContinuation
402 }
403 _ => {
404 self.pos = start + 1;
405 self.errors.push(SyntaxError::new(
406 TextRange::new(start, self.pos),
407 "stray `\\` outside a line continuation",
408 ));
409 SyntaxKind::Unknown
410 }
411 }
412 }
413
414 fn lex_comment(&mut self) -> SyntaxKind {
415 let doc = self.byte_at(self.pos + 1) == Some(b'#');
416 self.bump_while(|b| b != b'\n' && b != b'\r');
417 if doc {
418 SyntaxKind::DocComment
419 } else {
420 SyntaxKind::Comment
421 }
422 }
423
424 fn lex_number(&mut self) -> SyntaxKind {
427 let mut is_float = false;
428
429 if self.byte_at(self.pos) == Some(b'0')
430 && matches!(self.byte_at(self.pos + 1), Some(b'x' | b'X' | b'b' | b'B'))
431 {
432 let radix_marker = self.byte_at(self.pos + 1).expect("checked above");
433 self.pos += 2;
434 if radix_marker == b'x' || radix_marker == b'X' {
435 self.bump_while(|b| b.is_ascii_hexdigit() || b == b'_');
436 } else {
437 self.bump_while(|b| matches!(b, b'0' | b'1' | b'_'));
438 }
439 return SyntaxKind::Int;
440 }
441
442 self.bump_while(|b| b.is_ascii_digit() || b == b'_');
443
444 if self.byte_at(self.pos) == Some(b'.')
447 && matches!(self.byte_at(self.pos + 1), Some(b'0'..=b'9'))
448 {
449 is_float = true;
450 self.pos += 1;
451 self.bump_while(|b| b.is_ascii_digit() || b == b'_');
452 } else if self.byte_at(self.pos) == Some(b'.')
453 && !matches!(self.byte_at(self.pos + 1), Some(b'.'))
454 && !matches!(self.byte_at(self.pos + 1), Some(b) if is_ident_start(b))
455 {
456 is_float = true;
458 self.pos += 1;
459 }
460
461 if matches!(self.byte_at(self.pos), Some(b'e' | b'E')) {
462 let mut cursor = self.pos + 1;
463 if matches!(self.byte_at(cursor), Some(b'+' | b'-')) {
464 cursor += 1;
465 }
466 if matches!(self.byte_at(cursor), Some(b'0'..=b'9')) {
467 is_float = true;
468 self.pos = cursor;
469 self.bump_while(|b| b.is_ascii_digit() || b == b'_');
470 }
471 }
472
473 if is_float {
474 SyntaxKind::Float
475 } else {
476 SyntaxKind::Int
477 }
478 }
479
480 fn starts_annotated_string(&self) -> bool {
483 matches!(self.byte_at(self.pos + 1), Some(b'"' | b'\'')) && !self.prev_can_end_expr()
484 }
485
486 fn lex_annotated_string(&mut self, sigil: u8) -> SyntaxKind {
487 self.pos += 1;
488 self.lex_string();
489 if sigil == b'&' {
490 SyntaxKind::StringName
491 } else {
492 SyntaxKind::NodePath
493 }
494 }
495
496 fn starts_unique_node(&self) -> bool {
499 if self.prev_can_end_expr() {
500 return false;
501 }
502 matches!(self.byte_at(self.pos + 1), Some(b'"' | b'\''))
503 || matches!(self.byte_at(self.pos + 1), Some(b) if is_ident_start(b))
504 }
505
506 fn lex_node_path(&mut self, kind: SyntaxKind) -> SyntaxKind {
511 let sigil_start = self.pos;
512 self.pos += 1;
513
514 if matches!(self.byte_at(self.pos), Some(b'"' | b'\'')) {
515 self.lex_string();
516 return kind;
517 }
518
519 let mut matched_any = false;
520
521 if self.byte_at(self.pos) == Some(b'/') {
523 self.pos += 1;
524 matched_any = true;
525 }
526
527 loop {
528 if self.byte_at(self.pos) == Some(b'.') && self.byte_at(self.pos + 1) == Some(b'.') {
530 self.pos += 2;
531 matched_any = true;
532 } else {
533 if self.byte_at(self.pos) == Some(b'%') {
534 self.pos += 1;
535 matched_any = true;
536 }
537 if matches!(self.byte_at(self.pos), Some(b) if is_ident_start(b)) {
538 self.bump_while(is_ident_continue);
539 matched_any = true;
540 } else {
541 break;
542 }
543 }
544
545 if self.byte_at(self.pos) == Some(b'/') {
546 self.pos += 1;
547 } else {
548 break;
549 }
550 }
551
552 if !matched_any {
553 self.errors.push(SyntaxError::new(
554 TextRange::new(sigil_start, self.pos),
555 "expected a node path after the sigil",
556 ));
557 }
558 kind
559 }
560
561 fn lex_string(&mut self) -> SyntaxKind {
562 let start = self.pos;
563 let quote = self.byte_at(self.pos).expect("caller checked for a quote");
564
565 let triple =
567 self.byte_at(self.pos + 1) == Some(quote) && self.byte_at(self.pos + 2) == Some(quote);
568 let delim_len = if triple { 3 } else { 1 };
569 self.pos += delim_len;
570
571 loop {
572 let Some(byte) = self.byte_at(self.pos) else {
573 self.errors.push(SyntaxError::new(
574 TextRange::new(start, self.pos),
575 "unterminated string literal",
576 ));
577 break;
578 };
579
580 if byte == b'\\' {
583 self.pos += 1;
584 if self.pos < self.len() {
585 self.pos += 1;
586 }
587 continue;
588 }
589
590 if !triple && matches!(byte, b'\n' | b'\r') {
591 self.errors.push(SyntaxError::new(
592 TextRange::new(start, self.pos),
593 "unterminated string literal",
594 ));
595 break;
596 }
597
598 if byte == quote {
599 if triple {
600 if self.byte_at(self.pos + 1) == Some(quote)
601 && self.byte_at(self.pos + 2) == Some(quote)
602 {
603 self.pos += 3;
604 break;
605 }
606 self.pos += 1;
607 continue;
608 }
609 self.pos += 1;
610 break;
611 }
612
613 self.pos += 1;
614 }
615
616 SyntaxKind::Str
617 }
618
619 fn lex_ident_or_keyword(&mut self) -> SyntaxKind {
620 let start = self.pos;
621 self.bump_while(is_ident_continue);
622 let text = TextRange::new(start, self.pos).slice(self.source);
623
624 if text == "r" && matches!(self.byte_at(self.pos), Some(b'"' | b'\'')) {
626 self.lex_string();
627 return SyntaxKind::Str;
628 }
629
630 SyntaxKind::from_keyword(text).unwrap_or(SyntaxKind::Ident)
631 }
632
633 #[allow(clippy::too_many_lines)]
636 fn lex_operator(&mut self) -> SyntaxKind {
637 let byte = self.byte_at(self.pos).expect("caller checked for a byte");
638 let next = self.byte_at(self.pos + 1);
639 let after = self.byte_at(self.pos + 2);
640
641 match byte {
645 b')' | b']' | b'}' => {
646 while self
647 .lambda_stack
648 .last()
649 .is_some_and(|context| context.bracket_depth >= self.bracket_depth)
650 {
651 self.close_top_lambda(self.pos);
652 }
653 }
654 b',' => {
655 while self
656 .lambda_stack
657 .last()
658 .is_some_and(|context| context.bracket_depth == self.bracket_depth)
659 {
660 self.close_top_lambda(self.pos);
661 }
662 }
663 _ => {}
664 }
665
666 self.pos += 1;
667
668 macro_rules! two {
669 ($kind:expr) => {{
670 self.pos += 1;
671 $kind
672 }};
673 }
674 macro_rules! three {
675 ($kind:expr) => {{
676 self.pos += 2;
677 $kind
678 }};
679 }
680
681 match byte {
682 b'+' if next == Some(b'=') => two!(SyntaxKind::PlusEq),
683 b'+' => SyntaxKind::Plus,
684 b'-' if next == Some(b'=') => two!(SyntaxKind::MinusEq),
685 b'-' if next == Some(b'>') => two!(SyntaxKind::Arrow),
686 b'-' => SyntaxKind::Minus,
687 b'*' if next == Some(b'*') && after == Some(b'=') => three!(SyntaxKind::StarStarEq),
688 b'*' if next == Some(b'*') => two!(SyntaxKind::StarStar),
689 b'*' if next == Some(b'=') => two!(SyntaxKind::StarEq),
690 b'*' => SyntaxKind::Star,
691 b'/' if next == Some(b'=') => two!(SyntaxKind::SlashEq),
692 b'/' => SyntaxKind::Slash,
693 b'%' if next == Some(b'=') => two!(SyntaxKind::PercentEq),
694 b'%' => SyntaxKind::Percent,
695 b'=' if next == Some(b'=') => two!(SyntaxKind::EqEq),
696 b'=' => SyntaxKind::Eq,
697 b'!' if next == Some(b'=') => two!(SyntaxKind::BangEq),
698 b'!' => SyntaxKind::Bang,
699 b'<' if next == Some(b'<') && after == Some(b'=') => three!(SyntaxKind::ShlEq),
700 b'<' if next == Some(b'<') => two!(SyntaxKind::Shl),
701 b'<' if next == Some(b'=') => two!(SyntaxKind::LtEq),
702 b'<' => SyntaxKind::Lt,
703 b'>' if next == Some(b'>') && after == Some(b'=') => three!(SyntaxKind::ShrEq),
704 b'>' if next == Some(b'>') => two!(SyntaxKind::Shr),
705 b'>' if next == Some(b'=') => two!(SyntaxKind::GtEq),
706 b'>' => SyntaxKind::Gt,
707 b'&' if next == Some(b'&') => two!(SyntaxKind::AmpAmp),
708 b'&' if next == Some(b'=') => two!(SyntaxKind::AmpEq),
709 b'&' => SyntaxKind::Amp,
710 b'|' if next == Some(b'|') => two!(SyntaxKind::PipePipe),
711 b'|' if next == Some(b'=') => two!(SyntaxKind::PipeEq),
712 b'|' => SyntaxKind::Pipe,
713 b'^' if next == Some(b'=') => two!(SyntaxKind::CaretEq),
714 b'^' => SyntaxKind::Caret,
715 b'~' => SyntaxKind::Tilde,
716 b':' if next == Some(b'=') => two!(SyntaxKind::ColonEq),
717 b':' => SyntaxKind::Colon,
718 b';' => SyntaxKind::Semicolon,
719 b',' => SyntaxKind::Comma,
720 b'.' if next == Some(b'.') && after == Some(b'.') => three!(SyntaxKind::Ellipsis),
721 b'.' if next == Some(b'.') => two!(SyntaxKind::DotDot),
722 b'.' => SyntaxKind::Dot,
723 b'@' => SyntaxKind::At,
724 b'$' => SyntaxKind::Dollar,
725 b'(' => {
726 self.bracket_depth += 1;
727 SyntaxKind::LParen
728 }
729 b')' => {
730 self.bracket_depth = self.bracket_depth.saturating_sub(1);
731 SyntaxKind::RParen
732 }
733 b'[' => {
734 self.bracket_depth += 1;
735 SyntaxKind::LBracket
736 }
737 b']' => {
738 self.bracket_depth = self.bracket_depth.saturating_sub(1);
739 SyntaxKind::RBracket
740 }
741 b'{' => {
742 self.bracket_depth += 1;
743 SyntaxKind::LBrace
744 }
745 b'}' => {
746 self.bracket_depth = self.bracket_depth.saturating_sub(1);
747 SyntaxKind::RBrace
748 }
749 _ => {
750 while self.pos < self.len() && !self.source.is_char_boundary(self.pos as usize) {
753 self.pos += 1;
754 }
755 self.errors.push(SyntaxError::new(
756 TextRange::new(self.pos - 1, self.pos),
757 "unexpected character",
758 ));
759 SyntaxKind::Unknown
760 }
761 }
762 }
763
764 fn prev_can_end_expr(&self) -> bool {
777 for token in self.tokens.iter().rev() {
778 if token.kind == SyntaxKind::Newline {
781 return false;
782 }
783 if token.kind.is_trivia() {
784 continue;
785 }
786 return matches!(
787 token.kind,
788 SyntaxKind::Ident
789 | SyntaxKind::Int
790 | SyntaxKind::Float
791 | SyntaxKind::Str
792 | SyntaxKind::StringName
793 | SyntaxKind::NodePath
794 | SyntaxKind::GetNode
795 | SyntaxKind::UniqueNode
796 | SyntaxKind::RParen
797 | SyntaxKind::RBracket
798 | SyntaxKind::RBrace
799 | SyntaxKind::SelfKw
800 | SyntaxKind::SuperKw
801 | SyntaxKind::TrueKw
802 | SyntaxKind::FalseKw
803 | SyntaxKind::NullKw
804 );
805 }
806 false
807 }
808
809 fn push(&mut self, kind: SyntaxKind, range: TextRange) {
810 self.tokens.push(Token { kind, range });
811 }
812
813 fn bump_while(&mut self, predicate: impl Fn(u8) -> bool) {
814 while let Some(byte) = self.byte_at(self.pos) {
815 if !predicate(byte) {
816 break;
817 }
818 self.pos += 1;
819 }
820 }
821
822 fn byte_at(&self, pos: u32) -> Option<u8> {
823 self.bytes.get(pos as usize).copied()
824 }
825
826 fn len(&self) -> u32 {
827 self.bytes.len() as u32
828 }
829
830 fn at_eof(&self) -> bool {
831 self.pos >= self.len()
832 }
833}
834
835fn is_ident_start(byte: u8) -> bool {
836 byte.is_ascii_alphabetic() || byte == b'_' || byte >= 0x80
837}
838
839fn is_ident_continue(byte: u8) -> bool {
840 byte.is_ascii_alphanumeric() || byte == b'_' || byte >= 0x80
841}
842
843#[cfg(test)]
844mod tests {
845 use super::*;
846
847 fn assert_lossless(source: &str) {
849 let lexed = tokenize(source);
850 let rebuilt: String = lexed
851 .tokens
852 .iter()
853 .map(|token| token.text(source))
854 .collect();
855 assert_eq!(rebuilt, source, "token spans must cover the source exactly");
856 }
857
858 fn kinds(source: &str) -> Vec<SyntaxKind> {
859 tokenize(source)
860 .tokens
861 .into_iter()
862 .map(|token| token.kind)
863 .filter(|kind| !kind.is_trivia() && *kind != SyntaxKind::Eof)
864 .collect()
865 }
866
867 #[test]
868 fn round_trips_a_realistic_script() {
869 assert_lossless(
870 "@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",
871 );
872 }
873
874 #[test]
875 fn round_trips_edge_cases() {
876 for source in [
877 "",
878 "\n",
879 "\n\n\n",
880 "pass",
881 "\tpass\n",
882 "a\r\nb\r\n",
883 "var s = \"unterminated\n",
884 "if a \\\n\tand b:\n\tpass\n",
885 "x = 1 § 2\n",
886 "func f():\n\t\t\tpass\n\treturn\n",
887 ] {
888 assert_lossless(source);
889 }
890 }
891
892 #[test]
893 fn emits_indent_and_dedent_around_blocks() {
894 let kinds = kinds("func f():\n\tpass\nvar x = 1\n");
895 assert_eq!(
896 kinds,
897 vec![
898 SyntaxKind::FuncKw,
899 SyntaxKind::Ident,
900 SyntaxKind::LParen,
901 SyntaxKind::RParen,
902 SyntaxKind::Colon,
903 SyntaxKind::Indent,
904 SyntaxKind::PassKw,
905 SyntaxKind::Dedent,
906 SyntaxKind::VarKw,
907 SyntaxKind::Ident,
908 SyntaxKind::Eq,
909 SyntaxKind::Int,
910 ]
911 );
912 }
913
914 #[test]
915 fn blank_and_comment_lines_do_not_shift_indentation() {
916 let kinds = kinds("func f():\n\tvar a = 1\n\n# note\n\tvar b = 2\n");
918 assert_eq!(
919 kinds.iter().filter(|k| **k == SyntaxKind::Indent).count(),
920 1
921 );
922 assert_eq!(
923 kinds.iter().filter(|k| **k == SyntaxKind::Dedent).count(),
924 1
925 );
926 }
927
928 #[test]
929 fn newlines_inside_brackets_are_not_line_breaks() {
930 let kinds = kinds("var a = [\n\t1,\n\t2,\n]\n");
932 assert!(!kinds.contains(&SyntaxKind::Indent));
933 assert!(!kinds.contains(&SyntaxKind::Dedent));
934 }
935
936 #[test]
937 fn distinguishes_modulo_from_unique_node() {
938 assert_eq!(
939 kinds("var a = b % c\n"),
940 vec![
941 SyntaxKind::VarKw,
942 SyntaxKind::Ident,
943 SyntaxKind::Eq,
944 SyntaxKind::Ident,
945 SyntaxKind::Percent,
946 SyntaxKind::Ident,
947 ]
948 );
949 assert_eq!(
950 kinds("var a = %HealthBar\n"),
951 vec![
952 SyntaxKind::VarKw,
953 SyntaxKind::Ident,
954 SyntaxKind::Eq,
955 SyntaxKind::UniqueNode,
956 ]
957 );
958 assert_eq!(
959 kinds("print(%Bar, a % 2)\n"),
960 vec![
961 SyntaxKind::Ident,
962 SyntaxKind::LParen,
963 SyntaxKind::UniqueNode,
964 SyntaxKind::Comma,
965 SyntaxKind::Ident,
966 SyntaxKind::Percent,
967 SyntaxKind::Int,
968 SyntaxKind::RParen,
969 ]
970 );
971 }
972
973 #[test]
974 fn lexes_node_paths_as_single_tokens() {
975 assert_eq!(kinds("$Sprite2D\n"), vec![SyntaxKind::GetNode]);
976 assert_eq!(kinds("$../Sibling/%Unique\n"), vec![SyntaxKind::GetNode]);
977 assert_eq!(kinds("$\"quoted/path\"\n"), vec![SyntaxKind::GetNode]);
978 assert_eq!(
980 kinds("$Sprite2D.position\n"),
981 vec![SyntaxKind::GetNode, SyntaxKind::Dot, SyntaxKind::Ident]
982 );
983 }
984
985 #[test]
986 fn lexes_string_name_and_node_path_literals() {
987 assert_eq!(kinds("emit(&\"died\")\n")[2], SyntaxKind::StringName);
988 assert_eq!(kinds("var p = ^\"a/b\"\n")[3], SyntaxKind::NodePath);
989 assert_eq!(kinds("var x = a & b\n")[4], SyntaxKind::Amp);
991 }
992
993 #[test]
994 fn lexes_number_forms() {
995 assert_eq!(kinds("1_000_000"), vec![SyntaxKind::Int]);
996 assert_eq!(kinds("0xfb8c0b"), vec![SyntaxKind::Int]);
997 assert_eq!(kinds("0b1010_1010"), vec![SyntaxKind::Int]);
998 assert_eq!(kinds("0.234"), vec![SyntaxKind::Float]);
999 assert_eq!(kinds("1e-5"), vec![SyntaxKind::Float]);
1000 assert_eq!(kinds("1.5e10"), vec![SyntaxKind::Float]);
1001 assert_eq!(
1003 kinds("1..2"),
1004 vec![SyntaxKind::Int, SyntaxKind::DotDot, SyntaxKind::Int]
1005 );
1006 assert_eq!(
1008 kinds("1.max(2)"),
1009 vec![
1010 SyntaxKind::Int,
1011 SyntaxKind::Dot,
1012 SyntaxKind::Ident,
1013 SyntaxKind::LParen,
1014 SyntaxKind::Int,
1015 SyntaxKind::RParen
1016 ]
1017 );
1018 }
1019
1020 #[test]
1021 fn lexes_string_forms() {
1022 assert_eq!(kinds("\"double\""), vec![SyntaxKind::Str]);
1023 assert_eq!(kinds("'single'"), vec![SyntaxKind::Str]);
1024 assert_eq!(kinds("\"\"\"triple\nspanning\"\"\""), vec![SyntaxKind::Str]);
1025 assert_eq!(kinds("r\"raw\\n\""), vec![SyntaxKind::Str]);
1026 assert_eq!(kinds("\"esc\\\"aped\""), vec![SyntaxKind::Str]);
1027 }
1028
1029 #[test]
1030 fn separates_doc_comments_from_plain_comments() {
1031 let lexed = tokenize("## doc\n# plain\n");
1032 let comments: Vec<_> = lexed
1033 .tokens
1034 .iter()
1035 .filter(|t| t.kind.is_comment())
1036 .map(|t| t.kind)
1037 .collect();
1038 assert_eq!(comments, vec![SyntaxKind::DocComment, SyntaxKind::Comment]);
1039 }
1040
1041 #[test]
1042 fn line_continuation_joins_lines() {
1043 let kinds = kinds("var a = 1 + \\\n\t2\n");
1044 assert!(!kinds.contains(&SyntaxKind::Indent));
1045 assert_eq!(kinds.last(), Some(&SyntaxKind::Int));
1046 }
1047
1048 #[test]
1049 fn reports_inconsistent_dedent() {
1050 let lexed = tokenize("func f():\n\t\tpass\n\treturn\n");
1051 assert!(
1052 lexed
1053 .errors
1054 .iter()
1055 .any(|e| e.message().contains("unindent")),
1056 "expected an unindent diagnostic, got {:?}",
1057 lexed.errors
1058 );
1059 }
1060
1061 #[test]
1062 fn reports_unterminated_string() {
1063 let lexed = tokenize("var s = \"oops\n");
1064 assert!(
1065 lexed
1066 .errors
1067 .iter()
1068 .any(|e| e.message().contains("unterminated"))
1069 );
1070 }
1071
1072 #[test]
1073 fn closes_open_blocks_at_eof() {
1074 let lexed = tokenize("func f():\n\tif a:\n\t\tpass");
1075 let dedents = lexed
1076 .tokens
1077 .iter()
1078 .filter(|t| t.kind == SyntaxKind::Dedent)
1079 .count();
1080 assert_eq!(dedents, 2);
1081 assert_eq!(lexed.tokens.last().map(|t| t.kind), Some(SyntaxKind::Eof));
1082 }
1083}