///|
pub(all) enum Argument {
Flag(String)
Literal(String)
Strings(Array[String])
Quantity(Int)
} derive(Eq, Debug)
///|
pub(all) enum Test {
Call(String, Array[Argument], Span)
Not(Test, Span)
AnyOf(Array[Test], Span)
AllOf(Array[Test], Span)
} derive(Eq, Debug)
///|
pub(all) struct Conditional {
condition : Test
body : Array[Statement]
} derive(Eq, Debug)
///|
pub(all) enum Statement {
Command(String, Array[Argument], Span)
Branch(Array[Conditional], Array[Statement], Span)
} derive(Eq, Debug)
///|
pub(all) struct Script {
statements : Array[Statement]
} derive(Eq, Debug)
///|
priv struct Parser {
tokens : Array[Token]
mut pos : Int
limits : Limits
}
///|
fn Parser::current(self : Parser) -> Token {
self.tokens[self.pos]
}
///|
fn Parser::is_symbol(self : Parser, c : Char) -> Bool {
self.current().kind == Symbol(c)
}
///|
fn Parser::expect(self : Parser, c : Char) -> Unit raise SieveError {
if !self.is_symbol(c) {
fail("parse.expected", "expected punctuation \{c}", self.current().span)
}
self.pos += 1
}
///|
fn Parser::arguments(self : Parser) -> Array[Argument] raise SieveError {
let result : Array[Argument] = []
while true {
match self.current().kind {
Tag(s) => {
result.push(Flag(s))
self.pos += 1
}
Text(s) => {
result.push(Literal(s))
self.pos += 1
}
Number(n) => {
result.push(Quantity(n))
self.pos += 1
}
Symbol('[') => {
self.pos += 1
let strings : Array[String] = []
while true {
match self.current().kind {
Text(s) => {
strings.push(s)
self.pos += 1
}
_ =>
fail(
"parse.string_list",
"expected a nonempty string list",
self.current().span,
)
}
if self.is_symbol(']') {
self.pos += 1
break
}
self.expect(',')
}
result.push(Strings(strings))
}
_ => break
}
}
result
}
///|
fn Parser::test_expr(self : Parser, depth : Int) -> Test raise SieveError {
if depth > self.limits.depth {
fail("limit.depth", "condition nesting too deep", self.current().span)
}
let token = self.current()
let name = match token.kind {
Word(s) => s
_ => {
fail("parse.condition", "expected condition name", token.span)
""
}
}
self.pos += 1
match name {
"not" => Not(self.test_expr(depth + 1), token.span)
"anyof" | "allof" => {
self.expect('(')
let tests : Array[Test] = []
while true {
tests.push(self.test_expr(depth + 1))
if self.is_symbol(')') {
self.pos += 1
break
}
self.expect(',')
}
if name == "anyof" {
AnyOf(tests, token.span)
} else {
AllOf(tests, token.span)
}
}
_ => Call(name, self.arguments(), token.span)
}
}
///|
fn Parser::block(
self : Parser,
depth : Int,
) -> Array[Statement] raise SieveError {
self.expect('{')
let body = self.statements(depth + 1, true)
self.expect('}')
body
}
///|
fn Parser::statements(
self : Parser,
depth : Int,
nested : Bool,
) -> Array[Statement] raise SieveError {
if depth > self.limits.depth {
fail("limit.depth", "statement nesting too deep", self.current().span)
}
let out : Array[Statement] = []
while self.current().kind != End {
if nested && self.is_symbol('}') {
break
}
let token = self.current()
let name = match token.kind {
Word(s) => s
_ => {
fail("parse.command", "expected command or if", token.span)
""
}
}
self.pos += 1
if name == "if" {
let choices : Array[Conditional] = []
let condition = self.test_expr(depth + 1)
choices.push({ condition, body: self.block(depth) })
while self.current().kind == Word("elsif") {
self.pos += 1
let condition = self.test_expr(depth + 1)
choices.push({ condition, body: self.block(depth) })
}
let otherwise = if self.current().kind == Word("else") {
self.pos += 1
self.block(depth)
} else {
[]
}
out.push(Branch(choices, otherwise, token.span))
} else {
if name == "else" || name == "elsif" {
fail(
"parse.orphan_branch",
"branch must immediately follow if",
token.span,
)
}
let args = self.arguments()
self.expect(';')
out.push(Command(name, args, token.span))
}
}
out
}
///|
pub fn parse(
source : String,
limits? : Limits = Limits::default(),
) -> Script raise SieveError {
let parser : Parser = { tokens: tokenize(source, limits~), pos: 0, limits }
{ statements: parser.statements(0, false) }
}