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 kcl_error::KclErrorDetails;
11use parse_display::Display;
12use serde::Deserialize;
13use serde::Serialize;
14use tower_lsp::lsp_types::SemanticTokenType;
15use winnow::stream::ContainsToken;
16use winnow::stream::Stream;
17use winnow::{self};
18
19use crate::CompilationIssue;
20use crate::ModuleId;
21use crate::SourceRange;
22use crate::errors::KclError;
23use crate::parsing::ast::types::ItemVisibility;
24use crate::parsing::ast::types::VariableKind;
25
26mod tokeniser;
27
28#[cfg(all(test, feature = "new-scanner"))]
29mod compat_tests;
30
31pub(crate) use tokeniser::RESERVED_SKETCH_BLOCK_WORDS;
32pub(crate) use tokeniser::RESERVED_WORDS;
33
34pub const NUM_SUFFIXES: [&str; 10] = ["mm", "cm", "m", "inch", "in", "ft", "yd", "deg", "rad", "?"];
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, ts_rs::TS)]
38#[repr(u32)]
39pub enum NumericSuffix {
40 None,
41 Count,
42 Length,
43 Angle,
44 Mm,
45 Cm,
46 M,
47 Inch,
48 Ft,
49 Yd,
50 Deg,
51 Rad,
52 Unknown,
53}
54
55impl NumericSuffix {
56 #[allow(dead_code)]
57 pub fn is_none(self) -> bool {
58 self == Self::None
59 }
60
61 pub fn is_some(self) -> bool {
62 self != Self::None
63 }
64
65 pub fn digestable_id(&self) -> &[u8] {
66 match self {
67 NumericSuffix::None => &[],
68 NumericSuffix::Count => b"_",
69 NumericSuffix::Unknown => b"?",
70 NumericSuffix::Length => b"Length",
71 NumericSuffix::Angle => b"Angle",
72 NumericSuffix::Mm => b"mm",
73 NumericSuffix::Cm => b"cm",
74 NumericSuffix::M => b"m",
75 NumericSuffix::Inch => b"in",
76 NumericSuffix::Ft => b"ft",
77 NumericSuffix::Yd => b"yd",
78 NumericSuffix::Deg => b"deg",
79 NumericSuffix::Rad => b"rad",
80 }
81 }
82}
83
84impl FromStr for NumericSuffix {
85 type Err = CompilationIssue;
86
87 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 match s {
89 "_" | "Count" => Ok(NumericSuffix::Count),
90 "Length" => Ok(NumericSuffix::Length),
91 "Angle" => Ok(NumericSuffix::Angle),
92 "mm" | "millimeters" => Ok(NumericSuffix::Mm),
93 "cm" | "centimeters" => Ok(NumericSuffix::Cm),
94 "m" | "meters" => Ok(NumericSuffix::M),
95 "inch" | "in" => Ok(NumericSuffix::Inch),
96 "ft" | "feet" => Ok(NumericSuffix::Ft),
97 "yd" | "yards" => Ok(NumericSuffix::Yd),
98 "deg" | "degrees" => Ok(NumericSuffix::Deg),
99 "rad" | "radians" => Ok(NumericSuffix::Rad),
100 "?" => Ok(NumericSuffix::Unknown),
101 _ => Err(CompilationIssue::err(SourceRange::default(), "invalid unit of measure")),
102 }
103 }
104}
105
106impl fmt::Display for NumericSuffix {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match self {
109 NumericSuffix::None => Ok(()),
110 NumericSuffix::Count => write!(f, "_"),
111 NumericSuffix::Unknown => write!(f, "_?"),
112 NumericSuffix::Length => write!(f, "Length"),
113 NumericSuffix::Angle => write!(f, "Angle"),
114 NumericSuffix::Mm => write!(f, "mm"),
115 NumericSuffix::Cm => write!(f, "cm"),
116 NumericSuffix::M => write!(f, "m"),
117 NumericSuffix::Inch => write!(f, "in"),
118 NumericSuffix::Ft => write!(f, "ft"),
119 NumericSuffix::Yd => write!(f, "yd"),
120 NumericSuffix::Deg => write!(f, "deg"),
121 NumericSuffix::Rad => write!(f, "rad"),
122 }
123 }
124}
125
126#[derive(Clone, Debug, PartialEq)]
127pub(crate) struct TokenStream {
128 tokens: Vec<Token>,
129}
130
131impl TokenStream {
132 fn new(tokens: Vec<Token>) -> Self {
133 Self { tokens }
134 }
135
136 pub(super) fn remove_unknown(&mut self) -> Vec<Token> {
137 let tokens = std::mem::take(&mut self.tokens);
138 let (tokens, unknown_tokens): (Vec<Token>, Vec<Token>) = tokens
139 .into_iter()
140 .partition(|token| token.token_type != TokenType::Unknown);
141 self.tokens = tokens;
142 unknown_tokens
143 }
144
145 pub fn iter(&self) -> impl Iterator<Item = &Token> {
146 self.tokens.iter()
147 }
148
149 pub fn is_empty(&self) -> bool {
150 self.tokens.is_empty()
151 }
152
153 pub fn as_slice(&self) -> TokenSlice<'_> {
154 TokenSlice::from(self)
155 }
156}
157
158impl<'a> From<&'a TokenStream> for TokenSlice<'a> {
159 fn from(stream: &'a TokenStream) -> Self {
160 TokenSlice {
161 start: 0,
162 end: stream.tokens.len(),
163 stream,
164 }
165 }
166}
167
168impl IntoIterator for TokenStream {
169 type Item = Token;
170
171 type IntoIter = std::vec::IntoIter<Token>;
172
173 fn into_iter(self) -> Self::IntoIter {
174 self.tokens.into_iter()
175 }
176}
177
178#[derive(Debug, Clone)]
179pub(crate) struct TokenSlice<'a> {
180 stream: &'a TokenStream,
181 start: usize,
183 end: usize,
185}
186
187impl<'a> std::ops::Deref for TokenSlice<'a> {
188 type Target = [Token];
189
190 fn deref(&self) -> &Self::Target {
191 &self.stream.tokens[self.start..self.end]
192 }
193}
194
195impl<'a> TokenSlice<'a> {
196 pub fn token(&self, i: usize) -> &Token {
197 &self.stream.tokens[i + self.start]
198 }
199
200 pub fn iter(&self) -> impl Iterator<Item = &Token> {
201 (**self).iter()
202 }
203
204 pub fn without_ends(&self) -> Self {
205 Self {
206 start: self.start + 1,
207 end: self.end - 1,
208 stream: self.stream,
209 }
210 }
211
212 pub fn as_source_range(&self) -> SourceRange {
213 let stream_len = self.stream.tokens.len();
214 let first_token = if stream_len == self.start {
215 &self.stream.tokens[self.start - 1]
216 } else {
217 self.token(0)
218 };
219 let last_token = if stream_len == self.end {
220 &self.stream.tokens[stream_len - 1]
221 } else {
222 self.token(self.end - self.start)
223 };
224 SourceRange::new(first_token.start, last_token.end, last_token.module_id)
225 }
226}
227
228impl<'a> IntoIterator for TokenSlice<'a> {
229 type Item = &'a Token;
230
231 type IntoIter = std::slice::Iter<'a, Token>;
232
233 fn into_iter(self) -> Self::IntoIter {
234 self.stream.tokens[self.start..self.end].iter()
235 }
236}
237
238impl<'a> Stream for TokenSlice<'a> {
239 type Token = Token;
240 type Slice = Self;
241 type IterOffsets = Enumerate<std::vec::IntoIter<Token>>;
242 type Checkpoint = Checkpoint;
243
244 fn iter_offsets(&self) -> Self::IterOffsets {
245 #[allow(clippy::unnecessary_to_owned)]
246 self.to_vec().into_iter().enumerate()
247 }
248
249 fn eof_offset(&self) -> usize {
250 self.len()
251 }
252
253 fn next_token(&mut self) -> Option<Self::Token> {
254 let token = self.first()?.clone();
255 self.start += 1;
256 Some(token)
257 }
258
259 fn peek_token(&self) -> Option<Self::Token> {
261 Some(self.first()?.clone())
262 }
263
264 fn offset_for<P>(&self, predicate: P) -> Option<usize>
265 where
266 P: Fn(Self::Token) -> bool,
267 {
268 self.iter().position(|b| predicate(b.clone()))
269 }
270
271 fn offset_at(&self, tokens: usize) -> Result<usize, winnow::error::Needed> {
272 if let Some(needed) = tokens.checked_sub(self.len()).and_then(NonZeroUsize::new) {
273 Err(winnow::error::Needed::Size(needed))
274 } else {
275 Ok(tokens)
276 }
277 }
278
279 fn next_slice(&mut self, offset: usize) -> Self::Slice {
280 assert!(self.start + offset <= self.end);
281
282 let next = TokenSlice {
283 stream: self.stream,
284 start: self.start,
285 end: self.start + offset,
286 };
287 self.start += offset;
288 next
289 }
290
291 fn peek_slice(&self, offset: usize) -> Self::Slice {
293 assert!(self.start + offset <= self.end);
294
295 TokenSlice {
296 stream: self.stream,
297 start: self.start,
298 end: self.start + offset,
299 }
300 }
301
302 fn checkpoint(&self) -> Self::Checkpoint {
303 Checkpoint(self.start, self.end)
304 }
305
306 fn reset(&mut self, checkpoint: &Self::Checkpoint) {
307 self.start = checkpoint.0;
308 self.end = checkpoint.1;
309 }
310
311 fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
312 write!(f, "{self:?}")
313 }
314}
315
316impl<'a> winnow::stream::Offset for TokenSlice<'a> {
317 fn offset_from(&self, start: &Self) -> usize {
318 self.start - start.start
319 }
320}
321
322impl<'a> winnow::stream::Offset<Checkpoint> for TokenSlice<'a> {
323 fn offset_from(&self, start: &Checkpoint) -> usize {
324 self.start - start.0
325 }
326}
327
328impl winnow::stream::Offset for Checkpoint {
329 fn offset_from(&self, start: &Self) -> usize {
330 self.0 - start.0
331 }
332}
333
334impl<'a> winnow::stream::StreamIsPartial for TokenSlice<'a> {
335 type PartialState = ();
336
337 fn complete(&mut self) -> Self::PartialState {}
338
339 fn restore_partial(&mut self, _: Self::PartialState) {}
340
341 fn is_partial_supported() -> bool {
342 false
343 }
344}
345
346impl<'a> winnow::stream::FindSlice<&str> for TokenSlice<'a> {
347 fn find_slice(&self, substr: &str) -> Option<std::ops::Range<usize>> {
348 self.iter()
349 .enumerate()
350 .find_map(|(i, b)| if b.value == substr { Some(i..self.end) } else { None })
351 }
352}
353
354#[derive(Clone, Debug)]
355pub struct Checkpoint(usize, usize);
356
357#[derive(Debug, PartialEq, Eq, Copy, Clone, Display)]
359#[display(style = "camelCase")]
360pub enum TokenType {
361 Number,
363 Word,
365 Operator,
367 String,
369 Keyword,
371 Type,
373 Brace,
375 Hash,
377 Bang,
379 Dollar,
381 Whitespace,
383 Comma,
385 Colon,
387 DoubleColon,
389 Period,
391 DoublePeriod,
393 DoublePeriodLessThan,
395 LineComment,
397 BlockComment,
399 Function,
401 Unknown,
403 QuestionMark,
405 At,
407 SemiColon,
409}
410
411impl TryFrom<TokenType> for SemanticTokenType {
413 type Error = anyhow::Error;
414 fn try_from(token_type: TokenType) -> Result<Self> {
415 Ok(match token_type {
418 TokenType::Number => Self::NUMBER,
419 TokenType::Word => Self::VARIABLE,
420 TokenType::Keyword => Self::KEYWORD,
421 TokenType::Type => Self::TYPE,
422 TokenType::Operator => Self::OPERATOR,
423 TokenType::QuestionMark => Self::OPERATOR,
424 TokenType::String => Self::STRING,
425 TokenType::Bang => Self::OPERATOR,
426 TokenType::LineComment => Self::COMMENT,
427 TokenType::BlockComment => Self::COMMENT,
428 TokenType::Function => Self::FUNCTION,
429 TokenType::Whitespace
430 | TokenType::Brace
431 | TokenType::Comma
432 | TokenType::Colon
433 | TokenType::DoubleColon
434 | TokenType::Period
435 | TokenType::DoublePeriod
436 | TokenType::DoublePeriodLessThan
437 | TokenType::Hash
438 | TokenType::Dollar
439 | TokenType::At
440 | TokenType::SemiColon
441 | TokenType::Unknown => {
442 anyhow::bail!("unsupported token type: {:?}", token_type)
443 }
444 })
445 }
446}
447
448impl TokenType {
449 pub fn is_whitespace(&self) -> bool {
450 matches!(self, Self::Whitespace)
451 }
452
453 pub fn is_comment(&self) -> bool {
454 matches!(self, Self::LineComment | Self::BlockComment)
455 }
456}
457
458#[derive(Debug, PartialEq, Eq, Clone)]
459pub struct Token {
460 pub token_type: TokenType,
461 pub start: usize,
463 pub end: usize,
465 pub(super) module_id: ModuleId,
466 pub(super) value: String,
467}
468
469impl ContainsToken<Token> for (TokenType, &str) {
470 fn contains_token(&self, token: Token) -> bool {
471 self.0 == token.token_type && self.1 == token.value
472 }
473}
474
475impl ContainsToken<Token> for TokenType {
476 fn contains_token(&self, token: Token) -> bool {
477 *self == token.token_type
478 }
479}
480
481impl Token {
482 pub fn from_range(
483 range: std::ops::Range<usize>,
484 module_id: ModuleId,
485 token_type: TokenType,
486 value: String,
487 ) -> Self {
488 Self {
489 start: range.start,
490 end: range.end,
491 module_id,
492 value,
493 token_type,
494 }
495 }
496 pub fn is_code_token(&self) -> bool {
497 !matches!(
498 self.token_type,
499 TokenType::Whitespace | TokenType::LineComment | TokenType::BlockComment
500 )
501 }
502
503 pub fn as_source_range(&self) -> SourceRange {
504 SourceRange::new(self.start, self.end, self.module_id)
505 }
506
507 pub fn as_source_ranges(&self) -> Vec<SourceRange> {
508 vec![self.as_source_range()]
509 }
510
511 pub fn visibility_keyword(&self) -> Option<ItemVisibility> {
512 if !matches!(self.token_type, TokenType::Keyword) {
513 return None;
514 }
515 match self.value.as_str() {
516 "export" => Some(ItemVisibility::Export),
517 _ => None,
518 }
519 }
520
521 pub fn numeric_value(&self) -> Option<f64> {
522 if self.token_type != TokenType::Number {
523 return None;
524 }
525 let value = &self.value;
526 let value = value
527 .split_once(|c: char| c == '_' || c.is_ascii_alphabetic())
528 .map(|(s, _)| s)
529 .unwrap_or(value);
530 value.parse().ok()
531 }
532
533 pub fn uint_value(&self) -> Option<u32> {
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 numeric_suffix(&self) -> NumericSuffix {
546 if self.token_type != TokenType::Number {
547 return NumericSuffix::None;
548 }
549
550 if self.value.ends_with('_') {
551 return NumericSuffix::Count;
552 }
553
554 for suffix in NUM_SUFFIXES {
555 if self.value.ends_with(suffix) {
556 return suffix.parse().unwrap();
557 }
558 }
559
560 NumericSuffix::None
561 }
562
563 pub fn declaration_keyword(&self) -> Option<VariableKind> {
567 if !matches!(self.token_type, TokenType::Keyword) {
568 return None;
569 }
570 Some(match self.value.as_str() {
571 "fn" => VariableKind::Fn,
572 "var" | "let" | "const" => VariableKind::Const,
573 _ => return None,
574 })
575 }
576}
577
578impl From<Token> for SourceRange {
579 fn from(token: Token) -> Self {
580 Self::new(token.start, token.end, token.module_id)
581 }
582}
583
584impl From<&Token> for SourceRange {
585 fn from(token: &Token) -> Self {
586 Self::new(token.start, token.end, token.module_id)
587 }
588}
589
590pub fn lex(s: &str, module_id: ModuleId) -> Result<TokenStream, KclError> {
591 tokeniser::lex(s, module_id).map_err(|err| {
592 let (input, offset): (Vec<char>, usize) = (err.input().chars().collect(), err.offset());
593 let module_id = err.input().state.module_id;
594
595 if offset >= input.len() {
596 return KclError::new_lexical(KclErrorDetails::new(
602 "unexpected EOF while parsing".to_owned(),
603 vec![SourceRange::new(offset, offset, module_id)],
604 ));
605 }
606
607 let bad_token = &input[offset];
610 KclError::new_lexical(KclErrorDetails::new(
613 format!("found unknown token '{bad_token}'"),
614 vec![SourceRange::new(offset, offset + 1, module_id)],
615 ))
616 })
617}