1use super::{Evaluator, types::*};
2use serde_json::Value;
3use super::super::compiled::CompiledLogic;
4use super::helpers;
5
6#[cfg(feature = "parallel")]
7use rayon::prelude::*;
8
9#[cfg(feature = "parallel")]
11const PARALLEL_THRESHOLD: usize = 1000;
12
13impl Evaluator {
14 #[inline]
16 pub(super) fn resolve_table_ref<'a>(&self, table_expr: &CompiledLogic, user_data: &'a Value, internal_context: &'a Value, depth: usize) -> Result<TableRef<'a>, String> {
17 match table_expr {
18 CompiledLogic::Var(name, _) => {
19 let value = if name.is_empty() {
21 helpers::get_var(user_data, name)
22 } else {
23 helpers::get_var(user_data, name)
25 .or_else(|| helpers::get_var(internal_context, name))
26 };
27 value
28 .filter(|v| !v.is_null())
29 .map(TableRef::Borrowed)
30 .ok_or_else(|| format!("Variable not found: {}", name))
31 }
32 CompiledLogic::Ref(path, _) => {
33 let value = helpers::get_var(user_data, path)
35 .or_else(|| helpers::get_var(internal_context, path));
36 value
37 .filter(|v| !v.is_null())
38 .map(TableRef::Borrowed)
39 .ok_or_else(|| format!("Reference not found: {}", path))
40 }
41 _ => {
42 self.evaluate_with_context(table_expr, user_data, internal_context, depth + 1)
43 .map(TableRef::Owned)
44 }
45 }
46 }
47
48 #[inline]
50 pub(super) fn get_table_array<'a>(&self, table_expr: &CompiledLogic, user_data: &'a Value, internal_context: &'a Value, depth: usize) -> Result<TableRef<'a>, String> {
51 let table_ref = self.resolve_table_ref(table_expr, user_data, internal_context, depth)?;
52 match table_ref.as_value() {
53 Value::Array(_) => Ok(table_ref),
54 Value::Null => Err("Table reference is null".to_string()),
55 _ => Err("Table reference is not an array".to_string()),
56 }
57 }
58
59 #[inline]
61 pub(super) fn resolve_column_name(&self, col_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
62 match col_expr {
63 CompiledLogic::String(s) => Ok(Value::String(s.clone())),
65 CompiledLogic::Var(name, _) => {
66 let value = if name.is_empty() {
68 helpers::get_var(user_data, name)
69 } else {
70 helpers::get_var(user_data, name)
71 .or_else(|| helpers::get_var(internal_context, name))
72 };
73 Ok(value.cloned().unwrap_or(Value::Null))
74 }
75 _ => self.evaluate_with_context(col_expr, user_data, internal_context, depth)
76 }
77 }
78
79 pub(super) fn eval_valueat(&self, table_expr: &CompiledLogic, row_idx_expr: &CompiledLogic, col_name_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
81 if let Some(result) = self.try_eval_valueat_combined(table_expr, row_idx_expr, col_name_expr, user_data, internal_context, depth)? {
83 return Ok(result);
84 }
85
86 let row_idx = match row_idx_expr {
88 CompiledLogic::Number(n) => {
89 match n.parse::<i64>() {
91 Ok(idx) if idx >= 0 => Some(idx as usize),
92 _ => None,
93 }
94 }
95 _ => {
96 let row_idx_val = self.evaluate_with_context(row_idx_expr, user_data, internal_context, depth + 1)?;
98 let row_idx_num = helpers::to_number(&row_idx_val) as i64;
99 if row_idx_num >= 0 {
100 Some(row_idx_num as usize)
101 } else {
102 None
103 }
104 }
105 };
106
107 let row_idx = match row_idx {
109 Some(idx) => idx,
110 None => return Ok(Value::Null),
111 };
112
113 let table_ref = self.get_table_array(table_expr, user_data, internal_context, depth)?;
115 let arr = match table_ref.as_array() {
116 Some(arr) if !arr.is_empty() && row_idx < arr.len() => arr,
117 _ => return Ok(Value::Null),
118 };
119
120 let row = &arr[row_idx];
121
122 let result = if let Some(col_expr) = col_name_expr {
124 let col_name = match col_expr.as_ref() {
126 CompiledLogic::String(s) => s.as_str(),
127 _ => {
128 match self.resolve_column_name(col_expr, user_data, internal_context, depth)? {
130 Value::String(s) => {
131 if let Value::Object(obj) = row {
133 return Ok(obj.get(&s).cloned().unwrap_or(Value::Null));
134 } else {
135 return Ok(Value::Null);
136 }
137 }
138 _ => return Ok(Value::Null),
139 }
140 }
141 };
142
143 if let Value::Object(obj) = row {
145 obj.get(col_name).cloned().unwrap_or(Value::Null)
146 } else {
147 Value::Null
148 }
149 } else {
150 row.clone()
151 };
152
153 Ok(result)
154 }
155
156 pub(super) fn eval_maxat(&self, table_expr: &CompiledLogic, col_name_expr: &CompiledLogic, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
158 let table_ref = self.get_table_array(table_expr, user_data, internal_context, depth)?;
159 let col_val = self.resolve_column_name(col_name_expr, user_data, internal_context, depth)?;
160
161 if let Value::String(col_name) = &col_val {
162 let result = if let Some(arr) = table_ref.as_array() {
163 arr.last()
164 .and_then(|last_row| last_row.as_object())
165 .and_then(|obj| obj.get(col_name))
166 .cloned()
167 .unwrap_or(Value::Null)
168 } else {
169 Value::Null
170 };
171 Ok(result)
172 } else {
173 Ok(Value::Null)
174 }
175 }
176
177 pub(super) fn eval_indexat(&self, lookup_expr: &CompiledLogic, table_expr: &CompiledLogic, field_expr: &CompiledLogic, range_expr: &Option<Box<CompiledLogic>>, user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
179 let lookup_val = self.evaluate_with_context(lookup_expr, user_data, internal_context, depth + 1)?;
180 let field_val = self.resolve_column_name(field_expr, user_data, internal_context, depth)?;
181 let field_name = match field_val {
182 Value::String(s) => s,
183 _ => return Ok(self.f64_to_json(-1.0)),
184 };
185
186 let is_range = if let Some(r_expr) = range_expr {
187 let r_val = self.evaluate_with_context(r_expr, user_data, internal_context, depth + 1)?;
188 helpers::is_truthy(&r_val)
189 } else {
190 false
191 };
192
193 let table_ref = self.get_table_array(table_expr, user_data, internal_context, depth)?;
194 let arr = match table_ref.as_array() {
195 Some(arr) if !arr.is_empty() => arr,
196 _ => return Ok(self.f64_to_json(-1.0)),
197 };
198
199 let lookup_num = if is_range { helpers::to_number(&lookup_val) } else { 0.0 };
200
201 #[cfg(feature = "parallel")]
202 if !is_range && arr.len() >= PARALLEL_THRESHOLD {
203 let result = arr.par_iter()
204 .enumerate()
205 .find_map_first(|(idx, row)| {
206 if let Value::Object(obj) = row {
207 if let Some(cell_val) = obj.get(&field_name) {
208 if helpers::loose_equal(&lookup_val, cell_val) {
209 return Some(idx as f64);
210 }
211 }
212 }
213 None
214 });
215 return Ok(self.f64_to_json(result.unwrap_or(-1.0)));
216 }
217
218 if is_range {
219 for (idx, row) in arr.iter().enumerate() {
220 if let Value::Object(obj) = row {
221 if let Some(cell_val) = obj.get(&field_name) {
222 let cell_num = helpers::to_number(cell_val);
223 if cell_num <= lookup_num {
224 return Ok(self.f64_to_json(idx as f64));
225 }
226 }
227 }
228 }
229 Ok(self.f64_to_json(-1.0))
230 } else {
231 for (idx, row) in arr.iter().enumerate() {
232 if let Value::Object(obj) = row {
233 if let Some(cell_val) = obj.get(&field_name) {
234 if helpers::loose_equal(&lookup_val, cell_val) {
235 return Ok(self.f64_to_json(idx as f64));
236 }
237 }
238 }
239 }
240 Ok(self.f64_to_json(-1.0))
241 }
242 }
243
244 pub(super) fn eval_match(&self, table_expr: &CompiledLogic, conditions: &[CompiledLogic], user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
246 let table_ref = self.get_table_array(table_expr, user_data, internal_context, depth)?;
247
248 let mut evaluated_conditions = Vec::with_capacity(conditions.len() / 2);
250 for chunk in conditions.chunks(2) {
251 if chunk.len() == 2 {
252 let value_val = self.evaluate_with_context(&chunk[0], user_data, internal_context, depth + 1)?;
253 let field_val = self.evaluate_with_context(&chunk[1], user_data, internal_context, depth + 1)?;
254 if let Value::String(field) = field_val {
255 evaluated_conditions.push((value_val, field));
256 }
257 }
258 }
259
260 if let Some(arr) = table_ref.as_array() {
261 #[cfg(feature = "parallel")]
262 if arr.len() >= PARALLEL_THRESHOLD {
263 let result = arr.par_iter()
264 .enumerate()
265 .find_map_first(|(idx, row)| {
266 if let Value::Object(obj) = row {
267 let all_match = evaluated_conditions.iter().all(|(value_val, field)| {
268 obj.get(field)
269 .map(|cell_val| helpers::loose_equal(value_val, cell_val))
270 .unwrap_or(false)
271 });
272 if all_match { Some(idx as f64) } else { None }
273 } else {
274 None
275 }
276 });
277 return Ok(self.f64_to_json(result.unwrap_or(-1.0)));
278 }
279
280 for (idx, row) in arr.iter().enumerate() {
281 if let Value::Object(obj) = row {
282 let all_match = evaluated_conditions.iter().all(|(value_val, field)| {
283 obj.get(field)
284 .map(|cell_val| helpers::loose_equal(value_val, cell_val))
285 .unwrap_or(false)
286 });
287 if all_match {
288 return Ok(self.f64_to_json(idx as f64));
289 }
290 }
291 }
292 Ok(self.f64_to_json(-1.0))
293 } else {
294 Ok(Value::Null)
295 }
296 }
297
298 pub(super) fn eval_matchrange(&self, table_expr: &CompiledLogic, conditions: &[CompiledLogic], user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
300 let table_ref = self.get_table_array(table_expr, user_data, internal_context, depth)?;
301
302 let mut evaluated_conditions = Vec::with_capacity(conditions.len() / 3);
303 for chunk in conditions.chunks(3) {
304 if chunk.len() == 3 {
305 let min_col_val = self.evaluate_with_context(&chunk[0], user_data, internal_context, depth + 1)?;
306 let max_col_val = self.evaluate_with_context(&chunk[1], user_data, internal_context, depth + 1)?;
307 let check_val = self.evaluate_with_context(&chunk[2], user_data, internal_context, depth + 1)?;
308
309 if let (Value::String(min_col), Value::String(max_col)) = (&min_col_val, &max_col_val) {
310 let check_num = helpers::to_number(&check_val);
311 evaluated_conditions.push((min_col.clone(), max_col.clone(), check_num));
312 }
313 }
314 }
315
316 if let Some(arr) = table_ref.as_array() {
317 #[cfg(feature = "parallel")]
318 if arr.len() >= PARALLEL_THRESHOLD {
319 let result = arr.par_iter()
320 .enumerate()
321 .find_map_first(|(idx, row)| {
322 if let Value::Object(obj) = row {
323 let all_match = evaluated_conditions.iter().all(|(min_col, max_col, check_num)| {
324 let min_num = obj.get(min_col).map(|v| helpers::to_number(v)).unwrap_or(0.0);
325 let max_num = obj.get(max_col).map(|v| helpers::to_number(v)).unwrap_or(0.0);
326 *check_num >= min_num && *check_num <= max_num
327 });
328 if all_match { Some(idx as f64) } else { None }
329 } else {
330 None
331 }
332 });
333 return Ok(self.f64_to_json(result.unwrap_or(-1.0)));
334 }
335
336 for (idx, row) in arr.iter().enumerate() {
337 if let Value::Object(obj) = row {
338 let all_match = evaluated_conditions.iter().all(|(min_col, max_col, check_num)| {
339 let min_num = obj.get(min_col).map(|v| helpers::to_number(v)).unwrap_or(0.0);
340 let max_num = obj.get(max_col).map(|v| helpers::to_number(v)).unwrap_or(0.0);
341 *check_num >= min_num && *check_num <= max_num
342 });
343 if all_match {
344 return Ok(self.f64_to_json(idx as f64));
345 }
346 }
347 }
348 Ok(self.f64_to_json(-1.0))
349 } else {
350 Ok(Value::Null)
351 }
352 }
353
354 pub(super) fn eval_choose(&self, table_expr: &CompiledLogic, conditions: &[CompiledLogic], user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
356 let table_ref = self.get_table_array(table_expr, user_data, internal_context, depth)?;
357
358 let mut evaluated_conditions = Vec::with_capacity(conditions.len() / 2);
359 for chunk in conditions.chunks(2) {
360 if chunk.len() == 2 {
361 let value_val = self.evaluate_with_context(&chunk[0], user_data, internal_context, depth + 1)?;
362 let field_val = self.evaluate_with_context(&chunk[1], user_data, internal_context, depth + 1)?;
363 if let Value::String(field) = field_val {
364 evaluated_conditions.push((value_val, field));
365 }
366 }
367 }
368
369 if let Some(arr) = table_ref.as_array() {
370 #[cfg(feature = "parallel")]
371 if arr.len() >= PARALLEL_THRESHOLD {
372 let result = arr.par_iter()
373 .enumerate()
374 .find_map_first(|(idx, row)| {
375 if let Value::Object(obj) = row {
376 let any_match = evaluated_conditions.iter().any(|(value_val, field)| {
377 obj.get(field)
378 .map(|cell_val| helpers::loose_equal(value_val, cell_val))
379 .unwrap_or(false)
380 });
381 if any_match { Some(idx as f64) } else { None }
382 } else {
383 None
384 }
385 });
386 return Ok(self.f64_to_json(result.unwrap_or(-1.0)));
387 }
388
389 for (idx, row) in arr.iter().enumerate() {
390 if let Value::Object(obj) = row {
391 let any_match = evaluated_conditions.iter().any(|(value_val, field)| {
392 obj.get(field)
393 .map(|cell_val| helpers::loose_equal(value_val, cell_val))
394 .unwrap_or(false)
395 });
396 if any_match {
397 return Ok(self.f64_to_json(idx as f64));
398 }
399 }
400 }
401 Ok(self.f64_to_json(-1.0))
402 } else {
403 Ok(Value::Null)
404 }
405 }
406
407 pub(super) fn eval_findindex(&self, table_expr: &CompiledLogic, conditions: &[CompiledLogic], user_data: &Value, internal_context: &Value, depth: usize) -> Result<Value, String> {
409 let table_ref = self.get_table_array(table_expr, user_data, internal_context, depth)?;
410 let arr = match table_ref.as_array() {
411 Some(arr) if !arr.is_empty() => arr,
412 _ => return Ok(self.f64_to_json(-1.0)),
413 };
414
415 if conditions.is_empty() {
416 return Ok(self.f64_to_json(0.0));
417 }
418
419 #[cfg(feature = "parallel")]
420 if arr.len() >= PARALLEL_THRESHOLD {
421 let result = arr.par_iter()
422 .enumerate()
423 .find_map_first(|(idx, row)| {
424 for condition in conditions {
425 match self.evaluate_with_context(condition, row, user_data, depth + 1) {
427 Ok(result) if helpers::is_truthy(&result) => continue,
428 _ => return None,
429 }
430 }
431 Some(idx as f64)
432 });
433 return Ok(self.f64_to_json(result.unwrap_or(-1.0)));
434 }
435
436 for (idx, row) in arr.iter().enumerate() {
437 let mut all_match = true;
438 for condition in conditions {
439 match self.evaluate_with_context(condition, row, user_data, depth + 1) {
441 Ok(result) if helpers::is_truthy(&result) => continue,
442 _ => {
443 all_match = false;
444 break;
445 }
446 }
447 }
448 if all_match {
449 return Ok(self.f64_to_json(idx as f64));
450 }
451 }
452 Ok(self.f64_to_json(-1.0))
453 }
454}