use crate::ast::node::Node;
use crate::ast::program::Program;
use crate::context::ContextScope;
use crate::functions::{
array, bitwise, collection, convert, json, misc, number, string, temporal, ExprCall, Function,
};
use crate::parser::compile;
use crate::{bail, ContextProvider, Result, Value};
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use std::fmt;
use std::fmt::{Debug, Formatter};
pub fn run(program: &Program, ctx: &dyn ContextProvider) -> Result<Value> {
DEFAULT_ENVIRONMENT.run(program, ctx)
}
pub fn eval(code: &str, ctx: &dyn ContextProvider) -> Result<Value> {
DEFAULT_ENVIRONMENT.eval(code, ctx)
}
pub struct Environment<'a> {
pub(crate) functions: IndexMap<String, Function<'a>>,
}
impl Debug for Environment<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("ExprEnvironment").finish()
}
}
impl Default for Environment<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> Environment<'a> {
pub fn new() -> Self {
let mut p = Self {
functions: IndexMap::new(),
};
string::add_string_functions(&mut p);
temporal::add_temporal_functions(&mut p);
array::add_array_functions(&mut p);
bitwise::add_bitwise_functions(&mut p);
collection::add_collection_functions(&mut p);
convert::add_conversion_functions(&mut p);
json::add_json_functions(&mut p);
misc::add_misc_functions(&mut p);
number::add_number_functions(&mut p);
p
}
pub fn add_function<F>(&mut self, name: &str, f: F)
where
F: Fn(ExprCall) -> Result<Value> + 'a + Sync + Send,
{
self.functions.insert(name.to_string(), Box::new(f));
}
pub fn run(&self, program: &Program, ctx: &dyn ContextProvider) -> Result<Value> {
let mut ctx = ContextScope::new(ctx);
for (id, expr) in &program.lines {
ctx.insert(id, self.eval_expr(&ctx, expr)?);
}
self.eval_expr(&ctx, &program.expr)
}
pub(crate) fn run_with_binding(
&self,
program: &Program,
ctx: &dyn ContextProvider,
key: &str,
value: Value,
) -> Result<Value> {
self.run_with_bindings(program, ctx, [(key, value)])
}
pub(crate) fn run_with_bindings<'b>(
&self,
program: &Program,
ctx: &dyn ContextProvider,
bindings: impl IntoIterator<Item = (&'b str, Value)>,
) -> Result<Value> {
let mut scope = ContextScope::new(ctx);
for (key, value) in bindings {
scope.insert(key, value);
}
self.run(program, &scope)
}
pub fn eval(&self, code: &str, ctx: &dyn ContextProvider) -> Result<Value> {
let program = compile(code)?;
self.run(&program, ctx)
}
pub fn eval_expr(&self, ctx: &dyn ContextProvider, node: &Node) -> Result<Value> {
let value = match node {
Node::Value(value) => value.clone(),
Node::Ident(id) => {
if id == "$env" {
Value::Map(ctx.environment().0)
} else if let Some(value) = ctx.get(id) {
value.clone()
} else if let Some(item) = ctx
.get("#")
.and_then(|o| o.as_map())
.and_then(|m| m.get(id))
{
item.clone()
} else {
bail!("unknown variable: {id}")
}
}
Node::Func {
ident,
args,
predicate,
} => {
let args = args
.iter()
.map(|e| self.eval_expr(ctx, e))
.collect::<Result<_>>()?;
self.eval_func(ctx, ident, args, predicate.as_deref())?
}
Node::Operation {
left,
operator,
right,
compiled_regex,
} => self.eval_operator(ctx, operator, left, right, compiled_regex.as_ref())?,
Node::Unary { operator, node } => self.eval_unary_operator(ctx, operator, node)?,
Node::Postfix { operator, node } => self.eval_postfix_operator(ctx, operator, node)?,
Node::Array(a) => Value::Array(
a.iter()
.map(|e| self.eval_expr(ctx, e))
.collect::<Result<_>>()?,
), Node::Range(start, end) => {
match (self.eval_expr(ctx, start)?, self.eval_expr(ctx, end)?) {
(Value::Integer(start), Value::Integer(end)) => {
Value::Array((start..=end).map(Value::Integer).collect())
}
(start, end) => bail!("invalid range: {start:?}..{end:?}"),
}
}
Node::Conditional { condition, consequent, alternative } => {
match self.eval_expr(ctx, condition)? {
Value::Bool(true) => self.run(consequent, ctx)?,
Value::Bool(false) => self.run(alternative, ctx)?,
value => bail!("Invalid condition for if: {value:?}"),
}
}
};
Ok(value)
}
}
pub(crate) static DEFAULT_ENVIRONMENT: Lazy<Environment> = Lazy::new(Environment::new);