1use crate::value::{deep_equals, Value};
15#[cfg(feature = "ir")]
16use serde_json::Value as J;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ExprFailureCode {
21 IntOverflow,
22 NanOrInf,
23 ModZero,
24 PrecisionLoss,
25 TypeMismatch,
26 NullRef,
27 MissingProp,
28 UnknownBinding,
29 UnknownOp,
30 InvalidNode,
31 InvalidLiteral,
32 ForbiddenKey,
33}
34
35pub const FORBIDDEN_OBJECT_KEY: &str = "__proto__";
40
41impl ExprFailureCode {
42 pub fn as_str(self) -> &'static str {
44 match self {
45 ExprFailureCode::IntOverflow => "INT_OVERFLOW",
46 ExprFailureCode::NanOrInf => "NAN_OR_INF",
47 ExprFailureCode::ModZero => "MOD_ZERO",
48 ExprFailureCode::PrecisionLoss => "PRECISION_LOSS",
49 ExprFailureCode::TypeMismatch => "TYPE_MISMATCH",
50 ExprFailureCode::NullRef => "NULL_REF",
51 ExprFailureCode::MissingProp => "MISSING_PROP",
52 ExprFailureCode::UnknownBinding => "UNKNOWN_BINDING",
53 ExprFailureCode::UnknownOp => "UNKNOWN_OP",
54 ExprFailureCode::InvalidNode => "INVALID_NODE",
55 ExprFailureCode::InvalidLiteral => "INVALID_LITERAL",
56 ExprFailureCode::ForbiddenKey => "FORBIDDEN_KEY",
57 }
58 }
59}
60
61#[derive(Debug, Clone)]
62pub struct ExprFailure {
63 pub code: ExprFailureCode,
64 pub message: String,
65 pub detail: Option<Box<crate::plan::ErrorDetail>>,
69}
70
71impl std::fmt::Display for ExprFailure {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(f, "{}: {}", self.code.as_str(), self.message)
74 }
75}
76impl std::error::Error for ExprFailure {}
77
78type R = Result<Value, ExprFailure>;
79
80fn fail<T>(code: ExprFailureCode, message: impl Into<String>) -> Result<T, ExprFailure> {
81 Err(ExprFailure {
82 code,
83 message: message.into(),
84 detail: None,
85 })
86}
87
88const WIDEN_EXACT: i64 = 1 << 53; fn check_finite(v: f64) -> R {
91 if v.is_finite() {
92 Ok(Value::Float(v))
93 } else {
94 fail(ExprFailureCode::NanOrInf, format!("non-finite float: {v}"))
95 }
96}
97
98fn widen_to_float(v: &Value) -> Result<f64, ExprFailure> {
99 match v {
100 Value::Float(f) => Ok(*f),
101 Value::Int(i) => {
102 if *i > WIDEN_EXACT || *i < -WIDEN_EXACT {
103 fail(
104 ExprFailureCode::PrecisionLoss,
105 format!("int {i} exceeds exact float range (±2^53)"),
106 )
107 } else {
108 Ok(*i as f64)
109 }
110 }
111 other => fail(
112 ExprFailureCode::TypeMismatch,
113 format!("numeric operand expected, got {}", other.type_name()),
114 ),
115 }
116}
117
118pub fn cmp_code_points(a: &str, b: &str) -> std::cmp::Ordering {
122 a.cmp(b)
123}
124
125pub(crate) fn require_bool(v: &Value, ctx: &str) -> Result<bool, ExprFailure> {
126 match v {
127 Value::Bool(b) => Ok(*b),
128 other => fail(
129 ExprFailureCode::TypeMismatch,
130 format!(
131 "{ctx}: bool expected, got {} (no truthiness)",
132 other.type_name()
133 ),
134 ),
135 }
136}
137
138pub(crate) fn arith(op: &str, a: &Value, b: &Value) -> R {
145 match (a, b) {
146 (Value::Int(x), Value::Int(y)) => {
147 let r = match op {
148 "add" => x.checked_add(*y),
149 "sub" => x.checked_sub(*y),
150 _ => x.checked_mul(*y),
151 };
152 match r {
153 Some(v) => Ok(Value::Int(v)),
154 None => fail(
155 ExprFailureCode::IntOverflow,
156 format!("i64 overflow in {op}"),
157 ),
158 }
159 }
160 (Value::Float(x), Value::Float(y)) => {
161 let r = match op {
162 "add" => x + y,
163 "sub" => x - y,
164 _ => x * y,
165 };
166 check_finite(r)
167 }
168 _ => fail(
169 ExprFailureCode::TypeMismatch,
170 format!(
171 "{op}: int×int or float×float (got {}×{})",
172 a.type_name(),
173 b.type_name()
174 ),
175 ),
176 }
177}
178
179pub(crate) fn neg(a: &Value) -> R {
181 match a {
182 Value::Int(i) => match i.checked_neg() {
183 Some(v) => Ok(Value::Int(v)),
184 None => fail(ExprFailureCode::IntOverflow, "i64 overflow in neg"),
185 },
186 Value::Float(f) => check_finite(-f),
187 other => fail(
188 ExprFailureCode::TypeMismatch,
189 format!("neg: numeric expected, got {}", other.type_name()),
190 ),
191 }
192}
193
194pub(crate) fn div(a: &Value, b: &Value) -> R {
196 let fa = widen_to_float(a)?;
197 let fb = widen_to_float(b)?;
198 check_finite(fa / fb)
199}
200
201pub(crate) fn rem(a: &Value, b: &Value) -> R {
203 match (a, b) {
204 (Value::Int(x), Value::Int(y)) => {
205 if *y == 0 {
206 return fail(ExprFailureCode::ModZero, "int mod by zero");
207 }
208 match x.checked_rem(*y) {
209 Some(v) => Ok(Value::Int(v)),
210 None => fail(ExprFailureCode::IntOverflow, "i64 overflow in mod"),
211 }
212 }
213 (Value::Float(x), Value::Float(y)) => check_finite(x % y),
214 _ => fail(
215 ExprFailureCode::TypeMismatch,
216 format!(
217 "mod: int×int or float×float (got {}×{})",
218 a.type_name(),
219 b.type_name()
220 ),
221 ),
222 }
223}
224
225pub(crate) fn eq_ne(op: &str, a: &Value, b: &Value) -> R {
227 let equal = value_equals(a, b)?;
228 Ok(Value::Bool(if op == "eq" { equal } else { !equal }))
229}
230
231pub(crate) fn compare(op: &str, a: &Value, b: &Value) -> R {
233 use std::cmp::Ordering;
234 let c: Ordering = match (a, b) {
235 (Value::Int(x), Value::Int(y)) => x.cmp(y),
236 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
237 (Value::Str(x), Value::Str(y)) => cmp_code_points(x, y),
238 _ => {
239 return fail(
240 ExprFailureCode::TypeMismatch,
241 format!(
242 "{op}: same-typed int/float/string only (got {}×{})",
243 a.type_name(),
244 b.type_name()
245 ),
246 )
247 }
248 };
249 let res = match op {
250 "lt" => c == Ordering::Less,
251 "le" => c != Ordering::Greater,
252 "gt" => c == Ordering::Greater,
253 _ => c != Ordering::Less,
254 };
255 Ok(Value::Bool(res))
256}
257
258pub(crate) fn make_obj(pairs: Vec<(String, Value)>) -> R {
261 for (k, _) in &pairs {
262 if k == FORBIDDEN_OBJECT_KEY {
263 return fail(
264 ExprFailureCode::ForbiddenKey,
265 format!("obj key \"{FORBIDDEN_OBJECT_KEY}\" is forbidden (fail-closed)"),
266 );
267 }
268 }
269 Ok(Value::Obj(pairs))
270}
271
272pub(crate) fn len(a: &Value) -> R {
274 match a {
275 Value::Arr(v) => Ok(Value::Int(v.len() as i64)),
276 other => fail(
277 ExprFailureCode::TypeMismatch,
278 format!(
279 "len: arrays only (string length is not v1; got {})",
280 other.type_name()
281 ),
282 ),
283 }
284}
285
286const SAFE_INT: i64 = 9_007_199_254_740_991; pub(crate) fn int_lit(s: &str) -> R {
290 match s.parse::<i64>() {
291 Ok(v) => Ok(Value::Int(v)),
292 Err(_) => {
293 if s.trim_start_matches('-')
294 .chars()
295 .all(|c| c.is_ascii_digit())
296 && !s.is_empty()
297 && s != "-"
298 {
299 fail(ExprFailureCode::IntOverflow, format!("i64 overflow: {s}"))
300 } else {
301 fail(
302 ExprFailureCode::InvalidLiteral,
303 format!("invalid int literal: {s}"),
304 )
305 }
306 }
307 }
308}
309
310pub(crate) fn float_lit(n: f64) -> R {
312 check_finite(n)
313}
314
315pub(crate) fn number_lit(n: f64) -> R {
317 if n.is_finite() && n.fract() == 0.0 {
318 if n.abs() <= SAFE_INT as f64 {
319 Ok(Value::Int(n as i64))
320 } else {
321 fail(
322 ExprFailureCode::InvalidLiteral,
323 format!("integral literal {n} exceeds safe range; use {{int:\"…\"}}"),
324 )
325 }
326 } else {
327 check_finite(n)
328 }
329}
330
331#[cfg(feature = "ir")]
333pub fn evaluate(node: &J, scope: &[(String, Value)]) -> R {
334 match node {
335 J::Null => Ok(Value::Null),
336 J::Bool(b) => Ok(Value::Bool(*b)),
337 J::String(s) => Ok(Value::Str(s.clone())),
338 J::Number(n) => {
339 if n.is_i64() {
341 let i = n.as_i64().unwrap();
342 const SAFE: i64 = 9_007_199_254_740_991;
344 if !(-SAFE..=SAFE).contains(&i) {
345 return fail(
346 ExprFailureCode::InvalidLiteral,
347 format!("integral literal {i} exceeds safe range; use {{int:\"…\"}}"),
348 );
349 }
350 Ok(Value::Int(i))
351 } else if n.is_u64() {
352 fail(
354 ExprFailureCode::InvalidLiteral,
355 format!("integral literal {n} exceeds safe range; use {{int:\"…\"}}"),
356 )
357 } else {
358 let f = n.as_f64().ok_or(ExprFailure {
359 code: ExprFailureCode::InvalidLiteral,
360 message: format!("bad number literal {n}"),
361 detail: None,
362 })?;
363 check_finite(f)
364 }
365 }
366 J::Array(_) => fail(
367 ExprFailureCode::InvalidNode,
368 "bare array is not an expression (use {arr:[...]})",
369 ),
370 J::Object(map) => {
371 if map.len() != 1 {
372 let keys: Vec<&str> = map.keys().map(|s| s.as_str()).collect();
373 return fail(
374 ExprFailureCode::InvalidNode,
375 format!(
376 "operator node must have exactly one key, got [{}]",
377 keys.join(", ")
378 ),
379 );
380 }
381 let (op, arg) = map.iter().next().unwrap();
382 eval_op(op, arg, scope)
383 }
384 }
385}
386
387#[cfg(feature = "ir")]
388fn eval_op(op: &str, arg: &J, scope: &[(String, Value)]) -> R {
389 match op {
390 "int" => {
391 let s = arg.as_str().ok_or_else(|| ExprFailure {
392 code: ExprFailureCode::InvalidNode,
393 message: "{int:…} expects a string".into(),
394 detail: None,
395 })?;
396 int_lit(s)
397 }
398 "float" => {
399 let n = arg.as_f64().ok_or_else(|| ExprFailure {
400 code: ExprFailureCode::InvalidNode,
401 message: "{float:…} expects a number".into(),
402 detail: None,
403 })?;
404 float_lit(n)
405 }
406 "ref" | "refOpt" => eval_ref(op, arg, scope),
407 "obj" => {
408 let m = arg.as_object().ok_or_else(|| ExprFailure {
409 code: ExprFailureCode::InvalidNode,
410 message: "{obj:…} expects an object".into(),
411 detail: None,
412 })?;
413 let mut out = Vec::with_capacity(m.len());
416 for (k, v) in m {
417 if k == FORBIDDEN_OBJECT_KEY {
418 return fail(
419 ExprFailureCode::ForbiddenKey,
420 format!("obj key \"{FORBIDDEN_OBJECT_KEY}\" is forbidden (fail-closed)"),
421 );
422 }
423 out.push((k.clone(), evaluate(v, scope)?));
424 }
425 Ok(Value::Obj(out))
426 }
427 "arr" => {
428 let a = arg_array(op, arg)?;
429 let mut out = Vec::with_capacity(a.len());
430 for e in a {
431 out.push(evaluate(e, scope)?);
432 }
433 Ok(Value::Arr(out))
434 }
435 "add" | "sub" | "mul" => {
436 let (a, b) = eval_binary(op, arg, scope)?;
437 arith(op, &a, &b)
438 }
439 "neg" => {
440 let a = evaluate(arg_unary(op, arg)?, scope)?;
441 neg(&a)
442 }
443 "div" => {
444 let (a, b) = eval_binary(op, arg, scope)?;
445 div(&a, &b)
446 }
447 "mod" => {
448 let (a, b) = eval_binary(op, arg, scope)?;
449 rem(&a, &b)
450 }
451 "concat" => {
452 let a = arg_array(op, arg)?;
453 if a.len() < 2 {
455 return fail(
456 ExprFailureCode::InvalidNode,
457 format!("concat expects >= 2 args, got {}", a.len()),
458 );
459 }
460 let mut s = String::new();
461 for e in a {
462 match evaluate(e, scope)? {
463 Value::Str(p) => s.push_str(&p),
464 other => {
465 return fail(
466 ExprFailureCode::TypeMismatch,
467 format!(
468 "concat: strings only (got {}; no implicit toString)",
469 other.type_name()
470 ),
471 )
472 }
473 }
474 }
475 Ok(Value::Str(s))
476 }
477 "eq" | "ne" => {
478 let (a, b) = eval_binary(op, arg, scope)?;
479 eq_ne(op, &a, &b)
480 }
481 "lt" | "le" | "gt" | "ge" => {
482 let (a, b) = eval_binary(op, arg, scope)?;
483 compare(op, &a, &b)
484 }
485 "and" | "or" => {
486 let (ea, eb) = raw_binary(op, arg)?;
487 let a = require_bool(&evaluate(ea, scope)?, op)?;
488 if op == "and" && !a {
489 return Ok(Value::Bool(false));
490 }
491 if op == "or" && a {
492 return Ok(Value::Bool(true));
493 }
494 Ok(Value::Bool(require_bool(&evaluate(eb, scope)?, op)?))
495 }
496 "not" => {
497 let a = require_bool(&evaluate(arg_unary(op, arg)?, scope)?, "not")?;
498 Ok(Value::Bool(!a))
499 }
500 "coalesce" => {
501 let (ea, eb) = raw_binary(op, arg)?;
502 let a = evaluate(ea, scope)?;
503 match a {
504 Value::Null => evaluate(eb, scope),
505 other => Ok(other),
506 }
507 }
508 "cond" => {
509 let a = arg
510 .as_array()
511 .filter(|a| a.len() == 3)
512 .ok_or_else(|| ExprFailure {
513 code: ExprFailureCode::InvalidNode,
514 message: "cond expects [c, t, e]".into(),
515 detail: None,
516 })?;
517 let c = require_bool(&evaluate(&a[0], scope)?, "cond")?;
518 evaluate(if c { &a[1] } else { &a[2] }, scope)
519 }
520 "len" => {
521 let a = evaluate(arg_unary(op, arg)?, scope)?;
522 len(&a)
523 }
524 _ => fail(
525 ExprFailureCode::UnknownOp,
526 format!("unknown operator: {op} (fail-closed)"),
527 ),
528 }
529}
530
531#[cfg(feature = "ir")]
532fn eval_ref(op: &str, arg: &J, scope: &[(String, Value)]) -> R {
533 let path = arg_array(op, arg)?;
534 if path.is_empty() || !path.iter().all(|p| p.is_string()) {
535 return fail(
536 ExprFailureCode::InvalidNode,
537 format!("{op} expects a non-empty string path"),
538 );
539 }
540 let head = path[0].as_str().unwrap();
541 let mut cur: Value = match scope.iter().find(|(k, _)| k == head) {
542 Some((_, v)) => v.clone(),
543 None => {
544 return fail(
545 ExprFailureCode::UnknownBinding,
546 format!("unknown binding: {head}"),
547 )
548 }
549 };
550 for seg_node in &path[1..] {
551 let seg = seg_node.as_str().unwrap();
552 match cur {
553 Value::Null => {
554 if op == "refOpt" {
555 return Ok(Value::Null);
556 }
557 return fail(
558 ExprFailureCode::NullRef,
559 format!("null intermediate at .{seg} (use ?.)"),
560 );
561 }
562 Value::Obj(ref pairs) => match pairs.iter().find(|(k, _)| k == seg) {
563 Some((_, v)) => {
564 let next = v.clone();
565 cur = next;
566 }
567 None => {
568 return fail(
569 ExprFailureCode::MissingProp,
570 format!("missing property .{seg}"),
571 )
572 }
573 },
574 ref other => {
575 return fail(
576 ExprFailureCode::TypeMismatch,
577 format!("cannot access .{seg} on {}", other.type_name()),
578 )
579 }
580 }
581 }
582 Ok(cur)
583}
584
585fn value_equals(a: &Value, b: &Value) -> Result<bool, ExprFailure> {
587 if matches!(a, Value::Null) || matches!(b, Value::Null) {
588 return Ok(matches!(a, Value::Null) && matches!(b, Value::Null));
589 }
590 let ta = a.type_name();
591 let tb = b.type_name();
592 if ta != tb {
593 return fail(
594 ExprFailureCode::TypeMismatch,
595 format!("eq/ne: same type only (got {ta}×{tb})"),
596 );
597 }
598 if ta == "arr" || ta == "obj" {
599 return fail(
600 ExprFailureCode::TypeMismatch,
601 "eq/ne: obj/arr equality is undefined in v1",
602 );
603 }
604 Ok(deep_equals(a, b))
605}
606
607#[cfg(feature = "ir")]
609fn arg_array<'a>(op: &str, arg: &'a J) -> Result<&'a Vec<J>, ExprFailure> {
610 arg.as_array().ok_or_else(|| ExprFailure {
611 code: ExprFailureCode::InvalidNode,
612 message: format!("{op} expects an args array"),
613 detail: None,
614 })
615}
616#[cfg(feature = "ir")]
617fn arg_unary<'a>(op: &str, arg: &'a J) -> Result<&'a J, ExprFailure> {
618 let a = arg_array(op, arg)?;
619 if a.len() != 1 {
620 return Err(ExprFailure {
621 code: ExprFailureCode::InvalidNode,
622 message: format!("{op} expects 1 arg"),
623 detail: None,
624 });
625 }
626 Ok(&a[0])
627}
628#[cfg(feature = "ir")]
629fn raw_binary<'a>(op: &str, arg: &'a J) -> Result<(&'a J, &'a J), ExprFailure> {
630 let a = arg_array(op, arg)?;
631 if a.len() != 2 {
632 return Err(ExprFailure {
633 code: ExprFailureCode::InvalidNode,
634 message: format!("{op} expects 2 args"),
635 detail: None,
636 });
637 }
638 Ok((&a[0], &a[1]))
639}
640#[cfg(feature = "ir")]
641fn eval_binary(
642 op: &str,
643 arg: &J,
644 scope: &[(String, Value)],
645) -> Result<(Value, Value), ExprFailure> {
646 let (ea, eb) = raw_binary(op, arg)?;
647 Ok((evaluate(ea, scope)?, evaluate(eb, scope)?))
648}