lunar_wave/src/value.rs

229 lines
4.8 KiB
Rust
Raw Normal View History

use std::{
2023-09-26 20:49:12 +00:00
cmp::{
Eq,
PartialEq,
},
collections::HashMap,
fmt,
rc::Rc,
};
2023-09-26 20:49:12 +00:00
#[derive (Debug, Eq, PartialEq)]
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.
Float (f64),
2023-09-26 20:49:12 +00:00
Integer (i64),
String (Rc <String>),
2023-09-26 20:49:12 +00:00
Table (Rc <Table>),
// 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>>),
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 <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)
}
}
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 {}
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),
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)
}
}
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);
}
}