///|
/// The quote character of a terminal string.
pub(all) enum Quote {
Double
Single
} derive(Eq, Debug)
///|
pub extend Quote with Eq::{equal, not_equal}
///|
pub extend Quote with @debug.Debug::{to_repr}
///|
/// The meaning of one grammar expression node. Composite kinds name their
/// children by node identity, so a grammar is a flat, bounded arena.
pub(all) enum Kind {
/// A quoted terminal string.
Terminal(String, Quote)
/// A named nonterminal. The optional identity names its target rule
/// explicitly; without it, validation resolves the name.
Reference(String, String?)
/// A special sequence such as `? digits ?`.
Special(String)
/// An empty term.
Empty
Sequence(ReadOnlyArray[String])
Choice(ReadOnlyArray[String])
/// `[ x ]`
Optional(String)
/// `{ x }`
ZeroOrMore(String)
/// `{ x }-`
OneOrMore(String)
/// `n * x`, with the decimal count spelling.
Count(String, String)
/// `x - y`: the left term, constrained so that it does not match the excluded term.
Exclusion(String, String)
} derive(Eq, Debug)
///|
pub extend Kind with Eq::{equal, not_equal}
///|
pub extend Kind with @debug.Debug::{to_repr}
///|
pub struct Node {
id : String
kind : Kind
notes_above : ReadOnlyArray[String]
notes_below : ReadOnlyArray[String]
source : @diagrams.Span?
} derive(Eq, Debug)
///|
pub extend Node with Eq::{equal, not_equal}
///|
pub extend Node with @debug.Debug::{to_repr}
///|
pub fn Node::new(
id : String,
kind : Kind,
notes_above? : ArrayView[String] = [],
notes_below? : ArrayView[String] = [],
source? : @diagrams.Span,
) -> Node {
{
id,
kind,
notes_above: ReadOnlyArray::from_array(notes_above),
notes_below: ReadOnlyArray::from_array(notes_below),
source,
}
}
///|
/// The children of `node` in drawing order. An exclusion lists its left term
/// first and its excluded term second.
pub fn Node::children(self : Node) -> ReadOnlyArray[String] {
match self.kind {
Sequence(items) | Choice(items) => items
Optional(child) | ZeroOrMore(child) | OneOrMore(child) | Count(_, child) =>
[child]
Exclusion(left, excluded) => [left, excluded]
_ => []
}
}
///|
/// Terminals, references, special sequences and empty terms are leaves.
pub fn Node::is_leaf(self : Node) -> Bool {
match self.kind {
Terminal(_, _) | Reference(_, _) | Special(_) | Empty => true
_ => false
}
}
///|
pub fn Node::label(self : Node) -> String {
match self.kind {
Terminal(text, _) => text
Reference(name, _) => name
Special(text) => text
Empty => "empty"
Sequence(_) => "sequence"
Choice(_) => "choice"
Optional(_) => "optional"
ZeroOrMore(_) => "zero or more"
OneOrMore(_) => "one or more"
Count(count, _) => count + "\u{d7}"
Exclusion(_, _) => "except"
}
}
///|
/// One named rule. A rule without a body is an empty definition.
pub struct Rule {
id : String
name : String
body : String?
note_above : String?
note_below : String?
source : @diagrams.Span?
} derive(Eq, Debug)
///|
pub extend Rule with Eq::{equal, not_equal}
///|
pub extend Rule with @debug.Debug::{to_repr}
///|
pub fn Rule::new(
id : String,
name : String,
body? : String,
note_above? : String,
note_below? : String,
source? : @diagrams.Span,
) -> Rule {
{ id, name, body, note_above, note_below, source }
}
///|
/// One ordered grammar statement: a rule or a free-standing note.
pub(all) enum Item {
Rule(Rule)
Note(String, String)
} derive(Eq, Debug)
///|
pub extend Item with Eq::{equal, not_equal}
///|
pub extend Item with @debug.Debug::{to_repr}
///|
/// One immutable grammar diagram. It describes a grammar; it does not parse input.
pub struct Grammar {
id : String
items : ReadOnlyArray[Item]
nodes : ReadOnlyArray[Node]
title : String?
accessibility_title : String?
accessibility_description : String?
} derive(Eq, Debug)
///|
pub extend Grammar with Eq::{equal, not_equal}
///|
pub extend Grammar with @debug.Debug::{to_repr}
///|
pub fn Grammar::new(
id : String,
items : ArrayView[Item],
nodes : ArrayView[Node],
title? : String,
accessibility_title? : String,
accessibility_description? : String,
) -> Grammar {
{
id,
items: ReadOnlyArray::from_array(items),
nodes: ReadOnlyArray::from_array(nodes),
title,
accessibility_title,
accessibility_description,
}
}
///|
/// The rules of the grammar in source order.
pub fn Grammar::rules(self : Grammar) -> Array[Rule] {
let out = []
for item in self.items {
if item is Rule(rule) {
out.push(rule)
}
}
out
}