mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
[wgsl-in] Use codespan to report errors
This commit is contained in:
committed by
Dzmitry Malyshau
parent
53f7f9de92
commit
bb7105387d
@@ -12,6 +12,7 @@ license = "MIT OR Apache-2.0"
|
||||
[dependencies]
|
||||
bitflags = "1"
|
||||
bit-set = "0.5"
|
||||
codespan-reporting = { version = "0.11.0", optional = true }
|
||||
fxhash = "0.2"
|
||||
log = "0.4"
|
||||
num-traits = "0.2"
|
||||
@@ -32,7 +33,7 @@ serialize = ["serde"]
|
||||
deserialize = ["serde"]
|
||||
spv-in = ["petgraph", "spirv"]
|
||||
spv-out = ["spirv"]
|
||||
wgsl-in = []
|
||||
wgsl-in = ["codespan-reporting"]
|
||||
|
||||
[[bin]]
|
||||
name = "convert"
|
||||
|
||||
@@ -100,7 +100,14 @@ fn main() {
|
||||
#[cfg(feature = "wgsl-in")]
|
||||
"wgsl" => {
|
||||
let input = fs::read_to_string(input_path).unwrap();
|
||||
naga::front::wgsl::parse_str(&input).unwrap_pretty()
|
||||
let result = naga::front::wgsl::parse_str(&input);
|
||||
match result {
|
||||
Ok(v) => v,
|
||||
Err(ref e) => {
|
||||
e.emit_to_stderr();
|
||||
panic!("unable to parse WGSL");
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "glsl-in")]
|
||||
"vert" => {
|
||||
|
||||
@@ -258,7 +258,7 @@ impl<'a> Lexer<'a> {
|
||||
pub(super) fn next_ident(&mut self) -> Result<&'a str, Error<'a>> {
|
||||
match self.next() {
|
||||
(Token::Word(word), _) => Ok(word),
|
||||
other => Err(Error::Unexpected(other, "ident")),
|
||||
other => Err(Error::Unexpected(other, "identifier")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,10 +305,6 @@ impl<'a> Lexer<'a> {
|
||||
self.expect(Token::Paren('>'))?;
|
||||
Ok(format)
|
||||
}
|
||||
|
||||
pub(super) fn offset_from(&self, source: &'a str) -> usize {
|
||||
source.len() - self.input.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -14,11 +14,21 @@ use crate::{
|
||||
};
|
||||
|
||||
use self::lexer::Lexer;
|
||||
use std::{num::NonZeroU32, ops::Range};
|
||||
use codespan_reporting::{
|
||||
diagnostic::{Diagnostic, Label},
|
||||
files::SimpleFile,
|
||||
term::{
|
||||
self,
|
||||
termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor},
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
num::NonZeroU32,
|
||||
ops::Range,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
const SPACE: &str = " ";
|
||||
|
||||
type TokenSpan<'a> = (Token<'a>, Range<usize>);
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
@@ -44,6 +54,27 @@ pub enum Token<'a> {
|
||||
End,
|
||||
}
|
||||
|
||||
impl<'a> Error<'a> {
|
||||
fn as_parse_error(&self, source: &'a str) -> ParseError<'a> {
|
||||
match self {
|
||||
Error::Unexpected((_, unexpected_span), expected) => ParseError {
|
||||
message: format!(
|
||||
"expected {}, found '{}'",
|
||||
expected,
|
||||
&source[unexpected_span.clone()]
|
||||
),
|
||||
labels: vec![(unexpected_span.clone(), format!("expected {}", expected))],
|
||||
source,
|
||||
},
|
||||
error => ParseError {
|
||||
message: error.to_string(),
|
||||
labels: Vec::new(),
|
||||
source,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum Error<'a> {
|
||||
#[error("unexpected token {:?}, expected {1}", (.0).0)]
|
||||
@@ -332,13 +363,55 @@ struct ParsedVariable<'a> {
|
||||
init: Option<Handle<crate::Constant>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
#[error("error while parsing WGSL in scopes {scopes:?} at line {line} pos {pos}: {error}")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParseError<'a> {
|
||||
pub error: Error<'a>,
|
||||
pub scopes: Vec<Scope>,
|
||||
pub line: usize,
|
||||
pub pos: usize,
|
||||
message: String,
|
||||
labels: Vec<(Range<usize>, String)>,
|
||||
source: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> ParseError<'a> {
|
||||
fn diagnostic(&self) -> Diagnostic<()> {
|
||||
let diagnostic = Diagnostic::error()
|
||||
.with_message(self.message.to_string())
|
||||
.with_labels(
|
||||
self.labels
|
||||
.iter()
|
||||
.map(|label| {
|
||||
Label::primary((), label.0.clone()).with_message(label.1.to_string())
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
diagnostic
|
||||
}
|
||||
|
||||
pub fn emit_to_stderr(&self) {
|
||||
let files = SimpleFile::new("wgsl", self.source);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
let writer = StandardStream::stderr(ColorChoice::Always);
|
||||
term::emit(&mut writer.lock(), &config, &files, &self.diagnostic())
|
||||
.expect("cannot write error");
|
||||
}
|
||||
|
||||
pub fn emit_to_string(&self) -> String {
|
||||
let files = SimpleFile::new("wgsl", self.source);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
let mut writer = StringErrorBuffer::new();
|
||||
term::emit(&mut writer, &config, &files, &self.diagnostic()).expect("cannot write error");
|
||||
writer.into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for ParseError<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::error::Error for ParseError<'a> {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Parser {
|
||||
@@ -2324,42 +2397,12 @@ impl Parser {
|
||||
let mut lookup_global_expression = FastHashMap::default();
|
||||
loop {
|
||||
match self.parse_global_decl(&mut lexer, &mut module, &mut lookup_global_expression) {
|
||||
Err(error) => {
|
||||
let pos = lexer.offset_from(source);
|
||||
let (mut rows, mut cols) = (0, 1);
|
||||
let (mut prev_line, mut cur_line) = ("", "");
|
||||
for line in source[..pos].lines() {
|
||||
rows += 1;
|
||||
cols = line.len();
|
||||
prev_line = cur_line;
|
||||
cur_line = line;
|
||||
}
|
||||
log::error!("|\t{}", prev_line);
|
||||
log::error!(
|
||||
">\t{}{}",
|
||||
cur_line,
|
||||
source[pos..].lines().next().unwrap_or_default()
|
||||
);
|
||||
if cols <= SPACE.len() {
|
||||
log::error!("|\t{}^", &SPACE[..cols]);
|
||||
}
|
||||
return Err(ParseError {
|
||||
error,
|
||||
scopes: std::mem::replace(&mut self.scopes, Vec::new()),
|
||||
line: rows,
|
||||
pos: cols,
|
||||
});
|
||||
}
|
||||
Err(error) => return Err(error.as_parse_error(lexer.source)),
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
if !self.scopes.is_empty() {
|
||||
log::error!("Reached the end of file, but scopes are not closed");
|
||||
return Err(ParseError {
|
||||
error: Error::Other,
|
||||
scopes: std::mem::replace(&mut self.scopes, Vec::new()),
|
||||
line: 0,
|
||||
pos: 0,
|
||||
});
|
||||
return Err(Error::Other.as_parse_error(lexer.source));
|
||||
};
|
||||
return Ok(module);
|
||||
}
|
||||
@@ -2371,3 +2414,42 @@ impl Parser {
|
||||
pub fn parse_str(source: &str) -> Result<crate::Module, ParseError> {
|
||||
Parser::new().parse(source)
|
||||
}
|
||||
|
||||
pub struct StringErrorBuffer {
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl StringErrorBuffer {
|
||||
pub fn new() -> Self {
|
||||
Self { buf: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
String::from_utf8(self.buf).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for StringErrorBuffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.extend(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteColor for StringErrorBuffer {
|
||||
fn supports_color(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#[cfg(feature = "wgsl-in")]
|
||||
macro_rules! err {
|
||||
($value:expr, @$snapshot:literal) => {
|
||||
::insta::assert_snapshot!(naga::front::wgsl::parse_str($value).expect_err("expected parser error").to_string(), @$snapshot);
|
||||
::insta::assert_snapshot!(
|
||||
naga::front::wgsl::parse_str($value)
|
||||
.expect_err("expected parser error")
|
||||
.emit_to_string(),
|
||||
@$snapshot
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +15,13 @@ macro_rules! err {
|
||||
fn function_without_identifier() {
|
||||
err!(
|
||||
"fn () {}",
|
||||
@"error while parsing WGSL in scopes [FunctionDecl] at line 1 pos 4: unexpected token Paren('('), expected ident"
|
||||
@r###"
|
||||
error: expected identifier, found '('
|
||||
┌─ wgsl:1:4
|
||||
│
|
||||
1 │ fn () {}
|
||||
│ ^ expected identifier
|
||||
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user