///|
pub(all) enum Value {
Number(Int32)
Flag(Bool)
Text(String)
} derive(Eq, @debug.Debug)
///|
pub extend Value with Eq::{equal, not_equal}
///|
pub extend Value with @debug.Debug::{to_repr}
///|
pub fn Value::text(self : Value) -> String {
match self {
Number(n) => n.to_string()
Flag(b) => b.to_string()
Text(s) => s
}
}
///|
fn Value::kind(self : Value) -> Int32 {
match self {
Number(_) => 0
Flag(_) => 1
Text(_) => 2
}
}
///|
priv enum Expr {
Lit(Value)
Read(String)
Unary(String, Expr)
Binary(String, Expr, Expr)
}
///|
priv struct ExprParser {
ts : Array[Token]
line : Int32
mut pos : Int32
}
///|
fn precedence(op : String) -> Int32 {
match op {
"||" => 1
"&&" => 2
"==" | "!=" => 3
"<" | "<=" | ">" | ">=" => 4
"+" | "-" => 5
_ => 0
}
}
///|
fn ExprParser::take(self : ExprParser, word : String) -> Bool {
if self.pos < self.ts.length() && self.ts[self.pos].kind == Word(word) {
self.pos = self.pos + 1
true
} else {
false
}
}
///|
fn ExprParser::parse(
self : ExprParser,
minimum : Int32,
depth : Int32,
) -> Expr raise StoryError {
if depth > 32 {
raise Limit("expression nesting exceeds 32")
}
if self.pos >= self.ts.length() {
raise Syntax(self.line, 1, "expected expression")
}
let tok = self.ts[self.pos]
self.pos = self.pos + 1
let mut left = match tok.kind {
Quoted(s) => Lit(Text(s))
Word("true") => Lit(Flag(true))
Word("false") => Lit(Flag(false))
Word("(") => {
let inner = self.parse(1, depth + 1)
if !self.take(")") {
raise Syntax(self.line, tok.col, "missing closing parenthesis")
}
inner
}
Word("!" | "-" as op) =>
// Permit the negative Int32 boundary without first overflowing its positive magnitude.
if op == "-" &&
self.pos < self.ts.length() &&
self.ts[self.pos].kind == Word("2147483648") {
self.pos = self.pos + 1
Lit(Number(-2147483648))
} else {
Unary(op, self.parse(6, depth + 1))
}
Word(s) =>
if identifier(s) {
Read(s)
} else {
let n = @string.parse_int(s[:]) catch {
_ => raise Syntax(self.line, tok.col, "invalid Int32 literal")
}
Lit(Number(n))
}
}
while self.pos < self.ts.length() {
guard self.ts[self.pos].kind is Word(op) else { break }
let p = precedence(op)
if p < minimum || p == 0 {
break
}
self.pos = self.pos + 1
left = Binary(op, left, self.parse(p + 1, depth + 1))
}
left
}
///|
fn expression(ts : Array[Token], line : Int32) -> Expr raise StoryError {
let p = ExprParser::{ ts, line, pos: 0, }
let e = p.parse(1, 0)
if p.pos != ts.length() {
raise Syntax(line, ts[p.pos].col, "unexpected token after expression")
}
e
}
///|
fn expr_kind(
e : Expr,
vars : Map[String, Value],
line : Int32,
) -> Int32 raise StoryError {
match e {
Lit(v) => v.kind()
Read(name) =>
match vars.get(name) {
Some(v) => v.kind()
None => raise Type(line, "undeclared variable " + name)
}
Unary(op, a) => {
let kind = expr_kind(a, vars, line)
let wanted = if op == "!" { 1 } else { 0 }
if kind != wanted {
raise Type(line, "invalid operand for " + op)
}
wanted
}
Binary(op, a, b) => {
let ka = expr_kind(a, vars, line)
let kb = expr_kind(b, vars, line)
if ka != kb {
raise Type(line, "operands have different types")
}
match op {
"&&" | "||" => {
if ka != 1 {
raise Type(line, "boolean operands required")
}
1
}
"==" | "!=" => 1
"<" | "<=" | ">" | ">=" => {
if ka != 0 {
raise Type(line, "integer comparison required")
}
1
}
"+" => {
if ka == 1 {
raise Type(line, "cannot add booleans")
}
ka
}
_ => {
if ka != 0 {
raise Type(line, "integer operands required")
}
0
}
}
}
}
}
///|
fn checked(n : Int64) -> Value raise StoryError {
if n < -2147483648L || n > 2147483647L {
raise Runtime("overflow", "Int32 arithmetic overflow")
}
Number(n.to_int())
}
///|
fn eval(e : Expr, vars : Map[String, Value]) -> Value raise StoryError {
match e {
Lit(v) => v
Read(name) =>
match vars.get(name) {
Some(v) => v
None => raise Runtime("variable", "missing " + name)
}
Unary("!", a) =>
match eval(a, vars) {
Flag(b) => Flag(!b)
_ => raise Runtime("type", "expected boolean")
}
Unary(_, a) =>
match eval(a, vars) {
Number(n) => checked(-n.to_int64())
_ => raise Runtime("type", "expected integer")
}
Binary(op, a, b) => {
let va = eval(a, vars)
if op == "&&" && va == Flag(false) {
return Flag(false)
}
if op == "||" && va == Flag(true) {
return Flag(true)
}
let vb = eval(b, vars)
if op == "==" {
return Flag(va == vb)
}
if op == "!=" {
return Flag(va != vb)
}
match (va, vb) {
(Number(x), Number(y)) =>
match op {
"+" => checked(x.to_int64() + y.to_int64())
"-" => checked(x.to_int64() - y.to_int64())
"<" => Flag(x < y)
"<=" => Flag(x <= y)
">" => Flag(x > y)
_ => Flag(x >= y)
}
(Text(x), Text(y)) => {
if x.length() + y.length() > 262144 {
raise Limit("string value exceeds 262144 code units")
}
Text(x + y)
}
(Flag(x), Flag(y)) => Flag(if op == "&&" { x && y } else { x || y })
_ => raise Runtime("type", "invalid expression operands")
}
}
}
}