parse multi-backtracking

This commit is contained in:
Folkert 2021-03-19 00:10:02 +01:00
parent 85309444e1
commit 73e6128ce3
6 changed files with 250 additions and 90 deletions

View file

@ -34,8 +34,8 @@ pub fn test_parse_expr<'a>(
}
}
#[derive(Debug, Clone, Copy)]
enum MultiBackpassing {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MultiBackpassing {
Allow,
Disallow,
}
@ -159,6 +159,7 @@ fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> {
fn parse_loc_term<'a>(
min_indent: u16,
multi_backpassing: MultiBackpassing,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
@ -166,7 +167,10 @@ fn parse_loc_term<'a>(
loc_expr_in_parens_etc_help(min_indent),
loc!(specialize(EExpr::Str, string_literal_help())),
loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize(EExpr::Lambda, closure_help(min_indent))),
loc!(specialize(
EExpr::Lambda,
closure_help(min_indent, multi_backpassing)
)),
loc!(record_literal_help(min_indent)),
loc!(specialize(EExpr::List, list_literal_help(min_indent))),
loc!(map_with_arena!(
@ -179,13 +183,17 @@ fn parse_loc_term<'a>(
fn loc_possibly_negative_or_negated_term<'a>(
min_indent: u16,
multi_backpassing: MultiBackpassing,
) -> impl Parser<'a, Located<Expr<'a>>, EExpr<'a>> {
one_of![
|arena, state: State<'a>| {
let initial = state;
let (_, (loc_op, loc_expr), state) = and!(loc!(unary_negate()), |a, s| parse_loc_term(
min_indent, a, s
min_indent,
multi_backpassing,
a,
s
))
.parse(arena, state)?;
@ -197,7 +205,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
loc!(specialize(EExpr::Number, number_literal_help())),
loc!(map_with_arena!(
and!(loc!(word1(b'!', EExpr::Start)), |a, s| {
parse_loc_term(min_indent, a, s)
parse_loc_term(min_indent, multi_backpassing, a, s)
}),
|arena: &'a Bump, (loc_op, loc_expr): (Located<_>, _)| {
Expr::UnaryOp(
@ -208,7 +216,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
)),
|arena, state| {
// TODO use parse_loc_term_better
parse_loc_term(min_indent, arena, state)
parse_loc_term(min_indent, multi_backpassing, arena, state)
}
]
}
@ -258,9 +266,18 @@ fn parse_expr_start<'a>(
state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
one_of![
loc!(specialize(EExpr::If, if_expr_help(min_indent))),
loc!(specialize(EExpr::When, when::expr_help(min_indent))),
loc!(specialize(EExpr::Lambda, closure_help(min_indent))),
loc!(specialize(
EExpr::If,
if_expr_help(min_indent, multi_backpassing)
)),
loc!(specialize(
EExpr::When,
when::expr_help(min_indent, multi_backpassing)
)),
loc!(specialize(
EExpr::Lambda,
closure_help(min_indent, multi_backpassing)
)),
loc!(move |a, s| parse_expr_operator_chain(min_indent, multi_backpassing, start, a, s)),
fail_expr_start_e()
]
@ -274,7 +291,8 @@ fn parse_expr_operator_chain<'a>(
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let (_, expr, state) = loc_possibly_negative_or_negated_term(min_indent).parse(arena, state)?;
let (_, expr, state) =
loc_possibly_negative_or_negated_term(min_indent, multi_backpassing).parse(arena, state)?;
let initial = state;
let end = state.get_position();
@ -746,6 +764,7 @@ fn parse_defs_end<'a>(
parse_defs_end(multi_backpassing, start, def_state, arena, state)
}
_ => Ok((MadeProgress, def_state, initial)),
},
}
@ -813,7 +832,8 @@ fn parse_expr_operator<'a>(
BinOp::Minus if expr_state.end != op_start && op_end == new_start => {
// negative terms
let (_, negated_expr, state) = parse_loc_term(min_indent, arena, state)?;
let (_, negated_expr, state) =
parse_loc_term(min_indent, multi_backpassing, arena, state)?;
let new_end = state.get_position();
let arg = numeric_negate_expression(
@ -1041,7 +1061,9 @@ fn parse_expr_operator<'a>(
parse_defs_expr(multi_backpassing, start, def_state, arena, state)
}
_ => match loc_possibly_negative_or_negated_term(min_indent).parse(arena, state) {
_ => match loc_possibly_negative_or_negated_term(min_indent, multi_backpassing)
.parse(arena, state)
{
Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)),
Ok((_, mut new_expr, state)) => {
let new_end = state.get_position();
@ -1107,7 +1129,7 @@ fn parse_expr_end<'a>(
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let parser = skip_first!(
crate::blankspace::check_indent(min_indent, EExpr::IndentEnd),
move |a, s| parse_loc_term(min_indent, a, s)
move |a, s| parse_loc_term(min_indent, multi_backpassing, a, s)
);
match parser.parse(arena, state) {
@ -1167,24 +1189,87 @@ fn parse_expr_end<'a>(
state,
)
}
Err((NoProgress, _, _)) => {
// roll back space parsing
let state = expr_state.initial;
Err((NoProgress, _, mut state)) => {
// try multi-backpassing
if multi_backpassing == MultiBackpassing::Allow && state.bytes.starts_with(b",")
{
state.bytes = &state.bytes[1..];
state.column += 1;
if expr_state.operators.is_empty() {
let expr = to_call(arena, expr_state.arguments, expr_state.expr);
let (_, mut patterns, state) = specialize_ref(
EExpr::Pattern,
crate::parser::sep_by0(
word1(b',', EPattern::Start),
space0_around_ee(
crate::pattern::loc_pattern_help(min_indent),
min_indent,
EPattern::Space,
EPattern::Start,
EPattern::IndentEnd,
),
),
)
.parse(arena, state)?;
Ok((MadeProgress, expr.value, state))
} else {
let mut expr = to_call(arena, expr_state.arguments, expr_state.expr);
expr_state.consume_spaces(arena);
let call = to_call(arena, expr_state.arguments, expr_state.expr);
for (left_arg, op) in expr_state.operators.into_iter().rev() {
let region = Region::span_across(&left_arg.region, &expr.region);
let new = Expr::BinOp(arena.alloc((left_arg, op, expr)));
expr = Located::at(region, new);
let loc_pattern = Located::at(
call.region,
expr_to_pattern_help(arena, &call.value).unwrap(),
);
patterns.insert(0, loc_pattern);
match word2(b'<', b'-', EExpr::BackpassArrow).parse(arena, state) {
Err((_, fail, state)) => Err((MadeProgress, fail, state)),
Ok((_, _, state)) => {
let parse_body = space0_before_e(
move |a, s| parse_loc_expr(min_indent + 1, a, s),
min_indent,
EExpr::Space,
EExpr::IndentEnd,
);
let (_, loc_body, state) = parse_body.parse(arena, state)?;
let parse_cont = space0_before_e(
move |a, s| parse_loc_expr(min_indent, a, s),
min_indent,
EExpr::Space,
EExpr::IndentEnd,
);
let (_, loc_cont, state) = parse_cont.parse(arena, state)?;
let ret = Expr::Backpassing(
patterns.into_bump_slice(),
arena.alloc(loc_body),
arena.alloc(loc_cont),
);
Ok((MadeProgress, ret, state))
}
}
} else {
// roll back space parsing
let state = expr_state.initial;
Ok((MadeProgress, expr.value, state))
if expr_state.operators.is_empty() {
let expr = to_call(arena, expr_state.arguments, expr_state.expr);
Ok((MadeProgress, expr.value, state))
} else {
let mut expr = to_call(arena, expr_state.arguments, expr_state.expr);
for (left_arg, op) in expr_state.operators.into_iter().rev() {
let region = Region::span_across(&left_arg.region, &expr.region);
let new = Expr::BinOp(arena.alloc((left_arg, op, expr)));
expr = Located::at(region, new);
}
Ok((MadeProgress, expr.value, state))
}
}
}
}
@ -1200,6 +1285,14 @@ fn parse_loc_expr<'a>(
parse_loc_expr_with_options(min_indent, MultiBackpassing::Allow, arena, state)
}
pub fn parse_loc_expr_no_multi_backpassing<'a>(
min_indent: u16,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
parse_loc_expr_with_options(min_indent, MultiBackpassing::Disallow, arena, state)
}
fn parse_loc_expr_with_options<'a>(
min_indent: u16,
multi_backpassing: MultiBackpassing,
@ -1401,7 +1494,10 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Def<'a>>>, E
// PARSER HELPERS
fn closure_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
fn closure_help<'a>(
min_indent: u16,
multi_backpassing: MultiBackpassing,
) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
map_with_arena!(
skip_first!(
// All closures start with a '\' - e.g. (\x -> x + 1)
@ -1427,9 +1523,9 @@ fn closure_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
word2(b'-', b'>', ELambda::Arrow),
// Parse the body
space0_before_e(
specialize_ref(ELambda::Body, move |arena, state| parse_loc_expr(
min_indent, arena, state
)),
specialize_ref(ELambda::Body, move |arena, state| {
parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state)
}),
min_indent,
ELambda::Space,
ELambda::IndentBody
@ -1451,14 +1547,17 @@ mod when {
use crate::ast::WhenBranch;
/// Parser for when expressions.
pub fn expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, When<'a>> {
pub fn expr_help<'a>(
min_indent: u16,
multi_backpassing: MultiBackpassing,
) -> impl Parser<'a, Expr<'a>, When<'a>> {
then(
and!(
when_with_indent(),
skip_second!(
space0_around_ee(
specialize_ref(When::Condition, move |arena, state| {
parse_loc_expr(min_indent, arena, state)
parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state)
}),
min_indent,
When::Space,
@ -1702,7 +1801,10 @@ fn if_branch<'a>(
}
}
fn if_expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, If<'a>> {
fn if_expr_help<'a>(
min_indent: u16,
multi_backpassing: MultiBackpassing,
) -> impl Parser<'a, Expr<'a>, If<'a>> {
move |arena: &'a Bump, state| {
let (_, _, state) = parser::keyword_e(keyword::IF, If::If).parse(arena, state)?;
@ -1733,7 +1835,7 @@ fn if_expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, If<'a>> {
let (_, else_branch, state) = space0_before_e(
specialize_ref(If::ElseBranch, move |arena, state| {
parse_loc_expr(min_indent, arena, state)
parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state)
}),
min_indent,
If::Space,
@ -1825,11 +1927,8 @@ fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, List<'a>>
move |arena, state| {
let (_, (parsed_elems, final_comments), state) = collection_trailing_sep_e!(
word1(b'[', List::Open),
specialize_ref(List::Expr, move |a, s| parse_loc_expr_with_options(
min_indent,
MultiBackpassing::Disallow,
a,
s
specialize_ref(List::Expr, move |a, s| parse_loc_expr_no_multi_backpassing(
min_indent, a, s
)),
word1(b',', List::End),
word1(b']', List::End),
@ -1878,12 +1977,9 @@ fn record_field_help<'a>(
word1(b'?', ERecord::QuestionMark)
),
space0_before_e(
specialize_ref(ERecord::Expr, move |a, s| parse_loc_expr_with_options(
min_indent,
MultiBackpassing::Disallow,
a,
s
)),
specialize_ref(ERecord::Expr, move |a, s| {
parse_loc_expr_no_multi_backpassing(min_indent, a, s)
}),
min_indent,
ERecord::Space,
ERecord::IndentEnd,