[wgsl-in] overhaul number lexing / parsing

Bring the lexer's parsing of numeric literals in line with the WGSL
specification as of 86a23b83 (2022-05-10).
This commit is contained in:
teoxoy
2022-05-02 17:02:35 +02:00
committed by Jim Blandy
parent 89bed99bcc
commit 53aa3e2df5
5 changed files with 724 additions and 709 deletions

View File

@@ -1,269 +1,10 @@
use super::{conv, Error, ExpectedToken, NumberType, Span, Token, TokenSpan};
use super::{conv, number::consume_number, Error, ExpectedToken, Span, Token, TokenSpan};
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)
}
/// Tries to skip a given prefix in the input string.
/// Returns whether the prefix was present and could therefore be skipped,
/// the remaining str and the number of *bytes* skipped.
pub fn try_skip_prefix<'a, 'b>(input: &'a str, prefix: &'b str) -> (bool, &'a str, usize) {
if let Some(rem) = input.strip_prefix(prefix) {
(true, rem, prefix.len())
} else {
(false, input, 0)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum NLDigitState {
Nothing,
LeadingZero,
DigitBeforeDot,
OnlyDot,
DigitsThenDot,
DigitAfterDot,
Exponent,
SignAfterExponent,
DigitAfterExponent,
}
struct NumberLexerState {
_minus: bool,
hex: bool,
leading_zeros: usize,
digit_state: NLDigitState,
uint_suffix: bool,
}
impl NumberLexerState {
// TODO: add proper error reporting, possibly through try_into_token function returning Result
pub fn _is_valid_number(&self) -> bool {
match *self {
Self {
_minus: false, // No negative zero for integers.
hex,
leading_zeros,
digit_state: NLDigitState::LeadingZero,
..
} => hex || leading_zeros == 1, // No leading zeros allowed in non-hex integers, "0" is always allowed.
Self {
_minus: minus,
hex,
leading_zeros,
digit_state: NLDigitState::DigitBeforeDot,
uint_suffix,
} => {
(hex || leading_zeros == 0) // No leading zeros allowed in non-hex integers.
// In this state the number has non-zero digits,
// i.e. it is not just "0".
&& (minus ^ uint_suffix) // Either a negative number, or and unsigned integer, not both.
}
_ => self.is_float(),
}
}
pub fn is_float(&self) -> bool {
!self.uint_suffix
&& (self.digit_state == NLDigitState::DigitsThenDot
|| self.digit_state == NLDigitState::DigitAfterDot
|| self.digit_state == NLDigitState::DigitAfterExponent)
}
}
fn consume_number(input: &str) -> (Token, &str) {
let (minus, working_substr, minus_offset) = try_skip_prefix(input, "-");
let (hex, working_substr, hex_offset) = try_skip_prefix(working_substr, "0x");
let mut state = NumberLexerState {
_minus: minus,
hex,
leading_zeros: 0,
digit_state: NLDigitState::Nothing,
uint_suffix: false,
};
let mut what = |c| {
match state {
NumberLexerState {
uint_suffix: true, ..
} => return false, // Scanning is done once we've reached a type suffix.
NumberLexerState {
hex,
digit_state: NLDigitState::Nothing,
..
} => match c {
'0' => {
state.digit_state = NLDigitState::LeadingZero;
state.leading_zeros += 1;
}
'1'..='9' => {
state.digit_state = NLDigitState::DigitBeforeDot;
}
'a'..='f' | 'A'..='F' if hex => {
state.digit_state = NLDigitState::DigitBeforeDot;
}
'.' => {
state.digit_state = NLDigitState::OnlyDot;
}
_ => return false,
},
NumberLexerState {
hex,
digit_state: NLDigitState::LeadingZero,
..
} => match c {
'0' => {
// We stay in NLDigitState::LeadingZero.
state.leading_zeros += 1;
}
'1'..='9' => {
state.digit_state = NLDigitState::DigitBeforeDot;
}
'a'..='f' | 'A'..='F' if hex => {
state.digit_state = NLDigitState::DigitBeforeDot;
}
'.' => {
state.digit_state = NLDigitState::DigitsThenDot;
}
'e' | 'E' if !hex => {
state.digit_state = NLDigitState::Exponent;
}
'p' | 'P' if hex => {
state.digit_state = NLDigitState::Exponent;
}
'u' => {
// We stay in NLDigitState::LeadingZero.
state.uint_suffix = true;
}
_ => return false,
},
NumberLexerState {
hex,
digit_state: NLDigitState::DigitBeforeDot,
..
} => match c {
'0'..='9' => {
// We stay in NLDigitState::DigitBeforeDot.
}
'a'..='f' | 'A'..='F' if hex => {
// We stay in NLDigitState::DigitBeforeDot.
}
'.' => {
state.digit_state = NLDigitState::DigitsThenDot;
}
'e' | 'E' if !hex => {
state.digit_state = NLDigitState::Exponent;
}
'p' | 'P' if hex => {
state.digit_state = NLDigitState::Exponent;
}
'u' => {
// We stay in NLDigitState::DigitBeforeDot.
state.uint_suffix = true;
}
_ => return false,
},
NumberLexerState {
hex,
digit_state: NLDigitState::OnlyDot,
..
} => match c {
'0'..='9' => {
state.digit_state = NLDigitState::DigitAfterDot;
}
'a'..='f' | 'A'..='F' if hex => {
state.digit_state = NLDigitState::DigitAfterDot;
}
_ => return false,
},
NumberLexerState {
hex,
digit_state: NLDigitState::DigitsThenDot | NLDigitState::DigitAfterDot,
..
} => match c {
'0'..='9' => {
state.digit_state = NLDigitState::DigitAfterDot;
}
'a'..='f' | 'A'..='F' if hex => {
state.digit_state = NLDigitState::DigitAfterDot;
}
'e' | 'E' if !hex => {
state.digit_state = NLDigitState::Exponent;
}
'p' | 'P' if hex => {
state.digit_state = NLDigitState::Exponent;
}
_ => return false,
},
NumberLexerState {
digit_state: NLDigitState::Exponent,
..
} => match c {
'0'..='9' => {
state.digit_state = NLDigitState::DigitAfterExponent;
}
'-' | '+' => {
state.digit_state = NLDigitState::SignAfterExponent;
}
_ => return false,
},
NumberLexerState {
digit_state: NLDigitState::SignAfterExponent | NLDigitState::DigitAfterExponent,
..
} => match c {
'0'..='9' => {
state.digit_state = NLDigitState::DigitAfterExponent;
}
_ => return false,
},
}
// No match branch has rejected this yet, so we are still in a number literal
true
};
let pos = working_substr
.find(|c| !what(c))
.unwrap_or(working_substr.len());
let (value, rest) = input.split_at(pos + minus_offset + hex_offset);
// NOTE: This code can use string slicing,
// because number literals are exclusively ASCII.
// This means all relevant characters fit into one byte
// and using string slicing (which slices UTF-8 bytes) works for us.
// TODO: A syntax error can already be recognized here, possibly report it at this stage.
// Return possibly knowably incorrect (given !state.is_valid_number()) token for now.
(
Token::Number {
value: if state.uint_suffix {
&value[..value.len() - 1]
} else {
value
},
ty: if state.uint_suffix {
NumberType::Uint
} else if state.is_float() {
NumberType::Float
} else {
NumberType::Sint
},
},
rest,
)
}
fn consume_token(input: &str, generic: bool) -> (Token<'_>, &str) {
let mut chars = input.chars();
let cur = match chars.next() {
@@ -632,6 +373,9 @@ impl<'a> Lexer<'a> {
}
}
#[cfg(test)]
use super::{number::Number, NumberError};
#[cfg(test)]
fn sub_test(source: &str, expected_tokens: &[Token]) {
let mut lex = Lexer::new(source);
@@ -641,41 +385,195 @@ fn sub_test(source: &str, expected_tokens: &[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 {
value: "92",
ty: NumberType::Sint,
},
Token::Word("No"),
],
&[Token::Number(Ok(Number::I32(92))), Token::Word("No")],
);
sub_test(
"2u3o",
&[
Token::Number {
value: "2",
ty: NumberType::Uint,
},
Token::Number {
value: "3",
ty: NumberType::Sint,
},
Token::Number(Ok(Number::U32(2))),
Token::Number(Ok(Number::I32(3))),
Token::Word("o"),
],
);
sub_test(
"2.4f44po",
&[
Token::Number {
value: "2.4",
ty: NumberType::Float,
},
Token::Word("f44po"),
Token::Number(Ok(Number::F32(2.4))),
Token::Number(Ok(Number::I32(44))),
Token::Word("po"),
],
);
sub_test(
@@ -715,10 +613,7 @@ fn test_variable_decl() {
Token::Attribute,
Token::Word("group"),
Token::Paren('('),
Token::Number {
value: "0",
ty: NumberType::Sint,
},
Token::Number(Ok(Number::I32(0))),
Token::Paren(')'),
Token::Word("var"),
Token::Paren('<'),

View File

@@ -7,7 +7,7 @@ Frontend for [WGSL][wgsl] (WebGPU Shading Language).
mod construction;
mod conv;
mod lexer;
mod number_literals;
mod number;
#[cfg(test)]
mod tests;
@@ -18,16 +18,10 @@ use crate::{
},
span::SourceLocation,
span::Span as NagaSpan,
Bytes, ConstantInner, FastHashMap, ScalarValue,
ConstantInner, FastHashMap, ScalarValue,
};
use self::{
lexer::Lexer,
number_literals::{
get_f32_literal, get_i32_literal, get_u32_literal, parse_generic_non_negative_int_literal,
parse_non_negative_sint_literal,
},
};
use self::{lexer::Lexer, number::Number};
use codespan_reporting::{
diagnostic::{Diagnostic, Label},
files::SimpleFile,
@@ -36,12 +30,11 @@ use codespan_reporting::{
termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor},
},
};
use hexf_parse::ParseHexfError;
use std::{
borrow::Cow,
convert::TryFrom,
io::{self, Write},
num::{NonZeroU32, ParseFloatError, ParseIntError},
num::NonZeroU32,
ops,
};
use thiserror::Error;
@@ -49,19 +42,12 @@ use thiserror::Error;
type Span = ops::Range<usize>;
type TokenSpan<'a> = (Token<'a>, Span);
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum NumberType {
Sint,
Uint,
Float,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Token<'a> {
Separator(char),
Paren(char),
Attribute,
Number { value: &'a str, ty: NumberType },
Number(Result<Number, NumberError>),
Word(&'a str),
Operation(char),
LogicalOperation(char),
@@ -75,14 +61,18 @@ pub enum Token<'a> {
End,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum NumberType {
I32,
U32,
F32,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ExpectedToken<'a> {
Token(Token<'a>),
Identifier,
Number {
ty: Option<NumberType>,
width: Option<Bytes>,
},
Number(NumberType),
Integer,
Constant,
/// Expected: constant, parenthesized expression, identifier
@@ -101,36 +91,25 @@ pub enum ExpectedToken<'a> {
GlobalItem,
}
#[derive(Clone, Debug, Error)]
pub enum BadIntError {
#[error(transparent)]
ParseIntError(#[from] ParseIntError),
#[error("non-hex negative zero integer literals are not allowed")]
NegativeZero,
#[error("leading zeros for non-hex integer literals are not allowed")]
LeadingZeros,
}
#[derive(Clone, Debug, Error)]
pub enum BadFloatError {
#[error(transparent)]
ParseFloatError(#[from] ParseFloatError),
#[error(transparent)]
ParseHexfError(#[from] ParseHexfError),
#[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(Clone, Debug)]
pub enum Error<'a> {
Unexpected(TokenSpan<'a>, ExpectedToken<'a>),
UnexpectedComponents(Span),
BadU32(Span, BadIntError),
BadI32(Span, BadIntError),
BadNumber(Span, NumberError),
/// A negative signed integer literal where both signed and unsigned,
/// but only non-negative literals are allowed.
NegativeInt(Span),
BadFloat(Span, BadFloatError),
BadU32Constant(Span),
BadScalarWidth(Span, Bytes),
BadMatrixScalarKind(Span, crate::ScalarKind, u8),
BadAccessor(Span),
BadTexture(Span),
@@ -147,7 +126,7 @@ pub enum Error<'a> {
BadIncrDecrReferenceType(Span),
InvalidResolve(ResolveError),
InvalidForInitializer(Span),
InvalidGatherComponent(Span, i32),
InvalidGatherComponent(Span, u32),
InvalidConstructorComponentType(Span, i32),
InvalidIdentifierUnderscore(Span),
ReservedIdentifierPrefix(Span),
@@ -192,9 +171,7 @@ impl<'a> Error<'a> {
Token::Separator(c) => format!("'{}'", c),
Token::Paren(c) => format!("'{}'", c),
Token::Attribute => "@".to_string(),
Token::Number { value, .. } => {
format!("number ({})", value)
}
Token::Number(_) => "number".to_string(),
Token::Word(s) => s.to_string(),
Token::Operation(c) => format!("operation ('{}')", c),
Token::LogicalOperation(c) => format!("logical operation ('{}')", c),
@@ -210,25 +187,12 @@ impl<'a> Error<'a> {
}
}
ExpectedToken::Identifier => "identifier".to_string(),
ExpectedToken::Number { ty, width } => {
let literal_ty_str = match ty {
Some(NumberType::Float) => "floating-point",
Some(NumberType::Uint) => "unsigned integer",
Some(NumberType::Sint) => "signed integer",
None => "arbitrary number",
};
if let Some(width) = width {
format!(
"{} literal of {}-bit width",
literal_ty_str,
width as u32 * 8,
)
} else {
format!(
"{} literal of arbitrary width",
literal_ty_str,
)
}
ExpectedToken::Number(ty) => {
match ty {
NumberType::I32 => "32-bit signed integer literal",
NumberType::U32 => "32-bit unsigned integer literal",
NumberType::F32 => "32-bit floating-point literal",
}.to_string()
},
ExpectedToken::Integer => "unsigned/signed integer literal".to_string(),
ExpectedToken::Constant => "constant".to_string(),
@@ -258,21 +222,13 @@ impl<'a> Error<'a> {
labels: vec![(bad_span.clone(), "unexpected components".into())],
notes: vec![],
},
Error::BadU32(ref bad_span, ref err) => ParseError {
Error::BadNumber(ref bad_span, ref err) => ParseError {
message: format!(
"expected unsigned integer literal, found `{}`",
&source[bad_span.clone()],
"{}: `{}`",
err,&source[bad_span.clone()],
),
labels: vec![(bad_span.clone(), "expected unsigned integer".into())],
notes: vec![err.to_string()],
},
Error::BadI32(ref bad_span, ref err) => ParseError {
message: format!(
"expected integer literal, found `{}`",
&source[bad_span.clone()],
),
labels: vec![(bad_span.clone(), "expected signed integer".into())],
notes: vec![err.to_string()],
labels: vec![(bad_span.clone(), err.to_string().into())],
notes: vec![],
},
Error::NegativeInt(ref bad_span) => ParseError {
message: format!(
@@ -282,14 +238,6 @@ impl<'a> Error<'a> {
labels: vec![(bad_span.clone(), "expected non-negative integer".into())],
notes: vec![],
},
Error::BadFloat(ref bad_span, ref err) => ParseError {
message: format!(
"expected floating-point literal, found `{}`",
&source[bad_span.clone()],
),
labels: vec![(bad_span.clone(), "expected floating-point literal".into())],
notes: vec![err.to_string()],
},
Error::BadU32Constant(ref bad_span) => ParseError {
message: format!(
"expected unsigned integer constant expression, found `{}`",
@@ -298,11 +246,6 @@ impl<'a> Error<'a> {
labels: vec![(bad_span.clone(), "expected unsigned integer".into())],
notes: vec![],
},
Error::BadScalarWidth(ref bad_span, width) => ParseError {
message: format!("invalid width of `{}` bits for literal", width as u32 * 8,),
labels: vec![(bad_span.clone(), "invalid width".into())],
notes: vec!["the only valid width is 32 for now".to_string()],
},
Error::BadMatrixScalarKind(
ref span,
kind,
@@ -1235,7 +1178,7 @@ impl BindingParser {
match name {
"location" => {
lexer.expect(Token::Paren('('))?;
self.location = Some(parse_non_negative_sint_literal(lexer, 4)?);
self.location = Some(Parser::parse_non_negative_i32_literal(lexer)?);
lexer.expect(Token::Paren(')'))?;
}
"builtin" => {
@@ -1424,38 +1367,45 @@ impl Parser {
lexer.span_from(initial)
}
fn get_constant_inner<'a>(
word: &'a str,
ty: NumberType,
token_span: TokenSpan<'a>,
) -> Result<ConstantInner, Error<'a>> {
let span = token_span.1;
let value = match ty {
NumberType::Sint => {
get_i32_literal(word, span).map(|val| crate::ScalarValue::Sint(val as i64))?
}
NumberType::Uint => {
get_u32_literal(word, span).map(|val| crate::ScalarValue::Uint(val as u64))?
}
NumberType::Float => {
get_f32_literal(word, span).map(|val| crate::ScalarValue::Float(val as f64))?
}
};
Ok(crate::ConstantInner::Scalar { value, width: 4 })
}
fn parse_switch_value<'a>(lexer: &mut Lexer<'a>, uint: bool) -> Result<i32, Error<'a>> {
let token_span = lexer.next();
let word = match token_span.0 {
Token::Number { value, .. } => value,
_ => return Err(Error::Unexpected(token_span, ExpectedToken::Integer)),
};
match token_span.0 {
Token::Number(Ok(Number::U32(num))) if uint => Ok(num as i32),
Token::Number(Ok(Number::I32(num))) if !uint => Ok(num),
Token::Number(Err(e)) => Err(Error::BadNumber(token_span.1, e)),
_ => Err(Error::Unexpected(token_span, ExpectedToken::Integer)),
}
}
match uint {
true => get_u32_literal(word, token_span.1).map(|v| v as i32),
false => get_i32_literal(word, token_span.1),
/// Parse a non-negative signed integer literal.
/// This is for attributes like `size`, `location` and others.
fn parse_non_negative_i32_literal<'a>(lexer: &mut Lexer<'a>) -> Result<u32, Error<'a>> {
match lexer.next() {
(Token::Number(Ok(Number::I32(num))), span) => {
u32::try_from(num).map_err(|_| Error::NegativeInt(span))
}
(Token::Number(Err(e)), span) => Err(Error::BadNumber(span, e)),
other => Err(Error::Unexpected(
other,
ExpectedToken::Number(NumberType::I32),
)),
}
}
/// Parse a non-negative integer literal that may be either signed or unsigned.
/// This is for the `workgroup_size` attribute and array lengths.
/// Note: these values should be no larger than [`i32::MAX`], but this is not checked here.
fn parse_generic_non_negative_int_literal<'a>(lexer: &mut Lexer<'a>) -> Result<u32, Error<'a>> {
match lexer.next() {
(Token::Number(Ok(Number::I32(num))), span) => {
u32::try_from(num).map_err(|_| Error::NegativeInt(span))
}
(Token::Number(Ok(Number::U32(num))), _) => Ok(num),
(Token::Number(Err(e)), span) => Err(Error::BadNumber(span, e)),
other => Err(Error::Unexpected(
other,
ExpectedToken::Number(NumberType::I32),
)),
}
}
@@ -1999,17 +1949,9 @@ impl Parser {
"textureGather" => {
let _ = lexer.next();
lexer.open_arguments()?;
let component = if let (
Token::Number {
value,
ty: NumberType::Sint,
},
span,
) = lexer.peek()
{
let _ = lexer.next();
let component = if let (Token::Number(..), span) = lexer.peek() {
let index = Self::parse_non_negative_i32_literal(lexer)?;
lexer.expect(Token::Separator(','))?;
let index = get_i32_literal(value, span.clone())?;
*crate::SwizzleComponent::XYZW
.get(index as usize)
.ok_or(Error::InvalidGatherComponent(span, index))?
@@ -2212,9 +2154,22 @@ impl Parser {
let inner = match first_token_span {
(Token::Word("true"), _) => crate::ConstantInner::boolean(true),
(Token::Word("false"), _) => crate::ConstantInner::boolean(false),
(Token::Number { value, ty }, _) => {
Self::get_constant_inner(value, ty, first_token_span)?
}
(Token::Number(num), _) => match num {
Ok(Number::I32(num)) => crate::ConstantInner::Scalar {
value: crate::ScalarValue::Sint(num as i64),
width: 4,
},
Ok(Number::U32(num)) => crate::ConstantInner::Scalar {
value: crate::ScalarValue::Uint(num as u64),
width: 4,
},
Ok(Number::F32(num)) => crate::ConstantInner::Scalar {
value: crate::ScalarValue::Float(num as f64),
width: 4,
},
Ok(Number::AbstractInt(_) | Number::AbstractFloat(_)) => unreachable!(),
Err(e) => return Err(Error::BadNumber(first_token_span.1, e)),
},
(Token::Word(name), name_span) => {
// look for an existing constant first
for (handle, var) in const_arena.iter() {
@@ -2305,10 +2260,8 @@ impl Parser {
self.pop_scope(lexer);
expr
}
token @ (Token::Word("true" | "false") | Token::Number { .. }, _) => {
let _ = lexer.next();
let const_handle =
self.parse_const_expression_impl(token, lexer, None, ctx.types, ctx.constants)?;
(Token::Word("true" | "false") | Token::Number(..), _) => {
let const_handle = self.parse_const_expression(lexer, ctx.types, ctx.constants)?;
let span = NagaSpan::from(self.pop_scope(lexer));
TypedExpression::non_reference(
ctx.interrupt_emitter(crate::Expression::Constant(const_handle), span),
@@ -2853,15 +2806,15 @@ impl Parser {
match lexer.next_ident_with_span()? {
("size", _) => {
lexer.expect(Token::Paren('('))?;
let (value, span) = lexer
.capture_span(|lexer| parse_non_negative_sint_literal(lexer, 4))?;
let (value, span) =
lexer.capture_span(Self::parse_non_negative_i32_literal)?;
lexer.expect(Token::Paren(')'))?;
size = Some(NonZeroU32::new(value).ok_or(Error::ZeroSizeOrAlign(span))?);
}
("align", _) => {
lexer.expect(Token::Paren('('))?;
let (value, span) = lexer
.capture_span(|lexer| parse_non_negative_sint_literal(lexer, 4))?;
let (value, span) =
lexer.capture_span(Self::parse_non_negative_i32_literal)?;
lexer.expect(Token::Paren(')'))?;
align = Some(NonZeroU32::new(value).ok_or(Error::ZeroSizeOrAlign(span))?);
}
@@ -4237,12 +4190,12 @@ impl Parser {
match lexer.next_ident_with_span()? {
("binding", _) => {
lexer.expect(Token::Paren('('))?;
bind_index = Some(parse_non_negative_sint_literal(lexer, 4)?);
bind_index = Some(Self::parse_non_negative_i32_literal(lexer)?);
lexer.expect(Token::Paren(')'))?;
}
("group", _) => {
lexer.expect(Token::Paren('('))?;
bind_group = Some(parse_non_negative_sint_literal(lexer, 4)?);
bind_group = Some(Self::parse_non_negative_i32_literal(lexer)?);
lexer.expect(Token::Paren(')'))?;
}
("vertex", _) => {
@@ -4256,8 +4209,9 @@ impl Parser {
}
("workgroup_size", _) => {
lexer.expect(Token::Paren('('))?;
workgroup_size = [1u32; 3];
for (i, size) in workgroup_size.iter_mut().enumerate() {
*size = parse_generic_non_negative_int_literal(lexer, 4)?;
*size = Self::parse_generic_non_negative_int_literal(lexer)?;
match lexer.next() {
(Token::Paren(')'), _) => break,
(Token::Separator(','), _) if i != 2 => (),
@@ -4269,11 +4223,6 @@ impl Parser {
}
}
}
for size in workgroup_size.iter_mut() {
if *size == 0 {
*size = 1;
}
}
}
("early_depth_test", _) => {
let conservative = if lexer.skip(Token::Paren('(')) {

445
src/front/wgsl/number.rs Normal file
View File

@@ -0,0 +1,445 @@
use std::borrow::Cow;
use super::{NumberError, 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) => {
use std::convert::TryFrom;
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(super) 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)|* => $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]
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(|| Number::AbstractFloat(num))
.ok_or(NumberError::NotRepresentable)
}
Some(FloatKind::F32) => {
let num = input.parse::<f32>().unwrap(); // will never fail
num.is_finite()
.then(|| 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

@@ -1,204 +0,0 @@
use std::convert::TryFrom;
use hexf_parse::parse_hexf32;
use crate::Bytes;
use super::{
lexer::{try_skip_prefix, Lexer},
BadFloatError, BadIntError, Error, ExpectedToken, NumberType, Span, Token,
};
fn check_int_literal(word_without_minus: &str, minus: bool, hex: bool) -> Result<(), BadIntError> {
let leading_zeros = word_without_minus
.bytes()
.take_while(|&b| b == b'0')
.count();
if word_without_minus == "0" && minus {
Err(BadIntError::NegativeZero)
} else if word_without_minus != "0" && !hex && leading_zeros != 0 {
Err(BadIntError::LeadingZeros)
} else {
Ok(())
}
}
pub fn get_i32_literal(word: &str, span: Span) -> Result<i32, Error<'_>> {
let (minus, word_without_minus, _) = try_skip_prefix(word, "-");
let (hex, word_without_minus_and_0x, _) = try_skip_prefix(word_without_minus, "0x");
check_int_literal(word_without_minus, minus, hex)
.map_err(|e| Error::BadI32(span.clone(), e))?;
let parsed_val = match (hex, minus) {
(true, true) => i32::from_str_radix(&format!("-{}", word_without_minus_and_0x), 16),
(true, false) => i32::from_str_radix(word_without_minus_and_0x, 16),
(false, _) => word.parse(),
};
parsed_val.map_err(|e| Error::BadI32(span, e.into()))
}
pub fn get_u32_literal(word: &str, span: Span) -> Result<u32, Error<'_>> {
let (minus, word_without_minus, _) = try_skip_prefix(word, "-");
let (hex, word_without_minus_and_0x, _) = try_skip_prefix(word_without_minus, "0x");
check_int_literal(word_without_minus, minus, hex)
.map_err(|e| Error::BadU32(span.clone(), e))?;
// We need to add a minus here as well, since the lexer also accepts syntactically incorrect negative uints
let parsed_val = match (hex, minus) {
(true, true) => u32::from_str_radix(&format!("-{}", word_without_minus_and_0x), 16),
(true, false) => u32::from_str_radix(word_without_minus_and_0x, 16),
(false, _) => word.parse(),
};
parsed_val.map_err(|e| Error::BadU32(span, e.into()))
}
pub fn get_f32_literal(word: &str, span: Span) -> Result<f32, Error<'_>> {
let hex = word.starts_with("0x") || word.starts_with("-0x");
let parsed_val = if hex {
parse_hexf32(word, false).map_err(BadFloatError::ParseHexfError)
} else {
word.parse::<f32>().map_err(BadFloatError::ParseFloatError)
};
parsed_val.map_err(|e| Error::BadFloat(span, e))
}
pub(super) fn _parse_uint_literal<'a>(
lexer: &mut Lexer<'a>,
width: Bytes,
) -> Result<u32, Error<'a>> {
let token_span = lexer.next();
if width != 4 {
// Only 32-bit literals supported by the spec and naga for now!
return Err(Error::BadScalarWidth(token_span.1, width));
}
match token_span {
(
Token::Number {
value,
ty: NumberType::Uint,
},
span,
) => get_u32_literal(value, span),
other => Err(Error::Unexpected(
other,
ExpectedToken::Number {
ty: Some(NumberType::Uint),
width: Some(width),
},
)),
}
}
/// Parse a non-negative signed integer literal.
/// This is for attributes like `size`, `location` and others.
pub(super) fn parse_non_negative_sint_literal<'a>(
lexer: &mut Lexer<'a>,
width: Bytes,
) -> Result<u32, Error<'a>> {
let token_span = lexer.next();
if width != 4 {
// Only 32-bit literals supported by the spec and naga for now!
return Err(Error::BadScalarWidth(token_span.1, width));
}
match token_span {
(
Token::Number {
value,
ty: NumberType::Sint,
},
span,
) => {
let i32_val = get_i32_literal(value, span.clone())?;
u32::try_from(i32_val).map_err(|_| Error::NegativeInt(span))
}
other => Err(Error::Unexpected(
other,
ExpectedToken::Number {
ty: Some(NumberType::Sint),
width: Some(width),
},
)),
}
}
/// Parse a non-negative integer literal that may be either signed or unsigned.
/// This is for the `workgroup_size` attribute and array lengths.
/// Note: these values should be no larger than [`i32::MAX`], but this is not checked here.
pub(super) fn parse_generic_non_negative_int_literal<'a>(
lexer: &mut Lexer<'a>,
width: Bytes,
) -> Result<u32, Error<'a>> {
let token_span = lexer.next();
if width != 4 {
// Only 32-bit literals supported by the spec and naga for now!
return Err(Error::BadScalarWidth(token_span.1, width));
}
match token_span {
(
Token::Number {
value,
ty: NumberType::Sint,
},
span,
) => {
let i32_val = get_i32_literal(value, span.clone())?;
u32::try_from(i32_val).map_err(|_| Error::NegativeInt(span))
}
(
Token::Number {
value,
ty: NumberType::Uint,
},
span,
) => get_u32_literal(value, span),
other => Err(Error::Unexpected(
other,
ExpectedToken::Number {
ty: Some(NumberType::Sint),
width: Some(width),
},
)),
}
}
pub(super) fn _parse_float_literal<'a>(
lexer: &mut Lexer<'a>,
width: Bytes,
) -> Result<f32, Error<'a>> {
let token_span = lexer.next();
if width != 4 {
// Only 32-bit literals supported by the spec and naga for now!
return Err(Error::BadScalarWidth(token_span.1, width));
}
match token_span {
(
Token::Number {
value,
ty: NumberType::Float,
},
span,
) => get_f32_literal(value, span),
other => Err(Error::Unexpected(
other,
ExpectedToken::Number {
ty: Some(NumberType::Float),
width: Some(width),
},
)),
}
}

View File

@@ -14,76 +14,6 @@ fn parse_comment() {
.unwrap();
}
// Regexes for the literals are taken from the working draft at
// https://www.w3.org/TR/2021/WD-WGSL-20210806/#literals
#[test]
fn parse_decimal_floats() {
// /^(-?[0-9]*\.[0-9]+|-?[0-9]+\.[0-9]*)((e|E)(\+|-)?[0-9]+)?$/
parse_str("let a : f32 = -1.;").unwrap();
parse_str("let a : f32 = -.1;").unwrap();
parse_str("let a : f32 = 42.1234;").unwrap();
parse_str("let a : f32 = -1.E3;").unwrap();
parse_str("let a : f32 = -.1e-5;").unwrap();
parse_str("let a : f32 = 2.3e+55;").unwrap();
assert!(parse_str("let a : f32 = 42.1234f;").is_err());
assert!(parse_str("let a : f32 = 42.1234f32;").is_err());
}
#[test]
fn parse_hex_floats() {
// /^-?0x([0-9a-fA-F]*\.?[0-9a-fA-F]+|[0-9a-fA-F]+\.[0-9a-fA-F]*)(p|P)(\+|-)?[0-9]+$/
parse_str("let a : f32 = -0xa.p1;").unwrap();
parse_str("let a : f32 = -0x.fp9;").unwrap();
parse_str("let a : f32 = 0x2a.4D2P4;").unwrap();
parse_str("let a : f32 = -0x.1p-5;").unwrap();
parse_str("let a : f32 = 0xC.8p+55;").unwrap();
parse_str("let a : f32 = 0x1p1;").unwrap();
assert!(parse_str("let a : f32 = 0x1p1f;").is_err());
assert!(parse_str("let a : f32 = 0x1p1f32;").is_err());
}
#[test]
fn parse_decimal_ints() {
// i32 /^-?0x[0-9a-fA-F]+|0|-?[1-9][0-9]*$/
parse_str("let a : i32 = 0;").unwrap();
parse_str("let a : i32 = 1092;").unwrap();
parse_str("let a : i32 = -9923;").unwrap();
assert!(parse_str("let a : i32 = -0;").is_err());
assert!(parse_str("let a : i32 = 01;").is_err());
assert!(parse_str("let a : i32 = 1.0;").is_err());
assert!(parse_str("let a : i32 = 1i;").is_err());
assert!(parse_str("let a : i32 = 1i32;").is_err());
// u32 /^0x[0-9a-fA-F]+u|0u|[1-9][0-9]*u$/
parse_str("let a : u32 = 0u;").unwrap();
parse_str("let a : u32 = 1092u;").unwrap();
assert!(parse_str("let a : u32 = -0u;").is_err());
assert!(parse_str("let a : u32 = 01u;").is_err());
assert!(parse_str("let a : u32 = 1.0u;").is_err());
assert!(parse_str("let a : u32 = 1u32;").is_err());
}
#[test]
fn parse_hex_ints() {
// i32 /^-?0x[0-9a-fA-F]+|0|-?[1-9][0-9]*$/
parse_str("let a : i32 = -0x0;").unwrap();
parse_str("let a : i32 = 0x2a4D2;").unwrap();
assert!(parse_str("let a : i32 = 0x2a4D2i;").is_err());
assert!(parse_str("let a : i32 = 0x2a4D2i32;").is_err());
// u32 /^0x[0-9a-fA-F]+u|0u|[1-9][0-9]*u$/
parse_str("let a : u32 = 0x0u;").unwrap();
parse_str("let a : u32 = 0x2a4D2u;").unwrap();
assert!(parse_str("let a : u32 = 0x2a4D2u32;").is_err());
}
#[test]
fn parse_types() {
parse_str("let a : i32 = 2;").unwrap();