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    Option(Option<Box<Value>>),       // Option<T>: Some(T) / None
43}
44
45/// Hashable key for `Dict`/`Set` (spec §11.6): a value-semantic, immutable subset of `Value` —
46/// numbers (canonicalized), strings, chars, bools, and symbol/expr handles.
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48pub enum ValueKey {
49    Int(i64),
50    BigInt(num_bigint::BigInt),
51    Rational(num_bigint::BigInt, num_bigint::BigInt), // reduced fraction; denominator positive
52    Float(u64),          // f64 bit pattern; NaN keys are rejected
53    Str(String),
54    Char(char),
55    Bool(bool),
56    Symbol(u32),         // SymbolId.0
57    Expr(u32),           // ExprId inner value
58}
59
60impl ValueKey {
61    /// Convert a `Value` to a hashable key, or `None` if the value is not a valid key type
62    /// (complex numbers, arrays, dicts, sets, class instances, etc. → `None`; NaN → `None`).
63    pub fn from_value(v: &Value) -> Option<ValueKey> {
64        match v {
65            Value::Number(n) => number_to_key(n),
66            Value::String(s) => Some(ValueKey::Str(s.clone())),
67            Value::Char(c) => Some(ValueKey::Char(*c)),
68            Value::Bool(b) => Some(ValueKey::Bool(*b)),
69            Value::Symbol(s) => Some(ValueKey::Symbol(*s)),
70            Value::Expr(id) => Some(ValueKey::Expr(id.as_u32())),
71            _ => None,
72        }
73    }
74
75    /// Reconstruct the corresponding `Value` (numeric keys produce `Number`, `Symbol` produces
76    /// `Value::Symbol`, `Expr` produces `Value::Expr`).
77    pub fn to_value(&self) -> Value {
78        match self {
79            ValueKey::Int(i) => Value::Number(Number::from(*i)),
80            ValueKey::BigInt(b) => Value::Number(Number::Integer(b.clone())),
81            ValueKey::Rational(n, d) => Value::Number(Number::Rational(BigRational::new(n.clone(), d.clone()))),
82            ValueKey::Float(bits) => Value::Number(Number::Real(Real::F64(f64::from_bits(*bits)))),
83            ValueKey::Str(s) => Value::String(s.clone()),
84            ValueKey::Char(c) => Value::Char(*c),
85            ValueKey::Bool(b) => Value::Bool(*b),
86            ValueKey::Symbol(s) => Value::Symbol(*s),
87            ValueKey::Expr(u) => Value::Expr(ExprId::from_u32(*u)),
88        }
89    }
90}
91
92/// Map a `Number` to a hashable key (spec §11.6): integers that fit `i64` → `Int` (else `BigInt`),
93/// rationals → reduced `Rational` with positive denominator, reals → `Float` bit pattern (NaN → `None`),
94/// complex → `None`. Fixed-width collapsed variants are keyed by value after normalizing to the
95/// exact/`Real` layer (spec §6.1).
96fn number_to_key(n: &Number) -> Option<ValueKey> {
97    if n.is_complex() {
98        return None;
99    }
100    match n {
101        Number::Integer(i) => match i.to_i64() {
102            Some(v) => Some(ValueKey::Int(v)),
103            None => Some(ValueKey::BigInt(i.clone())),
104        },
105        Number::Rational(r) => {
106            // Re-normalize so the key always holds a reduced fraction with a positive denominator (spec §6.1).
107            let r = BigRational::new(r.numer().clone(), r.denom().clone());
108            Some(ValueKey::Rational(r.numer().clone(), r.denom().clone()))
109        }
110        Number::Real(Real::F64(x)) => (!x.is_nan()).then_some(ValueKey::Float(x.to_bits())),
111        Number::Real(Real::F32(x)) => (!x.is_nan()).then_some(ValueKey::Float((*x as f64).to_bits())),
112        Number::BigFloat(f) => (!f.is_nan()).then_some(ValueKey::Float(f.to_bits())),
113        // Fixed-width collapsed integers normalize to the exact layer (spec §6.1).
114        other => match other.as_i64() {
115            Some(v) => Some(ValueKey::Int(v)),
116            None => other.as_bigint().map(ValueKey::BigInt),
117        },
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use num_bigint::BigInt;
125    use num_rational::BigRational;
126
127    use crate::expr_pool::ExprPool;
128
129    /// `from_value` → key, `to_value` → the original value (round-trip, spec §11.6).
130    fn assert_roundtrip(v: Value, key: ValueKey) {
131        let k = ValueKey::from_value(&v).unwrap_or_else(|| panic!("expected a key for {v:?}"));
132        assert_eq!(k, key);
133        assert_eq!(k.to_value(), v);
134    }
135
136    #[test]
137    fn int_key_roundtrip() {
138        assert_roundtrip(Value::Number(Number::from(1)), ValueKey::Int(1));
139        assert_roundtrip(Value::Number(Number::from(-7)), ValueKey::Int(-7));
140    }
141
142    #[test]
143    fn bigint_key_roundtrip() {
144        let big = BigInt::from(i64::MAX) + BigInt::from(1);
145        assert_roundtrip(Value::Number(Number::Integer(big.clone())), ValueKey::BigInt(big));
146    }
147
148    #[test]
149    fn rational_key_roundtrip() {
150        assert_roundtrip(
151            Value::Number(Number::Rational(BigRational::new(BigInt::from(1), BigInt::from(3)))),
152            ValueKey::Rational(BigInt::from(1), BigInt::from(3)),
153        );
154        // The key always holds a reduced fraction with a positive denominator (spec §6.1).
155        assert_roundtrip(
156            Value::Number(Number::Rational(BigRational::new(BigInt::from(2), BigInt::from(-3)))),
157            ValueKey::Rational(BigInt::from(-2), BigInt::from(3)),
158        );
159    }
160
161    #[test]
162    fn float_key_roundtrip() {
163        assert_roundtrip(Value::Number(Number::from(2.5)), ValueKey::Float(2.5f64.to_bits()));
164        // F32 promotes to F64 when keyed (spec §6.1 promotion); the key is the F64 bit pattern.
165        let v = Value::Number(Number::Real(Real::F32(1.5)));
166        assert_eq!(ValueKey::from_value(&v), Some(ValueKey::Float((1.5f32 as f64).to_bits())));
167        assert_eq!(
168            ValueKey::Float((1.5f32 as f64).to_bits()).to_value(),
169            Value::Number(Number::Real(Real::F64(1.5)))
170        );
171    }
172
173    #[test]
174    fn scalar_key_roundtrip() {
175        assert_roundtrip(Value::String("hello".to_string()), ValueKey::Str("hello".to_string()));
176        assert_roundtrip(Value::Char('x'), ValueKey::Char('x'));
177        assert_roundtrip(Value::Bool(true), ValueKey::Bool(true));
178    }
179
180    #[test]
181    fn symbol_and_expr_keys() {
182        assert_eq!(ValueKey::from_value(&Value::Symbol(42)), Some(ValueKey::Symbol(42)));
183        assert_eq!(ValueKey::Symbol(42).to_value(), Value::Symbol(42));
184
185        let pool = ExprPool::new();
186        let id = pool.integer(3);
187        assert_eq!(ValueKey::from_value(&Value::Expr(id)), Some(ValueKey::Expr(id.as_u32())));
188        assert_eq!(ValueKey::Expr(id.as_u32()).to_value(), Value::Expr(id));
189    }
190
191    #[test]
192    fn unsupported_values_are_none() {
193        assert_eq!(ValueKey::from_value(&Value::Number(Number::complex(1, 2))), None);
194        assert_eq!(ValueKey::from_value(&Value::Array(vec![Value::Number(Number::from(1))])), None);
195        assert_eq!(ValueKey::from_value(&Value::Dict(HashMap::new())), None);
196        assert_eq!(ValueKey::from_value(&Value::Set(HashSet::new())), None);
197        assert_eq!(ValueKey::from_value(&Value::Undefined), None);
198        assert_eq!(ValueKey::from_value(&Value::Nil), None);
199    }
200
201    #[test]
202    fn nan_is_not_a_key() {
203        assert_eq!(ValueKey::from_value(&Value::Number(Number::Real(Real::F64(f64::NAN)))), None);
204        assert_eq!(ValueKey::from_value(&Value::Number(Number::Real(Real::F32(f32::NAN)))), None);
205    }
206
207    #[test]
208    fn to_value_reconstructs_values() {
209        assert_eq!(ValueKey::Int(5).to_value(), Value::Number(Number::from(5)));
210        assert_eq!(
211            ValueKey::BigInt(BigInt::from(1u64 << 40)).to_value(),
212            Value::Number(Number::Integer(BigInt::from(1u64 << 40)))
213        );
214        assert_eq!(ValueKey::Float(2.5f64.to_bits()).to_value(), Value::Number(Number::Real(Real::F64(2.5))));
215        assert_eq!(ValueKey::Str("abc".to_string()).to_value(), Value::String("abc".to_string()));
216        assert_eq!(ValueKey::Char('z').to_value(), Value::Char('z'));
217        assert_eq!(ValueKey::Bool(false).to_value(), Value::Bool(false));
218    }
219
220    #[test]
221    fn dict_and_set_hold_hashable_keys() {
222        let mut m = HashMap::new();
223        m.insert(ValueKey::Str("a".into()), Value::Number(Number::from(1)));
224        let Value::Dict(d) = Value::Dict(m) else { unreachable!() };
225        assert_eq!(d.get(&ValueKey::Str("a".into())), Some(&Value::Number(Number::from(1))));
226
227        let mut s = HashSet::new();
228        s.insert(ValueKey::Int(1));
229        s.insert(ValueKey::Int(1));
230        s.insert(ValueKey::Float(2.5f64.to_bits()));
231        let Value::Set(s) = Value::Set(s) else { unreachable!() };
232        assert_eq!(s.len(), 2);
233        assert!(s.contains(&ValueKey::Int(1)));
234        assert!(s.contains(&ValueKey::Float(2.5f64.to_bits())));
235    }
236}