lysp: add hashtable support
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
(setq h (hash/new
|
||||
'(key . value)
|
||||
'(1 . 2)
|
||||
'("string" . 3)
|
||||
'(3 . 4)))
|
||||
|
||||
(assert (= 'value (hash/get h 'key)))
|
||||
(assert (= 2 (hash/get h 1)))
|
||||
(assert (= 100 (hash/get h 0 100)))
|
||||
(assert (= NIL (hash/get h 0)))
|
||||
|
||||
(hash/map! (lambda (_ v) v) h)
|
||||
(assert (= 'value (hash/get h 'key)))
|
||||
(assert (= 2 (hash/get h 1)))
|
||||
(assert (= 100 (hash/get h 0 100)))
|
||||
(assert (= NIL (hash/get h 0)))
|
||||
|
||||
(hash/remove! h 'key)
|
||||
(assert (= NIL (hash/get h 'key)))
|
||||
|
||||
(hash/map! (lambda (_ v) (+ v 100)) h)
|
||||
(assert (= 102 (hash/get h 1)))
|
||||
(assert (= 103 (hash/get h "string")))
|
||||
(assert (= 100 (hash/get h 0 100)))
|
||||
(assert (= NIL (hash/get h 0)))
|
||||
|
||||
(setq hl (hash->list h))
|
||||
(assert (= '(3 . 104) (find (lambda (a) (= (car a) 3)) hl)))
|
||||
(assert (= '("string" . 103) (find (lambda (a) (= (car a) "string")) hl)))
|
||||
(assert (= '(1 . 102) (find (lambda (a) (= (car a) 1)) hl)))
|
||||
|
||||
(setq sum-1 0)
|
||||
(hash/for-each (lambda (_ v) (setq sum-1 (+ sum-1 v))) h)
|
||||
(setq sum-2 (hash/fold 0 (lambda (a _ v) (+ a v)) h))
|
||||
(assert (= sum-1 sum-2 309))
|
||||
|
||||
(hash/filter! (lambda (k _) (/= k "string")) h)
|
||||
;; Hash equality
|
||||
(assert (= h (hash/new '(1 . 102) '(3 . 104))))
|
||||
(assert (=
|
||||
(hash/new '(1 . 2) '(2 . 3) '("string" . "value"))
|
||||
(list->hash '((2 . 3) ("string" . "value") (1 . 2)))
|
||||
))
|
||||
|
||||
;; hash->list->hash idempotence
|
||||
(setq h (hash/new))
|
||||
(let (i 0)
|
||||
(while (< i 10000)
|
||||
(hash/put! h (+ "key" i) (+ "value" i))
|
||||
(setq i (+ i 1))
|
||||
))
|
||||
(assert (= h (list->hash (hash->list h))))
|
||||
@@ -66,6 +66,7 @@ impl Expression {
|
||||
fn parse_inner(value: &Value) -> Rc<Self> {
|
||||
match value {
|
||||
Value::Vector(vector) => Rc::new(Self::Vector(vector.clone())),
|
||||
Value::HashTable(_) => todo!(),
|
||||
Value::String(value) => Rc::new(Self::StringLiteral(value.clone())),
|
||||
Value::Quasi(_value) => todo!("{value}"),
|
||||
Value::Unquote(_value) => todo!("Unquote {_value}"),
|
||||
|
||||
@@ -97,6 +97,8 @@ pub enum MachineError {
|
||||
Read(ReadError),
|
||||
#[error("compile error: {0}")]
|
||||
Compile(#[from] CompileError),
|
||||
#[error("value cannot be used as a hashmap key: {0}")]
|
||||
InvalidHashTableKey(Value),
|
||||
}
|
||||
|
||||
impl MachineError {
|
||||
|
||||
@@ -40,6 +40,7 @@ impl MacroExpand for Value {
|
||||
| Self::NativeFunction(_)
|
||||
| Self::NativeValue(_)
|
||||
| Self::Vector(_)
|
||||
| Self::HashTable(_)
|
||||
| Self::UnquoteSplice(_)
|
||||
| Self::Unquote(_) => Ok(self.clone()),
|
||||
// | Self::NativeFunction(_) => Ok(self.clone()),
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
Value,
|
||||
env::Environment,
|
||||
value::{
|
||||
ConsCell,
|
||||
ConsCell, HashTable, HashTableData,
|
||||
convert::{AnyFunction, TryFromValue},
|
||||
},
|
||||
},
|
||||
@@ -154,4 +154,183 @@ pub fn load(env: &Rc<Environment>) {
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
|
||||
// Hash table
|
||||
env.defun_native(
|
||||
"hash/new",
|
||||
"Creates a hash table from the list of pairs",
|
||||
|_, _, args| {
|
||||
let mut hash = HashTableData::new(16);
|
||||
for arg in args {
|
||||
let Value::Cons(cons) = arg else {
|
||||
return Err(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "a pair".into(),
|
||||
got: arg.clone(),
|
||||
}));
|
||||
};
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
hash.insert(car.clone(), cdr.clone())?;
|
||||
}
|
||||
let hash = HashTable::from(hash);
|
||||
Ok(hash.into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/length",
|
||||
"Returns the number of associations in the hashtable",
|
||||
|_, _, args| {
|
||||
let [table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let len = table.borrow().len();
|
||||
Ok(len.into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash->list",
|
||||
"Converts a hashtable into a list of pairs",
|
||||
|_, _, args| {
|
||||
let [table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let mut list = Value::Nil;
|
||||
for (key, value) in table.borrow().iter() {
|
||||
list = key.clone().cons(value.clone()).cons(list);
|
||||
}
|
||||
Ok(list)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"list->hash",
|
||||
"Converts a list of pairs into a hashtable",
|
||||
|_, _, args| {
|
||||
let [pairs] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let mut hash = HashTableData::new(16);
|
||||
let pair_iter =
|
||||
pairs.proper_iter(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "a list of pairs".into(),
|
||||
got: pairs.clone(),
|
||||
}));
|
||||
for pair in pair_iter {
|
||||
let pair = pair?;
|
||||
let Value::Cons(cons) = pair else {
|
||||
return Err(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "a pair".into(),
|
||||
got: pair.clone(),
|
||||
}));
|
||||
};
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
|
||||
hash.insert(car.clone(), cdr.clone())?;
|
||||
}
|
||||
let hash = HashTable::from(hash);
|
||||
Ok(hash.into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/put!",
|
||||
"Inserts an association into the hashtable",
|
||||
|_, _, args| {
|
||||
let [table, key, value] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let value = table.borrow_mut().insert(key.clone(), value.clone())?;
|
||||
Ok(value)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/remove!",
|
||||
"Removes an association from the hashtable",
|
||||
|_, _, args| {
|
||||
let [table, key] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let value = table.borrow_mut().remove(key).unwrap_or(Value::Nil);
|
||||
Ok(value)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/for-each",
|
||||
"Applies a (k, v) -> void function to all associations in the hashtable",
|
||||
|vm, env, args| {
|
||||
let [function, table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let function = AnyFunction::try_from_value(function)?;
|
||||
for (key, value) in table.borrow().iter() {
|
||||
function.invoke(vm, env, &[key.clone(), value.clone()])?;
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/fold",
|
||||
"Applies a (acc, k, v) -> acc' function to all associations in the hashtable",
|
||||
|vm, env, args| {
|
||||
let [acc, function, table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let function = AnyFunction::try_from_value(function)?;
|
||||
let mut acc = acc.clone();
|
||||
for (key, value) in table.borrow().iter() {
|
||||
acc = function.invoke(vm, env, &[acc, key.clone(), value.clone()])?;
|
||||
}
|
||||
Ok(acc)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/filter!",
|
||||
"Removes associations not matching the predicate from the hashtable",
|
||||
|vm, env, args| {
|
||||
let [predicate, table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let predicate = AnyFunction::try_from_value(predicate)?;
|
||||
table.borrow_mut().retain_invoke(vm, env, &predicate)?;
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/map!",
|
||||
"Applies a (k, v) -> v' transform on the hashtable",
|
||||
|vm, env, args| {
|
||||
let [transform, table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let transform = AnyFunction::try_from_value(transform)?;
|
||||
{
|
||||
let mut borrow = table.borrow_mut();
|
||||
borrow.iter_mut().try_for_each(|(key, value)| {
|
||||
let output = transform.invoke(vm, env, &[key.clone(), value.clone()])?;
|
||||
*value = output;
|
||||
Ok::<_, MachineError>(())
|
||||
})?;
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/get",
|
||||
"Retrieves a value from the hashtable associated with given key",
|
||||
|_, _, args| {
|
||||
let (table, key, default) = match args {
|
||||
[table, key] => (table, key, &Value::Nil),
|
||||
[table, key, default] => (table, key, default),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let value = table.borrow().get(key).unwrap_or(default).clone();
|
||||
Ok(value)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ pub fn load(env: &Rc<Environment>) {
|
||||
Value::UnquoteSplice(_) => "an unquote-spliced value".into(),
|
||||
Value::Identifier(identifier) => format!("an identifier {:?}", identifier.as_ref()),
|
||||
Value::Vector(_) => "a vector".into(),
|
||||
Value::HashTable(_) => "a hash table".into(),
|
||||
Value::String(_) => "a string".into(),
|
||||
Value::Keyword(_) => "a keyword".into(),
|
||||
Value::Closure(closure) => {
|
||||
|
||||
@@ -104,8 +104,11 @@ pub(super) fn load(env: &Rc<Environment>) {
|
||||
|
||||
fn value_add(a: &Value, b: &Value) -> Result<Value, MachineError> {
|
||||
match (a, b) {
|
||||
(Value::String(a), _) => Ok(Value::String(format!("{a}{b}").into())),
|
||||
(_, Value::String(b)) => Ok(Value::String(format!("{a}{b}").into())),
|
||||
(Value::String(a), Value::String(b)) => {
|
||||
Ok(Value::String(format!("{}{}", &**a, &**b).into()))
|
||||
}
|
||||
(Value::String(a), _) => Ok(Value::String(format!("{}{b}", &**a).into())),
|
||||
(_, Value::String(b)) => Ok(Value::String(format!("{a}{}", &**b).into())),
|
||||
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(*a + *b)),
|
||||
(Value::Number(a), _) => Ok(Value::Number(*a + NumberValue::try_from_value(b)?)),
|
||||
(_, Value::Number(b)) => Ok(Value::Number(NumberValue::try_from_value(a)? + *b)),
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
env::Environment,
|
||||
machine::Machine,
|
||||
value::{
|
||||
BooleanValue, BytecodeFunction, ClosureValue, ConsCell, IdentifierValue,
|
||||
BooleanValue, BytecodeFunction, ClosureValue, ConsCell, HashTable, IdentifierValue,
|
||||
NativeFunction, NativeValue, NumberValue, StringValue, Vector,
|
||||
},
|
||||
},
|
||||
@@ -139,6 +139,24 @@ impl From<StringValue> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for Rc<HashTable> {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::HashTable(table) => Ok(table.clone()),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "hash".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashTable> for Value {
|
||||
fn from(value: HashTable) -> Self {
|
||||
Value::HashTable(Rc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for IdentifierValue {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
fmt,
|
||||
rc::Rc,
|
||||
slice,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{Value, env::Environment, machine::Machine, value::convert::AnyFunction},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct HashTable(RefCell<HashTableData>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HashTableData {
|
||||
buckets: Box<[Vec<(Value, Value)>]>,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
pub struct HashTableIter<'a> {
|
||||
buckets: slice::Iter<'a, Vec<(Value, Value)>>,
|
||||
inner: Option<slice::Iter<'a, (Value, Value)>>,
|
||||
}
|
||||
pub struct HashTableIterMut<'a> {
|
||||
buckets: slice::IterMut<'a, Vec<(Value, Value)>>,
|
||||
inner: Option<slice::IterMut<'a, (Value, Value)>>,
|
||||
}
|
||||
|
||||
impl HashTable {
|
||||
pub fn borrow(&self) -> Ref<'_, HashTableData> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> RefMut<'_, HashTableData> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HashTable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&*self.0.borrow(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl HashTableData {
|
||||
pub fn new(bucket_count: usize) -> Self {
|
||||
let buckets = vec![vec![]; bucket_count].into_boxed_slice();
|
||||
Self { buckets, length: 0 }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.length == 0
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.length
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
for bucket in self.buckets.iter_mut() {
|
||||
bucket.clear();
|
||||
}
|
||||
self.length = 0;
|
||||
}
|
||||
|
||||
// Returns the value inserted
|
||||
pub fn insert(&mut self, key: Value, value: Value) -> Result<Value, MachineError> {
|
||||
let hash = key
|
||||
.hash()
|
||||
.ok_or_else(|| MachineError::InvalidHashTableKey(key.clone()))?;
|
||||
|
||||
let bucket_index = (hash % self.buckets.len() as u64) as usize;
|
||||
let slot_index = self.buckets[bucket_index]
|
||||
.iter()
|
||||
.position(|(k, _)| k == &key);
|
||||
if let Some(slot_index) = slot_index {
|
||||
self.buckets[bucket_index][slot_index].1 = value.clone();
|
||||
} else {
|
||||
self.buckets[bucket_index].push((key, value.clone()));
|
||||
self.length += 1;
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn retain_invoke(
|
||||
&mut self,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
predicate: &AnyFunction,
|
||||
) -> Result<(), MachineError> {
|
||||
for bucket in self.buckets.iter_mut() {
|
||||
let mut index = 0;
|
||||
loop {
|
||||
if index >= bucket.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let (key, value) = &bucket[index];
|
||||
let output = predicate.invoke(vm, env, &[key.clone(), value.clone()])?;
|
||||
if !output.is_trueish() {
|
||||
bucket.remove(index);
|
||||
self.length -= 1;
|
||||
} else {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &Value) -> Option<&Value> {
|
||||
let hash = key.hash()?;
|
||||
let bucket_index = (hash % self.buckets.len() as u64) as usize;
|
||||
|
||||
self.buckets[bucket_index]
|
||||
.iter()
|
||||
.find(|(k, _)| k == key)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &Value) -> Option<Value> {
|
||||
let hash = key.hash()?;
|
||||
let bucket_index = (hash % self.buckets.len() as u64) as usize;
|
||||
let slot_index = self.buckets[bucket_index]
|
||||
.iter()
|
||||
.position(|(k, _)| k == key)?;
|
||||
let value = self.buckets[bucket_index].remove(slot_index);
|
||||
self.length -= 1;
|
||||
Some(value.1)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> HashTableIter<'_> {
|
||||
HashTableIter {
|
||||
buckets: self.buckets.iter(),
|
||||
inner: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> HashTableIterMut<'_> {
|
||||
HashTableIterMut {
|
||||
buckets: self.buckets.iter_mut(),
|
||||
inner: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for HashTableData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.length != other.length {
|
||||
return false;
|
||||
}
|
||||
for (key, value1) in self.iter() {
|
||||
let Some(value2) = other.get(key) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if value1 != value2 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HashTableData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut list = f.debug_list();
|
||||
for bucket in self.buckets.iter() {
|
||||
for cell in bucket.iter() {
|
||||
list.entry(cell);
|
||||
}
|
||||
}
|
||||
list.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HashTableData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut index = 0;
|
||||
for bucket in self.buckets.iter() {
|
||||
for (key, value) in bucket.iter() {
|
||||
if index != 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "({key} . {value})")?;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashTableData> for HashTable {
|
||||
fn from(value: HashTableData) -> Self {
|
||||
Self(RefCell::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for HashTableIter<'a> {
|
||||
type Item = (&'a Value, &'a Value);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let Some(inner) = &mut self.inner
|
||||
&& let Some((key, value)) = inner.next()
|
||||
{
|
||||
return Some((key, value));
|
||||
}
|
||||
|
||||
let next_bucket = self.buckets.next()?;
|
||||
self.inner = Some(next_bucket.iter());
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> Iterator for HashTableIterMut<'a> {
|
||||
type Item = (&'a Value, &'a mut Value);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let Some(inner) = &mut self.inner
|
||||
&& let Some((key, value)) = inner.next()
|
||||
{
|
||||
return Some((key, value));
|
||||
}
|
||||
|
||||
let next_bucket = self.buckets.next()?;
|
||||
self.inner = Some(next_bucket.iter_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
fmt::{self, Write},
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
@@ -7,6 +8,7 @@ mod boolean;
|
||||
mod closure;
|
||||
mod cons;
|
||||
mod function;
|
||||
mod hashtable;
|
||||
mod identifier;
|
||||
mod iter;
|
||||
mod keyword;
|
||||
@@ -21,6 +23,7 @@ pub use boolean::BooleanValue;
|
||||
pub use closure::{ClosureValue, UpvalueValue};
|
||||
pub use cons::ConsCell;
|
||||
pub use function::BytecodeFunction;
|
||||
pub use hashtable::{HashTable, HashTableData};
|
||||
pub use identifier::IdentifierValue;
|
||||
pub use keyword::Keyword;
|
||||
pub use native::{NativeFunction, NativeObject, NativeValue};
|
||||
@@ -40,7 +43,6 @@ pub enum Value {
|
||||
// Syntactic
|
||||
Nil,
|
||||
Cons(Rc<ConsCell>),
|
||||
Vector(Rc<Vector>),
|
||||
Number(NumberValue),
|
||||
Boolean(BooleanValue),
|
||||
Keyword(Keyword),
|
||||
@@ -50,6 +52,9 @@ pub enum Value {
|
||||
Quote(Rc<Value>),
|
||||
Unquote(Rc<Value>),
|
||||
UnquoteSplice(Rc<Value>),
|
||||
// Collections
|
||||
Vector(Rc<Vector>),
|
||||
HashTable(Rc<HashTable>),
|
||||
// Semantic
|
||||
Closure(ClosureValue),
|
||||
Function(Rc<BytecodeFunction>),
|
||||
@@ -59,6 +64,73 @@ pub enum Value {
|
||||
}
|
||||
|
||||
impl Value {
|
||||
fn hash_inner<H: Hasher>(&self, state: &mut H) -> bool {
|
||||
match self {
|
||||
Self::Nil => state.write_u8(0),
|
||||
Self::Cons(cons) => {
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
state.write_u8(1);
|
||||
if !car.hash_inner(state) {
|
||||
return false;
|
||||
}
|
||||
if !cdr.hash_inner(state) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Self::Number(value) => {
|
||||
state.write_u8(2);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::Boolean(value) => {
|
||||
state.write_u8(3);
|
||||
value.hash(state);
|
||||
}
|
||||
Self::Keyword(value) => {
|
||||
state.write_u8(4);
|
||||
value.hash(state);
|
||||
}
|
||||
Self::Identifier(value) => {
|
||||
state.write_u8(5);
|
||||
value.hash(state);
|
||||
}
|
||||
Self::String(value) => {
|
||||
state.write_u8(6);
|
||||
value.hash(state);
|
||||
}
|
||||
Self::Quasi(value) => {
|
||||
state.write_u8(7);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::Quote(value) => {
|
||||
state.write_u8(8);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::Unquote(value) => {
|
||||
state.write_u8(9);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::UnquoteSplice(value) => {
|
||||
state.write_u8(10);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::Vector(_)
|
||||
| Self::HashTable(_)
|
||||
| Self::NativeFunction(_)
|
||||
| Self::Function(_)
|
||||
| Self::NativeValue(_)
|
||||
| Self::Closure(_) => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> Option<u64> {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
if !self.hash_inner(&mut hasher) {
|
||||
return None;
|
||||
}
|
||||
Some(hasher.finish())
|
||||
}
|
||||
|
||||
pub fn is_nil(&self) -> bool {
|
||||
matches!(self, Self::Nil)
|
||||
}
|
||||
@@ -103,6 +175,7 @@ impl Value {
|
||||
Value::list_or_nil([Self::Identifier("unquote-splice".into()), value.type_id()])
|
||||
}
|
||||
Self::Vector(_) => Self::Identifier("vector".into()),
|
||||
Self::HashTable(_) => Self::Identifier("hash".into()),
|
||||
Self::Keyword(_) => Self::Identifier("keyword".into()),
|
||||
Self::String(_) => Self::Identifier("string".into()),
|
||||
Self::NativeValue(_) => Self::Identifier("native".into()),
|
||||
@@ -117,6 +190,7 @@ impl Value {
|
||||
Self::Nil => false,
|
||||
Self::Cons(_) => true,
|
||||
Self::Vector(vector) => !vector.is_empty(),
|
||||
Self::HashTable(table) => !table.borrow().is_empty(),
|
||||
Self::Number(value) => value.is_trueish(),
|
||||
Self::String(value) => !value.is_empty(),
|
||||
Self::Boolean(BooleanValue(value)) => *value,
|
||||
@@ -212,11 +286,6 @@ impl fmt::Display for Value {
|
||||
fmt::Display::fmt(cons, f)?;
|
||||
f.write_char(')')
|
||||
}
|
||||
Self::Vector(vector) => {
|
||||
f.write_str("#[")?;
|
||||
fmt::Display::fmt(vector, f)?;
|
||||
f.write_char(']')
|
||||
}
|
||||
Self::Keyword(keyword) => write!(f, "{keyword}"),
|
||||
Self::Identifier(identifier) => write!(f, "{identifier}"),
|
||||
Self::String(value) => fmt::Display::fmt(value, f),
|
||||
@@ -242,6 +311,16 @@ impl fmt::Display for Value {
|
||||
Self::Function(value) => fmt::Display::fmt(value, f),
|
||||
Self::NativeValue(value) => fmt::Display::fmt(value, f),
|
||||
Self::NativeFunction(value) => fmt::Display::fmt(value, f),
|
||||
Self::Vector(vector) => {
|
||||
f.write_str("#[")?;
|
||||
fmt::Display::fmt(vector, f)?;
|
||||
f.write_char(']')
|
||||
}
|
||||
Self::HashTable(table) => {
|
||||
f.write_str("#hash{")?;
|
||||
fmt::Display::fmt(table, f)?;
|
||||
f.write_char('}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
num::TryFromIntError,
|
||||
ops::{Add, Div, DivAssign, Mul, Neg, Rem, RemAssign, Sub, SubAssign},
|
||||
};
|
||||
@@ -20,6 +21,21 @@ pub enum NumberValue {
|
||||
}
|
||||
|
||||
impl NumberValue {
|
||||
pub(super) fn hash_inner<H: Hasher>(&self, state: &mut H) -> bool {
|
||||
match self {
|
||||
Self::Int(value) => {
|
||||
state.write_u8(1);
|
||||
value.hash(state)
|
||||
}
|
||||
Self::Float(value) if !value.is_nan() => {
|
||||
state.write_u8(2);
|
||||
state.write(&value.to_le_bytes());
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub const fn nan() -> Self {
|
||||
Self::Float(f64::NAN)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@ impl Deref for StringValue {
|
||||
|
||||
impl fmt::Display for StringValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self.0.as_ref(), f)
|
||||
fmt::Debug::fmt(self.0.as_ref(), f)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user