2023-09-26 19:41:10 +00:00
|
|
|
use std::{
|
2023-09-26 20:49:12 +00:00
|
|
|
cmp::{
|
|
|
|
Eq,
|
|
|
|
PartialEq,
|
|
|
|
},
|
|
|
|
collections::HashMap,
|
2023-09-26 20:57:23 +00:00
|
|
|
fmt,
|
2023-09-26 19:41:10 +00:00
|
|
|
rc::Rc,
|
|
|
|
};
|
|
|
|
|
2023-09-26 20:49:12 +00:00
|
|
|
#[derive (Debug, Eq, PartialEq)]
|
2023-09-26 19:41:10 +00:00
|
|
|
pub struct BogusClosure {
|
|
|
|
pub idx: usize,
|
|
|
|
pub upvalues: Vec <Value>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive (Clone, Debug, PartialEq)]
|
|
|
|
pub enum Value {
|
|
|
|
Nil,
|
|
|
|
Boolean (bool),
|
2023-09-26 20:49:12 +00:00
|
|
|
|
|
|
|
// Rust is very strict about float equality, so some of my code
|
|
|
|
// here is probably wrong in subtle ways.
|
2023-09-26 19:41:10 +00:00
|
|
|
Float (f64),
|
2023-09-26 20:49:12 +00:00
|
|
|
|
2023-09-26 19:41:10 +00:00
|
|
|
Integer (i64),
|
|
|
|
String (Rc <String>),
|
2023-09-26 20:49:12 +00:00
|
|
|
Table (Rc <Table>),
|
2023-09-26 19:41:10 +00:00
|
|
|
|
|
|
|
// These are all bogus, I haven't figured out how to implement
|
|
|
|
// tables and function pointers yet
|
|
|
|
|
|
|
|
BogusArg (Rc <Vec <String>>),
|
|
|
|
BogusClosure (Rc <BogusClosure>),
|
2023-09-26 20:49:12 +00:00
|
|
|
BogusEnv (Rc <HashMap <String, Value>>),
|
2023-09-26 19:41:10 +00:00
|
|
|
BogusPrint,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Value {
|
|
|
|
fn default () -> Self {
|
|
|
|
Self::Nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-26 20:57:23 +00:00
|
|
|
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"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-26 19:41:10 +00:00
|
|
|
impl From <String> 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 <i32> for Value {
|
|
|
|
fn from (x: i32) -> Self {
|
2023-09-26 20:49:12 +00:00
|
|
|
Self::Integer (i64::from (x))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From <i64> for Value {
|
|
|
|
fn from (x: i64) -> Self {
|
|
|
|
Self::Integer (x)
|
2023-09-26 19:41:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From <f64> for Value {
|
|
|
|
fn from (x: f64) -> Self {
|
|
|
|
Self::Float (x)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-26 20:49:12 +00:00
|
|
|
impl From <Table> for Value {
|
|
|
|
fn from (x: Table) -> Self {
|
|
|
|
Self::Table (x.into ())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Eq for Value {}
|
|
|
|
|
2023-09-26 19:41:10 +00:00
|
|
|
impl std::hash::Hash for Value {
|
|
|
|
fn hash <H: std::hash::Hasher> (&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),
|
2023-09-26 20:49:12 +00:00
|
|
|
|
|
|
|
// 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),
|
|
|
|
|
2023-09-26 19:41:10 +00:00
|
|
|
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"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-26 20:49:12 +00:00
|
|
|
impl PartialEq <i64> for Value {
|
|
|
|
fn eq (&self, rhs: &i64) -> bool {
|
|
|
|
*self == Value::from (*rhs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-26 19:41:10 +00:00
|
|
|
impl Value {
|
|
|
|
pub fn as_float (&self) -> Option <f64> {
|
|
|
|
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 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
|
|
|
|
}
|
|
|
|
}
|
2023-09-26 20:49:12 +00:00
|
|
|
|
|
|
|
#[derive (Debug, Default, Eq, PartialEq)]
|
|
|
|
pub struct Table {
|
|
|
|
array: Vec <Value>,
|
|
|
|
hash: HashMap <Value, Value>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Table {
|
|
|
|
fn get_inner (&self, key: &Value) -> Value {
|
|
|
|
self.hash.get (key).cloned ().unwrap_or_default ()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get <A: Into <Value>> (&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 <A: Into <Value>, B: Into <Value>> (
|
|
|
|
&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);
|
|
|
|
}
|
|
|
|
}
|