use NotNan as number representation

This commit is contained in:
Scott Richmond 2025-07-06 00:27:50 -04:00
parent e3683f82b5
commit 9c8159dde6
9 changed files with 81 additions and 86 deletions

4
pkg/rudus.d.ts vendored
View File

@ -14,8 +14,8 @@ export interface InitOutput {
readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_export_6: WebAssembly.Table; readonly __wbindgen_export_6: WebAssembly.Table;
readonly closure354_externref_shim: (a: number, b: number, c: any) => void; readonly closure357_externref_shim: (a: number, b: number, c: any) => void;
readonly closure377_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly closure380_externref_shim: (a: number, b: number, c: any, d: any) => void;
readonly __wbindgen_start: () => void; readonly __wbindgen_start: () => void;
} }

View File

@ -240,13 +240,13 @@ function _assertNum(n) {
function __wbg_adapter_20(arg0, arg1, arg2) { function __wbg_adapter_20(arg0, arg1, arg2) {
_assertNum(arg0); _assertNum(arg0);
_assertNum(arg1); _assertNum(arg1);
wasm.closure354_externref_shim(arg0, arg1, arg2); wasm.closure357_externref_shim(arg0, arg1, arg2);
} }
function __wbg_adapter_46(arg0, arg1, arg2, arg3) { function __wbg_adapter_46(arg0, arg1, arg2, arg3) {
_assertNum(arg0); _assertNum(arg0);
_assertNum(arg1); _assertNum(arg1);
wasm.closure377_externref_shim(arg0, arg1, arg2, arg3); wasm.closure380_externref_shim(arg0, arg1, arg2, arg3);
} }
async function __wbg_load(module, imports) { async function __wbg_load(module, imports) {
@ -403,8 +403,8 @@ function __wbg_get_imports() {
_assertBoolean(ret); _assertBoolean(ret);
return ret; return ret;
}; };
imports.wbg.__wbindgen_closure_wrapper8124 = function() { return logError(function (arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper8156 = function() { return logError(function (arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 355, __wbg_adapter_20); const ret = makeMutClosure(arg0, arg1, 358, __wbg_adapter_20);
return ret; return ret;
}, arguments) }; }, arguments) };
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {

BIN
pkg/rudus_bg.wasm (Stored with Git LFS)

Binary file not shown.

View File

@ -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_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_export_6: WebAssembly.Table; export const __wbindgen_export_6: WebAssembly.Table;
export const closure354_externref_shim: (a: number, b: number, c: any) => void; export const closure357_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 closure380_externref_shim: (a: number, b: number, c: any, d: any) => void;
export const __wbindgen_start: () => void; export const __wbindgen_start: () => void;

View File

@ -152,17 +152,11 @@ pub fn append(x: &Value, y: &Value) -> Value {
} }
pub fn dec(x: &Value) -> Value { pub fn dec(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64() - 1.0)
Value::Number(n) => Value::Number(n - 1.0),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn inc(x: &Value) -> Value { pub fn inc(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64() + 1.0)
Value::Number(n) => Value::Number(n + 1.0),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn div(x: &Value, y: &Value) -> Value { 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 { pub fn pow(x: &Value, y: &Value) -> Value {
match (x, y) { let x = x.as_f64();
(Value::Number(x), Value::Number(y)) => Value::Number(x.powf(*y)), let y = y.as_f64();
_ => unreachable!("internal ludus error: pow expects numbers"), Value::from_f64(x.powf(y))
}
} }
pub fn dissoc(dict: &Value, key: &Value) -> Value { 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 { pub fn at(ordered: &Value, i: &Value) -> Value {
match (ordered, i) { match (ordered, i) {
(Value::List(list), Value::Number(n)) => { (Value::List(list), Value::Number(n)) => {
let i = *n as usize; let i = f64::from(*n) as usize;
match list.get(i) { match list.get(i) {
Some(n) => n.clone(), Some(n) => n.clone(),
None => Value::Nil, None => Value::Nil,
} }
} }
(Value::Tuple(tuple), Value::Number(n)) => { (Value::Tuple(tuple), Value::Number(n)) => {
let i = *n as usize; let i = f64::from(*n) as usize;
match tuple.get(i) { match tuple.get(i) {
Some(n) => n.clone(), Some(n) => n.clone(),
None => Value::Nil, None => Value::Nil,
@ -308,11 +301,11 @@ pub fn rest(ordered: &Value) -> Value {
pub fn count(coll: &Value) -> Value { pub fn count(coll: &Value) -> Value {
match coll { match coll {
Value::Dict(d) => Value::Number(d.len() as f64), Value::Dict(d) => Value::from_usize(d.len()),
Value::List(l) => Value::Number(l.len() as f64), Value::List(l) => Value::from_usize(l.len()),
Value::Tuple(t) => Value::Number(t.len() as f64), Value::Tuple(t) => Value::from_usize(t.len()),
Value::String(s) => Value::Number(s.len() as f64), Value::String(s) => Value::from_usize(s.len()),
Value::Interned(s) => Value::Number(s.len() as f64), Value::Interned(s) => Value::from_usize(s.len()),
_ => unreachable!("internal Ludus error"), _ => unreachable!("internal Ludus error"),
} }
} }
@ -320,11 +313,11 @@ pub fn count(coll: &Value) -> Value {
pub fn range(start: &Value, end: &Value) -> Value { pub fn range(start: &Value, end: &Value) -> Value {
match (start, end) { match (start, end) {
(Value::Number(start), Value::Number(end)) => { (Value::Number(start), Value::Number(end)) => {
let start = *start as isize; let start = f64::from(*start) as isize;
let end = *end as isize; let end = f64::from(*end) as isize;
let mut range = Vector::new(); let mut range = Vector::new();
for n in start..end { 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)) Value::List(Box::new(range))
} }
@ -336,14 +329,14 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value {
match (ordered, start, end) { match (ordered, start, end) {
(Value::List(list), Value::Number(start), Value::Number(end)) => { (Value::List(list), Value::Number(start), Value::Number(end)) => {
let mut newlist = list.clone(); let mut newlist = list.clone();
let start = std::cmp::max(*start as usize, 0); let start = std::cmp::max(f64::from(*start) as usize, 0);
let end = std::cmp::min(*end as usize, list.len()); let end = std::cmp::min(f64::from(*end) as usize, list.len());
Value::List(Box::new(newlist.slice(start..end))) Value::List(Box::new(newlist.slice(start..end)))
} }
// TODO: figure out something better to do than return an empty string on a bad slice // 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)) => { (Value::String(string), Value::Number(start), Value::Number(end)) => {
let start = std::cmp::max(*start as usize, 0); let start = std::cmp::max(f64::from(*start) as usize, 0);
let end = std::cmp::min(*end as usize, string.len()); let end = std::cmp::min(f64::from(*end) as usize, string.len());
Value::String(Rc::new( Value::String(Rc::new(
string string
.clone() .clone()
@ -354,8 +347,8 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value {
)) ))
} }
(Value::Interned(string), Value::Number(start), Value::Number(end)) => { (Value::Interned(string), Value::Number(start), Value::Number(end)) => {
let start = std::cmp::max(*start as usize, 0); let start = std::cmp::max(f64::from(*start) as usize, 0);
let end = std::cmp::min(*end as usize, string.len()); let end = std::cmp::min(f64::from(*end) as usize, string.len());
Value::String(Rc::new(string.get(start..end).unwrap_or("").to_string())) Value::String(Rc::new(string.get(start..end).unwrap_or("").to_string()))
} }
_ => unreachable!("internal Ludus error"), _ => unreachable!("internal Ludus error"),
@ -382,14 +375,14 @@ pub fn list(x: &Value) -> Value {
pub fn number(x: &Value) -> Value { pub fn number(x: &Value) -> Value {
match x { match x {
Value::Interned(string) => match string.parse::<f64>() { Value::Interned(string) => match string.parse::<f64>() {
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![ Err(_) => Value::Tuple(Rc::new(vec![
Value::Keyword("err"), Value::Keyword("err"),
Value::String(Rc::new(format!("could not parse `{string}` as a number"))), Value::String(Rc::new(format!("could not parse `{string}` as a number"))),
])), ])),
}, },
Value::String(string) => match string.parse::<f64>() { Value::String(string) => match string.parse::<f64>() {
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![ Err(_) => Value::Tuple(Rc::new(vec![
Value::Keyword("err"), Value::Keyword("err"),
Value::String(Rc::new(format!("could not parse `{string}` as a number"))), 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 { pub fn atan_2(x: &Value, y: &Value) -> Value {
match (x, y) { let x = x.as_f64();
(Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)), let y = y.as_f64();
_ => unreachable!("internal Ludus error"), Value::from_f64(x.atan2(y))
}
} }
pub fn ceil(x: &Value) -> Value { pub fn ceil(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64().ceil())
Value::Number(x) => Value::Number(x.ceil()),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn cos(x: &Value) -> Value { pub fn cos(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64().cos())
Value::Number(x) => Value::Number(x.cos()),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn floor(x: &Value) -> Value { pub fn floor(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64().floor())
Value::Number(x) => Value::Number(x.floor()),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn base_random() -> Value { pub fn base_random() -> Value {
Value::Number(random()) Value::from_f64(random())
} }
pub fn round(x: &Value) -> Value { pub fn round(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64().round())
Value::Number(x) => Value::Number(x.round()),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn sin(x: &Value) -> Value { pub fn sin(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64().sin())
Value::Number(x) => Value::Number(x.sin()),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn sqrt(x: &Value) -> Value { pub fn sqrt(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64().sqrt())
Value::Number(x) => Value::Number(x.sqrt()),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn tan(x: &Value) -> Value { pub fn tan(x: &Value) -> Value {
match x { Value::from_f64(x.as_f64().tan())
Value::Number(x) => Value::Number(x.tan()),
_ => unreachable!("internal Ludus error"),
}
} }
pub fn gt(x: &Value, y: &Value) -> Value { pub fn gt(x: &Value, y: &Value) -> Value {
@ -675,7 +646,7 @@ pub fn make_base() -> Value {
"number", "number",
Value::BaseFn(Box::new(BaseFn::Unary("number", 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)))), ("pow", Value::BaseFn(Box::new(BaseFn::Binary("pow", pow)))),
( (
"print!", "print!",
@ -706,7 +677,7 @@ pub fn make_base() -> Value {
Value::BaseFn(Box::new(BaseFn::Binary("split", split))), Value::BaseFn(Box::new(BaseFn::Binary("split", split))),
), ),
("sqrt", Value::BaseFn(Box::new(BaseFn::Unary("sqrt", sqrt)))), ("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!", "store!",
Value::BaseFn(Box::new(BaseFn::Binary("store!", store))), Value::BaseFn(Box::new(BaseFn::Binary("store!", store))),

View File

@ -4,6 +4,7 @@ use crate::op::Op;
use crate::spans::Spanned; use crate::spans::Spanned;
use crate::value::*; use crate::value::*;
use chumsky::prelude::SimpleSpan; use chumsky::prelude::SimpleSpan;
use ordered_float::NotNan;
use regex::Regex; use regex::Regex;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
@ -419,7 +420,7 @@ impl Compiler {
self.emit_op(Op::Nil); self.emit_op(Op::Nil);
self.stack_depth += 1; 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) => { Boolean(b) => {
self.emit_op(if *b { Op::True } else { Op::False }); self.emit_op(if *b { Op::True } else { Op::False });
self.stack_depth += 1; self.stack_depth += 1;
@ -536,7 +537,7 @@ impl Compiler {
} }
} }
NumberPattern(n) => { NumberPattern(n) => {
self.match_constant(Value::Number(*n)); self.match_constant(Value::Number(NotNan::new(*n).unwrap()));
} }
KeywordPattern(s) => { KeywordPattern(s) => {
let existing_kw = self.chunk.keywords.iter().position(|kw| kw == s); let existing_kw = self.chunk.keywords.iter().position(|kw| kw == s);

View File

@ -54,7 +54,7 @@ impl MsgIn {
MsgIn::Input(str) => Value::string(str), MsgIn::Input(str) => Value::string(str),
MsgIn::Fetch(url, status_f64, string) => { MsgIn::Fetch(url, status_f64, string) => {
let url = Value::string(url); let url = Value::string(url);
let status = Value::Number(status_f64); let status = Value::from_f64(status_f64);
let text = Value::string(string); let text = Value::string(string);
let result_tuple = if status_f64 == 200.0 { let result_tuple = if status_f64 == 200.0 {
Value::tuple(vec![OK, text]) Value::tuple(vec![OK, text])

View File

@ -1,6 +1,7 @@
use crate::base::BaseFn; use crate::base::BaseFn;
use crate::chunk::Chunk; use crate::chunk::Chunk;
use imbl::{HashMap, Vector}; use imbl::{HashMap, Vector};
use ordered_float::NotNan;
use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
@ -171,7 +172,8 @@ pub enum Value {
Keyword(&'static str), Keyword(&'static str),
Interned(&'static str), Interned(&'static str),
String(Rc<String>), String(Rc<String>),
Number(f64), // Number(f64),
Number(NotNan<f64>),
Tuple(Rc<Vec<Value>>), Tuple(Rc<Vec<Value>>),
List(Box<Vector<Value>>), List(Box<Vector<Value>>),
Dict(Box<HashMap<Key, Value>>), Dict(Box<HashMap<Key, Value>>),
@ -270,7 +272,7 @@ impl Serialize for Value {
Nil => srlzr.serialize_none(), Nil => srlzr.serialize_none(),
True => srlzr.serialize_bool(true), True => srlzr.serialize_bool(true),
False => srlzr.serialize_bool(false), 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), Interned(s) => srlzr.serialize_str(s),
Keyword(k) => srlzr.serialize_str(k), Keyword(k) => srlzr.serialize_str(k),
String(s) => srlzr.serialize_str(s.as_str()), String(s) => srlzr.serialize_str(s.as_str()),
@ -540,6 +542,21 @@ impl Value {
Value::Tuple(Rc::new(vec)) 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 { // pub fn get_shared_box(&self, name: &'static str) -> Value {
// match self { // match self {
// Value::Dict(dict) => dict // Value::Dict(dict) => dict

View File

@ -7,6 +7,7 @@ use crate::value::{Key, LFn, Value};
use crate::world::Zoo; use crate::world::Zoo;
use imbl::{HashMap, Vector}; use imbl::{HashMap, Vector};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use ordered_float::NotNan;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt; use std::fmt;
@ -376,7 +377,10 @@ impl Creature {
let Value::Number(ms) = args[1] else { let Value::Number(ms) = args[1] else {
unreachable!() 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.r#yield = true;
self.push(Value::Keyword("ok")); self.push(Value::Keyword("ok"));
} }
@ -462,7 +466,7 @@ impl Creature {
let jump_len = self.read2(); let jump_len = self.read2();
let cond = self.pop(); let cond = self.pop();
match cond { 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(..) => (), Value::Number(..) => (),
_ => { _ => {
return self return self
@ -828,7 +832,9 @@ impl Creature {
ToInt => { ToInt => {
let val = self.pop(); let val = self.pop();
if let Value::Number(x) = val { 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 { } else {
return self.panic_with(format!("repeat requires a number, but got {val}")); return self.panic_with(format!("repeat requires a number, but got {val}"));
} }
@ -836,7 +842,7 @@ impl Creature {
Decrement => { Decrement => {
let val = self.pop(); let val = self.pop();
if let Value::Number(x) = val { if let Value::Number(x) = val {
self.push(Value::Number(x - 1.0)); self.push(Value::from_f64(f64::from(x) - 1.0));
} else { } else {
return self return self
.panic_with(format!("you may only decrement a number, but got {val}")); .panic_with(format!("you may only decrement a number, but got {val}"));
@ -959,10 +965,10 @@ impl Creature {
let ordered = self.pop(); let ordered = self.pop();
let value = match (ordered, idx.clone()) { let value = match (ordered, idx.clone()) {
(Value::List(l), Value::Number(i)) => { (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)) => { (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, (_, Value::Number(_)) => Value::Nil,
_ => { _ => {