use crate::ast::{Expr, Stmt, Context, LoopControl, Op};
pub fn eval_expr(expr: &Expr, ctx: &Context) -> String {
match expr {
Expr::Int(i) => i.to_string(),
Expr::Str(s) => s.clone(),
Expr::Var(name) => ctx
.variables
.get(name)
.cloned()
.unwrap_or_else(|| panic!("Undefined variable: {}", name)),
Expr::Binary(left, op, right) => {
let l = eval_expr(left, ctx).parse::<i64>().unwrap();
let r = eval_expr(right, ctx).parse::<i64>().unwrap();
let result = match op {
Op::Add => l + r,
Op::Sub => l - r,
Op::Mul => l * r,
Op::Div => l / r,
Op::Greater => (l > r) as i64,
Op::Less => (l < r) as i64,
Op::GreaterEq => (l >= r) as i64,
Op::LessEq => (l <= r) as i64,
Op::Equal => (l == r) as i64,
Op::NotEqual => (l != r) as i64,
};
result.to_string()
}
Expr::Call(name, args) => {
let (params, body) = ctx
.functions
.get(name)
.unwrap_or_else(|| panic!("Undefined function: {}", name))
.clone();
if params.len() != args.len() {
panic!(
"Function '{}' expected {} args, got {}",
name,
params.len(),
args.len()
);
}
let mut local_ctx = Context::default();
for (param, arg) in params.iter().zip(args.iter()) {
let value = eval_expr(arg, ctx);
local_ctx.variables.insert(param.clone(), value);
}
for stmt in body {
match exec_stmt(&stmt, &mut local_ctx) {
LoopControl::Return(val) => return val,
LoopControl::None => continue,
_ => panic!("Unexpected control flow in function"),
}
}
"".to_string()
}
}
}
pub fn exec_stmt(stmt: &Stmt, ctx: &mut Context) -> LoopControl {
match stmt {
Stmt::Print(expr) => {
println!("{}", eval_expr(expr, ctx));
LoopControl::None
}
Stmt::Let(name, expr) => {
let value = eval_expr(expr, ctx);
ctx.variables.insert(name.clone(), value);
LoopControl::None
}
Stmt::Break => LoopControl::Break,
Stmt::Continue => LoopControl::Continue,
Stmt::If {
condition,
then_branch,
else_branch,
} => {
let cond_value = eval_expr(condition, ctx);
let is_true = cond_value != "0" && cond_value != "" && cond_value != "false";
let fallback = Vec::new();
let branch = if is_true {
then_branch
} else {
else_branch.as_ref().unwrap_or(&fallback)
};
for stmt in branch {
match exec_stmt(stmt, ctx) {
LoopControl::None => continue,
control => return control,
}
}
LoopControl::None
}
Stmt::While { condition, body } => {
while eval_expr(condition, ctx) != "0" {
for stmt in body {
match exec_stmt(stmt, ctx) {
LoopControl::None => continue,
LoopControl::Break => return LoopControl::None,
LoopControl::Continue => break,
LoopControl::Return(val) => return LoopControl::Return(val),
}
}
}
LoopControl::None
}
Stmt::Fn { name, params, body } => {
ctx.functions
.insert(name.clone(), (params.clone(), body.to_vec()));
LoopControl::None
}
Stmt::Call(name, args) => {
let (params, body) = ctx.functions.get(name).unwrap().clone();
let mut local_ctx = Context::default();
for (param, arg) in params.iter().zip(args.iter()) {
let value = eval_expr(arg, ctx);
local_ctx.variables.insert(param.clone(), value);
}
for stmt in body {
exec_stmt(&stmt, &mut local_ctx);
}
LoopControl::None
}
Stmt::Return(expr) => {
let value = eval_expr(expr, ctx);
LoopControl::Return(value)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Expr, Op, Context};
#[test]
fn test_addition_expr() {
let expr = Expr::Binary(Box::new(Expr::Int(2)), Op::Add, Box::new(Expr::Int(3)));
let ctx = Context::default();
let result = eval_expr(&expr, &ctx);
assert_eq!(result, "5");
}
#[test]
fn test_variable_lookup() {
let mut ctx = Context::default();
ctx.variables.insert("x".to_string(), "42".to_string());
let expr = Expr::Var("x".to_string());
let result = eval_expr(&expr, &ctx);
assert_eq!(result, "42");
}
}