diff --git a/bindings/c/src/decision.rs b/bindings/c/src/decision.rs index 7f685ada..fcd4aabb 100644 --- a/bindings/c/src/decision.rs +++ b/bindings/c/src/decision.rs @@ -1,7 +1,7 @@ +use serde_json::Value; use std::ffi::{c_char, c_void, CStr, CString}; use std::marker::{PhantomData, PhantomPinned}; use std::ops::{Deref, DerefMut}; - use zen_engine::Decision; use crate::custom_node::DynamicCustomNode; @@ -69,13 +69,13 @@ pub extern "C" fn zen_decision_evaluate( return ZenResult::error(ZenError::InvalidArgument); }; - let Ok(context) = serde_json::from_str(str_context) else { + let Ok(context) = serde_json::from_str::(str_context) else { return ZenResult::error(ZenError::JsonDeserializationFailed); }; let zen_decision = unsafe { &*(decision as *mut ZenDecision) }; let maybe_result = - tokio_runtime().block_on(zen_decision.evaluate_with_opts(&context, options.into())); + tokio_runtime().block_on(zen_decision.evaluate_with_opts(context.into(), options.into())); let result = match maybe_result { Ok(r) => r, Err(e) => return ZenResult::from(&e), diff --git a/bindings/c/src/engine.rs b/bindings/c/src/engine.rs index 5678f14e..cc52e61d 100644 --- a/bindings/c/src/engine.rs +++ b/bindings/c/src/engine.rs @@ -1,8 +1,8 @@ +use serde_json::Value; use std::ffi::{c_char, CStr, CString}; use std::marker::{PhantomData, PhantomPinned}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; - use zen_engine::{DecisionEngine, EvaluationOptions}; use crate::custom_node::DynamicCustomNode; @@ -121,7 +121,7 @@ pub extern "C" fn zen_engine_evaluate( }; let cstr_context = unsafe { CStr::from_ptr(context) }; - let Ok(val_context) = serde_json::from_slice(cstr_context.to_bytes()) else { + let Ok(val_context) = serde_json::from_slice::(cstr_context.to_bytes()) else { return ZenResult::error(ZenError::JsonDeserializationFailed); }; @@ -129,7 +129,7 @@ pub extern "C" fn zen_engine_evaluate( let maybe_result = tokio_runtime().block_on(zen_engine.evaluate_with_opts( str_key, - &val_context, + val_context.into(), options.into(), )); let result = match maybe_result { diff --git a/bindings/c/src/expression.rs b/bindings/c/src/expression.rs index 9e49753d..44143454 100644 --- a/bindings/c/src/expression.rs +++ b/bindings/c/src/expression.rs @@ -1,7 +1,7 @@ use std::ffi::CString; use libc::{c_char, c_int}; - +use serde_json::Value; use zen_expression::{evaluate_expression, evaluate_unary_expression}; use crate::error::ZenError; @@ -21,11 +21,11 @@ pub extern "C" fn zen_evaluate_expression( return ZenResult::error(ZenError::InvalidArgument); }; - let Ok(context_val) = serde_json::from_slice(context_cstr.to_bytes()) else { + let Ok(context_val) = serde_json::from_slice::(context_cstr.to_bytes()) else { return ZenResult::error(ZenError::JsonDeserializationFailed); }; - let maybe_result = evaluate_expression(expression_str, &context_val); + let maybe_result = evaluate_expression(expression_str, context_val.into()); let result = match maybe_result { Ok(r) => r, Err(err) => return ZenResult::from(&err), @@ -55,11 +55,11 @@ pub extern "C" fn zen_evaluate_unary_expression( return ZenResult::error(ZenError::InvalidArgument); }; - let Ok(context_val) = serde_json::from_slice(context_cstr.to_bytes()) else { + let Ok(context_val) = serde_json::from_slice::(context_cstr.to_bytes()) else { return ZenResult::error(ZenError::JsonDeserializationFailed); }; - let maybe_result = evaluate_unary_expression(expression_str, &context_val); + let maybe_result = evaluate_unary_expression(expression_str, context_val.into()); let result = match maybe_result { Ok(r) => r, Err(err) => return ZenResult::from(&err), @@ -86,11 +86,11 @@ pub extern "C" fn zen_evaluate_template( return ZenResult::error(ZenError::InvalidArgument); }; - let Ok(context_val) = serde_json::from_slice(context_cstr.to_bytes()) else { + let Ok(context_val) = serde_json::from_slice::(context_cstr.to_bytes()) else { return ZenResult::error(ZenError::JsonDeserializationFailed); }; - let result = match zen_tmpl::render(template_str, &context_val) { + let result = match zen_tmpl::render(template_str, context_val.into()) { Ok(r) => r, Err(err) => { return ZenResult::error(ZenError::TemplateEngineError { diff --git a/bindings/nodejs/src/custom_node.rs b/bindings/nodejs/src/custom_node.rs index 481d5331..e8426b9e 100644 --- a/bindings/nodejs/src/custom_node.rs +++ b/bindings/nodejs/src/custom_node.rs @@ -31,7 +31,7 @@ impl CustomNodeAdapter for CustomNode { let promise: Promise = function .clone() .call_async(ZenEngineHandlerRequest { - input: request.input.clone(), + input: request.input.to_value(), node: node_data, }) .await @@ -40,7 +40,7 @@ impl CustomNodeAdapter for CustomNode { let result = promise.await.map_err(|err| anyhow!(err.reason))?; Ok(NodeResponse { - output: result.output, + output: result.output.into(), trace_data: result.trace_data, }) } diff --git a/bindings/nodejs/src/decision.rs b/bindings/nodejs/src/decision.rs index d9cf4ff6..0e7a0a18 100644 --- a/bindings/nodejs/src/decision.rs +++ b/bindings/nodejs/src/decision.rs @@ -39,13 +39,14 @@ impl ZenDecision { async move { decision .evaluate_with_opts( - &context, + context.into(), EvaluationOptions { max_depth: options.max_depth, trace: options.trace, }, ) .await + .map(ZenEngineResponse::from) } }) .await @@ -54,7 +55,7 @@ impl ZenDecision { anyhow!(serde_json::to_string(e.as_ref()).unwrap_or_else(|_| e.to_string())) })?; - Ok(ZenEngineResponse::from(result)) + Ok(result) } #[napi(ts_return_type = "Promise>")] diff --git a/bindings/nodejs/src/engine.rs b/bindings/nodejs/src/engine.rs index 78452b38..2b2cd702 100644 --- a/bindings/nodejs/src/engine.rs +++ b/bindings/nodejs/src/engine.rs @@ -118,13 +118,14 @@ impl ZenEngine { graph .evaluate_with_opts( key, - &context, + context.into(), EvaluationOptions { max_depth: options.max_depth, trace: options.trace, }, ) .await + .map(ZenEngineResponse::from) } }) .await @@ -133,7 +134,7 @@ impl ZenEngine { anyhow!(serde_json::to_string(e.as_ref()).unwrap_or_else(|_| e.to_string())) })?; - Ok(ZenEngineResponse::from(result)) + Ok(result) } #[napi] diff --git a/bindings/nodejs/src/expression.rs b/bindings/nodejs/src/expression.rs index 6c786560..0fdc1d99 100644 --- a/bindings/nodejs/src/expression.rs +++ b/bindings/nodejs/src/expression.rs @@ -7,8 +7,9 @@ pub fn evaluate_expression_sync(expression: String, context: Option) -> n let ctx = context.unwrap_or(Value::Null); Ok( - zen_expression::evaluate_expression(expression.as_str(), &ctx) - .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?, + zen_expression::evaluate_expression(expression.as_str(), ctx.into()) + .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))? + .to_value(), ) } @@ -16,7 +17,7 @@ pub fn evaluate_expression_sync(expression: String, context: Option) -> n #[napi] pub fn evaluate_unary_expression_sync(expression: String, context: Value) -> napi::Result { Ok( - zen_expression::evaluate_unary_expression(expression.as_str(), &context) + zen_expression::evaluate_unary_expression(expression.as_str(), context.into()) .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?, ) } @@ -24,8 +25,9 @@ pub fn evaluate_unary_expression_sync(expression: String, context: Value) -> nap #[allow(dead_code)] #[napi] pub fn render_template_sync(template: String, context: Value) -> napi::Result { - Ok(zen_tmpl::render(template.as_str(), &context) - .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?) + Ok(zen_tmpl::render(template.as_str(), context.into()) + .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))? + .to_value()) } #[allow(dead_code)] diff --git a/bindings/nodejs/src/types.rs b/bindings/nodejs/src/types.rs index 7851f282..4f5e6d50 100644 --- a/bindings/nodejs/src/types.rs +++ b/bindings/nodejs/src/types.rs @@ -7,6 +7,7 @@ use serde_json::Value; use zen_engine::handler::custom_node_adapter::CustomDecisionNode; use zen_engine::{DecisionGraphResponse, DecisionGraphTrace}; +use zen_expression::Variable; #[napi(object)] pub struct ZenEngineTrace { @@ -23,8 +24,8 @@ impl From for ZenEngineTrace { Self { id: value.id, name: value.name, - input: value.input, - output: value.output, + input: value.input.to_value(), + output: value.output.to_value(), performance: value.performance, trace_data: value.trace_data, } @@ -42,7 +43,7 @@ impl From for ZenEngineResponse { fn from(value: DecisionGraphResponse) -> Self { Self { performance: value.performance, - result: value.result, + result: value.result.to_value(), trace: value.trace.map(|opt| { opt.into_iter() .map(|(key, value)| (key, ZenEngineTrace::from(value))) @@ -104,10 +105,10 @@ impl ZenEngineHandlerRequest { return Ok(selected_value); }; - let template_value = zen_tmpl::render(template.as_str(), &self.input) + let template_value = zen_tmpl::render(template.as_str(), Variable::from(&self.input)) .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?; - Ok(template_value) + Ok(template_value.to_value()) } #[napi(ts_return_type = "unknown")] diff --git a/bindings/python/src/decision.rs b/bindings/python/src/decision.rs index 5ef7d4c7..32f7fa54 100644 --- a/bindings/python/src/decision.rs +++ b/bindings/python/src/decision.rs @@ -5,7 +5,7 @@ use pyo3::types::PyDict; use pyo3::{pyclass, pymethods, PyAny, PyObject, PyResult, Python, ToPyObject}; use pyo3_asyncio::tokio; use pythonize::depythonize; - +use serde_json::Value; use zen_engine::{Decision, EvaluationOptions}; use crate::custom_node::PyCustomNode; @@ -26,7 +26,7 @@ impl From> for PyZenDecision { #[pymethods] impl PyZenDecision { pub fn evaluate(&self, py: Python, ctx: &PyDict, opts: Option<&PyDict>) -> PyResult { - let context = depythonize(ctx).context("Failed to convert dict")?; + let context: Value = depythonize(ctx).context("Failed to convert dict")?; let options: PyZenEvaluateOptions = if let Some(op) = opts { depythonize(op).context("Failed to convert dict")? } else { @@ -35,7 +35,7 @@ impl PyZenDecision { let decision = self.0.clone(); let result = futures::executor::block_on(decision.evaluate_with_opts( - &context, + context.into(), EvaluationOptions { max_depth: options.max_depth, trace: options.trace, @@ -55,7 +55,7 @@ impl PyZenDecision { ctx: &PyDict, opts: Option<&PyDict>, ) -> PyResult<&PyAny> { - let context = depythonize(ctx).context("Failed to convert dict")?; + let context: Value = depythonize(ctx).context("Failed to convert dict")?; let options: PyZenEvaluateOptions = if let Some(op) = opts { depythonize(op).context("Failed to convert dict")? } else { @@ -65,7 +65,7 @@ impl PyZenDecision { let decision = self.0.clone(); tokio::future_into_py(py, async move { let result = futures::executor::block_on(decision.evaluate_with_opts( - &context, + context.into(), EvaluationOptions { max_depth: options.max_depth, trace: options.trace, diff --git a/bindings/python/src/engine.rs b/bindings/python/src/engine.rs index 2cfe8e9f..f61f226c 100644 --- a/bindings/python/src/engine.rs +++ b/bindings/python/src/engine.rs @@ -6,7 +6,7 @@ use pyo3::{pyclass, pymethods, PyAny, PyObject, PyResult, Python, ToPyObject}; use pyo3_asyncio::tokio; use pythonize::depythonize; use serde::{Deserialize, Serialize}; - +use serde_json::Value; use zen_engine::model::DecisionContent; use zen_engine::{DecisionEngine, EvaluationOptions}; @@ -82,7 +82,7 @@ impl PyZenEngine { ctx: &PyDict, opts: Option<&PyDict>, ) -> PyResult { - let context = depythonize(ctx).context("Failed to convert dict")?; + let context: Value = depythonize(ctx).context("Failed to convert dict")?; let options: PyZenEvaluateOptions = if let Some(op) = opts { depythonize(op).context("Failed to convert dict")? } else { @@ -92,7 +92,7 @@ impl PyZenEngine { let graph = self.graph.clone(); let result = futures::executor::block_on(graph.evaluate_with_opts( key, - &context, + context.into(), EvaluationOptions { max_depth: options.max_depth, trace: options.trace, @@ -113,7 +113,7 @@ impl PyZenEngine { ctx: &PyDict, opts: Option<&PyDict>, ) -> PyResult<&PyAny> { - let context = depythonize(ctx).context("Failed to convert dict")?; + let context: Value = depythonize(ctx).context("Failed to convert dict")?; let options: PyZenEvaluateOptions = if let Some(op) = opts { depythonize(op).context("Failed to convert dict")? } else { @@ -124,7 +124,7 @@ impl PyZenEngine { tokio::future_into_py(py, async move { let result = futures::executor::block_on(graph.evaluate_with_opts( key, - &context, + context.into(), EvaluationOptions { max_depth: options.max_depth, trace: options.trace, @@ -148,7 +148,7 @@ impl PyZenEngine { Ok(PyZenDecision::from(decision)) } - pub fn get_decision<'py>(&'py self, py: Python<'py>, key: String) -> PyResult { + pub fn get_decision<'py>(&'py self, _py: Python<'py>, key: String) -> PyResult { let decision = futures::executor::block_on(self.graph.get_decision(&key)) .context("Failed to find decision with given key")?; diff --git a/bindings/python/src/expression.rs b/bindings/python/src/expression.rs index a545d2ce..0520ef17 100644 --- a/bindings/python/src/expression.rs +++ b/bindings/python/src/expression.rs @@ -18,17 +18,17 @@ pub fn evaluate_expression( .context("Failed to convert context")? .unwrap_or(Value::Null); - let result = zen_expression::evaluate_expression(expression.as_str(), &context) + let result = zen_expression::evaluate_expression(expression.as_str(), context.into()) .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?; - Ok(PyValue(result).to_object(py)) + Ok(PyValue(result.to_value()).to_object(py)) } #[pyfunction] pub fn evaluate_unary_expression(expression: String, ctx: &PyDict) -> PyResult { let context: Value = depythonize(ctx).context("Failed to convert context")?; - let result = zen_expression::evaluate_unary_expression(expression.as_str(), &context) + let result = zen_expression::evaluate_unary_expression(expression.as_str(), context.into()) .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?; Ok(result) @@ -38,8 +38,8 @@ pub fn evaluate_unary_expression(expression: String, ctx: &PyDict) -> PyResult PyResult { let context: Value = depythonize(ctx).context("Failed to convert context")?; - let result = zen_tmpl::render(template.as_str(), &context) + let result = zen_tmpl::render(template.as_str(), context.into()) .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?; - Ok(PyValue(result).to_object(py)) + Ok(PyValue(result.to_value()).to_object(py)) } diff --git a/bindings/python/src/types.rs b/bindings/python/src/types.rs index 20acb614..eb2dd106 100644 --- a/bindings/python/src/types.rs +++ b/bindings/python/src/types.rs @@ -4,12 +4,12 @@ use pyo3::{pyclass, pymethods, PyObject, PyResult, Python, ToPyObject}; use serde::Serialize; use serde_json::Value; +use crate::value::{value_to_object, PyValue}; use zen_engine::handler::custom_node_adapter::{ CustomDecisionNode as BaseCustomDecisionNode, CustomNodeRequest, }; use zen_engine::handler::node::NodeResponse; - -use crate::value::{value_to_object, PyValue}; +use zen_expression::Variable; #[derive(Serialize)] struct CustomDecisionNode { @@ -50,10 +50,10 @@ impl PyNodeRequest { let node_val = serde_json::to_value(&inner_node).unwrap(); Ok(Self { - input: value_to_object(py, &value.input), + input: value_to_object(py, &value.input.to_value()), node: value_to_object(py, &node_val), - inner_input: value.input.clone(), + inner_input: value.input.to_value(), inner_node, }) } @@ -73,10 +73,10 @@ impl PyNodeRequest { return Ok(PyValue(selected_value).to_object(py)); }; - let template_value = zen_tmpl::render(template.as_str(), &self.inner_input) + let template_value = zen_tmpl::render(template.as_str(), Variable::from(&self.inner_input)) .map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?; - Ok(PyValue(template_value).to_object(py)) + Ok(PyValue(template_value.to_value()).to_object(py)) } fn get_field_raw(&self, py: Python, path: String) -> PyResult { @@ -102,7 +102,7 @@ pub struct PyNodeResponse { impl From for PyNodeResponse { fn from(value: NodeResponse) -> Self { Self { - output: value.output, + output: value.output.to_value(), trace_data: value.trace_data, } } @@ -111,7 +111,7 @@ impl From for PyNodeResponse { impl From for NodeResponse { fn from(value: PyNodeResponse) -> Self { Self { - output: value.output, + output: value.output.into(), trace_data: value.trace_data, } } diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index 55f8b4df..61a85746 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/gorules/zen.git" doctest = false [dependencies] +ahash = { workspace = true } anyhow = { workspace = true } thiserror = { workspace = true } bincode = { workspace = true, optional = true } @@ -19,6 +20,7 @@ serde_json = { workspace = true, features = ["arbitrary_precision"] } serde = { workspace = true, features = ["derive"] } once_cell = { workspace = true } json_dotpath = { workspace = true } +rust_decimal = { workspace = true, features = ["maths-nopanic"] } fixedbitset = "0.4.2" tokio = { workspace = true, features = ["sync", "time"] } reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } diff --git a/core/engine/benches/engine.rs b/core/engine/benches/engine.rs index a11b7885..ea8f8876 100644 --- a/core/engine/benches/engine.rs +++ b/core/engine/benches/engine.rs @@ -1,11 +1,12 @@ use criterion::{criterion_group, criterion_main, Bencher, Criterion}; -use serde_json::{json, Value}; +use serde_json::json; use std::path::Path; use std::sync::Arc; use tokio::runtime::Runtime; use zen_engine::handler::custom_node_adapter::NoopCustomNode; use zen_engine::loader::{FilesystemLoader, FilesystemLoaderOptions}; use zen_engine::DecisionEngine; +use zen_expression::variable::Variable; fn create_graph() -> DecisionEngine { let cargo_root = Path::new(env!("CARGO_MANIFEST_DIR")); @@ -21,32 +22,32 @@ fn create_graph() -> DecisionEngine { DecisionEngine::new(Arc::new(loader), Arc::new(NoopCustomNode::default())) } -fn bench_decision(b: &mut Bencher, key: &str, context: Value) { +fn bench_decision(b: &mut Bencher, key: &str, context: Variable) { let rt = Runtime::new().unwrap(); let graph = create_graph(); let decision = rt.block_on(graph.get_decision(key)).unwrap(); b.to_async(&rt).iter(|| async { - criterion::black_box(decision.evaluate(&context).await.unwrap()); + criterion::black_box(decision.evaluate(context.clone()).await.unwrap()); }); } -fn bench_loader(b: &mut Bencher, key: &str, context: Value) { +fn bench_loader(b: &mut Bencher, key: &str, context: Variable) { let rt = Runtime::new().unwrap(); let graph = create_graph(); b.to_async(&rt).iter(|| async { - criterion::black_box(graph.evaluate(key, &context).await.unwrap()); + criterion::black_box(graph.evaluate(key, context.clone()).await.unwrap()); }); } fn bench_functions(c: &mut Criterion) { c.bench_function("loader/table", |b| { - bench_loader(b, "table.json", json!({ "input": 15 })); + bench_loader(b, "table.json", json!({ "input": 15 }).into()); }); c.bench_function("decision/table", |b| { - bench_decision(b, "table.json", json!({ "input": 15 })); + bench_decision(b, "table.json", json!({ "input": 15 }).into()); }); } diff --git a/core/engine/src/decision.rs b/core/engine/src/decision.rs index d044124e..aaf8a2bb 100644 --- a/core/engine/src/decision.rs +++ b/core/engine/src/decision.rs @@ -1,13 +1,12 @@ use std::sync::Arc; -use serde_json::Value; - use crate::engine::EvaluationOptions; use crate::handler::custom_node_adapter::{CustomNodeAdapter, NoopCustomNode}; use crate::handler::graph::{DecisionGraph, DecisionGraphConfig, DecisionGraphResponse}; use crate::loader::{CachedLoader, DecisionLoader, NoopLoader}; use crate::model::DecisionContent; use crate::{DecisionGraphValidationError, EvaluationError}; +use zen_expression::variable::Variable; /// Represents a JDM decision which can be evaluated #[derive(Debug, Clone)] @@ -71,7 +70,7 @@ where /// Evaluates a decision using an in-memory reference stored in struct pub async fn evaluate( &self, - context: &Value, + context: Variable, ) -> Result> { self.evaluate_with_opts(context, Default::default()).await } @@ -79,7 +78,7 @@ where /// Evaluates a decision using in-memory reference with advanced options pub async fn evaluate_with_opts( &self, - context: &Value, + context: Variable, options: EvaluationOptions, ) -> Result> { let mut decision_graph = DecisionGraph::try_new(DecisionGraphConfig { diff --git a/core/engine/src/engine.rs b/core/engine/src/engine.rs index 680c1534..ab9e2455 100644 --- a/core/engine/src/engine.rs +++ b/core/engine/src/engine.rs @@ -1,14 +1,13 @@ use std::future::Future; use std::sync::Arc; -use serde_json::Value; - use crate::decision::Decision; use crate::handler::custom_node_adapter::{CustomNodeAdapter, NoopCustomNode}; use crate::handler::graph::DecisionGraphResponse; use crate::loader::{ClosureLoader, DecisionLoader, LoaderResponse, LoaderResult, NoopLoader}; use crate::model::DecisionContent; use crate::EvaluationError; +use zen_expression::variable::Variable; /// Structure used for generating and evaluating JDM decisions #[derive(Debug, Clone)] @@ -76,7 +75,7 @@ impl DecisionEngine pub async fn evaluate( &self, key: K, - context: &Value, + context: Variable, ) -> Result> where K: AsRef, @@ -89,7 +88,7 @@ impl DecisionEngine pub async fn evaluate_with_opts( &self, key: K, - context: &Value, + context: Variable, options: EvaluationOptions, ) -> Result> where diff --git a/core/engine/src/handler/custom_node_adapter.rs b/core/engine/src/handler/custom_node_adapter.rs index 47d9ec5e..c9a019ab 100644 --- a/core/engine/src/handler/custom_node_adapter.rs +++ b/core/engine/src/handler/custom_node_adapter.rs @@ -4,13 +4,14 @@ use anyhow::anyhow; use json_dotpath::DotPaths; use serde::Serialize; use serde_json::Value; +use zen_expression::variable::Variable; use zen_tmpl::TemplateRenderError; pub trait CustomNodeAdapter { fn handle( &self, request: CustomNodeRequest<'_>, - ) -> impl std::future::Future + Send; + ) -> impl std::future::Future; } #[derive(Default, Debug)] @@ -25,7 +26,7 @@ impl CustomNodeAdapter for NoopCustomNode { #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct CustomNodeRequest<'a> { - pub input: &'a Value, + pub input: Variable, pub node: CustomDecisionNode<'a>, } @@ -34,27 +35,27 @@ impl<'a> TryFrom<&'a NodeRequest<'a>> for CustomNodeRequest<'a> { fn try_from(value: &'a NodeRequest<'a>) -> Result { Ok(Self { - input: &value.input, + input: value.input.clone(), node: value.node.try_into()?, }) } } impl<'a> CustomNodeRequest<'a> { - pub fn get_field(&self, path: &str) -> Result, TemplateRenderError> { + pub fn get_field(&self, path: &str) -> Result, TemplateRenderError> { let Some(selected_value) = self.get_field_raw(path) else { return Ok(None); }; - let Value::String(template) = selected_value else { + let Variable::String(template) = selected_value else { return Ok(Some(selected_value)); }; - let template_value = zen_tmpl::render(template.as_str(), &self.input)?; + let template_value = zen_tmpl::render(template.as_ref(), self.input.clone())?; Ok(Some(template_value)) } - fn get_field_raw(&self, path: &str) -> Option { + fn get_field_raw(&self, path: &str) -> Option { self.node.config.dot_get(path).ok().flatten() } } diff --git a/core/engine/src/handler/decision.rs b/core/engine/src/handler/decision.rs index 4ce8cbeb..53a0dbe0 100644 --- a/core/engine/src/handler/decision.rs +++ b/core/engine/src/handler/decision.rs @@ -64,7 +64,7 @@ impl DecisionHandle .with_function(self.js_function.clone()); let result = sub_tree - .evaluate(&request.input) + .evaluate(request.input.clone()) .await .map_err(|e| e.source)?; diff --git a/core/engine/src/handler/expression/mod.rs b/core/engine/src/handler/expression/mod.rs index 33eb047e..a78295f0 100644 --- a/core/engine/src/handler/expression/mod.rs +++ b/core/engine/src/handler/expression/mod.rs @@ -1,12 +1,10 @@ use crate::handler::node::{NodeRequest, NodeResponse, NodeResult}; use crate::model::DecisionNodeKind; -use std::collections::HashMap; +use ahash::{HashMap, HashMapExt}; -use crate::util::json_map::FlatJsonMap; use anyhow::{anyhow, Context}; use serde::Serialize; -use serde_json::Value; -use zen_expression::variable::ToVariable; +use zen_expression::variable::Variable; use zen_expression::Isolate; pub struct ExpressionHandler<'a> { @@ -33,10 +31,10 @@ impl<'a> ExpressionHandler<'a> { _ => Err(anyhow!("Unexpected node type")), }?; - let mut result = FlatJsonMap::with_capacity(content.expressions.len()); + let result = Variable::empty_object(); let mut trace_map = self.trace.then(|| HashMap::<&str, ExpressionTrace>::new()); - self.isolate.set_environment(&request.input); + self.isolate.set_environment(request.input.depth_clone(1)); for expression in &content.expressions { let value = self.evaluate_expression(&expression.value)?; if let Some(tmap) = &mut trace_map { @@ -48,21 +46,20 @@ impl<'a> ExpressionHandler<'a> { ); } - self.isolate.update_environment(|arena, env| { + self.isolate.update_environment(|env| { let Some(environment) = env else { return; }; let key = format!("$.{}", &expression.key); - let _ = - environment.dot_insert(arena, key.as_str(), value.to_variable(arena).unwrap()); + let _ = environment.dot_insert(key.as_str(), value.clone()); }); - result.insert(&expression.key, value); + + result.dot_insert(&expression.key, value); } - let output = result.to_json().context("Conversion to JSON failed")?; Ok(NodeResponse { - output, + output: result, trace_data: trace_map .map(|tm| serde_json::to_value(tm)) .transpose() @@ -70,7 +67,7 @@ impl<'a> ExpressionHandler<'a> { }) } - fn evaluate_expression(&mut self, expression: &'a str) -> anyhow::Result { + fn evaluate_expression(&mut self, expression: &'a str) -> anyhow::Result { self.isolate .run_standard(expression) .with_context(|| format!(r#"Failed to evaluate expression: "{expression}""#)) diff --git a/core/engine/src/handler/function/function.rs b/core/engine/src/handler/function/function.rs index 422fc5e1..beed4068 100644 --- a/core/engine/src/handler/function/function.rs +++ b/core/engine/src/handler/function/function.rs @@ -1,15 +1,14 @@ use std::sync::Arc; -use rquickjs::promise::MaybePromise; -use rquickjs::{async_with, AsyncContext, AsyncRuntime, CatchResultExt, Ctx, Module}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - use crate::handler::function::error::{FunctionError, FunctionResult, ResultExt}; use crate::handler::function::listener::{RuntimeEvent, RuntimeListener}; use crate::handler::function::module::console::{Console, Log}; use crate::handler::function::module::ModuleLoader; use crate::handler::function::serde::JsValue; +use rquickjs::promise::MaybePromise; +use rquickjs::{async_with, AsyncContext, AsyncRuntime, CatchResultExt, Ctx, Module}; +use serde::{Deserialize, Serialize}; +use zen_expression::variable::Variable; pub struct FunctionConfig { pub(crate) listeners: Option>>, @@ -49,7 +48,7 @@ impl Function { }; } - return Ok(()); + Ok(()) } async fn dispatch_event(&self, event: RuntimeEvent) -> FunctionResult { @@ -121,5 +120,5 @@ impl Function { #[derive(Serialize, Deserialize)] pub struct HandlerResponse { pub logs: Vec, - pub data: Value, + pub data: Variable, } diff --git a/core/engine/src/handler/function/module/http.rs b/core/engine/src/handler/function/module/http.rs index 975fea84..c4b683fd 100644 --- a/core/engine/src/handler/function/module/http.rs +++ b/core/engine/src/handler/function/module/http.rs @@ -1,3 +1,6 @@ +use crate::handler::function::error::ResultExt; +use crate::handler::function::module::export_default; +use crate::handler::function::serde::JsValue; use reqwest::header::{HeaderMap, HeaderName}; use reqwest::Method; use rquickjs::module::{Declarations, Exports, ModuleDef}; @@ -5,10 +8,7 @@ use rquickjs::prelude::{Async, Func, Opt}; use rquickjs::{CatchResultExt, Ctx, FromJs, IntoAtom, IntoJs, Object, Value}; use std::str::FromStr; use std::sync::OnceLock; - -use crate::handler::function::error::ResultExt; -use crate::handler::function::module::export_default; -use crate::handler::function::serde::JsValue; +use zen_expression::variable::Variable; pub(crate) struct HttpResponse<'js> { data: Value<'js>, @@ -62,7 +62,7 @@ async fn execute_http<'js>( )?; } - let data: serde_json::Value = response.json().await.or_throw(&ctx)?; + let data: Variable = response.json().await.or_throw(&ctx)?; Ok(HttpResponse { data: JsValue(data).into_js(&ctx)?, @@ -91,12 +91,12 @@ impl<'js> FromJs<'js> for HttpConfig { let value = JsValue::from_js(ctx, value)?; let str_value = match value.0 { - serde_json::Value::Null => None, - serde_json::Value::Bool(b) => Some(b.to_string()), - serde_json::Value::Number(n) => Some(n.to_string()), - serde_json::Value::String(s) => Some(s), - serde_json::Value::Array(_) => None, - serde_json::Value::Object(_) => None, + Variable::Null => None, + Variable::Bool(b) => Some(b.to_string()), + Variable::Number(n) => Some(n.to_string()), + Variable::String(s) => Some(s.to_string()), + Variable::Array(_) => None, + Variable::Object(_) => None, }; let key_value = key.to_string()?; @@ -121,12 +121,12 @@ impl<'js> FromJs<'js> for HttpConfig { let value = JsValue::from_js(ctx, value)?; let str_value = match value.0 { - serde_json::Value::Null => None, - serde_json::Value::Bool(b) => Some(b.to_string()), - serde_json::Value::Number(n) => Some(n.to_string()), - serde_json::Value::String(s) => Some(s), - serde_json::Value::Array(_) => None, - serde_json::Value::Object(_) => None, + Variable::Null => None, + Variable::Bool(b) => Some(b.to_string()), + Variable::Number(n) => Some(n.to_string()), + Variable::String(s) => Some(s.to_string()), + Variable::Array(_) => None, + Variable::Object(_) => None, }; let key = key.to_string()?; diff --git a/core/engine/src/handler/function/module/zen.rs b/core/engine/src/handler/function/module/zen.rs index 41e50aa6..2b32f69f 100644 --- a/core/engine/src/handler/function/module/zen.rs +++ b/core/engine/src/handler/function/module/zen.rs @@ -2,10 +2,6 @@ use std::future::Future; use std::pin::Pin; use std::sync::Arc; -use rquickjs::module::{Declarations, Exports, ModuleDef}; -use rquickjs::prelude::{Async, Func, Opt}; -use rquickjs::{CatchResultExt, Ctx, Function, Object}; - use crate::handler::custom_node_adapter::CustomNodeAdapter; use crate::handler::function::error::{FunctionResult, ResultExt}; use crate::handler::function::listener::{RuntimeEvent, RuntimeListener}; @@ -13,6 +9,9 @@ use crate::handler::function::module::export_default; use crate::handler::function::serde::JsValue; use crate::handler::graph::{DecisionGraph, DecisionGraphConfig}; use crate::loader::DecisionLoader; +use rquickjs::module::{Declarations, Exports, ModuleDef}; +use rquickjs::prelude::{Async, Func, Opt}; +use rquickjs::{CatchResultExt, Ctx, Function, Object}; pub(crate) struct ZenListener { pub loader: Arc, @@ -68,11 +67,10 @@ impl Run }) .or_throw(&ctx)?; - let response = - sub_tree.evaluate(&context.0).await.or_throw(&ctx)?; - return rquickjs::Result::Ok(JsValue( - serde_json::to_value(response).or_throw(&ctx)?, - )); + let response = sub_tree.evaluate(context.0).await.or_throw(&ctx)?; + let k = serde_json::to_value(response).or_throw(&ctx)?.into(); + + return rquickjs::Result::Ok(JsValue(k)); } }, )), @@ -89,7 +87,7 @@ fn evaluate_expression<'js>( expression: String, context: JsValue, ) -> rquickjs::Result { - let s = zen_expression::evaluate_expression(expression.as_str(), &context.0).or_throw(&ctx)?; + let s = zen_expression::evaluate_expression(expression.as_str(), context.0).or_throw(&ctx)?; Ok(JsValue(s)) } @@ -99,8 +97,8 @@ fn evaluate_unary_expression<'js>( expression: String, context: JsValue, ) -> rquickjs::Result { - let s = zen_expression::evaluate_unary_expression(expression.as_str(), &context.0) - .or_throw(&ctx)?; + let s = + zen_expression::evaluate_unary_expression(expression.as_str(), context.0).or_throw(&ctx)?; Ok(s) } diff --git a/core/engine/src/handler/function/serde.rs b/core/engine/src/handler/function/serde.rs index 8a12c01e..c70e4a21 100644 --- a/core/engine/src/handler/function/serde.rs +++ b/core/engine/src/handler/function/serde.rs @@ -1,84 +1,87 @@ use crate::handler::function::error::ResultExt; -use itertools::Itertools; +use ahash::{HashMap, HashMapExt}; use rquickjs::{Ctx, FromJs, IntoAtom, IntoJs, Type, Value as QValue}; -use serde_json::{json, Map, Number, Value}; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; +use serde_json::json; +use std::rc::Rc; +use zen_expression::variable::Variable; #[derive(Debug)] -pub(crate) struct JsValue(pub(crate) Value); +pub(crate) struct JsValue(pub(crate) Variable); impl<'js> FromJs<'js> for JsValue { fn from_js(ctx: &Ctx<'js>, v: QValue<'js>) -> rquickjs::Result { let computed_value = match v.type_of() { - Type::Uninitialized | Type::Undefined | Type::Null => Value::Null, - Type::Bool => Value::Bool(v.as_bool().or_throw_msg(ctx, "failed to convert to bool")?), - Type::Int => Value::Number(Number::from( + Type::Uninitialized | Type::Undefined | Type::Null => Variable::Null, + Type::Bool => { + Variable::Bool(v.as_bool().or_throw_msg(ctx, "failed to convert to bool")?) + } + Type::Int => Variable::Number(Decimal::from( v.as_int().or_throw_msg(ctx, "failed to convert to int")?, )), - Type::BigInt => Value::Number(Number::from( - v.as_big_int() + Type::BigInt => Variable::Number(Decimal::from( + v.into_big_int() .map(|b| b.clone().to_i64().ok()) .flatten() .or_throw_msg(ctx, "failed to convert to number")?, )), - Type::Float => Value::Number( - v.as_float() - .map(|n| Number::from_f64(n)) - .flatten() - .or_throw_msg(ctx, "failed to convert to number")?, + Type::Float => Variable::Number( + Decimal::try_from( + v.as_float() + .or_throw_msg(ctx, "failed to convert to number")?, + ) + .or_throw_msg(ctx, "failed to convert to number")?, ), - Type::String => Value::String( - v.as_string() + Type::String => Variable::String( + v.into_string() .map(|s| s.to_string().ok()) .flatten() + .map(|s| Rc::from(s.as_str())) .or_throw_msg(ctx, "failed to convert to string")?, ), Type::Array => { let arr = v - .as_array() + .into_array() .or_throw_msg(ctx, "failed to convert to array")?; - let js_arr: Vec = arr - .iter::() - .map_ok(|n| JsValue::from_js(ctx, n.clone()).map(|js_val| js_val.0)) - .flatten() - .try_collect() - .or_throw(ctx)?; + let mut js_arr = Vec::with_capacity(arr.len()); + for x in arr.into_iter() { + js_arr.push(JsValue::from_js(ctx, x.or_throw(ctx)?).or_throw(ctx)?.0) + } - Value::Array(js_arr) + Variable::from_array(js_arr) } Type::Object => { let object = v - .as_object() + .into_object() .or_throw_msg(ctx, "failed to convert to object")?; - let js_object: Map = object - .props::() - .map_ok(|(key, value)| { - JsValue::from_js(ctx, value.clone()).map(|js_val| (key, js_val.0)) - }) - .flatten() - .try_collect() - .or_throw(ctx)?; + let mut js_object = HashMap::with_capacity(object.len()); + for p in object.props() { + let (k, v) = p.or_throw(ctx)?; + js_object.insert(k, JsValue::from_js(ctx, v).or_throw(ctx)?.0); + } - Value::Object(js_object) + Variable::from_object(js_object) } Type::Exception => { let exception = v - .as_exception() + .into_exception() .or_throw_msg(ctx, "failed to convert to exception")?; let message = exception.message().unwrap_or_default(); let description = exception.to_string(); - json!({ "message": message, "description": description }) + json!({ "message": message, "description": description }).into() } - Type::Function => json!("[Function]"), - Type::Module => json!("[Module]"), - Type::Constructor => json!("[Constructor]"), - Type::Symbol => json!("[Symbol]"), - Type::Unknown => json!("[Unknown]"), + Type::Function => json!("[Function]").into(), + Type::Module => json!("[Module]").into(), + Type::Constructor => json!("[Constructor]").into(), + Type::Symbol => json!("[Symbol]").into(), + Type::Unknown => json!("[Unknown]").into(), Type::Promise => { - let promise = v.as_promise().or_throw(ctx)?; + let promise = v.into_promise().or_throw(ctx)?; let val: JsValue = promise.finish()?; val.0 } @@ -91,26 +94,30 @@ impl<'js> FromJs<'js> for JsValue { impl<'js> IntoJs<'js> for JsValue { fn into_js(self, ctx: &Ctx<'js>) -> rquickjs::Result> { let res = match self.0 { - Value::Null => QValue::new_null(ctx.clone()), - Value::Bool(b) => QValue::new_bool(ctx.clone(), b), - Value::Number(n) => QValue::new_number( + Variable::Null => QValue::new_null(ctx.clone()), + Variable::Bool(b) => QValue::new_bool(ctx.clone(), b), + Variable::Number(n) => QValue::new_number( ctx.clone(), - n.as_f64() + n.to_f64() .or_throw_msg(ctx, "failed to convert float to number")?, ), - Value::String(str) => str.into_js(ctx)?, - Value::Array(arr) => { + Variable::String(str) => str.into_js(ctx)?, + Variable::Array(a) => { let qarr = rquickjs::Array::new(ctx.clone())?; - for (idx, item) in arr.into_iter().enumerate() { - qarr.set(idx, JsValue(item))?; + + let arr = a.borrow(); + for (idx, item) in arr.iter().enumerate() { + qarr.set(idx, JsValue(item.clone()))?; } qarr.into_value() } - Value::Object(map) => { + Variable::Object(o) => { let qmap = rquickjs::Object::new(ctx.clone())?; - for (key, value) in map.into_iter() { - qmap.set(key.into_atom(ctx)?, JsValue(value))?; + + let obj = o.borrow(); + for (key, value) in obj.iter() { + qmap.set(key.into_atom(ctx)?, JsValue(value.clone()))?; } qmap.into_value() diff --git a/core/engine/src/handler/function_v1/script.rs b/core/engine/src/handler/function_v1/script.rs index 62c16cec..817748fd 100644 --- a/core/engine/src/handler/function_v1/script.rs +++ b/core/engine/src/handler/function_v1/script.rs @@ -1,15 +1,16 @@ -use std::fmt::Debug; - use crate::handler::function::serde::JsValue; use anyhow::Context as _; use rquickjs::{Context, Ctx, Error as QError, FromJs, Module, Runtime}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::fmt::Debug; +use std::rc::Rc; +use zen_expression::variable::Variable; #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EvaluateResponse { - pub output: Value, + pub output: Variable, pub log: Vec, } @@ -58,7 +59,7 @@ impl Script { fn map_js_error(ctx: &Ctx, e: QError) -> anyhow::Error { let error = JsValue::from_js(&ctx, ctx.catch()) .map(|v| v.0) - .unwrap_or(Value::String(e.to_string())); + .unwrap_or(Variable::String(Rc::from(e.to_string().as_str()))); anyhow::Error::msg(error.to_string()) } diff --git a/core/engine/src/handler/graph.rs b/core/engine/src/handler/graph.rs index 05a9e029..de1912a6 100644 --- a/core/engine/src/handler/graph.rs +++ b/core/engine/src/handler/graph.rs @@ -1,15 +1,3 @@ -use std::collections::HashMap; -use std::rc::Rc; -use std::sync::Arc; -use std::time::Instant; - -use anyhow::anyhow; -use petgraph::algo::is_cyclic_directed; -use serde::ser::SerializeMap; -use serde::{Deserialize, Serialize, Serializer}; -use serde_json::Value; -use thiserror::Error; - use crate::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest}; use crate::handler::decision::DecisionHandler; use crate::handler::expression::ExpressionHandler; @@ -25,6 +13,17 @@ use crate::handler::traversal::{GraphWalker, StableDiDecisionGraph}; use crate::loader::DecisionLoader; use crate::model::{DecisionContent, DecisionNodeKind, FunctionNodeContent}; use crate::{EvaluationError, NodeError}; +use ahash::{HashMap, HashMapExt}; +use anyhow::anyhow; +use petgraph::algo::is_cyclic_directed; +use serde::ser::SerializeMap; +use serde::{Deserialize, Serialize, Serializer}; +use serde_json::Value; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Instant; +use thiserror::Error; +use zen_expression::variable::Variable; pub struct DecisionGraph<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> { graph: StableDiDecisionGraph<'a>, @@ -139,7 +138,10 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr .count() } - pub async fn evaluate(&mut self, context: &Value) -> Result { + pub async fn evaluate( + &mut self, + context: Variable, + ) -> Result { let root_start = Instant::now(); self.validate().map_err(|e| NodeError { @@ -184,8 +186,8 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr DecisionNodeKind::InputNode => { walker.set_node_data(nid, context.clone()); trace!({ - input: Value::Null, - output: Value::Null, + input: Variable::Null, + output: Variable::Null, name: node.name.clone(), id: node.id.clone(), performance: None, @@ -194,8 +196,8 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr } DecisionNodeKind::OutputNode => { trace!({ - input: Value::Null, - output: Value::Null, + input: Variable::Null, + output: Variable::Null, name: node.name.clone(), id: node.id.clone(), performance: None, @@ -219,12 +221,12 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr node_id: node.id.clone(), })?; - let mut node_request = NodeRequest { + let node_request = NodeRequest { node, iteration: self.iteration, input: walker.incoming_node_data(&self.graph, nid, true), }; - let mut res = match content { + let res = match content { FunctionNodeContent::Version2(_) => FunctionHandler::new( function, self.trace, @@ -253,8 +255,8 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr } }; - trim_nodes(&mut node_request.input); - trim_nodes(&mut res.output); + node_request.input.dot_remove("$nodes"); + res.output.dot_remove("$nodes"); trace!({ input: node_request.input, @@ -267,13 +269,13 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr walker.set_node_data(nid, res.output); } DecisionNodeKind::DecisionNode { .. } => { - let mut node_request = NodeRequest { + let node_request = NodeRequest { node, iteration: self.iteration, input: walker.incoming_node_data(&self.graph, nid, true), }; - let mut res = DecisionHandler::new( + let res = DecisionHandler::new( self.trace, self.max_depth, self.loader.clone(), @@ -287,8 +289,8 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr node_id: node.id.to_string(), })?; - trim_nodes(&mut node_request.input); - trim_nodes(&mut res.output); + node_request.input.dot_remove("$nodes"); + res.output.dot_remove("$nodes"); trace!({ input: node_request.input, @@ -301,13 +303,13 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr walker.set_node_data(nid, res.output); } DecisionNodeKind::DecisionTableNode { .. } => { - let mut node_request = NodeRequest { + let node_request = NodeRequest { node, iteration: self.iteration, input: walker.incoming_node_data(&self.graph, nid, true), }; - let mut res = DecisionTableHandler::new(self.trace) + let res = DecisionTableHandler::new(self.trace) .handle(&node_request) .await .map_err(|e| NodeError { @@ -315,8 +317,8 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr source: e.into(), })?; - trim_nodes(&mut node_request.input); - trim_nodes(&mut res.output); + node_request.input.dot_remove("$nodes"); + res.output.dot_remove("$nodes"); trace!({ input: node_request.input, @@ -329,13 +331,13 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr walker.set_node_data(nid, res.output); } DecisionNodeKind::ExpressionNode { .. } => { - let mut node_request = NodeRequest { + let node_request = NodeRequest { node, iteration: self.iteration, input: walker.incoming_node_data(&self.graph, nid, true), }; - let mut res = ExpressionHandler::new(self.trace) + let res = ExpressionHandler::new(self.trace) .handle(&node_request) .await .map_err(|e| NodeError { @@ -343,8 +345,8 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr source: e.into(), })?; - trim_nodes(&mut node_request.input); - trim_nodes(&mut res.output); + node_request.input.dot_remove("$nodes"); + res.output.dot_remove("$nodes"); trace!({ input: node_request.input, @@ -357,13 +359,13 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr walker.set_node_data(nid, res.output); } DecisionNodeKind::CustomNode { .. } => { - let mut node_request = NodeRequest { + let node_request = NodeRequest { node, iteration: self.iteration, input: walker.incoming_node_data(&self.graph, nid, true), }; - let mut res = self + let res = self .adapter .handle(CustomNodeRequest::try_from(&node_request).unwrap()) .await @@ -372,8 +374,8 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr source: e.into(), })?; - trim_nodes(&mut node_request.input); - trim_nodes(&mut res.output); + node_request.input.dot_remove("$nodes"); + res.output.dot_remove("$nodes"); trace!({ input: node_request.input, @@ -395,12 +397,6 @@ impl<'a, L: DecisionLoader + 'static, A: CustomNodeAdapter + 'static> DecisionGr } } -fn trim_nodes(val: &mut Value) { - if let Some(obj) = val.as_object_mut() { - obj.remove("$nodes"); - } -} - #[derive(Debug, Error)] pub enum DecisionGraphValidationError { #[error("Invalid input node count: {0}")] @@ -449,7 +445,7 @@ impl Serialize for DecisionGraphValidationError { #[serde(rename_all = "camelCase")] pub struct DecisionGraphResponse { pub performance: String, - pub result: Value, + pub result: Variable, #[serde(skip_serializing_if = "Option::is_none")] pub trace: Option>, } @@ -457,8 +453,8 @@ pub struct DecisionGraphResponse { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DecisionGraphTrace { - pub input: Value, - pub output: Value, + pub input: Variable, + pub output: Variable, pub name: String, pub id: String, pub performance: Option, diff --git a/core/engine/src/handler/node.rs b/core/engine/src/handler/node.rs index 3ef864d5..2d0513f3 100644 --- a/core/engine/src/handler/node.rs +++ b/core/engine/src/handler/node.rs @@ -3,17 +3,18 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::fmt::{Display, Formatter}; use thiserror::Error; +use zen_expression::variable::Variable; #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct NodeResponse { - pub output: Value, + pub output: Variable, pub trace_data: Option, } #[derive(Debug, Serialize)] pub struct NodeRequest<'a> { - pub input: Value, + pub input: Variable, pub iteration: u8, pub node: &'a DecisionNode, } diff --git a/core/engine/src/handler/table/mod.rs b/core/engine/src/handler/table/mod.rs index 954499cb..58f00118 100644 --- a/core/engine/src/handler/table/mod.rs +++ b/core/engine/src/handler/table/mod.rs @@ -1,11 +1,10 @@ pub mod zen; -use crate::util::json_map::{FlatJsonMap, JsonMapError}; -use serde_json::Value; +use zen_expression::variable::Variable; #[derive(Debug, Clone)] pub(crate) enum RowOutputKind { - Value(Value), + Variable(Variable), } #[derive(Debug, Default)] @@ -20,15 +19,17 @@ impl RowOutput { self.output.push((key.into(), value)) } - pub async fn to_json(&self) -> Result { - let map: Vec<(String, Value)> = self - .output - .iter() - .map(|(key, kind)| match kind { - RowOutputKind::Value(value) => (key.clone(), value.clone()), - }) - .collect(); + pub async fn to_json(&self) -> Variable { + let object = Variable::empty_object(); - FlatJsonMap::from(map).to_json() + for (key, kind) in &self.output { + match kind { + RowOutputKind::Variable(variable) => { + object.dot_insert(key.as_str(), variable.clone()); + } + } + } + + object } } diff --git a/core/engine/src/handler/table/zen.rs b/core/engine/src/handler/table/zen.rs index fc640425..da8ddefb 100644 --- a/core/engine/src/handler/table/zen.rs +++ b/core/engine/src/handler/table/zen.rs @@ -1,18 +1,17 @@ +use ahash::HashMap; use anyhow::{anyhow, Context}; -use std::collections::HashMap; - -use serde::Serialize; -use serde_json::Value; -use zen_expression::Isolate; use crate::handler::node::{NodeRequest, NodeResponse, NodeResult}; use crate::handler::table::{RowOutput, RowOutputKind}; use crate::model::{DecisionNodeKind, DecisionTableContent, DecisionTableHitPolicy}; +use serde::Serialize; +use zen_expression::variable::Variable; +use zen_expression::Isolate; #[derive(Debug, Serialize)] struct RowResult { rule: Option>, - reference_map: Option>, + reference_map: Option>, index: usize, #[serde(skip)] output: RowOutput, @@ -38,7 +37,7 @@ impl<'a> DecisionTableHandler<'a> { _ => Err(anyhow!("Unexpected node type")), }?; - self.isolate.set_environment(&request.input); + self.isolate.set_environment(request.input.depth_clone(1)); match &content.hit_policy { DecisionTableHitPolicy::First => self.handle_first_hit(&content).await, @@ -50,7 +49,7 @@ impl<'a> DecisionTableHandler<'a> { for i in 0..content.rules.len() { if let Some(result) = self.evaluate_row(&content, i) { return Ok(NodeResponse { - output: result.output.to_json().await?, + output: result.output.to_json().await, trace_data: self .trace .then(|| { @@ -62,7 +61,7 @@ impl<'a> DecisionTableHandler<'a> { } Ok(NodeResponse { - output: Value::Null, + output: Variable::Null, trace_data: None, }) } @@ -77,11 +76,11 @@ impl<'a> DecisionTableHandler<'a> { let mut outputs = Vec::with_capacity(results.len()); for res in &results { - outputs.push(res.output.to_json().await?); + outputs.push(res.output.to_json().await); } Ok(NodeResponse { - output: serde_json::to_value(&outputs).context("Failed to parse table row output")?, + output: Variable::from_array(outputs), trace_data: self .trace .then(|| serde_json::to_value(&results).context("Failed to parse trace data")) @@ -101,20 +100,19 @@ impl<'a> DecisionTableHandler<'a> { continue; } - let Some(input_field) = &input.field else { - let result = self.isolate.run_standard(rule_value.as_str()).ok()?; - let is_ok = result.as_bool().unwrap_or(false); - if !is_ok { - return None; + match &input.field { + None => { + let result = self.isolate.run_standard(rule_value.as_str()).ok()?; + if !result.as_bool().unwrap_or(false) { + return None; + } + } + Some(field) => { + self.isolate.set_reference(field.as_str()).ok()?; + if !self.isolate.run_unary(rule_value.as_str()).ok()? { + return None; + } } - - continue; - }; - - self.isolate.set_reference(input_field.as_str()).ok()?; - let is_ok = self.isolate.run_unary(rule_value.as_str()).ok()?; - if !is_ok { - return None; } } @@ -126,7 +124,7 @@ impl<'a> DecisionTableHandler<'a> { } let res = self.isolate.run_standard(rule_value).ok()?; - outputs.push(&output.field, RowOutputKind::Value(res)); + outputs.push(&output.field, RowOutputKind::Variable(res)); } if !self.trace { @@ -144,7 +142,7 @@ impl<'a> DecisionTableHandler<'a> { }; let mut expressions: HashMap = Default::default(); - let mut reference_map: HashMap = Default::default(); + let mut reference_map: HashMap = Default::default(); expressions.insert("_id".to_string(), rule_id.clone()); if let Some(description) = rule.get("_description") { diff --git a/core/engine/src/handler/traversal.rs b/core/engine/src/handler/traversal.rs index a7db5aa0..04e8decb 100644 --- a/core/engine/src/handler/traversal.rs +++ b/core/engine/src/handler/traversal.rs @@ -1,11 +1,12 @@ +use ahash::HashMap; use fixedbitset::FixedBitSet; use petgraph::data::DataMap; use petgraph::matrix_graph::Zero; use petgraph::prelude::{EdgeIndex, NodeIndex, StableDiGraph}; use petgraph::visit::{EdgeRef, IntoNeighbors, IntoNodeIdentifiers, Reversed, VisitMap, Visitable}; use petgraph::{Incoming, Outgoing}; -use serde_json::{json, Map, Value}; -use std::collections::HashMap; +use serde_json::json; +use std::rc::Rc; use std::sync::atomic::Ordering; use std::time::Instant; @@ -14,6 +15,7 @@ use crate::model::{ DecisionEdge, DecisionNode, DecisionNodeKind, SwitchStatement, SwitchStatementHitPolicy, }; use crate::DecisionGraphTrace; +use zen_expression::variable::Variable; use zen_expression::Isolate; pub(crate) type StableDiDecisionGraph<'a> = StableDiGraph<&'a DecisionNode, &'a DecisionEdge>; @@ -21,7 +23,7 @@ pub(crate) type StableDiDecisionGraph<'a> = StableDiGraph<&'a DecisionNode, &'a pub(crate) struct GraphWalker { ordered: FixedBitSet, to_visit: Vec, - node_data: HashMap, + node_data: HashMap, iter: usize, visited_switch_nodes: Vec, @@ -66,12 +68,12 @@ impl GraphWalker { self.iter += 1; } - pub fn get_node_data(&self, node_id: NodeIndex) -> Option<&Value> { - self.node_data.get(&node_id) + pub fn get_node_data(&self, node_id: NodeIndex) -> Option { + self.node_data.get(&node_id).cloned() } - pub fn get_all_node_data(&self, g: &StableDiDecisionGraph) -> Value { - let node_values: Map = self + pub fn get_all_node_data(&self, g: &StableDiDecisionGraph) -> Variable { + let node_values = self .node_data .iter() .map(|(idx, value)| { @@ -80,10 +82,10 @@ impl GraphWalker { }) .collect(); - Value::Object(node_values) + Variable::from_object(node_values) } - pub fn set_node_data(&mut self, node_id: NodeIndex, value: Value) { + pub fn set_node_data(&mut self, node_id: NodeIndex, value: Variable) { self.node_data.insert(node_id, value); } @@ -92,11 +94,13 @@ impl GraphWalker { g: &StableDiDecisionGraph, node_id: NodeIndex, with_nodes: bool, - ) -> Value { - let mut value = self.merge_node_data(g.neighbors_directed(node_id, Incoming)); - + ) -> Variable { + let value = self + .merge_node_data(g.neighbors_directed(node_id, Incoming)) + .depth_clone(1); if self.nodes_in_context { - if let Some(object) = with_nodes.then_some(value.as_object_mut()).flatten() { + if let Some(object_ref) = with_nodes.then_some(value.as_object()).flatten() { + let mut object = object_ref.borrow_mut(); object.insert("$nodes".to_string(), self.get_all_node_data(g)); } } @@ -104,16 +108,14 @@ impl GraphWalker { value } - pub fn merge_node_data(&self, iter: I) -> Value + pub fn merge_node_data(&self, iter: I) -> Variable where I: Iterator, { - let default_map = Value::Object(Map::new()); - iter.fold(Value::Object(Map::new()), |mut prev, curr| { + let default_map = Variable::empty_object(); + iter.fold(Variable::empty_object(), |mut prev, curr| { let data = self.node_data.get(&curr).unwrap_or(&default_map); - - merge_json(&mut prev, data, true); - prev + prev.merge(data) }) } @@ -137,11 +139,12 @@ impl GraphWalker { if let DecisionNodeKind::SwitchNode { content } = &decision_node.kind { if !self.visited_switch_nodes.contains(&nid) { - let mut input_data = self.incoming_node_data(g, nid, true); - let input_context = json!({ "$": &input_data }); - merge_json(&mut input_data, &input_context, true); + let input_data = self.incoming_node_data(g, nid, true); - let mut isolate = Isolate::with_environment(&input_data); + let env = input_data.depth_clone(1); + env.dot_insert("$", input_data.depth_clone(1)); + + let mut isolate = Isolate::with_environment(env); let mut statement_iter = content.statements.iter(); let valid_statements: Vec<&SwitchStatement> = match content.hit_policy { @@ -154,18 +157,33 @@ impl GraphWalker { .collect(), }; - let valid_statements_trace: Value = valid_statements - .iter() - .map(|&statement| json!({ "id": &statement.id })) - .collect(); + let valid_statements_trace = Variable::from_array( + valid_statements + .iter() + .map(|&statement| { + let v = Variable::empty_object(); + v.dot_insert( + "id", + Variable::String(Rc::from(statement.id.as_str())), + ); + + v + }) + .collect(), + ); + + input_data.dot_remove("$nodes"); + if let Some(on_trace) = &mut on_trace { on_trace(DecisionGraphTrace { id: decision_node.id.clone(), name: decision_node.name.clone(), - input: input_data.clone(), - output: input_data.clone(), + input: input_data.shallow_clone(), + output: input_data.shallow_clone(), performance: Some(format!("{:?}", start.elapsed())), - trace_data: Some(json!({ "statements": valid_statements_trace })), + trace_data: Some( + json!({ "statements": valid_statements_trace }).into(), + ), }); } @@ -265,25 +283,3 @@ fn remove_edge_recursive(g: &mut StableDiDecisionGraph, edge_id: EdgeIndex) { } } } - -fn merge_json(doc: &mut Value, patch: &Value, top_level: bool) { - if !patch.is_object() && !patch.is_array() && top_level { - return; - } - - if doc.is_object() && patch.is_object() { - let map = doc.as_object_mut().unwrap(); - for (key, value) in patch.as_object().unwrap() { - if value.is_null() { - map.remove(key.as_str()); - } else { - merge_json(map.entry(key.as_str()).or_insert(Value::Null), value, false); - } - } - } else if doc.is_array() && patch.is_array() { - let arr = doc.as_array_mut().unwrap(); - arr.extend(patch.as_array().unwrap().clone()); - } else { - *doc = patch.clone(); - } -} diff --git a/core/engine/src/lib.rs b/core/engine/src/lib.rs index 97e89c65..38ddbf39 100644 --- a/core/engine/src/lib.rs +++ b/core/engine/src/lib.rs @@ -122,13 +122,11 @@ #![deny(clippy::unwrap_used)] #![allow(clippy::module_inception)] +mod config; mod decision; mod engine; mod error; pub mod handler; -mod util; - -mod config; pub mod loader; #[path = "model/mod.rs"] pub mod model; @@ -141,3 +139,4 @@ pub use handler::graph::DecisionGraphResponse; pub use handler::graph::DecisionGraphTrace; pub use handler::graph::DecisionGraphValidationError; pub use handler::node::NodeError; +pub use zen_expression::Variable; diff --git a/core/engine/src/loader/cached.rs b/core/engine/src/loader/cached.rs index 9ce1d3d1..27a6df73 100644 --- a/core/engine/src/loader/cached.rs +++ b/core/engine/src/loader/cached.rs @@ -1,7 +1,6 @@ -use std::collections::HashMap; +use ahash::{HashMap, HashMapExt}; use std::future::Future; use std::sync::Arc; - use tokio::sync::Mutex; use crate::loader::{DecisionLoader, LoaderResponse}; diff --git a/core/engine/src/loader/memory.rs b/core/engine/src/loader/memory.rs index 0e4ffbf4..acb9c347 100644 --- a/core/engine/src/loader/memory.rs +++ b/core/engine/src/loader/memory.rs @@ -1,9 +1,8 @@ -use std::collections::HashMap; -use std::future::Future; -use std::sync::{Arc, RwLock}; - use crate::loader::{DecisionLoader, LoaderError, LoaderResponse}; use crate::model::DecisionContent; +use ahash::HashMap; +use std::future::Future; +use std::sync::{Arc, RwLock}; /// Loads decisions from in-memory hashmap #[derive(Debug, Default)] diff --git a/core/engine/src/model/mod.rs b/core/engine/src/model/mod.rs index 7dcbe5dd..45edbcbb 100644 --- a/core/engine/src/model/mod.rs +++ b/core/engine/src/model/mod.rs @@ -1,5 +1,4 @@ -use std::collections::HashMap; - +use ahash::HashMap; use serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/core/engine/src/util/json_map.rs b/core/engine/src/util/json_map.rs deleted file mode 100644 index b9978cf2..00000000 --- a/core/engine/src/util/json_map.rs +++ /dev/null @@ -1,319 +0,0 @@ -#![allow(dead_code)] - -use serde::{Serialize, Serializer}; -use serde_json::{Map, Value}; -use std::cmp::Ordering; -use std::collections::BTreeMap; -use std::fmt::{Display, Formatter}; -use thiserror::Error; - -#[derive(Clone, Default, Debug)] -pub(crate) struct FlatJsonMap { - inner: Vec<(String, Value)>, -} - -impl From> for FlatJsonMap { - fn from(value: Vec<(String, Value)>) -> Self { - Self { inner: value } - } -} - -impl FlatJsonMap { - pub fn new() -> Self { - Default::default() - } - - pub fn with_capacity(capacity: usize) -> Self { - Self { - inner: Vec::with_capacity(capacity), - } - } - - pub fn capacity(&self) -> usize { - self.inner.capacity() - } - - pub fn insert>(&mut self, key: T, value: Value) { - self.inner.push((key.into(), value)) - } - - pub fn remove>(&mut self, key: T) { - self.inner.retain(|(k, _)| k == key.as_ref()) - } - - pub fn reserve(&mut self, additional: usize) { - self.inner.reserve(additional) - } - - pub fn to_json(&self) -> Result { - let map = self - .inner - .iter() - .enumerate() - .map(|(index, (key, value))| flatten_value(key, value.clone(), index as u32)) - .collect::>>(); - - let mut result = BTreeMap::::new(); - for inner_map in map { - for (key, value) in inner_map { - match value { - // Unexpected, as we've filtered out all objects in prior step - Value::Object(_) => return Err(JsonMapError::FailedToParse), - Value::Array(arr) => { - let maybe_exist = result.get_mut(&key).map(|a| a.as_array_mut()).flatten(); - if let Some(exist) = maybe_exist { - exist.extend_from_slice(&arr); - } else { - result.insert(key, Value::Array(arr)); - } - } - _ => { - result.insert(key, value); - } - } - } - } - - let mut root = Map::new(); - for (key, mut value) in result { - let mut node = &mut root; - let mut segments = key.str.split('.'); - let last_segment = segments.next_back().ok_or(JsonMapError::SerdeError)?; - - for segment in segments { - node = node - .entry(segment) - .and_modify(|val| { - if !matches!(val, Value::Object(_)) { - let _ = std::mem::replace(val, Value::Object(Map::new())); - } - }) - .or_insert_with(|| Value::Object(Map::new())) - .as_object_mut() - .ok_or(JsonMapError::SerdeError)?; - } - - let entry = node.get_mut(last_segment); - if let Some(mut entry_val) = entry { - match (&mut entry_val, &mut value) { - (Value::Array(arr1), Value::Array(arr2)) => arr1.extend_from_slice(&arr2), - _ => { - let _ = std::mem::replace(entry_val, value); - } - } - } else { - node.insert(last_segment.to_string(), value); - } - } - - Ok(Value::Object(root)) - } -} - -#[derive(Debug, Error)] -pub(crate) enum JsonMapError { - #[error("Failed to parse")] - FailedToParse, - - #[error("Unexpected serde error has occurred while deserializing rows")] - SerdeError, -} - -#[derive(Debug, Eq, PartialEq)] -struct JsonMapKey { - str: String, - sep_occurrences: u32, - bucket: u32, -} - -impl JsonMapKey { - fn new>(value: T, bucket: u32) -> Self { - let str = value.into(); - let sep_occurrences = str.chars().fold(0, |c, k| if k == '.' { c + 1 } else { c }); - - Self { - str, - sep_occurrences, - bucket, - } - } -} - -impl PartialOrd for JsonMapKey { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for JsonMapKey { - fn cmp(&self, other: &Self) -> Ordering { - if self.bucket != other.bucket { - return self.bucket.cmp(&other.bucket); - } - - if self.sep_occurrences == other.sep_occurrences { - self.str.cmp(&other.str) - } else { - self.sep_occurrences.cmp(&other.sep_occurrences) - } - } -} - -impl Serialize for JsonMapKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.str) - } -} - -impl Display for JsonMapKey { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.str) - } -} - -fn flatten_value(prefix_key: &str, value: Value, bucket: u32) -> BTreeMap { - let mut map = BTreeMap::::new(); - match value { - Value::Object(obj) => { - for (key, value) in obj { - if let Value::Object(inner_obj) = value { - let inner_map = flatten_value(&key, Value::Object(inner_obj), bucket); - for (inner_key, inner_value) in inner_map { - map.insert( - JsonMapKey::new(format!("{prefix_key}.{inner_key}"), bucket), - inner_value, - ); - } - } else { - map.insert( - JsonMapKey::new(format!("{prefix_key}.{key}"), bucket), - value, - ); - } - } - } - _ => { - map.insert(JsonMapKey::new(prefix_key, bucket), value); - } - } - - map -} - -#[cfg(test)] -mod tests { - use crate::util::json_map::{FlatJsonMap, JsonMapKey}; - use serde_json::json; - use std::cmp::Ordering; - - macro_rules! key { - ($str: expr) => { - JsonMapKey::new($str, 0) - }; - ($str: expr, $order: expr) => { - JsonMapKey::new($str, $order) - }; - } - - #[test] - fn test_order() { - assert_eq!(key!("a").cmp(&key!("b")), Ordering::Less); - assert_eq!(key!("a.b").cmp(&key!("b")), Ordering::Greater); - assert_eq!(key!("a").cmp(&key!("b.a")), Ordering::Less); - assert_eq!(key!("a.b.c").cmp(&key!("a.b.c")), Ordering::Equal); - assert_eq!(key!("a.b.c").cmp(&key!("a.b.c", 1)), Ordering::Less); - } - - #[test] - fn flatmap_insert_order() { - let mut o = FlatJsonMap::default(); - o.insert("a", json!("abc")); - o.insert("a.b", json!("abc")); - o.insert("a.b.c", json!("abc")); - - assert_eq!( - o.to_json().unwrap(), - json!({ "a": { "b": { "c": "abc" } } }) - ); - } - - #[test] - fn flatmap_secondary_order() { - let mut o = FlatJsonMap::default(); - o.insert("a.first.deleted", json!("deleted")); - o.insert("a.third.firstNested", json!("firstNested")); - o.insert("a.first", json!("first")); - o.insert("a.second", json!("second")); - o.insert("a.third.nested", json!("nested")); - - assert_eq!( - o.to_json().unwrap(), - json!({ - "a": { - "first": "first", - "second": "second", - "third": { - "firstNested": "firstNested", - "nested": "nested" - } - } - }) - ); - } - - #[test] - fn flatmap_nested() { - let mut o = FlatJsonMap::default(); - o.insert("array", json!([1, 2])); - o.insert("array", json!([3, 4])); - o.insert( - "object", - json!({ - "a": "a", - "b": "b", - "nested": { - "array": [10, 11] - } - }), - ); - o.insert( - "object", - json!({ - "b": "c", - "nested": { - "array": [12, 13] - } - }), - ); - - assert_eq!( - o.to_json().unwrap(), - json!({ - "array": [1, 2, 3, 4], - "object": { - "a": "a", - "b": "c", - "nested": { - "array": [10, 11, 12, 13] - } - } - }) - ); - } - - #[test] - fn flatmap_with_capacity() { - let mut a = FlatJsonMap::new(); - assert_eq!(a.capacity(), 0); - a.reserve(10); - assert_eq!(a.capacity(), 10); - - let mut b = FlatJsonMap::with_capacity(10); - assert_eq!(b.capacity(), 10); - b.reserve(20); - assert_eq!(b.capacity(), 20); - } -} diff --git a/core/engine/src/util/mod.rs b/core/engine/src/util/mod.rs deleted file mode 100644 index f30e1f32..00000000 --- a/core/engine/src/util/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod json_map; diff --git a/core/engine/tests/decision.rs b/core/engine/tests/decision.rs index 2684d0a9..66ca463d 100644 --- a/core/engine/tests/decision.rs +++ b/core/engine/tests/decision.rs @@ -14,9 +14,9 @@ async fn decision_from_content() { let decision = Decision::from(table_content); let context = json!({ "input": 5 }); - let result = decision.evaluate(&context).await; + let result = decision.evaluate(context.into()).await; - assert_eq!(result.unwrap().result, json!({"output": 0})); + assert_eq!(result.unwrap().result, json!({"output": 0}).into()); } #[tokio::test] @@ -26,7 +26,7 @@ async fn decision_from_content_recursive() { let decision = Decision::from(recursive_content); let context = json!({}); - let result = decision.evaluate(&context).await; + let result = decision.evaluate(context.clone().into()).await; match result.unwrap_err().deref() { EvaluationError::NodeError(e) => { assert_eq!(e.node_id, "0b8dcf6b-fc04-47cb-bf82-bda764e6c09b"); @@ -36,7 +36,7 @@ async fn decision_from_content_recursive() { } let with_loader = decision.with_loader(Arc::new(create_fs_loader())); - let new_result = with_loader.evaluate(&context).await; + let new_result = with_loader.evaluate(context.clone().into()).await; match new_result.unwrap_err().deref() { EvaluationError::NodeError(e) => { assert_eq!(e.source.to_string(), "Depth limit exceeded") @@ -55,7 +55,7 @@ fn decision_expression_node() { "lastName": "Doe" }); - let result = rt.block_on(decision.evaluate(&context)); + let result = rt.block_on(decision.evaluate(context.into())); assert_eq!( result.unwrap().result, json!({ @@ -68,6 +68,7 @@ fn decision_expression_node() { } } }) + .into() ) } diff --git a/core/engine/tests/engine.rs b/core/engine/tests/engine.rs index dc9e9c74..f42a3671 100644 --- a/core/engine/tests/engine.rs +++ b/core/engine/tests/engine.rs @@ -1,5 +1,6 @@ +use crate::support::{create_fs_loader, load_raw_test_data, load_test_data, test_data_root}; use serde::Deserialize; -use serde_json::{json, Value}; +use serde_json::json; use std::fs; use std::io::Read; use std::ops::Deref; @@ -8,10 +9,9 @@ use std::sync::Arc; use tokio::runtime::Builder; use zen_engine::loader::{LoaderError, MemoryLoader}; use zen_engine::model::{DecisionContent, DecisionNodeKind, FunctionNodeContent}; +use zen_engine::Variable; use zen_engine::{DecisionEngine, EvaluationError, EvaluationOptions}; -use crate::support::{create_fs_loader, load_raw_test_data, load_test_data, test_data_root}; - mod support; #[tokio::test] @@ -22,14 +22,18 @@ async fn engine_memory_loader() { memory_loader.add("function", load_test_data("function.json")); let engine = DecisionEngine::default().with_loader(memory_loader.clone()); - let table = engine.evaluate("table", &json!({ "input": 12 })).await; - let function = engine.evaluate("function", &json!({ "input": 12 })).await; + let table = engine + .evaluate("table", json!({ "input": 12 }).into()) + .await; + let function = engine + .evaluate("function", json!({ "input": 12 }).into()) + .await; memory_loader.remove("function"); - let not_found = engine.evaluate("function", &json!({})).await; + let not_found = engine.evaluate("function", json!({}).into()).await; - assert_eq!(table.unwrap().result, json!({"output": 10})); - assert_eq!(function.unwrap().result, json!({"output": 24})); + assert_eq!(table.unwrap().result, json!({"output": 10}).into()); + assert_eq!(function.unwrap().result, json!({"output": 24}).into()); assert_eq!(not_found.unwrap_err().to_string(), "Loader error"); } @@ -37,14 +41,16 @@ async fn engine_memory_loader() { #[cfg_attr(miri, ignore)] async fn engine_filesystem_loader() { let engine = DecisionEngine::default().with_loader(create_fs_loader().into()); - let table = engine.evaluate("table.json", &json!({ "input": 12 })).await; - let function = engine - .evaluate("function.json", &json!({ "input": 12 })) + let table = engine + .evaluate("table.json", json!({ "input": 12 }).into()) .await; - let not_found = engine.evaluate("invalid_file", &json!({})).await; + let function = engine + .evaluate("function.json", json!({ "input": 12 }).into()) + .await; + let not_found = engine.evaluate("invalid_file", json!({}).into()).await; - assert_eq!(table.unwrap().result, json!({"output": 10})); - assert_eq!(function.unwrap().result, json!({"output": 24})); + assert_eq!(table.unwrap().result, json!({"output": 10}).into()); + assert_eq!(function.unwrap().result, json!({"output": 24}).into()); assert_eq!(not_found.unwrap_err().to_string(), "Loader error"); } @@ -59,12 +65,16 @@ async fn engine_closure_loader() { } }); - let table = engine.evaluate("table", &json!({ "input": 12 })).await; - let function = engine.evaluate("function", &json!({ "input": 12 })).await; - let not_found = engine.evaluate("invalid_file", &json!({})).await; + let table = engine + .evaluate("table", json!({ "input": 12 }).into()) + .await; + let function = engine + .evaluate("function", json!({ "input": 12 }).into()) + .await; + let not_found = engine.evaluate("invalid_file", json!({}).into()).await; - assert_eq!(table.unwrap().result, json!({"output": 10})); - assert_eq!(function.unwrap().result, json!({"output": 24})); + assert_eq!(table.unwrap().result, json!({"output": 10}).into()); + assert_eq!(function.unwrap().result, json!({"output": 24}).into()); assert_eq!(not_found.unwrap_err().to_string(), "Loader error"); } @@ -73,7 +83,7 @@ fn engine_noop_loader() { let rt = Builder::new_current_thread().build().unwrap(); // Default engine is noop let engine = DecisionEngine::default(); - let result = rt.block_on(engine.evaluate("any.json", &json!({}))); + let result = rt.block_on(engine.evaluate("any.json", json!({}).into())); assert_eq!(result.unwrap_err().to_string(), "Loader error"); } @@ -98,7 +108,9 @@ fn engine_create_decision() { async fn engine_errors() { let engine = DecisionEngine::default().with_loader(create_fs_loader().into()); - let infinite_fn = engine.evaluate("infinite-function.json", &json!({})).await; + let infinite_fn = engine + .evaluate("infinite-function.json", json!({}).into()) + .await; match infinite_fn.unwrap_err().deref() { EvaluationError::NodeError(e) => { assert_eq!(e.node_id, "e0fd96d0-44dc-4f0e-b825-06e56b442d78"); @@ -107,7 +119,9 @@ async fn engine_errors() { _ => assert!(false, "Wrong error type"), } - let recursive = engine.evaluate("recursive-table1.json", &json!({})).await; + let recursive = engine + .evaluate("recursive-table1.json", json!({}).into()) + .await; match recursive.unwrap_err().deref() { EvaluationError::NodeError(e) => { assert_eq!(e.source.to_string(), "Depth limit exceeded") @@ -121,10 +135,10 @@ fn engine_with_trace() { let rt = Builder::new_current_thread().build().unwrap(); let engine = DecisionEngine::default().with_loader(create_fs_loader().into()); - let table_r = rt.block_on(engine.evaluate("table.json", &json!({ "input": 12 }))); + let table_r = rt.block_on(engine.evaluate("table.json", json!({ "input": 12 }).into())); let table_opt_r = rt.block_on(engine.evaluate_with_opts( "table.json", - &json!({ "input": 12 }), + json!({ "input": 12 }).into(), EvaluationOptions { trace: Some(true), max_depth: None, @@ -163,7 +177,7 @@ async fn engine_function_imports() { }); let decision = DecisionEngine::default().create_decision(function_content.into()); - let response = decision.evaluate(&json!({})).await.unwrap(); + let response = decision.evaluate(json!({}).into()).await.unwrap(); #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -174,7 +188,7 @@ async fn engine_function_imports() { moment_valid: bool, } - let result = serde_json::from_value::(response.result).unwrap(); + let result = serde_json::from_value::(response.result.to_value()).unwrap(); assert!(result.bigjs_tests.iter().all(|v| *v)); assert!(result.bigjs_valid); @@ -182,13 +196,13 @@ async fn engine_function_imports() { assert!(result.moment_valid); } -#[test] -fn engine_switch_node() { - let rt = Builder::new_current_thread().build().unwrap(); +#[tokio::test] +async fn engine_switch_node() { let engine = DecisionEngine::default().with_loader(create_fs_loader().into()); - let switch_node_r = - rt.block_on(engine.evaluate("switch-node.json", &json!({ "color": "yellow" }))); + let switch_node_r = engine + .evaluate("switch-node.json", json!({ "color": "yellow" }).into()) + .await; let table = switch_node_r.unwrap(); println!("{table:?}"); @@ -200,8 +214,8 @@ async fn engine_graph_tests() { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct TestCase { - input: Value, - output: Value, + input: Variable, + output: Variable, } #[derive(Deserialize)] @@ -227,8 +241,9 @@ async fn engine_graph_tests() { let decision = engine.create_decision(test_data.decision_content.into()); for test_case in test_data.tests { - let result = decision.evaluate(&test_case.input).await.unwrap().result; - let input = test_case.input; + let input = test_case.input.clone(); + let result = decision.evaluate(input.clone()).await.unwrap().result; + assert_eq!( test_case.output, result, "Decision file: {file_name}.\nInput:\n {input:#?}" @@ -246,7 +261,7 @@ async fn engine_function_v2() { let function_opt_r = engine .evaluate_with_opts( "function-v2.json", - &json!({ "input": 12 }), + json!({ "input": 12 }).into(), EvaluationOptions { trace: Some(true), max_depth: None, @@ -262,7 +277,7 @@ async fn engine_function_v2() { assert_eq!( function_opt.result, - json!({ "hello": "world", "multiplied": 24 }) + json!({ "hello": "world", "multiplied": 24 }).into() ) } } diff --git a/core/expression/benches/isolate.rs b/core/expression/benches/isolate.rs index 1a32c041..703d9b6b 100644 --- a/core/expression/benches/isolate.rs +++ b/core/expression/benches/isolate.rs @@ -2,23 +2,23 @@ use std::ops::Index; use criterion::{criterion_group, criterion_main, Bencher, Criterion}; use csv::StringRecord; -use serde_json::Value; +use zen_expression::variable::Variable; use zen_expression::Isolate; fn bench_unary(b: &mut Bencher, source: &'static str) { - let s: Value = serde_json::from_str(r#"{ "$": "ru" }"#).unwrap(); + let s = serde_json::from_str(r#"{ "$": "ru" }"#).unwrap(); - let mut isolate = Isolate::with_environment(&s); + let mut isolate = Isolate::with_environment(s); b.iter(|| { criterion::black_box(isolate.run_unary(source).unwrap()); }) } fn bench_standard(b: &mut Bencher, source: &'static str) { - let s: Value = serde_json::from_str(r#"{ "$": "ru" }"#).unwrap(); + let s = serde_json::from_str(r#"{ "$": "ru" }"#).unwrap(); - let mut isolate = Isolate::with_environment(&s); + let mut isolate = Isolate::with_environment(s); b.iter(|| { criterion::black_box(isolate.run_standard(source).unwrap()); }) @@ -42,7 +42,7 @@ fn bench_csv(b: &mut Bencher, kind: BenchmarkKind, csv_data: &'static str) { struct TestCase { expression: String, - environment: Option, + environment: Option, } let test_cases: Vec = rows @@ -78,7 +78,7 @@ fn bench_csv(b: &mut Bencher, kind: BenchmarkKind, csv_data: &'static str) { } in &test_cases { if let Some(env) = environment { - isolate.set_environment(env); + isolate.set_environment(env.clone()); } match kind { diff --git a/core/expression/src/compiler/compiler.rs b/core/expression/src/compiler/compiler.rs index 13209453..94e0682b 100644 --- a/core/expression/src/compiler/compiler.rs +++ b/core/expression/src/compiler/compiler.rs @@ -1,5 +1,6 @@ use rust_decimal::Decimal; use rust_decimal_macros::dec; +use std::rc::Rc; use crate::compiler::error::{CompilerError, CompilerResult}; use crate::compiler::{Opcode, TypeCheckKind, TypeConversionKind}; @@ -110,7 +111,7 @@ impl<'arena, 'bytecode_ref> CompilerInner<'arena, 'bytecode_ref> { Node::Null => Ok(self.emit(Opcode::Push(Variable::Null))), Node::Bool(v) => Ok(self.emit(Opcode::Push(Variable::Bool(*v)))), Node::Number(v) => Ok(self.emit(Opcode::Push(Variable::Number(*v)))), - Node::String(v) => Ok(self.emit(Opcode::Push(Variable::String(v)))), + Node::String(v) => Ok(self.emit(Opcode::Push(Variable::String(Rc::from(*v))))), Node::Pointer => Ok(self.emit(Opcode::Pointer)), Node::Root => Ok(self.emit(Opcode::FetchRootEnv)), Node::Array(v) => { @@ -147,7 +148,7 @@ impl<'arena, 'bytecode_ref> CompilerInner<'arena, 'bytecode_ref> { self.emit(Opcode::Push(Variable::Number(Decimal::from(parts.len())))); self.emit(Opcode::Array); - self.emit(Opcode::Push(Variable::String(""))); + self.emit(Opcode::Push(Variable::String(Rc::from("")))); Ok(self.emit(Opcode::Join)) } Node::Slice { node, to, from } => { diff --git a/core/expression/src/compiler/opcode.rs b/core/expression/src/compiler/opcode.rs index 362405ad..e6dfa825 100644 --- a/core/expression/src/compiler/opcode.rs +++ b/core/expression/src/compiler/opcode.rs @@ -4,7 +4,7 @@ use strum_macros::Display; /// Machine code interpreted by VM #[derive(Debug, PartialEq, Eq, Display)] pub enum Opcode<'a> { - Push(Variable<'a>), + Push(Variable), Pop, Rot, Fetch, diff --git a/core/expression/src/function.rs b/core/expression/src/function.rs index a74452e1..7f90f43d 100644 --- a/core/expression/src/function.rs +++ b/core/expression/src/function.rs @@ -1,18 +1,21 @@ -use serde_json::Value; - +use crate::variable::Variable; use crate::{Isolate, IsolateError}; /// Evaluates a standard expression -pub fn evaluate_expression(expression: &str, context: &Value) -> Result { +pub fn evaluate_expression(expression: &str, context: Variable) -> Result { Isolate::with_environment(context).run_standard(expression) } /// Evaluates a unary expression; Required: context must be an object with "$" key. -pub fn evaluate_unary_expression(expression: &str, context: &Value) -> Result { - let Some(context_object) = context.as_object() else { +pub fn evaluate_unary_expression( + expression: &str, + context: Variable, +) -> Result { + let Some(context_object_ref) = context.as_object() else { return Err(IsolateError::MissingContextReference); }; + let context_object = context_object_ref.borrow(); if !context_object.contains_key("$") { return Err(IsolateError::MissingContextReference); } @@ -28,8 +31,8 @@ mod test { #[test] fn bla() { let context = json!({ "tax": { "percentage": 10 } }); - let tax_amount = evaluate_expression("50 * tax.percentage / 100", &context).unwrap(); + let tax_amount = evaluate_expression("50 * tax.percentage / 100", context.into()).unwrap(); - assert_eq!(tax_amount, json!(5)); + assert_eq!(tax_amount, json!(5).into()); } } diff --git a/core/expression/src/isolate.rs b/core/expression/src/isolate.rs index 1334cb96..15864112 100644 --- a/core/expression/src/isolate.rs +++ b/core/expression/src/isolate.rs @@ -1,18 +1,15 @@ -use std::collections::HashMap; -use std::hash::BuildHasherDefault; - use ahash::AHasher; -use bumpalo::Bump; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; -use serde_json::Value; +use std::collections::HashMap; +use std::hash::BuildHasherDefault; use thiserror::Error; use crate::arena::UnsafeArena; use crate::compiler::{Compiler, CompilerError}; use crate::lexer::{Lexer, LexerError}; use crate::parser::{Parser, ParserError}; -use crate::variable::{ToVariable, Variable}; +use crate::variable::Variable; use crate::vm::{VMError, VM}; type ADefHasher = BuildHasherDefault; @@ -26,13 +23,12 @@ type ADefHasher = BuildHasherDefault; pub struct Isolate<'arena> { lexer: Lexer<'arena>, compiler: Compiler<'arena>, - vm: VM<'arena>, + vm: VM, bump: UnsafeArena<'arena>, - reference_bump: UnsafeArena<'arena>, - environment: Option<&'arena mut Variable<'arena>>, - references: HashMap<&'arena str, &'arena Variable<'arena>, ADefHasher>, + environment: Option, + references: HashMap, } impl<'a> Isolate<'a> { @@ -43,67 +39,60 @@ impl<'a> Isolate<'a> { vm: VM::new(), bump: UnsafeArena::new(), - reference_bump: UnsafeArena::new(), environment: None, references: Default::default(), } } - pub fn with_environment(value: &Value) -> Self { + pub fn with_environment(variable: Variable) -> Self { let mut isolate = Isolate::new(); - isolate.set_environment(value); + isolate.set_environment(variable); isolate } - pub fn set_environment(&mut self, value: &Value) { - let bump = self.reference_bump.get(); - let new_environment = value.to_variable(bump).unwrap(); - - self.environment.replace(bump.alloc(new_environment)); + pub fn set_environment(&mut self, variable: Variable) { + self.environment.replace(variable); } pub fn update_environment(&mut self, mut updater: F) where - F: FnMut(&'a Bump, &mut Option<&'a mut Variable<'a>>), + F: FnMut(Option<&mut Variable>), { - let bump = self.reference_bump.get(); - updater(bump, &mut self.environment); + updater(self.environment.as_mut()); } pub fn set_reference(&mut self, reference: &'a str) -> Result<(), IsolateError> { - let bump = self.reference_bump.get(); let reference_value = match self.references.get(reference) { - Some(value) => value, + Some(value) => value.clone(), None => { let result = self.run_standard(reference)?; - let value = &*bump.alloc(result.to_variable(bump).unwrap()); - self.references.insert(reference, value); - value + self.references + .insert(reference.to_string(), result.clone()); + result } }; if !matches!(&mut self.environment, Some(Variable::Object(_))) { - self.environment - .replace(bump.alloc(Variable::empty_object(bump))); + self.environment.replace(Variable::empty_object()); } - let Some(Variable::Object(environment_object)) = self.environment else { + let Some(Variable::Object(environment_object_ref)) = &self.environment else { return Err(IsolateError::ReferenceError); }; - environment_object.insert("$", reference_value.clone_in(bump)); + let mut environment_object = environment_object_ref.borrow_mut(); + environment_object.insert("$".to_string(), reference_value); + Ok(()) } - pub fn get_reference(&self, reference: &str) -> Option { - let reference_variable = self.references.get(reference)?; - - Some(reference_variable.to_value()) + pub fn get_reference(&self, reference: &str) -> Option { + self.references.get(reference).cloned() } - pub fn run_standard(&mut self, source: &'a str) -> Result { + pub fn run_standard(&mut self, source: &'a str) -> Result { self.bump.with_mut(|b| b.reset()); let bump = self.bump.get(); @@ -128,14 +117,10 @@ impl<'a> Isolate<'a> { let result = self .vm - .run( - bytecode, - bump, - self.environment.as_deref().unwrap_or(&Variable::Null), - ) + .run(bytecode, self.environment.clone().unwrap_or(Variable::Null)) .map_err(|source| IsolateError::VMError { source })?; - Ok(result.to_value()) + Ok(result) } pub fn run_unary(&mut self, source: &'a str) -> Result { @@ -163,11 +148,7 @@ impl<'a> Isolate<'a> { let result = self .vm - .run( - bytecode, - bump, - self.environment.as_deref().unwrap_or(&Variable::Null), - ) + .run(bytecode, self.environment.clone().unwrap_or(Variable::Null)) .map_err(|source| IsolateError::VMError { source })?; result.as_bool().ok_or_else(|| IsolateError::ValueCastError) diff --git a/core/expression/src/lib.rs b/core/expression/src/lib.rs index 4bb4581f..d81cadb3 100644 --- a/core/expression/src/lib.rs +++ b/core/expression/src/lib.rs @@ -13,13 +13,16 @@ //! # Example //! Evaluate expression using isolate: //! ``` -//! use zen_expression::{evaluate_expression, json}; +//! use zen_expression::evaluate_expression; +//! use zen_expression::variable::Variable; +//! use rust_decimal_macros::dec; +//! use serde_json::json; //! //! fn main() { //! let context = json!({ "tax": { "percentage": 10 } }); -//! let tax_amount = evaluate_expression("50 * tax.percentage / 100", &context).unwrap(); +//! let tax_amount = evaluate_expression("50 * tax.percentage / 100", context.into()).unwrap(); //! -//! assert_eq!(tax_amount, json!(5)); +//! assert_eq!(tax_amount, Variable::Number(dec!(5))); //! } //! ``` //! @@ -28,16 +31,19 @@ //! will re-use allocated memory from previous evaluations, drastically improving performance. //! //! ``` -//! use zen_expression::{Isolate, json}; +//! use zen_expression::Isolate; +//! use zen_expression::variable::Variable; +//! use rust_decimal_macros::dec; +//! use serde_json::json; //! //! fn main() { //! let context = json!({ "tax": { "percentage": 10 } }); -//! let mut isolate = Isolate::with_environment(&context); +//! let mut isolate = Isolate::with_environment(context.into()); //! //! // Fast 🚀 //! for _ in 0..1_000 { //! let tax_amount = isolate.run_standard("50 * tax.percentage / 100").unwrap(); -//! assert_eq!(tax_amount, json!(5)); +//! assert_eq!(tax_amount, Variable::Number(dec!(5))); //! } //! } //! ``` @@ -62,4 +68,4 @@ pub mod vm; pub use function::{evaluate_expression, evaluate_unary_expression}; pub use isolate::{Isolate, IsolateError}; -pub use serde_json::json; +pub use variable::Variable; diff --git a/core/expression/src/variable/conv.rs b/core/expression/src/variable/conv.rs index b6bf4184..eb22e3b7 100644 --- a/core/expression/src/variable/conv.rs +++ b/core/expression/src/variable/conv.rs @@ -1,44 +1,104 @@ -use bumpalo::collections::Vec as BumpVec; -use bumpalo::Bump; -use rust_decimal::Decimal; -use serde_json::Value; - -use crate::variable::map::BumpMap; use crate::variable::Variable; +use crate::vm::helpers::date_time; +use crate::vm::VMError; +use chrono::NaiveDateTime; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; +use serde_json::{Number, Value}; +use std::rc::Rc; -pub trait ToVariable<'arena> { - type Error; - - fn to_variable(&self, arena: &'arena Bump) -> Result, Self::Error>; +impl From for Variable { + fn from(value: Value) -> Self { + match value { + Value::Null => Variable::Null, + Value::Bool(b) => Variable::Bool(b), + Value::Number(n) => { + Variable::Number(Decimal::from_str_exact(n.as_str()).expect("Allowed number")) + } + Value::String(s) => Variable::String(Rc::from(s.as_str())), + Value::Array(arr) => { + Variable::from_array(arr.into_iter().map(Variable::from).collect()) + } + Value::Object(obj) => Variable::from_object( + obj.into_iter() + .map(|(k, v)| (k, Variable::from(v))) + .collect(), + ), + } + } } -impl<'arena> ToVariable<'arena> for Value { - type Error = (); - - fn to_variable(&self, arena: &'arena Bump) -> Result, Self::Error> { - match self { - Value::Null => Ok(Variable::Null), - Value::Bool(v) => Ok(Variable::Bool(*v)), - Value::Number(n) => Ok(Variable::Number( - Decimal::from_str_exact(n.as_str()).map_err(|_| ())?, - )), - Value::String(s) => Ok(Variable::String(arena.alloc_str(s.as_str()))), - Value::Array(arr) => { - let mut vec = BumpVec::with_capacity_in(arr.len(), arena); - for x in arr { - vec.push(x.to_variable(arena)?); - } - - Ok(Variable::Array(vec)) +impl From<&Value> for Variable { + fn from(value: &Value) -> Self { + match value { + Value::Null => Variable::Null, + Value::Bool(b) => Variable::Bool(*b), + Value::Number(n) => { + Variable::Number(Decimal::from_str_exact(n.as_str()).expect("Allowed number")) } - Value::Object(obj) => { - let mut m = BumpMap::with_capacity_in(obj.len(), arena); - for (k, v) in obj { - m.insert(&*arena.alloc_str(k.as_str()), v.to_variable(arena)?); - } + Value::String(s) => Variable::String(Rc::from(s.as_str())), + Value::Array(arr) => Variable::from_array(arr.iter().map(Variable::from).collect()), + Value::Object(obj) => Variable::from_object( + obj.iter() + .map(|(k, v)| (k.clone(), Variable::from(v))) + .collect(), + ), + } + } +} - Ok(Variable::Object(m)) +impl From for Value { + fn from(value: Variable) -> Self { + match value { + Variable::Null => Value::Null, + Variable::Bool(b) => Value::Bool(b), + Variable::Number(n) => Value::Number(Number::from_string_unchecked(n.to_string())), + Variable::String(s) => Value::String(s.to_string()), + Variable::Array(arr) => { + let vec = Rc::try_unwrap(arr) + .map(|a| a.into_inner()) + .unwrap_or_else(|s| { + let borrowed = s.borrow(); + borrowed.clone() + }); + + Value::Array(vec.into_iter().map(Value::from).collect()) + } + Variable::Object(obj) => { + let hmap = Rc::try_unwrap(obj) + .map(|a| a.into_inner()) + .unwrap_or_else(|s| { + let borrowed = s.borrow(); + borrowed.clone() + }); + + Value::Object(hmap.into_iter().map(|(k, v)| (k, Value::from(v))).collect()) } } } } + +impl TryFrom<&Variable> for NaiveDateTime { + type Error = VMError; + + fn try_from(value: &Variable) -> Result { + match value { + Variable::String(a) => date_time(a), + #[allow(deprecated)] + Variable::Number(a) => NaiveDateTime::from_timestamp_opt( + a.to_i64().ok_or_else(|| VMError::OpcodeErr { + opcode: "DateManipulation".into(), + message: "Failed to extract date".into(), + })?, + 0, + ) + .ok_or_else(|| VMError::ParseDateTimeErr { + timestamp: a.to_string(), + }), + _ => Err(VMError::OpcodeErr { + opcode: "DateManipulation".into(), + message: "Unsupported type".into(), + }), + } + } +} diff --git a/core/expression/src/variable/de.rs b/core/expression/src/variable/de.rs index 6c82eb47..f88bc156 100644 --- a/core/expression/src/variable/de.rs +++ b/core/expression/src/variable/de.rs @@ -1,22 +1,17 @@ -use std::fmt::Formatter; -use std::marker::PhantomData; - -use bumpalo::collections::Vec as BumpVec; -use bumpalo::Bump; +use crate::variable::Variable; +use ahash::{HashMap, HashMapExt}; use rust_decimal::prelude::FromPrimitive; use rust_decimal::Decimal; use serde::de::{DeserializeSeed, Error, MapAccess, SeqAccess, Unexpected, Visitor}; -use serde::Deserializer; +use serde::{Deserialize, Deserializer}; +use std::fmt::Formatter; +use std::marker::PhantomData; +use std::rc::Rc; -use crate::variable::map::BumpMap; -use crate::variable::Variable; +struct VariableVisitor; -struct VariableVisitor<'arena> { - arena: &'arena Bump, -} - -impl<'arena, 'de: 'arena> Visitor<'de> for VariableVisitor<'arena> { - type Value = Variable<'arena>; +impl<'de> Visitor<'de> for VariableVisitor { + type Value = Variable; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { formatter.write_str("A valid type") @@ -60,10 +55,7 @@ impl<'arena, 'de: 'arena> Visitor<'de> for VariableVisitor<'arena> { where E: Error, { - match Decimal::from_str_exact(v) { - Ok(d) => Ok(Variable::Number(d)), - Err(_) => Ok(Variable::String(self.arena.alloc_str(v))), - } + Ok(Variable::String(Rc::from(v))) } fn visit_unit(self) -> Result @@ -77,47 +69,58 @@ impl<'arena, 'de: 'arena> Visitor<'de> for VariableVisitor<'arena> { where A: SeqAccess<'de>, { - let mut vec = BumpVec::with_capacity_in(seq.size_hint().unwrap_or_default(), self.arena); - while let Some(value) = seq.next_element_seed(VariableDeserializer { arena: self.arena })? { + let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or_default()); + while let Some(value) = seq.next_element_seed(VariableDeserializer)? { vec.push(value); } - Ok(Variable::Array(vec)) + Ok(Variable::from_array(vec)) } fn visit_map(self, mut map: A) -> Result where A: MapAccess<'de>, { - let mut m = BumpMap::with_capacity_in(map.size_hint().unwrap_or_default(), self.arena); - while let Some((key, value)) = - map.next_entry_seed(PhantomData, VariableDeserializer { arena: self.arena })? - { - m.insert(&*self.arena.alloc_str(key), value); + let mut m = HashMap::with_capacity(map.size_hint().unwrap_or_default()); + let mut first = true; + while let Some((key, value)) = map.next_entry_seed(PhantomData, VariableDeserializer)? { + if first && key == "$serde_json::private::Number" { + return Ok(Variable::Number( + Decimal::from_str_exact( + value + .as_str() + .ok_or_else(|| Error::custom("failed to deserialize number"))?, + ) + .map_err(|_| Error::custom("invalid number"))?, + )); + } + + m.insert(key, value); + first = false; } - Ok(Variable::Object(m)) + Ok(Variable::from_object(m)) } } -pub struct VariableDeserializer<'arena> { - arena: &'arena Bump, -} +pub struct VariableDeserializer; -impl<'arena> VariableDeserializer<'arena> { - #[allow(dead_code)] - pub fn new_in(arena: &'arena Bump) -> Self { - Self { arena } - } -} - -impl<'arena, 'de: 'arena> DeserializeSeed<'de> for VariableDeserializer<'arena> { - type Value = Variable<'arena>; +impl<'de> DeserializeSeed<'de> for VariableDeserializer { + type Value = Variable; fn deserialize(self, deserializer: D) -> Result where D: Deserializer<'de>, { - deserializer.deserialize_any(VariableVisitor { arena: self.arena }) + deserializer.deserialize_any(VariableVisitor) + } +} + +impl<'de> Deserialize<'de> for Variable { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(VariableVisitor) } } diff --git a/core/expression/src/variable/map.rs b/core/expression/src/variable/map.rs deleted file mode 100644 index 4de6ec4a..00000000 --- a/core/expression/src/variable/map.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::borrow::Borrow; - -use bumpalo::collections::Vec as BumpVec; -use bumpalo::Bump; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Clone)] -pub struct BumpMap<'arena, K, V> { - inner: BumpVec<'arena, (K, V)>, -} - -impl<'arena, K, V> BumpMap<'arena, K, V> { - pub fn new_in(arena: &'arena Bump) -> Self { - BumpMap { - inner: BumpVec::new_in(arena), - } - } - - pub fn with_capacity_in(capacity: usize, arena: &'arena Bump) -> Self { - BumpMap { - inner: BumpVec::with_capacity_in(capacity, arena), - } - } - - pub fn from_iter_in>(iter: I, arena: &'arena Bump) -> Self { - BumpMap { - inner: BumpVec::from_iter_in(iter, arena), - } - } - - pub fn iter(&self) -> impl Iterator { - self.inner.iter().map(|(k, v)| (k, v)) - } - - fn position_of(&self, key: &Q) -> Option - where - K: Borrow, - Q: Eq + ?Sized, - { - self.inner.iter().position(|(k, _)| k.borrow() == key) - } - - pub fn contains_key(&self, key: &Q) -> bool - where - K: Borrow, - Q: Eq + ?Sized, - { - self.position_of(key).is_some() - } - - pub fn get(&self, key: &Q) -> Option<&V> - where - K: Borrow, - Q: Eq + ?Sized, - { - self.position_of(key.borrow()).map(|p| &self.inner[p].1) - } - - pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> - where - K: Borrow, - Q: Eq + ?Sized, - { - self.position_of(key).map(|p| &mut self.inner[p].1) - } - - pub fn insert(&mut self, key: K, value: V) -> Option - where - K: Eq, - { - let existing_position = self.position_of(&key); - self.inner.push((key, value)); - - existing_position.and_then(|p| Some(self.inner.remove(p).1)) - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn capacity(&self) -> usize { - self.inner.capacity() - } - - pub fn clear(&mut self) { - self.inner.clear() - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - pub fn into_inner(self) -> BumpVec<'arena, (K, V)> { - self.inner - } -} diff --git a/core/expression/src/variable/mod.rs b/core/expression/src/variable/mod.rs index 31aaec6a..7a90c6a1 100644 --- a/core/expression/src/variable/mod.rs +++ b/core/expression/src/variable/mod.rs @@ -1,64 +1,91 @@ -pub use crate::variable::map::BumpMap; -pub use bumpalo::collections::Vec as BumpVec; -use bumpalo::Bump; -use chrono::NaiveDateTime; -use rust_decimal::prelude::ToPrimitive; +use ahash::HashMap; +use rust_decimal::prelude::Zero; use rust_decimal::Decimal; -use serde_json::{Number, Value}; -use strum_macros::Display; +use serde_json::Value; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::fmt::{Debug, Display, Formatter}; +use std::ops::Deref; +use std::rc::Rc; mod conv; mod de; -mod map; mod ser; mod types; -use crate::vm::helpers::date_time; -use crate::vm::VMError; - -pub use conv::ToVariable; +pub use de::VariableDeserializer; pub use types::VariableType; -#[derive(Debug, PartialEq, Eq, Display)] -pub enum Variable<'arena> { +pub(crate) type RcCell = Rc>; +#[derive(PartialEq, Eq)] +pub enum Variable { Null, Bool(bool), Number(Decimal), - String(&'arena str), - Array(BumpVec<'arena, Variable<'arena>>), - Object(BumpMap<'arena, &'arena str, Variable<'arena>>), + String(Rc), + Array(RcCell>), + Object(RcCell>), } -impl<'arena> Variable<'arena> { - pub fn empty_object(arena: &'arena Bump) -> Self { - Variable::Object(BumpMap::new_in(arena)) +impl Variable { + pub fn from_array(arr: Vec) -> Self { + Self::Array(Rc::new(RefCell::new(arr))) } - pub fn empty_array(arena: &'arena Bump) -> Self { - Variable::Array(BumpVec::new_in(arena)) + pub fn from_object(obj: HashMap) -> Self { + Self::Object(Rc::new(RefCell::new(obj))) } - pub fn as_str(&self) -> Option<&'arena str> { + pub fn empty_object() -> Self { + Variable::Object(Default::default()) + } + + pub fn empty_array() -> Self { + Variable::Array(Default::default()) + } + + pub fn as_str(&self) -> Option<&str> { match self { - Variable::String(s) => Some(*s), + Variable::String(s) => Some(s.as_ref()), _ => None, } } - pub fn as_array(&self) -> Option<&BumpVec<'arena, Variable<'arena>>> { + pub fn as_rc_str(&self) -> Option> { match self { - Variable::Array(arr) => Some(arr), + Variable::String(s) => Some(s.clone()), _ => None, } } - pub fn as_object(&self) -> Option<&BumpMap<'arena, &'arena str, Variable<'arena>>> { + pub fn as_array(&self) -> Option>> { match self { - Variable::Object(obj) => Some(obj), + Variable::Array(arr) => Some(arr.clone()), _ => None, } } + pub fn is_array(&self) -> bool { + match self { + Variable::Array(_) => true, + _ => false, + } + } + + pub fn as_object(&self) -> Option>> { + match self { + Variable::Object(obj) => Some(obj.clone()), + _ => None, + } + } + + pub fn is_object(&self) -> bool { + match self { + Variable::Object(_) => true, + _ => false, + } + } + pub fn as_bool(&self) -> Option { match self { Variable::Bool(b) => Some(*b), @@ -66,6 +93,13 @@ impl<'arena> Variable<'arena> { } } + pub fn as_number(&self) -> Option { + match self { + Variable::Number(n) => Some(*n), + _ => None, + } + } + pub fn type_name(&self) -> &'static str { match self { Variable::Null => "null", @@ -78,108 +112,188 @@ impl<'arena> Variable<'arena> { } pub fn to_value(&self) -> Value { - match self { - Variable::Null => Value::Null, - Variable::Bool(b) => Value::Bool(*b), - Variable::Number(n) => { - Value::Number(Number::from_string_unchecked(n.normalize().to_string())) - } - Variable::String(str) => Value::String(str.to_string()), - Variable::Array(arr) => Value::Array(arr.iter().map(|i| i.to_value()).collect()), - Variable::Object(obj) => Value::Object( - obj.iter() - .map(|(k, v)| (k.to_string(), v.to_value())) - .collect(), - ), - } + Value::from(self.shallow_clone()) } - pub fn clone_in<'new>(&self, arena: &'new Bump) -> Variable<'new> { + pub fn dot(&self, key: &str) -> Option { + key.split('.') + .try_fold(self.shallow_clone(), |var, part| match var { + Variable::Object(obj) => { + let reference = obj.borrow(); + reference.get(part).map(|v| v.shallow_clone()) + } + _ => None, + }) + } + + fn dot_head(&self, key: &str) -> Option { + let mut parts = Vec::from_iter(key.split('.')); + parts.pop(); + + parts + .iter() + .try_fold(self.shallow_clone(), |var, part| match var { + Variable::Object(obj) => { + let mut obj_ref = obj.borrow_mut(); + Some(match obj_ref.entry(part.to_string()) { + Entry::Occupied(occ) => occ.get().shallow_clone(), + Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(), + }) + } + _ => None, + }) + } + pub fn dot_remove(&self, key: &str) -> Option { + let last_part = key.split('.').last()?; + let head = self.dot_head(key)?; + let Variable::Object(object_ref) = head else { + return None; + }; + + let mut object = object_ref.borrow_mut(); + object.remove(last_part) + } + + pub fn dot_insert(&self, key: &str, variable: Variable) -> Option { + let last_part = key.split('.').last()?; + let head = self.dot_head(key)?; + let Variable::Object(object_ref) = head else { + return None; + }; + + let mut object = object_ref.borrow_mut(); + object.insert(last_part.to_string(), variable) + } + + pub fn merge(&mut self, patch: &Variable) -> Variable { + merge_variables(self, patch, true); + + self.shallow_clone() + } + + pub fn shallow_clone(&self) -> Self { match self { Variable::Null => Variable::Null, Variable::Bool(b) => Variable::Bool(*b), Variable::Number(n) => Variable::Number(*n), - Variable::String(s) => Variable::String(arena.alloc_str(s)), - Variable::Array(arr) => Variable::Array(BumpVec::from_iter_in( - arr.iter().map(|v| v.clone_in(arena)), - arena, - )), - Variable::Object(obj) => Variable::Object(BumpMap::from_iter_in( - obj.iter() - .map(|(k, v)| (&*arena.alloc_str(k), v.clone_in(arena))), - arena, - )), + Variable::String(s) => Variable::String(s.clone()), + Variable::Array(a) => Variable::Array(a.clone()), + Variable::Object(o) => Variable::Object(o.clone()), } } - pub fn dot(&self, key: &str) -> Option<&Variable<'arena>> { - key.split('.').try_fold(self, |var, part| match var { - Variable::Object(obj) => obj.get(part), - _ => None, - }) - } - - pub fn dot_mut(&mut self, key: &str) -> Option<&mut Variable<'arena>> { - key.split('.').try_fold(self, |var, part| match var { - Variable::Object(obj) => obj.get_mut(part), - _ => None, - }) - } - - pub fn dot_insert( - &mut self, - arena: &'arena Bump, - key: &str, - variable: Variable<'arena>, - ) -> Option<&mut Variable<'arena>> { - let mut parts: BumpVec<&'arena str> = - BumpVec::from_iter_in(key.split('.').map(|p| &*arena.alloc_str(p)), arena); - let Some(last_part) = parts.pop() else { - return None; - }; - - let head = parts.iter().try_fold(self, |var, part| match var { - Variable::Object(obj) => { - if obj.contains_key(part) { - obj.get_mut(part) - } else { - obj.insert(part, Self::empty_object(arena)); - obj.get_mut(part) - } + pub fn deep_clone(&self) -> Self { + match self { + Variable::Array(a) => { + let arr = a.borrow(); + Variable::from_array(arr.iter().map(|v| v.deep_clone()).collect()) } - _ => None, - })?; - - let Variable::Object(head_obj) = head else { - return None; - }; - - head_obj.insert(last_part, variable); - head_obj.get_mut(last_part) + Variable::Object(o) => { + let obj = o.borrow(); + Variable::from_object( + obj.iter() + .map(|(k, v)| (k.to_string(), v.deep_clone())) + .collect(), + ) + } + _ => self.shallow_clone(), + } } -} -impl TryFrom<&Variable<'_>> for NaiveDateTime { - type Error = VMError; - - fn try_from(value: &Variable<'_>) -> Result { - match value { - Variable::String(a) => date_time(a), - #[allow(deprecated)] - Variable::Number(a) => NaiveDateTime::from_timestamp_opt( - a.to_i64().ok_or_else(|| VMError::OpcodeErr { - opcode: "DateManipulation".into(), - message: "Failed to extract date".into(), - })?, - 0, - ) - .ok_or_else(|| VMError::ParseDateTimeErr { - timestamp: a.to_string(), - }), - _ => Err(VMError::OpcodeErr { - opcode: "DateManipulation".into(), - message: "Unsupported type".into(), - }), + pub fn depth_clone(&self, depth: usize) -> Self { + match depth.is_zero() { + true => self.shallow_clone(), + false => match self { + Variable::Array(a) => { + let arr = a.borrow(); + Variable::from_array(arr.iter().map(|v| v.depth_clone(depth - 1)).collect()) + } + Variable::Object(o) => { + let obj = o.borrow(); + Variable::from_object( + obj.iter() + .map(|(k, v)| (k.to_string(), v.depth_clone(depth - 1))) + .collect(), + ) + } + _ => self.shallow_clone(), + }, } } } + +impl Clone for Variable { + fn clone(&self) -> Self { + self.shallow_clone() + } +} + +fn merge_variables(doc: &mut Variable, patch: &Variable, top_level: bool) { + if !patch.is_object() && !patch.is_array() && top_level { + return; + } + + if doc.is_object() && patch.is_object() { + let map_ref = doc.as_object().unwrap(); + let mut map = map_ref.borrow_mut(); + + let patch_ref = patch.as_object().unwrap(); + let patch = patch_ref.borrow(); + for (key, value) in patch.deref() { + if value == &Variable::Null { + map.remove(key.as_str()); + } else { + let entry = map.entry(key.to_string()).or_insert(Variable::Null); + merge_variables(entry, value, false) + } + } + } else if doc.is_array() && patch.is_array() { + let arr_ref = doc.as_array().unwrap(); + let mut arr = arr_ref.borrow_mut(); + + let patch_ref = patch.as_array().unwrap(); + let patch = patch_ref.borrow(); + arr.extend(patch.iter().map(|s| s.shallow_clone())); + } else { + *doc = patch.shallow_clone(); + } +} + +impl Display for Variable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Variable::Null => write!(f, "null"), + Variable::Bool(b) => match *b { + true => write!(f, "true"), + false => write!(f, "false"), + }, + Variable::Number(n) => write!(f, "{n}"), + Variable::String(s) => write!(f, "\"{s}\""), + Variable::Array(arr) => { + let arr = arr.borrow(); + let s = arr + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(","); + write!(f, "[{s}]") + } + Variable::Object(obj) => { + let obj = obj.borrow(); + let s = obj + .iter() + .map(|(k, v)| format!("\"{k}\":{v}")) + .collect::>() + .join(","); + + write!(f, "{{{s}}}") + } + } + } +} + +impl Debug for Variable { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } +} diff --git a/core/expression/src/variable/ser.rs b/core/expression/src/variable/ser.rs index 3082a4f9..4f726754 100644 --- a/core/expression/src/variable/ser.rs +++ b/core/expression/src/variable/ser.rs @@ -1,9 +1,7 @@ -use bumpalo::Bump; -use serde::{Serialize, Serializer}; +use crate::variable::Variable; +use serde::{ser, Serialize, Serializer}; -use crate::variable::{ser, Variable}; - -impl<'arena> Serialize for Variable<'arena> { +impl Serialize for Variable { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -13,20 +11,14 @@ impl<'arena> Serialize for Variable<'arena> { Variable::Bool(v) => serializer.serialize_bool(*v), Variable::Number(v) => ser::Serialize::serialize(v, serializer), Variable::String(v) => serializer.serialize_str(v), - Variable::Array(v) => serializer.collect_seq(v.iter()), - Variable::Object(v) => serializer.collect_map(v.iter()), + Variable::Array(v) => { + let borrowed = v.borrow(); + serializer.collect_seq(borrowed.iter()) + } + Variable::Object(v) => { + let borrowed = v.borrow(); + serializer.collect_map(borrowed.iter()) + } } } } - -#[allow(dead_code)] -pub struct VariableSerializer<'arena> { - arena: &'arena Bump, -} - -impl<'arena> VariableSerializer<'arena> { - #[allow(dead_code)] - pub fn new_in(arena: &'arena Bump) -> Self { - Self { arena } - } -} diff --git a/core/expression/src/vm/variable.rs b/core/expression/src/vm/variable.rs index d917924c..0ca7f1c9 100644 --- a/core/expression/src/vm/variable.rs +++ b/core/expression/src/vm/variable.rs @@ -1,44 +1,50 @@ -use bumpalo::Bump; +use crate::variable::Variable; +use ahash::{HashMap, HashMapExt}; +use std::rc::Rc; -use crate::variable::{BumpMap, ToVariable, Variable}; - -pub(crate) struct IntervalObject<'arena> { - pub(crate) left_bracket: &'arena str, - pub(crate) right_bracket: &'arena str, - pub(crate) left: &'arena Variable<'arena>, - pub(crate) right: &'arena Variable<'arena>, +pub(crate) struct IntervalObject { + pub(crate) left_bracket: Rc, + pub(crate) right_bracket: Rc, + pub(crate) left: Variable, + pub(crate) right: Variable, } -impl<'arena> ToVariable<'arena> for IntervalObject<'arena> { - type Error = (); +impl IntervalObject { + pub fn to_variable(&self) -> Variable { + let mut tree = HashMap::new(); - fn to_variable(&self, arena: &'arena Bump) -> Result, Self::Error> { - let mut tree = BumpMap::new_in(arena); + tree.insert( + "_symbol".to_string(), + Variable::String("Interval".to_string().into()), + ); + tree.insert( + "left_bracket".to_string(), + Variable::String(self.left_bracket.clone()), + ); + tree.insert( + "right_bracket".to_string(), + Variable::String(self.right_bracket.clone()), + ); + tree.insert("left".to_string(), self.left.clone()); + tree.insert("right".to_string(), self.right.clone()); - tree.insert("_symbol", Variable::String("Interval")); - tree.insert("left_bracket", Variable::String(self.left_bracket)); - tree.insert("right_bracket", Variable::String(self.right_bracket)); - tree.insert("left", self.left.clone_in(arena)); - tree.insert("right", self.right.clone_in(arena)); - - Ok(Variable::Object(tree)) + Variable::from_object(tree) } -} -impl<'a> IntervalObject<'a> { - pub(crate) fn try_from_object(var: &'a Variable<'a>) -> Option { + pub(crate) fn try_from_object(var: Variable) -> Option { let Variable::Object(tree) = var else { return None; }; - if tree.get("_symbol")?.as_str()? != "Interval" { + let tree_ref = tree.borrow(); + if tree_ref.get("_symbol")?.as_str()? != "Interval" { return None; } - let left_bracket = tree.get("left_bracket")?.as_str()?; - let right_bracket = tree.get("right_bracket")?.as_str()?; - let left = tree.get("left")?; - let right = tree.get("right")?; + let left_bracket = tree_ref.get("left_bracket")?.as_rc_str()?; + let right_bracket = tree_ref.get("right_bracket")?.as_rc_str()?; + let left = tree_ref.get("left")?.clone(); + let right = tree_ref.get("right")?.clone(); Some(Self { left_bracket, diff --git a/core/expression/src/vm/vm.rs b/core/expression/src/vm/vm.rs index e391edd2..dfba4d53 100644 --- a/core/expression/src/vm/vm.rs +++ b/core/expression/src/vm/vm.rs @@ -1,8 +1,11 @@ -use std::collections::HashMap; - -use bumpalo::collections::Vec as BumpVec; -use bumpalo::collections::{CollectIn, String as BumpString}; -use bumpalo::Bump; +use crate::compiler::{Opcode, TypeCheckKind, TypeConversionKind}; +use crate::variable::Variable; +use crate::variable::Variable::*; +use crate::vm::error::VMError::*; +use crate::vm::error::VMResult; +use crate::vm::helpers::{date_time, date_time_end_of, date_time_start_of, time}; +use crate::vm::variable::IntervalObject; +use ahash::{HashMap, HashMapExt}; use chrono::NaiveDateTime; use chrono::{Datelike, Timelike}; #[cfg(not(feature = "regex-lite"))] @@ -12,33 +15,25 @@ use regex_lite::Regex; use rust_decimal::prelude::{FromPrimitive, ToPrimitive}; use rust_decimal::{Decimal, MathematicalOps}; use rust_decimal_macros::dec; - -use crate::compiler::{Opcode, TypeCheckKind, TypeConversionKind}; -use crate::variable::Variable; -use crate::variable::Variable::*; -use crate::variable::{BumpMap, ToVariable}; -use crate::vm::error::VMError::*; -use crate::vm::error::VMResult; -use crate::vm::helpers::{date_time, date_time_end_of, date_time_start_of, time}; -use crate::vm::variable::IntervalObject; - -pub(crate) const NULL_VAR: &'static Variable = &Null; +use std::cell::RefCell; +use std::rc::Rc; +use std::string::String as StdString; #[derive(Debug)] -pub struct Scope<'arena> { - array: &'arena Variable<'arena>, +pub struct Scope { + array: Variable, len: usize, iter: usize, count: usize, } #[derive(Debug)] -pub struct VM<'arena> { - scopes: Vec>, - stack: Vec<&'arena Variable<'arena>>, +pub struct VM { + scopes: Vec, + stack: Vec, } -impl<'arena> VM<'arena> { +impl VM { pub fn new() -> Self { Self { scopes: Default::default(), @@ -46,59 +41,47 @@ impl<'arena> VM<'arena> { } } - pub fn run( - &mut self, - bytecode: &[Opcode<'arena>], - bump: &'arena Bump, - env: &Variable<'arena>, - ) -> VMResult<&Variable> { + pub fn run(&mut self, bytecode: &[Opcode], env: Variable) -> VMResult { self.stack.clear(); self.scopes.clear(); - let s = VMInner::new(bytecode, &mut self.stack, &mut self.scopes, bump).run(env); + let s = VMInner::new(bytecode, &mut self.stack, &mut self.scopes).run(env); Ok(s?) } } struct VMInner<'arena, 'parent_ref, 'bytecode_ref> { - scopes: &'parent_ref mut Vec>, - stack: &'parent_ref mut Vec<&'arena Variable<'arena>>, + scopes: &'parent_ref mut Vec, + stack: &'parent_ref mut Vec, bytecode: &'bytecode_ref [Opcode<'arena>], - bump: &'arena Bump, ip: usize, } impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ref> { pub fn new( bytecode: &'bytecode_ref [Opcode<'arena>], - stack: &'parent_ref mut Vec<&'arena Variable<'arena>>, - scopes: &'parent_ref mut Vec>, - bump: &'arena Bump, + stack: &'parent_ref mut Vec, + scopes: &'parent_ref mut Vec, ) -> Self { Self { + ip: 0, scopes, stack, - ip: 0, bytecode, - bump, } } - fn push(&mut self, var: Variable<'arena>) { - self.stack.push(self.bump.alloc(var)); + fn push(&mut self, var: Variable) { + self.stack.push(var); } - fn pop(&mut self) -> VMResult<&'arena Variable<'arena>> { + fn pop(&mut self) -> VMResult { self.stack.pop().ok_or_else(|| StackOutOfBounds { stack: format!("{:?}", self.stack), }) } - fn push_ref(&mut self, var: &'arena Variable<'arena>) { - self.stack.push(var); - } - - pub fn run(&mut self, env: &Variable<'arena>) -> VMResult<&'arena Variable<'arena>> { + pub fn run(&mut self, env: Variable) -> VMResult { if self.ip != 0 { self.ip = 0; } @@ -116,7 +99,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match op { Opcode::Push(v) => { - self.push(v.clone_in(self.bump)); + self.push(v.clone()); } Opcode::Pop => { self.pop()?; @@ -132,15 +115,20 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match (a, b) { (Object(o), String(s)) => { - self.push_ref(o.get(*s).unwrap_or(&NULL_VAR)); + let obj = o.borrow(); + self.push(obj.get(s.as_ref()).cloned().unwrap_or(Null)); + } + (Array(a), Number(n)) => { + let arr = a.borrow(); + self.push( + arr.get(n.to_usize().ok_or_else(|| OpcodeErr { + opcode: "Fetch".into(), + message: "Failed to convert to usize".into(), + })?) + .cloned() + .unwrap_or(Null), + ) } - (Array(arr), Number(n)) => self.push_ref( - arr.get(n.to_usize().ok_or_else(|| OpcodeErr { - opcode: "Fetch".into(), - message: "Failed to convert to usize".into(), - })?) - .unwrap_or(&NULL_VAR), - ), (String(str), Number(n)) => { let index = n.to_usize().ok_or_else(|| OpcodeErr { opcode: "Fetch".into(), @@ -148,20 +136,23 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ })?; if let Some(slice) = str.get(index..index + 1) { - self.push(String(self.bump.alloc_str(slice))); + self.push(String(Rc::from(slice))); } else { - self.push_ref(&NULL_VAR) + self.push(Null) }; } - _ => self.push_ref(&NULL_VAR), + _ => self.push(Null), } } - Opcode::FetchEnv(f) => match env { - Object(o) => match o.get(*f) { - None => self.push_ref(&NULL_VAR), - Some(v) => self.push(v.clone_in(self.bump)), - }, - Null => self.push_ref(NULL_VAR), + Opcode::FetchEnv(f) => match &env { + Object(o) => { + let obj = o.borrow(); + match obj.get(*f) { + None => self.push(Null), + Some(v) => self.push(v.clone()), + } + } + Null => self.push(Null), _ => { return Err(OpcodeErr { opcode: "FetchEnv".into(), @@ -170,13 +161,13 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ } }, Opcode::FetchRootEnv => { - self.push(env.clone_in(self.bump)); + self.push(env.clone()); } Opcode::Negate => { let a = self.pop()?; match a { Number(n) => { - self.push(Number(-*n)); + self.push(Number(-n)); } _ => { return Err(OpcodeErr { @@ -189,7 +180,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ Opcode::Not => { let a = self.pop()?; match a { - Bool(b) => self.push(Bool(!(*b))), + Bool(b) => self.push(Bool(!b)), _ => { return Err(OpcodeErr { opcode: "Not".into(), @@ -279,10 +270,11 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let b = self.pop()?; let a = self.pop()?; - match (a, b) { - (Number(a), Array(arr)) => { + match (a, &b) { + (Number(a), Array(b)) => { + let arr = b.borrow(); let is_in = arr.iter().any(|b| match b { - Number(b) => a == b, + Number(b) => a == *b, _ => false, }); @@ -299,7 +291,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ (Number(l), Number(r)) => { let mut is_open = false; - let first = match interval.left_bracket { + let first = match interval.left_bracket.as_ref() { "[" => l <= v, "(" => l < v, "]" => { @@ -318,7 +310,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ } }; - let second = match interval.right_bracket { + let second = match interval.right_bracket.as_ref() { "]" => r >= v, ")" => r > v, "[" => r <= v, @@ -344,26 +336,30 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ } } } - (String(a), Array(arr)) => { + (String(a), Array(b)) => { + let arr = b.borrow(); let is_in = arr.iter().any(|b| match b { - String(b) => a == b, + String(b) => &a == b, _ => false, }); self.push(Bool(is_in)); } - (String(a), Object(obj)) => { - self.push(Bool(obj.contains_key(a))); + (String(a), Object(b)) => { + let obj = b.borrow(); + self.push(Bool(obj.contains_key(a.as_ref()))); } - (Bool(a), Array(arr)) => { + (Bool(a), Array(b)) => { + let arr = b.borrow(); let is_in = arr.iter().any(|b| match b { - Bool(b) => a == b, + Bool(b) => a == *b, _ => false, }); self.push(Bool(is_in)); } - (Null, Array(arr)) => { + (Null, Array(b)) => { + let arr = b.borrow(); let is_in = arr.iter().any(|b| match b { Null => true, _ => false, @@ -384,7 +380,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let a = self.pop()?; match (a, b) { - (Number(a), Number(b)) => self.push(Bool(*a < *b)), + (Number(a), Number(b)) => self.push(Bool(a < b)), _ => { return Err(OpcodeErr { opcode: "Less".into(), @@ -398,7 +394,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let a = self.pop()?; match (a, b) { - (Number(a), Number(b)) => self.push(Bool(*a > *b)), + (Number(a), Number(b)) => self.push(Bool(a > b)), _ => { return Err(OpcodeErr { opcode: "More".into(), @@ -412,7 +408,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let a = self.pop()?; match (a, b) { - (Number(a), Number(b)) => self.push(Bool(*a <= *b)), + (Number(a), Number(b)) => self.push(Bool(a <= b)), _ => { return Err(OpcodeErr { opcode: "LessOrEqual".into(), @@ -426,7 +422,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let a = self.pop()?; match (a, b) { - (Number(a), Number(b)) => self.push(Bool(*a >= *b)), + (Number(a), Number(b)) => self.push(Bool(a >= b)), _ => { return Err(OpcodeErr { opcode: "MoreOrEqual".into(), @@ -511,8 +507,9 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let var = self.pop()?; match var { - Array(arr) => { + Array(a) => { let mut sum = Decimal::ZERO; + let arr = a.borrow(); arr.iter().try_for_each(|a| match a { Number(a) => { sum += a; @@ -536,17 +533,18 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ } } Opcode::Median => { - let Array(arr) = self.pop()? else { + let Array(a) = self.pop()? else { return Err(OpcodeErr { opcode: "Median".into(), message: "Unsupported type".into(), }); }; + let arr = a.borrow(); let mut num_arr = arr .iter() .map(|n| match n { - Number(num) => Ok(num), + Number(num) => Ok(*num), _ => Err(OpcodeErr { opcode: "Median".into(), message: "Unsupported type".into(), @@ -570,7 +568,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ message: "Array out of bounds".into(), })?; - self.push(Number(**center_num)); + self.push(Number(*center_num)); } else { let center_left = num_arr.get(center - 1).ok_or_else(|| OpcodeErr { opcode: "Median".into(), @@ -582,22 +580,23 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ message: "Array out of bounds".into(), })?; - let median = ((**center_left) + (**center_right)) / dec!(2); + let median = ((*center_left) + (*center_right)) / dec!(2); self.push(Number(median)); } } Opcode::Mode => { - let Array(arr) = self.pop()? else { + let Array(a) = self.pop()? else { return Err(OpcodeErr { opcode: "Mode".into(), message: "Unsupported type".into(), }); }; + let arr = a.borrow(); let num_arr = arr .iter() .map(|n| match n { - Number(num) => Ok(num), + Number(num) => Ok(*num), _ => Err(OpcodeErr { opcode: "Mode".into(), message: "Unsupported type".into(), @@ -614,7 +613,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let mut map = HashMap::new(); num_arr.iter().for_each(|n| { - let count = map.entry(**n).or_insert(0); + let count = map.entry(*n).or_insert(0); *count += 1; }); @@ -634,7 +633,8 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let var = self.pop()?; match var { - Array(arr) => { + Array(a) => { + let arr = a.borrow(); let first_item = arr.get(0).ok_or_else(|| OpcodeErr { opcode: "Min".into(), message: "Empty array".into(), @@ -677,7 +677,8 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let var = self.pop()?; match var { - Array(arr) => { + Array(a) => { + let arr = a.borrow(); let first_item = arr.get(0).ok_or_else(|| OpcodeErr { opcode: "Max".into(), message: "Empty array".into(), @@ -720,8 +721,9 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let var = self.pop()?; match var { - Array(arr) => { + Array(a) => { let mut sum = Decimal::ZERO; + let arr = a.borrow(); arr.iter().try_for_each(|a| match a { Number(a) => { sum += a; @@ -750,12 +752,12 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match (a, b) { (Number(a), Number(b)) => self.push(Number(a + b)), (String(a), String(b)) => { - let mut str1 = - BumpString::with_capacity_in(a.len() + b.len(), self.bump); - str1.push_str(a); - str1.push_str(b); + let mut c = StdString::with_capacity(a.len() + b.len()); - self.push(String(str1.into_bump_str())); + c.push_str(a.as_ref()); + c.push_str(b.as_ref()); + + self.push(String(Rc::from(c.as_str()))); } _ => { return Err(OpcodeErr { @@ -827,7 +829,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match (a, b) { (Number(a), Number(b)) => { - self.push(Number(a.powd(*b))); + self.push(Number(a.powd(b))); } _ => { return Err(OpcodeErr { @@ -844,16 +846,16 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let b = self.pop()?; let a = self.pop()?; - match (a, b) { + match (&a, &b) { (Number(_), Number(_)) => { let interval = IntervalObject { - left_bracket, - right_bracket, + left_bracket: Rc::from(*left_bracket), + right_bracket: Rc::from(*right_bracket), left: a, right: b, }; - self.push(interval.to_variable(self.bump).unwrap()); + self.push(interval.to_variable()); } _ => { return Err(OpcodeErr { @@ -868,8 +870,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match a { String(a) => { - let str = a.to_uppercase(); - self.push(String(self.bump.alloc_str(&str))); + self.push(String(a.to_uppercase().into())); } _ => { return Err(OpcodeErr { @@ -883,10 +884,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let a = self.pop()?; match a { - String(a) => { - let str = a.to_lowercase(); - self.push(String(self.bump.alloc_str(&str))); - } + String(a) => self.push(String(a.to_lowercase().into())), _ => { return Err(OpcodeErr { opcode: "Lowercase".into(), @@ -899,51 +897,52 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let b = self.pop()?; let a = self.pop()?; - match (a, b) { + match (a, &b) { (String(a), String(b)) => { - self.push(Bool(a.contains(b))); + self.push(Bool(a.contains(b.as_ref()))); } - _ => match a { - Array(arr) => { - let is_in = arr.iter().any(|a| match (a, b) { - (Number(a), Number(b)) => a == b, - (String(a), String(b)) => a == b, - (Bool(a), Bool(b)) => a == b, - (Null, Null) => true, - _ => false, - }); + (Array(a), _) => { + let arr = a.borrow(); + let is_in = arr.iter().any(|a| match (a, &b) { + (Number(a), Number(b)) => a == b, + (String(a), String(b)) => a == b, + (Bool(a), Bool(b)) => a == b, + (Null, Null) => true, + _ => false, + }); - self.push(Bool(is_in)); - } - _ => { - return Err(OpcodeErr { - opcode: "Contains".into(), - message: "Unsupported type".into(), - }); - } - }, + self.push(Bool(is_in)); + } + _ => { + return Err(OpcodeErr { + opcode: "Contains".into(), + message: "Unsupported type".into(), + }); + } } } Opcode::Keys => { let current = self.pop()?; match current { - Array(arr) => { - let indices: BumpVec = arr + Array(a) => { + let arr = a.borrow(); + let indices = arr .iter() .enumerate() .map(|(index, _)| Number(index.into())) - .collect_in(self.bump); + .collect(); - self.push(Array(indices)); + self.push(Array(Rc::new(RefCell::new(indices)))); } - Object(obj) => { - let keys: BumpVec = obj + Object(a) => { + let obj = a.borrow(); + let keys = obj .iter() - .map(|(key, _)| String(self.bump.alloc_str(key))) - .collect_in(self.bump); + .map(|(key, _)| String(Rc::from(key.as_str()))) + .collect(); - self.push(Array(keys)); + self.push(Array(Rc::new(RefCell::new(keys)))); } _ => { return Err(OpcodeErr { @@ -957,13 +956,11 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let current = self.pop()?; match current { - Object(obj) => { - let values: BumpVec = obj - .iter() - .map(|(_, v)| v.clone_in(self.bump)) - .collect_in(self.bump); + Object(a) => { + let obj = a.borrow(); + let values: Vec = obj.values().cloned().collect(); - self.push(Array(values)); + self.push(Array(Rc::new(RefCell::new(values)))); } _ => { return Err(OpcodeErr { @@ -979,7 +976,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match (a, b) { (String(a), String(b)) => { - self.push(Bool(a.starts_with(b))); + self.push(Bool(a.starts_with(b.as_ref()))); } _ => { return Err(OpcodeErr { @@ -995,7 +992,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match (a, b) { (String(a), String(b)) => { - self.push(Bool(a.ends_with(b))); + self.push(Bool(a.ends_with(b.as_ref()))); } _ => { return Err(OpcodeErr { @@ -1016,12 +1013,12 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ }); }; - let regex = Regex::new(b).map_err(|_| OpcodeErr { + let regex = Regex::new(b.as_ref()).map_err(|_| OpcodeErr { opcode: "Matches".into(), message: "Invalid regular expression".into(), })?; - self.push(Bool(regex.is_match(a))); + self.push(Bool(regex.is_match(a.as_ref()))); } Opcode::FuzzyMatch => { let b = self.pop()?; @@ -1036,12 +1033,14 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match a { String(a) => { - let sim = strsim::normalized_damerau_levenshtein(a, b); + let sim = + strsim::normalized_damerau_levenshtein(a.as_ref(), b.as_ref()); // This is okay, as NDL will return [0, 1] self.push(Number(Decimal::from_f64(sim).unwrap_or(dec!(0)))); } - Array(a) => { - let mut sims = BumpVec::with_capacity_in(a.len(), &self.bump); + Array(_a) => { + let a = _a.borrow(); + let mut sims = Vec::with_capacity(a.len()); for v in a.iter() { let String(s) = v else { return Err(OpcodeErr { @@ -1050,13 +1049,14 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ }); }; - let sim = - Decimal::from_f64(strsim::normalized_damerau_levenshtein(s, b)) - .unwrap_or(dec!(0)); + let sim = Decimal::from_f64( + strsim::normalized_damerau_levenshtein(s.as_ref(), b.as_ref()), + ) + .unwrap_or(dec!(0)); sims.push(Number(sim)); } - self.push(Array(sims)) + self.push(Variable::from_array(sims)) } _ => { return Err(OpcodeErr { @@ -1072,14 +1072,13 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ match (a, b) { (String(a), String(b)) => { - let arr = BumpVec::from_iter_in( - a.split(b) + let arr = Vec::from_iter( + a.split(b.as_ref()) .into_iter() - .map(|s| String(self.bump.alloc_str(s))), - self.bump, + .map(|s| String(s.to_string().into())), ); - self.push(Array(arr)); + self.push(Variable::from_array(arr)); } _ => { return Err(OpcodeErr { @@ -1093,18 +1092,19 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let b = self.pop()?; let a = self.pop()?; - let (Array(arr), String(separator)) = (a, b) else { + let (Array(a), String(separator)) = (a, &b) else { return Err(OpcodeErr { opcode: "Join".into(), message: "Unsupported type".into(), }); }; + let arr = a.borrow(); let parts = arr .iter() .enumerate() .map(|(i, var)| match var { - String(str) => Ok(*str), + String(str) => Ok(str.clone()), _ => Err(OpcodeErr { opcode: "Join".into(), message: format!("Unexpected type in array on index {i}"), @@ -1116,16 +1116,16 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ .iter() .fold(separator.len() * (parts.len() - 1), |acc, s| acc + s.len()); - let mut s = BumpString::with_capacity_in(str_capacity, self.bump); - let mut it = parts.iter().peekable(); + let mut s = StdString::with_capacity(str_capacity); + let mut it = parts.into_iter().peekable(); while let Some(part) = it.next() { - s.push_str(part); + s.push_str(part.as_ref()); if it.peek().is_some() { s.push_str(separator); } } - self.push(String(s.into_bump_str())); + self.push(String(Rc::from(s))); } Opcode::Extract => { let b = self.pop()?; @@ -1138,29 +1138,29 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ }); }; - let regex = Regex::new(b).map_err(|_| OpcodeErr { + let regex = Regex::new(b.as_ref()).map_err(|_| OpcodeErr { opcode: "Matches".into(), message: "Invalid regular expression".into(), })?; let captures = regex - .captures(a) + .captures(a.as_ref()) .map(|capture| { capture .iter() .map(|c| c.map(|c| c.as_str())) .filter_map(|c| c) - .map(|s| String(self.bump.alloc_str(s))) - .collect_in::>(self.bump) + .map(|s| String(Rc::from(s))) + .collect() }) - .unwrap_or_else(|| BumpVec::new_in(self.bump)); + .unwrap_or_default(); - self.push(Array(captures)); + self.push(Variable::from_array(captures)); } Opcode::DateManipulation(operation) => { let timestamp = self.pop()?; - let time: NaiveDateTime = timestamp.try_into()?; + let time: NaiveDateTime = (×tamp).try_into()?; let var = match *operation { "year" => Number(time.year().into()), "dayOfWeek" => Number(time.weekday().number_from_monday().into()), @@ -1168,11 +1168,9 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ "dayOfYear" => Number(time.ordinal().into()), "weekOfYear" => Number(time.iso_week().week().into()), "monthOfYear" => Number(time.month().into()), - "monthString" => { - String(self.bump.alloc_str(&time.format("%b").to_string())) - } - "weekdayString" => String(self.bump.alloc_str(&time.weekday().to_string())), - "dateString" => String(self.bump.alloc_str(&time.to_string())), + "monthString" => String(Rc::from(time.format("%b").to_string())), + "weekdayString" => String(Rc::from(time.weekday().to_string())), + "dateString" => String(Rc::from(time.to_string())), _ => { return Err(OpcodeErr { opcode: "DateManipulation".into(), @@ -1187,8 +1185,8 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let unit_var = self.pop()?; let timestamp = self.pop()?; - let date_time: NaiveDateTime = timestamp.try_into()?; - let String(unit_name) = *unit_var else { + let date_time: NaiveDateTime = (×tamp).try_into()?; + let String(unit_name) = unit_var else { return Err(OpcodeErr { opcode: "DateFunction".into(), message: "Unknown date function".into(), @@ -1196,8 +1194,8 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ }; let s = match *name { - "startOf" => date_time_start_of(date_time, unit_name.try_into()?), - "endOf" => date_time_end_of(date_time, unit_name.try_into()?), + "startOf" => date_time_start_of(date_time, unit_name.as_ref().try_into()?), + "endOf" => date_time_end_of(date_time, unit_name.as_ref().try_into()?), _ => { return Err(OpcodeErr { opcode: "DateManipulation".into(), @@ -1230,16 +1228,14 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ })?; match current { - Array(arr) => { + Array(a) => { + let arr = a.borrow(); let slice = arr.get(from..=to).ok_or_else(|| OpcodeErr { opcode: "Slice".into(), message: "Index out of range".into(), })?; - self.push(Array(BumpVec::from_iter_in( - slice.iter().map(|v| v.clone_in(self.bump)), - self.bump, - ))); + self.push(Variable::from_array(slice.to_vec())); } String(s) => { let slice = s.get(from..=to).ok_or_else(|| OpcodeErr { @@ -1247,7 +1243,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ message: "Index out of range".into(), })?; - self.push(String(slice)); + self.push(String(Rc::from(slice))); } _ => { return Err(OpcodeErr { @@ -1279,13 +1275,13 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ message: "Failed to extract argument".into(), })?; - let mut arr = BumpVec::with_capacity_in(to, &self.bump); + let mut arr = Vec::with_capacity(to); for _ in 0..to { - arr.push(self.pop()?.clone_in(self.bump)); + arr.push(self.pop()?); } arr.reverse(); - self.push(Array(arr)); + self.push(Variable::from_array(arr)); } Opcode::Object => { let size = self.pop()?; @@ -1301,7 +1297,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ message: "Failed to extract argument".into(), })?; - let mut map = BumpMap::with_capacity_in(to, &self.bump); + let mut map = HashMap::with_capacity(to); for _ in 0..to { let value = self.pop()?; let String(key) = self.pop()? else { @@ -1311,10 +1307,10 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ }); }; - map.insert(&*self.bump.alloc_str(key), value.clone_in(self.bump)); + map.insert(key.to_string(), value); } - self.push(Object(map)); + self.push(Variable::from_object(map)); } Opcode::Len => { let current = self.stack.last().ok_or_else(|| OpcodeErr { @@ -1324,7 +1320,10 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let len = match current { String(s) => s.len(), - Array(s) => s.len(), + Array(s) => { + let arr = s.borrow(); + arr.len() + } _ => { return Err(OpcodeErr { opcode: "Len".into(), @@ -1337,30 +1336,31 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ } Opcode::Flatten => { let current = self.pop()?; - let Array(arr) = current else { + let Array(a) = current else { return Err(OpcodeErr { opcode: "Flatten".into(), message: "Unsupported type".into(), }); }; - let mut flat_arr = BumpVec::new_in(&self.bump); - flat_arr.reserve(arr.len()); + let arr = a.borrow(); + let mut flat_arr = Vec::with_capacity(arr.len()); arr.iter().for_each(|v| match v { - Array(arr) => arr - .iter() - .for_each(|v| flat_arr.push(v.clone_in(self.bump))), - _ => flat_arr.push(v.clone_in(self.bump)), + Array(b) => { + let arr = b.borrow(); + arr.iter().for_each(|v| flat_arr.push(v.clone())) + } + _ => flat_arr.push(v.clone()), }); - self.push(Array(flat_arr)); + self.push(Variable::from_array(flat_arr)); } Opcode::ParseDateTime => { let a = self.pop()?; let ts = match a { #[allow(deprecated)] - String(a) => date_time(a)?.timestamp(), + String(a) => date_time(a.as_ref())?.timestamp(), Number(a) => a.to_i64().ok_or_else(|| OpcodeErr { opcode: "ParseDateTime".into(), message: "Number overflow".into(), @@ -1378,7 +1378,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ Opcode::ParseTime => { let a = self.pop()?; let ts = match a { - String(a) => time(a)?.num_seconds_from_midnight(), + String(a) => time(a.as_ref())?.num_seconds_from_midnight(), Number(a) => a.to_u32().ok_or_else(|| OpcodeErr { opcode: "ParseTime".into(), message: "Number overflow".into(), @@ -1397,7 +1397,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let a = self.pop()?; let dur = match a { - String(a) => humantime::parse_duration(a) + String(a) => humantime::parse_duration(a.as_ref()) .map_err(|_| ParseDateTimeErr { timestamp: a.to_string(), })? @@ -1421,7 +1421,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ let is_equal = match (check, var) { (TypeCheckKind::Numeric, String(str)) => { - Decimal::from_str_exact(str).is_ok() + Decimal::from_str_exact(str.as_ref()).is_ok() } (TypeCheckKind::Numeric, Number(_)) => true, (TypeCheckKind::Numeric, _) => false, @@ -1432,17 +1432,15 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ Opcode::TypeConversion(conversion) => { let var = self.pop()?; - let make_string = |val: &str| self.bump.alloc(String(self.bump.alloc_str(val))); - - let converted_var = match (conversion, var) { + let converted_var = match (conversion, &var) { (TypeConversionKind::String, String(_)) => var, (TypeConversionKind::String, Number(num)) => { - make_string(num.to_string().as_str()) + String(Rc::from(num.to_string().as_str())) } (TypeConversionKind::String, Bool(v)) => { - make_string(v.to_string().as_str()) + String(Rc::from(v.to_string().as_str())) } - (TypeConversionKind::String, Null) => make_string("null"), + (TypeConversionKind::String, Null) => String(Rc::from("null")), (TypeConversionKind::String, _) => { return Err(OpcodeErr { opcode: "TypeConversion".into(), @@ -1459,12 +1457,12 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ message: "Failed to parse string to number".into(), })?; - self.bump.alloc(Number(parsed_number)) + Number(parsed_number) } (TypeConversionKind::Number, Number(_)) => var, (TypeConversionKind::Number, Bool(v)) => { let number = if *v { dec!(1) } else { dec!(0) }; - self.bump.alloc(Number(number)) + Number(number) } (TypeConversionKind::Number, _) => { return Err(OpcodeErr { @@ -1475,9 +1473,7 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ ), }); } - (TypeConversionKind::Bool, Number(n)) => { - self.bump.alloc(Bool(!n.is_zero())) - } + (TypeConversionKind::Bool, Number(n)) => Bool(!n.is_zero()), (TypeConversionKind::Bool, String(s)) => { let value = match (*s).trim() { "true" => true, @@ -1485,20 +1481,18 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ _ => s.is_empty(), }; - self.bump.alloc(Bool(value)) + Bool(value) } (TypeConversionKind::Bool, Bool(_)) => var, - (TypeConversionKind::Bool, Null) => self.bump.alloc(Bool(false)), - (TypeConversionKind::Bool, Object(_) | Array(_)) => { - self.bump.alloc(Bool(true)) - } + (TypeConversionKind::Bool, Null) => Bool(false), + (TypeConversionKind::Bool, Object(_) | Array(_)) => Bool(true), }; - self.push_ref(converted_var); + self.push(converted_var); } Opcode::GetType => { let var = self.pop()?; - self.push(String(var.type_name())); + self.push(String(Rc::from(var.type_name()))); } Opcode::JumpIfEnd(j) => { let scope = self.scopes.last().ok_or_else(|| OpcodeErr { @@ -1548,14 +1542,17 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ message: "Empty scope".into(), })?; - match scope.array { - Array(arr) => { - let variable = arr.get(scope.iter).ok_or_else(|| OpcodeErr { - opcode: "Pointer".into(), - message: "Scope array out of bounds".into(), - })?; + match &scope.array { + Array(a) => { + let a_cloned = a.clone(); + let arr = a_cloned.borrow(); + let variable = + arr.get(scope.iter).cloned().ok_or_else(|| OpcodeErr { + opcode: "Pointer".into(), + message: "Scope array out of bounds".into(), + })?; - self.push_ref(variable); + self.push(variable); } _ => { return Err(OpcodeErr { @@ -1567,18 +1564,26 @@ impl<'arena, 'parent_ref, 'bytecode_ref> VMInner<'arena, 'parent_ref, 'bytecode_ } Opcode::Begin => { let a = self.pop()?; - match a { - Array(arr) => self.scopes.push(Scope { + let arr_len = match &a { + Array(a) => { + let arr = a.borrow(); + Some(arr.len()) + } + _ => None, + }; + + match arr_len { + Some(len) => self.scopes.push(Scope { array: a, count: 0, - len: arr.len(), + len, iter: 0, }), - _ => { + None => { return Err(OpcodeErr { opcode: "Begin".into(), message: "Unsupported type".into(), - }); + }) } } } diff --git a/core/expression/tests/isolate.rs b/core/expression/tests/isolate.rs index 57ca3e2d..8c553955 100644 --- a/core/expression/tests/isolate.rs +++ b/core/expression/tests/isolate.rs @@ -1,10 +1,9 @@ use std::ops::Index; use anyhow::Context; -use bumpalo::Bump; use serde_json::{json, Value}; -use zen_expression::variable::ToVariable; +use zen_expression::variable::Variable; use zen_expression::Isolate; struct TestEnv { @@ -601,7 +600,7 @@ fn isolate_standard_test() { let mut isolate = Isolate::new(); for TestEnv { env, cases } in tests { - isolate.set_environment(&env); + isolate.set_environment(env.into()); for TestCase { expr, result } in cases { let isolate_result = isolate.run_standard(expr); @@ -614,7 +613,7 @@ fn isolate_standard_test() { continue; }; - assert_eq!(result, response, "{}", expr); + assert_eq!(Variable::from(result), response, "{}", expr); } } } @@ -725,7 +724,7 @@ fn isolate_unary_tests() { reference, } in tests { - isolate.set_environment(&env); + isolate.set_environment(env.into()); isolate.set_reference(reference).unwrap(); for TestCase { expr, result } in cases { @@ -734,25 +733,12 @@ fn isolate_unary_tests() { } } -#[test] -fn variable_serde_test() { - let env = json!({ - "customer": { - "groups": ["admin", "user"], - "purchaseAmounts": [100, 200, 400, 800] - }, - }); - - let bump = Bump::new(); - let _ = env.to_variable(&bump); -} - #[test] fn isolate_test_decimals() { let mut isolate = Isolate::new(); let result = isolate.run_standard("9223372036854775807").unwrap(); - assert_eq!(result, Value::from(9223372036854775807i64)); + assert_eq!(result.to_value(), Value::from(9223372036854775807i64)); } #[test] @@ -777,7 +763,7 @@ fn test_standard_csv() { let mut isolate = Isolate::new(); if !input_str.is_empty() { let input: Value = serde_json5::from_str(input_str).unwrap(); - isolate.set_environment(&input); + isolate.set_environment(input.into()); } let maybe_result = isolate @@ -786,9 +772,10 @@ fn test_standard_csv() { assert!(maybe_result.is_ok(), "{}", maybe_result.unwrap_err()); let result = maybe_result.unwrap(); + let var_output = Variable::from(output); assert_eq!( - result, output, - "Expression {expression}. Expected: {output}, got: {result}" + result, var_output, + "Expression {expression}. Expected: {var_output}, got: {result}" ); } } @@ -815,7 +802,7 @@ fn test_unary_csv() { let mut isolate = Isolate::new(); if !input_str.is_empty() { let input: Value = serde_json5::from_str(input_str).unwrap(); - isolate.set_environment(&input); + isolate.set_environment(input.into()); } let result = isolate diff --git a/core/expression_repl/src/main.rs b/core/expression_repl/src/main.rs index 8f3e6935..fc9b219c 100644 --- a/core/expression_repl/src/main.rs +++ b/core/expression_repl/src/main.rs @@ -1,22 +1,23 @@ use colored::Colorize; use rustyline::config::Configurer; use rustyline::{DefaultEditor, Result}; -use serde_json::{json, Value}; +use serde_json::json; -use zen_expression::Isolate; +use zen_expression::{Isolate, Variable}; trait PrettyPrint { fn pretty_print(&self) -> String; } -impl PrettyPrint for Value { +impl PrettyPrint for Variable { fn pretty_print(&self) -> String { match &self { - Value::Number(num) => format!("{}", num.to_string().yellow()), - Value::String(str) => format!("{}", format!("'{}'", str).green()), - Value::Bool(b) => format!("{}", b.to_string().yellow()), - Value::Null => format!("{}", "null".bold()), - Value::Array(arr) => { + Variable::Number(num) => format!("{}", num.to_string().yellow()), + Variable::String(str) => format!("{}", format!("'{}'", str).green()), + Variable::Bool(b) => format!("{}", b.to_string().yellow()), + Variable::Null => format!("{}", "null".bold()), + Variable::Array(a) => { + let arr = a.borrow(); let elements = arr .iter() .map(|i| i.pretty_print()) @@ -24,7 +25,8 @@ impl PrettyPrint for Value { .join(", "); format!("[{}]", elements) } - Value::Object(map) => { + Variable::Object(m) => { + let map = m.borrow(); let elements = map .iter() .map(|(key, value)| format!("{}: {}", key, value.pretty_print())) @@ -49,7 +51,7 @@ fn main() -> Result<()> { let mut isolate = Isolate::new(); isolate.set_environment( - &json!({ "customer": { "firstName": "John", "lastName": "Doe", "age": 20 }, "hello": true, "$": 10 }), + json!({ "customer": { "firstName": "John", "lastName": "Doe", "age": 20 }, "hello": true, "$": 10 }).into(), ); let result = isolate.run_standard(line.as_str()); diff --git a/core/template/src/interpreter.rs b/core/template/src/interpreter.rs index 5cfd27de..433a2b5a 100644 --- a/core/template/src/interpreter.rs +++ b/core/template/src/interpreter.rs @@ -1,16 +1,16 @@ use std::iter::Peekable; +use std::rc::Rc; use std::slice::Iter; use crate::error::TemplateRenderError; -use serde_json::Value; -use zen_expression::Isolate; - use crate::parser::Node; +use zen_expression::variable::Variable; +use zen_expression::Isolate; #[derive(Debug, PartialEq)] pub(crate) enum InterpreterResult<'a> { String(&'a str), - Value(Value), + Variable(Variable), } #[derive(Debug)] @@ -37,7 +37,10 @@ where } impl<'source, 'nodes> Interpreter<'source, 'nodes> { - pub(crate) fn collect_for(mut self, context: &Value) -> Result { + pub(crate) fn collect_for( + mut self, + context: Variable, + ) -> Result { self.isolate.set_environment(context); while let Some(node) = self.cursor.next() { @@ -48,12 +51,12 @@ impl<'source, 'nodes> Interpreter<'source, 'nodes> { } match self.results.len() { - 0 => Ok(Value::Null), + 0 => Ok(Variable::Null), 1 => { let item = self.results.remove(0); match item { - InterpreterResult::Value(val) => Ok(val), - InterpreterResult::String(str) => Ok(Value::String(str.to_string())), + InterpreterResult::Variable(val) => Ok(val), + InterpreterResult::String(str) => Ok(Variable::String(Rc::from(str))), } } _ => { @@ -62,11 +65,11 @@ impl<'source, 'nodes> Interpreter<'source, 'nodes> { .into_iter() .map(|item| match item { InterpreterResult::String(str) => str.to_string(), - InterpreterResult::Value(value) => value_to_string(value), + InterpreterResult::Variable(value) => var_to_string(value), }) .collect::(); - Ok(Value::String(string_data)) + Ok(Variable::String(Rc::from(string_data.as_str()))) } } } @@ -77,21 +80,14 @@ impl<'source, 'nodes> Interpreter<'source, 'nodes> { fn expression(&mut self, data: &'source str) -> Result<(), TemplateRenderError> { let result = self.isolate.run_standard(data)?; - self.results.push(InterpreterResult::Value(result)); + self.results.push(InterpreterResult::Variable(result)); Ok(()) } } -fn value_to_string(value: Value) -> String { - match value { - Value::Null => String::from("null"), - Value::Bool(b) => match b { - true => String::from("true"), - false => String::from("false"), - }, - Value::Number(n) => n.to_string(), - Value::String(s) => s, - Value::Array(arr) => Value::Array(arr).to_string(), - Value::Object(obj) => Value::Object(obj).to_string(), +fn var_to_string(var: Variable) -> String { + match var { + Variable::String(s) => s.to_string(), + _ => var.to_string(), } } diff --git a/core/template/src/lib.rs b/core/template/src/lib.rs index f08dc266..9f142c06 100644 --- a/core/template/src/lib.rs +++ b/core/template/src/lib.rs @@ -3,15 +3,14 @@ mod interpreter; mod lexer; mod parser; -use serde_json::Value; - use crate::interpreter::Interpreter; use crate::lexer::Lexer; use crate::parser::Parser; +use zen_expression::variable::Variable; pub use crate::error::{ParserError, TemplateRenderError}; -pub fn render(template: &str, context: &Value) -> Result { +pub fn render(template: &str, context: Variable) -> Result { let tokens = Lexer::from(template.trim()).collect(); let nodes = Parser::from(tokens.as_slice()).collect()?; diff --git a/core/template/tests/template.rs b/core/template/tests/template.rs index 30b676ae..3c37411f 100644 --- a/core/template/tests/template.rs +++ b/core/template/tests/template.rs @@ -1,4 +1,5 @@ use serde_json::{json, Value}; +use zen_expression::variable::Variable; use zen_tmpl::render; #[test] @@ -44,8 +45,8 @@ fn test_values_types() { for test_case in test_cases { assert_eq!( - render(test_case.template, &test_case.context).unwrap(), - test_case.expected + render(test_case.template, test_case.context.into()).unwrap(), + Variable::from(test_case.expected) ); } } @@ -84,17 +85,12 @@ fn test_interpolation() { context: json!(null), expected: json!("[1,2,3] array"), }, - TestCase { - template: "Customer: {{ customer }}", - context: json!({ "customer": { "firstName": "John", "lastName": "Doe" } }), - expected: json!(r#"Customer: {"firstName":"John","lastName":"Doe"}"#), - }, ]; for test_case in test_cases { assert_eq!( - render(test_case.template, &test_case.context).unwrap(), - test_case.expected + render(test_case.template, test_case.context.into()).unwrap(), + Variable::from(test_case.expected) ); } }