///|
/// A parsed shrubbery.
///
/// The shape mirrors the reference's S-expression representation exactly,
/// because that representation IS the interface: it is what `#{...}` escapes
/// speak, what the parse oracle compares, and what a language built on this
/// notation will pattern-match. A more strongly typed tree -- one where a
/// group's optional block and alternatives were separate fields, so that the
/// grammar's two structural laws were unrepresentable rather than merely true
/// -- was the alternative, and it would have made the parser port a translation
/// rather than a transcription of a mutual recursion with fourteen threaded
/// state fields. The laws are stated below and checked by the oracle instead.
///
/// The laws, for the record:
///
/// * `Block` appears only as the last item of a `Group`, or as the
/// second-to-last with `Alts` after it.
/// * `Alts` appears only as the last item of a `Group`, and contains only
/// `Block`s.
/// * A `Group` is never empty.
pub(all) enum Shrub {
/// An identifier, including the `#%` internal form and `#{...}` escapes that
/// named a symbol.
Id(String)
/// `~name`.
Kw(String)
/// An operator. Prints as `(op name)`, and the metadata lives on this node
/// rather than on the `op` wrapper the reference writes.
Op(String)
/// A number, string, byte string, boolean, character or `#void`.
Lit(@sexp.Datum)
/// A value spliced in by something built on this notation, opaque here.
Parsed(@sexp.Datum)
Group(Array[Node])
Block(Array[Node])
Alts(Array[Node])
Parens(Array[Node])
Brackets(Array[Node])
Braces(Array[Node])
Quotes(Array[Node])
/// A whole document: the result of parsing in `top` mode.
Multi(Array[Node])
}
///|
/// One node: what it is, where it came from, and how to write it back.
pub(all) struct Node {
it : Shrub
span : @basic.Span
meta : @raw.Meta
}
///|
pub fn Node::new(
it : Shrub,
span : @basic.Span,
meta? : @raw.Meta = @raw.Meta::new(),
) -> Node {
{ it, span, meta, }
}
///|
/// The children of a compound node, or an empty array for a leaf.
pub fn Node::children(self : Node) -> Array[Node] {
match self.it {
Group(xs)
| Block(xs)
| Alts(xs)
| Parens(xs)
| Brackets(xs)
| Braces(xs)
| Quotes(xs)
| Multi(xs) => xs
_ => []
}
}
///|
/// The reference's tag for this node, for messages and for the oracle.
pub fn Shrub::tag(self : Shrub) -> String {
match self {
Id(_) => "identifier"
Kw(_) => "keyword"
Op(_) => "op"
Lit(_) => "literal"
Parsed(_) => "parsed"
Group(_) => "group"
Block(_) => "block"
Alts(_) => "alts"
Parens(_) => "parens"
Brackets(_) => "brackets"
Braces(_) => "braces"
Quotes(_) => "quotes"
Multi(_) => "multi"
}
}
///|
/// The canonical text form, for comparing our parse against the reference's.
///
/// This is the S-expression the reference produces, written with `@sexp`'s
/// escaping so that both sides of the comparison are all-ASCII and an encoding
/// difference in transit cannot look like a parse difference.
pub fn Node::canonical(self : Node) -> String {
let buf = StringBuilder()
self.write_canonical(buf)
buf.to_string()
}
///|
pub fn Node::write_canonical(self : Node, buf : StringBuilder) -> Unit {
match self.it {
Id(name) => @sexp.Datum::Sym(name).write_canonical(buf)
Kw(name) => @sexp.Datum::Kw(name).write_canonical(buf)
Op(name) => {
buf.write_string("(op ")
@sexp.Datum::Sym(name).write_canonical(buf)
buf.write_char(')')
}
Lit(d) => d.write_canonical(buf)
Parsed(d) => {
buf.write_string("(parsed ")
d.write_canonical(buf)
buf.write_char(')')
}
_ => {
buf.write_char('(')
buf.write_string(self.it.tag())
for child in self.children() {
buf.write_char(' ')
child.write_canonical(buf)
}
buf.write_char(')')
}
}
}
///|
/// The source text this node came from, rebuilt from its metadata.
///
/// Emission order is the field order of `@raw.Meta` and is load-bearing --
/// prefix, inner prefix, raw, then children or `opaque_content`, then tail,
/// inner suffix, suffix -- with `opaque_raw` superseding the middle three.
pub fn Node::write_source(self : Node, buf : StringBuilder) -> Unit {
let m = self.meta
m.prefix.write_to(buf)
m.inner_prefix.write_to(buf)
if !m.opaque_raw.is_empty() {
m.opaque_raw.write_to(buf)
} else {
m.raw.write_to(buf)
if !m.opaque_content.is_empty() {
m.opaque_content.write_to(buf)
} else {
for child in self.children() {
child.write_source(buf)
}
}
m.tail.write_to(buf)
}
m.inner_suffix.write_to(buf)
m.suffix.write_to(buf)
}
///|
/// The source text this node came from.
pub fn Node::to_source(self : Node) -> String {
let buf = StringBuilder()
self.write_source(buf)
buf.to_string()
}