///|
priv struct Parser {
tokens : Array[Token]
mut pos : Int32
mut depth : Int32
mut ternary_depth : Int32
errors : Array[ParseError]
}
///|
fn infix_prec(kind : TokenKind) -> Int32 {
match kind {
Question => 1
Or => 2
And => 3
EqEq | NotEq => 4
Greater | Less | GreaterEq | LessEq => 5
Plus | Minus => 6
Star | Slash | Percent => 7
Dot | LBrack => 9
_ => 0
}
}
///|
fn Parser::current(self : Parser) -> Token {
self.tokens[self.pos]
}
///|
fn Parser::advance(self : Parser) -> Unit {
if self.pos + 1 < self.tokens.length() {
self.pos += 1
}
}
///|
fn Parser::peek_kind(self : Parser) -> TokenKind {
let next = self.pos + 1
if next >= self.tokens.length() {
EOF
} else {
self.tokens[next].kind
}
}
///|
fn Parser::error(self : Parser, message : String) -> Unit {
let tok = self.current()
self.errors.push({
line: tok.line,
column: tok.column,
kind: Expected(message),
})
}
///|
fn Parser::skip_nested_newlines(self : Parser) -> Unit {
if self.depth > 0 || self.ternary_depth > 0 {
while self.current().kind is Newline {
self.advance()
}
return
}
// A `?` on the next line continues the statement. A `:` only continues
// while a ternary is already open, which the branch above covers.
let mut peek = self.pos
while peek < self.tokens.length() && self.tokens[peek].kind is Newline {
peek += 1
}
if peek < self.tokens.length() && self.tokens[peek].kind is Question {
self.pos = peek
}
}
///|
fn Parser::skip_line(self : Parser) -> Unit {
while true {
match self.current().kind {
EOF => break
Newline => {
self.advance()
break
}
_ => self.advance()
}
}
}
///|
fn strip_state_prefix(name : String) -> String {
if name.length() > 0 && name.at(0).to_int() == '$'.to_int() {
name[1:].to_owned()
} else {
name
}
}
///|
fn Parser::parse_list(self : Parser, closing : TokenKind) -> Array[Expr] {
self.depth += 1
let items : Array[Expr] = []
while true {
self.skip_nested_newlines()
let kind = self.current().kind
if kind is EOF || kind == closing {
break
}
let before = self.pos
items.push(self.parse_expr(0))
if self.pos == before {
self.error("expected expression")
self.advance()
}
self.skip_nested_newlines()
if self.current().kind is Comma {
self.advance()
}
}
if self.current().kind == closing {
self.advance()
}
self.depth -= 1
items
}
///|
fn Parser::parse_call(self : Parser, is_function : Bool) -> Expr {
let name = self.current().text
let function = is_function && !is_builtin(name)
self.advance()
if self.current().kind is LParen {
self.advance()
}
let args = self.parse_list(RParen)
Comp(name, function, args)
}
///|
fn Parser::parse_array(self : Parser) -> Expr {
self.advance()
Arr(self.parse_list(RBrack))
}
///|
fn Parser::field_name(self : Parser) -> String {
let tok = self.current()
match tok.kind {
Ident | Type | Str | Num => {
self.advance()
tok.text
}
StateVar => {
self.advance()
strip_state_prefix(tok.text)
}
_ => {
self.advance()
"?"
}
}
}
///|
fn Parser::parse_object(self : Parser) -> Expr {
self.advance()
self.depth += 1
let entries : Array[(String, Expr)] = []
while true {
self.skip_nested_newlines()
let kind = self.current().kind
if kind is RBrace || kind is EOF {
break
}
let key = self.field_name()
self.skip_nested_newlines()
if self.current().kind is Colon {
self.advance()
} else {
self.error("expected ':'")
}
let before = self.pos
let value = self.parse_expr(0)
if self.pos == before {
self.error("expected expression")
self.advance()
}
entries.push((key, value))
self.skip_nested_newlines()
if self.current().kind is Comma {
self.advance()
}
}
if self.current().kind is RBrace {
self.advance()
}
self.depth -= 1
Obj(entries)
}
///|
fn Parser::parse_prefix(self : Parser) -> Expr {
self.skip_nested_newlines()
let tok = self.current()
match tok.kind {
Str => {
self.advance()
Str(tok.text)
}
Num => {
self.advance()
Num(parse_number(tok.text), tok.text)
}
True => {
self.advance()
Bool(true)
}
False => {
self.advance()
Bool(false)
}
Null => {
self.advance()
Null
}
LBrack => self.parse_array()
LBrace => self.parse_object()
StateVar => {
let name = tok.text
self.advance()
if self.current().kind is Equals {
self.advance()
Assign(name, self.parse_expr(0))
} else {
StateRef(name)
}
}
Type => {
let name = tok.text
if self.peek_kind() is LParen && is_component_call_name(name) {
self.parse_call(false)
} else {
self.advance()
Ref(name)
}
}
BuiltinCall =>
if self.peek_kind() is LParen {
self.parse_call(true)
} else {
let name = tok.text
self.advance()
Ref(name)
}
Ident => {
let name = tok.text
self.advance()
Ref(name)
}
Not => {
self.advance()
UnaryOp("!", self.parse_expr(8))
}
Minus => {
self.advance()
UnaryOp("-", self.parse_expr(8))
}
LParen => {
self.advance()
self.depth += 1
let inner = self.parse_expr(0)
self.skip_nested_newlines()
if self.current().kind is RParen {
self.advance()
}
self.depth -= 1
inner
}
_ => Null
}
}
///|
fn Parser::parse_infix(self : Parser, left : Expr, prec : Int32) -> Expr {
match self.current().kind {
Plus => {
self.advance()
BinOp("+", left, self.parse_expr(prec))
}
Minus => {
self.advance()
BinOp("-", left, self.parse_expr(prec))
}
Star => {
self.advance()
BinOp("*", left, self.parse_expr(prec))
}
Slash => {
self.advance()
BinOp("/", left, self.parse_expr(prec))
}
Percent => {
self.advance()
BinOp("%", left, self.parse_expr(prec))
}
EqEq => {
self.advance()
BinOp("==", left, self.parse_expr(prec))
}
NotEq => {
self.advance()
BinOp("!=", left, self.parse_expr(prec))
}
Greater => {
self.advance()
BinOp(">", left, self.parse_expr(prec))
}
Less => {
self.advance()
BinOp("<", left, self.parse_expr(prec))
}
GreaterEq => {
self.advance()
BinOp(">=", left, self.parse_expr(prec))
}
LessEq => {
self.advance()
BinOp("<=", left, self.parse_expr(prec))
}
And => {
self.advance()
BinOp("&&", left, self.parse_expr(prec))
}
Or => {
self.advance()
BinOp("||", left, self.parse_expr(prec))
}
Question => {
if self.depth == 0 {
self.ternary_depth += 1
}
self.advance()
let then_branch = self.parse_expr(0)
self.skip_nested_newlines()
if self.current().kind is Colon {
if self.depth == 0 && self.ternary_depth > 0 {
self.ternary_depth -= 1
}
self.advance()
} else if self.depth == 0 && self.ternary_depth > 0 {
self.ternary_depth -= 1
}
let else_branch = self.parse_expr(0)
Ternary(left, then_branch, else_branch)
}
Dot => {
self.advance()
let field = self.field_name()
Member(left, field)
}
LBrack => {
self.advance()
self.depth += 1
let index = self.parse_expr(0)
self.skip_nested_newlines()
if self.current().kind is RBrack {
self.advance()
}
self.depth -= 1
Index(left, index)
}
_ => left
}
}
///|
fn Parser::parse_expr(self : Parser, min_prec : Int32) -> Expr {
let mut left = self.parse_prefix()
while true {
self.skip_nested_newlines()
let prec = infix_prec(self.current().kind)
if prec <= min_prec {
break
}
left = self.parse_infix(left, prec)
}
left
}
///|
fn is_statement_name(kind : TokenKind) -> Bool {
match kind {
Ident | Type | StateVar => true
_ => false
}
}
///|
fn Parser::parse_statement(self : Parser) -> Statement? {
let tok = self.current()
if !is_statement_name(tok.kind) {
self.error("expected statement")
self.skip_line()
return None
}
let id = tok.text
let line = tok.line
self.advance()
if !(self.current().kind is Equals) {
self.error("expected '='")
self.skip_line()
return None
}
self.advance()
let start = self.pos
let expr = self.parse_expr(0)
if self.pos == start {
self.error("expected expression")
self.skip_line()
return None
}
match self.current().kind {
Newline => self.advance()
EOF => ()
_ => {
self.error("trailing tokens")
self.skip_line()
}
}
Some({ id, expr, line, })
}
///|
/// Parse assignment statements. A bad line is reported and skipped.
pub fn parse(src : String) -> Program {
let tokens = tokenize(src)
let parser = Parser::{
tokens,
pos: 0,
depth: 0,
ternary_depth: 0,
errors: [],
}
let statements : Array[Statement] = []
while !(parser.current().kind is EOF) {
while parser.current().kind is Newline {
parser.advance()
}
if parser.current().kind is EOF {
break
}
match parser.parse_statement() {
Some(stmt) => statements.push(stmt)
None => ()
}
}
{ statements, errors: parser.errors, }
}
///|
pub fn tokens_json(src : String) -> String {
let encoded : Array[Json] = []
for tok in tokenize(src) {
encoded.push(tok.to_json())
}
Json::array(encoded).stringify()
}
///|
pub fn parse_json(src : String) -> String {
parse(src).to_json().stringify(indent=2)
}
///|
pub fn summarize(src : String) -> String {
let program = parse(src)
let buf = StringBuilder()
buf.write_string("statements: ")
buf.write_string(program.statements.length().to_string())
buf.write_string("\nerrors: ")
buf.write_string(program.errors.length().to_string())
for stmt in program.statements {
buf.write_string("\n")
buf.write_string(stmt.line.to_string())
buf.write_string(" ")
buf.write_string(stmt.id)
buf.write_string(" = ")
buf.write_string(stmt.expr.dump())
}
for err in program.errors {
buf.write_string("\nerror ")
buf.write_string(err.line.to_string())
buf.write_string(":")
buf.write_string(err.column.to_string())
buf.write_string(" ")
buf.write_string(err.message())
}
buf.to_string()
}