///|
/// Parse a sequence of groups: the top level, the inside of an opener-closer
/// pair, or the body of a block.
///
/// Consumes the closer when there is one. The shape of this function is the
/// reference's: a dispatch on the next token, with every arm either finishing
/// the sequence, consuming something and recurring, or parsing one group and
/// recurring for the rest.
fn Parser::parse_groups(
self : Parser,
start : Int,
sg : GState,
) -> GroupsResult raise @err.ShrubberyError {
let mut i = start
let mut sg = sg
// The reference recurs in tail position on every arm that continues the
// sequence; a loop here says the same thing and keeps the stack shallow on a
// file with thousands of top-level groups.
for ;; {
let done = () => {
// A pending `#//` with no group to comment out is an error, but one that
// is still only "pending at the end" may yet find a group in the caller.
match sg.commenting {
Some(c) => self.fail_no_comment_group(c)
None => ()
}
(
{
groups: [],
rest: i,
end_line: sg.last_line,
end_delta: sg.delta,
end_token: None,
tail_commenting: sg.tail_commenting,
tail_raw: sg.raw,
} : GroupsResult)
}
if self.at_end(i) {
match sg.closer {
Delim(expected, opener) =>
self.fail(opener, DidNotFindMatching(expected~))
_ => ()
}
return done()
}
let t = self.tok(i)
let column = t.column()
// Indentation matters only for a token that STARTS a line. A `|` on the
// same line as a dedented closer still belongs to the enclosing construct,
// which is what the second half of this test protects.
let less_indented = sg.count &&
sg.closer.is_column() &&
(match sg.closer {
Col(c) => self.col_lt(t, column, c)
_ => false
}) &&
!line_eq_plus(t.line(), sg.last_line, sg.delta)
let check_column = (t : @lexer.Token, column : @column.Column) => {
if sg.check_column {
match sg.column {
Some(c) =>
if !self.col_eq(t, column, c) {
self.fail(t, WrongIndentation(missing_colon_hint=false))
}
None => ()
}
}
}
if t.kind is GroupComment {
match sg.commenting {
Some(c) => self.fail_no_comment_group(c)
None => ()
}
let line = t.line()
let a = self.next_of(
i + 1,
Some(line),
sg.delta,
sg.raw.push(t),
sg.count,
)
if sg.count &&
line_gt(line, sg.last_line) &&
self.next_line(a.rest, a.last_line, sg.count) {
// On its own line, so its column does not constrain anything.
sg = {
..sg,
last_line: a.last_line,
delta: a.delta,
tail_commenting: Some(t),
raw: a.raw,
}
i = a.rest
continue
}
if less_indented {
return done()
}
check_column(t, column)
sg = {
..sg,
check_column: self.next_line(a.rest, a.last_line, sg.count),
column: Some(sg.column.unwrap_or(column)),
bar_column: None,
last_line: a.last_line,
delta: a.delta,
tail_commenting: Some(t),
raw: a.raw,
}
i = a.rest
continue
}
if less_indented {
return done()
}
match t.kind {
Closer => {
if sg.closer.is_column() {
return done()
}
match sg.closer {
Delim(expected, _) =>
if expected != t.text {
self.fail(t, ExpectedCloser(expected~, found=t.text))
}
_ => self.fail(t, UnexpectedCloser(found=t.text))
}
let raw = sg.raw.push(t)
if sg.closer.is_delim() {
match sg.commenting {
Some(c) => self.fail_no_comment_group(c)
None => ()
}
return {
groups: [],
rest: i + 1,
end_line: Some(t.line()),
end_delta: sg.delta,
end_token: Some(t),
tail_commenting: None,
tail_raw: raw,
}
}
// At the top level an extra closer is reported and then skipped, so
// that the rest of the file is still parsed.
sg = { ..sg, last_line: Some(t.line()), raw, }
i = i + 1
continue
}
Whitespace | Comment | ContinueOperator => {
let a = self.next_of(i, sg.last_line, sg.delta, sg.raw, sg.count)
sg = { ..sg, last_line: a.last_line, delta: a.delta, raw: a.raw, }
i = a.rest
continue
}
CommaOperator => {
if sg.closer.is_column() {
return done()
}
if !(sg.paren_immed.is_immed() && sg.comma_time) {
self.fail(t, MisplacedComma(paren_immed=sg.paren_immed.is_immed()))
}
let a = self.next_of(
i + 1,
Some(t.line()),
sg.delta,
sg.raw.push(t),
sg.count,
)
// A comma counts as whitespace for indentation, so what line it was on
// does not matter -- which is what lets a leading `,` sit anywhere.
let nl = self.next_line(a.rest, sg.last_line, sg.count)
let bar_column = if nl || self.at_end(a.rest) {
sg.column
} else {
Some(self.tok(a.rest).column())
}
sg = {
..sg,
check_column: nl,
bar_column,
last_line: a.last_line,
comma_time: false,
delta: a.delta,
raw: a.raw,
}
i = a.rest
continue
}
SemicolonOperator => {
if sg.block_mode is Inside {
return done()
}
check_column(t, column)
let a = self.next_of(
i + 1,
Some(t.line()),
sg.delta,
sg.raw.push(t),
sg.count,
)
let next_t = self.peek(a.rest)
let is_splice = match next_t {
Some(nt) => nt.kind is Opener && nt.text == "\u{AB}"
None => false
}
if is_splice {
return self.parse_splice(t, a, sg)
}
if sg.paren_immed.is_immed() {
self.fail(t, MisplacedSemicolon)
}
sg = {
..sg,
check_column: self.next_line(a.rest, a.last_line, sg.count),
column: Some(sg.column.unwrap_or(column)),
bar_column: match next_t {
Some(nt) => Some(nt.column())
None => None
},
last_line: a.last_line,
delta: a.delta,
commenting: sg.tail_commenting,
tail_commenting: None,
block_mode: next_group_block_mode(sg.block_mode),
raw: a.raw,
}
i = a.rest
continue
}
_ => ()
}
if sg.comma_time && !(sg.paren_immed is AtArgs) {
self.fail(t, MissingCommaBeforeGroup)
}
let starts_bar = !(sg.block_mode is Start) && t.kind is BarOperator
if starts_bar {
return self.parse_bar_group(t, i, sg, column)
}
return self.parse_one_group(t, i, sg, column, check_column)
}
}
///|
/// The block mode a group sequence carries into its next group.
fn next_group_block_mode(mode : BlockMode) -> BlockMode {
match mode {
// Kept, so that a `|` may still follow.
Opened(_) => mode
_ => NoBlock
}
}