Fix indentation checking some more

This commit is contained in:
Richard Feldman 2019-10-01 21:07:19 +03:00
parent adb5ba7f5e
commit 4dddea0bb1
4 changed files with 81 additions and 37 deletions

View file

@ -211,9 +211,10 @@ pub fn space1<'a>(min_indent: u16) -> impl Parser<'a, &'a [CommentOrNewline<'a>]
#[inline(always)]
fn spaces<'a>(
require_at_least_one: bool,
_min_indent: u16,
min_indent: u16,
) -> impl Parser<'a, &'a [CommentOrNewline<'a>]> {
move |arena: &'a Bump, state: State<'a>| {
let original_state = state.clone();
let mut chars = state.input.chars().peekable();
let mut space_list = Vec::new_in(arena);
let mut chars_parsed = 0;
@ -228,16 +229,23 @@ fn spaces<'a>(
match comment_parsing {
CommentParsing::No => match ch {
' ' => {
// Don't check indentation here; it might not be enough
// indentation yet, but maybe it will be after more spaces happen!
state = state.advance_spaces(1)?;
}
'\n' => {
// No need to check indentation because we're about to reset it anyway.
state = state.newline()?;
// Newlines only get added to the list when they're outside comments.
space_list.push(Newline);
}
'#' => {
state = state.advance_without_indenting(1)?;
// Check indentation to make sure we were indented enough
// before this comment began.
state = state
.check_indent(min_indent)?
.advance_without_indenting(1)?;
// We're now parsing a line comment!
comment_parsing = CommentParsing::Line;
@ -248,6 +256,11 @@ fn spaces<'a>(
// but we require parsing at least one space!
Err(unexpected(nonblank, 0, state.clone(), state.attempting))
} else {
// First make sure we were indented enough!
state = state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state))?;
Ok((space_list.into_bump_slice(), state))
};
}
@ -255,7 +268,8 @@ fn spaces<'a>(
CommentParsing::Line => {
match ch {
' ' => {
state = state.advance_spaces(1)?;
// If we're in a line comment, this won't affect indentation anyway.
state = state.advance_without_indenting(1)?;
comment_line_buf.push(ch);
}
@ -300,6 +314,7 @@ fn spaces<'a>(
CommentParsing::Block => {
match ch {
' ' => {
// Block comments *do* interact with indentation.
state = state.advance_spaces(1)?;
comment_line_buf.push(ch);
@ -313,7 +328,7 @@ fn spaces<'a>(
comment_line_buf = String::new_in(arena);
}
'#' => {
// Three '#' in a row means the comment is finished.
// Three '#' chars in a row means the block comment is finished.
//
// We want to peek ahead two characters to see if there
// are another two '#' there. If so, this comment is done.
@ -384,6 +399,11 @@ fn spaces<'a>(
if require_at_least_one && chars_parsed == 0 {
Err(unexpected_eof(0, state.attempting, state))
} else {
// First make sure we were indented enough!
state = state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state))?;
Ok((space_list.into_bump_slice(), state))
}
}

View file

@ -200,13 +200,16 @@ pub fn def<'a>(min_indent: u16) -> impl Parser<'a, Def<'a>> {
// TODO support type annotations
map_with_arena(
and(
// A pattern followed by '='
skip_second(
space0_after(loc_closure_param(min_indent), min_indent),
equals_for_def(),
),
// Spaces after the '=' (at a normal indentation level) and then the expr.
// The expr itself must be indented more than the pattern and '='
space0_before(
loc(move |arena, state| parse_expr(indented_more, arena, state)),
indented_more,
min_indent,
),
),
|arena, (loc_pattern, loc_expr)| Def::BodyOnly(loc_pattern, arena.alloc(loc_expr)),
@ -226,11 +229,7 @@ fn parse_def_expr<'a>(
Err((
Fail {
attempting: state.attempting,
reason: FailReason::DefOutdentedTooFar(
original_indent,
min_indent,
loc_first_pattern.region,
),
reason: FailReason::OutdentedTooFar,
},
state,
))
@ -242,6 +241,8 @@ fn parse_def_expr<'a>(
let indented_more = original_indent + 1;
then(
attempt(
Attempting::Def,
and(
// Parse the body of the first def. It doesn't need any spaces
// around it parsed, because both the subsquent defs and the
@ -257,7 +258,8 @@ fn parse_def_expr<'a>(
// It should be indented the same amount as the original.
space1_before(
loc(move |arena, state| parse_expr(original_indent, arena, state)),
indented_more,
original_indent,
),
),
),
),

View file

@ -38,12 +38,26 @@ impl<'a> State<'a> {
input,
line: 0,
column: 0,
indent_col: 1,
indent_col: 0,
is_indenting: true,
attempting,
}
}
pub fn check_indent(self, min_indent: u16) -> Result<Self, (Fail, Self)> {
if self.indent_col < min_indent {
Err((
Fail {
attempting: self.attempting,
reason: FailReason::OutdentedTooFar,
},
self,
))
} else {
Ok(self)
}
}
/// Increments the line, then resets column, indent_col, and is_indenting.
/// Advances the input by 1, to consume the newline character.
pub fn newline(&self) -> Result<Self, (Fail, Self)> {
@ -136,7 +150,7 @@ pub type ParseResult<'a, Output> = Result<(Output, State<'a>), (Fail, State<'a>)
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FailReason {
Unexpected(char, Region),
DefOutdentedTooFar(u16, u16, Region),
OutdentedTooFar,
ConditionFailed,
LineTooLong(u32 /* which line was too long */),
TooManyLines,

View file

@ -638,17 +638,21 @@ mod test_parse {
let arena = Bump::new();
let newlines = bumpalo::vec![in &arena; Newline, Newline];
let def = Def::BodyOnly(
Located::new(0, 0, 0, 1, Identifier("x")),
arena.alloc(Located::new(0, 0, 2, 3, Int("5"))),
Located::new(1, 1, 0, 1, Identifier("x")),
arena.alloc(Located::new(1, 1, 2, 3, Int("5"))),
);
let defs = bumpalo::vec![in &arena; (Vec::new_in(&arena).into_bump_slice(), def)];
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
let loc_ret = Located::new(2, 2, 0, 2, ret);
let expected = Defs(arena.alloc((defs, loc_ret)));
let loc_ret = Located::new(3, 3, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" reset indentation")];
let expected = Expr::SpaceBefore(
arena.alloc(Defs(arena.alloc((defs, loc_ret)))),
reset_indentation.into_bump_slice(),
);
assert_parses_to(
indoc!(
r#"
r#"# reset indentation
x=5
42
@ -663,17 +667,21 @@ mod test_parse {
let arena = Bump::new();
let newlines = bumpalo::vec![in &arena; Newline, Newline];
let def = Def::BodyOnly(
Located::new(0, 0, 0, 1, Identifier("x")),
arena.alloc(Located::new(0, 0, 4, 5, Int("5"))),
Located::new(1, 1, 0, 1, Identifier("x")),
arena.alloc(Located::new(1, 1, 4, 5, Int("5"))),
);
let defs = bumpalo::vec![in &arena; (Vec::new_in(&arena).into_bump_slice(), def)];
let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice());
let loc_ret = Located::new(2, 2, 0, 2, ret);
let expected = Defs(arena.alloc((defs, loc_ret)));
let loc_ret = Located::new(3, 3, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" reset indentation")];
let expected = Expr::SpaceBefore(
arena.alloc(Defs(arena.alloc((defs, loc_ret)))),
reset_indentation.into_bump_slice(),
);
assert_parses_to(
indoc!(
r#"
r#"# reset indentation
x = 5
42