///|
pub(all) enum Atom {
Word(String)
Text(String)
} derive(Eq, Debug)
///|
pub(all) enum Node {
Attribute(String, Array[Atom], Position)
Complex(String, Array[Array[Atom]], Position)
Section(Group)
} derive(Eq, Debug)
///|
pub(all) struct Group {
kind : String
arguments : Array[Array[Atom]]
items : Array[Node]
position : Position
} derive(Eq, Debug)
///|
pub fn atom_text(atom : Atom) -> String {
match atom {
Word(value) | Text(value) => value
}
}
///|
pub fn atoms_text(atoms : Array[Atom]) -> String {
atoms.map(atom_text).join(" ")
}
///|
pub fn Group::name(self : Group) -> String {
if self.arguments.length() == 1 && self.arguments[0].length() == 1 {
atom_text(self.arguments[0][0])
} else {
""
}
}
///|
pub fn Group::groups(self : Group, kind : String) -> Array[Group] {
self.items.filter_map(node => {
match node {
Section(group) if group.kind == kind => Some(group)
_ => None
}
})
}
///|
pub fn Group::attribute(self : Group, name : String) -> Array[Atom]? {
for item in self.items {
if item is Attribute(key, value, _) && key == name {
return Some(value.copy())
}
}
None
}
///|
pub fn Group::text(self : Group, name : String) -> String? {
self.attribute(name).map(atoms_text)
}
///|
pub fn Group::complex(self : Group, name : String) -> Array[Array[Atom]]? {
for item in self.items {
if item is Complex(key, values, _) && key == name {
return Some(values.map(v => v.copy()))
}
}
None
}
///|
priv struct Parser {
tokens : Array[Token]
limits : Limits
mut index : Int32
mut nodes : Int32
}
///|
fn Parser::peek(self : Parser) -> Token {
self.tokens[self.index]
}
///|
fn Parser::is_symbol(self : Parser, text : String) -> Bool {
let token = self.peek()
token.kind == Symbol && token.text == text
}
///|
fn Parser::take(self : Parser) -> Token {
let token = self.peek()
self.index += 1
token
}
///|
fn Parser::expect(self : Parser, text : String) -> Unit raise LibertyError {
if !self.is_symbol(text) {
raise failure("ExpectedSymbol", "expected " + text, self.peek().position)
}
ignore(self.take())
}
///|
fn Parser::atom(self : Parser) -> Atom raise LibertyError {
let token = self.take()
match token.kind {
Quoted => Text(token.text)
Bare | Symbol => Word(token.text)
End => raise failure("UnexpectedEnd", "expected a value", token.position)
}
}
///|
fn Parser::arguments(self : Parser) -> Array[Array[Atom]] raise LibertyError {
self.expect("(")
if self.is_symbol(")") {
ignore(self.take())
return []
}
let values = []
let mut current = []
let mut nesting = 0
while true {
let token = self.peek()
if token.kind == End {
raise failure("UnclosedArguments", "expected )", token.position)
}
if self.is_symbol(")") && nesting == 0 {
if current.is_empty() {
raise failure(
"EmptyArgument",
"empty or trailing argument",
token.position,
)
}
values.push(current)
ignore(self.take())
return values
}
if self.is_symbol(",") && nesting == 0 {
if current.is_empty() {
raise failure("EmptyArgument", "empty argument", token.position)
}
values.push(current)
current = []
ignore(self.take())
} else {
if self.is_symbol("(") {
nesting += 1
if nesting > self.limits.max_depth {
raise failure(
"DepthLimit",
"argument nesting exceeds limit",
token.position,
)
}
} else if self.is_symbol(")") {
nesting -= 1
} else if self.is_symbol("{") ||
self.is_symbol("}") ||
self.is_symbol(";") {
raise failure(
"InvalidArgument",
"unexpected structural token in argument",
token.position,
)
}
current.push(self.atom())
}
} nobreak {
raise failure("UnclosedArguments", "expected )", self.peek().position)
}
}
///|
fn Parser::node(self : Parser, depth : Int32) -> Node raise LibertyError {
if depth > self.limits.max_depth {
raise failure(
"DepthLimit",
"group nesting exceeds limit",
self.peek().position,
)
}
self.nodes += 1
if self.nodes > self.limits.max_nodes {
raise failure(
"NodeLimit",
"AST node count exceeds limit",
self.peek().position,
)
}
let name = self.take()
if name.kind != Bare {
raise failure(
"ExpectedName",
"statement name must be unquoted",
name.position,
)
}
if self.is_symbol(":") {
ignore(self.take())
let atoms = []
let mut nesting = 0
while !self.is_symbol(";") {
let token = self.peek()
if self.is_symbol(":") && nesting == 0 {
raise failure(
"MissingSemicolon",
"unexpected attribute separator before ;",
token.position,
)
}
if token.kind == End || self.is_symbol("}") || self.is_symbol("{") {
raise failure(
"MissingSemicolon",
"attribute must end with ;",
token.position,
)
}
if self.is_symbol("(") {
nesting += 1
}
if self.is_symbol(")") {
nesting -= 1
if nesting < 0 {
raise failure(
"UnbalancedValue",
"extra ) in attribute",
token.position,
)
}
}
if nesting > self.limits.max_depth {
raise failure(
"DepthLimit",
"value nesting exceeds limit",
token.position,
)
}
atoms.push(self.atom())
}
if atoms.is_empty() {
raise failure("MissingValue", "attribute requires a value", name.position)
}
if nesting != 0 {
raise failure("UnbalancedValue", "unclosed ( in attribute", name.position)
}
ignore(self.take())
return Attribute(name.text, atoms, name.position)
}
if !self.is_symbol("(") {
raise failure(
"ExpectedStatement",
"expected : or ( after statement name",
self.peek().position,
)
}
let arguments = self.arguments()
if !self.is_symbol("{") {
self.expect(";")
return Complex(name.text, arguments, name.position)
}
ignore(self.take())
let items = []
while !self.is_symbol("}") {
if self.peek().kind == End {
raise failure(
"UnclosedGroup",
"expected } for " + name.text,
name.position,
)
}
items.push(self.node(depth + 1))
}
ignore(self.take())
// A semicolon after a closed group is tolerated by common Liberty writers.
if self.is_symbol(";") {
ignore(self.take())
}
Section({ kind: name.text, arguments, items, position: name.position, })
}
///|
pub fn parse_liberty(
text : String,
limits~ : Limits = Limits::default(),
) -> Group raise LibertyError {
let parser : Parser = {
tokens: tokenize(text, limits~),
limits,
index: 0,
nodes: 0,
}
if parser.peek().kind == End {
raise failure("MissingLibrary", "expected one library group", no_position())
}
let root = match parser.node(1) {
Section(group) if group.kind == "library" && !group.name().is_empty() =>
group
_ =>
raise failure(
"InvalidRoot",
"expected one named library(...) group",
no_position(),
)
}
if parser.peek().kind != End {
raise failure(
"MultipleRoots",
"only one library is accepted per document",
parser.peek().position,
)
}
root
}