1use serde_json::Value;
2use super::compiled::CompiledLogic;
3use super::config::RLogicConfig;
4
5pub mod types;
6pub mod helpers;
7pub mod arithmetic;
8pub mod comparison;
9pub mod logical;
10pub mod array_ops;
11pub mod array_lookup;
12pub mod string_ops;
13pub mod math_ops;
14pub mod date_ops;
15pub mod optimizations;
16
17pub use types::*;
18pub use helpers::*;
19
20pub struct Evaluator {
31 config: RLogicConfig,
32}
33
34impl Evaluator {
35 pub fn new() -> Self {
36 Self {
37 config: RLogicConfig::default(),
38 }
39 }
40
41 pub fn with_config(mut self, config: RLogicConfig) -> Self {
42 self.config = config;
43 self
44 }
45
46 #[inline]
49 pub fn evaluate(&self, logic: &CompiledLogic, data: &Value) -> Result<Value, String> {
50 match logic {
52 CompiledLogic::Null => return Ok(Value::Null),
53 CompiledLogic::Bool(b) => return Ok(Value::Bool(*b)),
54 CompiledLogic::Number(n) => {
55 let f = n.parse::<f64>().unwrap_or(0.0);
56 return Ok(self.f64_to_json(f));
57 }
58 CompiledLogic::String(s) => return Ok(Value::String(s.clone())),
59 CompiledLogic::Var(name, None) if !name.is_empty() => {
60 return self.eval_var_or_default(name, &None, data, &Value::Null, 0);
62 }
63 CompiledLogic::Ref(path, None) if !path.is_empty() => {
64 return self.eval_var_or_default(path, &None, data, &Value::Null, 0);
66 }
67 CompiledLogic::Add(items) if items.len() <= 5 => {
69 if let Some(result) = self.eval_arithmetic_fast(ArithOp::Add, items, data, &Value::Null) {
70 return Ok(result);
71 }
72 }
73 CompiledLogic::Subtract(items) if items.len() <= 5 => {
74 if let Some(result) = self.eval_arithmetic_fast(ArithOp::Sub, items, data, &Value::Null) {
75 return Ok(result);
76 }
77 }
78 CompiledLogic::Multiply(items) if items.len() <= 5 => {
79 if let Some(result) = self.eval_arithmetic_fast(ArithOp::Mul, items, data, &Value::Null) {
80 return Ok(result);
81 }
82 }
83 CompiledLogic::Divide(items) if items.len() <= 5 => {
84 if let Some(result) = self.eval_arithmetic_fast(ArithOp::Div, items, data, &Value::Null) {
85 return Ok(result);
86 }
87 }
88 _ => {}
89 }
90
91 self.evaluate_with_context(logic, data, &Value::Null, 0)
93 }
94
95 #[inline]
106 pub fn evaluate_with_internal_context(
107 &self,
108 logic: &CompiledLogic,
109 user_data: &Value,
110 internal_context: &Value,
111 ) -> Result<Value, String> {
112 self.evaluate_with_context(logic, user_data, internal_context, 0)
113 }
114
115 fn evaluate_with_context(
123 &self,
124 logic: &CompiledLogic,
125 user_data: &Value,
126 internal_context: &Value,
127 depth: usize,
128 ) -> Result<Value, String> {
129 if depth > self.config.recursion_limit {
131 return Err("Recursion limit exceeded".to_string());
132 }
133
134 match logic {
135 CompiledLogic::Null => Ok(Value::Null),
137 CompiledLogic::Bool(b) => Ok(Value::Bool(*b)),
138 CompiledLogic::Number(n) => {
139 let f = n.parse::<f64>().unwrap_or(0.0);
140 Ok(self.f64_to_json(f))
141 }
142 CompiledLogic::String(s) => Ok(Value::String(s.clone())),
143 CompiledLogic::Array(arr) => {
144 let results: Result<Vec<_>, _> = arr
145 .iter()
146 .map(|item| self.evaluate_with_context(item, user_data, internal_context, depth + 1))
147 .collect();
148 Ok(Value::Array(results?))
149 }
150
151 CompiledLogic::Var(name, default) => {
153 self.eval_var_or_default(name, default, user_data, internal_context, depth)
154 }
155
156 CompiledLogic::Ref(path, default) => {
157 self.eval_var_or_default(path, default, user_data, internal_context, depth)
158 }
159
160 CompiledLogic::And(items) => self.eval_and_or(items, true, user_data, internal_context, depth),
162 CompiledLogic::Or(items) => self.eval_and_or(items, false, user_data, internal_context, depth),
163 CompiledLogic::Not(expr) => {
164 let result = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
165 Ok(Value::Bool(!is_truthy(&result)))
166 }
167 CompiledLogic::If(cond, then_expr, else_expr) => {
168 let condition = self.evaluate_with_context(cond, user_data, internal_context, depth + 1)?;
169 if is_truthy(&condition) {
170 self.evaluate_with_context(then_expr, user_data, internal_context, depth + 1)
171 } else {
172 self.evaluate_with_context(else_expr, user_data, internal_context, depth + 1)
173 }
174 }
175
176 CompiledLogic::Equal(a, b) => self.eval_binary_compare(CompOp::Eq, a, b, user_data, internal_context, depth),
178 CompiledLogic::StrictEqual(a, b) => self.eval_binary_compare(CompOp::StrictEq, a, b, user_data, internal_context, depth),
179 CompiledLogic::NotEqual(a, b) => self.eval_binary_compare(CompOp::Ne, a, b, user_data, internal_context, depth),
180 CompiledLogic::StrictNotEqual(a, b) => self.eval_binary_compare(CompOp::StrictNe, a, b, user_data, internal_context, depth),
181 CompiledLogic::LessThan(a, b) => self.eval_binary_compare(CompOp::Lt, a, b, user_data, internal_context, depth),
182 CompiledLogic::LessThanOrEqual(a, b) => self.eval_binary_compare(CompOp::Le, a, b, user_data, internal_context, depth),
183 CompiledLogic::GreaterThan(a, b) => self.eval_binary_compare(CompOp::Gt, a, b, user_data, internal_context, depth),
184 CompiledLogic::GreaterThanOrEqual(a, b) => self.eval_binary_compare(CompOp::Ge, a, b, user_data, internal_context, depth),
185
186 CompiledLogic::Add(items) => self.eval_array_fold(items, 0.0, |acc, n| Some(acc + n), user_data, internal_context, depth),
188 CompiledLogic::Subtract(items) => {
189 if items.is_empty() {
190 return Ok(self.f64_to_json(0.0));
191 }
192 let first = self.evaluate_with_context(&items[0], user_data, internal_context, depth + 1)?;
193 let mut result = to_f64(&first);
194
195 if items.len() == 1 {
196 return Ok(self.f64_to_json(-result));
197 }
198
199 for item in &items[1..] {
200 let val = self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
201 result -= to_f64(&val);
202 }
203 Ok(self.f64_to_json(result))
204 }
205 CompiledLogic::Multiply(items) => {
206 if items.is_empty() {
208 return Ok(self.f64_to_json(0.0));
209 }
210 self.eval_array_fold(items, 1.0, |acc, n| Some(acc * n), user_data, internal_context, depth)
211 }
212 CompiledLogic::Divide(items) => {
213 if items.is_empty() {
214 return Ok(self.f64_to_json(0.0_f64));
215 }
216 let first = self.evaluate_with_context(&items[0], user_data, internal_context, depth + 1)?;
217 let mut result = to_f64(&first);
218
219 for item in &items[1..] {
220 let val = self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
221 let divisor = to_f64(&val);
222 if divisor == 0.0 {
223 return Ok(Value::Null);
224 }
225 result /= divisor;
226 }
227 Ok(self.f64_to_json(result))
228 }
229 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),
230 CompiledLogic::Power(a, b) => self.eval_binary_arith(a, b, |a, b| Some(a.powf(b)), user_data, internal_context, depth),
231
232 CompiledLogic::Map(array_expr, logic_expr) => self.eval_map(array_expr, logic_expr, user_data, internal_context, depth),
234 CompiledLogic::Filter(array_expr, logic_expr) => self.eval_filter(array_expr, logic_expr, user_data, internal_context, depth),
235 CompiledLogic::Reduce(array_expr, logic_expr, initial_expr) => self.eval_reduce(array_expr, logic_expr, initial_expr, user_data, internal_context, depth),
236 CompiledLogic::All(array_expr, logic_expr) => self.eval_quantifier(Quantifier::All, array_expr, logic_expr, user_data, internal_context, depth),
237 CompiledLogic::Some(array_expr, logic_expr) => self.eval_quantifier(Quantifier::Some, array_expr, logic_expr, user_data, internal_context, depth),
238 CompiledLogic::None(array_expr, logic_expr) => self.eval_quantifier(Quantifier::None, array_expr, logic_expr, user_data, internal_context, depth),
239 CompiledLogic::Merge(items) => self.eval_merge(items, user_data, internal_context, depth),
240 CompiledLogic::In(value_expr, array_expr) => self.eval_in(value_expr, array_expr, user_data, internal_context, depth),
241 CompiledLogic::Sum(array_expr, field_expr, threshold_expr) => self.eval_sum(array_expr, field_expr, threshold_expr, user_data, internal_context, depth),
242 CompiledLogic::For(start_expr, end_expr, logic_expr) => self.eval_for(start_expr, end_expr, logic_expr, user_data, internal_context, depth),
243 CompiledLogic::Multiplies(items) => self.eval_multiplies(items, user_data, internal_context, depth),
244 CompiledLogic::Divides(items) => self.eval_divides(items, user_data, internal_context, depth),
245
246 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),
248 CompiledLogic::MaxAt(table_expr, col_name_expr) => self.eval_maxat(table_expr, col_name_expr, user_data, internal_context, depth),
249 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),
250 CompiledLogic::Match(table_expr, conditions) => self.eval_match(table_expr, conditions, user_data, internal_context, depth),
251 CompiledLogic::MatchRange(table_expr, conditions) => self.eval_matchrange(table_expr, conditions, user_data, internal_context, depth),
252 CompiledLogic::Choose(table_expr, conditions) => self.eval_choose(table_expr, conditions, user_data, internal_context, depth),
253 CompiledLogic::FindIndex(table_expr, conditions) => self.eval_findindex(table_expr, conditions, user_data, internal_context, depth),
254
255 CompiledLogic::Cat(items) => self.concat_strings(items, user_data, internal_context, depth),
257 CompiledLogic::Substr(string_expr, start_expr, length_expr) => self.eval_substr(string_expr, start_expr, length_expr, user_data, internal_context, depth),
258 CompiledLogic::Search(find_expr, within_expr, start_expr) => self.eval_search(find_expr, within_expr, start_expr, user_data, internal_context, depth),
259 CompiledLogic::Left(text_expr, num_expr) => self.extract_text_side(text_expr, num_expr.as_deref(), true, user_data, internal_context, depth),
260 CompiledLogic::Right(text_expr, num_expr) => self.extract_text_side(text_expr, num_expr.as_deref(), false, user_data, internal_context, depth),
261 CompiledLogic::Mid(text_expr, start_expr, num_expr) => self.eval_mid(text_expr, start_expr, num_expr, user_data, internal_context, depth),
262 CompiledLogic::SplitText(value_expr, sep_expr, index_expr) => self.eval_split_text(value_expr, sep_expr, index_expr, user_data, internal_context, depth),
263 CompiledLogic::Concat(items) => self.concat_strings(items, user_data, internal_context, depth),
264 CompiledLogic::SplitValue(string_expr, sep_expr) => self.eval_split_value(string_expr, sep_expr, user_data, internal_context, depth),
265 CompiledLogic::StringFormat(value_expr, decimals, prefix, suffix, thousands_sep) => {
266 self.eval_string_format(value_expr, decimals, prefix, suffix, thousands_sep, user_data, internal_context, depth)
267 }
268 CompiledLogic::Length(expr) => self.eval_length(expr, user_data, internal_context, depth),
269 CompiledLogic::Len(expr) => self.eval_len(expr, user_data, internal_context, depth),
270
271 CompiledLogic::Abs(expr) => self.eval_unary_math(expr, |n| n.abs(), user_data, internal_context, depth),
273 CompiledLogic::Max(items) => self.eval_min_max(items, true, user_data, internal_context, depth),
274 CompiledLogic::Min(items) => self.eval_min_max(items, false, user_data, internal_context, depth),
275 CompiledLogic::Pow(base_expr, exp_expr) => self.eval_pow(base_expr, exp_expr, user_data, internal_context, depth),
276 CompiledLogic::Round(expr, decimals) => self.apply_round(expr, decimals, 0, user_data, internal_context, depth),
277 CompiledLogic::RoundUp(expr, decimals) => self.apply_round(expr, decimals, 1, user_data, internal_context, depth),
278 CompiledLogic::RoundDown(expr, decimals) => self.apply_round(expr, decimals, 2, user_data, internal_context, depth),
279 CompiledLogic::Ceiling(expr, significance) => self.eval_ceiling(expr, significance, user_data, internal_context, depth),
280 CompiledLogic::Floor(expr, significance) => self.eval_floor(expr, significance, user_data, internal_context, depth),
281 CompiledLogic::Trunc(expr, decimals) => self.eval_trunc(expr, decimals, user_data, internal_context, depth),
282 CompiledLogic::Mround(value_expr, multiple_expr) => self.eval_mround(value_expr, multiple_expr, user_data, internal_context, depth),
283
284 CompiledLogic::Today => self.eval_today(),
286 CompiledLogic::Now => self.eval_now(),
287 CompiledLogic::Days(end_expr, start_expr) => self.eval_days(end_expr, start_expr, user_data, internal_context, depth),
288 CompiledLogic::Year(expr) => self.extract_date_component(expr, "year", user_data, internal_context, depth),
289 CompiledLogic::Month(expr) => self.extract_date_component(expr, "month", user_data, internal_context, depth),
290 CompiledLogic::Day(expr) => self.extract_date_component(expr, "day", user_data, internal_context, depth),
291 CompiledLogic::Date(year_expr, month_expr, day_expr) => self.eval_date(year_expr, month_expr, day_expr, user_data, internal_context, depth),
292 CompiledLogic::DateFormat(date_expr, format_expr) => self.eval_date_format(date_expr, format_expr, user_data, internal_context, depth),
293 CompiledLogic::YearFrac(start_expr, end_expr, basis_expr) => self.eval_year_frac(start_expr, end_expr, basis_expr, user_data, internal_context, depth),
294 CompiledLogic::DateDif(start_expr, end_expr, unit_expr) => self.eval_date_dif(start_expr, end_expr, unit_expr, user_data, internal_context, depth),
295
296 CompiledLogic::Missing(keys) => {
298 let missing: Vec<_> = keys
299 .iter()
300 .filter(|key| is_key_missing(user_data, key))
301 .map(|k| Value::String(k.clone()))
302 .collect();
303 Ok(Value::Array(missing))
304 }
305 CompiledLogic::MissingSome(min_expr, keys) => {
306 let min_val = self.evaluate_with_context(min_expr, user_data, internal_context, depth + 1)?;
307 let minimum = to_number(&min_val) as usize;
308
309 let present = keys
310 .iter()
311 .filter(|key| !is_key_missing(user_data, key))
312 .count();
313
314 if present >= minimum {
315 Ok(Value::Array(vec![]))
316 } else {
317 let missing: Vec<_> = keys
318 .iter()
319 .filter(|key| is_key_missing(user_data, key))
320 .map(|k| Value::String(k.clone()))
321 .collect();
322 Ok(Value::Array(missing))
323 }
324 }
325
326 CompiledLogic::Xor(a_expr, b_expr) => {
328 let a_val = self.evaluate_with_context(a_expr, user_data, internal_context, depth + 1)?;
329 let b_val = self.evaluate_with_context(b_expr, user_data, internal_context, depth + 1)?;
330 Ok(Value::Bool(is_truthy(&a_val) ^ is_truthy(&b_val)))
331 }
332 CompiledLogic::IfNull(cond_expr, alt_expr) => {
333 let cond_val = self.evaluate_with_context(cond_expr, user_data, internal_context, depth + 1)?;
334 if is_null_like(&cond_val) {
335 self.evaluate_with_context(alt_expr, user_data, internal_context, depth + 1)
336 } else {
337 Ok(cond_val)
338 }
339 }
340 CompiledLogic::IsEmpty(expr) => {
341 let val = self.evaluate_with_context(expr, user_data, internal_context, depth + 1)?;
342 let empty = match &val {
343 Value::Null => true,
344 Value::String(s) => s.is_empty(),
345 _ => false,
346 };
347 Ok(Value::Bool(empty))
348 }
349 CompiledLogic::Empty => Ok(Value::String(String::new())),
350
351 CompiledLogic::RangeOptions(min_expr, max_expr) => {
353 let min_val = self.evaluate_with_context(min_expr, user_data, internal_context, depth + 1)?;
354 let max_val = self.evaluate_with_context(max_expr, user_data, internal_context, depth + 1)?;
355
356 let min = to_number(&min_val) as i32;
357 let max = to_number(&max_val) as i32;
358
359 if min > max {
360 return Ok(Value::Array(vec![]));
361 }
362
363 let options: Vec<Value> = (min..=max)
364 .map(|i| {
365 serde_json::json!({
366 "label": i.to_string(),
367 "value": i.to_string()
368 })
369 })
370 .collect();
371
372 Ok(Value::Array(options))
373 }
374 CompiledLogic::MapOptions(table_expr, label_expr, value_expr) => {
375 let table_val = self.evaluate_with_context(table_expr, user_data, internal_context, depth + 1)?;
376 let label_val = self.evaluate_with_context(label_expr, user_data, internal_context, depth + 1)?;
377 let value_val = self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
378
379 if let (Value::Array(arr), Value::String(label_field), Value::String(value_field)) =
380 (&table_val, &label_val, &value_val)
381 {
382 let options: Vec<Value> = arr
383 .iter()
384 .filter_map(|row| {
385 row.as_object()
386 .and_then(|obj| Some(create_option(obj.get(label_field)?, obj.get(value_field)?)))
387 })
388 .collect();
389 Ok(Value::Array(options))
390 } else {
391 Ok(Value::Array(vec![]))
392 }
393 }
394 CompiledLogic::MapOptionsIf(table_expr, label_expr, value_expr, conditions) => {
395 let table_val = self.evaluate_with_context(table_expr, user_data, internal_context, depth + 1)?;
396 let label_val = self.evaluate_with_context(label_expr, user_data, internal_context, depth + 1)?;
397 let value_val = self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
398
399 if let (Value::Array(arr), Value::String(label_field), Value::String(value_field)) =
400 (&table_val, &label_val, &value_val)
401 {
402 let mut options = Vec::new();
403
404 for row in arr {
405 let obj = match row.as_object() {
406 Some(obj) => obj,
407 None => continue,
408 };
409
410 let mut all_match = true;
411
412 for condition in conditions {
413 let result = self.evaluate_with_context(condition, row, user_data, depth + 1)?;
415 if !is_truthy(&result) {
416 all_match = false;
417 break;
418 }
419 }
420
421 if all_match {
422 if let (Some(label), Some(value)) = (obj.get(label_field), obj.get(value_field)) {
423 options.push(create_option(label, value));
424 }
425 }
426 }
427
428 Ok(Value::Array(options))
429 } else {
430 Ok(Value::Array(vec![]))
431 }
432 }
433 CompiledLogic::Return(value) => {
434 Ok(value.as_ref().clone())
436 }
437 }
438 }
439
440 #[inline]
442 fn eval_var_or_default(
443 &self,
444 name: &str,
445 default: &Option<Box<CompiledLogic>>,
446 user_data: &Value,
447 internal_context: &Value,
448 depth: usize,
449 ) -> Result<Value, String> {
450 let value = if name.is_empty() {
453 get_var(user_data, name)
454 } else {
455 get_var(internal_context, name)
456 .or_else(|| get_var(user_data, name))
457 };
458 match value {
459 Some(v) if !v.is_null() => Ok(v.clone()), _ => {
461 if let Some(def) = default {
462 self.evaluate_with_context(def, user_data, internal_context, depth + 1)
463 } else {
464 Ok(Value::Null)
465 }
466 }
467 }
468 }
469
470 #[inline(always)]
472 fn f64_to_json(&self, f: f64) -> Value {
473 helpers::f64_to_json(f, self.config.safe_nan_handling)
474 }
475}
476
477impl Default for Evaluator {
478 fn default() -> Self {
479 Self::new()
480 }
481}