use std::{collections::HashMap, error::Error, fmt::Display};
use crate::{
parser::ExprValue,
stdlib::{self, quote::call},
};
pub type EvalFn = fn(Vec<EvalResult>) -> EvalResult;
#[derive(Debug, PartialEq, Clone)]
pub struct EvalContext<'a> {
pub vars: HashMap<String, ExprValue<'a>>,
pub fns: HashMap<String, EvalFn>,
}
impl<'a> Default for EvalContext<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> EvalContext<'a> {
pub fn new() -> Self {
Self {
vars: HashMap::new(),
fns: HashMap::new(),
}
}
pub fn with_var<N: Into<String>>(&self, name: N, value: ExprValue<'a>) -> Self {
let mut cloned = self.clone();
cloned.vars.insert(name.into(), value);
cloned
}
pub fn with_fn<N: Into<String>>(&self, name: N, value: EvalFn) -> Self {
let mut cloned = self.clone();
cloned.fns.insert(name.into(), value);
cloned
}
pub fn merge(&self, other: Self) -> Self {
let mut cloned = self.clone();
for fun in other.fns {
cloned.fns.insert(fun.0, fun.1);
}
for var in other.vars {
cloned.vars.insert(var.0, var.1);
}
cloned
}
}
#[derive(Debug, Clone)]
pub struct NoSuchIdentError {
pub ident: String,
}
impl Error for NoSuchIdentError {}
impl Display for NoSuchIdentError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("No such identifier: {}", self.ident))
}
}
#[derive(PartialEq, Debug, Clone)]
pub enum EvalResult<'a> {
String(String),
Array(Vec<EvalResult<'a>>),
Object(Vec<(String, EvalResult<'a>)>),
Number(f64),
Boolean(bool),
Quoted(Box<ExprValue<'a>>, EvalContext<'a>),
None,
}
#[derive(Clone, Debug)]
enum Callable<'a> {
Fn(EvalFn),
Quote(EvalResult<'a>),
}
impl<'a> From<EvalResult<'a>> for ExprValue<'a> {
fn from(val: EvalResult<'a>) -> Self {
match val {
EvalResult::String(s) => ExprValue::String(s),
EvalResult::Array(eval_results) => {
ExprValue::Array(eval_results.iter().map(|r| r.clone().into()).collect())
}
EvalResult::Object(items) => ExprValue::Object(
items
.iter()
.map(|(k, v)| (k.clone(), v.clone().into()))
.collect(),
),
EvalResult::Number(f) => ExprValue::Number(f),
EvalResult::Boolean(b) => ExprValue::Boolean(b),
EvalResult::Quoted(expr_value, ctx) => ExprValue::QuoteWithCtx(expr_value, ctx),
EvalResult::None => ExprValue::Null,
}
}
}
impl<'a> Display for EvalResult<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(s) => f.write_str(s),
EvalResult::Array(v) => {
f.write_str("[")?;
for item in v {
item.fmt(f)?;
if item != v.last().unwrap() {
f.write_str(", ")?;
}
}
f.write_str("]")
}
EvalResult::Object(items) => {
f.write_str("{")?;
for item in items {
f.write_fmt(format_args!("{}: {}", item.0, item.1))?;
}
f.write_str("}")
}
EvalResult::Number(n) => f.write_fmt(format_args!("{n}")),
EvalResult::Boolean(b) => f.write_fmt(format_args!("{b}")),
EvalResult::Quoted(q, _) => f.write_fmt(format_args!("'{q:?}")),
EvalResult::None => f.write_str("null"),
}
}
}
fn resolve_identifier<'a, R>(
i: String,
map: &'a HashMap<String, R>,
pkgs: Option<&ExprValue<'a>>,
ctx: &EvalContext<'a>,
) -> Result<&'a R, NoSuchIdentError> {
map.get(&i)
.or_else(|| {
pkgs.and_then(|pkgs| {
if let Ok(EvalResult::Array(arr)) = eval(pkgs.clone(), ctx.clone()) {
arr.iter()
.map(|pkg| {
if let EvalResult::String(s) = pkg {
map.get(&format!("{}:{}", s, i))
} else {
None
}
})
.find(|o| o.is_some())
} else {
None
}
})
.flatten()
})
.ok_or(NoSuchIdentError {
ident: i.to_string(),
})
}
pub fn eval<'a>(
value: ExprValue<'a>,
ctx: EvalContext<'a>,
) -> Result<EvalResult<'a>, NoSuchIdentError> {
Ok(match value {
ExprValue::String(s) => EvalResult::String(s.to_string()),
ExprValue::Array(a) => EvalResult::Array(
a.iter()
.map(|v| eval(v.clone(), ctx.clone()))
.collect::<Result<Vec<_>, _>>()?,
),
ExprValue::Object(items) => EvalResult::Object(
items
.iter()
.map(|(key, val)| Ok((key.to_string(), eval(val.clone(), ctx.clone())?)))
.collect::<Result<Vec<_>, _>>()?,
),
ExprValue::Number(n) => EvalResult::Number(n),
ExprValue::Boolean(b) => EvalResult::Boolean(b),
ExprValue::Ident(i) => eval(
resolve_identifier(i.to_string(), &ctx.vars, ctx.vars.get("$use"), &ctx)?.clone(),
ctx,
)?,
ExprValue::FnCall(name, expr_values) => {
let c = resolve_identifier(name.to_string(), &ctx.fns, ctx.vars.get("$use"), &ctx)
.map(|f| Callable::Fn(f.clone()))
.or_else(|e| {
let ident_v =
resolve_identifier(name.to_string(), &ctx.vars, ctx.vars.get("$use"), &ctx);
if let Ok(ExprValue::QuoteWithCtx(q, ctx)) = ident_v {
Ok(Callable::Quote(EvalResult::Quoted(q.clone(), ctx.clone())))
} else if let Ok(ExprValue::Quote(q)) = ident_v {
Ok(Callable::Quote(EvalResult::Quoted(q.clone(), ctx.clone())))
} else {
Err(e)
}
})?;
let args = if let EvalResult::Array(args) = eval(expr_values.as_ref().clone(), ctx)? {
args
} else {
vec![]
};
match c {
Callable::Fn(f) => f(args),
Callable::Quote(q) => call(vec![q, EvalResult::Array(args)]),
}
}
ExprValue::Quote(q) => EvalResult::Quoted(q, ctx.clone()),
ExprValue::QuoteWithCtx(q, cx) => EvalResult::Quoted(q, cx),
ExprValue::Null => EvalResult::None,
})
}
#[cfg(test)]
pub(crate) fn test<'a>(args: Vec<EvalResult>) -> EvalResult {
if let Some(EvalResult::String(str)) = args.first()
&& let Some(EvalResult::Number(n)) = args.get(1)
{
EvalResult::String(format!("{str} {n}"))
} else {
EvalResult::None
}
}
#[cfg(test)]
mod tests {
use crate::eval::test;
use std::collections::HashMap;
use crate::{
eval::{EvalContext, EvalFn, EvalResult, eval},
parser::ExprValue,
};
#[test]
fn eval_tree_test() {
let mut fns = HashMap::new();
fns.insert("test".to_string(), test as EvalFn);
let mut vars = HashMap::new();
vars.insert("hello".to_string(), ExprValue::String("world".into()));
assert_eq!(
eval(
ExprValue::FnCall(
"test",
Box::new(ExprValue::Array(vec![
ExprValue::Ident("hello"),
ExprValue::Number(3.14)
]))
),
EvalContext { fns, vars }
)
.unwrap(),
EvalResult::String("world 3.14".to_string())
);
}
}