diff --git a/src/front/wgsl/lexer.rs b/src/front/wgsl/lexer.rs index 212e41f2bf..1d019e94fe 100644 --- a/src/front/wgsl/lexer.rs +++ b/src/front/wgsl/lexer.rs @@ -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('<'), diff --git a/src/front/wgsl/mod.rs b/src/front/wgsl/mod.rs index a7c1fdced6..0e32f54ff9 100644 --- a/src/front/wgsl/mod.rs +++ b/src/front/wgsl/mod.rs @@ -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; 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), 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, - width: Option, - }, + 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 { - message: format!( - "expected unsigned integer literal, found `{}`", - &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 { + Error::BadNumber(ref bad_span, ref err) => ParseError { message: format!( - "expected integer literal, found `{}`", - &source[bad_span.clone()], + "{}: `{}`", + err,&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> { - 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> { 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)), + } + } + + /// 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> { + 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), + )), + } + } - 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 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> { + 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('(')) { diff --git a/src/front/wgsl/number.rs b/src/front/wgsl/number.rs new file mode 100644 index 0000000000..d7e8801b09 --- /dev/null +++ b/src/front/wgsl/number.rs @@ -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 { + 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, &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, +) -> Result { + 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, +) -> Result { + 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, +) -> Result { + 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, +) -> Result { + 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) -> Result { + 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) -> Result { + match kind { + None => { + let num = input.parse::().unwrap(); // will never fail + num.is_finite() + .then(|| Number::AbstractFloat(num)) + .ok_or(NumberError::NotRepresentable) + } + Some(FloatKind::F32) => { + let num = input.parse::().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, + radix: u32, + is_negative: bool, +) -> Result { + 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)), + }, + } +} diff --git a/src/front/wgsl/number_literals.rs b/src/front/wgsl/number_literals.rs deleted file mode 100644 index 6691ab2d05..0000000000 --- a/src/front/wgsl/number_literals.rs +++ /dev/null @@ -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> { - 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> { - 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> { - 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::().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> { - 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> { - 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> { - 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> { - 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), - }, - )), - } -} diff --git a/src/front/wgsl/tests.rs b/src/front/wgsl/tests.rs index 1f7e451f94..8c5604ecc4 100644 --- a/src/front/wgsl/tests.rs +++ b/src/front/wgsl/tests.rs @@ -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();