use std::{collections::HashMap, rc::Rc};
use crate::{
stdlib::{NativeError, NativeFunction, NativeResult},
value::Value,
};
pub enum FunctionResult {
Exists,
NotFound,
WrongArity(usize, usize),
}
pub trait Environment {
fn variable(&self, name: &str) -> Option<Rc<Value>>;
fn call(&self, name: &str, params: &[Value]) -> NativeResult;
}
pub trait ValidateEnvironment {
fn variable_exists(&self, name: &str) -> bool;
fn function_exists(&self, name: &str, arity: usize) -> FunctionResult;
}
#[derive(Clone, Copy)]
pub enum Arity {
Polyadic { required: usize, optional: usize },
Variadic,
None,
}
impl Arity {
#[must_use]
pub const fn required(required: usize) -> Self {
Self::Polyadic {
required,
optional: 0,
}
}
#[must_use]
pub const fn optional(required: usize, optional: usize) -> Self {
Self::Polyadic { required, optional }
}
}
#[derive(Clone)]
pub struct Function {
pub name: String,
pub func: NativeFunction,
pub arity: Arity,
pub params: String,
}
impl Function {
#[must_use]
pub fn new(func: NativeFunction, arity: Arity, declaration: &str) -> Self {
let (name, params) = declaration
.split_once('(')
.map(|(name, param)| (name, format!("({param}")))
.unwrap_or((declaration, String::new()));
Self {
name: name.trim().to_string(),
func,
arity,
params,
}
}
}
#[derive(Default)]
pub struct StaticEnvironment {
variables: HashMap<String, Rc<Value>>,
functions: HashMap<String, Rc<Function>>,
}
fn get_env_key(name: &str) -> String {
name.to_lowercase()
}
impl StaticEnvironment {
pub fn add_variable(&mut self, name: &str, value: Value) {
self.variables.insert(get_env_key(name), Rc::new(value));
}
pub fn remove_variable(&mut self, name: &str) -> Option<Rc<Value>> {
self.variables.remove(&get_env_key(name))
}
pub fn clear_variables(&mut self) {
self.variables.clear();
}
pub fn add_function(&mut self, func: Function) {
self.functions
.insert(get_env_key(&func.name), Rc::new(func));
}
pub fn add_functions(&mut self, functions: Vec<Function>) {
for func in functions {
self.add_function(func);
}
}
pub fn remove_function(&mut self, name: &str) -> Option<Rc<Function>> {
self.functions.remove(&get_env_key(name))
}
#[must_use]
pub fn list_functions(&self) -> Vec<Rc<Function>> {
self.functions.values().cloned().collect()
}
}
impl Environment for StaticEnvironment {
fn variable(&self, name: &str) -> Option<Rc<Value>> {
self.variables.get(&get_env_key(name)).cloned()
}
fn call(&self, name: &str, params: &[Value]) -> NativeResult {
let function = self
.functions
.get(&get_env_key(name))
.ok_or(NativeError::FunctionNotFound(name.to_string()))?;
let call = function.func;
call(params)
}
}
impl ValidateEnvironment for StaticEnvironment {
fn variable_exists(&self, name: &str) -> bool {
self.variables.contains_key(&get_env_key(name))
}
fn function_exists(&self, name: &str, param_count: usize) -> FunctionResult {
if let Some(function) = self.functions.get(&get_env_key(name)) {
match function.arity {
Arity::Polyadic { required, optional } => {
let lower = required;
let upper = required + optional;
if param_count < lower {
FunctionResult::WrongArity(param_count, lower)
} else if param_count > upper {
FunctionResult::WrongArity(param_count, upper)
} else {
FunctionResult::Exists
}
}
Arity::Variadic => FunctionResult::Exists,
Arity::None => FunctionResult::WrongArity(param_count, 0),
}
} else {
FunctionResult::NotFound
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{compile, execute};
#[test]
fn static_variables() {
let mut env = StaticEnvironment::default();
env.add_variable("some_var", Value::Number(42.0));
let ast = compile("some_var = 42").unwrap();
assert_eq!(Ok(Value::Boolean(true)), execute(&env, &ast));
env.remove_variable("some_var");
assert_eq!(Ok(Value::Boolean(false)), execute(&env, &ast));
env.add_variable("some_var", Value::Number(42.0));
let ast = compile("some_var = 42").unwrap();
assert_eq!(Ok(Value::Boolean(true)), execute(&env, &ast));
env.clear_variables();
assert_eq!(Ok(Value::Boolean(false)), execute(&env, &ast));
}
#[test]
fn static_functions() {
fn test_func(_params: &[Value]) -> NativeResult {
unreachable!()
}
let mut env = StaticEnvironment::default();
env.add_function(Function::new(test_func, Arity::Variadic, "test(...)"));
let registered = env.list_functions();
assert_eq!(1, registered.len());
assert_eq!("test", registered.first().unwrap().name);
let removed = env.remove_function("test").unwrap();
assert_eq!(removed.name, registered.first().unwrap().name);
}
#[test]
fn new_function() {
fn test_func(_params: &[Value]) -> NativeResult {
unreachable!()
}
let func = Function::new(test_func, Arity::None, "some_name(param: Number): Number");
assert_eq!("some_name", func.name);
assert_eq!("(param: Number): Number", func.params);
let func = Function::new(test_func, Arity::None, "only_name");
assert_eq!("only_name", func.name);
assert_eq!("", func.params);
}
}