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

Add #{|input, pos| ... } custom expr syntax to replace undocumented ##method() #395

Merged
merged 3 commits into from
Jan 20, 2025
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
21 changes: 16 additions & 5 deletions peg-macros/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ impl<'a> LeftRecursionVisitor<'a> {
nullable
}

LiteralExpr(_) | PatternExpr(_) | MethodExpr(_, _) | FailExpr(_) | MarkerExpr(_) => false,
| LiteralExpr(_)
| PatternExpr(_)
| MethodExpr(_, _)
| CustomExpr(_)
| FailExpr(_)
| MarkerExpr(_) => false,

PositionExpr => true,
}
Expand Down Expand Up @@ -220,7 +225,7 @@ impl<'a> LoopNullabilityVisitor<'a> {
let name = rule_ident.to_string();
*self.rule_nullability.get(&name).unwrap_or(&false)
}

ActionExpr(ref elems, ..) => {
let mut nullable = true;
for elem in elems {
Expand Down Expand Up @@ -250,7 +255,7 @@ impl<'a> LoopNullabilityVisitor<'a> {
if inner_nullable && sep_nullable && !bound.has_upper_bound() {
self.errors.push(LoopNullabilityError { span: this_expr.span });
}

inner_nullable | !bound.has_lower_bound()
}

Expand All @@ -269,10 +274,16 @@ impl<'a> LoopNullabilityVisitor<'a> {
}
}

nullable
nullable
}

LiteralExpr(_) | PatternExpr(_) | MethodExpr(_, _) | FailExpr(_) | MarkerExpr(_) => false,
| LiteralExpr(_)
| PatternExpr(_)
| MethodExpr(_, _)
| CustomExpr(_)
| FailExpr(_)
| MarkerExpr(_) => false,

PositionExpr => true,
}
}
Expand Down
1 change: 1 addition & 0 deletions peg-macros/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ pub enum Expr {
PatternExpr(Group),
RuleExpr(Ident, Vec<RuleArg>),
MethodExpr(Ident, TokenStream),
CustomExpr(Group),
ChoiceExpr(Vec<SpannedExpr>),
OptionalExpr(Box<SpannedExpr>),
Repeat { inner: Box<SpannedExpr>, bound: BoundedRepeat, sep: Option<Box<SpannedExpr>> },
Expand Down
33 changes: 25 additions & 8 deletions peg-macros/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3440,7 +3440,7 @@ pub mod peg {
);
match __seq_res { :: peg :: RuleResult :: Matched (__pos , sp) => { { let __seq_res = __parse_BRACKET_GROUP (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , p) => { :: peg :: RuleResult :: Matched (__pos , (|| { PatternExpr (p) . at (sp) }) ()) } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , }
};
match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => { let __choice_res = match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "(") { :: peg :: RuleResult :: Matched (__pos , __val) => { { let __seq_res = __parse_sp (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , sp) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "@") { :: peg :: RuleResult :: Matched (__pos , __val) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , ")") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , (|| { MarkerExpr (true) . at (sp) }) ()) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\")\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"@\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"(\"") ; :: peg :: RuleResult :: Failed } } ; match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => { let __choice_res = { let __seq_res = __parse_sp (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , sp) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "@") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , (|| { MarkerExpr (false) . at (sp) }) ()) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"@\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } ; match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => { let __choice_res = { let __seq_res = __parse_sp (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , sp) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "##") { :: peg :: RuleResult :: Matched (__pos , __val) => { { let __seq_res = __parse_IDENT (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , method) => { { let __seq_res = __parse_PAREN_GROUP (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , args) => { :: peg :: RuleResult :: Matched (__pos , (|| { MethodExpr (method , args . stream ()) . at (sp) }) ()) } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"##\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } ; match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "(") { :: peg :: RuleResult :: Matched (__pos , __val) => { { let __seq_res = __parse_expression (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , expression) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , ")") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , (|| { expression }) ()) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\")\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"(\"") ; :: peg :: RuleResult :: Failed } } } } } } } } }
match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => { let __choice_res = match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "(") { :: peg :: RuleResult :: Matched (__pos , __val) => { { let __seq_res = __parse_sp (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , sp) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "@") { :: peg :: RuleResult :: Matched (__pos , __val) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , ")") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , (|| { MarkerExpr (true) . at (sp) }) ()) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\")\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"@\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"(\"") ; :: peg :: RuleResult :: Failed } } ; match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => { let __choice_res = { let __seq_res = __parse_sp (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , sp) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "@") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , (|| { MarkerExpr (false) . at (sp) }) ()) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"@\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } ; match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => { let __choice_res = { let __seq_res = __parse_sp (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , sp) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "##") { :: peg :: RuleResult :: Matched (__pos , __val) => { { let __seq_res = __parse_IDENT (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , method) => { { let __seq_res = __parse_PAREN_GROUP (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , args) => { :: peg :: RuleResult :: Matched (__pos , (|| { MethodExpr (method , args . stream ()) . at (sp) }) ()) } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"##\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } ; match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => { let __choice_res = { let __seq_res = __parse_sp (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , sp) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "#") { :: peg :: RuleResult :: Matched (__pos , __val) => { { let __seq_res = __parse_BRACE_GROUP (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , code) => { :: peg :: RuleResult :: Matched (__pos , (|| { CustomExpr (code) . at (sp) }) ()) } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"#\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } ; match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "(") { :: peg :: RuleResult :: Matched (__pos , __val) => { { let __seq_res = __parse_expression (__input , __state , __err_state , __pos) ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , expression) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , ")") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , (|| { expression }) ()) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\")\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"(\"") ; :: peg :: RuleResult :: Failed } } } } } } } } } } }
}
}
}
Expand Down Expand Up @@ -3503,7 +3503,11 @@ pub mod peg {
let mut __repeat_value = vec![];
loop {
let __pos = __repeat_pos;
let __step_res = __input.eat_until(__pos, ',');
let __step_res = ::peg::call_custom_closure(
(|input, pos| input.eat_until(pos, ',')),
__input,
__pos,
);
match __step_res {
::peg::RuleResult::Matched(__newpos, __value) => {
__repeat_pos = __newpos;
Expand Down Expand Up @@ -3639,7 +3643,7 @@ pub mod peg {
__pos: usize,
) -> ::peg::RuleResult<Span> {
#![allow(non_snake_case, unused, clippy::redundant_closure_call)]
__input.next_span(__pos)
::peg::call_custom_closure((|input, pos| input.next_span(pos)), __input, __pos)
}
fn __parse_KEYWORD<'input>(
__input: &'input Input,
Expand Down Expand Up @@ -3755,7 +3759,8 @@ pub mod peg {
};
match __seq_res {
::peg::RuleResult::Matched(__pos, _) => {
let __seq_res = __input.ident(__pos);
let __seq_res =
::peg::call_custom_closure((|input, pos| input.ident(pos)), __input, __pos);
match __seq_res {
::peg::RuleResult::Matched(__pos, i) => {
::peg::RuleResult::Matched(__pos, (|| i)())
Expand All @@ -3774,7 +3779,7 @@ pub mod peg {
__pos: usize,
) -> ::peg::RuleResult<Literal> {
#![allow(non_snake_case, unused, clippy::redundant_closure_call)]
__input.literal(__pos)
::peg::call_custom_closure((|input, pos| input.literal(pos)), __input, __pos)
}
fn __parse_PAREN_GROUP<'input>(
__input: &'input Input,
Expand All @@ -3783,7 +3788,11 @@ pub mod peg {
__pos: usize,
) -> ::peg::RuleResult<Group> {
#![allow(non_snake_case, unused, clippy::redundant_closure_call)]
__input.group(__pos, Delimiter::Parenthesis)
::peg::call_custom_closure(
(|input, pos| input.group(pos, Delimiter::Parenthesis)),
__input,
__pos,
)
}
fn __parse_BRACE_GROUP<'input>(
__input: &'input Input,
Expand All @@ -3792,7 +3801,11 @@ pub mod peg {
__pos: usize,
) -> ::peg::RuleResult<Group> {
#![allow(non_snake_case, unused, clippy::redundant_closure_call)]
__input.group(__pos, Delimiter::Brace)
::peg::call_custom_closure(
(|input, pos| input.group(pos, Delimiter::Brace)),
__input,
__pos,
)
}
fn __parse_BRACKET_GROUP<'input>(
__input: &'input Input,
Expand All @@ -3801,7 +3814,11 @@ pub mod peg {
__pos: usize,
) -> ::peg::RuleResult<Group> {
#![allow(non_snake_case, unused, clippy::redundant_closure_call)]
__input.group(__pos, Delimiter::Bracket)
::peg::call_custom_closure(
(|input, pos| input.group(pos, Delimiter::Bracket)),
__input,
__pos,
)
}
fn __parse_LIFETIME<'input>(
__input: &'input Input,
Expand Down
15 changes: 8 additions & 7 deletions peg-macros/grammar.rustpeg
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ rule primary() -> SpannedExpr
/ "(" sp:sp() "@" ")" { MarkerExpr(true).at(sp) }
/ sp:sp() "@" { MarkerExpr(false).at(sp) }
/ sp:sp() "##" method:IDENT() args:PAREN_GROUP() { MethodExpr(method, args.stream()).at(sp) }
/ sp:sp() "#" code:BRACE_GROUP() { CustomExpr(code).at(sp) }
/ "(" expression:expression() ")" { expression }

rule rule_arg() -> RuleArg
= "<" e:expression() ">" { RuleArg::Peg(e) }
/ tt:$( ##eat_until(',')+ ) { RuleArg::Rust(tt) }
/ tt:$( #{|input, pos| input.eat_until(pos, ',')}+ ) { RuleArg::Rust(tt) }

rule precedence_level() -> PrecedenceLevel
= operators:precedence_op()+
Expand All @@ -153,13 +154,13 @@ rule precedence_op() -> PrecedenceOperator
= span:sp() elements:labeled()* action:BRACE_GROUP()
{ PrecedenceOperator{ span, elements, action } }

rule sp() -> Span = ##next_span()
rule sp() -> Span = #{|input, pos| input.next_span(pos)}
rule KEYWORD() = "pub" / "rule" / "use" / "type" / "where"
rule IDENT() -> Ident = !KEYWORD() i:##ident() {i}
rule LITERAL() -> Literal = ##literal()
rule PAREN_GROUP() -> Group = ##group(Delimiter::Parenthesis)
rule BRACE_GROUP() -> Group = ##group(Delimiter::Brace)
rule BRACKET_GROUP() -> Group = ##group(Delimiter::Bracket)
rule IDENT() -> Ident = !KEYWORD() i:#{|input, pos| input.ident(pos)} {i}
rule LITERAL() -> Literal = #{|input, pos| input.literal(pos)}
rule PAREN_GROUP() -> Group = #{|input, pos| input.group(pos, Delimiter::Parenthesis)}
rule BRACE_GROUP() -> Group = #{|input, pos| input.group(pos, Delimiter::Brace)}
rule BRACKET_GROUP() -> Group = #{|input, pos| input.group(pos, Delimiter::Bracket)}
rule LIFETIME() = "'" IDENT()
rule INTEGER() = LITERAL()

Expand Down
5 changes: 5 additions & 0 deletions peg-macros/translate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,11 @@ fn compile_expr(context: &Context, e: &SpannedExpr, result_used: bool) -> TokenS
quote_spanned! { span=> __input.#method(__pos, #args) }
}

CustomExpr(ref code) => {
let code = code.stream();
quote_spanned! { span=> ::peg::call_custom_closure((#code), __input, __pos) }
}

ChoiceExpr(ref exprs) => ordered_choice(
span,
exprs
Expand Down
11 changes: 10 additions & 1 deletion peg-runtime/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ pub mod str;
/// The result type used internally in the parser.
///
/// You'll only need this if implementing the `Parse*` traits for a custom input
/// type. The public API of a parser adapts errors to `std::result::Result`.
/// type, or using the `#{}` syntax to embed a custom Rust snippet within the parser.
///
/// The public API of a parser adapts errors to `std::result::Result` instead of using this type.
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
pub enum RuleResult<T> {
/// Success, with final location
Expand Down Expand Up @@ -58,3 +60,10 @@ pub trait ParseSlice<'input>: Parse {
extern crate alloc;
#[cfg(not(feature = "std"))]
extern crate core as std;

// needed for type inference on the `#{|input, pos| ..}` closure, since there
// are different type inference rules on closures in function args.
#[doc(hidden)]
pub fn call_custom_closure<I, T>(f: impl FnOnce(I, usize) -> RuleResult<T>, input: I, pos: usize) -> RuleResult<T> {
f(input, pos)
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@
//! at the current location.
//! * `precedence!{ ... }` - Parse infix, prefix, or postfix expressions by precedence climbing.
//! [(details)](#precedence-climbing)
//! * `#{|input, pos| ... }` - _Custom:_ The provided closure is passed the full input and current
//! parse position, and returns a [`RuleResult`].
//!
//! ## Expression details
//!
Expand Down
25 changes: 25 additions & 0 deletions tests/run-pass/custom_expr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use peg::RuleResult;

peg::parser!( grammar test() for str {
rule position() -> usize = #{|input, pos| RuleResult::Matched(pos, pos)}
pub rule test1() -> usize = ['a']* p1:position() ['b']* { p1 }

pub rule fail() -> usize = #{|input, pos| RuleResult::Failed}

rule custom_literal(literal: &str) = #{|input, pos| {
let l = literal.len();
if input.len() >= pos + l && &input.as_bytes()[pos..pos + l] == literal.as_bytes() {
RuleResult::Matched(pos + l, ())
} else {
RuleResult::Failed
}
}}
pub rule test2() = custom_literal("foo") "_" custom_literal("bar")
});

fn main() {
assert_eq!(test::test1("aaaabb"), Ok(4));
assert_eq!(test::fail("aaaabb").unwrap_err().location.offset, 0);

assert_eq!(test::test2("foo_bar"), Ok(()));
}
Loading