Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved error recovery #36

Merged
merged 1 commit into from
May 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,190 changes: 5,190 additions & 0 deletions packages/parser/snapshots/tests/e2e/bevy_pbr_shadows-001.wgsl-ast

Large diffs are not rendered by default.

22 changes: 2 additions & 20 deletions packages/parser/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ impl Parse for AttributeList {
type Stream = ParseStream;

fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
let mut attributes = vec![];
while input.check(punct!["@"]) {
attributes.push(input.parse()?);
}
let attributes = input.parse_seq(|input| input.check(punct!["@"]))?;

Ok(Self {
attributes: attributes.into(),
Expand Down Expand Up @@ -279,23 +276,8 @@ impl Parse for ArgumentList {
type Stream = ParseStream;

fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
use Token::*;

let brace_open = input.consume(brace!["("])?;
let mut arguments = vec![];
while !input.check(brace![")"]) {
match input.peek() {
Some(Punct(lex, _)) if lex == "," => input.discard(),
Some(_) => arguments.push(input.parse::<Expr>()?),
None => {
return Err(SpannedError {
message: "Unexpected end of input".into(),
source: input.source(),
span: None,
})
}
};
}
let arguments = input.parse_seq_separated(punct![,], |input| !input.check(brace![")"]))?;
let brace_close = input.consume(brace![")"])?;

Ok(Self {
Expand Down
37 changes: 9 additions & 28 deletions packages/parser/src/decl/func.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Arc;

use gramatika::{Parse, ParseStreamer, Span, Spanned, SpannedError, Token as _};
use gramatika::{Parse, ParseStreamer, Span, Spanned};

use crate::{
common::{AttributeList, TypeDecl},
Expand Down Expand Up @@ -33,8 +33,6 @@ impl Parse for FunctionDecl {
type Stream = ParseStream;

fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
use TokenKind::*;

let storage = input.consume(keyword![fn])?;
let storage_modifier = if input.check(operator![<]) {
input.consume(operator![<])?;
Expand All @@ -48,32 +46,15 @@ impl Parse for FunctionDecl {
let name = input.consume_as(TokenKind::Ident, Token::function)?;
input.consume(brace!["("])?;

let mut params = vec![];
while !input.check(brace![")"]) {
match input.peek() {
Some(token) => match token.as_matchable() {
(Punct, "@", _) | (Ident, _, _) => {
let param = input.parse::<ParamDecl>()?;
params.push(Decl::Param(param));
}
(Punct, ",", _) => input.discard(),
(_, _, span) => {
return Err(SpannedError {
message: "Expected parameter, `,`, or `)`".into(),
span: Some(span),
source: input.source(),
})
}
},
None => {
return Err(SpannedError {
message: "Unexpected end of input".into(),
source: input.source(),
span: None,
})
let params = input.parse_seq_with_finisher(
|input| !input.check(brace![")"]),
|input, param| {
if !input.check(brace![")"]) {
input.consume(punct![,])?;
}
};
}
Ok(Decl::Param(param))
},
)?;

input.consume(brace![")"])?;
let return_ty = if input.check(operator![->]) {
Expand Down
10 changes: 2 additions & 8 deletions packages/parser/src/decl/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,8 @@ impl Parse for ImportPath {
(Brace, "{", _) => {
let brace_open = next;

let mut paths = vec![];
while !input.check(brace!("}")) {
paths.push(input.parse()?);

if !input.check(brace!("}")) {
input.consume(punct![,])?;
}
}
let paths =
input.parse_seq_separated(punct![,], |input| !input.check(brace!("}")))?;

let brace_close = input.consume(brace!("}"))?;

Expand Down
35 changes: 5 additions & 30 deletions packages/parser/src/decl/struc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Arc;

use gramatika::{Parse, ParseStreamer, Span, Spanned, SpannedError, Token as _};
use gramatika::{Parse, ParseStreamer, Span, Spanned};

use crate::{
common::{AttributeList, TypeDecl},
Expand Down Expand Up @@ -73,36 +73,11 @@ impl Parse for StructBody {
type Stream = ParseStream;

fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
use TokenKind::*;

let open_brace = input.consume(brace!["{"])?;

let mut fields = vec![];
while !input.check(brace!["}"]) {
match input.peek() {
Some(token) => match token.as_matchable() {
(Punct, "@", _) | (Ident, _, _) => {
let field = input.parse::<FieldDecl>()?;
fields.push(Decl::Field(field));
}
(_, _, span) => {
return Err(SpannedError {
message: "Expected field declaration".into(),
span: Some(span),
source: input.source(),
})
}
},
None => {
return Err(SpannedError {
message: "Unexpected end of input".into(),
source: input.source(),
span: None,
})
}
};
}

let fields = input.parse_seq_with_finisher::<FieldDecl, _>(
|input| !input.check(brace!["}"]),
|_, field| Ok(Decl::Field(field)),
)?;
let close_brace = input.consume(brace!["}"])?;

Ok(Self {
Expand Down
31 changes: 5 additions & 26 deletions packages/parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,33 +249,12 @@ impl Parse for SyntaxTree {
type Stream = ParseStream;

fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
// TODO: With errors now stored on the `ParseStream`, it should be
// possible to do more comprehensive error-recovery, e.g. in our
// `<ParseStream as ParseStreamer>::parse` implementation
let mut inner = vec![];
let mut recovering = false;

while let Some(token) = input.peek() {
match token {
Token::Punct(lexeme, _) if lexeme == ";" => input.discard(),
_ => match input.parse::<Decl>() {
Ok(decl) => {
if recovering {
recovering = false;
}
inner.push(decl);
}
Err(err) => {
if recovering {
input.discard();
} else {
input.errors.push(err);
recovering = true;
}
}
},
let inner = input.parse_seq::<Decl>(|input| {
while input.check(token::punct![;]) {
input.discard();
}
}
!input.is_empty()
})?;

Ok(Self { inner })
}
Expand Down
86 changes: 84 additions & 2 deletions packages/parser/src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use gramatika::{ArcStr, ParseStreamer, SpannedError, Token as _, TokenCtor};
use gramatika::{ArcStr, ParseStreamer, Spanned, SpannedError, Token as _, TokenCtor};

use crate::{
comment::Comment,
Expand Down Expand Up @@ -43,6 +43,88 @@ impl ParseStream {
self.inner.split_next(split_at, ctors)
}

pub fn parse_seq_separated<P>(
&mut self,
separator: Token,
loop_condition: impl Fn(&mut Self) -> bool,
) -> gramatika::Result<Vec<P>>
where
P: gramatika::Parse<Stream = Self>,
{
self.parse_seq_impl(&loop_condition, |input, parsed| {
if loop_condition(input) {
input.consume(separator.clone())?;
}
Ok(parsed)
})
}

pub fn parse_seq_with_finisher<P, R>(
&mut self,
loop_condition: impl Fn(&mut Self) -> bool,
finish: impl Fn(&mut Self, P) -> gramatika::Result<R>,
) -> gramatika::Result<Vec<R>>
where
P: gramatika::Parse<Stream = Self>,
{
self.parse_seq_impl(&loop_condition, finish)
}

pub fn parse_seq<P>(
&mut self,
loop_condition: impl Fn(&mut Self) -> bool,
) -> gramatika::Result<Vec<P>>
where
P: gramatika::Parse<Stream = Self>,
{
self.parse_seq_impl(&loop_condition, |_, parsed| Ok(parsed))
}

#[inline]
#[allow(clippy::type_complexity)]
fn parse_seq_impl<P, R>(
&mut self,
loop_condition: &impl Fn(&mut Self) -> bool,
finish: impl Fn(&mut Self, P) -> gramatika::Result<R>,
) -> gramatika::Result<Vec<R>>
where
P: gramatika::Parse<Stream = Self>,
{
let mut results = vec![];
let mut recovering = false;

while loop_condition(self) {
match self.parse::<P>() {
Ok(parsed) => {
if recovering {
recovering = false;
}

results.push(finish(self, parsed)?);
}
Err(err) => {
if recovering {
let needs_discard = self
.peek()
.and_then(|next_token| {
err.span.map(|err_span| (err_span, next_token.span()))
})
.map(|(err_span, next_token_span)| err_span.contains(next_token_span));

if matches!(needs_discard, Some(true)) {
self.discard();
}
} else {
self.errors.push(err);
recovering = true;
}
}
}
}

Ok(results)
}

fn did_parse_comment(&mut self) -> bool {
let Some(token) = self.inner.peek() else {
return false;
Expand Down Expand Up @@ -87,7 +169,7 @@ impl ParseStreamer for ParseStream {
type Token = Token;

fn is_empty(&mut self) -> bool {
self.inner.is_empty()
self.peek().is_none()
}

fn peek(&mut self) -> Option<&Token> {
Expand Down
7 changes: 1 addition & 6 deletions packages/parser/src/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,7 @@ impl Parse for BlockStmt {

fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
let brace_open = input.consume(brace!["{"])?;

let mut stmts = vec![];
while !input.check(brace!["}"]) {
stmts.push(input.parse::<Stmt>()?);
}

let stmts = input.parse_seq(|input| !input.check(brace!["}"]))?;
let brace_close = input.consume(brace!["}"])?;

Ok(Self {
Expand Down
5 changes: 5 additions & 0 deletions packages/parser/src/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ begin_snapshots!();
fn bevy_mesh_expanded() {
snapshot!(SyntaxTree include_str!("../../test-files/mesh.wgsl"));
}

#[snapshot_test]
fn bevy_pbr_shadows() {
snapshot!(SyntaxTree include_str!("../../test-files/modules/pbr_shadows.wgsl"));
}
Loading