Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1,703 @@
use crate::front::wgsl::parse::lexer::Token;
use crate::proc::{Alignment, ResolveError};
use crate::{SourceLocation, Span};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::files::SimpleFile;
use codespan_reporting::term;
use std::borrow::Cow;
use std::ops::Range;
use termcolor::{ColorChoice, NoColor, StandardStream};
use thiserror::Error;
#[derive(Clone, Debug)]
pub struct ParseError {
message: String,
labels: Vec<(Span, Cow<'static, str>)>,
notes: Vec<String>,
}
impl ParseError {
pub fn labels(&self) -> impl Iterator<Item = (Span, &str)> + ExactSizeIterator + '_ {
self.labels
.iter()
.map(|&(span, ref msg)| (span, msg.as_ref()))
}
pub fn message(&self) -> &str {
&self.message
}
fn diagnostic(&self) -> Diagnostic<()> {
let diagnostic = Diagnostic::error()
.with_message(self.message.to_string())
.with_labels(
self.labels
.iter()
.map(|label| {
Label::primary((), label.0.to_range().unwrap())
.with_message(label.1.to_string())
})
.collect(),
)
.with_notes(
self.notes
.iter()
.map(|note| format!("note: {note}"))
.collect(),
);
diagnostic
}
/// Emits a summary of the error to standard error stream.
pub fn emit_to_stderr(&self, source: &str) {
self.emit_to_stderr_with_path(source, "wgsl")
}
/// Emits a summary of the error to standard error stream.
pub fn emit_to_stderr_with_path(&self, source: &str, path: &str) {
let files = SimpleFile::new(path, source);
let config = codespan_reporting::term::Config::default();
let writer = StandardStream::stderr(ColorChoice::Auto);
term::emit(&mut writer.lock(), &config, &files, &self.diagnostic())
.expect("cannot write error");
}
/// Emits a summary of the error to a string.
pub fn emit_to_string(&self, source: &str) -> String {
self.emit_to_string_with_path(source, "wgsl")
}
/// Emits a summary of the error to a string.
pub fn emit_to_string_with_path(&self, source: &str, path: &str) -> String {
let files = SimpleFile::new(path, source);
let config = codespan_reporting::term::Config::default();
let mut writer = NoColor::new(Vec::new());
term::emit(&mut writer, &config, &files, &self.diagnostic()).expect("cannot write error");
String::from_utf8(writer.into_inner()).unwrap()
}
/// Returns a [`SourceLocation`] for the first label in the error message.
pub fn location(&self, source: &str) -> Option<SourceLocation> {
self.labels.get(0).map(|label| label.0.location(source))
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ExpectedToken<'a> {
Token(Token<'a>),
Identifier,
Number,
Integer,
/// Expected: constant, parenthesized expression, identifier
PrimaryExpression,
/// Expected: assignment, increment/decrement expression
Assignment,
/// Expected: 'case', 'default', '}'
SwitchItem,
/// Expected: ',', ')'
WorkgroupSizeSeparator,
/// Expected: 'struct', 'let', 'var', 'type', ';', 'fn', eof
GlobalItem,
/// Expected a type.
Type,
/// Access of `var`, `let`, `const`.
Variable,
/// Access of a function
Function,
}
#[derive(Clone, Copy, Debug, Error, PartialEq)]
pub enum NumberError {
#[error("invalid numeric literal format")]
Invalid,
#[error("numeric literal not representable by target type")]
NotRepresentable,
#[error("unimplemented f16 type")]
UnimplementedF16,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum InvalidAssignmentType {
Other,
Swizzle,
ImmutableBinding(Span),
}
#[derive(Clone, Debug)]
pub enum Error<'a> {
Unexpected(Span, ExpectedToken<'a>),
UnexpectedComponents(Span),
UnexpectedOperationInConstContext(Span),
BadNumber(Span, NumberError),
/// A negative signed integer literal where both signed and unsigned,
/// but only non-negative literals are allowed.
NegativeInt(Span),
BadU32Constant(Span),
BadMatrixScalarKind(Span, crate::ScalarKind, u8),
BadAccessor(Span),
BadTexture(Span),
BadTypeCast {
span: Span,
from_type: String,
to_type: String,
},
BadTextureSampleType {
span: Span,
kind: crate::ScalarKind,
width: u8,
},
BadIncrDecrReferenceType(Span),
InvalidResolve(ResolveError),
InvalidForInitializer(Span),
/// A break if appeared outside of a continuing block
InvalidBreakIf(Span),
InvalidGatherComponent(Span),
InvalidConstructorComponentType(Span, i32),
InvalidIdentifierUnderscore(Span),
ReservedIdentifierPrefix(Span),
UnknownAddressSpace(Span),
UnknownAttribute(Span),
UnknownBuiltin(Span),
UnknownAccess(Span),
UnknownIdent(Span, &'a str),
UnknownScalarType(Span),
UnknownType(Span),
UnknownStorageFormat(Span),
UnknownConservativeDepth(Span),
SizeAttributeTooLow(Span, u32),
AlignAttributeTooLow(Span, Alignment),
NonPowerOfTwoAlignAttribute(Span),
InconsistentBinding(Span),
TypeNotConstructible(Span),
TypeNotInferrable(Span),
InitializationTypeMismatch(Span, String, String),
MissingType(Span),
MissingAttribute(&'static str, Span),
InvalidAtomicPointer(Span),
InvalidAtomicOperandType(Span),
InvalidRayQueryPointer(Span),
Pointer(&'static str, Span),
NotPointer(Span),
NotReference(&'static str, Span),
InvalidAssignment {
span: Span,
ty: InvalidAssignmentType,
},
ReservedKeyword(Span),
/// Redefinition of an identifier (used for both module-scope and local redefinitions).
Redefinition {
/// Span of the identifier in the previous definition.
previous: Span,
/// Span of the identifier in the new definition.
current: Span,
},
/// A declaration refers to itself directly.
RecursiveDeclaration {
/// The location of the name of the declaration.
ident: Span,
/// The point at which it is used.
usage: Span,
},
/// A declaration refers to itself indirectly, through one or more other
/// definitions.
CyclicDeclaration {
/// The location of the name of some declaration in the cycle.
ident: Span,
/// The edges of the cycle of references.
///
/// Each `(decl, reference)` pair indicates that the declaration whose
/// name is `decl` has an identifier at `reference` whose definition is
/// the next declaration in the cycle. The last pair's `reference` is
/// the same identifier as `ident`, above.
path: Vec<(Span, Span)>,
},
InvalidSwitchValue {
uint: bool,
span: Span,
},
CalledEntryPoint(Span),
WrongArgumentCount {
span: Span,
expected: Range<u32>,
found: u32,
},
FunctionReturnsVoid(Span),
InvalidWorkGroupUniformLoad(Span),
Other,
ExpectedArraySize(Span),
NonPositiveArrayLength(Span),
}
impl<'a> Error<'a> {
pub(crate) fn as_parse_error(&self, source: &'a str) -> ParseError {
match *self {
Error::Unexpected(unexpected_span, expected) => {
let expected_str = match expected {
ExpectedToken::Token(token) => {
match token {
Token::Separator(c) => format!("'{c}'"),
Token::Paren(c) => format!("'{c}'"),
Token::Attribute => "@".to_string(),
Token::Number(_) => "number".to_string(),
Token::Word(s) => s.to_string(),
Token::Operation(c) => format!("operation ('{c}')"),
Token::LogicalOperation(c) => format!("logical operation ('{c}')"),
Token::ShiftOperation(c) => format!("bitshift ('{c}{c}')"),
Token::AssignmentOperation(c) if c=='<' || c=='>' => format!("bitshift ('{c}{c}=')"),
Token::AssignmentOperation(c) => format!("operation ('{c}=')"),
Token::IncrementOperation => "increment operation".to_string(),
Token::DecrementOperation => "decrement operation".to_string(),
Token::Arrow => "->".to_string(),
Token::Unknown(c) => format!("unknown ('{c}')"),
Token::Trivia => "trivia".to_string(),
Token::End => "end".to_string(),
}
}
ExpectedToken::Identifier => "identifier".to_string(),
ExpectedToken::Number => "32-bit signed integer literal".to_string(),
ExpectedToken::Integer => "unsigned/signed integer literal".to_string(),
ExpectedToken::PrimaryExpression => "expression".to_string(),
ExpectedToken::Assignment => "assignment or increment/decrement".to_string(),
ExpectedToken::SwitchItem => "switch item ('case' or 'default') or a closing curly bracket to signify the end of the switch statement ('}')".to_string(),
ExpectedToken::WorkgroupSizeSeparator => "workgroup size separator (',') or a closing parenthesis".to_string(),
ExpectedToken::GlobalItem => "global item ('struct', 'const', 'var', 'alias', ';', 'fn') or the end of the file".to_string(),
ExpectedToken::Type => "type".to_string(),
ExpectedToken::Variable => "variable access".to_string(),
ExpectedToken::Function => "function name".to_string(),
};
ParseError {
message: format!(
"expected {}, found '{}'",
expected_str, &source[unexpected_span],
),
labels: vec![(unexpected_span, format!("expected {expected_str}").into())],
notes: vec![],
}
}
Error::UnexpectedComponents(bad_span) => ParseError {
message: "unexpected components".to_string(),
labels: vec![(bad_span, "unexpected components".into())],
notes: vec![],
},
Error::UnexpectedOperationInConstContext(span) => ParseError {
message: "this operation is not supported in a const context".to_string(),
labels: vec![(span, "operation not supported here".into())],
notes: vec![],
},
Error::BadNumber(bad_span, ref err) => ParseError {
message: format!("{}: `{}`", err, &source[bad_span],),
labels: vec![(bad_span, err.to_string().into())],
notes: vec![],
},
Error::NegativeInt(bad_span) => ParseError {
message: format!(
"expected non-negative integer literal, found `{}`",
&source[bad_span],
),
labels: vec![(bad_span, "expected non-negative integer".into())],
notes: vec![],
},
Error::BadU32Constant(bad_span) => ParseError {
message: format!(
"expected unsigned integer constant expression, found `{}`",
&source[bad_span],
),
labels: vec![(bad_span, "expected unsigned integer".into())],
notes: vec![],
},
Error::BadMatrixScalarKind(span, kind, width) => ParseError {
message: format!(
"matrix scalar type must be floating-point, but found `{}`",
kind.to_wgsl(width)
),
labels: vec![(span, "must be floating-point (e.g. `f32`)".into())],
notes: vec![],
},
Error::BadAccessor(accessor_span) => ParseError {
message: format!("invalid field accessor `{}`", &source[accessor_span],),
labels: vec![(accessor_span, "invalid accessor".into())],
notes: vec![],
},
Error::UnknownIdent(ident_span, ident) => ParseError {
message: format!("no definition in scope for identifier: '{ident}'"),
labels: vec![(ident_span, "unknown identifier".into())],
notes: vec![],
},
Error::UnknownScalarType(bad_span) => ParseError {
message: format!("unknown scalar type: '{}'", &source[bad_span]),
labels: vec![(bad_span, "unknown scalar type".into())],
notes: vec!["Valid scalar types are f32, f64, i32, u32, bool".into()],
},
Error::BadTextureSampleType { span, kind, width } => ParseError {
message: format!(
"texture sample type must be one of f32, i32 or u32, but found {}",
kind.to_wgsl(width)
),
labels: vec![(span, "must be one of f32, i32 or u32".into())],
notes: vec![],
},
Error::BadIncrDecrReferenceType(span) => ParseError {
message:
"increment/decrement operation requires reference type to be one of i32 or u32"
.to_string(),
labels: vec![(span, "must be a reference type of i32 or u32".into())],
notes: vec![],
},
Error::BadTexture(bad_span) => ParseError {
message: format!(
"expected an image, but found '{}' which is not an image",
&source[bad_span]
),
labels: vec![(bad_span, "not an image".into())],
notes: vec![],
},
Error::BadTypeCast {
span,
ref from_type,
ref to_type,
} => {
let msg = format!("cannot cast a {from_type} to a {to_type}");
ParseError {
message: msg.clone(),
labels: vec![(span, msg.into())],
notes: vec![],
}
}
Error::InvalidResolve(ref resolve_error) => ParseError {
message: resolve_error.to_string(),
labels: vec![],
notes: vec![],
},
Error::InvalidForInitializer(bad_span) => ParseError {
message: format!(
"for(;;) initializer is not an assignment or a function call: '{}'",
&source[bad_span]
),
labels: vec![(bad_span, "not an assignment or function call".into())],
notes: vec![],
},
Error::InvalidBreakIf(bad_span) => ParseError {
message: "A break if is only allowed in a continuing block".to_string(),
labels: vec![(bad_span, "not in a continuing block".into())],
notes: vec![],
},
Error::InvalidGatherComponent(bad_span) => ParseError {
message: format!(
"textureGather component '{}' doesn't exist, must be 0, 1, 2, or 3",
&source[bad_span]
),
labels: vec![(bad_span, "invalid component".into())],
notes: vec![],
},
Error::InvalidConstructorComponentType(bad_span, component) => ParseError {
message: format!("invalid type for constructor component at index [{component}]"),
labels: vec![(bad_span, "invalid component type".into())],
notes: vec![],
},
Error::InvalidIdentifierUnderscore(bad_span) => ParseError {
message: "Identifier can't be '_'".to_string(),
labels: vec![(bad_span, "invalid identifier".into())],
notes: vec![
"Use phony assignment instead ('_ =' notice the absence of 'let' or 'var')"
.to_string(),
],
},
Error::ReservedIdentifierPrefix(bad_span) => ParseError {
message: format!(
"Identifier starts with a reserved prefix: '{}'",
&source[bad_span]
),
labels: vec![(bad_span, "invalid identifier".into())],
notes: vec![],
},
Error::UnknownAddressSpace(bad_span) => ParseError {
message: format!("unknown address space: '{}'", &source[bad_span]),
labels: vec![(bad_span, "unknown address space".into())],
notes: vec![],
},
Error::UnknownAttribute(bad_span) => ParseError {
message: format!("unknown attribute: '{}'", &source[bad_span]),
labels: vec![(bad_span, "unknown attribute".into())],
notes: vec![],
},
Error::UnknownBuiltin(bad_span) => ParseError {
message: format!("unknown builtin: '{}'", &source[bad_span]),
labels: vec![(bad_span, "unknown builtin".into())],
notes: vec![],
},
Error::UnknownAccess(bad_span) => ParseError {
message: format!("unknown access: '{}'", &source[bad_span]),
labels: vec![(bad_span, "unknown access".into())],
notes: vec![],
},
Error::UnknownStorageFormat(bad_span) => ParseError {
message: format!("unknown storage format: '{}'", &source[bad_span]),
labels: vec![(bad_span, "unknown storage format".into())],
notes: vec![],
},
Error::UnknownConservativeDepth(bad_span) => ParseError {
message: format!("unknown conservative depth: '{}'", &source[bad_span]),
labels: vec![(bad_span, "unknown conservative depth".into())],
notes: vec![],
},
Error::UnknownType(bad_span) => ParseError {
message: format!("unknown type: '{}'", &source[bad_span]),
labels: vec![(bad_span, "unknown type".into())],
notes: vec![],
},
Error::SizeAttributeTooLow(bad_span, min_size) => ParseError {
message: format!("struct member size must be at least {min_size}"),
labels: vec![(bad_span, format!("must be at least {min_size}").into())],
notes: vec![],
},
Error::AlignAttributeTooLow(bad_span, min_align) => ParseError {
message: format!("struct member alignment must be at least {min_align}"),
labels: vec![(bad_span, format!("must be at least {min_align}").into())],
notes: vec![],
},
Error::NonPowerOfTwoAlignAttribute(bad_span) => ParseError {
message: "struct member alignment must be a power of 2".to_string(),
labels: vec![(bad_span, "must be a power of 2".into())],
notes: vec![],
},
Error::InconsistentBinding(span) => ParseError {
message: "input/output binding is not consistent".to_string(),
labels: vec![(span, "input/output binding is not consistent".into())],
notes: vec![],
},
Error::TypeNotConstructible(span) => ParseError {
message: format!("type `{}` is not constructible", &source[span]),
labels: vec![(span, "type is not constructible".into())],
notes: vec![],
},
Error::TypeNotInferrable(span) => ParseError {
message: "type can't be inferred".to_string(),
labels: vec![(span, "type can't be inferred".into())],
notes: vec![],
},
Error::InitializationTypeMismatch(name_span, ref expected_ty, ref got_ty) => {
ParseError {
message: format!(
"the type of `{}` is expected to be `{}`, but got `{}`",
&source[name_span], expected_ty, got_ty,
),
labels: vec![(
name_span,
format!("definition of `{}`", &source[name_span]).into(),
)],
notes: vec![],
}
}
Error::MissingType(name_span) => ParseError {
message: format!("variable `{}` needs a type", &source[name_span]),
labels: vec![(
name_span,
format!("definition of `{}`", &source[name_span]).into(),
)],
notes: vec![],
},
Error::MissingAttribute(name, name_span) => ParseError {
message: format!(
"variable `{}` needs a '{}' attribute",
&source[name_span], name
),
labels: vec![(
name_span,
format!("definition of `{}`", &source[name_span]).into(),
)],
notes: vec![],
},
Error::InvalidAtomicPointer(span) => ParseError {
message: "atomic operation is done on a pointer to a non-atomic".to_string(),
labels: vec![(span, "atomic pointer is invalid".into())],
notes: vec![],
},
Error::InvalidAtomicOperandType(span) => ParseError {
message: "atomic operand type is inconsistent with the operation".to_string(),
labels: vec![(span, "atomic operand type is invalid".into())],
notes: vec![],
},
Error::InvalidRayQueryPointer(span) => ParseError {
message: "ray query operation is done on a pointer to a non-ray-query".to_string(),
labels: vec![(span, "ray query pointer is invalid".into())],
notes: vec![],
},
Error::NotPointer(span) => ParseError {
message: "the operand of the `*` operator must be a pointer".to_string(),
labels: vec![(span, "expression is not a pointer".into())],
notes: vec![],
},
Error::NotReference(what, span) => ParseError {
message: format!("{what} must be a reference"),
labels: vec![(span, "expression is not a reference".into())],
notes: vec![],
},
Error::InvalidAssignment { span, ty } => {
let (extra_label, notes) = match ty {
InvalidAssignmentType::Swizzle => (
None,
vec![
"WGSL does not support assignments to swizzles".into(),
"consider assigning each component individually".into(),
],
),
InvalidAssignmentType::ImmutableBinding(binding_span) => (
Some((binding_span, "this is an immutable binding".into())),
vec![format!(
"consider declaring '{}' with `var` instead of `let`",
&source[binding_span]
)],
),
InvalidAssignmentType::Other => (None, vec![]),
};
ParseError {
message: "invalid left-hand side of assignment".into(),
labels: std::iter::once((span, "cannot assign to this expression".into()))
.chain(extra_label)
.collect(),
notes,
}
}
Error::Pointer(what, span) => ParseError {
message: format!("{what} must not be a pointer"),
labels: vec![(span, "expression is a pointer".into())],
notes: vec![],
},
Error::ReservedKeyword(name_span) => ParseError {
message: format!("name `{}` is a reserved keyword", &source[name_span]),
labels: vec![(
name_span,
format!("definition of `{}`", &source[name_span]).into(),
)],
notes: vec![],
},
Error::Redefinition { previous, current } => ParseError {
message: format!("redefinition of `{}`", &source[current]),
labels: vec![
(
current,
format!("redefinition of `{}`", &source[current]).into(),
),
(
previous,
format!("previous definition of `{}`", &source[previous]).into(),
),
],
notes: vec![],
},
Error::RecursiveDeclaration { ident, usage } => ParseError {
message: format!("declaration of `{}` is recursive", &source[ident]),
labels: vec![(ident, "".into()), (usage, "uses itself here".into())],
notes: vec![],
},
Error::CyclicDeclaration { ident, ref path } => ParseError {
message: format!("declaration of `{}` is cyclic", &source[ident]),
labels: path
.iter()
.enumerate()
.flat_map(|(i, &(ident, usage))| {
[
(ident, "".into()),
(
usage,
if i == path.len() - 1 {
"ending the cycle".into()
} else {
format!("uses `{}`", &source[ident]).into()
},
),
]
})
.collect(),
notes: vec![],
},
Error::InvalidSwitchValue { uint, span } => ParseError {
message: "invalid switch value".to_string(),
labels: vec![(
span,
if uint {
"expected unsigned integer"
} else {
"expected signed integer"
}
.into(),
)],
notes: vec![if uint {
format!("suffix the integer with a `u`: '{}u'", &source[span])
} else {
let span = span.to_range().unwrap();
format!(
"remove the `u` suffix: '{}'",
&source[span.start..span.end - 1]
)
}],
},
Error::CalledEntryPoint(span) => ParseError {
message: "entry point cannot be called".to_string(),
labels: vec![(span, "entry point cannot be called".into())],
notes: vec![],
},
Error::WrongArgumentCount {
span,
ref expected,
found,
} => ParseError {
message: format!(
"wrong number of arguments: expected {}, found {}",
if expected.len() < 2 {
format!("{}", expected.start)
} else {
format!("{}..{}", expected.start, expected.end)
},
found
),
labels: vec![(span, "wrong number of arguments".into())],
notes: vec![],
},
Error::FunctionReturnsVoid(span) => ParseError {
message: "function does not return any value".to_string(),
labels: vec![(span, "".into())],
notes: vec![
"perhaps you meant to call the function in a separate statement?".into(),
],
},
Error::InvalidWorkGroupUniformLoad(span) => ParseError {
message: "incorrect type passed to workgroupUniformLoad".into(),
labels: vec![(span, "".into())],
notes: vec!["passed type must be a workgroup pointer".into()],
},
Error::Other => ParseError {
message: "other error".to_string(),
labels: vec![],
notes: vec![],
},
Error::ExpectedArraySize(span) => ParseError {
message: "array element count must resolve to an integer scalar (u32 or i32)"
.to_string(),
labels: vec![(span, "must resolve to u32/i32".into())],
notes: vec![],
},
Error::NonPositiveArrayLength(span) => ParseError {
message: "array element count must be greater than zero".to_string(),
labels: vec![(span, "must be greater than zero".into())],
notes: vec![],
},
}
}
}

View file

@ -0,0 +1,193 @@
use super::Error;
use crate::front::wgsl::parse::ast;
use crate::{FastHashMap, Handle, Span};
/// A `GlobalDecl` list in which each definition occurs before all its uses.
pub struct Index<'a> {
dependency_order: Vec<Handle<ast::GlobalDecl<'a>>>,
}
impl<'a> Index<'a> {
/// Generate an `Index` for the given translation unit.
///
/// Perform a topological sort on `tu`'s global declarations, placing
/// referents before the definitions that refer to them.
///
/// Return an error if the graph of references between declarations contains
/// any cycles.
pub fn generate(tu: &ast::TranslationUnit<'a>) -> Result<Self, Error<'a>> {
// Produce a map from global definitions' names to their `Handle<GlobalDecl>`s.
// While doing so, reject conflicting definitions.
let mut globals = FastHashMap::with_capacity_and_hasher(tu.decls.len(), Default::default());
for (handle, decl) in tu.decls.iter() {
let ident = decl_ident(decl);
let name = ident.name;
if let Some(old) = globals.insert(name, handle) {
return Err(Error::Redefinition {
previous: decl_ident(&tu.decls[old]).span,
current: ident.span,
});
}
}
let len = tu.decls.len();
let solver = DependencySolver {
globals: &globals,
module: tu,
visited: vec![false; len],
temp_visited: vec![false; len],
path: Vec::new(),
out: Vec::with_capacity(len),
};
let dependency_order = solver.solve()?;
Ok(Self { dependency_order })
}
/// Iterate over `GlobalDecl`s, visiting each definition before all its uses.
///
/// Produce handles for all of the `GlobalDecl`s of the `TranslationUnit`
/// passed to `Index::generate`, ordered so that a given declaration is
/// produced before any other declaration that uses it.
pub fn visit_ordered(&self) -> impl Iterator<Item = Handle<ast::GlobalDecl<'a>>> + '_ {
self.dependency_order.iter().copied()
}
}
/// An edge from a reference to its referent in the current depth-first
/// traversal.
///
/// This is like `ast::Dependency`, except that we've determined which
/// `GlobalDecl` it refers to.
struct ResolvedDependency<'a> {
/// The referent of some identifier used in the current declaration.
decl: Handle<ast::GlobalDecl<'a>>,
/// Where that use occurs within the current declaration.
usage: Span,
}
/// Local state for ordering a `TranslationUnit`'s module-scope declarations.
///
/// Values of this type are used temporarily by `Index::generate`
/// to perform a depth-first sort on the declarations.
/// Technically, what we want is a topological sort, but a depth-first sort
/// has one key benefit - it's much more efficient in storing
/// the path of each node for error generation.
struct DependencySolver<'source, 'temp> {
/// A map from module-scope definitions' names to their handles.
globals: &'temp FastHashMap<&'source str, Handle<ast::GlobalDecl<'source>>>,
/// The translation unit whose declarations we're ordering.
module: &'temp ast::TranslationUnit<'source>,
/// For each handle, whether we have pushed it onto `out` yet.
visited: Vec<bool>,
/// For each handle, whether it is an predecessor in the current depth-first
/// traversal. This is used to detect cycles in the reference graph.
temp_visited: Vec<bool>,
/// The current path in our depth-first traversal. Used for generating
/// error messages for non-trivial reference cycles.
path: Vec<ResolvedDependency<'source>>,
/// The list of declaration handles, with declarations before uses.
out: Vec<Handle<ast::GlobalDecl<'source>>>,
}
impl<'a> DependencySolver<'a, '_> {
/// Produce the sorted list of declaration handles, and check for cycles.
fn solve(mut self) -> Result<Vec<Handle<ast::GlobalDecl<'a>>>, Error<'a>> {
for (id, _) in self.module.decls.iter() {
if self.visited[id.index()] {
continue;
}
self.dfs(id)?;
}
Ok(self.out)
}
/// Ensure that all declarations used by `id` have been added to the
/// ordering, and then append `id` itself.
fn dfs(&mut self, id: Handle<ast::GlobalDecl<'a>>) -> Result<(), Error<'a>> {
let decl = &self.module.decls[id];
let id_usize = id.index();
self.temp_visited[id_usize] = true;
for dep in decl.dependencies.iter() {
if let Some(&dep_id) = self.globals.get(dep.ident) {
self.path.push(ResolvedDependency {
decl: dep_id,
usage: dep.usage,
});
let dep_id_usize = dep_id.index();
if self.temp_visited[dep_id_usize] {
// Found a cycle.
return if dep_id == id {
// A declaration refers to itself directly.
Err(Error::RecursiveDeclaration {
ident: decl_ident(decl).span,
usage: dep.usage,
})
} else {
// A declaration refers to itself indirectly, through
// one or more other definitions. Report the entire path
// of references.
let start_at = self
.path
.iter()
.rev()
.enumerate()
.find_map(|(i, dep)| (dep.decl == dep_id).then_some(i))
.unwrap_or(0);
Err(Error::CyclicDeclaration {
ident: decl_ident(&self.module.decls[dep_id]).span,
path: self.path[start_at..]
.iter()
.map(|curr_dep| {
let curr_id = curr_dep.decl;
let curr_decl = &self.module.decls[curr_id];
(decl_ident(curr_decl).span, curr_dep.usage)
})
.collect(),
})
};
} else if !self.visited[dep_id_usize] {
self.dfs(dep_id)?;
}
// Remove this edge from the current path.
self.path.pop();
}
// Ignore unresolved identifiers; they may be predeclared objects.
}
// Remove this node from the current path.
self.temp_visited[id_usize] = false;
// Now everything this declaration uses has been visited, and is already
// present in `out`. That means we we can append this one to the
// ordering, and mark it as visited.
self.out.push(id);
self.visited[id_usize] = true;
Ok(())
}
}
const fn decl_ident<'a>(decl: &ast::GlobalDecl<'a>) -> ast::Ident<'a> {
match decl.kind {
ast::GlobalDeclKind::Fn(ref f) => f.name,
ast::GlobalDeclKind::Var(ref v) => v.name,
ast::GlobalDeclKind::Const(ref c) => c.name,
ast::GlobalDeclKind::Struct(ref s) => s.name,
ast::GlobalDeclKind::Type(ref t) => t.name,
}
}

View file

@ -0,0 +1,606 @@
use std::num::NonZeroU32;
use crate::front::wgsl::parse::ast;
use crate::{Handle, Span};
use crate::front::wgsl::error::Error;
use crate::front::wgsl::lower::{ExpressionContext, Lowerer};
use crate::proc::TypeResolution;
enum ConcreteConstructorHandle {
PartialVector {
size: crate::VectorSize,
},
PartialMatrix {
columns: crate::VectorSize,
rows: crate::VectorSize,
},
PartialArray,
Type(Handle<crate::Type>),
}
impl ConcreteConstructorHandle {
fn borrow<'a>(&self, module: &'a crate::Module) -> ConcreteConstructor<'a> {
match *self {
Self::PartialVector { size } => ConcreteConstructor::PartialVector { size },
Self::PartialMatrix { columns, rows } => {
ConcreteConstructor::PartialMatrix { columns, rows }
}
Self::PartialArray => ConcreteConstructor::PartialArray,
Self::Type(handle) => ConcreteConstructor::Type(handle, &module.types[handle].inner),
}
}
}
enum ConcreteConstructor<'a> {
PartialVector {
size: crate::VectorSize,
},
PartialMatrix {
columns: crate::VectorSize,
rows: crate::VectorSize,
},
PartialArray,
Type(Handle<crate::Type>, &'a crate::TypeInner),
}
impl ConcreteConstructorHandle {
fn to_error_string(&self, ctx: ExpressionContext) -> String {
match *self {
Self::PartialVector { size } => {
format!("vec{}<?>", size as u32,)
}
Self::PartialMatrix { columns, rows } => {
format!("mat{}x{}<?>", columns as u32, rows as u32,)
}
Self::PartialArray => "array<?, ?>".to_string(),
Self::Type(ty) => ctx.format_type(ty),
}
}
}
enum ComponentsHandle<'a> {
None,
One {
component: Handle<crate::Expression>,
span: Span,
ty: &'a TypeResolution,
},
Many {
components: Vec<Handle<crate::Expression>>,
spans: Vec<Span>,
first_component_ty: &'a TypeResolution,
},
}
impl<'a> ComponentsHandle<'a> {
fn borrow(self, module: &'a crate::Module) -> Components<'a> {
match self {
Self::None => Components::None,
Self::One {
component,
span,
ty,
} => Components::One {
component,
span,
ty_inner: ty.inner_with(&module.types),
},
Self::Many {
components,
spans,
first_component_ty,
} => Components::Many {
components,
spans,
first_component_ty_inner: first_component_ty.inner_with(&module.types),
},
}
}
}
enum Components<'a> {
None,
One {
component: Handle<crate::Expression>,
span: Span,
ty_inner: &'a crate::TypeInner,
},
Many {
components: Vec<Handle<crate::Expression>>,
spans: Vec<Span>,
first_component_ty_inner: &'a crate::TypeInner,
},
}
impl Components<'_> {
fn into_components_vec(self) -> Vec<Handle<crate::Expression>> {
match self {
Self::None => vec![],
Self::One { component, .. } => vec![component],
Self::Many { components, .. } => components,
}
}
}
impl<'source, 'temp> Lowerer<'source, 'temp> {
/// Generate Naga IR for a type constructor expression.
///
/// The `constructor` value represents the head of the constructor
/// expression, which is at least a hint of which type is being built; if
/// it's one of the `Partial` variants, we need to consider the argument
/// types as well.
///
/// This is used for [`Construct`] expressions, but also for [`Call`]
/// expressions, once we've determined that the "callable" (in WGSL spec
/// terms) is actually a type.
///
/// [`Construct`]: ast::Expression::Construct
/// [`Call`]: ast::Expression::Call
pub fn construct(
&mut self,
span: Span,
constructor: &ast::ConstructorType<'source>,
ty_span: Span,
components: &[Handle<ast::Expression<'source>>],
mut ctx: ExpressionContext<'source, '_, '_>,
) -> Result<Handle<crate::Expression>, Error<'source>> {
let constructor_h = self.constructor(constructor, ctx.reborrow())?;
let components_h = match *components {
[] => ComponentsHandle::None,
[component] => {
let span = ctx.ast_expressions.get_span(component);
let component = self.expression(component, ctx.reborrow())?;
ctx.grow_types(component)?;
let ty = &ctx.typifier()[component];
ComponentsHandle::One {
component,
span,
ty,
}
}
[component, ref rest @ ..] => {
let span = ctx.ast_expressions.get_span(component);
let component = self.expression(component, ctx.reborrow())?;
let components = std::iter::once(Ok(component))
.chain(
rest.iter()
.map(|&component| self.expression(component, ctx.reborrow())),
)
.collect::<Result<_, _>>()?;
let spans = std::iter::once(span)
.chain(
rest.iter()
.map(|&component| ctx.ast_expressions.get_span(component)),
)
.collect();
ctx.grow_types(component)?;
let ty = &ctx.typifier()[component];
ComponentsHandle::Many {
components,
spans,
first_component_ty: ty,
}
}
};
let (components, constructor) = (
components_h.borrow(ctx.module),
constructor_h.borrow(ctx.module),
);
let expr = match (components, constructor) {
// Empty constructor
(Components::None, dst_ty) => match dst_ty {
ConcreteConstructor::Type(ty, _) => {
return Ok(ctx.interrupt_emitter(crate::Expression::ZeroValue(ty), span))
}
_ => return Err(Error::TypeNotInferrable(ty_span)),
},
// Scalar constructor & conversion (scalar -> scalar)
(
Components::One {
component,
ty_inner: &crate::TypeInner::Scalar { .. },
..
},
ConcreteConstructor::Type(_, &crate::TypeInner::Scalar { kind, width }),
) => crate::Expression::As {
expr: component,
kind,
convert: Some(width),
},
// Vector conversion (vector -> vector)
(
Components::One {
component,
ty_inner: &crate::TypeInner::Vector { size: src_size, .. },
..
},
ConcreteConstructor::Type(
_,
&crate::TypeInner::Vector {
size: dst_size,
kind: dst_kind,
width: dst_width,
},
),
) if dst_size == src_size => crate::Expression::As {
expr: component,
kind: dst_kind,
convert: Some(dst_width),
},
// Vector conversion (vector -> vector) - partial
(
Components::One {
component,
ty_inner:
&crate::TypeInner::Vector {
size: src_size,
kind: src_kind,
..
},
..
},
ConcreteConstructor::PartialVector { size: dst_size },
) if dst_size == src_size => crate::Expression::As {
expr: component,
kind: src_kind,
convert: None,
},
// Matrix conversion (matrix -> matrix)
(
Components::One {
component,
ty_inner:
&crate::TypeInner::Matrix {
columns: src_columns,
rows: src_rows,
..
},
..
},
ConcreteConstructor::Type(
_,
&crate::TypeInner::Matrix {
columns: dst_columns,
rows: dst_rows,
width: dst_width,
},
),
) if dst_columns == src_columns && dst_rows == src_rows => crate::Expression::As {
expr: component,
kind: crate::ScalarKind::Float,
convert: Some(dst_width),
},
// Matrix conversion (matrix -> matrix) - partial
(
Components::One {
component,
ty_inner:
&crate::TypeInner::Matrix {
columns: src_columns,
rows: src_rows,
..
},
..
},
ConcreteConstructor::PartialMatrix {
columns: dst_columns,
rows: dst_rows,
},
) if dst_columns == src_columns && dst_rows == src_rows => crate::Expression::As {
expr: component,
kind: crate::ScalarKind::Float,
convert: None,
},
// Vector constructor (splat) - infer type
(
Components::One {
component,
ty_inner: &crate::TypeInner::Scalar { .. },
..
},
ConcreteConstructor::PartialVector { size },
) => crate::Expression::Splat {
size,
value: component,
},
// Vector constructor (splat)
(
Components::One {
component,
ty_inner:
&crate::TypeInner::Scalar {
kind: src_kind,
width: src_width,
..
},
..
},
ConcreteConstructor::Type(
_,
&crate::TypeInner::Vector {
size,
kind: dst_kind,
width: dst_width,
},
),
) if dst_kind == src_kind || dst_width == src_width => crate::Expression::Splat {
size,
value: component,
},
// Vector constructor (by elements)
(
Components::Many {
components,
first_component_ty_inner:
&crate::TypeInner::Scalar { kind, width }
| &crate::TypeInner::Vector { kind, width, .. },
..
},
ConcreteConstructor::PartialVector { size },
)
| (
Components::Many {
components,
first_component_ty_inner:
&crate::TypeInner::Scalar { .. } | &crate::TypeInner::Vector { .. },
..
},
ConcreteConstructor::Type(_, &crate::TypeInner::Vector { size, width, kind }),
) => {
let inner = crate::TypeInner::Vector { size, kind, width };
let ty = ctx.ensure_type_exists(inner);
crate::Expression::Compose { ty, components }
}
// Matrix constructor (by elements)
(
Components::Many {
components,
first_component_ty_inner: &crate::TypeInner::Scalar { width, .. },
..
},
ConcreteConstructor::PartialMatrix { columns, rows },
)
| (
Components::Many {
components,
first_component_ty_inner: &crate::TypeInner::Scalar { .. },
..
},
ConcreteConstructor::Type(
_,
&crate::TypeInner::Matrix {
columns,
rows,
width,
},
),
) => {
let vec_ty = ctx.ensure_type_exists(crate::TypeInner::Vector {
width,
kind: crate::ScalarKind::Float,
size: rows,
});
let components = components
.chunks(rows as usize)
.map(|vec_components| {
ctx.append_expression(
crate::Expression::Compose {
ty: vec_ty,
components: Vec::from(vec_components),
},
Default::default(),
)
})
.collect();
let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix {
columns,
rows,
width,
});
crate::Expression::Compose { ty, components }
}
// Matrix constructor (by columns)
(
Components::Many {
components,
first_component_ty_inner: &crate::TypeInner::Vector { width, .. },
..
},
ConcreteConstructor::PartialMatrix { columns, rows },
)
| (
Components::Many {
components,
first_component_ty_inner: &crate::TypeInner::Vector { .. },
..
},
ConcreteConstructor::Type(
_,
&crate::TypeInner::Matrix {
columns,
rows,
width,
},
),
) => {
let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix {
columns,
rows,
width,
});
crate::Expression::Compose { ty, components }
}
// Array constructor - infer type
(components, ConcreteConstructor::PartialArray) => {
let components = components.into_components_vec();
let base = ctx.register_type(components[0])?;
let inner = crate::TypeInner::Array {
base,
size: crate::ArraySize::Constant(
NonZeroU32::new(u32::try_from(components.len()).unwrap()).unwrap(),
),
stride: {
self.layouter.update(ctx.module.to_ctx()).unwrap();
self.layouter[base].to_stride()
},
};
let ty = ctx.ensure_type_exists(inner);
crate::Expression::Compose { ty, components }
}
// Array constructor
(components, ConcreteConstructor::Type(ty, &crate::TypeInner::Array { .. })) => {
let components = components.into_components_vec();
crate::Expression::Compose { ty, components }
}
// Struct constructor
(components, ConcreteConstructor::Type(ty, &crate::TypeInner::Struct { .. })) => {
crate::Expression::Compose {
ty,
components: components.into_components_vec(),
}
}
// ERRORS
// Bad conversion (type cast)
(Components::One { span, ty_inner, .. }, _) => {
let from_type = ctx.format_typeinner(ty_inner);
return Err(Error::BadTypeCast {
span,
from_type,
to_type: constructor_h.to_error_string(ctx.reborrow()),
});
}
// Too many parameters for scalar constructor
(
Components::Many { spans, .. },
ConcreteConstructor::Type(_, &crate::TypeInner::Scalar { .. }),
) => {
let span = spans[1].until(spans.last().unwrap());
return Err(Error::UnexpectedComponents(span));
}
// Parameters are of the wrong type for vector or matrix constructor
(
Components::Many { spans, .. },
ConcreteConstructor::Type(
_,
&crate::TypeInner::Vector { .. } | &crate::TypeInner::Matrix { .. },
)
| ConcreteConstructor::PartialVector { .. }
| ConcreteConstructor::PartialMatrix { .. },
) => {
return Err(Error::InvalidConstructorComponentType(spans[0], 0));
}
// Other types can't be constructed
_ => return Err(Error::TypeNotConstructible(ty_span)),
};
let expr = ctx.append_expression(expr, span);
Ok(expr)
}
/// Build a Naga IR [`Type`] for `constructor` if there is enough
/// information to do so.
///
/// For `Partial` variants of [`ast::ConstructorType`], we don't know the
/// component type, so in that case we return the appropriate `Partial`
/// variant of [`ConcreteConstructorHandle`].
///
/// But for the other `ConstructorType` variants, we have everything we need
/// to know to actually produce a Naga IR type. In this case we add to/find
/// in [`ctx.module`] a suitable Naga `Type` and return a
/// [`ConcreteConstructorHandle::Type`] value holding its handle.
///
/// Note that constructing an [`Array`] type may require inserting
/// [`Constant`]s as well as `Type`s into `ctx.module`, to represent the
/// array's length.
///
/// [`Type`]: crate::Type
/// [`ctx.module`]: ExpressionContext::module
/// [`Array`]: crate::TypeInner::Array
/// [`Constant`]: crate::Constant
fn constructor<'out>(
&mut self,
constructor: &ast::ConstructorType<'source>,
mut ctx: ExpressionContext<'source, '_, 'out>,
) -> Result<ConcreteConstructorHandle, Error<'source>> {
let c = match *constructor {
ast::ConstructorType::Scalar { width, kind } => {
let ty = ctx.ensure_type_exists(crate::TypeInner::Scalar { width, kind });
ConcreteConstructorHandle::Type(ty)
}
ast::ConstructorType::PartialVector { size } => {
ConcreteConstructorHandle::PartialVector { size }
}
ast::ConstructorType::Vector { size, kind, width } => {
let ty = ctx.ensure_type_exists(crate::TypeInner::Vector { size, kind, width });
ConcreteConstructorHandle::Type(ty)
}
ast::ConstructorType::PartialMatrix { rows, columns } => {
ConcreteConstructorHandle::PartialMatrix { rows, columns }
}
ast::ConstructorType::Matrix {
rows,
columns,
width,
} => {
let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix {
columns,
rows,
width,
});
ConcreteConstructorHandle::Type(ty)
}
ast::ConstructorType::PartialArray => ConcreteConstructorHandle::PartialArray,
ast::ConstructorType::Array { base, size } => {
let base = self.resolve_ast_type(base, ctx.as_global())?;
let size = match size {
ast::ArraySize::Constant(expr) => {
let const_expr = self.expression(expr, ctx.as_const())?;
crate::ArraySize::Constant(ctx.array_length(const_expr)?)
}
ast::ArraySize::Dynamic => crate::ArraySize::Dynamic,
};
self.layouter.update(ctx.module.to_ctx()).unwrap();
let ty = ctx.ensure_type_exists(crate::TypeInner::Array {
base,
size,
stride: self.layouter[base].to_stride(),
});
ConcreteConstructorHandle::Type(ty)
}
ast::ConstructorType::Type(ty) => ConcreteConstructorHandle::Type(ty),
};
Ok(c)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,301 @@
/*!
Frontend for [WGSL][wgsl] (WebGPU Shading Language).
[wgsl]: https://gpuweb.github.io/gpuweb/wgsl.html
*/
mod error;
mod index;
mod lower;
mod parse;
#[cfg(test)]
mod tests;
use crate::front::wgsl::error::Error;
use crate::front::wgsl::parse::Parser;
use thiserror::Error;
pub use crate::front::wgsl::error::ParseError;
use crate::front::wgsl::lower::Lowerer;
pub struct Frontend {
parser: Parser,
}
impl Frontend {
pub const fn new() -> Self {
Self {
parser: Parser::new(),
}
}
pub fn parse(&mut self, source: &str) -> Result<crate::Module, ParseError> {
self.inner(source).map_err(|x| x.as_parse_error(source))
}
fn inner<'a>(&mut self, source: &'a str) -> Result<crate::Module, Error<'a>> {
let tu = self.parser.parse(source)?;
let index = index::Index::generate(&tu)?;
let module = Lowerer::new(&index).lower(&tu)?;
Ok(module)
}
}
pub fn parse_str(source: &str) -> Result<crate::Module, ParseError> {
Frontend::new().parse(source)
}
impl crate::StorageFormat {
const fn to_wgsl(self) -> &'static str {
use crate::StorageFormat as Sf;
match self {
Sf::R8Unorm => "r8unorm",
Sf::R8Snorm => "r8snorm",
Sf::R8Uint => "r8uint",
Sf::R8Sint => "r8sint",
Sf::R16Uint => "r16uint",
Sf::R16Sint => "r16sint",
Sf::R16Float => "r16float",
Sf::Rg8Unorm => "rg8unorm",
Sf::Rg8Snorm => "rg8snorm",
Sf::Rg8Uint => "rg8uint",
Sf::Rg8Sint => "rg8sint",
Sf::R32Uint => "r32uint",
Sf::R32Sint => "r32sint",
Sf::R32Float => "r32float",
Sf::Rg16Uint => "rg16uint",
Sf::Rg16Sint => "rg16sint",
Sf::Rg16Float => "rg16float",
Sf::Rgba8Unorm => "rgba8unorm",
Sf::Rgba8Snorm => "rgba8snorm",
Sf::Rgba8Uint => "rgba8uint",
Sf::Rgba8Sint => "rgba8sint",
Sf::Rgb10a2Unorm => "rgb10a2unorm",
Sf::Rg11b10Float => "rg11b10float",
Sf::Rg32Uint => "rg32uint",
Sf::Rg32Sint => "rg32sint",
Sf::Rg32Float => "rg32float",
Sf::Rgba16Uint => "rgba16uint",
Sf::Rgba16Sint => "rgba16sint",
Sf::Rgba16Float => "rgba16float",
Sf::Rgba32Uint => "rgba32uint",
Sf::Rgba32Sint => "rgba32sint",
Sf::Rgba32Float => "rgba32float",
Sf::R16Unorm => "r16unorm",
Sf::R16Snorm => "r16snorm",
Sf::Rg16Unorm => "rg16unorm",
Sf::Rg16Snorm => "rg16snorm",
Sf::Rgba16Unorm => "rgba16unorm",
Sf::Rgba16Snorm => "rgba16snorm",
}
}
}
impl crate::TypeInner {
/// Formats the type as it is written in wgsl.
///
/// For example `vec3<f32>`.
///
/// Note: The names of a `TypeInner::Struct` is not known. Therefore this method will simply return "struct" for them.
fn to_wgsl(&self, gctx: crate::proc::GlobalCtx) -> String {
use crate::TypeInner as Ti;
match *self {
Ti::Scalar { kind, width } => kind.to_wgsl(width),
Ti::Vector { size, kind, width } => {
format!("vec{}<{}>", size as u32, kind.to_wgsl(width))
}
Ti::Matrix {
columns,
rows,
width,
} => {
format!(
"mat{}x{}<{}>",
columns as u32,
rows as u32,
crate::ScalarKind::Float.to_wgsl(width),
)
}
Ti::Atomic { kind, width } => {
format!("atomic<{}>", kind.to_wgsl(width))
}
Ti::Pointer { base, .. } => {
let base = &gctx.types[base];
let name = base.name.as_deref().unwrap_or("unknown");
format!("ptr<{name}>")
}
Ti::ValuePointer { kind, width, .. } => {
format!("ptr<{}>", kind.to_wgsl(width))
}
Ti::Array { base, size, .. } => {
let member_type = &gctx.types[base];
let base = member_type.name.as_deref().unwrap_or("unknown");
match size {
crate::ArraySize::Constant(size) => format!("array<{base}, {size}>"),
crate::ArraySize::Dynamic => format!("array<{base}>"),
}
}
Ti::Struct { .. } => {
// TODO: Actually output the struct?
"struct".to_string()
}
Ti::Image {
dim,
arrayed,
class,
} => {
let dim_suffix = match dim {
crate::ImageDimension::D1 => "_1d",
crate::ImageDimension::D2 => "_2d",
crate::ImageDimension::D3 => "_3d",
crate::ImageDimension::Cube => "_cube",
};
let array_suffix = if arrayed { "_array" } else { "" };
let class_suffix = match class {
crate::ImageClass::Sampled { multi: true, .. } => "_multisampled",
crate::ImageClass::Depth { multi: false } => "_depth",
crate::ImageClass::Depth { multi: true } => "_depth_multisampled",
crate::ImageClass::Sampled { multi: false, .. }
| crate::ImageClass::Storage { .. } => "",
};
let type_in_brackets = match class {
crate::ImageClass::Sampled { kind, .. } => {
// Note: The only valid widths are 4 bytes wide.
// The lexer has already verified this, so we can safely assume it here.
// https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type
let element_type = kind.to_wgsl(4);
format!("<{element_type}>")
}
crate::ImageClass::Depth { multi: _ } => String::new(),
crate::ImageClass::Storage { format, access } => {
if access.contains(crate::StorageAccess::STORE) {
format!("<{},write>", format.to_wgsl())
} else {
format!("<{}>", format.to_wgsl())
}
}
};
format!("texture{class_suffix}{dim_suffix}{array_suffix}{type_in_brackets}")
}
Ti::Sampler { .. } => "sampler".to_string(),
Ti::AccelerationStructure => "acceleration_structure".to_string(),
Ti::RayQuery => "ray_query".to_string(),
Ti::BindingArray { base, size, .. } => {
let member_type = &gctx.types[base];
let base = member_type.name.as_deref().unwrap_or("unknown");
match size {
crate::ArraySize::Constant(size) => format!("binding_array<{base}, {size}>"),
crate::ArraySize::Dynamic => format!("binding_array<{base}>"),
}
}
}
}
}
mod type_inner_tests {
#[test]
fn to_wgsl() {
use std::num::NonZeroU32;
let mut types = crate::UniqueArena::new();
let mytype1 = types.insert(
crate::Type {
name: Some("MyType1".to_string()),
inner: crate::TypeInner::Struct {
members: vec![],
span: 0,
},
},
Default::default(),
);
let mytype2 = types.insert(
crate::Type {
name: Some("MyType2".to_string()),
inner: crate::TypeInner::Struct {
members: vec![],
span: 0,
},
},
Default::default(),
);
let gctx = crate::proc::GlobalCtx {
types: &types,
constants: &crate::Arena::new(),
const_expressions: &crate::Arena::new(),
};
let array = crate::TypeInner::Array {
base: mytype1,
stride: 4,
size: crate::ArraySize::Constant(unsafe { NonZeroU32::new_unchecked(32) }),
};
assert_eq!(array.to_wgsl(gctx), "array<MyType1, 32>");
let mat = crate::TypeInner::Matrix {
rows: crate::VectorSize::Quad,
columns: crate::VectorSize::Bi,
width: 8,
};
assert_eq!(mat.to_wgsl(gctx), "mat2x4<f64>");
let ptr = crate::TypeInner::Pointer {
base: mytype2,
space: crate::AddressSpace::Storage {
access: crate::StorageAccess::default(),
},
};
assert_eq!(ptr.to_wgsl(gctx), "ptr<MyType2>");
let img1 = crate::TypeInner::Image {
dim: crate::ImageDimension::D2,
arrayed: false,
class: crate::ImageClass::Sampled {
kind: crate::ScalarKind::Float,
multi: true,
},
};
assert_eq!(img1.to_wgsl(gctx), "texture_multisampled_2d<f32>");
let img2 = crate::TypeInner::Image {
dim: crate::ImageDimension::Cube,
arrayed: true,
class: crate::ImageClass::Depth { multi: false },
};
assert_eq!(img2.to_wgsl(gctx), "texture_depth_cube_array");
let img3 = crate::TypeInner::Image {
dim: crate::ImageDimension::D2,
arrayed: false,
class: crate::ImageClass::Depth { multi: true },
};
assert_eq!(img3.to_wgsl(gctx), "texture_depth_multisampled_2d");
let array = crate::TypeInner::BindingArray {
base: mytype1,
size: crate::ArraySize::Constant(unsafe { NonZeroU32::new_unchecked(32) }),
};
assert_eq!(array.to_wgsl(gctx), "binding_array<MyType1, 32>");
}
}
impl crate::ScalarKind {
/// Format a scalar kind+width as a type is written in wgsl.
///
/// Examples: `f32`, `u64`, `bool`.
fn to_wgsl(self, width: u8) -> String {
let prefix = match self {
crate::ScalarKind::Sint => "i",
crate::ScalarKind::Uint => "u",
crate::ScalarKind::Float => "f",
crate::ScalarKind::Bool => return "bool".to_string(),
};
format!("{}{}", prefix, width * 8)
}
}

View file

@ -0,0 +1,486 @@
use crate::front::wgsl::parse::number::Number;
use crate::{Arena, FastHashSet, Handle, Span};
use std::hash::Hash;
#[derive(Debug, Default)]
pub struct TranslationUnit<'a> {
pub decls: Arena<GlobalDecl<'a>>,
/// The common expressions arena for the entire translation unit.
///
/// All functions, global initializers, array lengths, etc. store their
/// expressions here. We apportion these out to individual Naga
/// [`Function`]s' expression arenas at lowering time. Keeping them all in a
/// single arena simplifies handling of things like array lengths (which are
/// effectively global and thus don't clearly belong to any function) and
/// initializers (which can appear in both function-local and module-scope
/// contexts).
///
/// [`Function`]: crate::Function
pub expressions: Arena<Expression<'a>>,
/// Non-user-defined types, like `vec4<f32>` or `array<i32, 10>`.
///
/// These are referred to by `Handle<ast::Type<'a>>` values.
/// User-defined types are referred to by name until lowering.
pub types: Arena<Type<'a>>,
}
#[derive(Debug, Clone, Copy)]
pub struct Ident<'a> {
pub name: &'a str,
pub span: Span,
}
#[derive(Debug)]
pub enum IdentExpr<'a> {
Unresolved(&'a str),
Local(Handle<Local>),
}
/// A reference to a module-scope definition or predeclared object.
///
/// Each [`GlobalDecl`] holds a set of these values, to be resolved to
/// specific definitions later. To support de-duplication, `Eq` and
/// `Hash` on a `Dependency` value consider only the name, not the
/// source location at which the reference occurs.
#[derive(Debug)]
pub struct Dependency<'a> {
/// The name referred to.
pub ident: &'a str,
/// The location at which the reference to that name occurs.
pub usage: Span,
}
impl Hash for Dependency<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.ident.hash(state);
}
}
impl PartialEq for Dependency<'_> {
fn eq(&self, other: &Self) -> bool {
self.ident == other.ident
}
}
impl Eq for Dependency<'_> {}
/// A module-scope declaration.
#[derive(Debug)]
pub struct GlobalDecl<'a> {
pub kind: GlobalDeclKind<'a>,
/// Names of all module-scope or predeclared objects this
/// declaration uses.
pub dependencies: FastHashSet<Dependency<'a>>,
}
#[derive(Debug)]
pub enum GlobalDeclKind<'a> {
Fn(Function<'a>),
Var(GlobalVariable<'a>),
Const(Const<'a>),
Struct(Struct<'a>),
Type(TypeAlias<'a>),
}
#[derive(Debug)]
pub struct FunctionArgument<'a> {
pub name: Ident<'a>,
pub ty: Handle<Type<'a>>,
pub binding: Option<crate::Binding>,
pub handle: Handle<Local>,
}
#[derive(Debug)]
pub struct FunctionResult<'a> {
pub ty: Handle<Type<'a>>,
pub binding: Option<crate::Binding>,
}
#[derive(Debug)]
pub struct EntryPoint {
pub stage: crate::ShaderStage,
pub early_depth_test: Option<crate::EarlyDepthTest>,
pub workgroup_size: [u32; 3],
}
#[cfg(doc)]
use crate::front::wgsl::lower::{RuntimeExpressionContext, StatementContext};
#[derive(Debug)]
pub struct Function<'a> {
pub entry_point: Option<EntryPoint>,
pub name: Ident<'a>,
pub arguments: Vec<FunctionArgument<'a>>,
pub result: Option<FunctionResult<'a>>,
/// Local variable and function argument arena.
///
/// Note that the `Local` here is actually a zero-sized type. The AST keeps
/// all the detailed information about locals - names, types, etc. - in
/// [`LocalDecl`] statements. For arguments, that information is kept in
/// [`arguments`]. This `Arena`'s only role is to assign a unique `Handle`
/// to each of them, and track their definitions' spans for use in
/// diagnostics.
///
/// In the AST, when an [`Ident`] expression refers to a local variable or
/// argument, its [`IdentExpr`] holds the referent's `Handle<Local>` in this
/// arena.
///
/// During lowering, [`LocalDecl`] statements add entries to a per-function
/// table that maps `Handle<Local>` values to their Naga representations,
/// accessed via [`StatementContext::local_table`] and
/// [`RuntimeExpressionContext::local_table`]. This table is then consulted when
/// lowering subsequent [`Ident`] expressions.
///
/// [`LocalDecl`]: StatementKind::LocalDecl
/// [`arguments`]: Function::arguments
/// [`Ident`]: Expression::Ident
/// [`StatementContext::local_table`]: StatementContext::local_table
/// [`RuntimeExpressionContext::local_table`]: RuntimeExpressionContext::local_table
pub locals: Arena<Local>,
pub body: Block<'a>,
}
#[derive(Debug)]
pub struct GlobalVariable<'a> {
pub name: Ident<'a>,
pub space: crate::AddressSpace,
pub binding: Option<crate::ResourceBinding>,
pub ty: Handle<Type<'a>>,
pub init: Option<Handle<Expression<'a>>>,
}
#[derive(Debug)]
pub struct StructMember<'a> {
pub name: Ident<'a>,
pub ty: Handle<Type<'a>>,
pub binding: Option<crate::Binding>,
pub align: Option<(u32, Span)>,
pub size: Option<(u32, Span)>,
}
#[derive(Debug)]
pub struct Struct<'a> {
pub name: Ident<'a>,
pub members: Vec<StructMember<'a>>,
}
#[derive(Debug)]
pub struct TypeAlias<'a> {
pub name: Ident<'a>,
pub ty: Handle<Type<'a>>,
}
#[derive(Debug)]
pub struct Const<'a> {
pub name: Ident<'a>,
pub ty: Option<Handle<Type<'a>>>,
pub init: Handle<Expression<'a>>,
}
/// The size of an [`Array`] or [`BindingArray`].
///
/// [`Array`]: Type::Array
/// [`BindingArray`]: Type::BindingArray
#[derive(Debug, Copy, Clone)]
pub enum ArraySize<'a> {
/// The length as a constant expression.
Constant(Handle<Expression<'a>>),
Dynamic,
}
#[derive(Debug)]
pub enum Type<'a> {
Scalar {
kind: crate::ScalarKind,
width: crate::Bytes,
},
Vector {
size: crate::VectorSize,
kind: crate::ScalarKind,
width: crate::Bytes,
},
Matrix {
columns: crate::VectorSize,
rows: crate::VectorSize,
width: crate::Bytes,
},
Atomic {
kind: crate::ScalarKind,
width: crate::Bytes,
},
Pointer {
base: Handle<Type<'a>>,
space: crate::AddressSpace,
},
Array {
base: Handle<Type<'a>>,
size: ArraySize<'a>,
},
Image {
dim: crate::ImageDimension,
arrayed: bool,
class: crate::ImageClass,
},
Sampler {
comparison: bool,
},
AccelerationStructure,
RayQuery,
RayDesc,
RayIntersection,
BindingArray {
base: Handle<Type<'a>>,
size: ArraySize<'a>,
},
/// A user-defined type, like a struct or a type alias.
User(Ident<'a>),
}
#[derive(Debug, Default)]
pub struct Block<'a> {
pub stmts: Vec<Statement<'a>>,
}
#[derive(Debug)]
pub struct Statement<'a> {
pub kind: StatementKind<'a>,
pub span: Span,
}
#[derive(Debug)]
pub enum StatementKind<'a> {
LocalDecl(LocalDecl<'a>),
Block(Block<'a>),
If {
condition: Handle<Expression<'a>>,
accept: Block<'a>,
reject: Block<'a>,
},
Switch {
selector: Handle<Expression<'a>>,
cases: Vec<SwitchCase<'a>>,
},
Loop {
body: Block<'a>,
continuing: Block<'a>,
break_if: Option<Handle<Expression<'a>>>,
},
Break,
Continue,
Return {
value: Option<Handle<Expression<'a>>>,
},
Kill,
Call {
function: Ident<'a>,
arguments: Vec<Handle<Expression<'a>>>,
},
Assign {
target: Handle<Expression<'a>>,
op: Option<crate::BinaryOperator>,
value: Handle<Expression<'a>>,
},
Increment(Handle<Expression<'a>>),
Decrement(Handle<Expression<'a>>),
Ignore(Handle<Expression<'a>>),
}
#[derive(Debug)]
pub enum SwitchValue {
I32(i32),
U32(u32),
Default,
}
#[derive(Debug)]
pub struct SwitchCase<'a> {
pub value: SwitchValue,
pub value_span: Span,
pub body: Block<'a>,
pub fall_through: bool,
}
/// A type at the head of a [`Construct`] expression.
///
/// WGSL has two types of [`type constructor expressions`]:
///
/// - Those that fully specify the type being constructed, like
/// `vec3<f32>(x,y,z)`, which obviously constructs a `vec3<f32>`.
///
/// - Those that leave the component type of the composite being constructed
/// implicit, to be inferred from the argument types, like `vec3(x,y,z)`,
/// which constructs a `vec3<T>` where `T` is the type of `x`, `y`, and `z`.
///
/// This enum represents the head type of both cases. The `PartialFoo` variants
/// represent the second case, where the component type is implicit.
///
/// This does not cover structs or types referred to by type aliases. See the
/// documentation for [`Construct`] and [`Call`] expressions for details.
///
/// [`Construct`]: Expression::Construct
/// [`type constructor expressions`]: https://gpuweb.github.io/gpuweb/wgsl/#type-constructor-expr
/// [`Call`]: Expression::Call
#[derive(Debug)]
pub enum ConstructorType<'a> {
/// A scalar type or conversion: `f32(1)`.
Scalar {
kind: crate::ScalarKind,
width: crate::Bytes,
},
/// A vector construction whose component type is inferred from the
/// argument: `vec3(1.0)`.
PartialVector { size: crate::VectorSize },
/// A vector construction whose component type is written out:
/// `vec3<f32>(1.0)`.
Vector {
size: crate::VectorSize,
kind: crate::ScalarKind,
width: crate::Bytes,
},
/// A matrix construction whose component type is inferred from the
/// argument: `mat2x2(1,2,3,4)`.
PartialMatrix {
columns: crate::VectorSize,
rows: crate::VectorSize,
},
/// A matrix construction whose component type is written out:
/// `mat2x2<f32>(1,2,3,4)`.
Matrix {
columns: crate::VectorSize,
rows: crate::VectorSize,
width: crate::Bytes,
},
/// An array whose component type and size are inferred from the arguments:
/// `array(3,4,5)`.
PartialArray,
/// An array whose component type and size are written out:
/// `array<u32, 4>(3,4,5)`.
Array {
base: Handle<Type<'a>>,
size: ArraySize<'a>,
},
/// Constructing a value of a known Naga IR type.
///
/// This variant is produced only during lowering, when we have Naga types
/// available, never during parsing.
Type(Handle<crate::Type>),
}
#[derive(Debug, Copy, Clone)]
pub enum Literal {
Bool(bool),
Number(Number),
}
#[cfg(doc)]
use crate::front::wgsl::lower::Lowerer;
#[derive(Debug)]
pub enum Expression<'a> {
Literal(Literal),
Ident(IdentExpr<'a>),
/// A type constructor expression.
///
/// This is only used for expressions like `KEYWORD(EXPR...)` and
/// `KEYWORD<PARAM>(EXPR...)`, where `KEYWORD` is a [type-defining keyword] like
/// `vec3`. These keywords cannot be shadowed by user definitions, so we can
/// tell that such an expression is a construction immediately.
///
/// For ordinary identifiers, we can't tell whether an expression like
/// `IDENTIFIER(EXPR, ...)` is a construction expression or a function call
/// until we know `IDENTIFIER`'s definition, so we represent those as
/// [`Call`] expressions.
///
/// [type-defining keyword]: https://gpuweb.github.io/gpuweb/wgsl/#type-defining-keywords
/// [`Call`]: Expression::Call
Construct {
ty: ConstructorType<'a>,
ty_span: Span,
components: Vec<Handle<Expression<'a>>>,
},
Unary {
op: crate::UnaryOperator,
expr: Handle<Expression<'a>>,
},
AddrOf(Handle<Expression<'a>>),
Deref(Handle<Expression<'a>>),
Binary {
op: crate::BinaryOperator,
left: Handle<Expression<'a>>,
right: Handle<Expression<'a>>,
},
/// A function call or type constructor expression.
///
/// We can't tell whether an expression like `IDENTIFIER(EXPR, ...)` is a
/// construction expression or a function call until we know `IDENTIFIER`'s
/// definition, so we represent everything of that form as one of these
/// expressions until lowering. At that point, [`Lowerer::call`] has
/// everything's definition in hand, and can decide whether to emit a Naga
/// [`Constant`], [`As`], [`Splat`], or [`Compose`] expression.
///
/// [`Lowerer::call`]: Lowerer::call
/// [`Constant`]: crate::Expression::Constant
/// [`As`]: crate::Expression::As
/// [`Splat`]: crate::Expression::Splat
/// [`Compose`]: crate::Expression::Compose
Call {
function: Ident<'a>,
arguments: Vec<Handle<Expression<'a>>>,
},
Index {
base: Handle<Expression<'a>>,
index: Handle<Expression<'a>>,
},
Member {
base: Handle<Expression<'a>>,
field: Ident<'a>,
},
Bitcast {
expr: Handle<Expression<'a>>,
to: Handle<Type<'a>>,
ty_span: Span,
},
}
#[derive(Debug)]
pub struct LocalVariable<'a> {
pub name: Ident<'a>,
pub ty: Option<Handle<Type<'a>>>,
pub init: Option<Handle<Expression<'a>>>,
pub handle: Handle<Local>,
}
#[derive(Debug)]
pub struct Let<'a> {
pub name: Ident<'a>,
pub ty: Option<Handle<Type<'a>>>,
pub init: Handle<Expression<'a>>,
pub handle: Handle<Local>,
}
#[derive(Debug)]
pub enum LocalDecl<'a> {
Var(LocalVariable<'a>),
Let(Let<'a>),
}
#[derive(Debug)]
/// A placeholder for a local variable declaration.
///
/// See [`Function::locals`] for more information.
pub struct Local;

View file

@ -0,0 +1,236 @@
use super::Error;
use crate::Span;
pub fn map_address_space(word: &str, span: Span) -> Result<crate::AddressSpace, Error<'_>> {
match word {
"private" => Ok(crate::AddressSpace::Private),
"workgroup" => Ok(crate::AddressSpace::WorkGroup),
"uniform" => Ok(crate::AddressSpace::Uniform),
"storage" => Ok(crate::AddressSpace::Storage {
access: crate::StorageAccess::default(),
}),
"push_constant" => Ok(crate::AddressSpace::PushConstant),
"function" => Ok(crate::AddressSpace::Function),
_ => Err(Error::UnknownAddressSpace(span)),
}
}
pub fn map_built_in(word: &str, span: Span) -> Result<crate::BuiltIn, Error<'_>> {
Ok(match word {
"position" => crate::BuiltIn::Position { invariant: false },
// vertex
"vertex_index" => crate::BuiltIn::VertexIndex,
"instance_index" => crate::BuiltIn::InstanceIndex,
"view_index" => crate::BuiltIn::ViewIndex,
// fragment
"front_facing" => crate::BuiltIn::FrontFacing,
"frag_depth" => crate::BuiltIn::FragDepth,
"primitive_index" => crate::BuiltIn::PrimitiveIndex,
"sample_index" => crate::BuiltIn::SampleIndex,
"sample_mask" => crate::BuiltIn::SampleMask,
// compute
"global_invocation_id" => crate::BuiltIn::GlobalInvocationId,
"local_invocation_id" => crate::BuiltIn::LocalInvocationId,
"local_invocation_index" => crate::BuiltIn::LocalInvocationIndex,
"workgroup_id" => crate::BuiltIn::WorkGroupId,
"num_workgroups" => crate::BuiltIn::NumWorkGroups,
_ => return Err(Error::UnknownBuiltin(span)),
})
}
pub fn map_interpolation(word: &str, span: Span) -> Result<crate::Interpolation, Error<'_>> {
match word {
"linear" => Ok(crate::Interpolation::Linear),
"flat" => Ok(crate::Interpolation::Flat),
"perspective" => Ok(crate::Interpolation::Perspective),
_ => Err(Error::UnknownAttribute(span)),
}
}
pub fn map_sampling(word: &str, span: Span) -> Result<crate::Sampling, Error<'_>> {
match word {
"center" => Ok(crate::Sampling::Center),
"centroid" => Ok(crate::Sampling::Centroid),
"sample" => Ok(crate::Sampling::Sample),
_ => Err(Error::UnknownAttribute(span)),
}
}
pub fn map_storage_format(word: &str, span: Span) -> Result<crate::StorageFormat, Error<'_>> {
use crate::StorageFormat as Sf;
Ok(match word {
"r8unorm" => Sf::R8Unorm,
"r8snorm" => Sf::R8Snorm,
"r8uint" => Sf::R8Uint,
"r8sint" => Sf::R8Sint,
"r16unorm" => Sf::R16Unorm,
"r16snorm" => Sf::R16Snorm,
"r16uint" => Sf::R16Uint,
"r16sint" => Sf::R16Sint,
"r16float" => Sf::R16Float,
"rg8unorm" => Sf::Rg8Unorm,
"rg8snorm" => Sf::Rg8Snorm,
"rg8uint" => Sf::Rg8Uint,
"rg8sint" => Sf::Rg8Sint,
"r32uint" => Sf::R32Uint,
"r32sint" => Sf::R32Sint,
"r32float" => Sf::R32Float,
"rg16unorm" => Sf::Rg16Unorm,
"rg16snorm" => Sf::Rg16Snorm,
"rg16uint" => Sf::Rg16Uint,
"rg16sint" => Sf::Rg16Sint,
"rg16float" => Sf::Rg16Float,
"rgba8unorm" => Sf::Rgba8Unorm,
"rgba8snorm" => Sf::Rgba8Snorm,
"rgba8uint" => Sf::Rgba8Uint,
"rgba8sint" => Sf::Rgba8Sint,
"rgb10a2unorm" => Sf::Rgb10a2Unorm,
"rg11b10float" => Sf::Rg11b10Float,
"rg32uint" => Sf::Rg32Uint,
"rg32sint" => Sf::Rg32Sint,
"rg32float" => Sf::Rg32Float,
"rgba16unorm" => Sf::Rgba16Unorm,
"rgba16snorm" => Sf::Rgba16Snorm,
"rgba16uint" => Sf::Rgba16Uint,
"rgba16sint" => Sf::Rgba16Sint,
"rgba16float" => Sf::Rgba16Float,
"rgba32uint" => Sf::Rgba32Uint,
"rgba32sint" => Sf::Rgba32Sint,
"rgba32float" => Sf::Rgba32Float,
_ => return Err(Error::UnknownStorageFormat(span)),
})
}
pub fn get_scalar_type(word: &str) -> Option<(crate::ScalarKind, crate::Bytes)> {
match word {
// "f16" => Some((crate::ScalarKind::Float, 2)),
"f32" => Some((crate::ScalarKind::Float, 4)),
"f64" => Some((crate::ScalarKind::Float, 8)),
"i32" => Some((crate::ScalarKind::Sint, 4)),
"u32" => Some((crate::ScalarKind::Uint, 4)),
"bool" => Some((crate::ScalarKind::Bool, crate::BOOL_WIDTH)),
_ => None,
}
}
pub fn map_derivative(word: &str) -> Option<(crate::DerivativeAxis, crate::DerivativeControl)> {
use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl};
match word {
"dpdxCoarse" => Some((Axis::X, Ctrl::Coarse)),
"dpdyCoarse" => Some((Axis::Y, Ctrl::Coarse)),
"fwidthCoarse" => Some((Axis::Width, Ctrl::Coarse)),
"dpdxFine" => Some((Axis::X, Ctrl::Fine)),
"dpdyFine" => Some((Axis::Y, Ctrl::Fine)),
"fwidthFine" => Some((Axis::Width, Ctrl::Fine)),
"dpdx" => Some((Axis::X, Ctrl::None)),
"dpdy" => Some((Axis::Y, Ctrl::None)),
"fwidth" => Some((Axis::Width, Ctrl::None)),
_ => None,
}
}
pub fn map_relational_fun(word: &str) -> Option<crate::RelationalFunction> {
match word {
"any" => Some(crate::RelationalFunction::Any),
"all" => Some(crate::RelationalFunction::All),
_ => None,
}
}
pub fn map_standard_fun(word: &str) -> Option<crate::MathFunction> {
use crate::MathFunction as Mf;
Some(match word {
// comparison
"abs" => Mf::Abs,
"min" => Mf::Min,
"max" => Mf::Max,
"clamp" => Mf::Clamp,
"saturate" => Mf::Saturate,
// trigonometry
"cos" => Mf::Cos,
"cosh" => Mf::Cosh,
"sin" => Mf::Sin,
"sinh" => Mf::Sinh,
"tan" => Mf::Tan,
"tanh" => Mf::Tanh,
"acos" => Mf::Acos,
"acosh" => Mf::Acosh,
"asin" => Mf::Asin,
"asinh" => Mf::Asinh,
"atan" => Mf::Atan,
"atanh" => Mf::Atanh,
"atan2" => Mf::Atan2,
"radians" => Mf::Radians,
"degrees" => Mf::Degrees,
// decomposition
"ceil" => Mf::Ceil,
"floor" => Mf::Floor,
"round" => Mf::Round,
"fract" => Mf::Fract,
"trunc" => Mf::Trunc,
"modf" => Mf::Modf,
"frexp" => Mf::Frexp,
"ldexp" => Mf::Ldexp,
// exponent
"exp" => Mf::Exp,
"exp2" => Mf::Exp2,
"log" => Mf::Log,
"log2" => Mf::Log2,
"pow" => Mf::Pow,
// geometry
"dot" => Mf::Dot,
"outerProduct" => Mf::Outer,
"cross" => Mf::Cross,
"distance" => Mf::Distance,
"length" => Mf::Length,
"normalize" => Mf::Normalize,
"faceForward" => Mf::FaceForward,
"reflect" => Mf::Reflect,
"refract" => Mf::Refract,
// computational
"sign" => Mf::Sign,
"fma" => Mf::Fma,
"mix" => Mf::Mix,
"step" => Mf::Step,
"smoothstep" => Mf::SmoothStep,
"sqrt" => Mf::Sqrt,
"inverseSqrt" => Mf::InverseSqrt,
"transpose" => Mf::Transpose,
"determinant" => Mf::Determinant,
// bits
"countTrailingZeros" => Mf::CountTrailingZeros,
"countLeadingZeros" => Mf::CountLeadingZeros,
"countOneBits" => Mf::CountOneBits,
"reverseBits" => Mf::ReverseBits,
"extractBits" => Mf::ExtractBits,
"insertBits" => Mf::InsertBits,
"firstTrailingBit" => Mf::FindLsb,
"firstLeadingBit" => Mf::FindMsb,
// data packing
"pack4x8snorm" => Mf::Pack4x8snorm,
"pack4x8unorm" => Mf::Pack4x8unorm,
"pack2x16snorm" => Mf::Pack2x16snorm,
"pack2x16unorm" => Mf::Pack2x16unorm,
"pack2x16float" => Mf::Pack2x16float,
// data unpacking
"unpack4x8snorm" => Mf::Unpack4x8snorm,
"unpack4x8unorm" => Mf::Unpack4x8unorm,
"unpack2x16snorm" => Mf::Unpack2x16snorm,
"unpack2x16unorm" => Mf::Unpack2x16unorm,
"unpack2x16float" => Mf::Unpack2x16float,
_ => return None,
})
}
pub fn map_conservative_depth(
word: &str,
span: Span,
) -> Result<crate::ConservativeDepth, Error<'_>> {
use crate::ConservativeDepth as Cd;
match word {
"greater_equal" => Ok(Cd::GreaterEqual),
"less_equal" => Ok(Cd::LessEqual),
"unchanged" => Ok(Cd::Unchanged),
_ => Err(Error::UnknownConservativeDepth(span)),
}
}

View file

@ -0,0 +1,723 @@
use super::{number::consume_number, Error, ExpectedToken};
use crate::front::wgsl::error::NumberError;
use crate::front::wgsl::parse::{conv, Number};
use crate::Span;
type TokenSpan<'a> = (Token<'a>, Span);
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Token<'a> {
Separator(char),
Paren(char),
Attribute,
Number(Result<Number, NumberError>),
Word(&'a str),
Operation(char),
LogicalOperation(char),
ShiftOperation(char),
AssignmentOperation(char),
IncrementOperation,
DecrementOperation,
Arrow,
Unknown(char),
Trivia,
End,
}
fn consume_any(input: &str, what: impl Fn(char) -> bool) -> (&str, &str) {
let pos = input.find(|c| !what(c)).unwrap_or(input.len());
input.split_at(pos)
}
/// Return the token at the start of `input`.
///
/// If `generic` is `false`, then the bit shift operators `>>` or `<<`
/// are valid lookahead tokens for the current parser state (see [§3.1
/// Parsing] in the WGSL specification). In other words:
///
/// - If `generic` is `true`, then we are expecting an angle bracket
/// around a generic type parameter, like the `<` and `>` in
/// `vec3<f32>`, so interpret `<` and `>` as `Token::Paren` tokens,
/// even if they're part of `<<` or `>>` sequences.
///
/// - Otherwise, interpret `<<` and `>>` as shift operators:
/// `Token::LogicalOperation` tokens.
///
/// [§3.1 Parsing]: https://gpuweb.github.io/gpuweb/wgsl/#parsing
fn consume_token(input: &str, generic: bool) -> (Token<'_>, &str) {
let mut 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();
match chars.next() {
Some('=') if !generic => (Token::LogicalOperation(cur), chars.as_str()),
Some(c) if c == cur && !generic => {
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('/') => {
let _ = chars.position(is_comment_end);
(Token::Trivia, chars.as_str())
}
Some('*') => {
let mut depth = 1;
let mut prev = None;
for c in &mut chars {
match (prev, c) {
(Some('*'), '/') => {
prev = None;
depth -= 1;
if depth == 0 {
return (Token::Trivia, chars.as_str());
}
}
(Some('/'), '*') => {
prev = None;
depth += 1;
}
_ => {
prev = Some(c);
}
}
}
(Token::End, "")
}
Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
_ => (Token::Operation(cur), og_chars),
}
}
'-' => {
let og_chars = chars.as_str();
match chars.next() {
Some('>') => (Token::Arrow, chars.as_str()),
Some('0'..='9' | '.') => consume_number(input),
Some('-') => (Token::DecrementOperation, chars.as_str()),
Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
_ => (Token::Operation(cur), og_chars),
}
}
'+' => {
let og_chars = chars.as_str();
match chars.next() {
Some('+') => (Token::IncrementOperation, chars.as_str()),
Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
_ => (Token::Operation(cur), og_chars),
}
}
'*' | '%' | '^' => {
let og_chars = chars.as_str();
match chars.next() {
Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
_ => (Token::Operation(cur), og_chars),
}
}
'~' => (Token::Operation(cur), chars.as_str()),
'=' | '!' => {
let og_chars = chars.as_str();
match chars.next() {
Some('=') => (Token::LogicalOperation(cur), chars.as_str()),
_ => (Token::Operation(cur), og_chars),
}
}
'&' | '|' => {
let og_chars = chars.as_str();
match chars.next() {
Some(c) if c == cur => (Token::LogicalOperation(cur), chars.as_str()),
Some('=') => (Token::AssignmentOperation(cur), chars.as_str()),
_ => (Token::Operation(cur), og_chars),
}
}
_ if is_blankspace(cur) => {
let (_, rest) = consume_any(input, is_blankspace);
(Token::Trivia, rest)
}
_ if is_word_start(cur) => {
let (word, rest) = consume_any(input, is_word_part);
(Token::Word(word), rest)
}
_ => (Token::Unknown(cur), chars.as_str()),
}
}
/// Returns whether or not a char is a comment end
/// (Unicode Pattern_White_Space excluding U+0020, U+0009, U+200E and U+200F)
const fn 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)
const fn 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_xid::UnicodeXID::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_xid::UnicodeXID::is_xid_continue(c)
}
#[derive(Clone)]
pub(in crate::front::wgsl) struct Lexer<'a> {
input: &'a str,
pub(in crate::front::wgsl) source: &'a str,
// The byte offset of the end of the last non-trivia token.
last_end_offset: usize,
}
impl<'a> Lexer<'a> {
pub(in crate::front::wgsl) const fn new(input: &'a str) -> Self {
Lexer {
input,
source: input,
last_end_offset: 0,
}
}
/// 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]
pub fn capture_span<T, E>(
&mut self,
inner: impl FnOnce(&mut Self) -> Result<T, E>,
) -> 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)))
}
pub(in crate::front::wgsl) fn start_byte_offset(&mut self) -> usize {
loop {
// Eat all trivia because `next` doesn't eat trailing trivia.
let (token, rest) = consume_token(self.input, false);
if let Token::Trivia = token {
self.input = rest;
} else {
return self.current_byte_offset();
}
}
}
fn peek_token_and_rest(&mut self) -> (TokenSpan<'a>, &'a str) {
let mut cloned = self.clone();
let token = cloned.next();
let rest = cloned.input;
(token, rest)
}
const fn current_byte_offset(&self) -> usize {
self.source.len() - self.input.len()
}
pub(in crate::front::wgsl) fn span_from(&self, offset: usize) -> Span {
Span::from(offset..self.last_end_offset)
}
/// 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(in crate::front::wgsl) fn next(&mut self) -> TokenSpan<'a> {
self.next_impl(false)
}
/// Return the next non-whitespace token from `self`.
///
/// Assume we are in a parse state where angle brackets may occur,
/// but not bit shift operators.
#[must_use]
pub(in crate::front::wgsl) fn next_generic(&mut self) -> TokenSpan<'a> {
self.next_impl(true)
}
/// Return the next non-whitespace token from `self`, with a span.
///
/// See [`consume_token`] for the meaning of `generic`.
fn next_impl(&mut self, generic: bool) -> TokenSpan<'a> {
let mut start_byte_offset = self.current_byte_offset();
loop {
let (token, rest) = consume_token(self.input, generic);
self.input = rest;
match token {
Token::Trivia => start_byte_offset = self.current_byte_offset(),
_ => {
self.last_end_offset = self.current_byte_offset();
return (token, self.span_from(start_byte_offset));
}
}
}
}
#[must_use]
pub(in crate::front::wgsl) fn peek(&mut self) -> TokenSpan<'a> {
let (token, _) = self.peek_token_and_rest();
token
}
pub(in crate::front::wgsl) fn expect_span(
&mut self,
expected: Token<'a>,
) -> Result<Span, Error<'a>> {
let next = self.next();
if next.0 == expected {
Ok(next.1)
} else {
Err(Error::Unexpected(next.1, ExpectedToken::Token(expected)))
}
}
pub(in crate::front::wgsl) fn expect(&mut self, expected: Token<'a>) -> Result<(), Error<'a>> {
self.expect_span(expected)?;
Ok(())
}
pub(in crate::front::wgsl) fn expect_generic_paren(
&mut self,
expected: char,
) -> Result<(), Error<'a>> {
let next = self.next_generic();
if next.0 == Token::Paren(expected) {
Ok(())
} else {
Err(Error::Unexpected(
next.1,
ExpectedToken::Token(Token::Paren(expected)),
))
}
}
/// If the next token matches it is skipped and true is returned
pub(in crate::front::wgsl) fn skip(&mut self, what: Token<'_>) -> bool {
let (peeked_token, rest) = self.peek_token_and_rest();
if peeked_token.0 == what {
self.input = rest;
true
} else {
false
}
}
pub(in crate::front::wgsl) fn next_ident_with_span(
&mut self,
) -> Result<(&'a str, Span), Error<'a>> {
match self.next() {
(Token::Word(word), span) if word == "_" => {
Err(Error::InvalidIdentifierUnderscore(span))
}
(Token::Word(word), span) if word.starts_with("__") => {
Err(Error::ReservedIdentifierPrefix(span))
}
(Token::Word(word), span) => Ok((word, span)),
other => Err(Error::Unexpected(other.1, ExpectedToken::Identifier)),
}
}
pub(in crate::front::wgsl) fn next_ident(
&mut self,
) -> Result<super::ast::Ident<'a>, Error<'a>> {
let ident = self
.next_ident_with_span()
.map(|(name, span)| super::ast::Ident { name, span })?;
if crate::keywords::wgsl::RESERVED.contains(&ident.name) {
return Err(Error::ReservedKeyword(ident.span));
}
Ok(ident)
}
/// Parses a generic scalar type, for example `<f32>`.
pub(in crate::front::wgsl) fn next_scalar_generic(
&mut self,
) -> Result<(crate::ScalarKind, crate::Bytes), Error<'a>> {
self.expect_generic_paren('<')?;
let pair = match self.next() {
(Token::Word(word), span) => {
conv::get_scalar_type(word).ok_or(Error::UnknownScalarType(span))
}
(_, span) => Err(Error::UnknownScalarType(span)),
}?;
self.expect_generic_paren('>')?;
Ok(pair)
}
/// Parses a generic scalar type, for example `<f32>`.
///
/// Returns the span covering the inner type, excluding the brackets.
pub(in crate::front::wgsl) fn next_scalar_generic_with_span(
&mut self,
) -> Result<(crate::ScalarKind, crate::Bytes, Span), Error<'a>> {
self.expect_generic_paren('<')?;
let pair = match self.next() {
(Token::Word(word), span) => conv::get_scalar_type(word)
.map(|(a, b)| (a, b, span))
.ok_or(Error::UnknownScalarType(span)),
(_, span) => Err(Error::UnknownScalarType(span)),
}?;
self.expect_generic_paren('>')?;
Ok(pair)
}
pub(in crate::front::wgsl) fn next_storage_access(
&mut self,
) -> Result<crate::StorageAccess, Error<'a>> {
let (ident, span) = self.next_ident_with_span()?;
match ident {
"read" => Ok(crate::StorageAccess::LOAD),
"write" => Ok(crate::StorageAccess::STORE),
"read_write" => Ok(crate::StorageAccess::LOAD | crate::StorageAccess::STORE),
_ => Err(Error::UnknownAccess(span)),
}
}
pub(in crate::front::wgsl) fn next_format_generic(
&mut self,
) -> Result<(crate::StorageFormat, crate::StorageAccess), Error<'a>> {
self.expect(Token::Paren('<'))?;
let (ident, ident_span) = self.next_ident_with_span()?;
let format = conv::map_storage_format(ident, ident_span)?;
self.expect(Token::Separator(','))?;
let access = self.next_storage_access()?;
self.expect(Token::Paren('>'))?;
Ok((format, access))
}
pub(in crate::front::wgsl) fn open_arguments(&mut self) -> Result<(), Error<'a>> {
self.expect(Token::Paren('('))
}
pub(in crate::front::wgsl) fn close_arguments(&mut self) -> Result<(), Error<'a>> {
let _ = self.skip(Token::Separator(','));
self.expect(Token::Paren(')'))
}
pub(in crate::front::wgsl) fn next_argument(&mut self) -> Result<bool, Error<'a>> {
let paren = Token::Paren(')');
if self.skip(Token::Separator(',')) {
Ok(!self.skip(paren))
} else {
self.expect(paren).map(|()| false)
}
}
}
#[cfg(test)]
fn sub_test(source: &str, expected_tokens: &[Token]) {
let mut lex = Lexer::new(source);
for &token in expected_tokens {
assert_eq!(lex.next().0, token);
}
assert_eq!(lex.next().0, Token::End);
}
#[test]
fn test_numbers() {
// WGSL spec examples //
// decimal integer
sub_test(
"0x123 0X123u 1u 123 0 0i 0x3f",
&[
Token::Number(Ok(Number::I32(291))),
Token::Number(Ok(Number::U32(291))),
Token::Number(Ok(Number::U32(1))),
Token::Number(Ok(Number::I32(123))),
Token::Number(Ok(Number::I32(0))),
Token::Number(Ok(Number::I32(0))),
Token::Number(Ok(Number::I32(63))),
],
);
// decimal floating point
sub_test(
"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",
&[
Token::Number(Ok(Number::F32(0.))),
Token::Number(Ok(Number::F32(1.))),
Token::Number(Ok(Number::F32(0.01))),
Token::Number(Ok(Number::F32(12.34))),
Token::Number(Ok(Number::F32(0.))),
Token::Number(Err(NumberError::UnimplementedF16)),
Token::Number(Ok(Number::F32(0.001))),
Token::Number(Ok(Number::F32(43.75))),
Token::Number(Ok(Number::F32(16.))),
Token::Number(Ok(Number::F32(0.1875))),
Token::Number(Err(NumberError::UnimplementedF16)),
Token::Number(Ok(Number::F32(0.12109375))),
Token::Number(Err(NumberError::UnimplementedF16)),
],
);
// MIN / MAX //
// min / max decimal signed integer
sub_test(
"-2147483648i 2147483647i -2147483649i 2147483648i",
&[
Token::Number(Ok(Number::I32(i32::MIN))),
Token::Number(Ok(Number::I32(i32::MAX))),
Token::Number(Err(NumberError::NotRepresentable)),
Token::Number(Err(NumberError::NotRepresentable)),
],
);
// min / max decimal unsigned integer
sub_test(
"0u 4294967295u -1u 4294967296u",
&[
Token::Number(Ok(Number::U32(u32::MIN))),
Token::Number(Ok(Number::U32(u32::MAX))),
Token::Number(Err(NumberError::NotRepresentable)),
Token::Number(Err(NumberError::NotRepresentable)),
],
);
// min / max hexadecimal signed integer
sub_test(
"-0x80000000i 0x7FFFFFFFi -0x80000001i 0x80000000i",
&[
Token::Number(Ok(Number::I32(i32::MIN))),
Token::Number(Ok(Number::I32(i32::MAX))),
Token::Number(Err(NumberError::NotRepresentable)),
Token::Number(Err(NumberError::NotRepresentable)),
],
);
// min / max hexadecimal unsigned integer
sub_test(
"0x0u 0xFFFFFFFFu -0x1u 0x100000000u",
&[
Token::Number(Ok(Number::U32(u32::MIN))),
Token::Number(Ok(Number::U32(u32::MAX))),
Token::Number(Err(NumberError::NotRepresentable)),
Token::Number(Err(NumberError::NotRepresentable)),
],
);
/// ≈ 2^-126 * 2^23 (= 2^149)
const SMALLEST_POSITIVE_SUBNORMAL_F32: f32 = 1e-45;
/// ≈ 2^-126 * (1 2^23)
const LARGEST_SUBNORMAL_F32: f32 = 1.1754942e-38;
/// ≈ 2^-126
const SMALLEST_POSITIVE_NORMAL_F32: f32 = f32::MIN_POSITIVE;
/// ≈ 1 2^24
const LARGEST_F32_LESS_THAN_ONE: f32 = 0.99999994;
/// ≈ 1 + 2^23
const SMALLEST_F32_LARGER_THAN_ONE: f32 = 1.0000001;
/// ≈ -(2^127 * (2 2^23))
const SMALLEST_NORMAL_F32: f32 = f32::MIN;
/// ≈ 2^127 * (2 2^23)
const LARGEST_NORMAL_F32: f32 = f32::MAX;
// decimal floating point
sub_test(
"1e-45f 1.1754942e-38f 1.17549435e-38f 0.99999994f 1.0000001f -3.40282347e+38f 3.40282347e+38f",
&[
Token::Number(Ok(Number::F32(
SMALLEST_POSITIVE_SUBNORMAL_F32,
))),
Token::Number(Ok(Number::F32(
LARGEST_SUBNORMAL_F32,
))),
Token::Number(Ok(Number::F32(
SMALLEST_POSITIVE_NORMAL_F32,
))),
Token::Number(Ok(Number::F32(
LARGEST_F32_LESS_THAN_ONE,
))),
Token::Number(Ok(Number::F32(
SMALLEST_F32_LARGER_THAN_ONE,
))),
Token::Number(Ok(Number::F32(
SMALLEST_NORMAL_F32,
))),
Token::Number(Ok(Number::F32(
LARGEST_NORMAL_F32,
))),
],
);
sub_test(
"-3.40282367e+38f 3.40282367e+38f",
&[
Token::Number(Err(NumberError::NotRepresentable)), // ≈ -2^128
Token::Number(Err(NumberError::NotRepresentable)), // ≈ 2^128
],
);
// hexadecimal floating point
sub_test(
"0x1p-149f 0x7FFFFFp-149f 0x1p-126f 0xFFFFFFp-24f 0x800001p-23f -0xFFFFFFp+104f 0xFFFFFFp+104f",
&[
Token::Number(Ok(Number::F32(
SMALLEST_POSITIVE_SUBNORMAL_F32,
))),
Token::Number(Ok(Number::F32(
LARGEST_SUBNORMAL_F32,
))),
Token::Number(Ok(Number::F32(
SMALLEST_POSITIVE_NORMAL_F32,
))),
Token::Number(Ok(Number::F32(
LARGEST_F32_LESS_THAN_ONE,
))),
Token::Number(Ok(Number::F32(
SMALLEST_F32_LARGER_THAN_ONE,
))),
Token::Number(Ok(Number::F32(
SMALLEST_NORMAL_F32,
))),
Token::Number(Ok(Number::F32(
LARGEST_NORMAL_F32,
))),
],
);
sub_test(
"-0x1p128f 0x1p128f 0x1.000001p0f",
&[
Token::Number(Err(NumberError::NotRepresentable)), // = -2^128
Token::Number(Err(NumberError::NotRepresentable)), // = 2^128
Token::Number(Err(NumberError::NotRepresentable)),
],
);
}
#[test]
fn test_tokens() {
sub_test("id123_OK", &[Token::Word("id123_OK")]);
sub_test(
"92No",
&[Token::Number(Ok(Number::I32(92))), Token::Word("No")],
);
sub_test(
"2u3o",
&[
Token::Number(Ok(Number::U32(2))),
Token::Number(Ok(Number::I32(3))),
Token::Word("o"),
],
);
sub_test(
"2.4f44po",
&[
Token::Number(Ok(Number::F32(2.4))),
Token::Number(Ok(Number::I32(44))),
Token::Word("po"),
],
);
sub_test(
"Δέλτα réflexion Кызыл 𐰓𐰏𐰇 朝焼け سلام 검정 שָׁלוֹם गुलाबी փիրուզ",
&[
Token::Word("Δέλτα"),
Token::Word("réflexion"),
Token::Word("Кызыл"),
Token::Word("𐰓𐰏𐰇"),
Token::Word("朝焼け"),
Token::Word("سلام"),
Token::Word("검정"),
Token::Word("שָׁלוֹם"),
Token::Word("गुलाबी"),
Token::Word("փիրուզ"),
],
);
sub_test("æNoø", &[Token::Word("æNoø")]);
sub_test("No¾", &[Token::Word("No"), Token::Unknown('¾')]);
sub_test("No好", &[Token::Word("No好")]);
sub_test("_No", &[Token::Word("_No")]);
sub_test(
"*/*/***/*//=/*****//",
&[
Token::Operation('*'),
Token::AssignmentOperation('/'),
Token::Operation('/'),
],
);
}
#[test]
fn test_variable_decl() {
sub_test(
"@group(0 ) var< uniform> texture: texture_multisampled_2d <f32 >;",
&[
Token::Attribute,
Token::Word("group"),
Token::Paren('('),
Token::Number(Ok(Number::I32(0))),
Token::Paren(')'),
Token::Word("var"),
Token::Paren('<'),
Token::Word("uniform"),
Token::Paren('>'),
Token::Word("texture"),
Token::Separator(':'),
Token::Word("texture_multisampled_2d"),
Token::Paren('<'),
Token::Word("f32"),
Token::Paren('>'),
Token::Separator(';'),
],
);
sub_test(
"var<storage,read_write> buffer: array<u32>;",
&[
Token::Word("var"),
Token::Paren('<'),
Token::Word("storage"),
Token::Separator(','),
Token::Word("read_write"),
Token::Paren('>'),
Token::Word("buffer"),
Token::Separator(':'),
Token::Word("array"),
Token::Paren('<'),
Token::Word("u32"),
Token::Paren('>'),
Token::Separator(';'),
],
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,443 @@
use std::borrow::Cow;
use crate::front::wgsl::error::NumberError;
use crate::front::wgsl::parse::lexer::Token;
/// When using this type assume no Abstract Int/Float for now
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Number {
/// Abstract Int (-2^63 ≤ i < 2^63)
AbstractInt(i64),
/// Abstract Float (IEEE-754 binary64)
AbstractFloat(f64),
/// Concrete i32
I32(i32),
/// Concrete u32
U32(u32),
/// Concrete f32
F32(f32),
}
impl Number {
/// Convert abstract numbers to a plausible concrete counterpart.
///
/// Return concrete numbers unchanged. If the conversion would be
/// lossy, return an error.
fn abstract_to_concrete(self) -> Result<Number, NumberError> {
match self {
Number::AbstractInt(num) => i32::try_from(num)
.map(Number::I32)
.map_err(|_| NumberError::NotRepresentable),
Number::AbstractFloat(num) => {
let num = num as f32;
if num.is_finite() {
Ok(Number::F32(num))
} else {
Err(NumberError::NotRepresentable)
}
}
num => Ok(num),
}
}
}
// TODO: when implementing Creation-Time Expressions, remove the ability to match the minus sign
pub(in crate::front::wgsl) fn consume_number(input: &str) -> (Token<'_>, &str) {
let (result, rest) = parse(input);
(
Token::Number(result.and_then(Number::abstract_to_concrete)),
rest,
)
}
enum Kind {
Int(IntKind),
Float(FloatKind),
}
enum IntKind {
I32,
U32,
}
enum FloatKind {
F32,
F16,
}
// The following regexes (from the WGSL spec) will be matched:
// int_literal:
// | / 0 [iu]? /
// | / [1-9][0-9]* [iu]? /
// | / 0[xX][0-9a-fA-F]+ [iu]? /
// decimal_float_literal:
// | / 0 [fh] /
// | / [1-9][0-9]* [fh] /
// | / [0-9]* \.[0-9]+ ([eE][+-]?[0-9]+)? [fh]? /
// | / [0-9]+ \.[0-9]* ([eE][+-]?[0-9]+)? [fh]? /
// | / [0-9]+ [eE][+-]?[0-9]+ [fh]? /
// hex_float_literal:
// | / 0[xX][0-9a-fA-F]* \.[0-9a-fA-F]+ ([pP][+-]?[0-9]+ [fh]?)? /
// | / 0[xX][0-9a-fA-F]+ \.[0-9a-fA-F]* ([pP][+-]?[0-9]+ [fh]?)? /
// | / 0[xX][0-9a-fA-F]+ [pP][+-]?[0-9]+ [fh]? /
// You could visualize the regex below via https://debuggex.com to get a rough idea what `parse` is doing
// -?(?:0[xX](?:([0-9a-fA-F]+\.[0-9a-fA-F]*|[0-9a-fA-F]*\.[0-9a-fA-F]+)(?:([pP][+-]?[0-9]+)([fh]?))?|([0-9a-fA-F]+)([pP][+-]?[0-9]+)([fh]?)|([0-9a-fA-F]+)([iu]?))|((?:[0-9]+[eE][+-]?[0-9]+|(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?))([fh]?)|((?:[0-9]|[1-9][0-9]+))([iufh]?))
fn parse(input: &str) -> (Result<Number, NumberError>, &str) {
/// returns `true` and consumes `X` bytes from the given byte buffer
/// if the given `X` nr of patterns are found at the start of the buffer
macro_rules! consume {
($bytes:ident, $($pattern:pat),*) => {
match $bytes {
&[$($pattern),*, ref rest @ ..] => { $bytes = rest; true },
_ => false,
}
};
}
/// consumes one byte from the given byte buffer
/// if one of the given patterns are found at the start of the buffer
/// returning the corresponding expr for the matched pattern
macro_rules! consume_map {
($bytes:ident, [$($pattern:pat_param => $to:expr),*]) => {
match $bytes {
$( &[$pattern, ref rest @ ..] => { $bytes = rest; Some($to) }, )*
_ => None,
}
};
}
/// consumes all consecutive bytes matched by the `0-9` pattern from the given byte buffer
/// returning the number of consumed bytes
macro_rules! consume_dec_digits {
($bytes:ident) => {{
let start_len = $bytes.len();
while let &[b'0'..=b'9', ref rest @ ..] = $bytes {
$bytes = rest;
}
start_len - $bytes.len()
}};
}
/// consumes all consecutive bytes matched by the `0-9 | a-f | A-F` pattern from the given byte buffer
/// returning the number of consumed bytes
macro_rules! consume_hex_digits {
($bytes:ident) => {{
let start_len = $bytes.len();
while let &[b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F', ref rest @ ..] = $bytes {
$bytes = rest;
}
start_len - $bytes.len()
}};
}
/// maps the given `&[u8]` (tail of the initial `input: &str`) to a `&str`
macro_rules! rest_to_str {
($bytes:ident) => {
&input[input.len() - $bytes.len()..]
};
}
struct ExtractSubStr<'a>(&'a str);
impl<'a> ExtractSubStr<'a> {
/// given an `input` and a `start` (tail of the `input`)
/// creates a new [`ExtractSubStr`](`Self`)
fn start(input: &'a str, start: &'a [u8]) -> Self {
let start = input.len() - start.len();
Self(&input[start..])
}
/// given an `end` (tail of the initial `input`)
/// returns a substring of `input`
fn end(&self, end: &'a [u8]) -> &'a str {
let end = self.0.len() - end.len();
&self.0[..end]
}
}
let mut bytes = input.as_bytes();
let general_extract = ExtractSubStr::start(input, bytes);
let is_negative = consume!(bytes, b'-');
if consume!(bytes, b'0', b'x' | b'X') {
let digits_extract = ExtractSubStr::start(input, bytes);
let consumed = consume_hex_digits!(bytes);
if consume!(bytes, b'.') {
let consumed_after_period = consume_hex_digits!(bytes);
if consumed + consumed_after_period == 0 {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
let significand = general_extract.end(bytes);
if consume!(bytes, b'p' | b'P') {
consume!(bytes, b'+' | b'-');
let consumed = consume_dec_digits!(bytes);
if consumed == 0 {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
let number = general_extract.end(bytes);
let kind = consume_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]);
(parse_hex_float(number, kind), rest_to_str!(bytes))
} else {
(
parse_hex_float_missing_exponent(significand, None),
rest_to_str!(bytes),
)
}
} else {
if consumed == 0 {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
let significand = general_extract.end(bytes);
let digits = digits_extract.end(bytes);
let exp_extract = ExtractSubStr::start(input, bytes);
if consume!(bytes, b'p' | b'P') {
consume!(bytes, b'+' | b'-');
let consumed = consume_dec_digits!(bytes);
if consumed == 0 {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
let exponent = exp_extract.end(bytes);
let kind = consume_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]);
(
parse_hex_float_missing_period(significand, exponent, kind),
rest_to_str!(bytes),
)
} else {
let kind = consume_map!(bytes, [b'i' => IntKind::I32, b'u' => IntKind::U32]);
(
parse_hex_int(is_negative, digits, kind),
rest_to_str!(bytes),
)
}
}
} else {
let is_first_zero = bytes.first() == Some(&b'0');
let consumed = consume_dec_digits!(bytes);
if consume!(bytes, b'.') {
let consumed_after_period = consume_dec_digits!(bytes);
if consumed + consumed_after_period == 0 {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
if consume!(bytes, b'e' | b'E') {
consume!(bytes, b'+' | b'-');
let consumed = consume_dec_digits!(bytes);
if consumed == 0 {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
}
let number = general_extract.end(bytes);
let kind = consume_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]);
(parse_dec_float(number, kind), rest_to_str!(bytes))
} else {
if consumed == 0 {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
if consume!(bytes, b'e' | b'E') {
consume!(bytes, b'+' | b'-');
let consumed = consume_dec_digits!(bytes);
if consumed == 0 {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
let number = general_extract.end(bytes);
let kind = consume_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]);
(parse_dec_float(number, kind), rest_to_str!(bytes))
} else {
// make sure the multi-digit numbers don't start with zero
if consumed > 1 && is_first_zero {
return (Err(NumberError::Invalid), rest_to_str!(bytes));
}
let digits_with_sign = general_extract.end(bytes);
let kind = consume_map!(bytes, [
b'i' => Kind::Int(IntKind::I32),
b'u' => Kind::Int(IntKind::U32),
b'f' => Kind::Float(FloatKind::F32),
b'h' => Kind::Float(FloatKind::F16)
]);
(
parse_dec(is_negative, digits_with_sign, kind),
rest_to_str!(bytes),
)
}
}
}
}
fn parse_hex_float_missing_exponent(
// format: -?0[xX] ( [0-9a-fA-F]+\.[0-9a-fA-F]* | [0-9a-fA-F]*\.[0-9a-fA-F]+ )
significand: &str,
kind: Option<FloatKind>,
) -> Result<Number, NumberError> {
let hexf_input = format!("{}{}", significand, "p0");
parse_hex_float(&hexf_input, kind)
}
fn parse_hex_float_missing_period(
// format: -?0[xX] [0-9a-fA-F]+
significand: &str,
// format: [pP][+-]?[0-9]+
exponent: &str,
kind: Option<FloatKind>,
) -> Result<Number, NumberError> {
let hexf_input = format!("{significand}.{exponent}");
parse_hex_float(&hexf_input, kind)
}
fn parse_hex_int(
is_negative: bool,
// format: [0-9a-fA-F]+
digits: &str,
kind: Option<IntKind>,
) -> Result<Number, NumberError> {
let digits_with_sign = if is_negative {
Cow::Owned(format!("-{digits}"))
} else {
Cow::Borrowed(digits)
};
parse_int(&digits_with_sign, kind, 16, is_negative)
}
fn parse_dec(
is_negative: bool,
// format: -? ( [0-9] | [1-9][0-9]+ )
digits_with_sign: &str,
kind: Option<Kind>,
) -> Result<Number, NumberError> {
match kind {
None => parse_int(digits_with_sign, None, 10, is_negative),
Some(Kind::Int(kind)) => parse_int(digits_with_sign, Some(kind), 10, is_negative),
Some(Kind::Float(kind)) => parse_dec_float(digits_with_sign, Some(kind)),
}
}
// Float parsing notes
// The following chapters of IEEE 754-2019 are relevant:
//
// 7.4 Overflow (largest finite number is exceeded by what would have been
// the rounded floating-point result were the exponent range unbounded)
//
// 7.5 Underflow (tiny non-zero result is detected;
// for decimal formats tininess is detected before rounding when a non-zero result
// computed as though both the exponent range and the precision were unbounded
// would lie strictly between 2^126)
//
// 7.6 Inexact (rounded result differs from what would have been computed
// were both exponent range and precision unbounded)
// The WGSL spec requires us to error:
// on overflow for decimal floating point literals
// on overflow and inexact for hexadecimal floating point literals
// (underflow is not mentioned)
// hexf_parse errors on overflow, underflow, inexact
// rust std lib float from str handles overflow, underflow, inexact transparently (rounds and will not error)
// Therefore we only check for overflow manually for decimal floating point literals
// input format: -?0[xX] ( [0-9a-fA-F]+\.[0-9a-fA-F]* | [0-9a-fA-F]*\.[0-9a-fA-F]+ ) [pP][+-]?[0-9]+
fn parse_hex_float(input: &str, kind: Option<FloatKind>) -> Result<Number, NumberError> {
match kind {
None => match hexf_parse::parse_hexf64(input, false) {
Ok(num) => Ok(Number::AbstractFloat(num)),
// can only be ParseHexfErrorKind::Inexact but we can't check since it's private
_ => Err(NumberError::NotRepresentable),
},
Some(FloatKind::F32) => match hexf_parse::parse_hexf32(input, false) {
Ok(num) => Ok(Number::F32(num)),
// can only be ParseHexfErrorKind::Inexact but we can't check since it's private
_ => Err(NumberError::NotRepresentable),
},
Some(FloatKind::F16) => Err(NumberError::UnimplementedF16),
}
}
// input format: -? ( [0-9]+\.[0-9]* | [0-9]*\.[0-9]+ ) ([eE][+-]?[0-9]+)?
// | -? [0-9]+ [eE][+-]?[0-9]+
fn parse_dec_float(input: &str, kind: Option<FloatKind>) -> Result<Number, NumberError> {
match kind {
None => {
let num = input.parse::<f64>().unwrap(); // will never fail
num.is_finite()
.then_some(Number::AbstractFloat(num))
.ok_or(NumberError::NotRepresentable)
}
Some(FloatKind::F32) => {
let num = input.parse::<f32>().unwrap(); // will never fail
num.is_finite()
.then_some(Number::F32(num))
.ok_or(NumberError::NotRepresentable)
}
Some(FloatKind::F16) => Err(NumberError::UnimplementedF16),
}
}
fn parse_int(
input: &str,
kind: Option<IntKind>,
radix: u32,
is_negative: bool,
) -> Result<Number, NumberError> {
fn map_err(e: core::num::ParseIntError) -> NumberError {
match *e.kind() {
core::num::IntErrorKind::PosOverflow | core::num::IntErrorKind::NegOverflow => {
NumberError::NotRepresentable
}
_ => unreachable!(),
}
}
match kind {
None => match i64::from_str_radix(input, radix) {
Ok(num) => Ok(Number::AbstractInt(num)),
Err(e) => Err(map_err(e)),
},
Some(IntKind::I32) => match i32::from_str_radix(input, radix) {
Ok(num) => Ok(Number::I32(num)),
Err(e) => Err(map_err(e)),
},
Some(IntKind::U32) if is_negative => Err(NumberError::NotRepresentable),
Some(IntKind::U32) => match u32::from_str_radix(input, radix) {
Ok(num) => Ok(Number::U32(num)),
Err(e) => Err(map_err(e)),
},
}
}

View file

@ -0,0 +1,483 @@
use super::parse_str;
#[test]
fn parse_comment() {
parse_str(
"//
////
///////////////////////////////////////////////////////// asda
//////////////////// dad ////////// /
/////////////////////////////////////////////////////////////////////////////////////////////////////
//
",
)
.unwrap();
}
#[test]
fn parse_types() {
parse_str("const a : i32 = 2;").unwrap();
assert!(parse_str("const a : x32 = 2;").is_err());
parse_str("var t: texture_2d<f32>;").unwrap();
parse_str("var t: texture_cube_array<i32>;").unwrap();
parse_str("var t: texture_multisampled_2d<u32>;").unwrap();
parse_str("var t: texture_storage_1d<rgba8uint,write>;").unwrap();
parse_str("var t: texture_storage_3d<r32float,read>;").unwrap();
}
#[test]
fn parse_type_inference() {
parse_str(
"
fn foo() {
let a = 2u;
let b: u32 = a;
var x = 3.;
var y = vec2<f32>(1, 2);
}",
)
.unwrap();
assert!(parse_str(
"
fn foo() { let c : i32 = 2.0; }",
)
.is_err());
}
#[test]
fn parse_type_cast() {
parse_str(
"
const a : i32 = 2;
fn main() {
var x: f32 = f32(a);
x = f32(i32(a + 1) / 2);
}
",
)
.unwrap();
parse_str(
"
fn main() {
let x: vec2<f32> = vec2<f32>(1.0, 2.0);
let y: vec2<u32> = vec2<u32>(x);
}
",
)
.unwrap();
parse_str(
"
fn main() {
let x: vec2<f32> = vec2<f32>(0.0);
}
",
)
.unwrap();
assert!(parse_str(
"
fn main() {
let x: vec2<f32> = vec2<f32>(0);
}
",
)
.is_err());
}
#[test]
fn parse_struct() {
parse_str(
"
struct Foo { x: i32 }
struct Bar {
@size(16) x: vec2<i32>,
@align(16) y: f32,
@size(32) @align(128) z: vec3<f32>,
};
struct Empty {}
var<storage,read_write> s: Foo;
",
)
.unwrap();
}
#[test]
fn parse_standard_fun() {
parse_str(
"
fn main() {
var x: i32 = min(max(1, 2), 3);
}
",
)
.unwrap();
}
#[test]
fn parse_statement() {
parse_str(
"
fn main() {
;
{}
{;}
}
",
)
.unwrap();
parse_str(
"
fn foo() {}
fn bar() { foo(); }
",
)
.unwrap();
}
#[test]
fn parse_if() {
parse_str(
"
fn main() {
if true {
discard;
} else {}
if 0 != 1 {}
if false {
return;
} else if true {
return;
} else {}
}
",
)
.unwrap();
}
#[test]
fn parse_parentheses_if() {
parse_str(
"
fn main() {
if (true) {
discard;
} else {}
if (0 != 1) {}
if (false) {
return;
} else if (true) {
return;
} else {}
}
",
)
.unwrap();
}
#[test]
fn parse_loop() {
parse_str(
"
fn main() {
var i: i32 = 0;
loop {
if i == 1 { break; }
continuing { i = 1; }
}
loop {
if i == 0 { continue; }
break;
}
}
",
)
.unwrap();
parse_str(
"
fn main() {
var found: bool = false;
var i: i32 = 0;
while !found {
if i == 10 {
found = true;
}
i = i + 1;
}
}
",
)
.unwrap();
parse_str(
"
fn main() {
while true {
break;
}
}
",
)
.unwrap();
parse_str(
"
fn main() {
var a: i32 = 0;
for(var i: i32 = 0; i < 4; i = i + 1) {
a = a + 2;
}
}
",
)
.unwrap();
parse_str(
"
fn main() {
for(;;) {
break;
}
}
",
)
.unwrap();
}
#[test]
fn parse_switch() {
parse_str(
"
fn main() {
var pos: f32;
switch (3) {
case 0, 1: { pos = 0.0; }
case 2: { pos = 1.0; }
default: { pos = 3.0; }
}
}
",
)
.unwrap();
}
#[test]
fn parse_switch_optional_colon_in_case() {
parse_str(
"
fn main() {
var pos: f32;
switch (3) {
case 0, 1 { pos = 0.0; }
case 2 { pos = 1.0; }
default { pos = 3.0; }
}
}
",
)
.unwrap();
}
#[test]
fn parse_switch_default_in_case() {
parse_str(
"
fn main() {
var pos: f32;
switch (3) {
case 0, 1: { pos = 0.0; }
case 2: {}
case default, 3: { pos = 3.0; }
}
}
",
)
.unwrap();
}
#[test]
fn parse_parentheses_switch() {
parse_str(
"
fn main() {
var pos: f32;
switch pos > 1.0 {
default: { pos = 3.0; }
}
}
",
)
.unwrap();
}
#[test]
fn parse_texture_load() {
parse_str(
"
var t: texture_3d<u32>;
fn foo() {
let r: vec4<u32> = textureLoad(t, vec3<u32>(0.0, 1.0, 2.0), 1);
}
",
)
.unwrap();
parse_str(
"
var t: texture_multisampled_2d_array<i32>;
fn foo() {
let r: vec4<i32> = textureLoad(t, vec2<i32>(10, 20), 2, 3);
}
",
)
.unwrap();
parse_str(
"
var t: texture_storage_1d_array<r32float,read>;
fn foo() {
let r: vec4<f32> = textureLoad(t, 10, 2);
}
",
)
.unwrap();
}
#[test]
fn parse_texture_store() {
parse_str(
"
var t: texture_storage_2d<rgba8unorm,write>;
fn foo() {
textureStore(t, vec2<i32>(10, 20), vec4<f32>(0.0, 1.0, 2.0, 3.0));
}
",
)
.unwrap();
}
#[test]
fn parse_texture_query() {
parse_str(
"
var t: texture_multisampled_2d_array<f32>;
fn foo() {
var dim: vec2<u32> = textureDimensions(t);
dim = textureDimensions(t, 0);
let layers: u32 = textureNumLayers(t);
let samples: u32 = textureNumSamples(t);
}
",
)
.unwrap();
}
#[test]
fn parse_postfix() {
parse_str(
"fn foo() {
let x: f32 = vec4<f32>(1.0, 2.0, 3.0, 4.0).xyz.rgbr.aaaa.wz.g;
let y: f32 = fract(vec2<f32>(0.5, x)).x;
}",
)
.unwrap();
}
#[test]
fn parse_expressions() {
parse_str("fn foo() {
let x: f32 = select(0.0, 1.0, true);
let y: vec2<f32> = select(vec2<f32>(1.0, 1.0), vec2<f32>(x, x), vec2<bool>(x < 0.5, x > 0.5));
let z: bool = !(0.0 == 1.0);
}").unwrap();
}
#[test]
fn parse_pointers() {
parse_str(
"fn foo() {
var x: f32 = 1.0;
let px = &x;
let py = frexp(0.5, px);
}",
)
.unwrap();
}
#[test]
fn parse_struct_instantiation() {
parse_str(
"
struct Foo {
a: f32,
b: vec3<f32>,
}
@fragment
fn fs_main() {
var foo: Foo = Foo(0.0, vec3<f32>(0.0, 1.0, 42.0));
}
",
)
.unwrap();
}
#[test]
fn parse_array_length() {
parse_str(
"
struct Foo {
data: array<u32>
} // this is used as both input and output for convenience
@group(0) @binding(0)
var<storage> foo: Foo;
@group(0) @binding(1)
var<storage> bar: array<u32>;
fn baz() {
var x: u32 = arrayLength(foo.data);
var y: u32 = arrayLength(bar);
}
",
)
.unwrap();
}
#[test]
fn parse_storage_buffers() {
parse_str(
"
@group(0) @binding(0)
var<storage> foo: array<u32>;
",
)
.unwrap();
parse_str(
"
@group(0) @binding(0)
var<storage,read> foo: array<u32>;
",
)
.unwrap();
parse_str(
"
@group(0) @binding(0)
var<storage,write> foo: array<u32>;
",
)
.unwrap();
parse_str(
"
@group(0) @binding(0)
var<storage,read_write> foo: array<u32>;
",
)
.unwrap();
}
#[test]
fn parse_alias() {
parse_str(
"
alias Vec4 = vec4<f32>;
",
)
.unwrap();
}