From 9c8159dde6681869d2498189c928f8114392ccda Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 00:27:50 -0400 Subject: [PATCH] use NotNan as number representation --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 8 ++-- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/base.rs | 101 +++++++++++++++-------------------------- src/compiler.rs | 5 +- src/io.rs | 2 +- src/value.rs | 21 ++++++++- src/vm.rs | 18 +++++--- 9 files changed, 81 insertions(+), 86 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 37254ac..5913583 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure354_externref_shim: (a: number, b: number, c: any) => void; - readonly closure377_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure357_externref_shim: (a: number, b: number, c: any) => void; + readonly closure380_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 7df40f1..e5f1e2e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure354_externref_shim(arg0, arg1, arg2); + wasm.closure357_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure377_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure380_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -403,8 +403,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8124 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 355, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8156 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 358, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 59c0631..4811ed9 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2beab9e352b26346c02c2bfae5109b327f088f92a07681ee0a3a430d0ca9e9df -size 16764143 +oid sha256:49098e30b1c09fc518086777a0e61b6e3a2b9d9cc70b5a24e0be15241631e947 +size 16769925 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 2823719..77b24ed 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure354_externref_shim: (a: number, b: number, c: any) => void; -export const closure377_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure357_externref_shim: (a: number, b: number, c: any) => void; +export const closure380_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index b0127ce..999afa7 100644 --- a/src/base.rs +++ b/src/base.rs @@ -152,17 +152,11 @@ pub fn append(x: &Value, y: &Value) -> Value { } pub fn dec(x: &Value) -> Value { - match x { - Value::Number(n) => Value::Number(n - 1.0), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64() - 1.0) } pub fn inc(x: &Value) -> Value { - match x { - Value::Number(n) => Value::Number(n + 1.0), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64() + 1.0) } pub fn div(x: &Value, y: &Value) -> Value { @@ -180,10 +174,9 @@ pub fn mult(x: &Value, y: &Value) -> Value { } pub fn pow(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x.powf(*y)), - _ => unreachable!("internal ludus error: pow expects numbers"), - } + let x = x.as_f64(); + let y = y.as_f64(); + Value::from_f64(x.powf(y)) } pub fn dissoc(dict: &Value, key: &Value) -> Value { @@ -226,14 +219,14 @@ pub fn first(ordered: &Value) -> Value { pub fn at(ordered: &Value, i: &Value) -> Value { match (ordered, i) { (Value::List(list), Value::Number(n)) => { - let i = *n as usize; + let i = f64::from(*n) as usize; match list.get(i) { Some(n) => n.clone(), None => Value::Nil, } } (Value::Tuple(tuple), Value::Number(n)) => { - let i = *n as usize; + let i = f64::from(*n) as usize; match tuple.get(i) { Some(n) => n.clone(), None => Value::Nil, @@ -308,11 +301,11 @@ pub fn rest(ordered: &Value) -> Value { pub fn count(coll: &Value) -> Value { match coll { - Value::Dict(d) => Value::Number(d.len() as f64), - Value::List(l) => Value::Number(l.len() as f64), - Value::Tuple(t) => Value::Number(t.len() as f64), - Value::String(s) => Value::Number(s.len() as f64), - Value::Interned(s) => Value::Number(s.len() as f64), + Value::Dict(d) => Value::from_usize(d.len()), + Value::List(l) => Value::from_usize(l.len()), + Value::Tuple(t) => Value::from_usize(t.len()), + Value::String(s) => Value::from_usize(s.len()), + Value::Interned(s) => Value::from_usize(s.len()), _ => unreachable!("internal Ludus error"), } } @@ -320,11 +313,11 @@ pub fn count(coll: &Value) -> Value { pub fn range(start: &Value, end: &Value) -> Value { match (start, end) { (Value::Number(start), Value::Number(end)) => { - let start = *start as isize; - let end = *end as isize; + let start = f64::from(*start) as isize; + let end = f64::from(*end) as isize; let mut range = Vector::new(); for n in start..end { - range.push_back(Value::Number(n as f64)) + range.push_back(Value::from_usize(n as usize)) } Value::List(Box::new(range)) } @@ -336,14 +329,14 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value { match (ordered, start, end) { (Value::List(list), Value::Number(start), Value::Number(end)) => { let mut newlist = list.clone(); - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, list.len()); + let start = std::cmp::max(f64::from(*start) as usize, 0); + let end = std::cmp::min(f64::from(*end) as usize, list.len()); Value::List(Box::new(newlist.slice(start..end))) } // TODO: figure out something better to do than return an empty string on a bad slice (Value::String(string), Value::Number(start), Value::Number(end)) => { - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, string.len()); + let start = std::cmp::max(f64::from(*start) as usize, 0); + let end = std::cmp::min(f64::from(*end) as usize, string.len()); Value::String(Rc::new( string .clone() @@ -354,8 +347,8 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value { )) } (Value::Interned(string), Value::Number(start), Value::Number(end)) => { - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, string.len()); + let start = std::cmp::max(f64::from(*start) as usize, 0); + let end = std::cmp::min(f64::from(*end) as usize, string.len()); Value::String(Rc::new(string.get(start..end).unwrap_or("").to_string())) } _ => unreachable!("internal Ludus error"), @@ -382,14 +375,14 @@ pub fn list(x: &Value) -> Value { pub fn number(x: &Value) -> Value { match x { Value::Interned(string) => match string.parse::() { - Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])), + Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::from_f64(n)])), Err(_) => Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::String(Rc::new(format!("could not parse `{string}` as a number"))), ])), }, Value::String(string) => match string.parse::() { - Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])), + Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::from_f64(n)])), Err(_) => Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::String(Rc::new(format!("could not parse `{string}` as a number"))), @@ -486,63 +479,41 @@ pub fn trimr(string: &Value) -> Value { } pub fn atan_2(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)), - _ => unreachable!("internal Ludus error"), - } + let x = x.as_f64(); + let y = y.as_f64(); + Value::from_f64(x.atan2(y)) } pub fn ceil(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.ceil()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().ceil()) } pub fn cos(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.cos()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().cos()) } pub fn floor(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.floor()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().floor()) } pub fn base_random() -> Value { - Value::Number(random()) + Value::from_f64(random()) } pub fn round(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.round()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().round()) } pub fn sin(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.sin()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().sin()) } pub fn sqrt(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.sqrt()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().sqrt()) } pub fn tan(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.tan()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().tan()) } pub fn gt(x: &Value, y: &Value) -> Value { @@ -675,7 +646,7 @@ pub fn make_base() -> Value { "number", Value::BaseFn(Box::new(BaseFn::Unary("number", number))), ), - ("pi", Value::Number(std::f64::consts::PI)), + ("pi", Value::from_f64(std::f64::consts::PI)), ("pow", Value::BaseFn(Box::new(BaseFn::Binary("pow", pow)))), ( "print!", @@ -706,7 +677,7 @@ pub fn make_base() -> Value { Value::BaseFn(Box::new(BaseFn::Binary("split", split))), ), ("sqrt", Value::BaseFn(Box::new(BaseFn::Unary("sqrt", sqrt)))), - ("sqrt_2", Value::Number(std::f64::consts::SQRT_2)), + ("sqrt_2", Value::from_f64(std::f64::consts::SQRT_2)), ( "store!", Value::BaseFn(Box::new(BaseFn::Binary("store!", store))), diff --git a/src/compiler.rs b/src/compiler.rs index 6a0ced7..1256b94 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -4,6 +4,7 @@ use crate::op::Op; use crate::spans::Spanned; use crate::value::*; use chumsky::prelude::SimpleSpan; +use ordered_float::NotNan; use regex::Regex; use std::cell::RefCell; use std::collections::HashMap; @@ -419,7 +420,7 @@ impl Compiler { self.emit_op(Op::Nil); self.stack_depth += 1; } - Number(n) => self.emit_constant(Value::Number(*n)), + Number(n) => self.emit_constant(Value::Number(NotNan::new(*n).unwrap())), Boolean(b) => { self.emit_op(if *b { Op::True } else { Op::False }); self.stack_depth += 1; @@ -536,7 +537,7 @@ impl Compiler { } } NumberPattern(n) => { - self.match_constant(Value::Number(*n)); + self.match_constant(Value::Number(NotNan::new(*n).unwrap())); } KeywordPattern(s) => { let existing_kw = self.chunk.keywords.iter().position(|kw| kw == s); diff --git a/src/io.rs b/src/io.rs index 2a54492..305ceaf 100644 --- a/src/io.rs +++ b/src/io.rs @@ -54,7 +54,7 @@ impl MsgIn { MsgIn::Input(str) => Value::string(str), MsgIn::Fetch(url, status_f64, string) => { let url = Value::string(url); - let status = Value::Number(status_f64); + let status = Value::from_f64(status_f64); let text = Value::string(string); let result_tuple = if status_f64 == 200.0 { Value::tuple(vec![OK, text]) diff --git a/src/value.rs b/src/value.rs index 5e7824c..16955cd 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,6 +1,7 @@ use crate::base::BaseFn; use crate::chunk::Chunk; use imbl::{HashMap, Vector}; +use ordered_float::NotNan; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::cell::RefCell; use std::rc::Rc; @@ -171,7 +172,8 @@ pub enum Value { Keyword(&'static str), Interned(&'static str), String(Rc), - Number(f64), + // Number(f64), + Number(NotNan), Tuple(Rc>), List(Box>), Dict(Box>), @@ -270,7 +272,7 @@ impl Serialize for Value { Nil => srlzr.serialize_none(), True => srlzr.serialize_bool(true), False => srlzr.serialize_bool(false), - Number(n) => srlzr.serialize_f64(*n), + Number(n) => srlzr.serialize_f64(f64::from(*n)), Interned(s) => srlzr.serialize_str(s), Keyword(k) => srlzr.serialize_str(k), String(s) => srlzr.serialize_str(s.as_str()), @@ -540,6 +542,21 @@ impl Value { Value::Tuple(Rc::new(vec)) } + pub fn from_f64(f: f64) -> Value { + Value::Number(NotNan::new(f).unwrap()) + } + + pub fn from_usize(n: usize) -> Value { + Value::Number(NotNan::new(n as f64).unwrap()) + } + + pub fn as_f64(&self) -> f64 { + match self { + Value::Number(n) => f64::from(*n), + _ => unreachable!("expected value to be a number"), + } + } + // pub fn get_shared_box(&self, name: &'static str) -> Value { // match self { // Value::Dict(dict) => dict diff --git a/src/vm.rs b/src/vm.rs index 302289d..0f898aa 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -7,6 +7,7 @@ use crate::value::{Key, LFn, Value}; use crate::world::Zoo; use imbl::{HashMap, Vector}; use num_traits::FromPrimitive; +use ordered_float::NotNan; use std::cell::RefCell; use std::collections::VecDeque; use std::fmt; @@ -376,7 +377,10 @@ impl Creature { let Value::Number(ms) = args[1] else { unreachable!() }; - self.zoo.as_ref().borrow_mut().sleep(self.pid, ms); + self.zoo + .as_ref() + .borrow_mut() + .sleep(self.pid, f64::from(ms)); self.r#yield = true; self.push(Value::Keyword("ok")); } @@ -462,7 +466,7 @@ impl Creature { let jump_len = self.read2(); let cond = self.pop(); match cond { - Value::Number(x) if x <= 0.0 => self.ip += jump_len, + Value::Number(x) if f64::from(x) <= 0.0 => self.ip += jump_len, Value::Number(..) => (), _ => { return self @@ -828,7 +832,9 @@ impl Creature { ToInt => { let val = self.pop(); if let Value::Number(x) = val { - self.push(Value::Number(x as usize as f64)); + self.push(Value::Number( + NotNan::new(f64::from(x) as usize as f64).unwrap(), + )); } else { return self.panic_with(format!("repeat requires a number, but got {val}")); } @@ -836,7 +842,7 @@ impl Creature { Decrement => { let val = self.pop(); if let Value::Number(x) = val { - self.push(Value::Number(x - 1.0)); + self.push(Value::from_f64(f64::from(x) - 1.0)); } else { return self .panic_with(format!("you may only decrement a number, but got {val}")); @@ -959,10 +965,10 @@ impl Creature { let ordered = self.pop(); let value = match (ordered, idx.clone()) { (Value::List(l), Value::Number(i)) => { - l.get(i as usize).unwrap_or(&Value::Nil).clone() + l.get(f64::from(i) as usize).unwrap_or(&Value::Nil).clone() } (Value::Tuple(t), Value::Number(i)) => { - t.get(i as usize).unwrap_or(&Value::Nil).clone() + t.get(f64::from(i) as usize).unwrap_or(&Value::Nil).clone() } (_, Value::Number(_)) => Value::Nil, _ => {