diff --git a/Cargo.lock b/Cargo.lock index 12999547..cb1dff01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,6 +293,12 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + [[package]] name = "fastrand" version = "2.4.1" @@ -795,10 +801,12 @@ name = "oxide_vm" version = "0.1.0" dependencies = [ "bumpalo", + "fast-float", "hashbrown 0.14.5", "oxide_compiler", "oxide_kernel", "proptest", + "ryu", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 78eeeb2c..1ed332ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,8 @@ bumpalo = "3" hashbrown = "0.14" proptest = "1" rustc-hash = "2" +fast-float = "0.2" +ryu = "1" [workspace.lints.clippy] all = { level = "deny", priority = -1 } diff --git a/crates/oxide_vm/Cargo.toml b/crates/oxide_vm/Cargo.toml index c329ef28..ae4e465e 100644 --- a/crates/oxide_vm/Cargo.toml +++ b/crates/oxide_vm/Cargo.toml @@ -11,6 +11,8 @@ oxide_compiler = { path = "../oxide_compiler" } oxide_kernel = { path = "../oxide_kernel" } bumpalo.workspace = true hashbrown.workspace = true +fast-float.workspace = true +ryu.workspace = true [dev-dependencies] proptest.workspace = true diff --git a/crates/oxide_vm/src/coercion.rs b/crates/oxide_vm/src/coercion.rs new file mode 100644 index 00000000..1e98c6b2 --- /dev/null +++ b/crates/oxide_vm/src/coercion.rs @@ -0,0 +1,176 @@ +use crate::value::JsValue; + +pub fn to_primitive(val: JsValue) -> JsValue { + if val.is_object() { + panic_to_object(); + } + val +} + +pub fn to_number(val: JsValue) -> f64 { + if val.is_int() { + return val.as_int() as f64; + } + if val.is_double() { + return val.as_double(); + } + if val.is_bool() { + return if val.as_bool() { 1.0 } else { 0.0 }; + } + if val.is_null() { + return 0.0; + } + if val.is_undefined() { + return f64::NAN; + } + if val.is_object() { + panic_to_object(); + } + f64::NAN +} + +pub fn to_string(val: JsValue) -> String { + if val.is_int() { + return val.as_int().to_string(); + } + if val.is_double() { + let d = val.as_double(); + if d.is_nan() { + return "NaN".to_string(); + } + if d.is_infinite() { + return if d.is_sign_positive() { + "Infinity".to_string() + } else { + "-Infinity".to_string() + }; + } + let mut buf = ryu::Buffer::new(); + return buf.format(d).to_string(); + } + if val.is_bool() { + return val.as_bool().to_string(); + } + if val.is_null() { + return "null".to_string(); + } + if val.is_undefined() { + return "undefined".to_string(); + } + if val.is_object() { + panic_to_object(); + } + String::new() +} + +pub fn to_boolean(val: JsValue) -> bool { + if val.is_undefined() || val.is_null() { + return false; + } + if val.is_bool() { + return val.as_bool(); + } + if val.is_int() { + return val.as_int() != 0; + } + if val.is_double() { + let d = val.as_double(); + return !(d == 0.0 || d == -0.0 || d.is_nan()); + } + if val.is_object() { + return true; + } + false +} + +pub fn abstract_eq(lhs: JsValue, rhs: JsValue) -> bool { + if lhs.is_null() && rhs.is_undefined() { + return true; + } + if lhs.is_undefined() && rhs.is_null() { + return true; + } + if lhs.is_int() && rhs.is_int() { + return lhs.as_int() == rhs.as_int(); + } + if lhs.is_double() && rhs.is_double() { + return strict_double_eq(lhs.as_double(), rhs.as_double()); + } + if lhs.is_bool() && rhs.is_bool() { + return lhs.as_bool() == rhs.as_bool(); + } + if lhs.is_null() && rhs.is_null() { + return true; + } + if lhs.is_undefined() && rhs.is_undefined() { + return true; + } + if (lhs.is_int() || lhs.is_double()) && (rhs.is_int() || rhs.is_double()) { + return strict_double_eq(to_number(lhs), to_number(rhs)); + } + if (lhs.is_int() || lhs.is_double()) && rhs.is_bool() { + return to_number(lhs) == to_number(rhs); + } + if lhs.is_bool() && (rhs.is_int() || rhs.is_double()) { + return to_number(lhs) == to_number(rhs); + } + if lhs.is_bool() || rhs.is_bool() { + return to_number(lhs) == to_number(rhs); + } + if lhs.is_null() || rhs.is_null() { + return false; + } + if lhs.is_object() || rhs.is_object() { + panic_to_object(); + } + false +} + +pub fn strict_eq(lhs: JsValue, rhs: JsValue) -> bool { + if lhs.is_int() && rhs.is_int() { + return lhs.as_int() == rhs.as_int(); + } + if lhs.is_double() && rhs.is_double() { + return strict_double_eq(lhs.as_double(), rhs.as_double()); + } + if lhs.is_bool() && rhs.is_bool() { + return lhs.as_bool() == rhs.as_bool(); + } + if lhs.is_null() && rhs.is_null() { + return true; + } + if lhs.is_undefined() && rhs.is_undefined() { + return true; + } + if lhs.is_object() && rhs.is_object() { + return lhs.as_ptr() == rhs.as_ptr(); + } + false +} + +fn strict_double_eq(a: f64, b: f64) -> bool { + if a.is_nan() || b.is_nan() { + return false; + } + a == b +} + +pub fn relational_compare(lhs: JsValue, rhs: JsValue) -> Option { + let l = to_number(lhs); + let r = to_number(rhs); + if l.is_nan() || r.is_nan() { + return None; + } + l.partial_cmp(&r).map(|o| o.is_lt()) +} + +pub fn string_concat(lhs: &str, rhs: &str) -> String { + let mut s = String::with_capacity(lhs.len() + rhs.len()); + s.push_str(lhs); + s.push_str(rhs); + s +} + +fn panic_to_object() { + panic!("ToObject not yet implemented (Phase 6)") +} diff --git a/crates/oxide_vm/src/lib.rs b/crates/oxide_vm/src/lib.rs index 5cc2637a..b7b76f6e 100644 --- a/crates/oxide_vm/src/lib.rs +++ b/crates/oxide_vm/src/lib.rs @@ -1,5 +1,6 @@ #![doc = "OxideJS - Register-based VM with epoch arena memory"] +pub mod coercion; pub mod mem; pub mod value; pub use value::JsValue;