use std::{ cell::RefCell, cmp::{ Eq, PartialEq, }, collections::HashMap, fmt, rc::Rc, }; #[derive (Debug, Eq, PartialEq)] pub struct BogusClosure { pub idx: usize, pub upvalues: Vec , } #[derive (Clone, Debug, PartialEq)] pub enum Value { Nil, Boolean (bool), // Rust is very strict about float equality, so some of my code // here is probably wrong in subtle ways. Float (f64), Integer (i64), String (Rc ), Table (Rc >), // These are all bogus, I haven't figured out how to implement // tables and function pointers yet BogusArg (Rc >), BogusClosure (Rc ), BogusEnv (Rc >), BogusPrint, } impl Default for Value { fn default () -> Self { Self::Nil } } impl fmt::Display for Value { fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Value::Nil => write! (f, "nil"), Value::Boolean (false) => write! (f, "false"), Value::Boolean (true) => write! (f, "true"), Value::Float (x) => write! (f, "{:?}", x), Value::Integer (x) => write! (f, "{}", x), Value::String (s) => write! (f, "{}", s), Value::Table (t) => write! (f, "table: {:?}", std::rc::Rc::as_ptr (t)), Value::BogusArg (_) => write! (f, "BogusArg"), Value::BogusClosure (_) => write! (f, "BogusClosure"), Value::BogusEnv (_) => write! (f, "BogusEnv"), Value::BogusPrint => write! (f, "BogusPrint"), } } } impl From for Value { fn from (x: bool) -> Self { Self::Boolean (x) } } impl From for Value { fn from (x: String) -> Self { Self::String (x.into ()) } } impl From <&str> for Value { fn from (x: &str) -> Self { Self::from (String::from (x)) } } impl From for Value { fn from (x: i32) -> Self { Self::Integer (i64::from (x)) } } impl From for Value { fn from (x: i64) -> Self { Self::Integer (x) } } impl From for Value { fn from (x: f64) -> Self { Self::Float (x) } } impl From for Value { fn from (x: Table) -> Self { Self::Table (Rc::new (RefCell::new (x))) } } impl Eq for Value {} impl std::hash::Hash for Value { fn hash (&self, state: &mut H) { // Per https://doc.rust-lang.org/std/hash/trait.Hash.html#prefix-collisions [0xff].hash (state); match self { // TODO: Weaken to a Lua error Self::Nil => panic! ("can't hash a nil value"), Self::Boolean (x) => x.hash (state), Self::Float (x) => x.to_ne_bytes ().hash (state), Self::Integer (x) => x.hash (state), // TODO: Implement string interning so we don't hash the whole string here Self::String (x) => x.hash (state), Self::Table (x) => Rc::as_ptr (&x).hash (state), Self::BogusArg (_) => panic! ("can't hash Bogus values"), Self::BogusClosure (_) => panic! ("can't hash Bogus values"), Self::BogusEnv (_) => panic! ("can't hash Bogus values"), Self::BogusPrint => panic! ("can't hash Bogus values"), } } } impl PartialEq for Value { fn eq (&self, rhs: &i64) -> bool { *self == Value::from (*rhs) } } impl Value { pub fn as_float (&self) -> Option { match self { Self::Float (x) => Some (*x), // FloatToInt isn't stable yet, so only ints in i32 space can practically be used for now Self::Integer (x) => f64::try_from (i32::try_from (*x).ok ()?).ok (), _ => None, } } pub fn as_table (&self) -> Option <&Rc >> { match self { Self::Table (t) => Some (t), _ => None, } } pub fn is_truthy (&self) -> bool { // And this is something Lua does better than JS and Python. match self { Value::Nil => false, Value::Boolean (false) => false, _ => true, } } pub fn take (&mut self) -> Self { let mut x = Value::Nil; std::mem::swap (self, &mut x); x } } #[derive (Debug, Default, Eq, PartialEq)] pub struct Table { array: Vec , hash: HashMap , } impl Table { fn get_inner (&self, key: &Value) -> Value { self.hash.get (key).cloned ().unwrap_or_default () } pub fn get > (&self, key: A) -> Value { self.get_inner (&(key.into ())) } fn insert_inner (&mut self, a: Value, b: Value) { self.hash.insert (a, b); } pub fn insert , B: Into > ( &mut self, a: A, b: B, ) { self.insert_inner (a.into (), b.into ()) } } #[cfg (test)] mod tests { use std::collections::HashMap; use super::{ Table, Value, }; #[test] fn smoke () { let v_a = Value::from (5.0); let v_b = Value::from (5); assert_eq! (v_a.as_float (), Some (5.0)); assert_eq! (v_b.as_float (), Some (5.0)); } #[test] fn tables () { let nil = Value::Nil; assert_ne! (Value::from (18.0), Value::from (19.0)); let mut t = HashMap::new (); t.insert (Value::from ("x"), Value::from (19.0)); assert_eq! (t.get (&Value::from ("x")), Some (&Value::from (19.0))); let mut t = Table::default (); assert_eq! (t.get ("a"), nil); assert_eq! (t.get ("b"), nil); t.insert ("a", 1993); t.insert ("b", 2007); assert_eq! (t.get ("a"), 1993); assert_eq! (t.get ("b"), 2007); t.insert (19, 93); assert_eq! (t.get (19), 93); } }