mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
[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:
@@ -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('<'),
|
||||
|
||||
@@ -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
445
src/front/wgsl/number.rs
Normal 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)),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user