behavior-contracts 0.6.0

Language-neutral IR runtime core (expression evaluation, template rendering, execution plan, canonical serialization) shared across DSL implementations. Passes the dsl-contracts conformance vectors byte-for-byte.
Documentation
//! Runtime value model — shared across all COMMON primitives.
//!
//! Mirrors the TS/Python reference model: int = checked i64, float = IEEE754 f64
//! (NaN/±Inf are Failures at the boundary), string, bool, null, arr, obj.
//! The distinction between `Int` and `Float` is normative: `int 1 != float 1.0`.

use std::collections::BTreeMap;

/// A runtime value. Object key ordering is preserved via a `Vec` of pairs so that
/// canonical serialization can apply an explicit code-point sort (a `BTreeMap`
/// would impose `String` Ord which — while it happens to equal code-point order —
/// we keep explicit for clarity and insertion-order retention where required).
#[derive(Debug, Clone)]
pub enum Value {
    Null,
    Bool(bool),
    Int(i64),
    Float(f64),
    Str(String),
    Arr(Vec<Value>),
    /// Object: insertion-ordered key/value pairs.
    Obj(Vec<(String, Value)>),
}

impl Value {
    pub fn type_name(&self) -> &'static str {
        match self {
            Value::Null => "null",
            Value::Bool(_) => "bool",
            Value::Int(_) => "int",
            Value::Float(_) => "float",
            Value::Str(_) => "string",
            Value::Arr(_) => "arr",
            Value::Obj(_) => "obj",
        }
    }

    /// Look up a key in an object value (linear scan; objects are small).
    pub fn obj_get(&self, key: &str) -> Option<&Value> {
        if let Value::Obj(pairs) = self {
            pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
        } else {
            None
        }
    }
}

/// Deep structural equality with strict int/float type distinction (PROTOCOL §4.1).
///
/// - `Int(1)` is NOT equal to `Float(1.0)`.
/// - NaN == NaN is treated as equal (used only for Failure-path fixtures).
/// - strings compare by content (code points), arrays are ordered, objects are
///   key-set + value equal (order-independent).
pub fn deep_equals(a: &Value, b: &Value) -> bool {
    match (a, b) {
        (Value::Null, Value::Null) => true,
        (Value::Bool(x), Value::Bool(y)) => x == y,
        (Value::Int(x), Value::Int(y)) => x == y,
        (Value::Float(x), Value::Float(y)) => x == y || (x.is_nan() && y.is_nan()),
        (Value::Str(x), Value::Str(y)) => x == y,
        (Value::Arr(x), Value::Arr(y)) => {
            x.len() == y.len() && x.iter().zip(y).all(|(u, v)| deep_equals(u, v))
        }
        (Value::Obj(x), Value::Obj(y)) => {
            if x.len() != y.len() {
                return false;
            }
            // key-set + per-key value equality (order independent)
            let mx: BTreeMap<&str, &Value> = x.iter().map(|(k, v)| (k.as_str(), v)).collect();
            let my: BTreeMap<&str, &Value> = y.iter().map(|(k, v)| (k.as_str(), v)).collect();
            if mx.len() != my.len() {
                return false; // duplicate keys collapsed — treat as unequal shape
            }
            mx.len() == my.len()
                && mx
                    .iter()
                    .all(|(k, v)| my.get(k).map(|w| deep_equals(v, w)).unwrap_or(false))
        }
        _ => false,
    }
}