// Expression sub-language: a lexer + Pratt parser for the contents of
// {{ ... }} and tag conditions.
//
// Precedence (low -> high): or, and, not, comparison, +/-, * / %, unary -,
// postfix (member `.`, index `[]`, filter `|`), primary.
///|
/// A token in the expression sub-language.
pub(all) enum ExprToken {
EIdent(String)
EInt(Int)
EStr(String)
EOp(String)
EDot
ELparen
ERparen
ELbracket
ERbracket
EComma
EPipe
EEof
} derive(Debug)
///|
fn is_digit(c : Char) -> Bool {
c >= '0' && c <= '9'
}
///|
fn is_ident_start(c : Char) -> Bool {
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
}
///|
fn is_ident_part(c : Char) -> Bool {
is_ident_start(c) || is_digit(c)
}
///|
/// Lex an expression string into tokens.
pub fn expr_lex(s : String) -> Array[ExprToken] {
let chars = s.to_array()
let n = chars.length()
let toks : Array[ExprToken] = []
let mut i = 0
while i < n {
let c = chars[i]
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
i = i + 1
} else if is_digit(c) {
let mut j = i
while j < n && is_digit(chars[j]) {
j = j + 1
}
let mut v = 0
for k = i; k < j; k = k + 1 {
v = v * 10 + (chars[k].to_int() - '0'.to_int())
}
toks.push(EInt(v))
i = j
} else if is_ident_start(c) {
let mut j = i
while j < n && is_ident_part(chars[j]) {
j = j + 1
}
toks.push(EIdent(s[i:j].to_owned()))
i = j
} else if c == '"' {
let mut j = i + 1
let buf = StringBuilder::new()
while j < n && chars[j] != '"' {
buf.write_char(chars[j])
j = j + 1
}
toks.push(EStr(buf.to_string()))
i = j + 1
} else if c == '.' {
toks.push(EDot)
i = i + 1
} else if c == '(' {
toks.push(ELparen)
i = i + 1
} else if c == ')' {
toks.push(ERparen)
i = i + 1
} else if c == '[' {
toks.push(ELbracket)
i = i + 1
} else if c == ']' {
toks.push(ERbracket)
i = i + 1
} else if c == ',' {
toks.push(EComma)
i = i + 1
} else if c == '|' {
toks.push(EPipe)
i = i + 1
} else {
let two = if i + 1 < n { s[i:i + 2].to_owned() } else { "" }
if two == "==" || two == "!=" || two == "<=" || two == ">=" {
toks.push(EOp(two))
i = i + 2
} else if c == '<' ||
c == '>' ||
c == '+' ||
c == '-' ||
c == '*' ||
c == '/' ||
c == '%' {
toks.push(EOp(s[i:i + 1].to_owned()))
i = i + 1
} else {
i = i + 1
}
}
}
toks.push(EEof)
toks
}
///|
priv struct EP {
toks : Array[ExprToken]
mut i : Int
}
///|
fn EP::peek(self : EP) -> ExprToken {
if self.i < self.toks.length() {
self.toks[self.i]
} else {
EEof
}
}
///|
fn EP::advance(self : EP) -> Unit {
self.i = self.i + 1
}
///|
fn EP::at_ident(self : EP, kw : String) -> Bool {
match self.peek() {
EIdent(k) => k == kw
_ => false
}
}
///|
fn EP::at_op(self : EP, ops : Array[String]) -> Bool {
match self.peek() {
EOp(o) => ops.contains(o)
_ => false
}
}
///|
fn EP::cur_op(self : EP) -> String {
match self.peek() {
EOp(o) => o
_ => ""
}
}
///|
fn EP::cur_ident(self : EP) -> String {
match self.peek() {
EIdent(k) => k
_ => ""
}
}
///|
fn EP::peek_is_pipe(self : EP) -> Bool {
match self.peek() {
EPipe => true
_ => false
}
}
///|
fn EP::peek_is_dot(self : EP) -> Bool {
match self.peek() {
EDot => true
_ => false
}
}
///|
fn EP::peek_is_lparen(self : EP) -> Bool {
match self.peek() {
ELparen => true
_ => false
}
}
///|
fn EP::peek_is_lbracket(self : EP) -> Bool {
match self.peek() {
ELbracket => true
_ => false
}
}
///|
fn EP::peek_is_comma(self : EP) -> Bool {
match self.peek() {
EComma => true
_ => false
}
}
///|
/// Parse a token list into an `Expr`.
pub fn expr_parse(toks : Array[ExprToken]) -> Expr {
let p = EP::{ toks, i: 0 }
p.parse_or()
}
///|
fn EP::parse_or(self : EP) -> Expr {
let mut left = self.parse_and()
while self.at_ident("or") {
self.advance()
left = EBinOp("or", left, self.parse_and())
}
left
}
///|
fn EP::parse_and(self : EP) -> Expr {
let mut left = self.parse_not()
while self.at_ident("and") {
self.advance()
left = EBinOp("and", left, self.parse_not())
}
left
}
///|
fn EP::parse_not(self : EP) -> Expr {
if self.at_ident("not") {
self.advance()
ENot(self.parse_not())
} else {
self.parse_is()
}
}
///|
/// Parse `expr is test_name` (and chains via precedence).
fn EP::parse_is(self : EP) -> Expr {
let mut left = self.parse_cmp()
while self.at_ident("is") {
self.advance()
let name = self.cur_ident()
self.advance()
left = EIs(left, name)
}
left
}
///|
fn EP::parse_cmp(self : EP) -> Expr {
let mut left = self.parse_add()
while self.at_op(["==", "!=", "<", ">", "<=", ">="]) {
let op = self.cur_op()
self.advance()
left = EBinOp(op, left, self.parse_add())
}
left
}
///|
fn EP::parse_add(self : EP) -> Expr {
let mut left = self.parse_mul()
while self.at_op(["+", "-"]) {
let op = self.cur_op()
self.advance()
left = EBinOp(op, left, self.parse_mul())
}
left
}
///|
fn EP::parse_mul(self : EP) -> Expr {
let mut left = self.parse_unary()
while self.at_op(["*", "/", "%"]) {
let op = self.cur_op()
self.advance()
left = EBinOp(op, left, self.parse_unary())
}
left
}
///|
fn EP::parse_unary(self : EP) -> Expr {
if self.at_op(["-"]) {
self.advance()
EBinOp("-", EInt(0), self.parse_unary())
} else {
self.parse_postfix()
}
}
///|
fn EP::parse_postfix(self : EP) -> Expr {
let mut e = self.parse_primary()
let mut cont = true
while cont {
if self.peek_is_dot() {
self.advance()
let field = self.cur_ident()
self.advance()
e = EMember(e, field)
} else if self.peek_is_lbracket() {
self.advance()
let index = self.parse_or()
self.advance() // consume ']'
e = EIndex(e, index)
} else if self.peek_is_pipe() {
self.advance()
let name = self.cur_ident()
self.advance()
let args : Array[Expr] = []
if self.peek_is_lparen() {
self.advance()
args.push(self.parse_or())
while self.peek_is_comma() {
self.advance()
args.push(self.parse_or())
}
self.advance() // consume ')'
}
e = EFilter(e, name, args)
} else {
cont = false
}
}
e
}
///|
fn EP::parse_primary(self : EP) -> Expr {
match self.peek() {
EInt(v) => {
self.advance()
EInt(v)
}
EStr(s) => {
self.advance()
EStr(s)
}
EIdent(name) => {
self.advance()
if name == "true" {
EBool(true)
} else if name == "false" {
EBool(false)
} else if name == "super" {
if self.peek_is_lparen() {
self.advance()
self.advance() // consume ')'
}
ESuper
} else {
EVar(name)
}
}
ELparen => {
self.advance()
let e = self.parse_or()
self.advance()
e
}
_ => EInt(0)
}
}