diff --git a/Cargo.lock b/Cargo.lock index 5f1998df..ec73ce59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -781,10 +781,12 @@ dependencies = [ name = "oxide_kernel" version = "0.1.0" dependencies = [ + "bumpalo", "dashmap", "hashbrown 0.14.5", "oxide_compiler", "oxide_parser", + "oxide_vm", "rustc-hash", ] @@ -808,7 +810,6 @@ dependencies = [ "fast-float", "hashbrown 0.14.5", "oxide_compiler", - "oxide_kernel", "oxide_parser", "proptest", "rustc-hash", diff --git a/crates/oxide_kernel/Cargo.toml b/crates/oxide_kernel/Cargo.toml index 4879d9cf..7f8f30df 100644 --- a/crates/oxide_kernel/Cargo.toml +++ b/crates/oxide_kernel/Cargo.toml @@ -9,6 +9,8 @@ description = "OxideJS - Shared kernel (CodeForge, ShapeForge, IcForge, StringFo [dependencies] oxide_compiler = { path = "../oxide_compiler" } oxide_parser = { path = "../oxide_parser" } +oxide_vm = { path = "../oxide_vm" } +bumpalo.workspace = true dashmap.workspace = true hashbrown.workspace = true rustc-hash.workspace = true diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs new file mode 100644 index 00000000..4943b820 --- /dev/null +++ b/crates/oxide_kernel/src/builtin.rs @@ -0,0 +1,240 @@ +use oxide_vm::mem::P; +use oxide_vm::object::JsObject; +use oxide_vm::value::JsValue; + +use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; +use crate::string_forge::Interner; + +pub struct BuiltinWorld { + pub object_proto: P, + pub array_proto: P, + pub function_proto: P, + pub string_proto: P, + pub number_proto: P, + pub boolean_proto: P, + pub error_proto: P, + pub symbol_proto: P, + pub object_constructor: P, + pub array_constructor: P, + pub function_constructor: P, + pub string_constructor: P, + pub number_constructor: P, + pub boolean_constructor: P, + pub error_constructor: P, + pub symbol_constructor: P, +} + +fn intern_label(interner: &Interner, label: &str) -> u32 { + interner.intern(label).0 +} + +fn make_pair( + string_forge: &Interner, + shape_forge: &ShapeForge, + name: &str, + si_prototype: u32, + si_constructor: u32, + si_name: u32, +) -> (P, P) { + intern_label(string_forge, name); + + let mut proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); + let mut ctor = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); + + let proto_shape = shape_forge.make_shape(EMPTY_SHAPE_ID, si_constructor); + proto.set_shape_id(proto_shape); + proto.set_prop_count(1); + + let ctor_shape1 = shape_forge.make_shape(EMPTY_SHAPE_ID, si_prototype); + let ctor_shape2 = shape_forge.make_shape(ctor_shape1, si_name); + ctor.set_shape_id(ctor_shape2); + ctor.set_prop_count(2); + ctor.set_function(true); + + proto.set_inline_prop(0, JsValue::undefined()); + ctor.set_inline_prop(0, JsValue::undefined()); + ctor.set_inline_prop(1, JsValue::undefined()); + + (P::new(proto), P::new(ctor)) +} + +impl BuiltinWorld { + pub fn new(string_forge: &Interner, shape_forge: &ShapeForge) -> Self { + let si_prototype = intern_label(string_forge, "prototype"); + let si_constructor = intern_label(string_forge, "constructor"); + let si_name = intern_label(string_forge, "name"); + intern_label(string_forge, "length"); + intern_label(string_forge, "toString"); + intern_label(string_forge, "valueOf"); + + let (object_proto, object_constructor) = make_pair( + string_forge, + shape_forge, + "Object", + si_prototype, + si_constructor, + si_name, + ); + let (array_proto, array_constructor) = make_pair( + string_forge, + shape_forge, + "Array", + si_prototype, + si_constructor, + si_name, + ); + let (function_proto, function_constructor) = make_pair( + string_forge, + shape_forge, + "Function", + si_prototype, + si_constructor, + si_name, + ); + let (string_proto, string_constructor) = make_pair( + string_forge, + shape_forge, + "String", + si_prototype, + si_constructor, + si_name, + ); + let (number_proto, number_constructor) = make_pair( + string_forge, + shape_forge, + "Number", + si_prototype, + si_constructor, + si_name, + ); + let (boolean_proto, boolean_constructor) = make_pair( + string_forge, + shape_forge, + "Boolean", + si_prototype, + si_constructor, + si_name, + ); + let (error_proto, error_constructor) = make_pair( + string_forge, + shape_forge, + "Error", + si_prototype, + si_constructor, + si_name, + ); + let (symbol_proto, symbol_constructor) = make_pair( + string_forge, + shape_forge, + "Symbol", + si_prototype, + si_constructor, + si_name, + ); + + Self { + object_proto, + array_proto, + function_proto, + string_proto, + number_proto, + boolean_proto, + error_proto, + symbol_proto, + object_constructor, + array_constructor, + function_constructor, + string_constructor, + number_constructor, + boolean_constructor, + error_constructor, + symbol_constructor, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; + use crate::string_forge::Interner; + + fn make_world() -> BuiltinWorld { + let sf = Interner::new(); + let sh = ShapeForge::new(); + BuiltinWorld::new(&sf, &sh) + } + + #[test] + fn test_all_protos_valid() { + let w = make_world(); + let protos = [ + &w.object_proto, + &w.array_proto, + &w.function_proto, + &w.string_proto, + &w.number_proto, + &w.boolean_proto, + &w.error_proto, + &w.symbol_proto, + ]; + for p in protos { + assert!( + p.shape_id() > EMPTY_SHAPE_ID, + "proto should have a non-empty shape" + ); + } + } + + #[test] + fn test_all_constructors_valid() { + let w = make_world(); + assert!(w.object_constructor.is_function()); + assert!(w.array_constructor.is_function()); + assert!(w.function_constructor.is_function()); + assert!(w.string_constructor.is_function()); + assert!(w.number_constructor.is_function()); + assert!(w.boolean_constructor.is_function()); + assert!(w.error_constructor.is_function()); + assert!(w.symbol_constructor.is_function()); + } + + #[test] + fn test_prototypes_are_not_functions() { + let w = make_world(); + assert!(!w.object_proto.is_function()); + assert!(!w.array_proto.is_function()); + assert!(!w.function_proto.is_function()); + assert!(!w.string_proto.is_function()); + assert!(!w.number_proto.is_function()); + assert!(!w.boolean_proto.is_function()); + assert!(!w.error_proto.is_function()); + assert!(!w.symbol_proto.is_function()); + } + + #[test] + fn test_protos_have_null_proto() { + let w = make_world(); + assert!(w.object_proto.proto().is_null()); + assert!(w.array_proto.proto().is_null()); + assert!(w.function_proto.proto().is_null()); + assert!(w.string_proto.proto().is_null()); + assert!(w.number_proto.proto().is_null()); + assert!(w.boolean_proto.proto().is_null()); + assert!(w.error_proto.proto().is_null()); + assert!(w.symbol_proto.proto().is_null()); + } + + #[test] + fn test_shapes_populated() { + let w = make_world(); + assert!( + w.object_constructor.shape_id() > EMPTY_SHAPE_ID, + "constructor should have prototype + name shape" + ); + assert!( + w.object_proto.shape_id() > EMPTY_SHAPE_ID, + "prototype should have constructor shape" + ); + } +} diff --git a/crates/oxide_kernel/src/lib.rs b/crates/oxide_kernel/src/lib.rs index e2865117..96c7e689 100644 --- a/crates/oxide_kernel/src/lib.rs +++ b/crates/oxide_kernel/src/lib.rs @@ -1,5 +1,6 @@ #![doc = "OxideJS - Shared kernel (CodeForge, ShapeForge, IcForge, StringForge, BuiltinWorld)"] +pub mod builtin; pub mod code_forge; pub mod prop_forge; pub mod shape_forge; diff --git a/crates/oxide_vm/Cargo.toml b/crates/oxide_vm/Cargo.toml index bb5da96c..f9c65002 100644 --- a/crates/oxide_vm/Cargo.toml +++ b/crates/oxide_vm/Cargo.toml @@ -8,7 +8,6 @@ description = "OxideJS - Register-based VM with epoch arena memory" [dependencies] oxide_compiler = { path = "../oxide_compiler" } -oxide_kernel = { path = "../oxide_kernel" } bumpalo.workspace = true hashbrown.workspace = true rustc-hash.workspace = true diff --git a/crates/oxide_vm/src/mem.rs b/crates/oxide_vm/src/mem.rs index 7b6333a6..604fbdc4 100644 --- a/crates/oxide_vm/src/mem.rs +++ b/crates/oxide_vm/src/mem.rs @@ -14,6 +14,13 @@ impl P { pub fn new(value: T) -> Self { Self(Arc::new(value)) } + + pub fn from_arena(value: &T) -> Self + where + T: Clone, + { + Self(Arc::new(value.clone())) + } } impl Clone for P {