mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Fix indentation checking some more
This commit is contained in:
parent
adb5ba7f5e
commit
4dddea0bb1
4 changed files with 81 additions and 37 deletions
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue