use std::collections::{HashMap, HashSet};
use num_rational::BigRational;
use num_traits::ToPrimitive;
use crate::expr_pool::ExprId;
use crate::number::{Number, Real};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IndeterminateForm {
ZeroOverZero,
InfOverInf,
ZeroTimesInf,
InfMinusInf,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Nil,
Number(Number),
Bool(bool),
Char(char),
String(String),
Array(Vec<Value>),
Dict(HashMap<ValueKey, Value>),
Set(HashSet<ValueKey>),
Expr(ExprId),
Symbol(u32),
Indeterminate(IndeterminateForm),
Undefined,
Error(String),
Tuple(Vec<Value>),
Result(std::result::Result<Box<Value>, String>),
Class(u32), Option(Option<Box<Value>>), }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ValueKey {
Int(i64),
BigInt(num_bigint::BigInt),
Rational(num_bigint::BigInt, num_bigint::BigInt), Float(u64), Str(String),
Char(char),
Bool(bool),
Symbol(u32), Expr(u32), }
impl ValueKey {
pub fn from_value(v: &Value) -> Option<ValueKey> {
match v {
Value::Number(n) => number_to_key(n),
Value::String(s) => Some(ValueKey::Str(s.clone())),
Value::Char(c) => Some(ValueKey::Char(*c)),
Value::Bool(b) => Some(ValueKey::Bool(*b)),
Value::Symbol(s) => Some(ValueKey::Symbol(*s)),
Value::Expr(id) => Some(ValueKey::Expr(id.as_u32())),
_ => None,
}
}
pub fn to_value(&self) -> Value {
match self {
ValueKey::Int(i) => Value::Number(Number::from(*i)),
ValueKey::BigInt(b) => Value::Number(Number::Integer(b.clone())),
ValueKey::Rational(n, d) => Value::Number(Number::Rational(BigRational::new(n.clone(), d.clone()))),
ValueKey::Float(bits) => Value::Number(Number::Real(Real::F64(f64::from_bits(*bits)))),
ValueKey::Str(s) => Value::String(s.clone()),
ValueKey::Char(c) => Value::Char(*c),
ValueKey::Bool(b) => Value::Bool(*b),
ValueKey::Symbol(s) => Value::Symbol(*s),
ValueKey::Expr(u) => Value::Expr(ExprId::from_u32(*u)),
}
}
}
fn number_to_key(n: &Number) -> Option<ValueKey> {
if n.is_complex() {
return None;
}
match n {
Number::Integer(i) => match i.to_i64() {
Some(v) => Some(ValueKey::Int(v)),
None => Some(ValueKey::BigInt(i.clone())),
},
Number::Rational(r) => {
let r = BigRational::new(r.numer().clone(), r.denom().clone());
Some(ValueKey::Rational(r.numer().clone(), r.denom().clone()))
}
Number::Real(Real::F64(x)) => (!x.is_nan()).then_some(ValueKey::Float(x.to_bits())),
Number::Real(Real::F32(x)) => (!x.is_nan()).then_some(ValueKey::Float((*x as f64).to_bits())),
Number::BigFloat(f) => (!f.is_nan()).then_some(ValueKey::Float(f.to_bits())),
other => match other.as_i64() {
Some(v) => Some(ValueKey::Int(v)),
None => other.as_bigint().map(ValueKey::BigInt),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigInt;
use num_rational::BigRational;
use crate::expr_pool::ExprPool;
fn assert_roundtrip(v: Value, key: ValueKey) {
let k = ValueKey::from_value(&v).unwrap_or_else(|| panic!("expected a key for {v:?}"));
assert_eq!(k, key);
assert_eq!(k.to_value(), v);
}
#[test]
fn int_key_roundtrip() {
assert_roundtrip(Value::Number(Number::from(1)), ValueKey::Int(1));
assert_roundtrip(Value::Number(Number::from(-7)), ValueKey::Int(-7));
}
#[test]
fn bigint_key_roundtrip() {
let big = BigInt::from(i64::MAX) + BigInt::from(1);
assert_roundtrip(Value::Number(Number::Integer(big.clone())), ValueKey::BigInt(big));
}
#[test]
fn rational_key_roundtrip() {
assert_roundtrip(
Value::Number(Number::Rational(BigRational::new(BigInt::from(1), BigInt::from(3)))),
ValueKey::Rational(BigInt::from(1), BigInt::from(3)),
);
assert_roundtrip(
Value::Number(Number::Rational(BigRational::new(BigInt::from(2), BigInt::from(-3)))),
ValueKey::Rational(BigInt::from(-2), BigInt::from(3)),
);
}
#[test]
fn float_key_roundtrip() {
assert_roundtrip(Value::Number(Number::from(2.5)), ValueKey::Float(2.5f64.to_bits()));
let v = Value::Number(Number::Real(Real::F32(1.5)));
assert_eq!(ValueKey::from_value(&v), Some(ValueKey::Float((1.5f32 as f64).to_bits())));
assert_eq!(
ValueKey::Float((1.5f32 as f64).to_bits()).to_value(),
Value::Number(Number::Real(Real::F64(1.5)))
);
}
#[test]
fn scalar_key_roundtrip() {
assert_roundtrip(Value::String("hello".to_string()), ValueKey::Str("hello".to_string()));
assert_roundtrip(Value::Char('x'), ValueKey::Char('x'));
assert_roundtrip(Value::Bool(true), ValueKey::Bool(true));
}
#[test]
fn symbol_and_expr_keys() {
assert_eq!(ValueKey::from_value(&Value::Symbol(42)), Some(ValueKey::Symbol(42)));
assert_eq!(ValueKey::Symbol(42).to_value(), Value::Symbol(42));
let pool = ExprPool::new();
let id = pool.integer(3);
assert_eq!(ValueKey::from_value(&Value::Expr(id)), Some(ValueKey::Expr(id.as_u32())));
assert_eq!(ValueKey::Expr(id.as_u32()).to_value(), Value::Expr(id));
}
#[test]
fn unsupported_values_are_none() {
assert_eq!(ValueKey::from_value(&Value::Number(Number::complex(1, 2))), None);
assert_eq!(ValueKey::from_value(&Value::Array(vec![Value::Number(Number::from(1))])), None);
assert_eq!(ValueKey::from_value(&Value::Dict(HashMap::new())), None);
assert_eq!(ValueKey::from_value(&Value::Set(HashSet::new())), None);
assert_eq!(ValueKey::from_value(&Value::Undefined), None);
assert_eq!(ValueKey::from_value(&Value::Nil), None);
}
#[test]
fn nan_is_not_a_key() {
assert_eq!(ValueKey::from_value(&Value::Number(Number::Real(Real::F64(f64::NAN)))), None);
assert_eq!(ValueKey::from_value(&Value::Number(Number::Real(Real::F32(f32::NAN)))), None);
}
#[test]
fn to_value_reconstructs_values() {
assert_eq!(ValueKey::Int(5).to_value(), Value::Number(Number::from(5)));
assert_eq!(
ValueKey::BigInt(BigInt::from(1u64 << 40)).to_value(),
Value::Number(Number::Integer(BigInt::from(1u64 << 40)))
);
assert_eq!(ValueKey::Float(2.5f64.to_bits()).to_value(), Value::Number(Number::Real(Real::F64(2.5))));
assert_eq!(ValueKey::Str("abc".to_string()).to_value(), Value::String("abc".to_string()));
assert_eq!(ValueKey::Char('z').to_value(), Value::Char('z'));
assert_eq!(ValueKey::Bool(false).to_value(), Value::Bool(false));
}
#[test]
fn dict_and_set_hold_hashable_keys() {
let mut m = HashMap::new();
m.insert(ValueKey::Str("a".into()), Value::Number(Number::from(1)));
let Value::Dict(d) = Value::Dict(m) else { unreachable!() };
assert_eq!(d.get(&ValueKey::Str("a".into())), Some(&Value::Number(Number::from(1))));
let mut s = HashSet::new();
s.insert(ValueKey::Int(1));
s.insert(ValueKey::Int(1));
s.insert(ValueKey::Float(2.5f64.to_bits()));
let Value::Set(s) = Value::Set(s) else { unreachable!() };
assert_eq!(s.len(), 2);
assert!(s.contains(&ValueKey::Int(1)));
assert!(s.contains(&ValueKey::Float(2.5f64.to_bits())));
}
}