[wgsl-in] Use codespan to report errors

This commit is contained in:
Joshua Groves
2021-02-28 00:52:34 -03:30
committed by Dzmitry Malyshau
parent 53f7f9de92
commit bb7105387d
5 changed files with 148 additions and 50 deletions

View File

@@ -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"

View File

@@ -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" => {

View File

@@ -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)]

View File

@@ -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(())
}
}

View File

@@ -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
"###
);
}