Skip to main content

behavior_contracts/
value.rs

1//! Runtime value model — shared across all COMMON primitives.
2//!
3//! Mirrors the TS/Python reference model: int = checked i64, float = IEEE754 f64
4//! (NaN/±Inf are Failures at the boundary), string, bool, null, arr, obj.
5//! The distinction between `Int` and `Float` is normative: `int 1 != float 1.0`.
6
7use std::collections::BTreeMap;
8
9/// A runtime value. Object key ordering is preserved via a `Vec` of pairs so that
10/// canonical serialization can apply an explicit code-point sort (a `BTreeMap`
11/// would impose `String` Ord which — while it happens to equal code-point order —
12/// we keep explicit for clarity and insertion-order retention where required).
13#[derive(Debug, Clone)]
14pub enum Value {
15    Null,
16    Bool(bool),
17    Int(i64),
18    Float(f64),
19    Str(String),
20    Arr(Vec<Value>),
21    /// Object: insertion-ordered key/value pairs.
22    Obj(Vec<(String, Value)>),
23}
24
25impl Value {
26    pub fn type_name(&self) -> &'static str {
27        match self {
28            Value::Null => "null",
29            Value::Bool(_) => "bool",
30            Value::Int(_) => "int",
31            Value::Float(_) => "float",
32            Value::Str(_) => "string",
33            Value::Arr(_) => "arr",
34            Value::Obj(_) => "obj",
35        }
36    }
37
38    /// Look up a key in an object value (linear scan; objects are small).
39    pub fn obj_get(&self, key: &str) -> Option<&Value> {
40        if let Value::Obj(pairs) = self {
41            pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
42        } else {
43            None
44        }
45    }
46}
47
48/// Deep structural equality with strict int/float type distinction (PROTOCOL §4.1).
49///
50/// - `Int(1)` is NOT equal to `Float(1.0)`.
51/// - NaN == NaN is treated as equal (used only for Failure-path fixtures).
52/// - strings compare by content (code points), arrays are ordered, objects are
53///   key-set + value equal (order-independent).
54pub fn deep_equals(a: &Value, b: &Value) -> bool {
55    match (a, b) {
56        (Value::Null, Value::Null) => true,
57        (Value::Bool(x), Value::Bool(y)) => x == y,
58        (Value::Int(x), Value::Int(y)) => x == y,
59        (Value::Float(x), Value::Float(y)) => x == y || (x.is_nan() && y.is_nan()),
60        (Value::Str(x), Value::Str(y)) => x == y,
61        (Value::Arr(x), Value::Arr(y)) => {
62            x.len() == y.len() && x.iter().zip(y).all(|(u, v)| deep_equals(u, v))
63        }
64        (Value::Obj(x), Value::Obj(y)) => {
65            if x.len() != y.len() {
66                return false;
67            }
68            // key-set + per-key value equality (order independent)
69            let mx: BTreeMap<&str, &Value> = x.iter().map(|(k, v)| (k.as_str(), v)).collect();
70            let my: BTreeMap<&str, &Value> = y.iter().map(|(k, v)| (k.as_str(), v)).collect();
71            if mx.len() != my.len() {
72                return false; // duplicate keys collapsed — treat as unequal shape
73            }
74            mx.len() == my.len()
75                && mx
76                    .iter()
77                    .all(|(k, v)| my.get(k).map(|w| deep_equals(v, w)).unwrap_or(false))
78        }
79        _ => false,
80    }
81}