Skip to main content

naga/front/wgsl/parse/
lexer.rs

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    /// A separator character: `:;,`, and `.` when not part of a numeric
16    /// literal.
17    Separator(char),
18
19    /// A parenthesis-like character: `()[]{}`, and also `<>`.
20    ///
21    /// Note that `<>` representing template argument brackets are distinguished
22    /// using WGSL's [template list discovery algorithm][tlda], and are returned
23    /// as [`Token::TemplateArgsStart`] and [`Token::TemplateArgsEnd`]. That is,
24    /// we use `Paren` for `<>` when they are *not* parens.
25    ///
26    /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery
27    Paren(char),
28
29    /// The attribute introduction character `@`.
30    Attribute,
31
32    /// A numeric literal, either integral or floating-point, including any
33    /// type suffix.
34    Number(core::result::Result<Number, NumberError>),
35
36    /// An identifier, possibly a reserved word.
37    Word(&'a str),
38
39    /// A string literal, used for `debugPrintf` format strings.
40    String(&'a str),
41
42    /// A miscellaneous single-character operator, like an arithmetic unary or
43    /// binary operator. This includes `=`, for assignment and initialization.
44    Operation(char),
45
46    /// Certain multi-character logical operators: `!=`, `==`, `&&`,
47    /// `||`, `<=` and `>=`. The value gives the operator's first
48    /// character.
49    ///
50    /// For `<` and `>` operators, see [`Token::Paren`].
51    LogicalOperation(char),
52
53    /// A shift operator: `>>` or `<<`.
54    ShiftOperation(char),
55
56    /// A compound assignment operator like `+=`.
57    ///
58    /// When the given character is `<` or `>`, those represent the left shift
59    /// and right shift assignment operators, `<<=` and `>>=`.
60    AssignmentOperation(char),
61
62    /// The `++` operator.
63    IncrementOperation,
64
65    /// The `--` operator.
66    DecrementOperation,
67
68    /// The `->` token.
69    Arrow,
70
71    /// A `<` representing the start of a template argument list, according to
72    /// WGSL's [template list discovery algorithm][tlda].
73    ///
74    /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery
75    TemplateArgsStart,
76
77    /// A `>` representing the end of a template argument list, according to
78    /// WGSL's [template list discovery algorithm][tlda].
79    ///
80    /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery
81    TemplateArgsEnd,
82
83    /// A character that does not represent a legal WGSL token.
84    Unknown(char),
85
86    /// Comment or whitespace.
87    Trivia,
88
89    /// A doc comment, beginning with `///` or `/**`.
90    DocComment(&'a str),
91
92    /// A module-level doc comment, beginning with `//!` or `/*!`.
93    ModuleDocComment(&'a str),
94
95    /// A block comment that is incomplete, and has not been closed with */.
96    ///
97    /// It's expected that the parser will consider this to be an error.
98    UnterminatedBlockComment(&'a str),
99
100    /// The end of the input.
101    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
128/// Produce at least one token, distinguishing [template lists] from other uses
129/// of `<` and `>`.
130///
131/// Consume one or more tokens from `input` and store them in `tokens`, updating
132/// `input` to refer to the remaining text. Apply WGSL's [template list
133/// discovery algorithm] to decide what sort of tokens `<` and `>` characters in
134/// the input actually represent.
135///
136/// Store the tokens in `tokens` in the *reverse* of the order they appear in
137/// the text, such that the caller can pop from the end of the vector to see the
138/// tokens in textual order.
139///
140/// The `tokens` vector must be empty on entry. The idea is for the caller to
141/// use it as a buffer of unconsumed tokens, and call this function to refill it
142/// when it's empty.
143///
144/// The `source` argument must be the whole original source code, used to
145/// compute spans.
146///
147/// If `ignore_doc_comments` is true, then doc comments are returned as
148/// [`Token::Trivia`], like ordinary comments.
149///
150/// [template lists]: https://gpuweb.github.io/gpuweb/wgsl/#template-lists-sec
151/// [template list discovery algorithm]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery
152fn 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    // Current nesting depth of `()` and `[]` brackets. (`{}` brackets
164    // exit all template list processing.)
165    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        // Decide whether `consume_token` should treat a `>` character as
179        // `TemplateArgsEnd`, without considering the characters that follow.
180        //
181        // This condition matches the one that determines whether the spec's
182        // template list discovery algorithm looks past a `>` character for a
183        // `=`. By passing this flag to `consume_token`, we ensure it follows
184        // that behavior.
185        let waiting_for_template_end = pending
186            .last()
187            .is_some_and(|candidate| candidate.depth == depth);
188
189        // Ask `consume_token` for the next token and add it to `tokens`, along
190        // with its span.
191        //
192        // This means that `<` enters the buffer as `Token::Paren('<')`, the
193        // ordinary comparison operator. We'll change that to
194        // `Token::TemplateArgsStart` later if appropriate.
195        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        // Since `consume_token` treats `<<=`, `<<` and `<=` as operators, not
201        // `Token::Paren`, that takes care of the WGSL algorithm's post-'<' lookahead
202        // for us.
203        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                // The `consume_token` function only returns `TemplateArgsEnd`
221                // if `waiting_for_template_end` is true, so we know `pending`
222                // has a top entry at the appropriate depth.
223                //
224                // Find the matching `<` token and change its type to
225                // `TemplateArgsStart`.
226                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        // The WGSL spec's template list discovery algorithm processes the
251        // entire source at once, but Naga would rather limit its lookahead to
252        // the actual text that could possibly be a template parameter list.
253        // This is usually less than a line.
254        if pending.is_empty() {
255            break;
256        }
257    }
258
259    tokens.reverse();
260}
261
262/// Return the token at the start of `input`.
263///
264/// The `waiting_for_template_end` flag enables some special handling to help out
265/// `discover_template_lists`:
266///
267/// - If `waiting_for_template_end` is `true`, then return text starting with
268///   '>` as [`Token::TemplateArgsEnd`] and consume only the `>` character,
269///   regardless of what characters follow it. This is required by the [template
270///   list discovery algorithm][tlda] when the `>` would end a template argument list.
271///
272/// - If `waiting_for_template_end` is false, recognize multi-character tokens
273///   beginning with `>` as usual.
274///
275/// If `ignore_doc_comments` is true, then doc comments are returned as
276/// [`Token::Trivia`], like ordinary comments.
277///
278/// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery
279fn 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
451/// Returns whether or not a char is a comment end
452/// (Unicode Pattern_White_Space excluding U+0020, U+0009, U+200E and U+200F)
453/// <https://www.w3.org/TR/WGSL/#line-break>
454const 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
461/// Returns whether or not a char is a blankspace (Unicode Pattern_White_Space)
462const 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
475/// Returns whether or not a char is a word start (Unicode XID_Start + '_')
476fn is_word_start(c: char) -> bool {
477    c == '_' || unicode_ident::is_xid_start(c)
478}
479
480/// Returns whether or not a char is a word part (Unicode XID_Continue)
481fn is_word_part(c: char) -> bool {
482    unicode_ident::is_xid_continue(c)
483}
484
485pub(in crate::front::wgsl) struct Lexer<'a> {
486    /// The remaining unconsumed input.
487    input: &'a str,
488
489    /// The full original source code.
490    ///
491    /// We compare `input` against this to compute the lexer's current offset in
492    /// the source.
493    pub(in crate::front::wgsl) source: &'a str,
494
495    /// The byte offset of the end of the most recently returned non-trivia
496    /// token.
497    ///
498    /// This is consulted by the `span_from` function, for finding the
499    /// end of the span for larger structures like expressions or
500    /// statements.
501    last_end_offset: usize,
502
503    /// A stack of unconsumed tokens to which template list discovery has been
504    /// applied.
505    ///
506    /// This is a stack: the next token is at the *end* of the vector, not the
507    /// start. So tokens appear here in the reverse of the order they appear in
508    /// the source.
509    ///
510    /// This doesn't contain the whole source, only those tokens produced by
511    /// [`discover_template_lists`]'s look-ahead, or that have been produced by
512    /// other look-ahead functions like `peek` and `next_if`. When this is empty,
513    /// we call [`discover_template_lists`] to get more.
514    tokens: Vec<(TokenSpan<'a>, &'a str)>,
515
516    /// Whether or not to ignore doc comments.
517    /// If `true`, doc comments are treated as [`Token::Trivia`].
518    ignore_doc_comments: bool,
519
520    /// The set of [enable-extensions] present in the module, determined in a pre-pass.
521    ///
522    /// [enable-extensions]: https://gpuweb.github.io/gpuweb/wgsl/#enable-extensions-sec
523    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    /// Check that `extension` is enabled in `self`.
539    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    /// Calls the function with a lexer and returns the result of the function as well as the span for everything the function parsed
548    ///
549    /// # Examples
550    /// ```ignore
551    /// let lexer = Lexer::new("5");
552    /// let (value, span) = lexer.capture_span(Lexer::next_uint_literal);
553    /// assert_eq!(value, 5);
554    /// ```
555    #[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            // Eat all trivia because `next` doesn't eat trailing trivia.
569            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    /// Collect all module doc comments until a non doc token is found.
579    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            // ignore blankspace
583            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    /// Collect all doc comments until a non doc token is found.
596    pub(in crate::front::wgsl) fn accumulate_doc_comments(&mut self) -> Vec<&'a str> {
597        let mut doc_comments = Vec::new();
598        loop {
599            // ignore blankspace
600            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    /// Return the next non-whitespace token from `self`.
624    ///
625    /// Assume we are a parse state where bit shift operators may
626    /// occur, but not angle brackets.
627    #[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    /// Return the next non-whitespace token from `self`, with a span.
638    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    /// If the next token matches it's consumed and true is returned
673    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    // WGSL spec examples //
782
783    // decimal integer
784    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    // decimal floating point
797    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            // https://github.com/gfx-rs/wgpu/issues/7046
811            Token::Number(Err(NumberError::NotRepresentable)), // Should be 0.75
812            Token::Number(Ok(Number::AbstractFloat(0.12109375))),
813            // https://github.com/gfx-rs/wgpu/issues/7046
814            Token::Number(Err(NumberError::NotRepresentable)), // Should be 12.5
815        ],
816    );
817
818    // MIN / MAX //
819
820    // min / max decimal integer
821    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    // min / max decimal unsigned integer
830    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    // min / max hexadecimal signed integer
840    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    // min / max hexadecimal unsigned integer
849    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    // min/max decimal abstract int
859    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    // min/max hexadecimal abstract int
869    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    /// ≈ 2^-126 * 2^−23 (= 2^−149)
879    const SMALLEST_POSITIVE_SUBNORMAL_F32: f32 = 1e-45;
880    /// ≈ 2^-126 * (1 − 2^−23)
881    const LARGEST_SUBNORMAL_F32: f32 = 1.1754942e-38;
882    /// ≈ 2^-126
883    const SMALLEST_POSITIVE_NORMAL_F32: f32 = f32::MIN_POSITIVE;
884    /// ≈ 1 − 2^−24
885    const LARGEST_F32_LESS_THAN_ONE: f32 = 0.99999994;
886    /// ≈ 1 + 2^−23
887    const SMALLEST_F32_LARGER_THAN_ONE: f32 = 1.0000001;
888    /// ≈ 2^127 * (2 − 2^−23)
889    const LARGEST_NORMAL_F32: f32 = f32::MAX;
890
891    // decimal floating point
892    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)), // ≈ 2^128
907        ],
908    );
909
910    // hexadecimal floating point
911    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)), // = 2^128
926            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    // Type suffixes are only allowed on hex float literals
1020    // if you provided an exponent.
1021    sub_test(
1022        "0x1.2f 0x1.2f 0x1.2h 0x1.2H 0x1.2lf",
1023        &[
1024            // The 'f' suffixes are taken as a hex digit:
1025            // the fractional part is 0x2f / 256.
1026            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}