Skip to main content

openjd_expr/
value.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// Copyright by contributors to this project.
3// SPDX-License-Identifier: (Apache-2.0 OR MIT)
4
5//! Runtime values for expression evaluation.
6
7use crate::path_mapping::PathFormat;
8use crate::range_expr::RangeExpr;
9use crate::types::{ExprType, TypeCode};
10
11/// A float with optional original string representation for passthrough.
12/// 16 bytes: 8 for f64, 8 for `Option<Box<str>>` (NULL or heap pointer).
13///
14/// Fields are private. Construction goes through [`Float64::new`] or
15/// [`Float64::with_str`], which enforce the no-NaN / no-Inf / no-`-0.0`
16/// invariants that the `Hash` and `PartialEq` impls on `ExprValue` depend on.
17#[derive(Debug, Clone, serde::Serialize)]
18pub struct Float64 {
19    value: f64,
20    original: Option<Box<str>>,
21}
22
23impl std::hash::Hash for Float64 {
24    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
25        self.value.to_bits().hash(state);
26    }
27}
28
29/// Normalize -0.0 to 0.0 (matches Python's copysign normalization).
30fn normalize_zero(v: f64) -> f64 {
31    if v == 0.0 {
32        0.0
33    } else {
34        v
35    }
36}
37
38impl Float64 {
39    /// Create a new `Float64`, rejecting NaN and infinity, normalizing -0.0 to 0.0.
40    pub fn new(v: f64) -> Result<Self, crate::error::ExpressionError> {
41        let v = normalize_zero(v);
42        if v.is_nan() {
43            return Err(crate::error::ExpressionError::float_error(
44                "Float operation produced NaN",
45            ));
46        }
47        if v.is_infinite() {
48            return Err(crate::error::ExpressionError::float_error(
49                "Float operation produced infinity",
50            ));
51        }
52        Ok(Self {
53            value: v,
54            original: None,
55        })
56    }
57    /// Create a `Float64` preserving the original string representation for lossless display.
58    pub fn with_str(v: f64, s: String) -> Result<Self, crate::error::ExpressionError> {
59        let v = normalize_zero(v);
60        if v.is_nan() {
61            return Err(crate::error::ExpressionError::float_error(
62                "Float operation produced NaN",
63            ));
64        }
65        if v.is_infinite() {
66            return Err(crate::error::ExpressionError::float_error(
67                "Float operation produced infinity",
68            ));
69        }
70        Ok(Self {
71            value: v,
72            original: if v == 0.0 && s != "0.0" {
73                None
74            } else {
75                Some(s.into_boxed_str())
76            },
77        })
78    }
79    /// The underlying `f64` value.
80    pub fn value(&self) -> f64 {
81        self.value
82    }
83    /// Display string: the original literal if preserved, otherwise formatted.
84    pub fn to_display_string(&self) -> String {
85        if let Some(s) = &self.original {
86            s.to_string()
87        } else {
88            format_float(self.value)
89        }
90    }
91}
92
93impl std::fmt::Display for Float64 {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(f, "{}", self.to_display_string())
96    }
97}
98
99impl std::ops::Deref for Float64 {
100    type Target = f64;
101    fn deref(&self) -> &f64 {
102        &self.value
103    }
104}
105
106impl PartialEq<f64> for Float64 {
107    fn eq(&self, other: &f64) -> bool {
108        self.value == *other
109    }
110}
111
112impl PartialOrd<f64> for Float64 {
113    fn partial_cmp(&self, other: &f64) -> Option<std::cmp::Ordering> {
114        self.value.partial_cmp(other)
115    }
116}
117
118/// A typed value during expression evaluation.
119///
120/// `#[non_exhaustive]` because future revisions or extensions may add
121/// new primitive types (e.g., `Duration`, `Url`, `Decimal`). Adding a
122/// variant must not be a breaking change for downstream crates that
123/// match on this enum. The `Path` variant has its own `#[non_exhaustive]`
124/// attribute, which serves a separate purpose (preventing direct
125/// struct-literal construction so that `ExprValue::new_path` can
126/// enforce the separator-normalization invariant).
127#[derive(Debug, Clone, serde::Serialize)]
128#[non_exhaustive]
129pub enum ExprValue {
130    Null,
131    Bool(bool),
132    Int(i64),
133    Float(Float64),
134    String(String),
135    /// A PATH value — a string path together with its format.
136    ///
137    /// `#[non_exhaustive]` prevents direct construction outside this crate;
138    /// downstream callers must use [`ExprValue::new_path`], which enforces
139    /// the separator-normalization invariant (`\` ↔ `/` per `PathFormat`,
140    /// and no normalization for URI paths). The fields remain visible for
141    /// pattern matching (using `..` is required from outside the crate).
142    #[non_exhaustive]
143    Path {
144        value: String,
145        format: PathFormat,
146    },
147    // Typed list variants (new)
148    ListBool(Vec<bool>),
149    ListInt(Vec<i64>),
150    ListFloat(Vec<Float64>),
151    ListString(Vec<String>, usize), // (elements, cached_memory_size)
152    ListPath(Vec<String>, PathFormat, usize), // (elements, format, cached_memory_size)
153    ListList(Vec<ExprValue>, ExprType, usize), // (elements, element_type_hint, cached_memory_size)
154    RangeExpr(RangeExpr),
155    Unresolved(ExprType),
156}
157
158impl std::hash::Hash for ExprValue {
159    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
160        // Must be consistent with PartialEq (which uses equals()):
161        // Int(1) == Float(1.0), String("x") == Path{value:"x",...}
162        // Empty lists of any type are equal, so they must hash identically.
163        match self {
164            Self::Null => 0u8.hash(state),
165            Self::Bool(b) => {
166                1u8.hash(state);
167                b.hash(state);
168            }
169            // Int hashes with integer tag + raw i64 bits.
170            Self::Int(i) => {
171                2u8.hash(state);
172                i.hash(state);
173            }
174            // Float hashes as Int when it's an exact integer in i64 range,
175            // otherwise uses float tag + f64 bits.
176            Self::Float(f) => {
177                let v = f.value;
178                if v.fract() == 0.0 && v >= i64::MIN as f64 && v <= i64::MAX as f64 {
179                    2u8.hash(state);
180                    (v as i64).hash(state);
181                } else {
182                    12u8.hash(state);
183                    v.to_bits().hash(state);
184                }
185            }
186            // String and Path hash the same way so they match
187            Self::String(s) => {
188                3u8.hash(state);
189                s.hash(state);
190            }
191            Self::Path { value, .. } => {
192                3u8.hash(state);
193                value.hash(state);
194            }
195            // All list types use discriminant 4 so empty lists hash equally.
196            // Elements are hashed via their ExprValue-equivalent hash to maintain
197            // consistency with cross-type equality (e.g. ListInt([1]) == ListFloat([1.0])).
198            Self::ListBool(v) => {
199                4u8.hash(state);
200                for b in v {
201                    1u8.hash(state);
202                    b.hash(state);
203                }
204            }
205            Self::ListInt(v) => {
206                4u8.hash(state);
207                for i in v {
208                    2u8.hash(state);
209                    i.hash(state);
210                }
211            }
212            Self::ListFloat(v) => {
213                4u8.hash(state);
214                for f in v {
215                    let fv = f.value;
216                    if fv.fract() == 0.0 && fv >= i64::MIN as f64 && fv <= i64::MAX as f64 {
217                        2u8.hash(state);
218                        (fv as i64).hash(state);
219                    } else {
220                        12u8.hash(state);
221                        fv.to_bits().hash(state);
222                    }
223                }
224            }
225            Self::ListString(v, _) => {
226                4u8.hash(state);
227                for s in v {
228                    3u8.hash(state);
229                    s.hash(state);
230                }
231            }
232            Self::ListPath(v, _, _) => {
233                4u8.hash(state);
234                for s in v {
235                    3u8.hash(state);
236                    s.hash(state);
237                }
238            }
239            Self::ListList(v, _, _) => {
240                4u8.hash(state);
241                for e in v {
242                    e.hash(state);
243                }
244            }
245            Self::RangeExpr(r) => {
246                10u8.hash(state);
247                r.hash(state);
248            }
249            Self::Unresolved(t) => {
250                11u8.hash(state);
251                t.hash(state);
252            }
253        }
254    }
255}
256
257impl Eq for ExprValue {}
258
259impl ExprValue {
260    /// Create a list, promoting elements as needed. Produces old List variant for compatibility.
261    fn make_list_string(v: Vec<String>) -> Self {
262        let heap =
263            v.len() * std::mem::size_of::<String>() + v.iter().map(|s| s.len()).sum::<usize>();
264        Self::ListString(v, heap)
265    }
266    fn make_list_path(v: Vec<String>, fmt: PathFormat) -> Self {
267        let heap =
268            v.len() * std::mem::size_of::<String>() + v.iter().map(|s| s.len()).sum::<usize>();
269        Self::ListPath(v, fmt, heap)
270    }
271    fn make_list_list(v: Vec<ExprValue>, elem_hint: ExprType) -> Self {
272        // Vec buffer holds ExprValues inline; only count their additional heap allocations
273        let heap = v.len() * std::mem::size_of::<ExprValue>()
274            + v.iter().map(|e| e.heap_size()).sum::<usize>();
275        let elem_type = v.first().map(|e| e.expr_type()).unwrap_or(elem_hint);
276        Self::ListList(v, elem_type, heap)
277    }
278
279    /// Estimate the heap allocation required to build a list from `elements`.
280    ///
281    /// Upper bound on the `heap_size()` of the resulting list — ignores the
282    /// type-promotion shortcuts in [`make_list`](Self::make_list) that can
283    /// shrink the final footprint (e.g. collapsing `ListInt` elements into
284    /// a single `ListFloat`). Treats the worst case of storing every
285    /// element through a `ListList`, which is what a heterogeneous input
286    /// ultimately materializes to.
287    ///
288    /// Used by [`make_list_checked`](Self::make_list_checked) to fail a
289    /// memory-bounded evaluator cleanly before the list allocation
290    /// happens, rather than after.
291    fn estimate_list_heap_size(elements: &[ExprValue]) -> usize {
292        let per_slot = std::mem::size_of::<ExprValue>();
293        elements
294            .iter()
295            .fold(elements.len().saturating_mul(per_slot), |acc, e| {
296                acc.saturating_add(e.heap_size())
297            })
298    }
299
300    /// Memory-checked variant of [`make_list`](Self::make_list).
301    ///
302    /// Pre-checks the evaluator's memory budget against an upper-bound
303    /// estimate of the list's heap footprint before any allocation occurs.
304    /// This is the defense-in-depth path: call sites that have an
305    /// [`EvalContext`](crate::function_library::EvalContext) available
306    /// should prefer this over [`make_list`](Self::make_list) so that a
307    /// memory-bounded evaluator fails cleanly on oversized intermediate
308    /// lists — even from code paths that did not charge ops proportionally
309    /// to the list size.
310    ///
311    /// Type promotion and nesting validation are otherwise identical to
312    /// [`make_list`](Self::make_list); this function forwards to it after
313    /// the memory check passes.
314    pub fn make_list_checked(
315        ctx: &mut dyn crate::function_library::EvalContext,
316        elements: Vec<ExprValue>,
317        hint_type: ExprType,
318    ) -> Result<Self, crate::error::ExpressionError> {
319        ctx.check_memory(Self::estimate_list_heap_size(&elements))?;
320        Self::make_list(elements, hint_type)
321    }
322
323    /// Construct a typed list from heterogeneous elements.
324    ///
325    /// Applies type promotion rules: int+float→float, path+string→string.
326    /// Uses `hint_type` for empty lists to determine the element type.
327    /// Returns an error if any element is a `ListList`, which would create 3+ nesting levels.
328    ///
329    /// When called from an evaluator or function implementation that has
330    /// an [`EvalContext`](crate::function_library::EvalContext), prefer
331    /// [`make_list_checked`](Self::make_list_checked) so that an oversized
332    /// intermediate list fails the evaluator's memory limit before the
333    /// allocation happens.
334    pub fn make_list(
335        mut elements: Vec<ExprValue>,
336        hint_type: ExprType,
337    ) -> Result<Self, crate::error::ExpressionError> {
338        // Reject 3+ nesting levels: if any element is itself a ListList with a
339        // non-nulltype element type, that's too deep. Empty lists (ListList with
340        // NULLTYPE) represent `list[nulltype]` — a flat empty list, not a nested one.
341        if elements
342            .iter()
343            .any(|e| matches!(e, Self::ListList(_, et, _) if *et != ExprType::NULLTYPE))
344        {
345            return Err(crate::error::ExpressionError::new(
346                "Lists may be nested at most 2 levels deep",
347            ));
348        }
349        // Convert empty ListList([], NULLTYPE) elements to match typed list siblings.
350        // e.g. in [[], [1]], the empty [] should become ListInt([]) not ListList([], NULLTYPE).
351        let has_empty_listlist = elements.iter().any(
352            |e| matches!(e, Self::ListList(v, et, _) if v.is_empty() && *et == ExprType::NULLTYPE),
353        );
354        if has_empty_listlist {
355            // Find the first typed list sibling to determine the target variant
356            let sibling_code = elements.iter().find_map(|e| match e {
357                Self::ListBool(v) if !v.is_empty() => Some(crate::types::TypeCode::Bool),
358                Self::ListInt(v) if !v.is_empty() => Some(crate::types::TypeCode::Int),
359                Self::ListFloat(_) => Some(crate::types::TypeCode::Float),
360                Self::ListString(v, _) if !v.is_empty() => Some(crate::types::TypeCode::String),
361                Self::ListPath(v, _, _) if !v.is_empty() => Some(crate::types::TypeCode::Path),
362                _ => None,
363            });
364            if let Some(code) = sibling_code {
365                for e in &mut elements {
366                    if matches!(e, Self::ListList(v, et, _) if v.is_empty() && *et == ExprType::NULLTYPE)
367                    {
368                        *e = match code {
369                            crate::types::TypeCode::Bool => Self::ListBool(Vec::new()),
370                            crate::types::TypeCode::Int => Self::ListInt(Vec::new()),
371                            crate::types::TypeCode::Float => Self::ListFloat(Vec::new()),
372                            crate::types::TypeCode::String => Self::ListString(Vec::new(), 0),
373                            crate::types::TypeCode::Path => {
374                                Self::make_list_path(Vec::new(), PathFormat::host())
375                            }
376                            _ => continue,
377                        };
378                    }
379                }
380            }
381        }
382        if elements.is_empty() {
383            // Empty lists are list[nulltype], compatible with any list type.
384            // When a concrete hint is provided, use the matching typed variant
385            // so that subsequent operations (e.g. append) preserve the type.
386            // Otherwise (Null or unknown hint), use ListList with NULLTYPE as the
387            // canonical empty list representation, compatible with any list type.
388            return Ok(match hint_type.code() {
389                crate::types::TypeCode::Bool => Self::ListBool(Vec::new()),
390                crate::types::TypeCode::Int => Self::ListInt(Vec::new()),
391                crate::types::TypeCode::Float => Self::ListFloat(Vec::new()),
392                crate::types::TypeCode::Path => {
393                    Self::make_list_path(Vec::new(), PathFormat::host())
394                }
395                crate::types::TypeCode::List => Self::make_list_list(Vec::new(), hint_type),
396                crate::types::TypeCode::String => Self::ListString(Vec::new(), 0),
397                crate::types::TypeCode::NullType => {
398                    Self::ListList(Vec::new(), ExprType::NULLTYPE, 0)
399                }
400                _ => Self::ListList(Vec::new(), ExprType::NULLTYPE, 0),
401            });
402        }
403        let has_int = elements.iter().any(|e| matches!(e, Self::Int(_)));
404        let has_float = elements.iter().any(|e| matches!(e, Self::Float(_)));
405        if has_int && has_float {
406            for e in &mut elements {
407                if let Self::Int(i) = e {
408                    *e = Self::Float(Float64::new(*i as f64).unwrap());
409                }
410            }
411            return Ok(Self::ListFloat(
412                elements
413                    .into_iter()
414                    .map(|e| match e {
415                        Self::Float(f) => f,
416                        _ => unreachable!("all elements promoted to Float above"),
417                    })
418                    .collect(),
419            ));
420        }
421        let has_list_int = elements
422            .iter()
423            .any(|e| e.is_list() && e.list_elem_type() == Some(ExprType::INT));
424        let has_list_float = elements
425            .iter()
426            .any(|e| e.is_list() && e.list_elem_type() == Some(ExprType::FLOAT));
427        if has_list_int && has_list_float {
428            for e in &mut elements {
429                if let Self::ListInt(ints) = e {
430                    *e = Self::ListFloat(
431                        ints.iter()
432                            .map(|i| Float64::new(*i as f64).unwrap())
433                            .collect(),
434                    );
435                }
436            }
437            return Ok(Self::make_list_list(elements, ExprType::NULLTYPE));
438        }
439        // Nested list path/string promotion: list[path] + list[string] → list[string]
440        let has_list_path = elements
441            .iter()
442            .any(|e| e.is_list() && e.list_elem_type() == Some(ExprType::PATH));
443        let has_list_string = elements
444            .iter()
445            .any(|e| e.is_list() && e.list_elem_type() == Some(ExprType::STRING));
446        if has_list_path && has_list_string {
447            for e in &mut elements {
448                if let Self::ListPath(paths, _, _) = e {
449                    *e = Self::make_list_string(std::mem::take(paths));
450                }
451            }
452            return Ok(Self::make_list_list(elements, ExprType::NULLTYPE));
453        }
454        // Path/string promotion: mix of path and string → string
455        let has_path = elements.iter().any(|e| matches!(e, Self::Path { .. }));
456        let has_string = elements.iter().any(|e| matches!(e, Self::String(_)));
457        if has_path && has_string {
458            return Ok(Self::make_list_string(
459                elements
460                    .into_iter()
461                    .map(|e| match e {
462                        Self::String(s) | Self::Path { value: s, .. } => s,
463                        _ => e.to_display_string(),
464                    })
465                    .collect(),
466            ));
467        }
468        Ok(match &elements[0] {
469            Self::Bool(_) => Self::ListBool(
470                elements
471                    .into_iter()
472                    .map(|e| match e {
473                        Self::Bool(b) => Ok(b),
474                        _ => Err(crate::error::ExpressionError::type_error(format!(
475                            "make_list expected bool element, got {}",
476                            e.type_name()
477                        ))),
478                    })
479                    .collect::<Result<_, _>>()?,
480            ),
481            Self::Int(_) => Self::ListInt(
482                elements
483                    .into_iter()
484                    .map(|e| match e {
485                        Self::Int(i) => Ok(i),
486                        _ => Err(crate::error::ExpressionError::type_error(format!(
487                            "make_list expected int element, got {}",
488                            e.type_name()
489                        ))),
490                    })
491                    .collect::<Result<_, _>>()?,
492            ),
493            Self::Float(_) => Self::ListFloat(
494                elements
495                    .into_iter()
496                    .map(|e| match e {
497                        Self::Float(f) => Ok(f),
498                        _ => Err(crate::error::ExpressionError::type_error(format!(
499                            "make_list expected float element, got {}",
500                            e.type_name()
501                        ))),
502                    })
503                    .collect::<Result<_, _>>()?,
504            ),
505            Self::String(_) => Self::make_list_string(
506                elements
507                    .into_iter()
508                    .map(|e| match e {
509                        Self::String(s) => Ok(s),
510                        _ => Err(crate::error::ExpressionError::type_error(format!(
511                            "make_list expected string element, got {}",
512                            e.type_name()
513                        ))),
514                    })
515                    .collect::<Result<_, _>>()?,
516            ),
517            Self::Path { format, .. } => {
518                let fmt = *format;
519                Self::make_list_path(
520                    elements
521                        .into_iter()
522                        .map(|e| match e {
523                            Self::Path { value, .. } => Ok(value),
524                            Self::String(value) => Ok(value),
525                            _ => Err(crate::error::ExpressionError::type_error(format!(
526                                "make_list expected path element, got {}",
527                                e.type_name()
528                            ))),
529                        })
530                        .collect::<Result<_, _>>()?,
531                    fmt,
532                )
533            }
534            _ if elements[0].is_list() => Self::make_list_list(elements, ExprType::NULLTYPE),
535            Self::RangeExpr(_) => Self::make_list_list(elements, ExprType::RANGE_EXPR),
536            _ => {
537                return Err(crate::error::ExpressionError::type_error(format!(
538                    "Cannot create list from {} elements",
539                    elements[0].type_name()
540                )))
541            }
542        })
543    }
544
545    /// Create an unresolved value with a type constraint (for validation-time type checking).
546    pub fn unresolved(constraint: ExprType) -> Self {
547        Self::Unresolved(constraint)
548    }
549    /// Returns `true` if this is an `Unresolved` value.
550    pub fn is_unresolved(&self) -> bool {
551        matches!(self, Self::Unresolved(_))
552    }
553
554    /// Create a PATH value with separators normalized to the given format.
555    ///
556    /// This is the only public constructor for `ExprValue::Path`; the variant
557    /// itself is `#[non_exhaustive]` so downstream crates cannot bypass the
558    /// separator-normalization invariant by constructing the struct directly.
559    ///
560    /// - `Posix`: no normalization — backslash is a valid filename character
561    /// - `Windows`: `/` → `\` (unless the value is a URI)
562    /// - `Uri`: no normalization
563    pub fn new_path(value: impl Into<String>, format: PathFormat) -> Self {
564        let value = value.into();
565        let normalized = normalize_path_separators(&value, format);
566        Self::Path {
567            value: normalized,
568            format,
569        }
570    }
571
572    /// Coerce a string value to the given type.
573    pub fn from_str_coerce(
574        s: &str,
575        target: &ExprType,
576        path_format: PathFormat,
577    ) -> Result<Self, String> {
578        match target.code() {
579            TypeCode::Int => s
580                .parse::<i64>()
581                .map(ExprValue::Int)
582                .map_err(|e| format!("Cannot convert '{s}' to int: {e}")),
583            TypeCode::Float => {
584                let v: f64 = s
585                    .parse()
586                    .map_err(|e| format!("Cannot convert '{s}' to float: {e}"))?;
587                if v.is_infinite() || v.is_nan() {
588                    return Err(format!("Cannot convert '{s}' to float"));
589                }
590                Ok(ExprValue::Float(
591                    Float64::with_str(v, s.to_string()).map_err(|e| e.to_string())?,
592                ))
593            }
594            TypeCode::Bool => match s.to_lowercase().as_str() {
595                "true" | "yes" | "on" | "1" => Ok(ExprValue::Bool(true)),
596                "false" | "no" | "off" | "0" => Ok(ExprValue::Bool(false)),
597                _ => Err(format!("Cannot convert '{s}' to bool")),
598            },
599            TypeCode::String => Ok(ExprValue::String(s.to_string())),
600            TypeCode::Path => Ok(ExprValue::new_path(s, path_format)),
601            TypeCode::RangeExpr => {
602                let r: crate::range_expr::RangeExpr =
603                    s.parse().map_err(|e: crate::error::ExpressionError| {
604                        format!("Cannot convert '{s}' to range_expr: {e}")
605                    })?;
606                Ok(ExprValue::RangeExpr(r))
607            }
608            TypeCode::NullType if s == "null" => Ok(ExprValue::Null),
609            _ => Err(format!("Cannot coerce string to {target}")),
610        }
611    }
612
613    /// Coerce a value to the given type.
614    pub fn coerce(self, target: &ExprType, path_format: PathFormat) -> Result<Self, String> {
615        if self.expr_type() == *target {
616            return Ok(self);
617        }
618        match (&self, target.code()) {
619            (ExprValue::Int(i), TypeCode::Float) => {
620                Ok(ExprValue::Float(Float64::new(*i as f64).unwrap()))
621            }
622            (ExprValue::Float(f), TypeCode::Int) => {
623                let v = f.value();
624                if v.fract() == 0.0 && v.is_finite() {
625                    Ok(ExprValue::Int(v as i64))
626                } else {
627                    Err(format!(
628                        "Cannot coerce float to int: {} is not a whole number",
629                        f.to_display_string()
630                    ))
631                }
632            }
633            (ExprValue::Bool(b), TypeCode::String) => Ok(ExprValue::String(
634                if *b { "true" } else { "false" }.to_string(),
635            )),
636            (ExprValue::Int(i), TypeCode::String) => Ok(ExprValue::String(i.to_string())),
637            (ExprValue::Float(f), TypeCode::String) => Ok(ExprValue::String(f.to_display_string())),
638            (ExprValue::String(s), _) => ExprValue::from_str_coerce(s, target, path_format),
639            (ExprValue::Path { value, .. }, TypeCode::String) => {
640                Ok(ExprValue::String(value.clone()))
641            }
642            (ExprValue::RangeExpr(r), TypeCode::String) => Ok(ExprValue::String(r.to_string())),
643            (ExprValue::RangeExpr(r), TypeCode::List) => Ok(ExprValue::ListInt(r.to_vec())),
644            _ if target.code() == TypeCode::List && target.params().len() == 1 => {
645                let elem_type = &target.params()[0];
646                if let Some(elements) = self.list_elements() {
647                    let coerced: Result<Vec<_>, _> = elements
648                        .into_iter()
649                        .map(|e| e.coerce(elem_type, path_format))
650                        .collect();
651                    Ok(ExprValue::make_list(coerced?, elem_type.clone())
652                        .map_err(|e| e.to_string())?)
653                } else {
654                    Err(format!("Cannot coerce {} to {target}", self.expr_type()))
655                }
656            }
657            _ => Err(format!("Cannot coerce {} to {target}", self.expr_type())),
658        }
659    }
660
661    /// Python-style repr: `ExprValue(42)`, `ExprValue('hello')`, `ExprValue([1, 2], type='list[int]')`.
662    pub fn repr_python(&self) -> String {
663        match self {
664            Self::Null => "ExprValue(None)".to_string(),
665            Self::Bool(b) => format!("ExprValue({})", if *b { "True" } else { "False" }),
666            Self::Int(i) => format!("ExprValue({i})"),
667            Self::Float(f) => {
668                if f.original.is_some() {
669                    format!("ExprValue('{}', type='float')", f.to_display_string())
670                } else {
671                    format!("ExprValue({})", f.to_display_string())
672                }
673            }
674            Self::String(s) => format!("ExprValue('{s}')"),
675            Self::Path { value, format } => {
676                format!(
677                    "ExprValue('{value}', type='path', path_format=PathFormat.{})",
678                    match format {
679                        PathFormat::Posix => "POSIX",
680                        PathFormat::Windows => "WINDOWS",
681                        PathFormat::Uri => "URI",
682                    }
683                )
684            }
685            Self::RangeExpr(r) => format!("ExprValue('{}', type='range_expr')", r),
686            Self::Unresolved(t) => format!("ExprValue.unresolved(ExprType(\"{t}\"))"),
687            val if val.is_list() => {
688                let type_str = val.expr_type().to_string();
689                // Find path format if any
690                let pf = val.find_path_format();
691                let pf_str = pf
692                    .map(|f| {
693                        format!(
694                            ", path_format=PathFormat.{}",
695                            match f {
696                                PathFormat::Posix => "POSIX",
697                                PathFormat::Windows => "WINDOWS",
698                                PathFormat::Uri => "URI",
699                            }
700                        )
701                    })
702                    .unwrap_or_default();
703                format!(
704                    "ExprValue({}, type='{type_str}'{pf_str})",
705                    val.repr_python_list()
706                )
707            }
708            _ => format!("ExprValue('{}')", self.to_display_string()),
709        }
710    }
711
712    fn repr_python_list(&self) -> String {
713        let elements = self.list_elements().unwrap_or_default();
714        let items: Vec<String> = elements
715            .iter()
716            .map(|e| {
717                if e.is_list() {
718                    e.repr_python_list()
719                } else {
720                    match e {
721                        ExprValue::String(s) | ExprValue::Path { value: s, .. } => format!("'{s}'"),
722                        ExprValue::Bool(b) => if *b { "True" } else { "False" }.to_string(),
723                        ExprValue::Int(i) => i.to_string(),
724                        ExprValue::Float(f) => f.to_display_string(),
725                        _ => e.to_display_string(),
726                    }
727                }
728            })
729            .collect();
730        format!("[{}]", items.join(", "))
731    }
732
733    fn find_path_format(&self) -> Option<PathFormat> {
734        match self {
735            Self::ListPath(_, fmt, _) => Some(*fmt),
736            Self::ListList(v, _, _) => v.first().and_then(|e| e.find_path_format()),
737            _ => None,
738        }
739    }
740
741    /// Serialize to JSON transport format: `{"type": "int", "value": "42"}`.
742    /// Lists serialize value as nested JSON arrays of strings.
743    /// The caller adds the `"name"` field.
744    pub fn to_json_transport(&self) -> serde_json::Value {
745        let type_str = self.expr_type().to_string();
746        let value = self.transport_value();
747        serde_json::json!({"type": type_str, "value": value})
748    }
749
750    pub fn transport_value(&self) -> serde_json::Value {
751        match self {
752            val if val.is_list() => {
753                let elements = val.list_elements().unwrap_or_default();
754                serde_json::Value::Array(elements.iter().map(|e| e.transport_value()).collect())
755            }
756            _ => serde_json::Value::String(self.to_display_string()),
757        }
758    }
759
760    /// Deserialize from JSON transport format.
761    /// `json` must have `"type"` and `"value"` fields.
762    pub fn from_json_transport(
763        json: &serde_json::Value,
764        path_format: PathFormat,
765    ) -> Result<Self, String> {
766        let type_str = json
767            .get("type")
768            .and_then(|v| v.as_str())
769            .ok_or("Missing 'type' field")?;
770        let value = json.get("value").ok_or("Missing 'value' field")?;
771        let expr_type = ExprType::parse(type_str)?;
772        Self::from_transport_value(value, &expr_type, path_format)
773    }
774
775    pub fn from_transport_value(
776        value: &serde_json::Value,
777        target: &ExprType,
778        path_format: PathFormat,
779    ) -> Result<Self, String> {
780        Self::from_transport_value_inner(value, target, path_format, 0)
781    }
782
783    fn from_transport_value_inner(
784        value: &serde_json::Value,
785        target: &ExprType,
786        path_format: PathFormat,
787        depth: usize,
788    ) -> Result<Self, String> {
789        if depth > 10 {
790            return Err("Transport value nesting depth exceeded".to_string());
791        }
792        if target.code() == TypeCode::List {
793            let elem_type = target
794                .params()
795                .first()
796                .ok_or("List type missing element type")?;
797            let arr = value.as_array().ok_or("Expected array for list type")?;
798            let elements: Result<Vec<_>, _> = arr
799                .iter()
800                .map(|v| Self::from_transport_value_inner(v, elem_type, path_format, depth + 1))
801                .collect();
802            return ExprValue::make_list(elements?, elem_type.clone()).map_err(|e| e.to_string());
803        }
804        let s = value
805            .as_str()
806            .ok_or_else(|| format!("Expected string value for {target}"))?;
807        ExprValue::from_str_coerce(s, target, path_format)
808    }
809
810    /// Returns `true` if this value is a list variant.
811    pub fn is_list(&self) -> bool {
812        matches!(
813            self,
814            Self::ListBool(_)
815                | Self::ListInt(_)
816                | Self::ListFloat(_)
817                | Self::ListString(_, _)
818                | Self::ListPath(_, _, _)
819                | Self::ListList(_, _, _)
820        )
821    }
822
823    /// Number of elements if this is a list, `None` otherwise.
824    pub fn list_len(&self) -> Option<usize> {
825        match self {
826            Self::ListBool(v) => Some(v.len()),
827            Self::ListInt(v) => Some(v.len()),
828            Self::ListFloat(v) => Some(v.len()),
829            Self::ListString(v, _) => Some(v.len()),
830            Self::ListPath(v, _, _) => Some(v.len()),
831            Self::ListList(v, _, _) => Some(v.len()),
832            _ => None,
833        }
834    }
835
836    /// Collect all elements into a `Vec`. Prefer [`list_iter`](Self::list_iter) to avoid allocation.
837    pub fn list_elements(&self) -> Option<Vec<ExprValue>> {
838        match self {
839            Self::ListBool(v) => Some(v.iter().map(|b| ExprValue::Bool(*b)).collect()),
840            Self::ListInt(v) => Some(v.iter().map(|i| ExprValue::Int(*i)).collect()),
841            Self::ListFloat(v) => Some(v.iter().map(|f| ExprValue::Float(f.clone())).collect()),
842            Self::ListString(v, _) => {
843                Some(v.iter().map(|s| ExprValue::String(s.clone())).collect())
844            }
845            Self::ListPath(v, fmt, _) => Some(
846                v.iter()
847                    .map(|s| ExprValue::new_path(s.clone(), *fmt))
848                    .collect(),
849            ),
850            Self::ListList(v, _, _) => Some(v.clone()),
851            _ => None,
852        }
853    }
854
855    /// Iterate over list elements without allocating a Vec.
856    /// Returns None for non-list values.
857    pub fn list_iter(&self) -> Option<ListIter<'_>> {
858        match self {
859            Self::ListBool(v) => Some(ListIter::Bool(v.iter())),
860            Self::ListInt(v) => Some(ListIter::Int(v.iter())),
861            Self::ListFloat(v) => Some(ListIter::Float(v.iter())),
862            Self::ListString(v, _) => Some(ListIter::String(v.iter())),
863            Self::ListPath(v, fmt, _) => Some(ListIter::Path(v.iter(), *fmt)),
864            Self::ListList(v, _, _) => Some(ListIter::List(v.iter())),
865            _ => None,
866        }
867    }
868
869    /// Get a single element by index without allocating.
870    /// Supports negative indexing (Python-style).
871    pub fn list_get(&self, index: i64) -> Option<ExprValue> {
872        let len = self.list_len()? as i64;
873        let i = if index < 0 { len + index } else { index };
874        if i < 0 || i >= len {
875            return None;
876        }
877        let i = i as usize;
878        match self {
879            Self::ListBool(v) => Some(ExprValue::Bool(v[i])),
880            Self::ListInt(v) => Some(ExprValue::Int(v[i])),
881            Self::ListFloat(v) => Some(ExprValue::Float(v[i].clone())),
882            Self::ListString(v, _) => Some(ExprValue::String(v[i].clone())),
883            Self::ListPath(v, fmt, _) => Some(ExprValue::new_path(v[i].clone(), *fmt)),
884            Self::ListList(v, _, _) => Some(v[i].clone()),
885            _ => None,
886        }
887    }
888
889    /// Element type of a list, or `None` for non-list values.
890    ///
891    /// Returns the element type based on the list variant, even for empty
892    /// lists. For example, an empty `ListString` returns `STRING`, not
893    /// `NULLTYPE`. This ensures that operations on empty typed lists
894    /// (e.g. `sorted([])` where `[]` was originally `list[string]`)
895    /// preserve the element type through round-trips via `into_list` +
896    /// `make_list`.
897    pub fn list_elem_type(&self) -> Option<ExprType> {
898        match self {
899            Self::ListBool(_) => Some(ExprType::BOOL),
900            Self::ListInt(_) => Some(ExprType::INT),
901            Self::ListFloat(_) => Some(ExprType::FLOAT),
902            Self::ListString(_, _) => Some(ExprType::STRING),
903            Self::ListPath(_, _, _) => Some(ExprType::PATH),
904            Self::ListList(_, elem_type, _) => Some(elem_type.clone()),
905            _ => None,
906        }
907    }
908
909    /// Destructure into (elements, elem_type) for migration compatibility.
910    pub fn into_list(self) -> Option<(Vec<ExprValue>, ExprType)> {
911        let et = self.list_elem_type()?;
912        Some((self.list_elements()?, et))
913    }
914
915    /// The [`ExprType`] of this value.
916    pub fn expr_type(&self) -> ExprType {
917        match self {
918            Self::Null => ExprType::NULLTYPE,
919            Self::Bool(_) => ExprType::BOOL,
920            Self::Int(_) => ExprType::INT,
921            Self::Float(_) => ExprType::FLOAT,
922            Self::String(_) => ExprType::STRING,
923            Self::Path { .. } => ExprType::PATH,
924            Self::ListBool(_) => ExprType::list(ExprType::BOOL),
925            Self::ListInt(_) => ExprType::list(ExprType::INT),
926            Self::ListFloat(_) => ExprType::list(ExprType::FLOAT),
927            Self::ListString(_, _) => ExprType::list(ExprType::STRING),
928            Self::ListPath(_, _, _) => ExprType::list(ExprType::PATH),
929            Self::ListList(_, elem_type, _) => ExprType::list(elem_type.clone()),
930            Self::RangeExpr(_) => ExprType::RANGE_EXPR,
931            Self::Unresolved(t) => ExprType::unresolved(t.clone()),
932        }
933    }
934
935    /// Get a string representation for use in path manipulation and constraint checking.
936    /// Returns a `Cow` to avoid allocation when the value is already a string.
937    pub fn as_str_repr(&self) -> std::borrow::Cow<'_, str> {
938        match self {
939            Self::String(s) => std::borrow::Cow::Borrowed(s),
940            Self::Path { value, .. } => std::borrow::Cow::Borrowed(value),
941            _ => std::borrow::Cow::Owned(self.to_display_string()),
942        }
943    }
944
945    /// Short type name for error messages.
946    pub fn type_name(&self) -> &'static str {
947        match self {
948            Self::Null => "null",
949            Self::Bool(_) => "bool",
950            Self::Int(_) => "int",
951            Self::Float(_) => "float",
952            Self::String(_) => "string",
953            Self::Path { .. } => "path",
954            Self::RangeExpr(_) => "range_expr",
955            Self::Unresolved(_) => "unresolved",
956            _ if self.is_list() => "list",
957            _ => "unknown",
958        }
959    }
960
961    /// Human-readable string for format string interpolation and display.
962    pub fn to_display_string(&self) -> String {
963        match self {
964            Self::Null => "null".to_string(),
965            Self::Bool(b) => if *b { "true" } else { "false" }.to_string(),
966            Self::Int(i) => i.to_string(),
967            Self::Float(fv) => fv.to_display_string(),
968            Self::String(s) => s.clone(),
969            Self::Path { value, .. } => value.clone(),
970            Self::ListBool(v) => format!(
971                "[{}]",
972                v.iter()
973                    .map(|b| if *b { "true" } else { "false" })
974                    .collect::<Vec<_>>()
975                    .join(", ")
976            ),
977            Self::ListInt(v) => format!(
978                "[{}]",
979                v.iter()
980                    .map(|i| i.to_string())
981                    .collect::<Vec<_>>()
982                    .join(", ")
983            ),
984            Self::ListFloat(v) => format!(
985                "[{}]",
986                v.iter()
987                    .map(|f| f.to_display_string())
988                    .collect::<Vec<_>>()
989                    .join(", ")
990            ),
991            Self::ListString(v, _) => format!(
992                "[{}]",
993                v.iter()
994                    .map(|s| format!("\"{}\"", s))
995                    .collect::<Vec<_>>()
996                    .join(", ")
997            ),
998            Self::ListPath(v, _, _) => format!(
999                "[{}]",
1000                v.iter()
1001                    .map(|s| format!("\"{}\"", s))
1002                    .collect::<Vec<_>>()
1003                    .join(", ")
1004            ),
1005            Self::ListList(v, _, _) => format!(
1006                "[{}]",
1007                v.iter()
1008                    .map(|e| e.to_display_string())
1009                    .collect::<Vec<_>>()
1010                    .join(", ")
1011            ),
1012            Self::RangeExpr(r) => r.to_string(),
1013            Self::Unresolved(t) => format!("<unresolved[{t}]>"),
1014        }
1015    }
1016
1017    /// Memory size: `size_of::<ExprValue>` (the enum itself) plus heap allocations.
1018    pub fn memory_size(&self) -> usize {
1019        std::mem::size_of::<ExprValue>() + self.heap_size()
1020    }
1021
1022    /// Heap-only allocation size (excludes the inline ExprValue struct).
1023    fn heap_size(&self) -> usize {
1024        use std::mem::size_of;
1025        match self {
1026            Self::Null | Self::Bool(_) | Self::Int(_) | Self::Unresolved(_) => 0,
1027            Self::Float(f) => f.original.as_ref().map_or(0, |s| s.len()),
1028            Self::String(s) | Self::Path { value: s, .. } => s.capacity(),
1029            Self::ListBool(v) => v.capacity(),
1030            Self::ListInt(v) => v.capacity() * size_of::<i64>(),
1031            Self::ListFloat(v) => v.capacity() * size_of::<Float64>(),
1032            Self::ListString(_, cached) | Self::ListPath(_, _, cached) => *cached,
1033            Self::ListList(_, _, cached) => *cached,
1034            Self::RangeExpr(r) => r.heap_size(),
1035        }
1036    }
1037
1038    /// Value equality with cross-type support (Int↔Float, String↔Path).
1039    pub fn equals(&self, other: &ExprValue) -> bool {
1040        match (self, other) {
1041            (Self::Null, Self::Null) => true,
1042            (Self::Bool(a), Self::Bool(b)) => a == b,
1043            (Self::Int(a), Self::Int(b)) => a == b,
1044            (Self::Float(a), Self::Float(b)) => a.value == b.value,
1045            (Self::Int(a), Self::Float(b)) => (*a as f64) == b.value,
1046            (Self::Float(a), Self::Int(b)) => a.value == (*b as f64),
1047            (Self::String(a), Self::String(b)) => a == b,
1048            (Self::Path { value: a, .. }, Self::Path { value: b, .. }) => a == b,
1049            (Self::String(a), Self::Path { value: b, .. })
1050            | (Self::Path { value: b, .. }, Self::String(a)) => a == b,
1051            _ if self.is_list() && other.is_list() => {
1052                let (a_iter, b_iter) = match (self.list_iter(), other.list_iter()) {
1053                    (Some(a), Some(b)) => (a, b),
1054                    _ => return false,
1055                };
1056                let (a_len, b_len) = (a_iter.len(), b_iter.len());
1057                if a_len != b_len {
1058                    return false;
1059                }
1060                a_iter.zip(b_iter).all(|(x, y)| x.equals(&y))
1061            }
1062            (Self::ListInt(elems), Self::RangeExpr(r))
1063            | (Self::RangeExpr(r), Self::ListInt(elems)) => {
1064                let rv: Vec<i64> = r.iter().collect();
1065                elems.len() == rv.len() && elems.iter().zip(rv.iter()).all(|(a, b)| a == b)
1066            }
1067            (Self::RangeExpr(a), Self::RangeExpr(b)) => a == b,
1068            (Self::Unresolved(a), Self::Unresolved(b)) => a == b,
1069            _ => false,
1070        }
1071    }
1072
1073    /// Ordering comparison. Returns `Err` for incomparable types.
1074    pub fn compare(
1075        &self,
1076        other: &ExprValue,
1077    ) -> Result<std::cmp::Ordering, crate::error::ExpressionError> {
1078        match (self, other) {
1079            (Self::Int(a), Self::Int(b)) => Ok(a.cmp(b)),
1080            (Self::Float(a), Self::Float(b)) => a
1081                .value
1082                .partial_cmp(&b.value)
1083                .ok_or_else(|| crate::error::ExpressionError::new("Cannot compare NaN")),
1084            (Self::Int(a), Self::Float(b)) => (*a as f64)
1085                .partial_cmp(&b.value)
1086                .ok_or_else(|| crate::error::ExpressionError::new("Cannot compare NaN")),
1087            (Self::Float(a), Self::Int(b)) => a
1088                .value
1089                .partial_cmp(&(*b as f64))
1090                .ok_or_else(|| crate::error::ExpressionError::new("Cannot compare NaN")),
1091            (Self::Bool(a), Self::Bool(b)) => Ok(a.cmp(b)),
1092            (Self::String(a), Self::String(b)) => Ok(a.cmp(b)),
1093            (Self::Path { value: a, .. }, Self::Path { value: b, .. }) => Ok(a.cmp(b)),
1094            (Self::String(a), Self::Path { value: b, .. })
1095            | (Self::Path { value: b, .. }, Self::String(a)) => Ok(a.cmp(b)),
1096            _ if self.is_list() && other.is_list() => {
1097                let (a_iter, b_iter) = match (self.list_iter(), other.list_iter()) {
1098                    (Some(a), Some(b)) => (a, b),
1099                    _ => {
1100                        return Err(crate::error::ExpressionError::new(format!(
1101                            "Cannot compare {} and {}",
1102                            self.expr_type(),
1103                            other.expr_type()
1104                        )))
1105                    }
1106                };
1107                let (a_len, b_len) = (a_iter.len(), b_iter.len());
1108                for (x, y) in a_iter.zip(b_iter) {
1109                    match x.compare(&y) {
1110                        Ok(std::cmp::Ordering::Equal) => continue,
1111                        other => return other,
1112                    }
1113                }
1114                Ok(a_len.cmp(&b_len))
1115            }
1116            _ => Err(crate::error::ExpressionError::new(format!(
1117                "Cannot compare {} and {}",
1118                self.expr_type(),
1119                other.expr_type()
1120            ))),
1121        }
1122    }
1123}
1124
1125impl PartialEq for ExprValue {
1126    fn eq(&self, other: &Self) -> bool {
1127        self.equals(other)
1128    }
1129}
1130
1131impl From<bool> for ExprValue {
1132    fn from(v: bool) -> Self {
1133        Self::Bool(v)
1134    }
1135}
1136impl From<i32> for ExprValue {
1137    fn from(v: i32) -> Self {
1138        Self::Int(v as i64)
1139    }
1140}
1141impl From<i64> for ExprValue {
1142    fn from(v: i64) -> Self {
1143        Self::Int(v)
1144    }
1145}
1146impl From<String> for ExprValue {
1147    fn from(v: String) -> Self {
1148        Self::String(v)
1149    }
1150}
1151impl From<&str> for ExprValue {
1152    fn from(v: &str) -> Self {
1153        Self::String(v.to_string())
1154    }
1155}
1156impl From<RangeExpr> for ExprValue {
1157    fn from(v: RangeExpr) -> Self {
1158        Self::RangeExpr(v)
1159    }
1160}
1161impl From<crate::types::ExprType> for ExprValue {
1162    fn from(t: crate::types::ExprType) -> Self {
1163        Self::Unresolved(t)
1164    }
1165}
1166
1167/// Zero-allocation iterator over list elements.
1168pub enum ListIter<'a> {
1169    Bool(std::slice::Iter<'a, bool>),
1170    Int(std::slice::Iter<'a, i64>),
1171    Float(std::slice::Iter<'a, Float64>),
1172    String(std::slice::Iter<'a, String>),
1173    Path(std::slice::Iter<'a, String>, PathFormat),
1174    List(std::slice::Iter<'a, ExprValue>),
1175}
1176
1177impl<'a> Iterator for ListIter<'a> {
1178    type Item = ExprValue;
1179    fn next(&mut self) -> Option<ExprValue> {
1180        match self {
1181            Self::Bool(it) => it.next().map(|b| ExprValue::Bool(*b)),
1182            Self::Int(it) => it.next().map(|i| ExprValue::Int(*i)),
1183            Self::Float(it) => it.next().map(|f| ExprValue::Float(f.clone())),
1184            Self::String(it) => it.next().map(|s| ExprValue::String(s.clone())),
1185            Self::Path(it, fmt) => it.next().map(|s| ExprValue::new_path(s.clone(), *fmt)),
1186            Self::List(it) => it.next().cloned(),
1187        }
1188    }
1189    fn size_hint(&self) -> (usize, Option<usize>) {
1190        match self {
1191            Self::Bool(it) => it.size_hint(),
1192            Self::Int(it) => it.size_hint(),
1193            Self::Float(it) => it.size_hint(),
1194            Self::String(it) => it.size_hint(),
1195            Self::Path(it, _) => it.size_hint(),
1196            Self::List(it) => it.size_hint(),
1197        }
1198    }
1199}
1200
1201impl<'a> ExactSizeIterator for ListIter<'a> {}
1202
1203pub fn format_float(f: f64) -> String {
1204    if f == 0.0 {
1205        return "0.0".to_string();
1206    }
1207    let abs = f.abs();
1208    if !(1e-4..1e16).contains(&abs) {
1209        format!("{:e}", f)
1210            .replace("e-0", "e-")
1211            .replace("e0", "e+0")
1212            .replace("e", "e+")
1213            .replace("e+-", "e-")
1214            .replace("e++", "e+")
1215    } else if f.fract() == 0.0 {
1216        format!("{}.0", f as i64)
1217    } else {
1218        f.to_string()
1219    }
1220}
1221
1222/// Normalize path separators to match `format`.
1223///
1224/// - `Posix`: no normalization — backslashes are valid filename characters
1225/// - `Windows`: `/` → `\` (unless the value is a URI)
1226/// - `Uri`: no normalization
1227#[must_use]
1228pub fn normalize_path_separators(value: &str, format: PathFormat) -> String {
1229    if crate::uri_path::is_uri(value) {
1230        return value.to_string();
1231    }
1232    match format {
1233        PathFormat::Windows => value.replace('/', "\\"),
1234        PathFormat::Posix | PathFormat::Uri => value.to_string(),
1235    }
1236}