use std::{collections::HashMap, fmt::Debug, rc::Rc}; use crate::{ ast::{Constant, Expression, Location}, types::{Value, ValueInner}, }; #[derive(Clone)] pub struct Function { name: String, inner: Rc) -> Value>, } impl Function { pub fn new(name: String, func: impl Fn(Vec) -> Value + 'static) -> Self { Self { name, inner: Rc::new(func), } } pub fn call(&self, args: Vec) -> Value { (self.inner)(args) } } impl Debug for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "", self.name) } } #[derive(Debug, Clone, Default)] pub struct Scope { variables: HashMap, functions: HashMap, } impl Scope { pub fn get(&self, name: impl AsRef) -> Value { self.variables.get(name.as_ref()).cloned() } pub fn set(&mut self, name: String, value: Value) { if let Some(value) = value { self.variables.insert(name, value); } else { self.variables.remove(&name); } } pub fn add_fn(&mut self, name: &'static str, f: impl Fn(Vec) -> Value + 'static) { self.functions .insert(name.into(), Function::new(name.into(), f)); } } pub trait Eval { fn eval(&self, scope: &mut Scope) -> Value; } impl Eval for Constant { fn eval(&self, _scope: &mut Scope) -> Value { match self { Constant::Nil => None, Constant::Boolean(value) => Some(ValueInner::Boolean(*value)), Constant::Number(value) => Some(ValueInner::Number(*value)), Constant::String(value) => Some(ValueInner::String(value.clone())), } } } impl Eval for Location { fn eval(&self, scope: &mut Scope) -> Value { match self { Location::Variable { name } => scope.get(name), Location::Field { table, index } => { let table = table.eval(scope); let Some(ValueInner::Table(table)) = table else { panic!("attempt to index non-table value {table:?}") }; let index = index.eval(scope); let Some(index) = index else { panic!("attempt to index with a nil value") }; table.get(index) } } } } impl Eval for Expression { fn eval(&self, scope: &mut Scope) -> Value { match self { Expression::Constant(inner) => inner.eval(scope), Expression::Variable(inner) => inner.eval(scope), Expression::Call { callee, args } => { let Some(function) = scope.functions.get(callee).cloned() else { panic!("attempt to call non-existent function {callee}") }; function.call(args.into_iter().map(|arg| arg.eval(scope)).collect()) } } } } #[cfg(test)] mod tests { use super::*; use crate::{ast, types}; #[test] fn test_consts() { let mut scope = Scope::default(); assert_eq!(ast::Constant::Nil.eval(&mut scope), None); assert_eq!( ast::Constant::Boolean(true).eval(&mut scope), Some(true.into()) ); assert_eq!(ast::Constant::Number(42).eval(&mut scope), Some(42.into())); assert_eq!( ast::Constant::String("foobar".into()).eval(&mut scope), Some("foobar".into()) ); } #[test] fn test_vars() { let mut scope = Scope::default(); let foo = ast::Location::Variable { name: "foo".into() }; let bar = ast::Location::Variable { name: "bar".into() }; assert_eq!(foo.eval(&mut scope), None); assert_eq!(bar.eval(&mut scope), None); scope.variables.insert("bar".into(), 42.into()); assert_eq!(foo.eval(&mut scope), None); assert_eq!(bar.eval(&mut scope), Some(42.into())); } #[test] fn test_fields() { let mut scope = Scope::default(); let loc = ast::Location::Field { table: Box::new(ast::Location::Variable { name: "foo".into() }), index: Box::new(ast::Expression::Constant(ast::Constant::String( "bar".into(), ))), }; let foo = types::Table::default(); let bar = types::Table::default(); scope.variables.insert("foo".into(), foo.clone().into()); assert_eq!(loc.eval(&mut scope), None); scope.variables.insert("bar".into(), bar.clone().into()); assert_eq!(loc.eval(&mut scope), None); bar.set("foo".into(), Some(666.into())); assert_eq!(loc.eval(&mut scope), None); foo.set("bar".into(), Some(42.into())); assert_eq!(loc.eval(&mut scope), Some(42.into())); } }