diff --git a/userspace/lib/lysp/examples/hash.lysp b/userspace/lib/lysp/examples/hash.lysp new file mode 100644 index 00000000..799bbc5d --- /dev/null +++ b/userspace/lib/lysp/examples/hash.lysp @@ -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)))) diff --git a/userspace/lib/lysp/src/compile/syntax/mod.rs b/userspace/lib/lysp/src/compile/syntax/mod.rs index d569a7c8..cd7f806a 100644 --- a/userspace/lib/lysp/src/compile/syntax/mod.rs +++ b/userspace/lib/lysp/src/compile/syntax/mod.rs @@ -66,6 +66,7 @@ impl Expression { fn parse_inner(value: &Value) -> Rc { 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}"), diff --git a/userspace/lib/lysp/src/error.rs b/userspace/lib/lysp/src/error.rs index d8a93ffe..b3b9d0ea 100644 --- a/userspace/lib/lysp/src/error.rs +++ b/userspace/lib/lysp/src/error.rs @@ -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 { diff --git a/userspace/lib/lysp/src/vm/macros.rs b/userspace/lib/lysp/src/vm/macros.rs index 2457747e..928adaf8 100644 --- a/userspace/lib/lysp/src/vm/macros.rs +++ b/userspace/lib/lysp/src/vm/macros.rs @@ -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()), diff --git a/userspace/lib/lysp/src/vm/prelude/collections.rs b/userspace/lib/lysp/src/vm/prelude/collections.rs index 85d56093..ff2358b6 100644 --- a/userspace/lib/lysp/src/vm/prelude/collections.rs +++ b/userspace/lib/lysp/src/vm/prelude/collections.rs @@ -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) { 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = TryFromValue::try_from_value(table)?; + let value = table.borrow().get(key).unwrap_or(default).clone(); + Ok(value) + }, + ); } diff --git a/userspace/lib/lysp/src/vm/prelude/debug.rs b/userspace/lib/lysp/src/vm/prelude/debug.rs index 0449c310..2d2a2b25 100644 --- a/userspace/lib/lysp/src/vm/prelude/debug.rs +++ b/userspace/lib/lysp/src/vm/prelude/debug.rs @@ -73,6 +73,7 @@ pub fn load(env: &Rc) { 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) => { diff --git a/userspace/lib/lysp/src/vm/prelude/math.rs b/userspace/lib/lysp/src/vm/prelude/math.rs index 7da44d33..c7f54743 100644 --- a/userspace/lib/lysp/src/vm/prelude/math.rs +++ b/userspace/lib/lysp/src/vm/prelude/math.rs @@ -104,8 +104,11 @@ pub(super) fn load(env: &Rc) { fn value_add(a: &Value, b: &Value) -> Result { 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)), diff --git a/userspace/lib/lysp/src/vm/value/convert.rs b/userspace/lib/lysp/src/vm/value/convert.rs index 297fc7ef..8af02f0e 100644 --- a/userspace/lib/lysp/src/vm/value/convert.rs +++ b/userspace/lib/lysp/src/vm/value/convert.rs @@ -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 for Value { } } +impl TryFromValue<'_> for Rc { + fn try_from_value(value: &'_ Value) -> Result { + match value { + Value::HashTable(table) => Ok(table.clone()), + _ => Err(ValueConversionError { + expected: "hash".into(), + got: value.clone(), + }), + } + } +} + +impl From for Value { + fn from(value: HashTable) -> Self { + Value::HashTable(Rc::new(value)) + } +} + impl TryFromValue<'_> for IdentifierValue { fn try_from_value(value: &'_ Value) -> Result { match value { diff --git a/userspace/lib/lysp/src/vm/value/hashtable.rs b/userspace/lib/lysp/src/vm/value/hashtable.rs new file mode 100644 index 00000000..8d09e247 --- /dev/null +++ b/userspace/lib/lysp/src/vm/value/hashtable.rs @@ -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); + +#[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>, +} +pub struct HashTableIterMut<'a> { + buckets: slice::IterMut<'a, Vec<(Value, Value)>>, + inner: Option>, +} + +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 { + 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, + 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 { + 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 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 { + 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 { + 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()); + } + } +} diff --git a/userspace/lib/lysp/src/vm/value/mod.rs b/userspace/lib/lysp/src/vm/value/mod.rs index 961803e5..7fad83ef 100644 --- a/userspace/lib/lysp/src/vm/value/mod.rs +++ b/userspace/lib/lysp/src/vm/value/mod.rs @@ -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), - Vector(Rc), Number(NumberValue), Boolean(BooleanValue), Keyword(Keyword), @@ -50,6 +52,9 @@ pub enum Value { Quote(Rc), Unquote(Rc), UnquoteSplice(Rc), + // Collections + Vector(Rc), + HashTable(Rc), // Semantic Closure(ClosureValue), Function(Rc), @@ -59,6 +64,73 @@ pub enum Value { } impl Value { + fn hash_inner(&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 { + 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('}') + } } } } diff --git a/userspace/lib/lysp/src/vm/value/number.rs b/userspace/lib/lysp/src/vm/value/number.rs index 80a1d865..d234a434 100644 --- a/userspace/lib/lysp/src/vm/value/number.rs +++ b/userspace/lib/lysp/src/vm/value/number.rs @@ -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(&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) } diff --git a/userspace/lib/lysp/src/vm/value/string.rs b/userspace/lib/lysp/src/vm/value/string.rs index 5a57a820..20bc4d2c 100644 --- a/userspace/lib/lysp/src/vm/value/string.rs +++ b/userspace/lib/lysp/src/vm/value/string.rs @@ -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) } }