1#![allow(clippy::needless_lifetimes)]
3
4use std::fmt;
5use std::iter::Enumerate;
6use std::num::NonZeroUsize;
7use std::str::FromStr;
8
9use anyhow::Result;
10use parse_display::Display;
11use serde::Deserialize;
12use serde::Serialize;
13use tower_lsp::lsp_types::SemanticTokenType;
14use winnow::stream::ContainsToken;
15use winnow::stream::Stream;
16use winnow::{self};
17
18use crate::CompilationIssue;
19use crate::ModuleId;
20use crate::SourceRange;
21use crate::errors::KclError;
22use crate::parsing::ast::types::ItemVisibility;
23use crate::parsing::ast::types::VariableKind;
24
25#[doc(hidden)]
26pub mod adapter;
27
28pub use kcl_syntax::keywords::KEYWORDS as RESERVED_WORDS;
29
30pub const NUM_SUFFIXES: [&str; 10] = ["mm", "cm", "m", "inch", "in", "ft", "yd", "deg", "rad", "?"];
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, ts_rs::TS)]
34#[repr(u32)]
35pub enum NumericSuffix {
36 None,
37 Count,
38 Length,
39 Angle,
40 Mm,
41 Cm,
42 M,
43 Inch,
44 Ft,
45 Yd,
46 Deg,
47 Rad,
48 Unknown,
49}
50
51impl NumericSuffix {
52 #[allow(dead_code)]
53 pub fn is_none(self) -> bool {
54 self == Self::None
55 }
56
57 pub fn is_some(self) -> bool {
58 self != Self::None
59 }
60
61 pub fn digestable_id(&self) -> &[u8] {
62 match self {
63 NumericSuffix::None => &[],
64 NumericSuffix::Count => b"_",
65 NumericSuffix::Unknown => b"?",
66 NumericSuffix::Length => b"Length",
67 NumericSuffix::Angle => b"Angle",
68 NumericSuffix::Mm => b"mm",
69 NumericSuffix::Cm => b"cm",
70 NumericSuffix::M => b"m",
71 NumericSuffix::Inch => b"in",
72 NumericSuffix::Ft => b"ft",
73 NumericSuffix::Yd => b"yd",
74 NumericSuffix::Deg => b"deg",
75 NumericSuffix::Rad => b"rad",
76 }
77 }
78}
79
80impl FromStr for NumericSuffix {
81 type Err = CompilationIssue;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 match s {
85 "_" | "Count" => Ok(NumericSuffix::Count),
86 "Length" => Ok(NumericSuffix::Length),
87 "Angle" => Ok(NumericSuffix::Angle),
88 "mm" | "millimeters" => Ok(NumericSuffix::Mm),
89 "cm" | "centimeters" => Ok(NumericSuffix::Cm),
90 "m" | "meters" => Ok(NumericSuffix::M),
91 "inch" | "in" => Ok(NumericSuffix::Inch),
92 "ft" | "feet" => Ok(NumericSuffix::Ft),
93 "yd" | "yards" => Ok(NumericSuffix::Yd),
94 "deg" | "degrees" => Ok(NumericSuffix::Deg),
95 "rad" | "radians" => Ok(NumericSuffix::Rad),
96 "?" => Ok(NumericSuffix::Unknown),
97 _ => Err(CompilationIssue::err(SourceRange::default(), "invalid unit of measure")),
98 }
99 }
100}
101
102impl fmt::Display for NumericSuffix {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 match self {
105 NumericSuffix::None => Ok(()),
106 NumericSuffix::Count => write!(f, "_"),
107 NumericSuffix::Unknown => write!(f, "_?"),
108 NumericSuffix::Length => write!(f, "Length"),
109 NumericSuffix::Angle => write!(f, "Angle"),
110 NumericSuffix::Mm => write!(f, "mm"),
111 NumericSuffix::Cm => write!(f, "cm"),
112 NumericSuffix::M => write!(f, "m"),
113 NumericSuffix::Inch => write!(f, "in"),
114 NumericSuffix::Ft => write!(f, "ft"),
115 NumericSuffix::Yd => write!(f, "yd"),
116 NumericSuffix::Deg => write!(f, "deg"),
117 NumericSuffix::Rad => write!(f, "rad"),
118 }
119 }
120}
121
122#[derive(Clone, Debug, PartialEq)]
123pub struct TokenStream {
124 tokens: Vec<Token>,
125}
126
127impl TokenStream {
128 fn new(tokens: Vec<Token>) -> Self {
129 Self { tokens }
130 }
131
132 pub(super) fn allow_use_identifiers(&mut self) -> Vec<SourceRange> {
135 self.tokens
136 .iter_mut()
137 .filter_map(|token| {
138 if token.token_type == TokenType::Keyword && token.value == "use" {
139 token.token_type = TokenType::Word;
140 Some(token.as_source_range())
141 } else {
142 None
143 }
144 })
145 .collect()
146 }
147
148 pub(super) fn remove_unknown(&mut self) -> Vec<Token> {
149 let tokens = std::mem::take(&mut self.tokens);
150 let (tokens, unknown_tokens): (Vec<Token>, Vec<Token>) = tokens
151 .into_iter()
152 .partition(|token| token.token_type != TokenType::Unknown);
153 self.tokens = tokens;
154 unknown_tokens
155 }
156
157 pub fn iter(&self) -> impl Iterator<Item = &Token> {
158 self.tokens.iter()
159 }
160
161 pub fn is_empty(&self) -> bool {
162 self.tokens.is_empty()
163 }
164
165 pub fn as_slice(&self) -> TokenSlice<'_> {
166 TokenSlice::from(self)
167 }
168}
169
170impl<'a> From<&'a TokenStream> for TokenSlice<'a> {
171 fn from(stream: &'a TokenStream) -> Self {
172 TokenSlice {
173 start: 0,
174 end: stream.tokens.len(),
175 stream,
176 }
177 }
178}
179
180impl IntoIterator for TokenStream {
181 type Item = Token;
182
183 type IntoIter = std::vec::IntoIter<Token>;
184
185 fn into_iter(self) -> Self::IntoIter {
186 self.tokens.into_iter()
187 }
188}
189
190#[derive(Debug, Clone)]
191pub struct TokenSlice<'a> {
192 stream: &'a TokenStream,
193 start: usize,
195 end: usize,
197}
198
199impl<'a> std::ops::Deref for TokenSlice<'a> {
200 type Target = [Token];
201
202 fn deref(&self) -> &Self::Target {
203 &self.stream.tokens[self.start..self.end]
204 }
205}
206
207impl<'a> TokenSlice<'a> {
208 pub fn token(&self, i: usize) -> &Token {
209 &self.stream.tokens[i + self.start]
210 }
211
212 pub fn iter(&self) -> impl Iterator<Item = &Token> {
213 (**self).iter()
214 }
215
216 pub fn without_ends(&self) -> Self {
217 Self {
218 start: self.start + 1,
219 end: self.end - 1,
220 stream: self.stream,
221 }
222 }
223
224 pub fn as_source_range(&self) -> SourceRange {
225 let stream_len = self.stream.tokens.len();
226 let first_token = if stream_len == self.start {
227 &self.stream.tokens[self.start - 1]
228 } else {
229 self.token(0)
230 };
231 let last_token = if stream_len == self.end {
232 &self.stream.tokens[stream_len - 1]
233 } else {
234 self.token(self.end - self.start)
235 };
236 SourceRange::new(first_token.start, last_token.end, last_token.module_id)
237 }
238}
239
240impl<'a> IntoIterator for TokenSlice<'a> {
241 type Item = &'a Token;
242
243 type IntoIter = std::slice::Iter<'a, Token>;
244
245 fn into_iter(self) -> Self::IntoIter {
246 self.stream.tokens[self.start..self.end].iter()
247 }
248}
249
250impl<'a> Stream for TokenSlice<'a> {
251 type Token = Token;
252 type Slice = Self;
253 type IterOffsets = Enumerate<std::vec::IntoIter<Token>>;
254 type Checkpoint = Checkpoint;
255
256 fn iter_offsets(&self) -> Self::IterOffsets {
257 #[allow(clippy::unnecessary_to_owned)]
258 self.to_vec().into_iter().enumerate()
259 }
260
261 fn eof_offset(&self) -> usize {
262 self.len()
263 }
264
265 fn next_token(&mut self) -> Option<Self::Token> {
266 let token = self.first()?.clone();
267 self.start += 1;
268 Some(token)
269 }
270
271 fn peek_token(&self) -> Option<Self::Token> {
273 Some(self.first()?.clone())
274 }
275
276 fn offset_for<P>(&self, predicate: P) -> Option<usize>
277 where
278 P: Fn(Self::Token) -> bool,
279 {
280 self.iter().position(|b| predicate(b.clone()))
281 }
282
283 fn offset_at(&self, tokens: usize) -> Result<usize, winnow::error::Needed> {
284 if let Some(needed) = tokens.checked_sub(self.len()).and_then(NonZeroUsize::new) {
285 Err(winnow::error::Needed::Size(needed))
286 } else {
287 Ok(tokens)
288 }
289 }
290
291 fn next_slice(&mut self, offset: usize) -> Self::Slice {
292 assert!(self.start + offset <= self.end);
293
294 let next = TokenSlice {
295 stream: self.stream,
296 start: self.start,
297 end: self.start + offset,
298 };
299 self.start += offset;
300 next
301 }
302
303 fn peek_slice(&self, offset: usize) -> Self::Slice {
305 assert!(self.start + offset <= self.end);
306
307 TokenSlice {
308 stream: self.stream,
309 start: self.start,
310 end: self.start + offset,
311 }
312 }
313
314 fn checkpoint(&self) -> Self::Checkpoint {
315 Checkpoint(self.start, self.end)
316 }
317
318 fn reset(&mut self, checkpoint: &Self::Checkpoint) {
319 self.start = checkpoint.0;
320 self.end = checkpoint.1;
321 }
322
323 fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
324 write!(f, "{self:?}")
325 }
326}
327
328impl<'a> winnow::stream::Offset for TokenSlice<'a> {
329 fn offset_from(&self, start: &Self) -> usize {
330 self.start - start.start
331 }
332}
333
334impl<'a> winnow::stream::Offset<Checkpoint> for TokenSlice<'a> {
335 fn offset_from(&self, start: &Checkpoint) -> usize {
336 self.start - start.0
337 }
338}
339
340impl winnow::stream::Offset for Checkpoint {
341 fn offset_from(&self, start: &Self) -> usize {
342 self.0 - start.0
343 }
344}
345
346impl<'a> winnow::stream::StreamIsPartial for TokenSlice<'a> {
347 type PartialState = ();
348
349 fn complete(&mut self) -> Self::PartialState {}
350
351 fn restore_partial(&mut self, _: Self::PartialState) {}
352
353 fn is_partial_supported() -> bool {
354 false
355 }
356}
357
358impl<'a> winnow::stream::FindSlice<&str> for TokenSlice<'a> {
359 fn find_slice(&self, substr: &str) -> Option<std::ops::Range<usize>> {
360 self.iter()
361 .enumerate()
362 .find_map(|(i, b)| if b.value == substr { Some(i..self.end) } else { None })
363 }
364}
365
366#[derive(Clone, Debug)]
367pub struct Checkpoint(usize, usize);
368
369#[derive(Debug, PartialEq, Eq, Copy, Clone, Display)]
371#[display(style = "camelCase")]
372pub enum TokenType {
373 Number,
375 Word,
377 Operator,
379 String,
381 Keyword,
383 Type,
385 Brace,
387 Hash,
389 Bang,
391 Dollar,
393 Whitespace,
395 Comma,
397 Colon,
399 DoubleColon,
401 Period,
403 DoublePeriod,
405 DoublePeriodLessThan,
407 LineComment,
409 BlockComment,
411 Function,
413 Unknown,
415 QuestionMark,
417 At,
419 SemiColon,
421}
422
423impl TryFrom<TokenType> for SemanticTokenType {
425 type Error = anyhow::Error;
426 fn try_from(token_type: TokenType) -> Result<Self> {
427 Ok(match token_type {
430 TokenType::Number => Self::NUMBER,
431 TokenType::Word => Self::VARIABLE,
432 TokenType::Keyword => Self::KEYWORD,
433 TokenType::Type => Self::TYPE,
434 TokenType::Operator => Self::OPERATOR,
435 TokenType::QuestionMark => Self::OPERATOR,
436 TokenType::String => Self::STRING,
437 TokenType::Bang => Self::OPERATOR,
438 TokenType::LineComment => Self::COMMENT,
439 TokenType::BlockComment => Self::COMMENT,
440 TokenType::Function => Self::FUNCTION,
441 TokenType::Whitespace
442 | TokenType::Brace
443 | TokenType::Comma
444 | TokenType::Colon
445 | TokenType::DoubleColon
446 | TokenType::Period
447 | TokenType::DoublePeriod
448 | TokenType::DoublePeriodLessThan
449 | TokenType::Hash
450 | TokenType::Dollar
451 | TokenType::At
452 | TokenType::SemiColon
453 | TokenType::Unknown => {
454 anyhow::bail!("unsupported token type: {:?}", token_type)
455 }
456 })
457 }
458}
459
460impl TokenType {
461 pub fn is_whitespace(&self) -> bool {
462 matches!(self, Self::Whitespace)
463 }
464
465 pub fn is_comment(&self) -> bool {
466 matches!(self, Self::LineComment | Self::BlockComment)
467 }
468}
469
470#[derive(Debug, PartialEq, Eq, Clone)]
471pub struct Token {
472 pub token_type: TokenType,
473 pub start: usize,
475 pub end: usize,
477 pub(super) module_id: ModuleId,
478 pub(super) value: String,
479}
480
481impl ContainsToken<Token> for (TokenType, &str) {
482 fn contains_token(&self, token: Token) -> bool {
483 self.0 == token.token_type && self.1 == token.value
484 }
485}
486
487impl ContainsToken<Token> for TokenType {
488 fn contains_token(&self, token: Token) -> bool {
489 *self == token.token_type
490 }
491}
492
493impl Token {
494 pub fn from_range(
495 range: std::ops::Range<usize>,
496 module_id: ModuleId,
497 token_type: TokenType,
498 value: String,
499 ) -> Self {
500 Self {
501 start: range.start,
502 end: range.end,
503 module_id,
504 value,
505 token_type,
506 }
507 }
508 pub fn is_code_token(&self) -> bool {
509 !matches!(
510 self.token_type,
511 TokenType::Whitespace | TokenType::LineComment | TokenType::BlockComment
512 )
513 }
514
515 pub fn as_source_range(&self) -> SourceRange {
516 SourceRange::new(self.start, self.end, self.module_id)
517 }
518
519 pub fn as_source_ranges(&self) -> Vec<SourceRange> {
520 vec![self.as_source_range()]
521 }
522
523 pub fn visibility_keyword(&self) -> Option<ItemVisibility> {
524 if !matches!(self.token_type, TokenType::Keyword) {
525 return None;
526 }
527 match self.value.as_str() {
528 "export" => Some(ItemVisibility::Export),
529 _ => None,
530 }
531 }
532
533 pub fn numeric_value(&self) -> Option<f64> {
534 if self.token_type != TokenType::Number {
535 return None;
536 }
537 let value = &self.value;
538 let value = value
539 .split_once(|c: char| c == '_' || c.is_ascii_alphabetic())
540 .map(|(s, _)| s)
541 .unwrap_or(value);
542 value.parse().ok()
543 }
544
545 pub fn uint_value(&self) -> Option<u32> {
546 if self.token_type != TokenType::Number {
547 return None;
548 }
549 let value = &self.value;
550 let value = value
551 .split_once(|c: char| c == '_' || c.is_ascii_alphabetic())
552 .map(|(s, _)| s)
553 .unwrap_or(value);
554 value.parse().ok()
555 }
556
557 pub fn numeric_suffix(&self) -> NumericSuffix {
558 if self.token_type != TokenType::Number {
559 return NumericSuffix::None;
560 }
561
562 if self.value.ends_with('_') {
563 return NumericSuffix::Count;
564 }
565
566 for suffix in NUM_SUFFIXES {
567 if self.value.ends_with(suffix) {
568 return suffix.parse().unwrap();
569 }
570 }
571
572 NumericSuffix::None
573 }
574
575 pub fn declaration_keyword(&self) -> Option<VariableKind> {
579 if !matches!(self.token_type, TokenType::Keyword) {
580 return None;
581 }
582 Some(match self.value.as_str() {
583 "fn" => VariableKind::Fn,
584 "var" | "let" | "const" => VariableKind::Const,
585 _ => return None,
586 })
587 }
588}
589
590impl From<Token> for SourceRange {
591 fn from(token: Token) -> Self {
592 Self::new(token.start, token.end, token.module_id)
593 }
594}
595
596impl From<&Token> for SourceRange {
597 fn from(token: &Token) -> Self {
598 Self::new(token.start, token.end, token.module_id)
599 }
600}
601
602pub fn lex(s: &str, module_id: ModuleId) -> Result<TokenStream, KclError> {
604 let result = adapter::lex_with_diagnostics(s, module_id);
605 match result.to_lexical_error() {
606 Some(err) => Err(err),
607 None => Ok(result.tokens),
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use super::Token;
614 use super::TokenSlice;
615 use super::TokenType;
616 use super::lex;
617 use crate::ModuleId;
618
619 #[test]
620 fn lex_uses_syntax_lexer() {
621 let module_id = ModuleId::default();
622 let tokens = lex("x = 1", module_id).expect("valid input should produce tokens");
623 assert!(!tokens.is_empty());
624
625 let err = lex("\"abc", module_id).expect_err("unterminated string is a lexical error");
626 assert_eq!(err.error_type(), "lexical");
627 assert_eq!(err.message(), "unterminated string literal");
628 }
629
630 #[test]
631 fn test_program2() {
632 let program = r#"const part001 = startSketchOn(XY)
633 |> startProfileAt([0.0000000000, 5.0000000000], %)
634 |> line([0.4900857016, -0.0240763666], %)
635
636const part002 = "part002"
637const things = [part001, 0.0]
638let blah = 1
639const foo = false
640let baz = {a: 1, part001: "thing"}
641
642fn ghi = (part001) => {
643 return part001
644}
645
646show(part001)"#;
647 let module_id = ModuleId::from_usize(1);
648 let actual = lex(program, module_id).unwrap();
649 insta::assert_debug_snapshot!(actual.tokens);
650 }
651
652 #[track_caller]
653 fn assert_tokens(expected: &[(TokenType, usize, usize)], actual: TokenSlice) {
654 let mut e = 0;
655 let mut issues = vec![];
656 for a in actual {
657 if expected[e].0 != a.token_type {
658 if a.token_type == TokenType::Whitespace {
659 continue;
660 }
661 issues.push(format!(
662 "Type mismatch: expected `{}`, found `{}` (`{a:?}`), at index {e}",
663 expected[e].0, a.token_type
664 ));
665 }
666
667 if expected[e].1 != a.start || expected[e].2 != a.end {
668 issues.push(format!(
669 "Source range mismatch: expected {}-{}, found {}-{} (`{a:?}`), at index {e}",
670 expected[e].1, expected[e].2, a.start, a.end
671 ));
672 }
673
674 e += 1;
675 }
676 if e < expected.len() {
677 issues.push(format!("Expected `{}` tokens, found `{e}`", expected.len()));
678 }
679 assert!(issues.is_empty(), "{}", issues.join("\n"));
680 }
681
682 #[test]
683 fn test_program0() {
684 let program = "const a=5";
685 let module_id = ModuleId::from_usize(1);
686 let actual = lex(program, module_id).unwrap();
687
688 use TokenType::*;
689 assert_tokens(
690 &[(Keyword, 0, 5), (Word, 6, 7), (Operator, 7, 8), (Number, 8, 9)],
691 actual.as_slice(),
692 );
693 }
694
695 #[test]
696 fn test_program1() {
697 let program = "54 + 22500 + 6";
698 let module_id = ModuleId::from_usize(1);
699 let actual = lex(program, module_id).unwrap();
700
701 use TokenType::*;
702 assert_tokens(
703 &[
704 (Number, 0, 2),
705 (Operator, 3, 4),
706 (Number, 5, 10),
707 (Operator, 11, 12),
708 (Number, 13, 14),
709 ],
710 actual.as_slice(),
711 );
712 }
713
714 #[test]
715 fn test_program3() {
716 let program = r#"
717// this is a comment
718const yo = { a: { b: { c: '123' } } }
719
720const key = 'c'
721const things = "things"
722
723// this is also a comment"#;
724 let module_id = ModuleId::from_usize(1);
725 let actual = lex(program, module_id).unwrap();
726
727 use TokenType::*;
728 assert_tokens(
729 &[
730 (Whitespace, 0, 1),
731 (LineComment, 1, 21),
732 (Whitespace, 21, 22),
733 (Keyword, 22, 27),
734 (Whitespace, 27, 28),
735 (Word, 28, 30),
736 (Whitespace, 30, 31),
737 (Operator, 31, 32),
738 (Whitespace, 32, 33),
739 (Brace, 33, 34),
740 (Whitespace, 34, 35),
741 (Word, 35, 36),
742 (Colon, 36, 37),
743 (Whitespace, 37, 38),
744 (Brace, 38, 39),
745 (Whitespace, 39, 40),
746 (Word, 40, 41),
747 (Colon, 41, 42),
748 (Whitespace, 42, 43),
749 (Brace, 43, 44),
750 (Whitespace, 44, 45),
751 (Word, 45, 46),
752 (Colon, 46, 47),
753 (Whitespace, 47, 48),
754 (String, 48, 53),
755 (Whitespace, 53, 54),
756 (Brace, 54, 55),
757 (Whitespace, 55, 56),
758 (Brace, 56, 57),
759 (Whitespace, 57, 58),
760 (Brace, 58, 59),
761 (Whitespace, 59, 61),
762 (Keyword, 61, 66),
763 (Whitespace, 66, 67),
764 (Word, 67, 70),
765 (Whitespace, 70, 71),
766 (Operator, 71, 72),
767 (Whitespace, 72, 73),
768 (String, 73, 76),
769 (Whitespace, 76, 77),
770 (Keyword, 77, 82),
771 (Whitespace, 82, 83),
772 (Word, 83, 89),
773 (Whitespace, 89, 90),
774 (Operator, 90, 91),
775 (Whitespace, 91, 92),
776 (String, 92, 100),
777 (Whitespace, 100, 102),
778 (LineComment, 102, 127),
779 ],
780 actual.as_slice(),
781 );
782 }
783
784 #[test]
785 fn test_program4() {
786 let program = "const myArray = [0..10]";
787 let module_id = ModuleId::from_usize(1);
788 let actual = lex(program, module_id).unwrap();
789
790 use TokenType::*;
791 assert_tokens(
792 &[
793 (Keyword, 0, 5),
794 (Word, 6, 13),
795 (Operator, 14, 15),
796 (Brace, 16, 17),
797 (Number, 17, 18),
798 (DoublePeriod, 18, 20),
799 (Number, 20, 22),
800 (Brace, 22, 23),
801 ],
802 actual.as_slice(),
803 );
804 }
805
806 #[test]
807 fn test_lexer_negative_word() {
808 let module_id = ModuleId::from_usize(1);
809 let actual = lex("-legX", module_id).unwrap();
810
811 use TokenType::*;
812 assert_tokens(&[(Operator, 0, 1), (Word, 1, 5)], actual.as_slice());
813 }
814
815 #[test]
816 fn not_eq() {
817 let module_id = ModuleId::from_usize(1);
818 let actual = lex("!=", module_id).unwrap();
819 let expected = vec![Token {
820 token_type: TokenType::Operator,
821 value: "!=".to_owned(),
822 start: 0,
823 end: 2,
824 module_id,
825 }];
826 assert_eq!(actual.tokens, expected);
827 }
828
829 #[test]
830 fn import_keyword() {
831 let module_id = ModuleId::from_usize(1);
832 let actual = lex("import foo", module_id).unwrap();
833 let expected = Token {
834 token_type: TokenType::Keyword,
835 value: "import".to_owned(),
836 start: 0,
837 end: 6,
838 module_id,
839 };
840 assert_eq!(actual.tokens[0], expected);
841 }
842
843 #[test]
844 fn import_function() {
845 let module_id = ModuleId::from_usize(1);
846 let actual = lex("import(3)", module_id).unwrap();
847 let expected = Token {
848 token_type: TokenType::Word,
849 value: "import".to_owned(),
850 start: 0,
851 end: 6,
852 module_id,
853 };
854 assert_eq!(actual.tokens[0], expected);
855 }
856
857 #[test]
858 fn use_keyword_and_function_name() {
859 let module_id = ModuleId::default();
860 for (source, expected_type) in [
861 ("use", TokenType::Keyword),
862 ("use = 1", TokenType::Keyword),
863 ("use(3)", TokenType::Word),
864 ("use (3)", TokenType::Keyword),
865 ("useful", TokenType::Word),
866 ] {
867 let tokens = lex(source, module_id).unwrap();
868 assert_eq!(tokens.tokens[0].token_type, expected_type, "{source}");
869 }
870 }
871
872 #[test]
873 fn test_is_code_token() {
874 let module_id = ModuleId::default();
875 let actual = lex("foo (4/* comment */ +,2,\"sdfsdf\") // comment", module_id).unwrap();
876 let non_code = [1, 4, 5, 12, 13];
877 for i in 0..14 {
878 if non_code.contains(&i) {
879 assert!(
880 !actual.tokens[i].is_code_token(),
881 "failed test {i}: {:?}",
882 actual.tokens[i],
883 );
884 } else {
885 assert!(
886 actual.tokens[i].is_code_token(),
887 "failed test {i}: {:?}",
888 actual.tokens[i],
889 );
890 }
891 }
892 }
893 #[test]
894 fn test_boolean_literal() {
895 let module_id = ModuleId::default();
896 let actual = lex("true", module_id).unwrap();
897 let expected = Token {
898 token_type: TokenType::Keyword,
899 value: "true".to_owned(),
900 start: 0,
901 end: 4,
902 module_id,
903 };
904 assert_eq!(actual.tokens[0], expected);
905 }
906
907 #[test]
908 fn test_word_starting_with_keyword() {
909 let module_id = ModuleId::default();
910 let actual = lex("truee", module_id).unwrap();
911 let expected = Token {
912 token_type: TokenType::Word,
913 value: "truee".to_owned(),
914 start: 0,
915 end: 5,
916 module_id,
917 };
918 assert_eq!(actual.tokens[0], expected);
919 }
920
921 #[test]
922 fn non_english_identifiers() {
923 let module_id = ModuleId::default();
924 let actual = lex("亞當", module_id).unwrap();
925 let expected = Token {
926 token_type: TokenType::Word,
927 value: "亞當".to_owned(),
928 start: 0,
929 end: 6,
930 module_id,
931 };
932 assert_eq!(actual.tokens[0], expected);
933 }
934}