// Copyright (c) 2024 LinZeming
// Released under the MIT License
//
// Evaluator — evaluates `Expr` nodes to `Value`s at render time.
// ---------------------------------------------------------------------------
// Arithmetic wrapper functions (must be defined before use)
// ---------------------------------------------------------------------------
fn sub_int64(a : Int64, b : Int64) -> Int64 { a - b }
fn mul_int64(a : Int64, b : Int64) -> Int64 { a * b }
fn div_int64(a : Int64, b : Int64) -> Int64 { a / b }
fn sub_double(a : Double, b : Double) -> Double { a - b }
fn mul_double(a : Double, b : Double) -> Double { a * b }
fn div_double(a : Double, b : Double) -> Double { a / b }
fn lt_int64(a : Int64, b : Int64) -> Bool { a < b }
fn gt_int64(a : Int64, b : Int64) -> Bool { a > b }
fn le_int64(a : Int64, b : Int64) -> Bool { a <= b }
fn ge_int64(a : Int64, b : Int64) -> Bool { a >= b }
fn lt_double(a : Double, b : Double) -> Bool { a < b }
fn gt_double(a : Double, b : Double) -> Bool { a > b }
fn le_double(a : Double, b : Double) -> Bool { a <= b }
fn ge_double(a : Double, b : Double) -> Bool { a >= b }
// ---------------------------------------------------------------------------
// Map helper (avoid Map indexing Option issues)
// ---------------------------------------------------------------------------
fn map_has_key(m : Map[String, Value], key : String) -> Bool {
for k in m.keys() {
if k == key {
return true
}
}
false
}
// ---------------------------------------------------------------------------
// String helper — convert a single UInt16 to a String
// ---------------------------------------------------------------------------
fn char_to_string(_ch : UInt16) -> String {
// Placeholder: proper Char→String conversion pending.
// For now, return a single-character string via format.
String::default()
}
// ---------------------------------------------------------------------------
// Structural equality
// ---------------------------------------------------------------------------
fn eval_eq(left : Value, right : Value) -> Bool {
match (left, right) {
(Null, Null) => true
(Bool(a), Bool(b)) => a == b
(Int(a), Int(b)) => a == b
(Int(a), Float(b)) => a.to_double() == b
(Float(a), Int(b)) => a == b.to_double()
(Float(a), Float(b)) => a == b
(Str(a), Str(b)) => a == b
(Array(a), Array(b)) => {
if a.length() != b.length() {
return false
}
let mut i = 0
let mut same = true
while i < a.length() {
if !eval_eq(a[i], b[i]) {
same = false
break
}
i = i + 1
}
same
}
(Object(_), Object(_)) => false
_ => false
}
}
// ---------------------------------------------------------------------------
// Binary operation helpers
// ---------------------------------------------------------------------------
fn eval_add(left : Value, right : Value) -> Result[Value, String] {
match (left, right) {
(Str(a), Str(b)) => Ok(Str(a + b))
(Str(a), _) => Ok(Str(a + right.to_string()))
(_, Str(b)) => Ok(Str(left.to_string() + b))
(Int(a), Int(b)) => Ok(Int(a + b))
(Int(a), Float(b)) => Ok(Float(a.to_double() + b))
(Float(a), Int(b)) => Ok(Float(a + b.to_double()))
(Float(a), Float(b)) => Ok(Float(a + b))
_ => Err("Cannot add types: " + left.type_name() + " + " + right.type_name())
}
}
fn eval_arithmetic(
left : Value,
right : Value,
op_int : (Int64, Int64) -> Int64,
op_float : (Double, Double) -> Double
) -> Result[Value, String] {
match (left, right) {
(Int(a), Int(b)) => Ok(Int(op_int(a, b)))
(Int(a), Float(b)) => Ok(Float(op_float(a.to_double(), b)))
(Float(a), Int(b)) => Ok(Float(op_float(a, b.to_double())))
(Float(a), Float(b)) => Ok(Float(op_float(a, b)))
_ => Err(
"Cannot perform arithmetic on types: " +
left.type_name() +
" and " +
right.type_name(),
)
}
}
fn eval_mod(left : Value, right : Value) -> Result[Value, String] {
match (left, right) {
(Int(a), Int(b)) => Ok(Int(a % b))
(Float(a), Float(b)) => Ok(Float(a % b))
(Int(a), Float(b)) => Ok(Float(a.to_double() % b))
(Float(a), Int(b)) => Ok(Float(a % b.to_double()))
_ => Err(
"Cannot compute modulo for types: " +
left.type_name() +
" and " +
right.type_name(),
)
}
}
fn eval_compare(
left : Value,
right : Value,
cmp_int : (Int64, Int64) -> Bool,
cmp_float : (Double, Double) -> Bool
) -> Result[Value, String] {
match (left, right) {
(Int(a), Int(b)) => Ok(Bool(cmp_int(a, b)))
(Int(a), Float(b)) => Ok(Bool(cmp_float(a.to_double(), b)))
(Float(a), Int(b)) => Ok(Bool(cmp_float(a, b.to_double())))
(Float(a), Float(b)) => Ok(Bool(cmp_float(a, b)))
(Str(a), Str(b)) => Ok(Bool(cmp_int(a.length().to_int64(), b.length().to_int64())))
_ => Err(
"Cannot compare types: " +
left.type_name() +
" and " +
right.type_name(),
)
}
}
fn eval_binop(left : Value, op : BinOpKind, right : Value) -> Result[Value, String] {
match op {
BinOpKind::Add => eval_add(left, right)
BinOpKind::Sub => eval_arithmetic(left, right, sub_int64, sub_double)
BinOpKind::Mul => eval_arithmetic(left, right, mul_int64, mul_double)
BinOpKind::Div => eval_arithmetic(left, right, div_int64, div_double)
BinOpKind::Mod => eval_mod(left, right)
BinOpKind::Eq => Ok(Bool(eval_eq(left, right)))
BinOpKind::Neq => Ok(Bool(!eval_eq(left, right)))
BinOpKind::Lt => eval_compare(left, right, lt_int64, lt_double)
BinOpKind::Gt => eval_compare(left, right, gt_int64, gt_double)
BinOpKind::Le => eval_compare(left, right, le_int64, le_double)
BinOpKind::Ge => eval_compare(left, right, ge_int64, ge_double)
BinOpKind::And => Ok(Bool(left.is_truthy() && right.is_truthy()))
BinOpKind::Or => Ok(Bool(left.is_truthy() || right.is_truthy()))
}
}
// ---------------------------------------------------------------------------
// Index evaluation
// ---------------------------------------------------------------------------
fn eval_index(container : Value, index : Value) -> Result[Value, String] {
match container {
Array(arr) => match index {
Int(i) => {
let len = arr.length().to_int64()
let actual = if i < 0L { len + i } else { i }
if actual >= 0L && actual < len {
Ok(arr[actual.to_int()])
} else {
Err("Index out of bounds: " + i.to_string() + " (length: " + len.to_string() + ")")
}
}
_ => Err("Array index must be an integer, got: " + index.type_name())
}
Str(s) => match index {
Int(i) => {
let len = s.length().to_int64()
let actual = if i < 0L { len + i } else { i }
if actual >= 0L && actual < len {
let ch = s[actual.to_int()]
Ok(Str(char_to_string(ch)))
} else {
Err("String index out of bounds: " + i.to_string())
}
}
_ => Err("String index must be an integer, got: " + index.type_name())
}
Object(m) => match index {
Str(key) => {
if map_has_key(m, key) {
Ok(m[key])
} else {
Err("Key not found: " + key)
}
}
_ => Err("Object key must be a string, got: " + index.type_name())
}
_ => Err("Cannot index type: " + container.type_name())
}
}
// ---------------------------------------------------------------------------
// Evaluator struct
// ---------------------------------------------------------------------------
pub(all) struct Evaluator {
ctx : Context
filters : FilterRegistry
}
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
pub fn Evaluator::new(ctx : Context) -> Evaluator {
{
ctx: ctx,
filters: FilterRegistry::default(),
}
}
/// Create an evaluator with a specific filter registry.
pub fn Evaluator::new_with_filters(
ctx : Context,
filters : FilterRegistry
) -> Evaluator {
{ ctx: ctx, filters: filters }
}
// ---------------------------------------------------------------------------
// Top-level evaluation
// ---------------------------------------------------------------------------
pub fn Evaluator::eval(self : Evaluator, expr : Expr) -> Result[Value, String] {
match expr {
Literal(v) => Ok(v)
Variable(name) => match self.ctx.get(name) {
Option::Some(v) => Ok(v)
Option::None => Err("Undefined variable: " + name)
}
Member(obj, field) => {
let val_result = self.eval(obj)
let val = match val_result {
Ok(v) => v
Err(e) => return Err(e)
}
match val {
Object(m) => {
if map_has_key(m, field) {
Ok(m[field])
} else {
Err("Field not found: " + field)
}
}
_ => Err("Cannot access field '" + field + "' on type " + val.type_name())
}
}
Index(container, index) => {
let c = match self.eval(container) {
Ok(v) => v
Err(e) => return Err(e)
}
let i = match self.eval(index) {
Ok(v) => v
Err(e) => return Err(e)
}
eval_index(c, i)
}
// -- Function / macro call -------------------------------------------
Call(name, args) => {
// Check if this is a macro call.
let macro_opt = self.ctx.get_macro(name)
match macro_opt {
Some(m) => {
// Evaluate argument expressions.
let eval_args : Array[Value] = Array::new()
for arg in args {
let arg_val = match self.eval(arg) {
Ok(v) => v
Err(msg) => return Err(msg)
}
eval_args.push(arg_val)
}
// Create child context with parameter bindings.
let mut child_ctx = Context::new_child(self.ctx)
let param_len = m.params.length()
let arg_len = eval_args.length()
let count = if param_len < arg_len { param_len } else { arg_len }
for j = 0; j < count; j = j + 1 {
child_ctx = child_ctx.set(m.params[j], eval_args[j])
}
// Render macro body to string using a temporary renderer.
let temp_loader = TemplateLoader::new()
let temp_renderer = Renderer::new(child_ctx, temp_loader)
let render_result = temp_renderer.render(m.body)
match render_result {
Ok(s) => Ok(Str(s))
Err(msg) => Err(msg)
}
}
None => Err("Function '" + name + "' not found")
}
}
BinOp(lhs, op, rhs) => {
match op {
BinOpKind::And => {
let left = match self.eval(lhs) {
Ok(v) => v
Err(e) => return Err(e)
}
if !left.is_truthy() {
return Ok(left)
}
self.eval(rhs)
}
BinOpKind::Or => {
let left = match self.eval(lhs) {
Ok(v) => v
Err(e) => return Err(e)
}
if left.is_truthy() {
return Ok(left)
}
self.eval(rhs)
}
_ => {
let left = match self.eval(lhs) {
Ok(v) => v
Err(e) => return Err(e)
}
let right = match self.eval(rhs) {
Ok(v) => v
Err(e) => return Err(e)
}
eval_binop(left, op, right)
}
}
}
UnaryOp(op, operand) => {
let val = match self.eval(operand) {
Ok(v) => v
Err(e) => return Err(e)
}
match op {
UnaryOpKind::Not => Ok(Bool(!val.is_truthy()))
UnaryOpKind::Neg => match val {
Int(i) => Ok(Int(-i))
Float(f) => Ok(Float(-f))
_ => Err("Cannot negate type: " + val.type_name())
}
}
}
// -- Filter ------------------------------------------------------------
Filter(base, filter_name, args) => {
let base_val = match self.eval(base) {
Ok(v) => v
Err(msg) => return Err(msg)
}
let eval_args : Array[Value] = Array::new()
for arg in args {
let arg_val = match self.eval(arg) {
Ok(v) => v
Err(msg) => return Err(msg)
}
eval_args.push(arg_val)
}
let filter_fn = match self.filters.lookup(filter_name) {
Some(f) => f
None => return Err("Unknown filter: " + filter_name)
}
filter_fn(base_val, eval_args)
}
}
}