use super::compiled::CompiledLogic;
use super::config::RLogicConfig;
use index::TableIndex;
use serde_json::Value;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::sync::RwLock;
pub mod arithmetic;
pub mod array_lookup;
pub mod array_ops;
pub mod comparison;
pub mod date_ops;
pub mod helpers;
pub mod index;
pub mod logical;
pub mod math_ops;
pub mod optimizations;
pub mod string_ops;
pub mod types;
pub use helpers::*;
pub use types::*;
pub(crate) struct TableScope {
pub path: String,
pub rows: *const Vec<Value>,
pub current_row: Option<usize>,
}
unsafe impl Send for TableScope {}
unsafe impl Send for Evaluator {}
unsafe impl Sync for Evaluator {}
pub struct TableScopeGuard<'a> {
evaluator: &'a Evaluator,
}
impl<'a> Drop for TableScopeGuard<'a> {
fn drop(&mut self) {
unsafe {
*self.evaluator.table_scope.get() = None;
}
}
}
pub struct Evaluator {
config: RLogicConfig,
indices: RwLock<HashMap<String, TableIndex>>,
static_arrays: Option<std::sync::Arc<indexmap::IndexMap<String, std::sync::Arc<Value>>>>,
pub(crate) table_scope: UnsafeCell<Option<TableScope>>,
}
impl Evaluator {
pub fn new() -> Self {
Self {
config: RLogicConfig::default(),
indices: RwLock::new(HashMap::new()),
static_arrays: None,
table_scope: UnsafeCell::new(None),
}
}
pub(crate) fn enter_table_scope<'a>(
&'a self,
path: String,
rows: &Vec<Value>,
) -> TableScopeGuard<'a> {
unsafe {
*self.table_scope.get() = Some(TableScope {
path,
rows: rows as *const Vec<Value>,
current_row: None,
});
}
TableScopeGuard { evaluator: self }
}
pub(crate) fn update_table_scope_rows(&self, rows: &Vec<Value>) {
unsafe {
if let Some(ts) = (*self.table_scope.get()).as_mut() {
ts.rows = rows as *const Vec<Value>;
}
}
}
pub(crate) fn set_table_scope_row(&self, row_idx: Option<usize>) {
unsafe {
if let Some(ts) = (*self.table_scope.get()).as_mut() {
ts.current_row = row_idx;
}
}
}
pub fn with_config(mut self, config: RLogicConfig) -> Self {
self.config = config;
self
}
pub fn set_static_arrays(
&mut self,
static_arrays: std::sync::Arc<indexmap::IndexMap<String, std::sync::Arc<Value>>>,
) {
self.static_arrays = Some(static_arrays);
}
pub fn index_table(&self, name: &str, data: &Value) {
if let Some(index) = TableIndex::new(data) {
if let Ok(mut indices) = self.indices.write() {
indices.insert(name.to_string(), index);
}
}
}
pub fn clear_indices(&self) {
if let Ok(mut indices) = self.indices.write() {
indices.clear();
}
}
#[inline]
pub fn evaluate(&self, logic: &CompiledLogic, data: &Value) -> Result<Value, String> {
match logic {
CompiledLogic::Null => return Ok(Value::Null),
CompiledLogic::Bool(b) => return Ok(Value::Bool(*b)),
CompiledLogic::Number(n) => {
return Ok(self.f64_to_json(*n));
}
CompiledLogic::String(s) => return Ok(Value::String(s.clone())),
CompiledLogic::Var(name, None) if !name.is_empty() => {
return self.eval_var_or_default(name, &None, data, &Value::Null, 0);
}
CompiledLogic::Ref(path, None) if !path.is_empty() => {
return self.eval_var_or_default(path, &None, data, &Value::Null, 0);
}
CompiledLogic::Add(items) if items.len() <= 5 => {
if let Some(result) =
self.eval_arithmetic_fast(ArithOp::Add, items, data, &Value::Null)
{
return Ok(result);
}
}
CompiledLogic::Subtract(items) if items.len() <= 5 => {
if let Some(result) =
self.eval_arithmetic_fast(ArithOp::Sub, items, data, &Value::Null)
{
return Ok(result);
}
}
CompiledLogic::Multiply(items) if items.len() <= 5 => {
if let Some(result) =
self.eval_arithmetic_fast(ArithOp::Mul, items, data, &Value::Null)
{
return Ok(result);
}
}
CompiledLogic::Divide(items) if items.len() <= 5 => {
if let Some(result) =
self.eval_arithmetic_fast(ArithOp::Div, items, data, &Value::Null)
{
return Ok(result);
}
}
_ => {}
}
self.evaluate_with_context(logic, data, &Value::Null, 0)
}
#[inline]
pub fn evaluate_with_internal_context(
&self,
logic: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
) -> Result<Value, String> {
self.evaluate_with_context(logic, user_data, internal_context, 0)
}
fn evaluate_with_context(
&self,
logic: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
if depth > self.config.recursion_limit {
return Err("Recursion limit exceeded".to_string());
}
match logic {
CompiledLogic::Null => Ok(Value::Null),
CompiledLogic::Bool(b) => Ok(Value::Bool(*b)),
CompiledLogic::Number(n) => Ok(self.f64_to_json(*n)),
CompiledLogic::String(s) => Ok(Value::String(s.clone())),
CompiledLogic::Array(arr) => {
let results: Result<Vec<_>, _> = arr
.iter()
.map(|item| {
self.evaluate_with_context(item, user_data, internal_context, depth + 1)
})
.collect();
Ok(Value::Array(results?))
}
CompiledLogic::Var(name, default) => {
self.eval_var_or_default(name, default, user_data, internal_context, depth)
}
CompiledLogic::Ref(path, default) => {
self.eval_var_or_default(path, default, user_data, internal_context, depth)
}
CompiledLogic::And(items) => {
self.eval_and_or(items, true, user_data, internal_context, depth)
}
CompiledLogic::Or(items) => {
self.eval_and_or(items, false, user_data, internal_context, depth)
}
CompiledLogic::Not(expr) => {
let result =
self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
Ok(Value::Bool(!is_truthy(&result)))
}
CompiledLogic::If(cond, then_expr, else_expr) => {
let condition =
self.evaluate_with_context(cond, user_data, internal_context, depth + 1)?;
if is_truthy(&condition) {
self.evaluate_with_context(then_expr, user_data, internal_context, depth + 1)
} else {
self.evaluate_with_context(else_expr, user_data, internal_context, depth + 1)
}
}
CompiledLogic::Equal(a, b) => {
self.eval_binary_compare(CompOp::Eq, a, b, user_data, internal_context, depth)
}
CompiledLogic::StrictEqual(a, b) => {
self.eval_binary_compare(CompOp::StrictEq, a, b, user_data, internal_context, depth)
}
CompiledLogic::NotEqual(a, b) => {
self.eval_binary_compare(CompOp::Ne, a, b, user_data, internal_context, depth)
}
CompiledLogic::StrictNotEqual(a, b) => {
self.eval_binary_compare(CompOp::StrictNe, a, b, user_data, internal_context, depth)
}
CompiledLogic::LessThan(a, b) => {
self.eval_binary_compare(CompOp::Lt, a, b, user_data, internal_context, depth)
}
CompiledLogic::LessThanOrEqual(a, b) => {
self.eval_binary_compare(CompOp::Le, a, b, user_data, internal_context, depth)
}
CompiledLogic::GreaterThan(a, b) => {
self.eval_binary_compare(CompOp::Gt, a, b, user_data, internal_context, depth)
}
CompiledLogic::GreaterThanOrEqual(a, b) => {
self.eval_binary_compare(CompOp::Ge, a, b, user_data, internal_context, depth)
}
CompiledLogic::Add(items) => self.eval_array_fold(
items,
0.0,
|acc, n| Some(acc + n),
user_data,
internal_context,
depth,
),
CompiledLogic::Subtract(items) => {
if items.is_empty() {
return Ok(self.f64_to_json(0.0));
}
let first =
self.evaluate_with_context(&items[0], user_data, internal_context, depth + 1)?;
let mut result = to_f64(&first);
if items.len() == 1 {
return Ok(self.f64_to_json(-result));
}
for item in &items[1..] {
let val =
self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
result -= to_f64(&val);
}
Ok(self.f64_to_json(result))
}
CompiledLogic::Multiply(items) => {
if items.is_empty() {
return Ok(self.f64_to_json(0.0));
}
self.eval_array_fold(
items,
1.0,
|acc, n| Some(acc * n),
user_data,
internal_context,
depth,
)
}
CompiledLogic::Divide(items) => {
if items.is_empty() {
return Ok(self.f64_to_json(0.0_f64));
}
let first =
self.evaluate_with_context(&items[0], user_data, internal_context, depth + 1)?;
let mut result = to_f64(&first);
for item in &items[1..] {
let val =
self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
let divisor = to_f64(&val);
if divisor == 0.0 {
return Ok(Value::Null);
}
result /= divisor;
}
Ok(self.f64_to_json(result))
}
CompiledLogic::Modulo(a, b) => self.eval_binary_arith(
a,
b,
|a, b| if b == 0.0 { None } else { Some(a % b) },
user_data,
internal_context,
depth,
),
CompiledLogic::Power(a, b) => self.eval_binary_arith(
a,
b,
|a, b| Some(a.powf(b)),
user_data,
internal_context,
depth,
),
CompiledLogic::Map(array_expr, logic_expr) => {
self.eval_map(array_expr, logic_expr, user_data, internal_context, depth)
}
CompiledLogic::Filter(array_expr, logic_expr) => {
self.eval_filter(array_expr, logic_expr, user_data, internal_context, depth)
}
CompiledLogic::Reduce(array_expr, logic_expr, initial_expr) => self.eval_reduce(
array_expr,
logic_expr,
initial_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::All(array_expr, logic_expr) => self.eval_quantifier(
Quantifier::All,
array_expr,
logic_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Some(array_expr, logic_expr) => self.eval_quantifier(
Quantifier::Some,
array_expr,
logic_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::None(array_expr, logic_expr) => self.eval_quantifier(
Quantifier::None,
array_expr,
logic_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Merge(items) => {
self.eval_merge(items, user_data, internal_context, depth)
}
CompiledLogic::In(value_expr, array_expr) => {
self.eval_in(value_expr, array_expr, user_data, internal_context, depth)
}
CompiledLogic::Sum(array_expr, field_expr, threshold_expr) => self.eval_sum(
array_expr,
field_expr,
threshold_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::For(start_expr, end_expr, logic_expr) => self.eval_for(
start_expr,
end_expr,
logic_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Multiplies(items) => {
self.eval_multiplies(items, user_data, internal_context, depth)
}
CompiledLogic::Divides(items) => {
self.eval_divides(items, user_data, internal_context, depth)
}
CompiledLogic::ValueAt(table_expr, row_idx_expr, col_name_expr) => self.eval_valueat(
table_expr,
row_idx_expr,
col_name_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::MaxAt(table_expr, col_name_expr) => self.eval_maxat(
table_expr,
col_name_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::IndexAt(lookup_expr, table_expr, field_expr, range_expr) => self
.eval_indexat(
lookup_expr,
table_expr,
field_expr,
range_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Match(table_expr, conditions) => {
self.eval_match(table_expr, conditions, user_data, internal_context, depth)
}
CompiledLogic::MatchRange(table_expr, conditions) => {
self.eval_matchrange(table_expr, conditions, user_data, internal_context, depth)
}
CompiledLogic::Choose(table_expr, conditions) => {
self.eval_choose(table_expr, conditions, user_data, internal_context, depth)
}
CompiledLogic::FindIndex(table_expr, conditions) => {
self.eval_findindex(table_expr, conditions, user_data, internal_context, depth)
}
CompiledLogic::Cat(items) => {
self.concat_strings(items, user_data, internal_context, depth)
}
CompiledLogic::Substr(string_expr, start_expr, length_expr) => self.eval_substr(
string_expr,
start_expr,
length_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Search(find_expr, within_expr, start_expr) => self.eval_search(
find_expr,
within_expr,
start_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Left(text_expr, num_expr) => self.extract_text_side(
text_expr,
num_expr.as_deref(),
true,
user_data,
internal_context,
depth,
),
CompiledLogic::Right(text_expr, num_expr) => self.extract_text_side(
text_expr,
num_expr.as_deref(),
false,
user_data,
internal_context,
depth,
),
CompiledLogic::Mid(text_expr, start_expr, num_expr) => self.eval_mid(
text_expr,
start_expr,
num_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::SplitText(value_expr, sep_expr, index_expr) => self.eval_split_text(
value_expr,
sep_expr,
index_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Concat(items) => {
self.concat_strings(items, user_data, internal_context, depth)
}
CompiledLogic::SplitValue(string_expr, sep_expr) => {
self.eval_split_value(string_expr, sep_expr, user_data, internal_context, depth)
}
CompiledLogic::StringFormat(value_expr, decimals, prefix, suffix, thousands_sep) => {
self.eval_string_format(
value_expr,
decimals,
prefix,
suffix,
thousands_sep,
user_data,
internal_context,
depth,
)
}
CompiledLogic::Length(expr) => {
self.eval_length(expr, user_data, internal_context, depth)
}
CompiledLogic::Len(expr) => self.eval_len(expr, user_data, internal_context, depth),
CompiledLogic::Abs(expr) => {
self.eval_unary_math(expr, |n| n.abs(), user_data, internal_context, depth)
}
CompiledLogic::Max(items) => {
self.eval_min_max(items, true, user_data, internal_context, depth)
}
CompiledLogic::Min(items) => {
self.eval_min_max(items, false, user_data, internal_context, depth)
}
CompiledLogic::Pow(base_expr, exp_expr) => {
self.eval_pow(base_expr, exp_expr, user_data, internal_context, depth)
}
CompiledLogic::Round(expr, decimals) => {
self.apply_round(expr, decimals, 0, user_data, internal_context, depth)
}
CompiledLogic::RoundUp(expr, decimals) => {
self.apply_round(expr, decimals, 1, user_data, internal_context, depth)
}
CompiledLogic::RoundDown(expr, decimals) => {
self.apply_round(expr, decimals, 2, user_data, internal_context, depth)
}
CompiledLogic::Ceiling(expr, significance) => {
self.eval_ceiling(expr, significance, user_data, internal_context, depth)
}
CompiledLogic::Floor(expr, significance) => {
self.eval_floor(expr, significance, user_data, internal_context, depth)
}
CompiledLogic::Trunc(expr, decimals) => {
self.eval_trunc(expr, decimals, user_data, internal_context, depth)
}
CompiledLogic::Mround(value_expr, multiple_expr) => self.eval_mround(
value_expr,
multiple_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Today => self.eval_today(),
CompiledLogic::Now => self.eval_now(),
CompiledLogic::Days(end_expr, start_expr) => {
self.eval_days(end_expr, start_expr, user_data, internal_context, depth)
}
CompiledLogic::Year(expr) => {
self.extract_date_component(expr, "year", user_data, internal_context, depth)
}
CompiledLogic::Month(expr) => {
self.extract_date_component(expr, "month", user_data, internal_context, depth)
}
CompiledLogic::Day(expr) => {
self.extract_date_component(expr, "day", user_data, internal_context, depth)
}
CompiledLogic::Date(year_expr, month_expr, day_expr) => self.eval_date(
year_expr,
month_expr,
day_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::DateFormat(date_expr, format_expr) => {
self.eval_date_format(date_expr, format_expr, user_data, internal_context, depth)
}
CompiledLogic::YearFrac(start_expr, end_expr, basis_expr) => self.eval_year_frac(
start_expr,
end_expr,
basis_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::DateDif(start_expr, end_expr, unit_expr) => self.eval_date_dif(
start_expr,
end_expr,
unit_expr,
user_data,
internal_context,
depth,
),
CompiledLogic::Missing(keys) => {
let missing: Vec<_> = keys
.iter()
.filter(|key| self.is_key_missing(user_data, key))
.map(|k| Value::String(k.clone()))
.collect();
Ok(Value::Array(missing))
}
CompiledLogic::MissingSome(min_expr, keys) => {
let min_val =
self.evaluate_with_context(min_expr, user_data, internal_context, depth + 1)?;
let minimum = to_number(&min_val) as usize;
let present = keys
.iter()
.filter(|key| !self.is_key_missing(user_data, key))
.count();
if present >= minimum {
Ok(Value::Array(vec![]))
} else {
let missing: Vec<_> = keys
.iter()
.filter(|key| self.is_key_missing(user_data, key))
.map(|k| Value::String(k.clone()))
.collect();
Ok(Value::Array(missing))
}
}
CompiledLogic::Xor(a_expr, b_expr) => {
let a_val =
self.evaluate_with_context(a_expr, user_data, internal_context, depth + 1)?;
let b_val =
self.evaluate_with_context(b_expr, user_data, internal_context, depth + 1)?;
Ok(Value::Bool(is_truthy(&a_val) ^ is_truthy(&b_val)))
}
CompiledLogic::IfNull(cond_expr, alt_expr) => {
let cond_val =
self.evaluate_with_context(cond_expr, user_data, internal_context, depth + 1)?;
if is_null_like(&cond_val) {
self.evaluate_with_context(alt_expr, user_data, internal_context, depth + 1)
} else {
Ok(cond_val)
}
}
CompiledLogic::IsEmpty(expr) => {
let val =
self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
let empty = match &val {
Value::Null => true,
Value::String(s) => s.is_empty(),
_ => false,
};
Ok(Value::Bool(empty))
}
CompiledLogic::Empty => Ok(Value::String(String::new())),
CompiledLogic::RangeOptions(min_expr, max_expr) => {
let min_val =
self.evaluate_with_context(min_expr, user_data, internal_context, depth + 1)?;
let max_val =
self.evaluate_with_context(max_expr, user_data, internal_context, depth + 1)?;
let min = to_number(&min_val) as i32;
let max = to_number(&max_val) as i32;
if min > max {
return Ok(Value::Array(vec![]));
}
let options: Vec<Value> = (min..=max)
.map(|i| {
serde_json::json!({
"label": i.to_string(),
"value": i.to_string()
})
})
.collect();
Ok(Value::Array(options))
}
CompiledLogic::MapOptions(table_expr, label_expr, value_expr) => {
let table_val =
self.evaluate_with_context(table_expr, user_data, internal_context, depth + 1)?;
let label_val =
self.evaluate_with_context(label_expr, user_data, internal_context, depth + 1)?;
let value_val =
self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
if let (Value::Array(arr), Value::String(label_field), Value::String(value_field)) =
(&table_val, &label_val, &value_val)
{
let options: Vec<Value> = arr
.iter()
.filter_map(|row| {
row.as_object().and_then(|obj| {
Some(create_option(obj.get(label_field)?, obj.get(value_field)?))
})
})
.collect();
Ok(Value::Array(options))
} else {
Ok(Value::Array(vec![]))
}
}
CompiledLogic::MapOptionsIf(table_expr, label_expr, value_expr, conditions) => {
let table_val =
self.evaluate_with_context(table_expr, user_data, internal_context, depth + 1)?;
let label_val =
self.evaluate_with_context(label_expr, user_data, internal_context, depth + 1)?;
let value_val =
self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
if let (Value::Array(arr), Value::String(label_field), Value::String(value_field)) =
(&table_val, &label_val, &value_val)
{
let mut options = Vec::new();
for row in arr {
let obj = match row.as_object() {
Some(obj) => obj,
None => continue,
};
let mut all_match = true;
for condition in conditions {
let result =
self.evaluate_with_context(condition, row, user_data, depth + 1)?;
if !is_truthy(&result) {
all_match = false;
break;
}
}
if all_match {
if let (Some(label), Some(value)) =
(obj.get(label_field), obj.get(value_field))
{
options.push(create_option(label, value));
}
}
}
Ok(Value::Array(options))
} else {
Ok(Value::Array(vec![]))
}
}
CompiledLogic::Return(value) => {
Ok(value.as_ref().clone())
}
}
}
#[inline]
fn eval_var_or_default(
&self,
name: &str,
default: &Option<Box<CompiledLogic>>,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
if !name.is_empty() {
let scope = unsafe { &*self.table_scope.get() };
if let Some(ts) = scope.as_ref() {
if name == ts.path {
let rows = unsafe { &*ts.rows };
return Ok(Value::Array(rows.clone()));
}
}
}
let value = if name.is_empty() {
self.get_var(user_data, name)
} else {
self.get_var(internal_context, name)
.or_else(|| self.get_var(user_data, name))
};
match value {
Some(v) if !v.is_null() => Ok(v.clone()), _ => {
if let Some(def) = default {
self.evaluate_with_context(def, user_data, internal_context, depth + 1)
} else {
Ok(Value::Null)
}
}
}
}
#[inline(always)]
fn f64_to_json(&self, f: f64) -> Value {
helpers::f64_to_json(f, self.config.safe_nan_handling)
}
}
impl Default for Evaluator {
fn default() -> Self {
Self::new()
}
}