diff --git a/crates/oxide_compiler/src/class/core.rs b/crates/oxide_compiler/src/class/core.rs index 1a80d6a..6fcb20c 100644 --- a/crates/oxide_compiler/src/class/core.rs +++ b/crates/oxide_compiler/src/class/core.rs @@ -1,87 +1,11 @@ use crate::compiler::{CompileCtx, Compiler}; -use oxide_parser::{ClassElement, MethodDefinitionKind}; impl Compiler { pub(crate) fn count_class(&self, class: &oxide_parser::Class, ctx: &mut CompileCtx) { - ctx.alloc_reg(); // ctor_reg - ctx.alloc_reg(); // proto_reg - if let Some(super_class) = &class.super_class { - self.count_expression(super_class, ctx); - } - ctx.count_instr(); // LOAD_CONST ctor - ctx.count_instr(); // NEW_OBJECT proto - - if class.super_class.is_some() { - ctx.alloc_reg(); // parent prototype key - ctx.count_instr(); // LOAD_CONST - ctx.alloc_reg(); // parent prototype value - ctx.count_instr(); // GET_PROP - ctx.alloc_reg(); // __proto__ key - ctx.count_instr(); // LOAD_CONST - ctx.count_instr(); // SET_PROP proto.__proto__ - ctx.count_instr(); // SET_PROP ctor.__proto__ - } - - ctx.count_load_const(); // constructor key - ctx.count_instr(); // SET_PROP proto.constructor = ctor - - ctx.count_load_const(); // prototype key - ctx.count_instr(); // SET_PROP ctor.prototype = proto - - for element in &class.body.body { - match element { - ClassElement::MethodDefinition(method) => { - let method = method.as_ref(); - if matches!(method.kind, MethodDefinitionKind::Constructor) { - continue; - } - if matches!(method.key, oxide_parser::PropertyKey::PrivateIdentifier(_)) { - // Mirror emit_private_method_init for static AND non-static private - // methods: private-id LOAD_CONST + CREATE_CLOSURE + SET_HOME_OBJECT + - // INIT_PRIVATE. - ctx.count_load_const(); // private id reg - ctx.count_load_const(); // method closure (CREATE_CLOSURE) - ctx.count_instr(); // SET_HOME_OBJECT - ctx.count_instr(); // INIT_PRIVATE - continue; - } - // Computed method key emits the full key expression; a plain key is one - // LOAD_CONST. Mirror emit_class_key_reg via the shared count_class_key. - self.count_class_key(&method.key, method.computed, ctx); - ctx.count_load_const(); // method closure (CREATE_CLOSURE) - ctx.count_instr(); // SET_HOME_OBJECT - match method.kind { - MethodDefinitionKind::Method => { - ctx.count_instr(); // SET_PROP - } - MethodDefinitionKind::Get | MethodDefinitionKind::Set => { - ctx.count_load_const(); // undefined placeholder - ctx.count_define_accessor(); // DEFINE_ACCESSOR + ext - } - MethodDefinitionKind::Constructor => {} - } - } - ClassElement::PropertyDefinition(prop) => { - let prop = prop.as_ref(); - if prop.r#static { - // Computed static field key emits the full key expression; mirror - // emit_public_field_init via the shared count_class_key. - self.count_class_key(&prop.key, prop.computed, ctx); - if let Some(value) = &prop.value { - self.count_expression(value, ctx); - } else { - ctx.count_load_const(); - } - ctx.count_instr(); // SET_PROP - } - } - ClassElement::StaticBlock(block) => { - for stmt in &block.body { - self.count_statement(stmt, ctx); - } - } - ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {} - } - } + self.count_class_header(class, ctx); + self.count_class_prototype(class.super_class.is_some(), ctx); + self.count_class_methods(&class.body.body, ctx); + self.count_class_static_fields(&class.body.body, ctx); + self.count_class_static_blocks(&class.body.body, ctx); } } diff --git a/crates/oxide_compiler/src/class/emit.rs b/crates/oxide_compiler/src/class/emit.rs index bb33290..c198872 100644 --- a/crates/oxide_compiler/src/class/emit.rs +++ b/crates/oxide_compiler/src/class/emit.rs @@ -1,5 +1,4 @@ use crate::compiler::{CompileCtx, Compiler, FunctionBodyContext}; -use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::{self, OpCode}; use oxide_parser::{Class, ClassElement, MethodDefinitionKind, PropertyKey}; @@ -50,16 +49,9 @@ impl Compiler { } } + let (ctor_reg, proto_reg, super_reg) = self.emit_class_header(class, ctx)?; let ctor_name = class.id.as_ref().map(|id| id.name.to_string()); - let ctor_reg = ctx.alloc_reg(); - let proto_reg = ctx.alloc_reg(); let self_binding = ctor_name.as_deref().map(|name| vec![(name, ctor_reg)]).unwrap_or_default(); - let super_reg = if let Some(super_expr) = &class.super_class { - Some(self.emit_expression(super_expr, ctx)?) - } else { - None - }; - let saved_derived = ctx.in_derived_constructor; let saved_private_names = ctx.scopes.private_name_map.clone(); ctx.in_derived_constructor = is_derived; @@ -190,137 +182,12 @@ impl Compiler { ctor_module.function_name = ctor_name.clone(); ctx.sub_modules.push(ctor_module); - ctx.emit_create_closure(ctor_reg, ctx.sub_modules.len() as u32); - ctx.emit(opcode::encode(OpCode::NEW_OBJECT, proto_reg, 0, 0)); - - if let Some(super_reg) = super_reg { - let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string())); - let parent_proto_key_reg = ctx.alloc_reg(); - ctx.emit_load_const(parent_proto_key_reg, proto_key_idx); - let parent_proto_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::GET_PROP, super_reg, parent_proto_reg, parent_proto_key_reg)); - - let proto_link_idx = ctx.add_constant(Constant::String("__proto__".to_string())); - let proto_link_key_reg = ctx.alloc_reg(); - ctx.emit_load_const(proto_link_key_reg, proto_link_idx); - ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, parent_proto_reg, proto_link_key_reg)); - ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, super_reg, proto_link_key_reg)); - } - - let ctor_key_idx = ctx.add_constant(Constant::String("constructor".to_string())); - let ctor_key_reg = ctx.alloc_reg(); - ctx.emit_load_const(ctor_key_reg, ctor_key_idx); - ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, ctor_reg, ctor_key_reg)); - - let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string())); - let proto_key_reg = ctx.alloc_reg(); - ctx.emit_load_const(proto_key_reg, proto_key_idx); - ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, proto_reg, proto_key_reg)); - - for element in &class.body.body { - match element { - ClassElement::MethodDefinition(method) => { - let method = method.as_ref(); - if method.kind == MethodDefinitionKind::Constructor { - continue; - } - if matches!(method.key, PropertyKey::PrivateIdentifier(_)) { - let home_reg = if method.r#static { ctor_reg } else { proto_reg }; - self.emit_private_method_init(home_reg, method, home_reg, ctx)?; - continue; - } - let home_reg = if method.r#static { ctor_reg } else { proto_reg }; - let key_reg = self.emit_class_key_reg(&method.key, method.computed, ctx)?; - let method_name = if method.computed { - "".to_string() - } else { - self.class_property_name(&method.key)? - }; - let accessor_reg = - self.emit_class_method_function(method, &method_name, home_reg, ctx, &self_binding)?; - match method.kind { - MethodDefinitionKind::Method => { - if method.computed { - ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, home_reg, key_reg, accessor_reg)); - } else { - ctx.emit(opcode::encode(OpCode::SET_PROP, home_reg, accessor_reg, key_reg)); - } - } - MethodDefinitionKind::Get | MethodDefinitionKind::Set => { - if method.computed { - return Err("computed class accessors not yet supported".into()); - } - let undef_reg = self.emit_undefined(ctx); - let (get_reg, set_reg) = if method.kind == MethodDefinitionKind::Get { - (accessor_reg, undef_reg) - } else { - (undef_reg, accessor_reg) - }; - let key_name = self.class_property_name(&method.key)?; - let key_idx = ctx.add_constant(Constant::String(key_name)); - ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, home_reg, get_reg, set_reg)); - ctx.emit(key_idx as u32); - } - MethodDefinitionKind::Constructor => continue, - } - } - ClassElement::PropertyDefinition(prop) => { - let prop = prop.as_ref(); - if prop.r#static { - let saved_static_this = ctx.static_block_this_reg; - ctx.static_block_this_reg = Some(ctor_reg); - if let PropertyKey::PrivateIdentifier(private) = &prop.key { - self.emit_private_field_init(ctor_reg, private.name.as_str(), prop.value.as_ref(), ctx)?; - } else { - self.emit_public_field_init(ctor_reg, &prop.key, prop.computed, prop.value.as_ref(), ctx)?; - } - ctx.static_block_this_reg = saved_static_this; - } - } - ClassElement::StaticBlock(block) => { - let saved_static_this = ctx.static_block_this_reg; - ctx.static_block_this_reg = Some(ctor_reg); - ctx.push_scope(); - for stmt in &block.body { - self.emit_statement(stmt, ctx)?; - } - ctx.pop_scope(); - ctx.static_block_this_reg = saved_static_this; - } - ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {} - } - } + self.emit_class_prototype(ctor_reg, proto_reg, super_reg, ctx.sub_modules.len() as u32, ctx)?; + self.emit_class_methods(&class.body.body, ctor_reg, proto_reg, &self_binding, ctx)?; + self.emit_class_static_fields(&class.body.body, ctor_reg, ctx)?; + self.emit_class_static_blocks(&class.body.body, ctor_reg, ctx)?; ctx.scopes.private_name_map = saved_private_names; Ok(ctor_reg) } - - pub(crate) fn emit_class_method_function( - &self, method: &oxide_parser::MethodDefinition, method_name: &str, home_reg: u8, ctx: &mut CompileCtx, - self_binding: &[(&str, u8)], - ) -> Result { - let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?; - let saved_instance = ctx.in_instance_method; - let saved_static = ctx.in_static_method; - ctx.in_instance_method = !method.r#static; - ctx.in_static_method = method.r#static; - let mut method_module = self.compile_function_body_with_bindings( - ¶m_names, - body_stmts, - ctx, - false, - self_binding, - FunctionBodyContext::ClassElement, - )?; - ctx.in_instance_method = saved_instance; - ctx.in_static_method = saved_static; - method_module.function_name = Some(method_name.to_string()); - method_module.needs_home_object = true; - ctx.sub_modules.push(method_module); - - let method_reg = ctx.alloc_reg(); - ctx.emit_create_closure(method_reg, ctx.sub_modules.len() as u32); - ctx.emit(opcode::encode(OpCode::SET_HOME_OBJECT, method_reg, home_reg, 0)); - Ok(method_reg) - } } diff --git a/crates/oxide_compiler/src/class/field.rs b/crates/oxide_compiler/src/class/field.rs new file mode 100644 index 0000000..5ad08ff --- /dev/null +++ b/crates/oxide_compiler/src/class/field.rs @@ -0,0 +1,42 @@ +use crate::compiler::{CompileCtx, Compiler}; +use oxide_parser::{ClassElement, PropertyKey}; + +impl Compiler { + pub(crate) fn count_class_static_fields(&self, elements: &[ClassElement], ctx: &mut CompileCtx) { + for element in elements { + if let ClassElement::PropertyDefinition(prop) = element { + let prop = prop.as_ref(); + if prop.r#static { + self.count_class_key(&prop.key, prop.computed, ctx); + if let Some(value) = &prop.value { + self.count_expression(value, ctx); + } else { + ctx.count_load_const(); + } + ctx.count_instr(); + } + } + } + } + + pub(crate) fn emit_class_static_fields( + &self, elements: &[ClassElement], ctor_reg: u8, ctx: &mut CompileCtx, + ) -> Result<(), String> { + for element in elements { + if let ClassElement::PropertyDefinition(prop) = element { + let prop = prop.as_ref(); + if prop.r#static { + let saved_static_this = ctx.static_block_this_reg; + ctx.static_block_this_reg = Some(ctor_reg); + if let PropertyKey::PrivateIdentifier(private) = &prop.key { + self.emit_private_field_init(ctor_reg, private.name.as_str(), prop.value.as_ref(), ctx)?; + } else { + self.emit_public_field_init(ctor_reg, &prop.key, prop.computed, prop.value.as_ref(), ctx)?; + } + ctx.static_block_this_reg = saved_static_this; + } + } + } + Ok(()) + } +} diff --git a/crates/oxide_compiler/src/class/header.rs b/crates/oxide_compiler/src/class/header.rs new file mode 100644 index 0000000..9bc7249 --- /dev/null +++ b/crates/oxide_compiler/src/class/header.rs @@ -0,0 +1,24 @@ +use crate::compiler::{CompileCtx, Compiler}; + +impl Compiler { + pub(crate) fn count_class_header(&self, class: &oxide_parser::Class, ctx: &mut CompileCtx) { + ctx.alloc_reg(); + ctx.alloc_reg(); + if let Some(super_class) = &class.super_class { + self.count_expression(super_class, ctx); + } + } + + pub(crate) fn emit_class_header( + &self, class: &oxide_parser::Class, ctx: &mut CompileCtx, + ) -> Result<(u8, u8, Option), String> { + let ctor_reg = ctx.alloc_reg(); + let proto_reg = ctx.alloc_reg(); + let super_reg = if let Some(super_expr) = &class.super_class { + Some(self.emit_expression(super_expr, ctx)?) + } else { + None + }; + Ok((ctor_reg, proto_reg, super_reg)) + } +} diff --git a/crates/oxide_compiler/src/class/method.rs b/crates/oxide_compiler/src/class/method.rs new file mode 100644 index 0000000..d635d80 --- /dev/null +++ b/crates/oxide_compiler/src/class/method.rs @@ -0,0 +1,125 @@ +use crate::compiler::{CompileCtx, Compiler, FunctionBodyContext}; +use oxide_bytecode::module::Constant; +use oxide_bytecode::opcode::{self, OpCode}; +use oxide_parser::{ClassElement, MethodDefinitionKind, PropertyKey}; + +impl Compiler { + pub(crate) fn count_class_methods(&self, elements: &[ClassElement], ctx: &mut CompileCtx) { + for element in elements { + match element { + ClassElement::MethodDefinition(method) => { + let method = method.as_ref(); + if matches!(method.kind, MethodDefinitionKind::Constructor) { + continue; + } + if matches!(method.key, PropertyKey::PrivateIdentifier(_)) { + ctx.count_load_const(); + ctx.count_load_const(); + ctx.count_instr(); + ctx.count_instr(); + continue; + } + self.count_class_key(&method.key, method.computed, ctx); + ctx.count_load_const(); + ctx.count_instr(); + match method.kind { + MethodDefinitionKind::Method => { + ctx.count_instr(); + } + MethodDefinitionKind::Get | MethodDefinitionKind::Set => { + ctx.count_load_const(); + ctx.count_define_accessor(); + } + MethodDefinitionKind::Constructor => {} + } + } + _ => {} + } + } + } + + pub(crate) fn emit_class_methods( + &self, elements: &[ClassElement], ctor_reg: u8, proto_reg: u8, self_binding: &[(&str, u8)], + ctx: &mut CompileCtx, + ) -> Result<(), String> { + for element in elements { + match element { + ClassElement::MethodDefinition(method) => { + let method = method.as_ref(); + if method.kind == MethodDefinitionKind::Constructor { + continue; + } + if matches!(method.key, PropertyKey::PrivateIdentifier(_)) { + let home_reg = if method.r#static { ctor_reg } else { proto_reg }; + self.emit_private_method_init(home_reg, method, home_reg, ctx)?; + continue; + } + let home_reg = if method.r#static { ctor_reg } else { proto_reg }; + let key_reg = self.emit_class_key_reg(&method.key, method.computed, ctx)?; + let method_name = if method.computed { + "".to_string() + } else { + self.class_property_name(&method.key)? + }; + let accessor_reg = + self.emit_class_method_function(method, &method_name, home_reg, ctx, self_binding)?; + match method.kind { + MethodDefinitionKind::Method => { + if method.computed { + ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, home_reg, key_reg, accessor_reg)); + } else { + ctx.emit(opcode::encode(OpCode::SET_PROP, home_reg, accessor_reg, key_reg)); + } + } + MethodDefinitionKind::Get | MethodDefinitionKind::Set => { + if method.computed { + return Err("computed class accessors not yet supported".into()); + } + let undef_reg = self.emit_undefined(ctx); + let (get_reg, set_reg) = if method.kind == MethodDefinitionKind::Get { + (accessor_reg, undef_reg) + } else { + (undef_reg, accessor_reg) + }; + let key_name = self.class_property_name(&method.key)?; + let key_idx = ctx.add_constant(Constant::String(key_name)); + ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, home_reg, get_reg, set_reg)); + ctx.emit(key_idx as u32); + } + MethodDefinitionKind::Constructor => continue, + } + } + _ => {} + } + } + Ok(()) + } + + pub(crate) fn emit_class_method_function( + &self, method: &oxide_parser::MethodDefinition, method_name: &str, home_reg: u8, ctx: &mut CompileCtx, + self_binding: &[(&str, u8)], + ) -> Result { + let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?; + let saved_instance = ctx.in_instance_method; + let saved_static = ctx.in_static_method; + ctx.in_instance_method = !method.r#static; + ctx.in_static_method = method.r#static; + let mut method_module = self.compile_function_body_with_bindings( + ¶m_names, + body_stmts, + ctx, + false, + self_binding, + FunctionBodyContext::ClassElement, + )?; + ctx.in_instance_method = saved_instance; + ctx.in_static_method = saved_static; + method_module.function_name = Some(method_name.to_string()); + method_module.needs_home_object = true; + ctx.sub_modules.push(method_module); + let method_reg = ctx.alloc_reg(); + ctx.emit_create_closure(method_reg, ctx.sub_modules.len() as u32); + ctx.emit(opcode::encode(OpCode::SET_HOME_OBJECT, method_reg, home_reg, 0)); + Ok(method_reg) + } +} diff --git a/crates/oxide_compiler/src/class/mod.rs b/crates/oxide_compiler/src/class/mod.rs index c6fa3a4..e418e29 100644 --- a/crates/oxide_compiler/src/class/mod.rs +++ b/crates/oxide_compiler/src/class/mod.rs @@ -1,2 +1,7 @@ pub mod core; pub mod emit; +pub mod field; +pub mod header; +pub mod method; +pub mod prototype; +pub mod static_block; diff --git a/crates/oxide_compiler/src/class/prototype.rs b/crates/oxide_compiler/src/class/prototype.rs new file mode 100644 index 0000000..41f1a1e --- /dev/null +++ b/crates/oxide_compiler/src/class/prototype.rs @@ -0,0 +1,52 @@ +use crate::compiler::{CompileCtx, Compiler}; +use oxide_bytecode::module::Constant; +use oxide_bytecode::opcode::{self, OpCode}; + +impl Compiler { + pub(crate) fn count_class_prototype(&self, has_super: bool, ctx: &mut CompileCtx) { + ctx.count_instr(); + ctx.count_instr(); + if has_super { + ctx.alloc_reg(); + ctx.count_instr(); + ctx.alloc_reg(); + ctx.count_instr(); + ctx.alloc_reg(); + ctx.count_instr(); + ctx.count_instr(); + ctx.count_instr(); + } + ctx.count_load_const(); + ctx.count_instr(); + ctx.count_load_const(); + ctx.count_instr(); + } + + pub(crate) fn emit_class_prototype( + &self, ctor_reg: u8, proto_reg: u8, super_reg: Option, sub_idx: u32, ctx: &mut CompileCtx, + ) -> Result<(), String> { + ctx.emit_create_closure(ctor_reg, sub_idx); + ctx.emit(opcode::encode(OpCode::NEW_OBJECT, proto_reg, 0, 0)); + if let Some(super_reg) = super_reg { + let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string())); + let parent_proto_key_reg = ctx.alloc_reg(); + ctx.emit_load_const(parent_proto_key_reg, proto_key_idx); + let parent_proto_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::GET_PROP, super_reg, parent_proto_reg, parent_proto_key_reg)); + let proto_link_idx = ctx.add_constant(Constant::String("__proto__".to_string())); + let proto_link_key_reg = ctx.alloc_reg(); + ctx.emit_load_const(proto_link_key_reg, proto_link_idx); + ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, parent_proto_reg, proto_link_key_reg)); + ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, super_reg, proto_link_key_reg)); + } + let ctor_key_idx = ctx.add_constant(Constant::String("constructor".to_string())); + let ctor_key_reg = ctx.alloc_reg(); + ctx.emit_load_const(ctor_key_reg, ctor_key_idx); + ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, ctor_reg, ctor_key_reg)); + let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string())); + let proto_key_reg = ctx.alloc_reg(); + ctx.emit_load_const(proto_key_reg, proto_key_idx); + ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, proto_reg, proto_key_reg)); + Ok(()) + } +} diff --git a/crates/oxide_compiler/src/class/static_block.rs b/crates/oxide_compiler/src/class/static_block.rs new file mode 100644 index 0000000..9feacbb --- /dev/null +++ b/crates/oxide_compiler/src/class/static_block.rs @@ -0,0 +1,32 @@ +use crate::compiler::{CompileCtx, Compiler}; +use oxide_parser::ClassElement; + +impl Compiler { + pub(crate) fn count_class_static_blocks(&self, elements: &[ClassElement], ctx: &mut CompileCtx) { + for element in elements { + if let ClassElement::StaticBlock(block) = element { + for stmt in &block.body { + self.count_statement(stmt, ctx); + } + } + } + } + + pub(crate) fn emit_class_static_blocks( + &self, elements: &[ClassElement], ctor_reg: u8, ctx: &mut CompileCtx, + ) -> Result<(), String> { + for element in elements { + if let ClassElement::StaticBlock(block) = element { + let saved_static_this = ctx.static_block_this_reg; + ctx.static_block_this_reg = Some(ctor_reg); + ctx.push_scope(); + for stmt in &block.body { + self.emit_statement(stmt, ctx)?; + } + ctx.pop_scope(); + ctx.static_block_this_reg = saved_static_this; + } + } + Ok(()) + } +}