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

Allow nonexhaustive rules #233

Merged
merged 4 commits into from
Apr 9, 2021
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Helpful `rustc` error messages for errors in the grammar definition or the Rust
code embedded within it
* Rule-level tracing to debug grammars
* Opt-in nonexhaustive parsing

## Example

Expand Down
3 changes: 2 additions & 1 deletion peg-macros/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub struct Rule {
pub doc: Option<TokenStream>,
pub visibility: Option<TokenStream>,
pub cached: bool,
pub nonexhaustive: bool,
}

#[derive(Debug)]
Expand Down Expand Up @@ -100,4 +101,4 @@ pub enum BoundedRepeat {
Plus,
Exact(TokenStream),
Both(Option<TokenStream>, Option<TokenStream>),
}
}
146 changes: 82 additions & 64 deletions peg-macros/grammar.rs

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions peg-macros/grammar.rustpeg
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ rule grammar_args() -> Vec<(Ident, TokenStream)>
= "(" args:((i:IDENT() ":" t:$(rust_type()) { (i, t) })**",") ","? ")" { args }

rule peg_rule() -> Rule
= doc:rust_doc_comment() cached:cacheflag() visibility:rust_visibility()
= doc:rust_doc_comment() cached:cacheflag() visibility:rust_visibility() nonexhaustive:nonexhaustive_flag()
"rule" name:IDENT() ty_params:rust_ty_params()? "(" params:rule_param()**"," ")" ret_type:("->" t:$(rust_type()) {t})? "=" expr:expression() ";"?
{ Rule { doc, name, ty_params, params, expr, ret_type, visibility, cached } }
{ Rule { doc, name, ty_params, params, expr, ret_type, visibility, cached, nonexhaustive } }

rule cacheflag() -> bool = "#" "[" "cache" "]" {true} / {false}

rule nonexhaustive_flag() -> bool = "nonexhaustive" { true } / { false }

rule rust_ty_params() -> Vec<TokenStream>
= "<" p:(($(IDENT() / LIFETIME())) ++ ",") ">" { p }

Expand Down
21 changes: 15 additions & 6 deletions peg-macros/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ pub(crate) fn compile_grammar(grammar: &Grammar) -> TokenStream {
}
}
}

}

let doc = &grammar.doc;
Expand Down Expand Up @@ -239,6 +238,20 @@ fn compile_rule_export(context: &Context, rule: &Rule) -> TokenStream {
let extra_args_def = &context.extra_args_def;
let extra_args_call = &context.extra_args_call;

let matched_action = if rule.nonexhaustive {
quote! {
return Ok(__value)
}
} else {
quote! {
if __pos == __input.len() {
return Ok(__value)
} else {
__err_state.mark_failure(__pos, "EOF");
}
}
};

quote! {
#doc
#visibility fn #name<'input #(, #ty_params)*>(__input: &'input Input #extra_args_def #(, #rule_params)*) -> ::std::result::Result<#ret_ty, ::peg::error::ParseError<PositionRepr>> {
Expand All @@ -248,11 +261,7 @@ fn compile_rule_export(context: &Context, rule: &Rule) -> TokenStream {
let mut __state = ParseState::new();
match #parse_fn(__input, &mut __state, &mut __err_state, ::peg::Parse::start(__input) #extra_args_call #(, #rule_params_call)*) {
::peg::RuleResult::Matched(__pos, __value) => {
if __pos == __input.len() {
return Ok(__value)
} else {
__err_state.mark_failure(__pos, "EOF");
}
#matched_action
}
_ => ()
}
Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@
//! rule beginning and ending with `@` is an infix expression. Prefix and postfix rules have one
//! `@` at the beginning or end, and atoms do not include `@`.
//!
//! ### Exhaustiveness
//!
//! Normally, parsers fail if not all of the input is consumed. To opt in to non exhaustive
//! parsing, add the `nonexhaustive` modifier before the `rule` keyword. Note that you should take
//! care to not miss a malformed `thing` at the last position if you have a `thing()*` repeat
//! parser.
//!
//! ## Input types
//!
//! The first line of the grammar declares an input type. This is normally
Expand Down
2 changes: 1 addition & 1 deletion tests/compile-fail/syntax_error.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: expected one of "#", "crate", "pub", "rule", "use", "}"
error: expected one of "#", "crate", "nonexhaustive", "pub", "rule", "use", "}"
--> $DIR/syntax_error.rs:4:5
|
4 | fn asdf() {} //~ ERROR expected one of "#", "crate", "pub", "rule", "use", "}"
Expand Down
12 changes: 12 additions & 0 deletions tests/run-pass/nonexhaustive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
extern crate peg;
use peg::parser;

parser!{
pub grammar nonexhaustive() for [u8] {
pub nonexhaustive rule foo() = "foo"
}
}

fn main() {
assert_eq!(nonexhaustive::foo(b"foobar"), Ok(()));
}