1use crate::ctx::EvalCtx;
5use crate::error::ApplyError;
6use serde_json::{Map, Number, Value};
7
8fn js_to_string(v: &Value) -> String {
13 match v {
14 Value::Object(_) => "[object Object]".into(),
15 Value::Bool(b) => b.to_string(),
16 Value::Null => "null".into(),
17 Value::Number(n) => n.to_string(),
18 Value::String(s) => s.clone(),
19 Value::Array(arr) => arr
20 .iter()
21 .map(|i| match i {
22 Value::Null => String::new(),
23 _ => js_to_string(i),
24 })
25 .collect::<Vec<_>>()
26 .join(","),
27 }
28}
29
30fn js_to_number(v: &Value) -> Option<f64> {
32 match v {
33 Value::Null => Some(0.0),
34 Value::Bool(true) => Some(1.0),
35 Value::Bool(false) => Some(0.0),
36 Value::Number(n) => n.as_f64(),
37 Value::String(s) => {
38 let trimmed = s.trim();
39 if trimmed.is_empty() {
40 Some(0.0)
41 } else {
42 trimmed.parse::<f64>().ok()
43 }
44 }
45 Value::Array(_) => js_to_number(&Value::String(js_to_string(v))),
46 Value::Object(_) => None,
47 }
48}
49
50fn js_parse_float(v: &Value) -> Option<f64> {
53 match v {
54 Value::Number(n) => n.as_f64(),
55 Value::String(s) => parse_float_str(s),
56 _ => parse_float_str(&js_to_string(v)),
57 }
58}
59
60fn parse_float_str(s: &str) -> Option<f64> {
61 const NUMERICS: &[char] = &['0','1','2','3','4','5','6','7','8','9','.', '-', '+', 'e', 'E'];
62 let trimmed = s.trim();
63 let (mut chars, mut saw_decimal) = (Vec::new(), false);
64 let mut broke = false;
65 for c in trimmed.chars() {
66 if broke { break; }
67 if NUMERICS.contains(&c) {
68 if c == '.' {
69 if saw_decimal { broke = true; continue; }
70 saw_decimal = true;
71 }
72 chars.push(c);
73 } else {
74 broke = true;
75 }
76 }
77 if chars.is_empty() { return None; }
78 if matches!(chars.last(), Some('e') | Some('E')) { chars.pop(); }
79 chars.iter().collect::<String>().parse::<f64>().ok()
80}
81
82pub(crate) fn truthy(v: &Value) -> bool {
84 match v {
85 Value::Null => false,
86 Value::Bool(b) => *b,
87 Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
88 Value::String(s) => !s.is_empty(),
89 Value::Array(a) => !a.is_empty(),
90 Value::Object(_) => true,
91 }
92}
93
94fn abstract_eq(a: &Value, b: &Value) -> bool {
96 match (a, b) {
97 (Value::Null, Value::Null) => true,
98 (Value::Number(x), Value::Number(y)) => {
99 x.as_f64().zip(y.as_f64()).map(|(xf, yf)| xf == yf).unwrap_or(false)
100 }
101 (Value::String(x), Value::String(y)) => x == y,
102 (Value::Bool(x), Value::Bool(y)) => x == y,
103 (Value::Bool(x), _) => {
105 let n = Value::Number(Number::from_f64(if *x { 1.0 } else { 0.0 }).unwrap());
106 abstract_eq(&n, b)
107 }
108 (_, Value::Bool(y)) => {
109 let n = Value::Number(Number::from_f64(if *y { 1.0 } else { 0.0 }).unwrap());
110 abstract_eq(a, &n)
111 }
112 (Value::Number(_), Value::String(_)) | (Value::String(_), Value::Number(_)) => {
114 let an = js_to_number(a);
115 let bn = js_to_number(b);
116 an.zip(bn).map(|(x, y)| x == y).unwrap_or(false)
117 }
118 (Value::String(_), Value::Array(_))
120 | (Value::Number(_), Value::Array(_))
121 | (Value::String(_), Value::Object(_))
122 | (Value::Number(_), Value::Object(_)) => {
123 abstract_eq(a, &Value::String(js_to_string(b)))
124 }
125 (Value::Array(_), Value::String(_))
126 | (Value::Array(_), Value::Number(_))
127 | (Value::Object(_), Value::String(_))
128 | (Value::Object(_), Value::Number(_)) => {
129 abstract_eq(&Value::String(js_to_string(a)), b)
130 }
131 _ => false,
132 }
133}
134
135fn abstract_lt(a: &Value, b: &Value) -> bool {
137 match (js_to_string_if_prim(a), js_to_string_if_prim(b)) {
139 (Some(sa), Some(sb)) => sa < sb,
140 _ => {
141 let an = js_to_number(a).unwrap_or(f64::NAN);
142 let bn = js_to_number(b).unwrap_or(f64::NAN);
143 an < bn
144 }
145 }
146}
147
148fn abstract_gt(a: &Value, b: &Value) -> bool { abstract_lt(b, a) }
149fn abstract_lte(a: &Value, b: &Value) -> bool { abstract_lt(a, b) || abstract_eq(a, b) }
150fn abstract_gte(a: &Value, b: &Value) -> bool { abstract_gt(a, b) || abstract_eq(a, b) }
151
152fn js_to_string_if_prim(v: &Value) -> Option<String> {
154 match v {
157 Value::String(s) => Some(s.clone()),
158 _ => None,
159 }
160}
161
162fn to_json_number(n: f64) -> Value {
163 if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
164 Value::Number(Number::from(n as i64))
165 } else {
166 Number::from_f64(n)
167 .map(Value::Number)
168 .unwrap_or(Value::Null)
169 }
170}
171
172fn var_get(data: &Value, key: &str) -> Option<Value> {
177 if key.is_empty() {
178 return Some(data.clone());
179 }
180 key.split('.').fold(Some(data.clone()), |acc, segment| {
181 let cur = acc?;
182 match cur {
183 Value::Object(ref map) => map.get(segment).cloned(),
184 Value::Array(ref arr) => segment
185 .parse::<i64>()
186 .ok()
187 .and_then(|i| {
188 let idx = if i < 0 {
189 arr.len().checked_sub(i.unsigned_abs() as usize)?
190 } else {
191 i as usize
192 };
193 arr.get(idx).cloned()
194 }),
195 Value::String(ref s) => segment
196 .parse::<i64>()
197 .ok()
198 .and_then(|i| {
199 let chars: Vec<char> = s.chars().collect();
200 let idx = if i < 0 {
201 chars.len().checked_sub(i.unsigned_abs() as usize)?
202 } else {
203 i as usize
204 };
205 chars.get(idx).map(|c| Value::String(c.to_string()))
206 }),
207 _ => None,
208 }
209 })
210}
211
212fn eval_var(args: &[&Value], data: &Value) -> Result<Value, ApplyError> {
213 if args.is_empty() {
214 return Ok(data.clone());
215 }
216 let key_str = match args[0] {
217 Value::Null => return Ok(data.clone()),
218 Value::String(s) => s.clone(),
219 Value::Number(n) => {
220 if let Some(i) = n.as_i64() {
221 i.to_string()
222 } else {
223 return Err(ApplyError::InvalidArguments("var key must be integer or string".into()));
224 }
225 }
226 other => return Err(ApplyError::InvalidArguments(
227 format!("var key must be string, number, or null; got {:?}", other)
228 )),
229 };
230 let val = var_get(data, &key_str);
231 Ok(val.unwrap_or_else(|| {
232 if args.len() >= 2 {
233 args[1].clone()
234 } else {
235 Value::Null
236 }
237 }))
238}
239
240fn eval_missing(args: &[&Value], data: &Value) -> Result<Value, ApplyError> {
241 let key_list: Vec<&Value> = if !args.is_empty() {
243 match args[0] {
244 Value::Array(inner) => inner.iter().collect(),
245 _ => args.to_vec(),
246 }
247 } else {
248 vec![]
249 };
250 let missing: Vec<Value> = key_list
251 .into_iter()
252 .filter(|k| {
253 let key_str = match k {
254 Value::String(s) => s.clone(),
255 Value::Number(n) => n.to_string(),
256 _ => return false,
257 };
258 var_get(data, &key_str).is_none()
259 })
260 .map(|k| (*k).clone())
261 .collect();
262 Ok(Value::Array(missing))
263}
264
265fn eval_missing_some(args: &[&Value], data: &Value) -> Result<Value, ApplyError> {
266 if args.len() < 2 {
267 return Err(ApplyError::InvalidArguments("missing_some requires 2 args".into()));
268 }
269 let threshold = match args[0] {
270 Value::Number(n) => n.as_u64().ok_or_else(|| {
271 ApplyError::InvalidArguments("missing_some threshold must be non-negative integer".into())
272 })?,
273 _ => return Err(ApplyError::InvalidArguments("missing_some threshold must be a number".into())),
274 };
275 let keys = match args[1] {
276 Value::Array(arr) => arr,
277 _ => return Err(ApplyError::InvalidArguments("missing_some keys must be an array".into())),
278 };
279 let mut missing_keys: Vec<Value> = Vec::new();
280 let mut present = 0u64;
281 for key in keys {
282 if present >= threshold { break; }
283 let key_str = match key {
284 Value::String(s) => s.clone(),
285 Value::Number(n) => n.to_string(),
286 Value::Null => continue,
287 _ => continue,
288 };
289 if var_get(data, &key_str).is_some() {
290 present += 1;
291 } else if !missing_keys.contains(key) {
292 missing_keys.push(key.clone());
293 }
294 }
295 if present >= threshold {
296 Ok(Value::Array(vec![]))
297 } else {
298 Ok(Value::Array(missing_keys))
299 }
300}
301
302pub(crate) fn apply_value(
307 logic: &Value,
308 data: &Value,
309 ctx: &EvalCtx,
310 fuel: &mut usize,
311) -> Result<Value, ApplyError> {
312 if *fuel == 0 {
313 return Err(ApplyError::FuelExceeded);
314 }
315 *fuel -= 1;
316
317 let obj = match logic {
319 Value::Object(m) if m.len() == 1 => m,
320 _ => return Ok(logic.clone()),
321 };
322
323 let (op, args_val) = obj.iter().next().unwrap();
324 let op: &str = op.as_str();
325
326 let args: Vec<&Value> = match args_val {
328 Value::Array(arr) => arr.iter().collect(),
329 _ => vec![args_val],
330 };
331
332 match op {
333 "var" => eval_var(&args, data),
335 "missing" => eval_missing(&args, data),
336 "missing_some" => eval_missing_some(&args, data),
337
338 "==" => {
340 require_args(op, &args, 2, 2)?;
341 let a = apply_value(args[0], data, ctx, fuel)?;
342 let b = apply_value(args[1], data, ctx, fuel)?;
343 Ok(Value::Bool(abstract_eq(&a, &b)))
344 }
345 "!=" => {
346 require_args(op, &args, 2, 2)?;
347 let a = apply_value(args[0], data, ctx, fuel)?;
348 let b = apply_value(args[1], data, ctx, fuel)?;
349 Ok(Value::Bool(!abstract_eq(&a, &b)))
350 }
351 "===" => {
352 require_args(op, &args, 2, 2)?;
353 let a = apply_value(args[0], data, ctx, fuel)?;
354 let b = apply_value(args[1], data, ctx, fuel)?;
355 Ok(Value::Bool(a == b))
356 }
357 "!==" => {
358 require_args(op, &args, 2, 2)?;
359 let a = apply_value(args[0], data, ctx, fuel)?;
360 let b = apply_value(args[1], data, ctx, fuel)?;
361 Ok(Value::Bool(a != b))
362 }
363
364 "!" => {
366 require_args(op, &args, 1, 1)?;
367 let a = apply_value(args[0], data, ctx, fuel)?;
368 Ok(Value::Bool(!truthy(&a)))
369 }
370 "!!" => {
371 require_args(op, &args, 1, 1)?;
372 let a = apply_value(args[0], data, ctx, fuel)?;
373 Ok(Value::Bool(truthy(&a)))
374 }
375
376 "and" => {
378 if args.is_empty() {
379 return Err(ApplyError::InvalidArguments("'and' requires at least 1 arg".into()));
380 }
381 let mut last = Value::Null;
382 for arg in &args {
383 last = apply_value(arg, data, ctx, fuel)?;
384 if !truthy(&last) {
385 return Ok(last);
386 }
387 }
388 Ok(last)
389 }
390 "or" => {
391 if args.is_empty() {
392 return Err(ApplyError::InvalidArguments("'or' requires at least 1 arg".into()));
393 }
394 let mut last = Value::Null;
395 for arg in &args {
396 last = apply_value(arg, data, ctx, fuel)?;
397 if truthy(&last) {
398 return Ok(last);
399 }
400 }
401 Ok(last)
402 }
403
404 "if" | "?:" => eval_if(&args, data, ctx, fuel),
406
407 "<" => {
409 require_args_range(op, &args, 2, 3)?;
410 let vals = eval_all(&args, data, ctx, fuel)?;
411 if vals.len() == 2 {
412 Ok(Value::Bool(abstract_lt(&vals[0], &vals[1])))
413 } else {
414 Ok(Value::Bool(abstract_lt(&vals[0], &vals[1]) && abstract_lt(&vals[1], &vals[2])))
415 }
416 }
417 "<=" => {
418 require_args_range(op, &args, 2, 3)?;
419 let vals = eval_all(&args, data, ctx, fuel)?;
420 if vals.len() == 2 {
421 Ok(Value::Bool(abstract_lte(&vals[0], &vals[1])))
422 } else {
423 Ok(Value::Bool(abstract_lte(&vals[0], &vals[1]) && abstract_lte(&vals[1], &vals[2])))
424 }
425 }
426 ">" => {
427 require_args_range(op, &args, 2, 3)?;
428 let vals = eval_all(&args, data, ctx, fuel)?;
429 if vals.len() == 2 {
430 Ok(Value::Bool(abstract_gt(&vals[0], &vals[1])))
431 } else {
432 Ok(Value::Bool(abstract_gt(&vals[0], &vals[1]) && abstract_gt(&vals[1], &vals[2])))
433 }
434 }
435 ">=" => {
436 require_args_range(op, &args, 2, 3)?;
437 let vals = eval_all(&args, data, ctx, fuel)?;
438 if vals.len() == 2 {
439 Ok(Value::Bool(abstract_gte(&vals[0], &vals[1])))
440 } else {
441 Ok(Value::Bool(abstract_gte(&vals[0], &vals[1]) && abstract_gte(&vals[1], &vals[2])))
442 }
443 }
444
445 "+" => {
447 let vals = eval_all(&args, data, ctx, fuel)?;
448 let sum = vals.iter().map(|v| {
449 js_parse_float(v).ok_or_else(|| {
450 ApplyError::InvalidArguments(format!("'+' arg could not be converted to float: {:?}", v))
451 })
452 }).try_fold(0.0f64, |acc, r| r.map(|n| acc + n))?;
453 Ok(to_json_number(sum))
454 }
455 "-" => {
456 require_args_range(op, &args, 1, 2)?;
457 let vals = eval_all(&args, data, ctx, fuel)?;
458 let result = if vals.len() == 1 {
459 js_to_number(&vals[0])
460 .map(|n| -n)
461 .ok_or_else(|| ApplyError::InvalidArguments(format!("'-' could not negate {:?}", vals[0])))?
462 } else {
463 let a = js_to_number(&vals[0]).ok_or_else(|| ApplyError::InvalidArguments(format!("'-' arg not numeric: {:?}", vals[0])))?;
464 let b = js_to_number(&vals[1]).ok_or_else(|| ApplyError::InvalidArguments(format!("'-' arg not numeric: {:?}", vals[1])))?;
465 a - b
466 };
467 Ok(to_json_number(result))
468 }
469 "*" => {
470 if args.is_empty() {
471 return Err(ApplyError::InvalidArguments("'*' requires at least 1 arg".into()));
472 }
473 let vals = eval_all(&args, data, ctx, fuel)?;
474 let product = vals.iter().map(|v| {
475 js_parse_float(v).ok_or_else(|| {
476 ApplyError::InvalidArguments(format!("'*' arg could not be converted to float: {:?}", v))
477 })
478 }).try_fold(1.0f64, |acc, r| r.map(|n| acc * n))?;
479 Ok(to_json_number(product))
480 }
481 "/" => {
482 require_args(op, &args, 2, 2)?;
483 let vals = eval_all(&args, data, ctx, fuel)?;
484 let a = js_to_number(&vals[0]).ok_or_else(|| ApplyError::InvalidArguments(format!("'/' arg not numeric: {:?}", vals[0])))?;
485 let b = js_to_number(&vals[1]).ok_or_else(|| ApplyError::InvalidArguments(format!("'/' arg not numeric: {:?}", vals[1])))?;
486 Ok(to_json_number(a / b))
487 }
488 "%" => {
489 require_args(op, &args, 2, 2)?;
490 let vals = eval_all(&args, data, ctx, fuel)?;
491 let a = js_to_number(&vals[0]).ok_or_else(|| ApplyError::InvalidArguments(format!("'%' arg not numeric: {:?}", vals[0])))?;
492 let b = js_to_number(&vals[1]).ok_or_else(|| ApplyError::InvalidArguments(format!("'%' arg not numeric: {:?}", vals[1])))?;
493 Ok(to_json_number(a % b))
494 }
495 "max" => {
496 if args.is_empty() {
497 return Err(ApplyError::InvalidArguments("'max' requires at least 1 arg".into()));
498 }
499 let vals = eval_all(&args, data, ctx, fuel)?;
500 let mut result = f64::NEG_INFINITY;
501 for v in &vals {
502 let n = js_to_number(v).ok_or_else(|| ApplyError::InvalidArguments(format!("'max' arg not numeric: {:?}", v)))?;
503 if n > result { result = n; }
504 }
505 Ok(to_json_number(result))
506 }
507 "min" => {
508 if args.is_empty() {
509 return Err(ApplyError::InvalidArguments("'min' requires at least 1 arg".into()));
510 }
511 let vals = eval_all(&args, data, ctx, fuel)?;
512 let mut result = f64::INFINITY;
513 for v in &vals {
514 let n = js_to_number(v).ok_or_else(|| ApplyError::InvalidArguments(format!("'min' arg not numeric: {:?}", v)))?;
515 if n < result { result = n; }
516 }
517 Ok(to_json_number(result))
518 }
519
520 "cat" => {
522 let vals = eval_all(&args, data, ctx, fuel)?;
523 let s: String = vals.iter().map(|v| js_to_string(v)).collect();
524 Ok(Value::String(s))
525 }
526 "substr" => {
527 require_args_range(op, &args, 2, 3)?;
528 let vals = eval_all(&args, data, ctx, fuel)?;
529 eval_substr(&vals)
530 }
531
532 "merge" => {
534 let vals = eval_all(&args, data, ctx, fuel)?;
535 let merged: Vec<Value> = vals.into_iter().flat_map(|v| match v {
536 Value::Array(arr) => arr,
537 other => vec![other],
538 }).collect();
539 Ok(Value::Array(merged))
540 }
541 "in" => {
542 require_args(op, &args, 2, 2)?;
543 let vals = eval_all(&args, data, ctx, fuel)?;
544 let needle = &vals[0];
545 let haystack = &vals[1];
546 match haystack {
547 Value::Null => Ok(Value::Bool(false)),
548 Value::Array(arr) => Ok(Value::Bool(arr.contains(needle))),
549 Value::String(s) => {
550 match needle {
551 Value::String(ns) => Ok(Value::Bool(s.contains(ns.as_str()))),
552 _ => Err(ApplyError::InvalidArguments(
553 "If 'in' haystack is a string, needle must also be a string".into()
554 )),
555 }
556 }
557 _ => Err(ApplyError::InvalidArguments(format!(
558 "'in' haystack must be array or string, got {:?}", haystack
559 ))),
560 }
561 }
562
563 "map" => {
565 require_args(op, &args, 2, 2)?;
566 let items_raw = apply_value(args[0], data, ctx, fuel)?;
567 let items = coerce_to_array(items_raw)?;
568 let expr = args[1];
569 let result: Result<Vec<Value>, _> = items.iter()
570 .map(|item| apply_value(expr, item, ctx, fuel))
571 .collect();
572 Ok(Value::Array(result?))
573 }
574 "filter" => {
575 require_args(op, &args, 2, 2)?;
576 let items_raw = apply_value(args[0], data, ctx, fuel)?;
577 let items = coerce_to_array(items_raw)?;
578 let expr = args[1];
579 let mut out = Vec::new();
580 for item in &items {
581 let pred = apply_value(expr, item, ctx, fuel)?;
582 if truthy(&pred) { out.push(item.clone()); }
583 }
584 Ok(Value::Array(out))
585 }
586 "reduce" => {
587 require_args(op, &args, 3, 3)?;
588 let items_raw = apply_value(args[0], data, ctx, fuel)?;
589 let items = coerce_to_array(items_raw)?;
590 let expr = args[1];
591 let mut acc = apply_value(args[2], data, ctx, fuel)?;
592 for item in items {
593 let mut reduce_data = Map::with_capacity(2);
594 reduce_data.insert("current".into(), item);
595 reduce_data.insert("accumulator".into(), acc);
596 acc = apply_value(expr, &Value::Object(reduce_data), ctx, fuel)?;
597 }
598 Ok(acc)
599 }
600 "all" => {
601 require_args(op, &args, 2, 2)?;
602 let items_raw = args_to_iterable(args[0], data, ctx, fuel)?;
603 if items_raw.is_empty() { return Ok(Value::Bool(false)); }
604 let expr = args[1];
605 for item in &items_raw {
606 let pred = apply_value(expr, item, ctx, fuel)?;
607 if !truthy(&pred) { return Ok(Value::Bool(false)); }
608 }
609 Ok(Value::Bool(true))
610 }
611 "some" => {
612 require_args(op, &args, 2, 2)?;
613 let items_raw = args_to_iterable(args[0], data, ctx, fuel)?;
614 if items_raw.is_empty() { return Ok(Value::Bool(false)); }
615 let expr = args[1];
616 for item in &items_raw {
617 let pred = apply_value(expr, item, ctx, fuel)?;
618 if truthy(&pred) { return Ok(Value::Bool(true)); }
619 }
620 Ok(Value::Bool(false))
621 }
622 "none" => {
623 require_args(op, &args, 2, 2)?;
624 let items_raw = args_to_iterable(args[0], data, ctx, fuel)?;
625 if items_raw.is_empty() { return Ok(Value::Bool(true)); }
626 let expr = args[1];
627 for item in &items_raw {
628 let pred = apply_value(expr, item, ctx, fuel)?;
629 if truthy(&pred) { return Ok(Value::Bool(false)); }
630 }
631 Ok(Value::Bool(true))
632 }
633
634 "log" => {
636 require_args(op, &args, 1, 1)?;
637 let val = apply_value(args[0], data, ctx, fuel)?;
638 eprintln!("{}", val);
640 Ok(val)
641 }
642
643 unknown_op => {
645 if let Some(handler) = ctx.ops.get(unknown_op) {
646 let evaluated: Result<Vec<Value>, ApplyError> = args
647 .iter()
648 .map(|arg| apply_value(arg, data, ctx, fuel))
649 .collect();
650 let evaluated = evaluated?;
651 handler(&evaluated).map_err(|e| ApplyError::CustomOpFailed {
652 op: unknown_op.to_string(),
653 source: e.to_string(),
654 })
655 } else {
656 Err(ApplyError::UnknownOperator(unknown_op.to_string()))
657 }
658 }
659 }
660}
661
662fn require_args(op: &str, args: &[&Value], min: usize, max: usize) -> Result<(), ApplyError> {
667 if args.len() < min || args.len() > max {
668 Err(ApplyError::InvalidArguments(format!(
669 "'{}' requires {}-{} args, got {}", op, min, max, args.len()
670 )))
671 } else {
672 Ok(())
673 }
674}
675
676fn require_args_range(op: &str, args: &[&Value], min: usize, max: usize) -> Result<(), ApplyError> {
677 require_args(op, args, min, max)
678}
679
680fn eval_all(
681 args: &[&Value],
682 data: &Value,
683 ctx: &EvalCtx,
684 fuel: &mut usize,
685) -> Result<Vec<Value>, ApplyError> {
686 args.iter().map(|a| apply_value(a, data, ctx, fuel)).collect()
687}
688
689fn eval_if(
690 args: &[&Value],
691 data: &Value,
692 ctx: &EvalCtx,
693 fuel: &mut usize,
694) -> Result<Value, ApplyError> {
695 match args.len() {
696 0 => Ok(Value::Null),
697 1 => apply_value(args[0], data, ctx, fuel),
698 _ => {
699 let mut i = 0;
700 while i + 1 < args.len() {
701 let cond = apply_value(args[i], data, ctx, fuel)?;
702 if truthy(&cond) {
703 return apply_value(args[i + 1], data, ctx, fuel);
704 }
705 i += 2;
706 }
707 if i < args.len() {
709 apply_value(args[i], data, ctx, fuel)
710 } else {
711 Ok(Value::Null)
712 }
713 }
714 }
715}
716
717fn coerce_to_array(v: Value) -> Result<Vec<Value>, ApplyError> {
718 match v {
719 Value::Array(arr) => Ok(arr),
720 Value::Null => Ok(vec![]),
721 other => Err(ApplyError::InvalidArguments(format!(
722 "Expected array, got {:?}", other
723 ))),
724 }
725}
726
727fn args_to_iterable(
730 first_arg: &Value,
731 data: &Value,
732 ctx: &EvalCtx,
733 fuel: &mut usize,
734) -> Result<Vec<Value>, ApplyError> {
735 let evaled = apply_value(first_arg, data, ctx, fuel)?;
736 match evaled {
737 Value::Array(arr) => Ok(arr),
738 Value::String(s) => Ok(s.chars().map(|c| Value::String(c.to_string())).collect()),
739 Value::Null => Ok(vec![]),
740 other => Err(ApplyError::InvalidArguments(format!(
741 "First argument to all/some/none must be array, string, or null; got {:?}", other
742 ))),
743 }
744}
745
746fn eval_substr(vals: &[Value]) -> Result<Value, ApplyError> {
747 let s = match &vals[0] {
748 Value::String(s) => s,
749 other => return Err(ApplyError::InvalidArguments(format!(
750 "'substr' first arg must be string, got {:?}", other
751 ))),
752 };
753 let idx = match &vals[1] {
754 Value::Number(n) => n.as_i64().ok_or_else(|| {
755 ApplyError::InvalidArguments("'substr' index must be integer".into())
756 })?,
757 other => return Err(ApplyError::InvalidArguments(format!(
758 "'substr' index must be number, got {:?}", other
759 ))),
760 };
761 let limit = if vals.len() > 2 {
762 Some(match &vals[2] {
763 Value::Number(n) => n.as_i64().ok_or_else(|| {
764 ApplyError::InvalidArguments("'substr' limit must be integer".into())
765 })?,
766 other => return Err(ApplyError::InvalidArguments(format!(
767 "'substr' limit must be number, got {:?}", other
768 ))),
769 })
770 } else {
771 None
772 };
773
774 let chars: Vec<char> = s.chars().collect();
775 let len = chars.len();
776
777 let start = if idx < 0 {
778 len.saturating_sub((-idx) as usize)
779 } else {
780 (idx as usize).min(len)
781 };
782
783 let end = match limit {
784 None => len,
785 Some(l) if l < 0 => len.saturating_sub((-l) as usize),
786 Some(l) => (start + l as usize).min(len),
787 };
788
789 let count = end.saturating_sub(start);
790 Ok(Value::String(chars.iter().skip(start).take(count).collect()))
791}