Skip to main content

prima_core/
value.rs

1use std::collections::{HashMap, HashSet};
2
3use num_rational::BigRational;
4use num_traits::ToPrimitive;
5
6use crate::expr_pool::ExprId;
7use crate::number::{Number, Real};
8
9/// Indeterminate form (spec §6.2): mathematically undefined forms (0/0 etc.) that exist **only in the symbolic layer**;
10/// they can take part in later simplification; when collapse to the numeric layer fails they become `Undefined`.
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub enum IndeterminateForm {
13    ZeroOverZero,
14    InfOverInf,
15    ZeroTimesInf,
16    InfMinusInf,
17}
18
19/// Value type (spec §5): covers the value forms of each layer of the three-world architecture —
20/// the symbolic layer (`Expr`/`Symbol`/`Indeterminate`), the numeric layer (`Number`), and the host layer
21/// (`Bool`/`String`/`Error`, etc.). `Array` is a variable-length heterogeneous sequence (v2.1, spec §11.3);
22/// `Dict`/`Set` are variable host collections keyed/elemented by immutable hashable `ValueKey`s (spec §4.6/§11.6).
23/// `Result`/`Error` carry a structured `Error` as a message string (the structured enum from spec §16.1 is deferred to a later stage).
24#[derive(Debug, Clone, PartialEq)]
25pub enum Value {
26    Nil,
27    Number(Number),
28    Bool(bool),
29    Char(char),
30    String(String),
31    Array(Vec<Value>),
32    Dict(HashMap<ValueKey, Value>),
33    Set(HashSet<ValueKey>),
34    Expr(ExprId),
35    Symbol(u32),
36    Indeterminate(IndeterminateForm),
37    Undefined,
38    Error(String),
39    Tuple(Vec<Value>),
40    Result(std::result::Result<Box<Value>, String>),
41    Class(u32),                       // class instance handle (spec §5); registry lives in prima-runtime
42    /// Compiled/JIT-ed function handle (spec §19.2/§19.4): a process-local id into the
43    /// `prima-runtime::jit` registry. Only produced by the `jit(...)` builtin. Like `Class`,
44    /// the id is process-local (no cross-process serialization) and lives for the process lifetime.
45    JitFunction(u32),
46    Option(Option<Box<Value>>),       // Option<T>: Some(T) / None
47}
48
49/// Hashable key for `Dict`/`Set` (spec §11.6): a value-semantic, immutable subset of `Value` —
50/// numbers (canonicalized), strings, chars, bools, and symbol/expr handles.
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub enum ValueKey {
53    Int(i64),
54    BigInt(num_bigint::BigInt),
55    Rational(num_bigint::BigInt, num_bigint::BigInt), // reduced fraction; denominator positive
56    Float(u64),          // f64 bit pattern; NaN keys are rejected
57    Str(String),
58    Char(char),
59    Bool(bool),
60    Symbol(u32),         // SymbolId.0
61    Expr(u32),           // ExprId inner value
62}
63
64impl ValueKey {
65    /// Convert a `Value` to a hashable key, or `None` if the value is not a valid key type
66    /// (complex numbers, arrays, dicts, sets, class instances, etc. → `None`; NaN → `None`).
67    pub fn from_value(v: &Value) -> Option<ValueKey> {
68        match v {
69            Value::Number(n) => number_to_key(n),
70            Value::String(s) => Some(ValueKey::Str(s.clone())),
71            Value::Char(c) => Some(ValueKey::Char(*c)),
72            Value::Bool(b) => Some(ValueKey::Bool(*b)),
73            Value::Symbol(s) => Some(ValueKey::Symbol(*s)),
74            Value::Expr(id) => Some(ValueKey::Expr(id.as_u32())),
75            _ => None,
76        }
77    }
78
79    /// Reconstruct the corresponding `Value` (numeric keys produce `Number`, `Symbol` produces
80    /// `Value::Symbol`, `Expr` produces `Value::Expr`).
81    pub fn to_value(&self) -> Value {
82        match self {
83            ValueKey::Int(i) => Value::Number(Number::from(*i)),
84            ValueKey::BigInt(b) => Value::Number(Number::Integer(b.clone())),
85            ValueKey::Rational(n, d) => Value::Number(Number::Rational(BigRational::new(n.clone(), d.clone()))),
86            ValueKey::Float(bits) => Value::Number(Number::Real(Real::F64(f64::from_bits(*bits)))),
87            ValueKey::Str(s) => Value::String(s.clone()),
88            ValueKey::Char(c) => Value::Char(*c),
89            ValueKey::Bool(b) => Value::Bool(*b),
90            ValueKey::Symbol(s) => Value::Symbol(*s),
91            ValueKey::Expr(u) => Value::Expr(ExprId::from_u32(*u)),
92        }
93    }
94}
95
96/// Map a `Number` to a hashable key (spec §11.6): integers that fit `i64` → `Int` (else `BigInt`),
97/// rationals → reduced `Rational` with positive denominator, reals → `Float` bit pattern (NaN → `None`),
98/// complex → `None`. Fixed-width collapsed variants are keyed by value after normalizing to the
99/// exact/`Real` layer (spec §6.1).
100fn number_to_key(n: &Number) -> Option<ValueKey> {
101    if n.is_complex() {
102        return None;
103    }
104    match n {
105        Number::Integer(i) => match i.to_i64() {
106            Some(v) => Some(ValueKey::Int(v)),
107            None => Some(ValueKey::BigInt(i.clone())),
108        },
109        Number::Rational(r) => {
110            // Re-normalize so the key always holds a reduced fraction with a positive denominator (spec §6.1).
111            let r = BigRational::new(r.numer().clone(), r.denom().clone());
112            Some(ValueKey::Rational(r.numer().clone(), r.denom().clone()))
113        }
114        Number::Real(Real::F64(x)) => (!x.is_nan()).then_some(ValueKey::Float(x.to_bits())),
115        Number::Real(Real::F32(x)) => (!x.is_nan()).then_some(ValueKey::Float((*x as f64).to_bits())),
116        Number::BigFloat(f) => (!f.is_nan()).then_some(ValueKey::Float(f.to_bits())),
117        // Fixed-width collapsed integers normalize to the exact layer (spec §6.1).
118        other => match other.as_i64() {
119            Some(v) => Some(ValueKey::Int(v)),
120            None => other.as_bigint().map(ValueKey::BigInt),
121        },
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use num_bigint::BigInt;
129    use num_rational::BigRational;
130
131    use crate::expr_pool::ExprPool;
132
133    /// `from_value` → key, `to_value` → the original value (round-trip, spec §11.6).
134    fn assert_roundtrip(v: Value, key: ValueKey) {
135        let k = ValueKey::from_value(&v).unwrap_or_else(|| panic!("expected a key for {v:?}"));
136        assert_eq!(k, key);
137        assert_eq!(k.to_value(), v);
138    }
139
140    #[test]
141    fn int_key_roundtrip() {
142        assert_roundtrip(Value::Number(Number::from(1)), ValueKey::Int(1));
143        assert_roundtrip(Value::Number(Number::from(-7)), ValueKey::Int(-7));
144    }
145
146    #[test]
147    fn bigint_key_roundtrip() {
148        let big = BigInt::from(i64::MAX) + BigInt::from(1);
149        assert_roundtrip(Value::Number(Number::Integer(big.clone())), ValueKey::BigInt(big));
150    }
151
152    #[test]
153    fn rational_key_roundtrip() {
154        assert_roundtrip(
155            Value::Number(Number::Rational(BigRational::new(BigInt::from(1), BigInt::from(3)))),
156            ValueKey::Rational(BigInt::from(1), BigInt::from(3)),
157        );
158        // The key always holds a reduced fraction with a positive denominator (spec §6.1).
159        assert_roundtrip(
160            Value::Number(Number::Rational(BigRational::new(BigInt::from(2), BigInt::from(-3)))),
161            ValueKey::Rational(BigInt::from(-2), BigInt::from(3)),
162        );
163    }
164
165    #[test]
166    fn float_key_roundtrip() {
167        assert_roundtrip(Value::Number(Number::from(2.5)), ValueKey::Float(2.5f64.to_bits()));
168        // F32 promotes to F64 when keyed (spec §6.1 promotion); the key is the F64 bit pattern.
169        let v = Value::Number(Number::Real(Real::F32(1.5)));
170        assert_eq!(ValueKey::from_value(&v), Some(ValueKey::Float((1.5f32 as f64).to_bits())));
171        assert_eq!(
172            ValueKey::Float((1.5f32 as f64).to_bits()).to_value(),
173            Value::Number(Number::Real(Real::F64(1.5)))
174        );
175    }
176
177    #[test]
178    fn scalar_key_roundtrip() {
179        assert_roundtrip(Value::String("hello".to_string()), ValueKey::Str("hello".to_string()));
180        assert_roundtrip(Value::Char('x'), ValueKey::Char('x'));
181        assert_roundtrip(Value::Bool(true), ValueKey::Bool(true));
182    }
183
184    #[test]
185    fn symbol_and_expr_keys() {
186        assert_eq!(ValueKey::from_value(&Value::Symbol(42)), Some(ValueKey::Symbol(42)));
187        assert_eq!(ValueKey::Symbol(42).to_value(), Value::Symbol(42));
188
189        let pool = ExprPool::new();
190        let id = pool.integer(3);
191        assert_eq!(ValueKey::from_value(&Value::Expr(id)), Some(ValueKey::Expr(id.as_u32())));
192        assert_eq!(ValueKey::Expr(id.as_u32()).to_value(), Value::Expr(id));
193    }
194
195    #[test]
196    fn unsupported_values_are_none() {
197        assert_eq!(ValueKey::from_value(&Value::Number(Number::complex(1, 2))), None);
198        assert_eq!(ValueKey::from_value(&Value::Array(vec![Value::Number(Number::from(1))])), None);
199        assert_eq!(ValueKey::from_value(&Value::Dict(HashMap::new())), None);
200        assert_eq!(ValueKey::from_value(&Value::Set(HashSet::new())), None);
201        assert_eq!(ValueKey::from_value(&Value::Undefined), None);
202        assert_eq!(ValueKey::from_value(&Value::Nil), None);
203    }
204
205    #[test]
206    fn nan_is_not_a_key() {
207        assert_eq!(ValueKey::from_value(&Value::Number(Number::Real(Real::F64(f64::NAN)))), None);
208        assert_eq!(ValueKey::from_value(&Value::Number(Number::Real(Real::F32(f32::NAN)))), None);
209    }
210
211    #[test]
212    fn to_value_reconstructs_values() {
213        assert_eq!(ValueKey::Int(5).to_value(), Value::Number(Number::from(5)));
214        assert_eq!(
215            ValueKey::BigInt(BigInt::from(1u64 << 40)).to_value(),
216            Value::Number(Number::Integer(BigInt::from(1u64 << 40)))
217        );
218        assert_eq!(ValueKey::Float(2.5f64.to_bits()).to_value(), Value::Number(Number::Real(Real::F64(2.5))));
219        assert_eq!(ValueKey::Str("abc".to_string()).to_value(), Value::String("abc".to_string()));
220        assert_eq!(ValueKey::Char('z').to_value(), Value::Char('z'));
221        assert_eq!(ValueKey::Bool(false).to_value(), Value::Bool(false));
222    }
223
224    #[test]
225    fn dict_and_set_hold_hashable_keys() {
226        let mut m = HashMap::new();
227        m.insert(ValueKey::Str("a".into()), Value::Number(Number::from(1)));
228        let Value::Dict(d) = Value::Dict(m) else { unreachable!() };
229        assert_eq!(d.get(&ValueKey::Str("a".into())), Some(&Value::Number(Number::from(1))));
230
231        let mut s = HashSet::new();
232        s.insert(ValueKey::Int(1));
233        s.insert(ValueKey::Int(1));
234        s.insert(ValueKey::Float(2.5f64.to_bits()));
235        let Value::Set(s) = Value::Set(s) else { unreachable!() };
236        assert_eq!(s.len(), 2);
237        assert!(s.contains(&ValueKey::Int(1)));
238        assert!(s.contains(&ValueKey::Float(2.5f64.to_bits())));
239    }
240}