1use super::{number::consume_number, Error, ExpectedToken, Result};
2use crate::front::wgsl::error::NumberError;
3use crate::front::wgsl::parse::directive::enable_extension::{
4 EnableExtensions, ImplementedEnableExtension,
5};
6use crate::front::wgsl::parse::Number;
7use crate::Span;
8
9use alloc::{boxed::Box, vec::Vec};
10
11pub type TokenSpan<'a> = (Token<'a>, Span);
12
13#[derive(Copy, Clone, Debug, PartialEq)]
14pub enum Token<'a> {
15 Separator(char),
18
19 Paren(char),
28
29 Attribute,
31
32 Number(core::result::Result<Number, NumberError>),
35
36 Word(&'a str),
38
39 String(&'a str),
41
42 Operation(char),
45
46 LogicalOperation(char),
52
53 ShiftOperation(char),
55
56 AssignmentOperation(char),
61
62 IncrementOperation,
64
65 DecrementOperation,
67
68 Arrow,
70
71 TemplateArgsStart,
76
77 TemplateArgsEnd,
82
83 Unknown(char),
85
86 Trivia,
88
89 DocComment(&'a str),
91
92 ModuleDocComment(&'a str),
94
95 UnterminatedBlockComment(&'a str),
99
100 End,
102}
103
104fn consume_any(input: &str, what: impl Fn(char) -> bool) -> (&str, &str) {
105 let pos = input.find(|c| !what(c)).unwrap_or(input.len());
106 input.split_at(pos)
107}
108
109fn find_string_literal_end(input: &str) -> Option<usize> {
110 let mut escaped = false;
111 for (index, c) in input.char_indices() {
112 if escaped {
113 escaped = false;
114 } else if c == '\\' {
115 escaped = true;
116 } else if c == '"' {
117 return Some(index);
118 }
119 }
120 None
121}
122
123struct UnclosedCandidate {
124 index: usize,
125 depth: usize,
126}
127
128fn discover_template_lists<'a>(
153 tokens: &mut Vec<(TokenSpan<'a>, &'a str)>,
154 source: &'a str,
155 mut input: &'a str,
156 ignore_doc_comments: bool,
157) {
158 assert!(tokens.is_empty());
159
160 let mut looking_for_template_start = false;
161 let mut pending: Vec<UnclosedCandidate> = Vec::new();
162
163 let mut depth = 0;
166
167 fn pop_until(pending: &mut Vec<UnclosedCandidate>, depth: usize) {
168 while pending
169 .last()
170 .map(|candidate| candidate.depth >= depth)
171 .unwrap_or(false)
172 {
173 pending.pop();
174 }
175 }
176
177 loop {
178 let waiting_for_template_end = pending
186 .last()
187 .is_some_and(|candidate| candidate.depth == depth);
188
189 let (token, rest) = consume_token(input, waiting_for_template_end, ignore_doc_comments);
196 let span = Span::from(source.len() - input.len()..source.len() - rest.len());
197 tokens.push(((token, span), rest));
198 input = rest;
199
200 match token {
204 Token::Word(_) => {
205 looking_for_template_start = true;
206 continue;
207 }
208 Token::Trivia | Token::DocComment(_) | Token::ModuleDocComment(_)
209 if looking_for_template_start =>
210 {
211 continue;
212 }
213 Token::Paren('<') if looking_for_template_start => {
214 pending.push(UnclosedCandidate {
215 index: tokens.len() - 1,
216 depth,
217 });
218 }
219 Token::TemplateArgsEnd => {
220 let candidate = pending.pop().unwrap();
227 let &mut ((ref mut token, _), _) = tokens.get_mut(candidate.index).unwrap();
228 *token = Token::TemplateArgsStart;
229 }
230 Token::Paren('(' | '[') => {
231 depth += 1;
232 }
233 Token::Paren(')' | ']') => {
234 pop_until(&mut pending, depth);
235 depth = depth.saturating_sub(1);
236 }
237 Token::Operation('=') | Token::Separator(':' | ';') | Token::Paren('{') => {
238 pending.clear();
239 depth = 0;
240 }
241 Token::LogicalOperation('&') | Token::LogicalOperation('|') => {
242 pop_until(&mut pending, depth);
243 }
244 Token::End => break,
245 _ => {}
246 }
247
248 looking_for_template_start = false;
249
250 if pending.is_empty() {
255 break;
256 }
257 }
258
259 tokens.reverse();
260}
261
262fn consume_token(
280 input: &str,
281 waiting_for_template_end: bool,
282 ignore_doc_comments: bool,
283) -> (Token<'_>, &str) {
284 let mut chars = input.chars();
285 let cur = match chars.next() {
286 Some(c) => c,
287 None => return (Token::End, ""),
288 };
289 match cur {
290 '"' => match find_string_literal_end(chars.as_str()) {
291 Some(len) => {
292 let content = &chars.as_str()[..len];
293 let rest = &chars.as_str()[len + 1..];
294 (Token::String(content), rest)
295 }
296 None => (Token::Unknown('"'), chars.as_str()),
297 },
298 ':' | ';' | ',' => (Token::Separator(cur), chars.as_str()),
299 '.' => {
300 let og_chars = chars.as_str();
301 match chars.next() {
302 Some('0'..='9') => consume_number(input),
303 _ => (Token::Separator(cur), og_chars),
304 }
305 }
306 '@' => (Token::Attribute, chars.as_str()),
307 '(' | ')' | '{' | '}' | '[' | ']' => (Token::Paren(cur), chars.as_str()),
308 '<' | '>' => {
309 let og_chars = chars.as_str();
310 if cur == '>' && waiting_for_template_end {
311 return (Token::TemplateArgsEnd, og_chars);
312 }
313 match chars.next() {
314 Some('=') => (Token::LogicalOperation(cur), chars.as_str()),
315 Some(c) if c == cur => {
316 let og_chars = chars.as_str();
317 match chars.next() {
318 Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
319 _ => (Token::ShiftOperation(cur), og_chars),
320 }
321 }
322 _ => (Token::Paren(cur), og_chars),
323 }
324 }
325 '0'..='9' => consume_number(input),
326 '/' => {
327 let og_chars = chars.as_str();
328 match chars.next() {
329 Some('/') => {
330 let mut input_chars = input.char_indices();
331 let doc_comment_end = input_chars
332 .find_map(|(index, c)| is_comment_end(c).then_some(index))
333 .unwrap_or(input.len());
334 let token = match chars.next() {
335 Some('/') if !ignore_doc_comments => {
336 Token::DocComment(&input[..doc_comment_end])
337 }
338 Some('!') if !ignore_doc_comments => {
339 Token::ModuleDocComment(&input[..doc_comment_end])
340 }
341 _ => Token::Trivia,
342 };
343 (token, input_chars.as_str())
344 }
345 Some('*') => {
346 let next_c = chars.next();
347
348 enum CommentType {
349 Doc,
350 ModuleDoc,
351 Normal,
352 }
353 let comment_type = match next_c {
354 Some('*') if !ignore_doc_comments => CommentType::Doc,
355 Some('!') if !ignore_doc_comments => CommentType::ModuleDoc,
356 _ => CommentType::Normal,
357 };
358
359 let mut depth = 1;
360 let mut prev = next_c;
361
362 for c in &mut chars {
363 match (prev, c) {
364 (Some('*'), '/') => {
365 prev = None;
366 depth -= 1;
367 if depth == 0 {
368 let rest = chars.as_str();
369 let token = match comment_type {
370 CommentType::Doc => {
371 let doc_comment_end = input.len() - rest.len();
372 Token::DocComment(&input[..doc_comment_end])
373 }
374 CommentType::ModuleDoc => {
375 let doc_comment_end = input.len() - rest.len();
376 Token::ModuleDocComment(&input[..doc_comment_end])
377 }
378 CommentType::Normal => Token::Trivia,
379 };
380 return (token, rest);
381 }
382 }
383 (Some('/'), '*') => {
384 prev = None;
385 depth += 1;
386 }
387 _ => {
388 prev = Some(c);
389 }
390 }
391 }
392
393 (Token::UnterminatedBlockComment(input), "")
394 }
395 Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
396 _ => (Token::Operation(cur), og_chars),
397 }
398 }
399 '-' => {
400 let og_chars = chars.as_str();
401 match chars.next() {
402 Some('>') => (Token::Arrow, chars.as_str()),
403 Some('-') => (Token::DecrementOperation, chars.as_str()),
404 Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
405 _ => (Token::Operation(cur), og_chars),
406 }
407 }
408 '+' => {
409 let og_chars = chars.as_str();
410 match chars.next() {
411 Some('+') => (Token::IncrementOperation, chars.as_str()),
412 Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
413 _ => (Token::Operation(cur), og_chars),
414 }
415 }
416 '*' | '%' | '^' => {
417 let og_chars = chars.as_str();
418 match chars.next() {
419 Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
420 _ => (Token::Operation(cur), og_chars),
421 }
422 }
423 '~' => (Token::Operation(cur), chars.as_str()),
424 '=' | '!' => {
425 let og_chars = chars.as_str();
426 match chars.next() {
427 Some('=') => (Token::LogicalOperation(cur), chars.as_str()),
428 _ => (Token::Operation(cur), og_chars),
429 }
430 }
431 '&' | '|' => {
432 let og_chars = chars.as_str();
433 match chars.next() {
434 Some(c) if c == cur => (Token::LogicalOperation(cur), chars.as_str()),
435 Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
436 _ => (Token::Operation(cur), og_chars),
437 }
438 }
439 _ if is_blankspace(cur) => {
440 let (_, rest) = consume_any(input, is_blankspace);
441 (Token::Trivia, rest)
442 }
443 _ if is_word_start(cur) => {
444 let (word, rest) = consume_any(input, is_word_part);
445 (Token::Word(word), rest)
446 }
447 _ => (Token::Unknown(cur), chars.as_str()),
448 }
449}
450
451const fn is_comment_end(c: char) -> bool {
455 match c {
456 '\u{000a}'..='\u{000d}' | '\u{0085}' | '\u{2028}' | '\u{2029}' => true,
457 _ => false,
458 }
459}
460
461const fn is_blankspace(c: char) -> bool {
463 match c {
464 '\u{0020}'
465 | '\u{0009}'..='\u{000d}'
466 | '\u{0085}'
467 | '\u{200e}'
468 | '\u{200f}'
469 | '\u{2028}'
470 | '\u{2029}' => true,
471 _ => false,
472 }
473}
474
475fn is_word_start(c: char) -> bool {
477 c == '_' || unicode_ident::is_xid_start(c)
478}
479
480fn is_word_part(c: char) -> bool {
482 unicode_ident::is_xid_continue(c)
483}
484
485pub(in crate::front::wgsl) struct Lexer<'a> {
486 input: &'a str,
488
489 pub(in crate::front::wgsl) source: &'a str,
494
495 last_end_offset: usize,
502
503 tokens: Vec<(TokenSpan<'a>, &'a str)>,
515
516 ignore_doc_comments: bool,
519
520 pub(in crate::front::wgsl) enable_extensions: EnableExtensions,
524}
525
526impl<'a> Lexer<'a> {
527 pub(in crate::front::wgsl) const fn new(input: &'a str, ignore_doc_comments: bool) -> Self {
528 Lexer {
529 input,
530 source: input,
531 last_end_offset: 0,
532 tokens: Vec::new(),
533 enable_extensions: EnableExtensions::empty(),
534 ignore_doc_comments,
535 }
536 }
537
538 pub(in crate::front::wgsl) fn require_enable_extension(
540 &self,
541 extension: ImplementedEnableExtension,
542 span: Span,
543 ) -> Result<'static, ()> {
544 self.enable_extensions.require(extension, span)
545 }
546
547 #[inline]
556 pub fn capture_span<T, E>(
557 &mut self,
558 inner: impl FnOnce(&mut Self) -> core::result::Result<T, E>,
559 ) -> core::result::Result<(T, Span), E> {
560 let start = self.current_byte_offset();
561 let res = inner(self)?;
562 let end = self.current_byte_offset();
563 Ok((res, Span::from(start..end)))
564 }
565
566 pub(in crate::front::wgsl) fn start_byte_offset(&mut self) -> usize {
567 loop {
568 let (token, rest) = consume_token(self.input, false, true);
570 if let Token::Trivia = token {
571 self.input = rest;
572 } else {
573 return self.current_byte_offset();
574 }
575 }
576 }
577
578 pub(in crate::front::wgsl) fn accumulate_module_doc_comments(&mut self) -> Vec<&'a str> {
580 let mut doc_comments = Vec::new();
581 loop {
582 self.input = consume_any(self.input, is_blankspace).1;
584
585 let (token, rest) = consume_token(self.input, false, self.ignore_doc_comments);
586 if let Token::ModuleDocComment(doc_comment) = token {
587 self.input = rest;
588 doc_comments.push(doc_comment);
589 } else {
590 return doc_comments;
591 }
592 }
593 }
594
595 pub(in crate::front::wgsl) fn accumulate_doc_comments(&mut self) -> Vec<&'a str> {
597 let mut doc_comments = Vec::new();
598 loop {
599 self.input = consume_any(self.input, is_blankspace).1;
601
602 let (token, rest) = consume_token(self.input, false, self.ignore_doc_comments);
603 if let Token::DocComment(doc_comment) = token {
604 self.input = rest;
605 doc_comments.push(doc_comment);
606 } else {
607 return doc_comments;
608 }
609 }
610 }
611
612 const fn current_byte_offset(&self) -> usize {
613 self.source.len() - self.input.len()
614 }
615
616 pub(in crate::front::wgsl) fn span_from(&self, offset: usize) -> Span {
617 Span::from(offset..self.last_end_offset)
618 }
619 pub(in crate::front::wgsl) fn span_with_start(&self, span: Span) -> Span {
620 span.until(&Span::from(0..self.last_end_offset))
621 }
622
623 #[must_use]
628 pub(in crate::front::wgsl) fn next(&mut self) -> TokenSpan<'a> {
629 self.next_impl(true)
630 }
631
632 #[cfg(test)]
633 pub fn next_with_unignored_doc_comments(&mut self) -> TokenSpan<'a> {
634 self.next_impl(false)
635 }
636
637 fn next_impl(&mut self, ignore_doc_comments: bool) -> TokenSpan<'a> {
639 loop {
640 if self.tokens.is_empty() {
641 discover_template_lists(
642 &mut self.tokens,
643 self.source,
644 self.input,
645 ignore_doc_comments || self.ignore_doc_comments,
646 );
647 }
648 assert!(!self.tokens.is_empty());
649 let (token, rest) = self.tokens.pop().unwrap();
650
651 self.input = rest;
652 self.last_end_offset = self.current_byte_offset();
653
654 match token.0 {
655 Token::Trivia => {}
656 _ => return token,
657 }
658 }
659 }
660
661 #[must_use]
662 pub(in crate::front::wgsl) fn peek(&mut self) -> TokenSpan<'a> {
663 let input = self.input;
664 let last_end_offset = self.last_end_offset;
665 let token = self.next();
666 self.tokens.push((token, self.input));
667 self.input = input;
668 self.last_end_offset = last_end_offset;
669 token
670 }
671
672 pub(in crate::front::wgsl) fn next_if(&mut self, what: Token<'_>) -> bool {
674 let input = self.input;
675 let last_end_offset = self.last_end_offset;
676 let token = self.next();
677 if token.0 == what {
678 true
679 } else {
680 self.tokens.push((token, self.input));
681 self.input = input;
682 self.last_end_offset = last_end_offset;
683 false
684 }
685 }
686
687 pub(in crate::front::wgsl) fn expect_span(&mut self, expected: Token<'a>) -> Result<'a, Span> {
688 let next = self.next();
689 if next.0 == expected {
690 Ok(next.1)
691 } else {
692 Err(Box::new(Error::Unexpected(
693 next.1,
694 ExpectedToken::Token(expected),
695 )))
696 }
697 }
698
699 pub(in crate::front::wgsl) fn expect(&mut self, expected: Token<'a>) -> Result<'a, ()> {
700 self.expect_span(expected)?;
701 Ok(())
702 }
703
704 pub(in crate::front::wgsl) fn next_ident_with_span(&mut self) -> Result<'a, (&'a str, Span)> {
705 match self.next() {
706 (Token::Word("_"), span) => Err(Box::new(Error::InvalidIdentifierUnderscore(span))),
707 (Token::Word(word), span) => {
708 if word.starts_with("__") {
709 Err(Box::new(Error::ReservedIdentifierPrefix(span)))
710 } else {
711 Ok((word, span))
712 }
713 }
714 (_, span) => Err(Box::new(Error::Unexpected(span, ExpectedToken::Identifier))),
715 }
716 }
717
718 pub(in crate::front::wgsl) fn next_ident(&mut self) -> Result<'a, super::ast::Ident<'a>> {
719 self.next_ident_with_span()
720 .and_then(|(word, span)| Self::word_as_ident(word, span))
721 .map(|(name, span)| super::ast::Ident { name, span })
722 }
723
724 fn word_as_ident(word: &'a str, span: Span) -> Result<'a, (&'a str, Span)> {
725 if crate::keywords::wgsl::RESERVED.contains(&word) {
726 Err(Box::new(Error::ReservedKeyword(span)))
727 } else {
728 Ok((word, span))
729 }
730 }
731
732 pub(in crate::front::wgsl) fn open_arguments(&mut self) -> Result<'a, ()> {
733 self.expect(Token::Paren('('))
734 }
735
736 pub(in crate::front::wgsl) fn next_argument(&mut self) -> Result<'a, bool> {
737 let paren = Token::Paren(')');
738 if self.next_if(Token::Separator(',')) {
739 Ok(!self.next_if(paren))
740 } else {
741 self.expect(paren).map(|()| false)
742 }
743 }
744}
745
746#[cfg(test)]
747#[track_caller]
748fn sub_test(source: &str, expected_tokens: &[Token]) {
749 sub_test_with(true, source, expected_tokens);
750}
751
752#[cfg(test)]
753#[track_caller]
754fn sub_test_with_and_without_doc_comments(source: &str, expected_tokens: &[Token]) {
755 sub_test_with(false, source, expected_tokens);
756 sub_test_with(
757 true,
758 source,
759 expected_tokens
760 .iter()
761 .filter(|v| !matches!(**v, Token::DocComment(_) | Token::ModuleDocComment(_)))
762 .cloned()
763 .collect::<Vec<_>>()
764 .as_slice(),
765 );
766}
767
768#[cfg(test)]
769#[track_caller]
770fn sub_test_with(ignore_doc_comments: bool, source: &str, expected_tokens: &[Token]) {
771 let mut lex = Lexer::new(source, ignore_doc_comments);
772 for &token in expected_tokens {
773 assert_eq!(lex.next_with_unignored_doc_comments().0, token);
774 }
775 assert_eq!(lex.next().0, Token::End);
776}
777
778#[test]
779fn test_numbers() {
780 use half::f16;
781 sub_test(
785 "0x123 0X123u 1u 123 0 0i 0x3f",
786 &[
787 Token::Number(Ok(Number::AbstractInt(291))),
788 Token::Number(Ok(Number::U32(291))),
789 Token::Number(Ok(Number::U32(1))),
790 Token::Number(Ok(Number::AbstractInt(123))),
791 Token::Number(Ok(Number::AbstractInt(0))),
792 Token::Number(Ok(Number::I32(0))),
793 Token::Number(Ok(Number::AbstractInt(63))),
794 ],
795 );
796 sub_test(
798 "0.e+4f 01. .01 12.34 .0f 0h 1e-3 0xa.fp+2 0x1P+4f 0X.3 0x3p+2h 0X1.fp-4 0x3.2p+2h",
799 &[
800 Token::Number(Ok(Number::F32(0.))),
801 Token::Number(Ok(Number::AbstractFloat(1.))),
802 Token::Number(Ok(Number::AbstractFloat(0.01))),
803 Token::Number(Ok(Number::AbstractFloat(12.34))),
804 Token::Number(Ok(Number::F32(0.))),
805 Token::Number(Ok(Number::F16(f16::from_f32(0.)))),
806 Token::Number(Ok(Number::AbstractFloat(0.001))),
807 Token::Number(Ok(Number::AbstractFloat(43.75))),
808 Token::Number(Ok(Number::F32(16.))),
809 Token::Number(Ok(Number::AbstractFloat(0.1875))),
810 Token::Number(Err(NumberError::NotRepresentable)), Token::Number(Ok(Number::AbstractFloat(0.12109375))),
813 Token::Number(Err(NumberError::NotRepresentable)), ],
816 );
817
818 sub_test(
822 "0i 2147483647i 2147483648i",
823 &[
824 Token::Number(Ok(Number::I32(0))),
825 Token::Number(Ok(Number::I32(i32::MAX))),
826 Token::Number(Err(NumberError::NotRepresentable)),
827 ],
828 );
829 sub_test(
831 "0u 4294967295u 4294967296u",
832 &[
833 Token::Number(Ok(Number::U32(u32::MIN))),
834 Token::Number(Ok(Number::U32(u32::MAX))),
835 Token::Number(Err(NumberError::NotRepresentable)),
836 ],
837 );
838
839 sub_test(
841 "0x0i 0x7FFFFFFFi 0x80000000i",
842 &[
843 Token::Number(Ok(Number::I32(0))),
844 Token::Number(Ok(Number::I32(i32::MAX))),
845 Token::Number(Err(NumberError::NotRepresentable)),
846 ],
847 );
848 sub_test(
850 "0x0u 0xFFFFFFFFu 0x100000000u",
851 &[
852 Token::Number(Ok(Number::U32(u32::MIN))),
853 Token::Number(Ok(Number::U32(u32::MAX))),
854 Token::Number(Err(NumberError::NotRepresentable)),
855 ],
856 );
857
858 sub_test(
860 "0 9223372036854775807 9223372036854775808",
861 &[
862 Token::Number(Ok(Number::AbstractInt(0))),
863 Token::Number(Ok(Number::AbstractInt(i64::MAX))),
864 Token::Number(Err(NumberError::NotRepresentable)),
865 ],
866 );
867
868 sub_test(
870 "0 0x7fffffffffffffff 0x8000000000000000",
871 &[
872 Token::Number(Ok(Number::AbstractInt(0))),
873 Token::Number(Ok(Number::AbstractInt(i64::MAX))),
874 Token::Number(Err(NumberError::NotRepresentable)),
875 ],
876 );
877
878 const SMALLEST_POSITIVE_SUBNORMAL_F32: f32 = 1e-45;
880 const LARGEST_SUBNORMAL_F32: f32 = 1.1754942e-38;
882 const SMALLEST_POSITIVE_NORMAL_F32: f32 = f32::MIN_POSITIVE;
884 const LARGEST_F32_LESS_THAN_ONE: f32 = 0.99999994;
886 const SMALLEST_F32_LARGER_THAN_ONE: f32 = 1.0000001;
888 const LARGEST_NORMAL_F32: f32 = f32::MAX;
890
891 sub_test(
893 "1e-45f 1.1754942e-38f 1.17549435e-38f 0.99999994f 1.0000001f 3.40282347e+38f",
894 &[
895 Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_SUBNORMAL_F32))),
896 Token::Number(Ok(Number::F32(LARGEST_SUBNORMAL_F32))),
897 Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_NORMAL_F32))),
898 Token::Number(Ok(Number::F32(LARGEST_F32_LESS_THAN_ONE))),
899 Token::Number(Ok(Number::F32(SMALLEST_F32_LARGER_THAN_ONE))),
900 Token::Number(Ok(Number::F32(LARGEST_NORMAL_F32))),
901 ],
902 );
903 sub_test(
904 "3.40282367e+38f",
905 &[
906 Token::Number(Err(NumberError::NotRepresentable)), ],
908 );
909
910 sub_test(
912 "0x1p-149f 0x7FFFFFp-149f 0x1p-126f 0xFFFFFFp-24f 0x800001p-23f 0xFFFFFFp+104f",
913 &[
914 Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_SUBNORMAL_F32))),
915 Token::Number(Ok(Number::F32(LARGEST_SUBNORMAL_F32))),
916 Token::Number(Ok(Number::F32(SMALLEST_POSITIVE_NORMAL_F32))),
917 Token::Number(Ok(Number::F32(LARGEST_F32_LESS_THAN_ONE))),
918 Token::Number(Ok(Number::F32(SMALLEST_F32_LARGER_THAN_ONE))),
919 Token::Number(Ok(Number::F32(LARGEST_NORMAL_F32))),
920 ],
921 );
922 sub_test(
923 "0x1p128f 0x1.000001p0f",
924 &[
925 Token::Number(Err(NumberError::NotRepresentable)), Token::Number(Err(NumberError::NotRepresentable)),
927 ],
928 );
929}
930
931#[test]
932fn double_floats() {
933 sub_test(
934 "0x1.2p4lf 0x1p8lf 0.0625lf 625e-4lf 10lf 10l",
935 &[
936 Token::Number(Ok(Number::F64(18.0))),
937 Token::Number(Ok(Number::F64(256.0))),
938 Token::Number(Ok(Number::F64(0.0625))),
939 Token::Number(Ok(Number::F64(0.0625))),
940 Token::Number(Ok(Number::F64(10.0))),
941 Token::Number(Ok(Number::AbstractInt(10))),
942 Token::Word("l"),
943 ],
944 )
945}
946
947#[test]
948fn test_tokens() {
949 sub_test("id123_OK", &[Token::Word("id123_OK")]);
950 sub_test(
951 "92No",
952 &[
953 Token::Number(Ok(Number::AbstractInt(92))),
954 Token::Word("No"),
955 ],
956 );
957 sub_test(
958 "2u3o",
959 &[
960 Token::Number(Ok(Number::U32(2))),
961 Token::Number(Ok(Number::AbstractInt(3))),
962 Token::Word("o"),
963 ],
964 );
965 sub_test(
966 "2.4f44po",
967 &[
968 Token::Number(Ok(Number::F32(2.4))),
969 Token::Number(Ok(Number::AbstractInt(44))),
970 Token::Word("po"),
971 ],
972 );
973 sub_test(
974 "Δέλτα réflexion Кызыл 𐰓𐰏𐰇 朝焼け سلام 검정 שָׁלוֹם गुलाबी փիրուզ",
975 &[
976 Token::Word("Δέλτα"),
977 Token::Word("réflexion"),
978 Token::Word("Кызыл"),
979 Token::Word("𐰓𐰏𐰇"),
980 Token::Word("朝焼け"),
981 Token::Word("سلام"),
982 Token::Word("검정"),
983 Token::Word("שָׁלוֹם"),
984 Token::Word("गुलाबी"),
985 Token::Word("փիրուզ"),
986 ],
987 );
988 sub_test("æNoø", &[Token::Word("æNoø")]);
989 sub_test("No¾", &[Token::Word("No"), Token::Unknown('¾')]);
990 sub_test("No好", &[Token::Word("No好")]);
991 sub_test("_No", &[Token::Word("_No")]);
992 sub_test(
993 r#""debug \"value\": %d", next"#,
994 &[
995 Token::String(r#"debug \"value\": %d"#),
996 Token::Separator(','),
997 Token::Word("next"),
998 ],
999 );
1000 sub_test(
1001 r#""debug\\", next"#,
1002 &[
1003 Token::String(r#"debug\\"#),
1004 Token::Separator(','),
1005 Token::Word("next"),
1006 ],
1007 );
1008
1009 sub_test_with_and_without_doc_comments(
1010 "*/*/***/*//=/*****//",
1011 &[
1012 Token::Operation('*'),
1013 Token::AssignmentOperation('/'),
1014 Token::DocComment("/*****/"),
1015 Token::Operation('/'),
1016 ],
1017 );
1018
1019 sub_test(
1022 "0x1.2f 0x1.2f 0x1.2h 0x1.2H 0x1.2lf",
1023 &[
1024 Token::Number(Ok(Number::AbstractFloat(1.0 + 0x2f as f64 / 256.0))),
1027 Token::Number(Ok(Number::AbstractFloat(1.0 + 0x2f as f64 / 256.0))),
1028 Token::Number(Ok(Number::AbstractFloat(1.125))),
1029 Token::Word("h"),
1030 Token::Number(Ok(Number::AbstractFloat(1.125))),
1031 Token::Word("H"),
1032 Token::Number(Ok(Number::AbstractFloat(1.125))),
1033 Token::Word("lf"),
1034 ],
1035 )
1036}
1037
1038#[test]
1039fn test_variable_decl() {
1040 sub_test(
1041 "@group(0 ) var< uniform> texture: texture_multisampled_2d <f32 >;",
1042 &[
1043 Token::Attribute,
1044 Token::Word("group"),
1045 Token::Paren('('),
1046 Token::Number(Ok(Number::AbstractInt(0))),
1047 Token::Paren(')'),
1048 Token::Word("var"),
1049 Token::TemplateArgsStart,
1050 Token::Word("uniform"),
1051 Token::TemplateArgsEnd,
1052 Token::Word("texture"),
1053 Token::Separator(':'),
1054 Token::Word("texture_multisampled_2d"),
1055 Token::TemplateArgsStart,
1056 Token::Word("f32"),
1057 Token::TemplateArgsEnd,
1058 Token::Separator(';'),
1059 ],
1060 );
1061 sub_test(
1062 "var<storage,read_write> buffer: array<u32>;",
1063 &[
1064 Token::Word("var"),
1065 Token::TemplateArgsStart,
1066 Token::Word("storage"),
1067 Token::Separator(','),
1068 Token::Word("read_write"),
1069 Token::TemplateArgsEnd,
1070 Token::Word("buffer"),
1071 Token::Separator(':'),
1072 Token::Word("array"),
1073 Token::TemplateArgsStart,
1074 Token::Word("u32"),
1075 Token::TemplateArgsEnd,
1076 Token::Separator(';'),
1077 ],
1078 );
1079}
1080
1081#[test]
1082fn test_template_list() {
1083 sub_test(
1084 "A<B||C>D",
1085 &[
1086 Token::Word("A"),
1087 Token::Paren('<'),
1088 Token::Word("B"),
1089 Token::LogicalOperation('|'),
1090 Token::Word("C"),
1091 Token::Paren('>'),
1092 Token::Word("D"),
1093 ],
1094 );
1095 sub_test(
1096 "A(B<C,D>(E))",
1097 &[
1098 Token::Word("A"),
1099 Token::Paren('('),
1100 Token::Word("B"),
1101 Token::TemplateArgsStart,
1102 Token::Word("C"),
1103 Token::Separator(','),
1104 Token::Word("D"),
1105 Token::TemplateArgsEnd,
1106 Token::Paren('('),
1107 Token::Word("E"),
1108 Token::Paren(')'),
1109 Token::Paren(')'),
1110 ],
1111 );
1112 sub_test(
1113 "array<i32,select(2,3,A>B)>",
1114 &[
1115 Token::Word("array"),
1116 Token::TemplateArgsStart,
1117 Token::Word("i32"),
1118 Token::Separator(','),
1119 Token::Word("select"),
1120 Token::Paren('('),
1121 Token::Number(Ok(Number::AbstractInt(2))),
1122 Token::Separator(','),
1123 Token::Number(Ok(Number::AbstractInt(3))),
1124 Token::Separator(','),
1125 Token::Word("A"),
1126 Token::Paren('>'),
1127 Token::Word("B"),
1128 Token::Paren(')'),
1129 Token::TemplateArgsEnd,
1130 ],
1131 );
1132 sub_test(
1133 "A[B<C]>D",
1134 &[
1135 Token::Word("A"),
1136 Token::Paren('['),
1137 Token::Word("B"),
1138 Token::Paren('<'),
1139 Token::Word("C"),
1140 Token::Paren(']'),
1141 Token::Paren('>'),
1142 Token::Word("D"),
1143 ],
1144 );
1145 sub_test(
1146 "A<B<<C>",
1147 &[
1148 Token::Word("A"),
1149 Token::TemplateArgsStart,
1150 Token::Word("B"),
1151 Token::ShiftOperation('<'),
1152 Token::Word("C"),
1153 Token::TemplateArgsEnd,
1154 ],
1155 );
1156 sub_test(
1157 "A<(B>=C)>",
1158 &[
1159 Token::Word("A"),
1160 Token::TemplateArgsStart,
1161 Token::Paren('('),
1162 Token::Word("B"),
1163 Token::LogicalOperation('>'),
1164 Token::Word("C"),
1165 Token::Paren(')'),
1166 Token::TemplateArgsEnd,
1167 ],
1168 );
1169 sub_test(
1170 "A<B>=C>",
1171 &[
1172 Token::Word("A"),
1173 Token::TemplateArgsStart,
1174 Token::Word("B"),
1175 Token::TemplateArgsEnd,
1176 Token::Operation('='),
1177 Token::Word("C"),
1178 Token::Paren('>'),
1179 ],
1180 );
1181}
1182
1183#[test]
1184fn test_comments() {
1185 sub_test("// Single comment", &[]);
1186
1187 sub_test(
1188 "/* multi
1189 line
1190 comment */",
1191 &[],
1192 );
1193 sub_test(
1194 "/* multi
1195 line
1196 comment */
1197 // and another",
1198 &[],
1199 );
1200}
1201
1202#[test]
1203fn test_doc_comments() {
1204 sub_test_with_and_without_doc_comments(
1205 "/// Single comment",
1206 &[Token::DocComment("/// Single comment")],
1207 );
1208
1209 sub_test_with_and_without_doc_comments(
1210 "/** multi
1211 line
1212 comment */",
1213 &[Token::DocComment(
1214 "/** multi
1215 line
1216 comment */",
1217 )],
1218 );
1219 sub_test_with_and_without_doc_comments(
1220 "/** multi
1221 line
1222 comment */
1223 /// and another",
1224 &[
1225 Token::DocComment(
1226 "/** multi
1227 line
1228 comment */",
1229 ),
1230 Token::DocComment("/// and another"),
1231 ],
1232 );
1233}
1234
1235#[test]
1236fn test_doc_comment_nested() {
1237 sub_test_with_and_without_doc_comments(
1238 "/**
1239 a comment with nested one /**
1240 nested comment
1241 */
1242 */
1243 const a : i32 = 2;",
1244 &[
1245 Token::DocComment(
1246 "/**
1247 a comment with nested one /**
1248 nested comment
1249 */
1250 */",
1251 ),
1252 Token::Word("const"),
1253 Token::Word("a"),
1254 Token::Separator(':'),
1255 Token::Word("i32"),
1256 Token::Operation('='),
1257 Token::Number(Ok(Number::AbstractInt(2))),
1258 Token::Separator(';'),
1259 ],
1260 );
1261}
1262
1263#[test]
1264fn test_doc_comment_long_character() {
1265 sub_test_with_and_without_doc_comments(
1266 "/// π/2
1267 /// D(𝐡) = ───────────────────────────────────────────────────
1268/// παₜα_b((𝐡 ⋅ 𝐭)² / αₜ²) + (𝐡 ⋅ 𝐛)² / α_b² +`
1269 const a : i32 = 2;",
1270 &[
1271 Token::DocComment("/// π/2"),
1272 Token::DocComment("/// D(𝐡) = ───────────────────────────────────────────────────"),
1273 Token::DocComment("/// παₜα_b((𝐡 ⋅ 𝐭)² / αₜ²) + (𝐡 ⋅ 𝐛)² / α_b² +`"),
1274 Token::Word("const"),
1275 Token::Word("a"),
1276 Token::Separator(':'),
1277 Token::Word("i32"),
1278 Token::Operation('='),
1279 Token::Number(Ok(Number::AbstractInt(2))),
1280 Token::Separator(';'),
1281 ],
1282 );
1283}
1284
1285#[test]
1286fn test_doc_comments_module() {
1287 sub_test_with_and_without_doc_comments(
1288 "//! Comment Module
1289 //! Another one.
1290 /*! Different module comment */
1291 /// Trying to break module comment
1292 // Trying to break module comment again
1293 //! After a regular comment is ok.
1294 /*! Different module comment again */
1295
1296 //! After a break is supported.
1297 const
1298 //! After anything else is not.",
1299 &[
1300 Token::ModuleDocComment("//! Comment Module"),
1301 Token::ModuleDocComment("//! Another one."),
1302 Token::ModuleDocComment("/*! Different module comment */"),
1303 Token::DocComment("/// Trying to break module comment"),
1304 Token::ModuleDocComment("//! After a regular comment is ok."),
1305 Token::ModuleDocComment("/*! Different module comment again */"),
1306 Token::ModuleDocComment("//! After a break is supported."),
1307 Token::Word("const"),
1308 Token::ModuleDocComment("//! After anything else is not."),
1309 ],
1310 );
1311}
1312
1313#[test]
1314fn test_block_comment_unclosed() {
1315 sub_test_with_and_without_doc_comments(
1316 "/** Unclosed Doc Comment",
1317 &[Token::UnterminatedBlockComment("/** Unclosed Doc Comment")],
1318 );
1319}