///|
/// What to read.
///
/// The mode decides the shape of the answer, which is why it is a parameter
/// rather than something inferred: `Top` always gives a `multi`, `Text` always
/// gives a `brackets`.
pub(all) enum Mode {
/// A whole document. Every group must start at the same column.
Top
/// As if the whole input were inside `@{`…`}`.
Text
} derive(Eq)
///|
/// A successful parse, and anything reported along the way.
///
/// `diagnostics` is non-empty only in recovery mode, where parsing continues
/// past a failure so that a file with three mistakes reports three. Outside
/// recovery, the first failure is raised instead.
pub struct Parsed {
root : @ast.Node
diagnostics : Array[@err.Diagnostic]
}
///|
pub fn Parsed::root(self : Parsed) -> @ast.Node {
self.root
}
///|
pub fn Parsed::diagnostics(self : Parsed) -> Array[@err.Diagnostic] {
self.diagnostics
}
///|
/// Parse shrubbery notation.
///
/// `start_column` accounts for characters that logically precede the input, for
/// an embedder splicing this into something larger; it affects indentation only,
/// never the positions reported.
pub fn parse(
src : String,
mode? : Mode = Top,
variant? : @lexer.Variant = @lexer.default_variant,
start_column? : @column.Column = @column.zero,
recover? : Bool = false,
) -> Parsed raise @err.ShrubberyError {
let toks = @lexer.lex_all(src, variant~, start_column~)
parse_tokens(toks, mode~, variant~, recover~)
}
///|
/// Parse an already-scanned token stream.
///
/// Exposed because a consumer that has its own reason to look at the tokens --
/// an editor, a highlighter -- should not have to scan twice.
pub fn parse_tokens(
toks : Array[@lexer.Token],
mode? : Mode = Top,
variant? : @lexer.Variant = @lexer.default_variant,
recover? : Bool = false,
) -> Parsed raise @err.ShrubberyError {
// The end-of-input token has served its purpose in the scanner; the parser
// asks "are there tokens left?" instead, as the reference does.
let body = []
for t in toks {
if !(t.kind is EndOfInput) {
body.push(t)
}
}
let p = { toks: body, diags: [], recover, }
let fallback = if body.length() > 0 {
body[0].span
} else {
@basic.Span::at(@basic.start)
}
match mode {
Top => {
let result = p.parse_groups(0, {
count: true,
closer: Eof,
paren_immed: NotImmed,
column: None,
bar_column: None,
check_column: false,
bar_closes: false,
bar_closes_line: None,
block_mode: NoBlock,
can_empty: true,
comma_time: false,
sequence_mode: AnyNumber,
last_line: Some(-1),
delta: 0,
commenting: None,
tail_commenting: None,
raw: RNil,
variant,
})
match result.tail_commenting {
Some(c) => p.fail_no_comment_group(c)
None => ()
}
let items = bars_insert_alts(result.groups, fallback)
let root = @ast.Node::new(Multi(items), span_over(items, fallback))
root.meta.suffix = result.tail_raw.to_raw()
normalize_group_raw(root)
{ root, diagnostics: p.diags, }
}
Text => {
let seq = p.parse_text_sequence(0, Some(1), 0, None, true, variant)
{ root: seq.node, diagnostics: p.diags, }
}
}
}