#[derive(Copy, Clone, Debug, PartialEq)] pubenum Token<'a> { /// A separator character: `:;,`, and `.` when not part of a numeric /// literal.
Separator(char),
/// A parenthesis-like character: `()[]{}`, and also `<>`. /// /// Note that `<>` representing template argument brackets are distinguished /// using WGSL's [template list discovery algorithm][tlda], and are returned /// as [`Token::TemplateArgsStart`] and [`Token::TemplateArgsEnd`]. That is, /// we use `Paren` for `<>` when they are *not* parens. /// /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery
Paren(char),
/// The attribute introduction character `@`.
Attribute,
/// A numeric literal, either integral or floating-point, including any /// type suffix.
Number(core::result::Result<Number, NumberError>),
/// An identifier, possibly a reserved word.
Word(&'a str),
/// A miscellaneous single-character operator, like an arithmetic unary or /// binary operator. This includes `=`, for assignment and initialization.
Operation(char),
/// Certain multi-character logical operators: `!=`, `==`, `&&`, /// `||`, `<=` and `>=`. The value gives the operator's first /// character. /// /// For `<` and `>` operators, see [`Token::Paren`].
LogicalOperation(char),
/// A shift operator: `>>` or `<<`.
ShiftOperation(char),
/// A compound assignment operator like `+=`. /// /// When the given character is `<` or `>`, those represent the left shift /// and right shift assignment operators, `<<=` and `>>=`.
AssignmentOperation(char),
/// A character that does not represent a legal WGSL token.
Unknown(char),
/// Comment or whitespace.
Trivia,
/// A doc comment, beginning with `///` or `/**`.
DocComment(&'a str),
/// A module-level doc comment, beginning with `//!` or `/*!`.
ModuleDocComment(&'a str),
/// A block comment that is incomplete, and has not been closed with */. /// /// It's expected that the parser will consider this to be an error.
UnterminatedBlockComment(&'a str),
/// Produce at least one token, distinguishing [template lists] from other uses /// of `<` and `>`. /// /// Consume one or more tokens from `input` and store them in `tokens`, updating /// `input` to refer to the remaining text. Apply WGSL's [template list /// discovery algorithm] to decide what sort of tokens `<` and `>` characters in /// the input actually represent. /// /// Store the tokens in `tokens` in the *reverse* of the order they appear in /// the text, such that the caller can pop from the end of the vector to see the /// tokens in textual order. /// /// The `tokens` vector must be empty on entry. The idea is for the caller to /// use it as a buffer of unconsumed tokens, and call this function to refill it /// when it's empty. /// /// The `source` argument must be the whole original source code, used to /// compute spans. /// /// If `ignore_doc_comments` is true, then doc comments are returned as /// [`Token::Trivia`], like ordinary comments. /// /// [template lists]: https://gpuweb.github.io/gpuweb/wgsl/#template-lists-sec /// [template list discovery algorithm]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery fn discover_template_lists<'a>(
tokens: &mut Vec<(TokenSpan<'a>, &'a str)>,
source: &'a str, mut input: &'a str,
ignore_doc_comments: bool,
) {
assert!(tokens.is_empty());
loop { // Decide whether `consume_token` should treat a `>` character as // `TemplateArgsEnd`, without considering the characters that follow. // // This condition matches the one that determines whether the spec's // template list discovery algorithm looks past a `>` character for a // `=`. By passing this flag to `consume_token`, we ensure it follows // that behavior. let waiting_for_template_end = pending
.last()
.is_some_and(|candidate| candidate.depth == depth);
// Ask `consume_token` for the next token and add it to `tokens`, along // with its span. // // This means that `<` enters the buffer as `Token::Paren('<')`, the // ordinary comparison operator. We'll change that to // `Token::TemplateArgsStart` later if appropriate. let (token, rest) = consume_token(input, waiting_for_template_end, ignore_doc_comments); let span = Span::from(source.len() - input.len()..source.len() - rest.len());
tokens.push(((token, span), rest));
input = rest;
// Since `consume_token` treats `<<=`, `<<` and `<=` as operators, not // `Token::Paren`, that takes care of the WGSL algorithm's post-'<' lookahead // for us. match token {
Token::Word(_) => {
looking_for_template_start = true; continue;
}
Token::Trivia | Token::DocComment(_) | Token::ModuleDocComment(_) if looking_for_template_start =>
{ continue;
}
Token::Paren('<') if looking_for_template_start => {
pending.push(UnclosedCandidate {
index: tokens.len() - 1,
depth,
});
}
Token::TemplateArgsEnd => { // The `consume_token` function only returns `TemplateArgsEnd` // if `waiting_for_template_end` is true, so we know `pending` // has a top entry at the appropriate depth. // // Find the matching `<` token and change its type to // `TemplateArgsStart`. let candidate = pending.pop().unwrap(); let &mut ((refmut token, _), _) = tokens.get_mut(candidate.index).unwrap();
*token = Token::TemplateArgsStart;
}
Token::Paren('(' | '[') => {
depth += 1;
}
Token::Paren(')' | ']') => {
pop_until(&mut pending, depth);
depth = depth.saturating_sub(1);
}
Token::Operation('=') | Token::Separator(':' | ';') | Token::Paren('{') => {
pending.clear();
depth = 0;
}
Token::LogicalOperation('&') | Token::LogicalOperation('|') => {
pop_until(&mut pending, depth);
}
Token::End => break,
_ => {}
}
looking_for_template_start = false;
// The WGSL spec's template list discovery algorithm processes the // entire source at once, but Naga would rather limit its lookahead to // the actual text that could possibly be a template parameter list. // This is usually less than a line. if pending.is_empty() { break;
}
}
tokens.reverse();
}
/// Return the token at the start of `input`. /// /// The `waiting_for_template_end` flag enables some special handling to help out /// `discover_template_lists`: /// /// - If `waiting_for_template_end` is `true`, then return text starting with /// '>` as [`Token::TemplateArgsEnd`] and consume only the `>` character, /// regardless of what characters follow it. This is required by the [template /// list discovery algorithm][tlda] when the `>` would end a template argument list. /// /// - If `waiting_for_template_end` is false, recognize multi-character tokens /// beginning with `>` as usual. /// /// If `ignore_doc_comments` is true, then doc comments are returned as /// [`Token::Trivia`], like ordinary comments. /// /// [tlda]: https://gpuweb.github.io/gpuweb/wgsl/#template-list-discovery fn consume_token(
input: &str,
waiting_for_template_end: bool,
ignore_doc_comments: bool,
) -> (Token<'_>, &str) { letmut chars = input.chars(); let cur = match chars.next() {
Some(c) => c,
None => return (Token::End, ""),
}; match cur { ':' | ';' | ',' => (Token::Separator(cur), chars.as_str()), '.' => { let og_chars = chars.as_str(); match chars.next() {
Some('0'..='9') => consume_number(input),
_ => (Token::Separator(cur), og_chars),
}
} '@' => (Token::Attribute, chars.as_str()), '(' | ')' | '{' | '}' | '[' | ']' => (Token::Paren(cur), chars.as_str()), '<' | '>' => { let og_chars = chars.as_str(); if cur == '>' && waiting_for_template_end { return (Token::TemplateArgsEnd, og_chars);
} match chars.next() {
Some('=') => (Token::LogicalOperation(cur), chars.as_str()),
Some(c) if c == cur => { let og_chars = chars.as_str(); match chars.next() {
Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
_ => (Token::ShiftOperation(cur), og_chars),
}
}
_ => (Token::Paren(cur), og_chars),
}
} '0'..='9' => consume_number(input), '/' => { let og_chars = chars.as_str(); match chars.next() {
Some('/') => { letmut input_chars = input.char_indices(); let doc_comment_end = input_chars
.find_map(|(index, c)| is_comment_end(c).then_some(index))
.unwrap_or(input.len()); let token = match chars.next() {
Some('/') if !ignore_doc_comments => {
Token::DocComment(&input[..doc_comment_end])
}
Some('!') if !ignore_doc_comments => {
Token::ModuleDocComment(&input[..doc_comment_end])
}
_ => Token::Trivia,
};
(token, input_chars.as_str())
}
Some('*') => { let next_c = chars.next();
enum CommentType {
Doc,
ModuleDoc,
Normal,
} let comment_type = match next_c {
Some('*') if !ignore_doc_comments => CommentType::Doc,
Some('!') if !ignore_doc_comments => CommentType::ModuleDoc,
_ => CommentType::Normal,
};
letmut depth = 1; letmut prev = next_c;
for c in &mut chars { match (prev, c) {
(Some('*'), '/') => {
prev = None;
depth -= 1; if depth == 0 { let rest = chars.as_str(); let token = match comment_type {
CommentType::Doc => { let doc_comment_end = input.len() - rest.len();
Token::DocComment(&input[..doc_comment_end])
}
CommentType::ModuleDoc => { let doc_comment_end = input.len() - rest.len();
Token::ModuleDocComment(&input[..doc_comment_end])
}
CommentType::Normal => Token::Trivia,
}; return (token, rest);
}
}
(Some('/'), '*') => {
prev = None;
depth += 1;
}
_ => {
prev = Some(c);
}
}
}
/// Returns whether or not a char is a comment end /// (Unicode Pattern_White_Space excluding U+0020, U+0009, U+200E and U+200F) /// <https://www.w3.org/TR/WGSL/#line-break> constfn is_comment_end(c: char) -> bool { match c { '\u{000a}'..='\u{000d}' | '\u{0085}' | '\u{2028}' | '\u{2029}' => true,
_ => false,
}
}
/// Returns whether or not a char is a blankspace (Unicode Pattern_White_Space) constfn is_blankspace(c: char) -> bool { match c { '\u{0020}'
| '\u{0009}'..='\u{000d}'
| '\u{0085}'
| '\u{200e}'
| '\u{200f}'
| '\u{2028}'
| '\u{2029}' => true,
_ => false,
}
}
/// Returns whether or not a char is a word start (Unicode XID_Start + '_') fn is_word_start(c: char) -> bool {
c == '_' || unicode_ident::is_xid_start(c)
}
/// Returns whether or not a char is a word part (Unicode XID_Continue) fn is_word_part(c: char) -> bool {
unicode_ident::is_xid_continue(c)
}
/// The full original source code. /// /// We compare `input` against this to compute the lexer's current offset in /// the source. pub(incrate::front::wgsl) source: &'a str,
/// The byte offset of the end of the most recently returned non-trivia /// token. /// /// This is consulted by the `span_from` function, for finding the /// end of the span for larger structures like expressions or /// statements.
last_end_offset: usize,
/// A stack of unconsumed tokens to which template list discovery has been /// applied. /// /// This is a stack: the next token is at the *end* of the vector, not the /// start. So tokens appear here in the reverse of the order they appear in /// the source. /// /// This doesn't contain the whole source, only those tokens produced by /// [`discover_template_lists`]'s look-ahead, or that have been produced by /// other look-ahead functions like `peek` and `next_if`. When this is empty, /// we call [`discover_template_lists`] to get more.
tokens: Vec<(TokenSpan<'a>, &'a str)>,
/// Whether or not to ignore doc comments. /// If `true`, doc comments are treated as [`Token::Trivia`].
ignore_doc_comments: bool,
/// The set of [enable-extensions] present in the module, determined in a pre-pass. /// /// [enable-extensions]: https://gpuweb.github.io/gpuweb/wgsl/#enable-extensions-sec pub(incrate::front::wgsl) enable_extensions: EnableExtensions,
}
/// Check that `extension` is enabled in `self`. pub(incrate::front::wgsl) fn require_enable_extension(
&self,
extension: ImplementedEnableExtension,
span: Span,
) -> Result<'static, ()> { self.enable_extensions.require(extension, span)
}
/// Calls the function with a lexer and returns the result of the function as well as the span for everything the function parsed /// /// # Examples /// ```ignore /// let lexer = Lexer::new("5"); /// let (value, span) = lexer.capture_span(Lexer::next_uint_literal); /// assert_eq!(value, 5); /// ``` #[inline] pubfn capture_span<T, E>(
&mutself,
inner: impl FnOnce(&mutSelf) -> core::result::Result<T, E>,
) -> core::result::Result<(T, Span), E> { let start = self.current_byte_offset(); let res = inner(self)?; let end = self.current_byte_offset();
Ok((res, Span::from(start..end)))
}
/// Return the next non-whitespace token from `self`. /// /// Assume we are a parse state where bit shift operators may /// occur, but not angle brackets. #[must_use] pub(incrate::front::wgsl) fn next(&mutself) -> TokenSpan<'a> { self.next_impl(true)
}
#[must_use] pub(incrate::front::wgsl) fn peek(&mutself) -> TokenSpan<'a> { let input = self.input; let last_end_offset = self.last_end_offset; let token = self.next(); self.tokens.push((token, self.input)); self.input = input; self.last_end_offset = last_end_offset;
token
}
/// If the next token matches it's consumed and true is returned pub(incrate::front::wgsl) fn next_if(&mutself, what: Token<'_>) -> bool { let input = self.input; let last_end_offset = self.last_end_offset; let token = self.next(); if token.0 == what { true
} else { self.tokens.push((token, self.input)); self.input = input; self.last_end_offset = last_end_offset; false
}
}
pub(incrate::front::wgsl) fn expect_span(&mutself, expected: Token<'a>) -> Result<'a, Span> { let next = self.next(); if next.0 == expected {
Ok(next.1)
} else {
Err(Box::new(Error::Unexpected(
next.1,
ExpectedToken::Token(expected),
)))
}
}
// Type suffixes are only allowed on hex float literals // if you provided an exponent.
sub_test( "0x1.2f 0x1.2f 0x1.2h 0x1.2H 0x1.2lf",
&[ // The 'f' suffixes are taken as a hex digit: // the fractional part is 0x2f / 256.
Token::Number(Ok(Number::AbstractFloat(1.0 + 0x2f as f64 / 256.0))),
Token::Number(Ok(Number::AbstractFloat(1.0 + 0x2f as f64 / 256.0))),
Token::Number(Ok(Number::AbstractFloat(1.125))),
Token::Word("h"),
Token::Number(Ok(Number::AbstractFloat(1.125))),
Token::Word("H"),
Token::Number(Ok(Number::AbstractFloat(1.125))),
Token::Word("lf"),
],
)
}
#[test] fn test_comments() {
sub_test("// Single comment", &[]);
sub_test( "/* multi
line
comment */",
&[],
);
sub_test( "/* multi
line
comment */ // and another",
&[],
);
}
#[test] fn test_doc_comments() {
sub_test_with_and_without_doc_comments( "/// Single comment",
&[Token::DocComment("/// Single comment")],
);
sub_test_with_and_without_doc_comments( "/** multi
line
comment */",
&[Token::DocComment( "/** multi
line
comment */",
)],
);
sub_test_with_and_without_doc_comments( "/** multi
line
comment */ /// and another",
&[
Token::DocComment( "/** multi
line
comment */",
),
Token::DocComment("/// and another"),
],
);
}
#[test] fn test_doc_comment_nested() {
sub_test_with_and_without_doc_comments( "/**
a comment with nested one /** nestedcomment
*/
*/ const a : i32 = 2;",
&[
Token::DocComment( "/**
a comment with nested one /** nestedcomment
*/
*/",
),
Token::Word("const"),
Token::Word("a"),
Token::Separator(':'),
Token::Word("i32"),
Token::Operation('='),
Token::Number(Ok(Number::AbstractInt(2))),
Token::Separator(';'),
],
);
}
#[test] fn test_doc_comments_module() {
sub_test_with_and_without_doc_comments( "//! Comment Module //! Another one. /*! Different module comment */ /// Trying to break module comment // Trying to break module comment again //! After a regular comment is ok. /*! Different module comment again */
//! After a break is supported. const //! After anything else is not.",
&[
Token::ModuleDocComment("//! Comment Module"),
Token::ModuleDocComment("//! Another one."),
Token::ModuleDocComment("/*! Different module comment */"),
Token::DocComment("/// Trying to break module comment"),
Token::ModuleDocComment("//! After a regular comment is ok."),
Token::ModuleDocComment("/*! Different module comment again */"),
Token::ModuleDocComment("//! After a break is supported."),
Token::Word("const"),
Token::ModuleDocComment("//! After anything else is not."),
],
);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.