diff --git a/crates/oxide_compiler/src/emitter/expr/assignment.rs b/crates/oxide_compiler/src/emitter/expr/assignment.rs new file mode 100644 index 0000000..abe3f13 --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/assignment.rs @@ -0,0 +1,166 @@ +use super::*; + +impl Compiler { + pub(in crate::emitter) fn emit_assignment_expression( + &self, assign: &oxide_parser::AssignmentExpression, ctx: &mut CompileCtx, + ) -> Result { + if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left { + if let Some(logical_op) = assign.operator.to_logical_operator() { + let id = ctx.next_label_id(); + let store_label = Label::TernaryElse(id); + let end_label = Label::TernaryEnd(id); + let obj_reg = self.emit_expression(&member.object, ctx)?; + let prop_name = member.property.name.as_str(); + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + let key_reg = ctx.alloc_reg(); + ctx.emit_load_const(key_reg, idx); + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, obj_reg, 0)); + ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, result_reg, key_reg)); + ctx.emit(0); + ctx.emit(0); + ctx.emit(0); + self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; + let val_reg = self.emit_expression(&assign.right, ctx)?; + ctx.emit(opcode::encode(OpCode::IC_SET_PROP, obj_reg, val_reg, key_reg)); + ctx.emit(0); + ctx.emit(0); + ctx.emit(0); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0)); + return Ok(result_reg); + } + let obj_reg = self.emit_expression(&member.object, ctx)?; + let val_reg = self.emit_expression(&assign.right, ctx)?; + let prop_name = member.property.name.as_str(); + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + let key_reg = ctx.alloc_reg(); + ctx.emit_load_const(key_reg, idx); + if assign.operator != AssignmentOperator::Assign { + let op = match assign.operator { + AssignmentOperator::Addition => OpCode::COMPOUND_MEMBER_ADD, + AssignmentOperator::Subtraction => OpCode::COMPOUND_MEMBER_SUB, + AssignmentOperator::Multiplication => OpCode::COMPOUND_MEMBER_MUL, + AssignmentOperator::Division => OpCode::COMPOUND_MEMBER_DIV, + AssignmentOperator::Remainder => OpCode::COMPOUND_MEMBER_MOD, + AssignmentOperator::Exponential => OpCode::COMPOUND_MEMBER_EXP, + _ => return Err(format!("compound assignment operator {:?} not supported", assign.operator)), + }; + ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg)); + ctx.emit(0); + ctx.emit(0); + ctx.emit(0); + Ok(val_reg) + } else { + ctx.emit(opcode::encode(OpCode::IC_SET_PROP, obj_reg, val_reg, key_reg)); + ctx.emit(0); + ctx.emit(0); + ctx.emit(0); + Ok(val_reg) + } + } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left { + if let Some(logical_op) = assign.operator.to_logical_operator() { + let id = ctx.next_label_id(); + let store_label = Label::TernaryElse(id); + let end_label = Label::TernaryEnd(id); + let obj_reg = self.emit_expression(&member.object, ctx)?; + let key_reg = self.emit_expression(&member.expression, ctx)?; + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::GET_PROP_DYNAMIC, obj_reg, key_reg, result_reg)); + self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; + let val_reg = self.emit_expression(&assign.right, ctx)?; + ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, obj_reg, key_reg, val_reg)); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0)); + return Ok(result_reg); + } + let obj_reg = self.emit_expression(&member.object, ctx)?; + let key_reg = self.emit_expression(&member.expression, ctx)?; + let val_reg = self.emit_expression(&assign.right, ctx)?; + ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, obj_reg, key_reg, val_reg)); + Ok(val_reg) + } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { + if assign.operator != AssignmentOperator::Assign { + return Err("compound assignment to private fields not supported".into()); + } + let obj_reg = self.emit_expression(&member.object, ctx)?; + let val_reg = self.emit_expression(&assign.right, ctx)?; + let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; + ctx.emit(opcode::encode(OpCode::SET_PRIVATE, obj_reg, val_reg, key_reg)); + Ok(val_reg) + } else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(id_ref) = &assign.left { + if assign.operator != AssignmentOperator::Assign { + if let Some(logical_op) = assign.operator.to_logical_operator() { + let id = ctx.next_label_id(); + let store_label = Label::TernaryElse(id); + let end_label = Label::TernaryEnd(id); + let name = id_ref.name.as_str(); + let var_reg = ctx.lookup_or_global(name); + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, var_reg, 0)); + self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; + let val_reg = self.emit_expression(&assign.right, ctx)?; + let is_const = ctx.lookup_const_flag(name); + let const_flag = if is_const { 1 } else { 0 }; + ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, const_flag)); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0)); + Ok(result_reg) + } else if assign.operator == AssignmentOperator::Addition + || assign.operator == AssignmentOperator::Subtraction + || assign.operator == AssignmentOperator::Multiplication + || assign.operator == AssignmentOperator::Division + || assign.operator == AssignmentOperator::Remainder + || assign.operator == AssignmentOperator::Exponential + || assign.operator == AssignmentOperator::BitwiseAnd + || assign.operator == AssignmentOperator::BitwiseOR + || assign.operator == AssignmentOperator::BitwiseXOR + || assign.operator == AssignmentOperator::ShiftLeft + || assign.operator == AssignmentOperator::ShiftRight + || assign.operator == AssignmentOperator::ShiftRightZeroFill + { + let rhs = self.emit_expression(&assign.right, ctx)?; + let name = id_ref.name.as_str(); + let var_reg = ctx.lookup_or_global(name); + let op = match assign.operator { + AssignmentOperator::Addition => OpCode::COMPOUND_ADD, + AssignmentOperator::Subtraction => OpCode::COMPOUND_SUB, + AssignmentOperator::Multiplication => OpCode::COMPOUND_MUL, + AssignmentOperator::Division => OpCode::COMPOUND_DIV, + AssignmentOperator::Remainder => OpCode::COMPOUND_MOD, + AssignmentOperator::Exponential => OpCode::COMPOUND_EXP, + AssignmentOperator::BitwiseAnd => OpCode::COMPOUND_AND, + AssignmentOperator::BitwiseOR => OpCode::COMPOUND_OR, + AssignmentOperator::BitwiseXOR => OpCode::COMPOUND_XOR, + AssignmentOperator::ShiftLeft => OpCode::COMPOUND_SHL, + AssignmentOperator::ShiftRight => OpCode::COMPOUND_SHR, + AssignmentOperator::ShiftRightZeroFill => OpCode::COMPOUND_USHR, + _ => return Err(format!("compound assignment operator {:?} not supported", assign.operator)), + }; + ctx.emit(opcode::encode(op, var_reg, rhs, 0)); + Ok(var_reg) + } else { + Err(format!("compound assignment operator {:?} not supported", assign.operator)) + } + } else { + let val_reg = self.emit_expression(&assign.right, ctx)?; + let name = id_ref.name.as_str(); + let var_reg = ctx.lookup_or_global(name); + let is_const = ctx.lookup_const_flag(name); + let const_flag = if is_const { 1 } else { 0 }; + ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, const_flag)); + Ok(val_reg) + } + } else if matches!( + &assign.left, + oxide_parser::AssignmentTarget::ArrayAssignmentTarget(_) + | oxide_parser::AssignmentTarget::ObjectAssignmentTarget(_) + ) { + if assign.operator != AssignmentOperator::Assign { + return Err("compound destructuring assignment not supported".into()); + } + let val_reg = self.emit_expression(&assign.right, ctx)?; + self.emit_assign_target(&assign.left, val_reg, ctx)?; + Ok(val_reg) + } else { + Err("assignment target not supported".into()) + } + } +} diff --git a/crates/oxide_compiler/src/emitter/expr/call.rs b/crates/oxide_compiler/src/emitter/expr/call.rs new file mode 100644 index 0000000..afd006a --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/call.rs @@ -0,0 +1,97 @@ +use super::*; + +impl Compiler { + pub(in crate::emitter) fn emit_call_expression( + &self, call: &oxide_parser::CallExpression, ctx: &mut CompileCtx, + ) -> Result { + if matches!(&call.callee, Expression::Super(_)) { + if !ctx.in_derived_constructor { + return Err("super() only supported in derived constructors".into()); + } + let mut arg_regs = Vec::new(); + for arg in &call.arguments { + if let Some(expr) = arg.as_expression() { + arg_regs.push(self.emit_expression(expr, ctx)?); + } + } + let first_arg_reg = if arg_regs.is_empty() { 0u8 } else { arg_regs[0] }; + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::SUPER_CALL, result_reg, first_arg_reg, 0)); + ctx.emit(arg_regs.len() as u32); + if !ctx.after_super_inserted { + if let Some(field_code) = ctx.after_super_insert.clone() { + ctx.bytecode.extend(field_code); + ctx.after_super_inserted = true; + } + } + return Ok(result_reg); + } + let (callee_reg, this_reg) = match &call.callee { + Expression::PrivateFieldExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; + let callee_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::GET_PRIVATE, callee_reg, obj_reg, key_reg)); + (callee_reg, obj_reg) + } + Expression::StaticMemberExpression(member) => { + let is_super_member = matches!(&member.object, Expression::Super(_)); + let obj_reg = if is_super_member { + let this_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, this_reg, 254, 0)); + this_reg + } else { + self.emit_expression(&member.object, ctx)? + }; + let prop_name = member.property.name.as_str(); + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + let key_reg = ctx.alloc_reg(); + ctx.emit_load_const(key_reg, idx); + let callee_reg = ctx.alloc_reg(); + if is_super_member { + if !ctx.in_instance_method && !ctx.in_static_method && !ctx.in_derived_constructor { + return Err("super property only supported in class methods".into()); + } + let op = if ctx.in_static_method { + OpCode::SUPER_STATIC_GET_PROP + } else { + OpCode::SUPER_GET_PROP + }; + ctx.emit(opcode::encode(op, callee_reg, obj_reg, key_reg)); + } else { + ctx.emit(opcode::encode(OpCode::LOAD_VAR, callee_reg, obj_reg, 0)); + ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, callee_reg, key_reg)); + ctx.emit(0); + ctx.emit(0); + ctx.emit(0); + } + (callee_reg, obj_reg) + } + _ => { + let callee_reg = self.emit_expression(&call.callee, ctx)?; + let this_idx = ctx.add_constant(Constant::Undefined); + let this_reg = ctx.alloc_reg(); + ctx.emit_load_const(this_reg, this_idx); + (callee_reg, this_reg) + } + }; + let mut arg_regs = Vec::new(); + for arg in &call.arguments { + if let Some(expr) = arg.as_expression() { + arg_regs.push(self.emit_expression(expr, ctx)?); + } + } + let first_arg_reg = if arg_regs.is_empty() { 0u8 } else { arg_regs[0] }; + let op = match &call.callee { + Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE, + _ => OpCode::CALL, + }; + ctx.emit(opcode::encode(op, callee_reg, this_reg, first_arg_reg)); + ctx.emit(arg_regs.len() as u32); + // Copy result from regs[0] into a dedicated register so multiple + // call expressions don't overwrite each other. + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, 0, 0)); + Ok(result_reg) + } +} diff --git a/crates/oxide_compiler/src/emitter/expr/function.rs b/crates/oxide_compiler/src/emitter/expr/function.rs new file mode 100644 index 0000000..681c0a4 --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/function.rs @@ -0,0 +1,101 @@ +use super::*; + +impl Compiler { + pub(in crate::emitter) fn emit_arrow_function_expression( + &self, arrow: &oxide_parser::ArrowFunctionExpression, ctx: &mut CompileCtx, + ) -> Result { + // Rest params not yet supported (D-06 placeholder) + if let Some(_rest) = &arrow.params.rest { + return Err("rest params in arrow functions not yet supported".into()); + } + + // Extract param names (same pattern as FunctionExpression) + let mut param_names = Vec::new(); + for (idx, param) in arrow.params.items.iter().enumerate() { + match ¶m.pattern { + oxide_parser::BindingPattern::BindingIdentifier(bi) => { + param_names.push(ParamSpec::Identifier(bi.name.to_string())); + } + pattern => { + param_names.push(ParamSpec::Pattern { + synthetic_name: format!("@@param_{idx}"), + pattern, + }); + } + } + } + + // Expression body: pass body statements directly with is_expression_body=true. + // Statement body: pass body statements with is_expression_body=false. + let body_stmts = &arrow.body.statements; + let is_expr_body = arrow.expression; + + let mut sub_module = self.compile_function_body(¶m_names, body_stmts, ctx, is_expr_body, true)?; + sub_module.is_arrow = true; + + ctx.sub_modules.push(sub_module); + // 1-indexed: 0 = no sub_module (sentinel) + let sub_idx = ctx.sub_modules.len() as u32; + + let const_idx = ctx.add_constant(Constant::BytecodeFunc(sub_idx)); + let r = ctx.alloc_reg(); + ctx.emit_load_const(r, const_idx); + Ok(r) + } + + pub(in crate::emitter) fn emit_function_expression( + &self, fe: &oxide_parser::Function, ctx: &mut CompileCtx, + ) -> Result { + // FunctionExpression: compile body, emit LOAD_CONST(BytecodeFunc) + let mut param_names = Vec::new(); + for (idx, param) in fe.params.items.iter().enumerate() { + match ¶m.pattern { + oxide_parser::BindingPattern::BindingIdentifier(bi) => { + param_names.push(ParamSpec::Identifier(bi.name.to_string())); + } + pattern => { + param_names.push(ParamSpec::Pattern { + synthetic_name: format!("@@param_{idx}"), + pattern, + }); + } + } + } + + let body_stmts: &[Statement] = if let Some(body) = &fe.body { &body.statements } else { &[] }; + + let mut sub_module = self.compile_function_body(¶m_names, body_stmts, ctx, false, false)?; + if let Some(id) = &fe.id { + sub_module.function_name = Some(id.name.to_string()); + } + ctx.sub_modules.push(sub_module); + // 1-indexed: 0 = no sub_module (sentinel) + let sub_idx = ctx.sub_modules.len() as u32; + + let const_idx = ctx.add_constant(Constant::BytecodeFunc(sub_idx)); + let r = ctx.alloc_reg(); + ctx.emit_load_const(r, const_idx); + Ok(r) + } + + pub(in crate::emitter) fn emit_class_expression(&self, class: &Class, ctx: &mut CompileCtx) -> Result { + self.emit_class(class, ctx) + } + + pub(in crate::emitter) fn emit_new_expression( + &self, ne: &oxide_parser::NewExpression, ctx: &mut CompileCtx, + ) -> Result { + let constructor_reg = self.emit_expression(&ne.callee, ctx)?; + let mut arg_regs = Vec::new(); + for arg in &ne.arguments { + if let Some(expr) = arg.as_expression() { + arg_regs.push(self.emit_expression(expr, ctx)?); + } + } + let first_arg_reg = if arg_regs.is_empty() { 0u8 } else { arg_regs[0] }; + let r = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::NEW_EXPRESSION, r, constructor_reg, first_arg_reg)); + ctx.emit(arg_regs.len() as u32); + Ok(r) + } +} diff --git a/crates/oxide_compiler/src/emitter/expr/literal.rs b/crates/oxide_compiler/src/emitter/expr/literal.rs new file mode 100644 index 0000000..dba865c --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/literal.rs @@ -0,0 +1,91 @@ +use super::*; + +impl Compiler { + pub(in crate::emitter) fn emit_numeric_literal_expression( + &self, n: &oxide_parser::NumericLiteral, ctx: &mut CompileCtx, + ) -> Result { + let idx = if is_int_literal(n.value) { + ctx.add_constant(Constant::Int(n.value as i32)) + } else { + ctx.add_constant(Constant::Number(n.value)) + }; + let r = ctx.alloc_reg(); + ctx.emit_load_const(r, idx); + Ok(r) + } + + pub(in crate::emitter) fn emit_string_literal_expression( + &self, s: &oxide_parser::StringLiteral, ctx: &mut CompileCtx, + ) -> Result { + let idx = ctx.add_constant(Constant::String(s.value.to_string())); + let r = ctx.alloc_reg(); + ctx.emit_load_const(r, idx); + Ok(r) + } + + pub(in crate::emitter) fn emit_boolean_literal_expression( + &self, b: &oxide_parser::BooleanLiteral, ctx: &mut CompileCtx, + ) -> Result { + let idx = ctx.add_constant(Constant::Boolean(b.value)); + let r = ctx.alloc_reg(); + ctx.emit_load_const(r, idx); + Ok(r) + } + + pub(in crate::emitter) fn emit_null_literal_expression(&self, ctx: &mut CompileCtx) -> Result { + let idx = ctx.add_constant(Constant::Null); + let r = ctx.alloc_reg(); + ctx.emit_load_const(r, idx); + Ok(r) + } + + pub(in crate::emitter) fn emit_identifier_expression( + &self, ident: &oxide_parser::IdentifierReference, ctx: &mut CompileCtx, + ) -> Result { + let var_reg = ctx.lookup_or_builtin(ident.name.as_str())?; + let r = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, var_reg, 0)); + Ok(r) + } + + pub(in crate::emitter) fn emit_parenthesized_expression( + &self, p: &oxide_parser::ParenthesizedExpression, ctx: &mut CompileCtx, + ) -> Result { + self.emit_expression(&p.expression, ctx) + } + + pub(in crate::emitter) fn emit_this_expression(&self, ctx: &mut CompileCtx) -> Result { + let r = ctx.alloc_reg(); + let src = ctx.static_block_this_reg.unwrap_or(254); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, src, 0)); + Ok(r) + } + + pub(in crate::emitter) fn emit_reg_exp_literal_expression( + &self, lit: &oxide_parser::RegExpLiteral, ctx: &mut CompileCtx, + ) -> Result { + if let Some(raw) = &lit.raw { + let raw_str = raw.to_string(); + if raw_str.len() >= 2 && raw_str.starts_with('/') { + let last_slash = raw_str.rfind('/').unwrap_or(raw_str.len() - 1); + let pattern = &raw_str[1..last_slash]; + let flags = &raw_str[last_slash + 1..]; + let ci = ctx.add_constant(Constant::RegExp(pattern.to_string(), flags.to_string())); + let r = ctx.alloc_reg(); + ctx.emit_load_const(r, ci); + Ok(r) + } else { + Err(format!("unsupported regexp literal: {:?}", lit)) + } + } else { + Err(format!("unsupported regexp literal: {:?}", lit)) + } + } + + pub(in crate::emitter) fn emit_unsupported_expression( + &self, expr: &Expression, ctx: &mut CompileCtx, + ) -> Result { + let _ = ctx; + Err(format!("unsupported expression type: {:?}", expr)) + } +} diff --git a/crates/oxide_compiler/src/emitter/expr/member.rs b/crates/oxide_compiler/src/emitter/expr/member.rs new file mode 100644 index 0000000..a1e7d0d --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/member.rs @@ -0,0 +1,75 @@ +use super::*; + +impl Compiler { + pub(in crate::emitter) fn emit_static_member_expression( + &self, member: &oxide_parser::StaticMemberExpression, ctx: &mut CompileCtx, + ) -> Result { + if matches!(&member.object, Expression::Super(_)) { + if !ctx.in_instance_method && !ctx.in_static_method && !ctx.in_derived_constructor { + return Err("super property only supported in class methods".into()); + } + let prop_name = member.property.name.as_str(); + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + let key_reg = ctx.alloc_reg(); + ctx.emit_load_const(key_reg, idx); + let this_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, this_reg, 254, 0)); + let result_reg = ctx.alloc_reg(); + let op = if ctx.in_static_method { + OpCode::SUPER_STATIC_GET_PROP + } else { + OpCode::SUPER_GET_PROP + }; + ctx.emit(opcode::encode(op, result_reg, this_reg, key_reg)); + return Ok(result_reg); + } + let obj_reg = self.emit_expression(&member.object, ctx)?; + let prop_name = member.property.name.as_str(); + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + let key_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_CONST, key_reg, (idx & 0xFF) as u8, ((idx >> 8) & 0xFF) as u8)); + ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, obj_reg, key_reg)); + ctx.emit(0); + ctx.emit(0); + ctx.emit(0); + Ok(obj_reg) + } + + pub(in crate::emitter) fn emit_computed_member_expression( + &self, member: &oxide_parser::ComputedMemberExpression, ctx: &mut CompileCtx, + ) -> Result { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let key_reg = self.emit_expression(&member.expression, ctx)?; + let r = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::GET_PROP_DYNAMIC, obj_reg, key_reg, r)); + Ok(r) + } + + pub(in crate::emitter) fn emit_private_field_expression( + &self, member: &oxide_parser::PrivateFieldExpression, ctx: &mut CompileCtx, + ) -> Result { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; + let r = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::GET_PRIVATE, r, obj_reg, key_reg)); + Ok(r) + } + + pub(in crate::emitter) fn emit_chain_expression( + &self, chain: &oxide_parser::ChainExpression, ctx: &mut CompileCtx, + ) -> Result { + let id = ctx.next_label_id(); + let short_label = Label::TernaryElse(id); + let end_label = Label::TernaryEnd(id); + let value_reg = self.emit_chain_element(&chain.expression, Some(short_label), ctx)?; + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, value_reg, 0)); + let end_pos = ctx.resolve_label(end_label)?; + let offset = (end_pos as isize) - (ctx.bytecode.len() as isize); + let offset = ctx.checked_jump_offset(offset); + ctx.emit(opcode::encode_jmp(offset)); + let undefined_idx = ctx.add_constant(Constant::Undefined); + ctx.emit_load_const(result_reg, undefined_idx); + Ok(result_reg) + } +} diff --git a/crates/oxide_compiler/src/emitter/expr/mod.rs b/crates/oxide_compiler/src/emitter/expr/mod.rs new file mode 100644 index 0000000..6270571 --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/mod.rs @@ -0,0 +1,10 @@ +use super::*; + +mod assignment; +mod call; +mod function; +mod literal; +mod member; +mod object; +mod operator; +mod template; diff --git a/crates/oxide_compiler/src/emitter/expr/object.rs b/crates/oxide_compiler/src/emitter/expr/object.rs new file mode 100644 index 0000000..96d6ee8 --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/object.rs @@ -0,0 +1,76 @@ +use super::*; + +impl Compiler { + pub(in crate::emitter) fn emit_object_expression( + &self, obj: &oxide_parser::ObjectExpression, ctx: &mut CompileCtx, + ) -> Result { + let obj_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::NEW_OBJECT, obj_reg, 0, 0)); + let prop_checkpoint = ctx.reg_checkpoint(); + for prop in &obj.properties { + let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop else { + return Err("spread properties not yet supported".into()); + }; + let prop_name = match &p.key { + oxide_parser::PropertyKey::StaticIdentifier(ident) => ident.name.as_str().to_string(), + oxide_parser::PropertyKey::StringLiteral(s) => s.value.to_string(), + _ => return Err("unsupported object property key type".into()), + }; + match p.kind { + PropertyKind::Get | PropertyKind::Set => { + let accessor_reg = self.emit_expression(&p.value, ctx)?; + if let Some(sub_mod) = ctx.sub_modules.last_mut() { + sub_mod.function_name = Some(prop_name.to_string()); + } + let undef_reg = self.emit_undefined(ctx); + let (get_reg, set_reg) = if p.kind == PropertyKind::Get { + (accessor_reg, undef_reg) + } else { + (undef_reg, accessor_reg) + }; + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, obj_reg, get_reg, set_reg)); + ctx.emit(idx as u32); + ctx.restore_reg_checkpoint(prop_checkpoint); + } + _ => { + let idx = ctx.add_constant(Constant::String(prop_name.to_string())); + let key_reg = ctx.alloc_reg(); + ctx.emit_load_const(key_reg, idx); + let val_reg = self.emit_expression(&p.value, ctx)?; + // Name inference (D-04): if property value is an arrow function, + // set the compiled sub_module's function_name to the property key. + if matches!(&p.value, Expression::ArrowFunctionExpression(_)) { + if let Some(sub_mod) = ctx.sub_modules.last_mut() { + sub_mod.function_name = Some(prop_name.to_string()); + } + } + ctx.emit(opcode::encode(OpCode::SET_PROP, obj_reg, val_reg, key_reg)); + ctx.restore_reg_checkpoint(prop_checkpoint); + } + } + } + Ok(obj_reg) + } + + pub(in crate::emitter) fn emit_array_expression( + &self, arr: &oxide_parser::ArrayExpression, ctx: &mut CompileCtx, + ) -> Result { + let arr_reg = ctx.alloc_reg(); + let n = arr.elements.len() as u16; + ctx.emit(opcode::encode(OpCode::NEW_ARRAY, arr_reg, (n & 0xFF) as u8, ((n >> 8) & 0xFF) as u8)); + let elem_checkpoint = ctx.reg_checkpoint(); + for (i, elem) in arr.elements.iter().enumerate() { + let Some(e) = elem.as_expression() else { + return Err("spread not supported".into()); + }; + let val_reg = self.emit_expression(e, ctx)?; + let idx_reg = ctx.alloc_reg(); + let idx = ctx.add_constant(Constant::Int(i as i32)); + ctx.emit_load_const(idx_reg, idx); + ctx.emit(opcode::encode(OpCode::SET_ELEM, arr_reg, idx_reg, val_reg)); + ctx.restore_reg_checkpoint(elem_checkpoint); + } + Ok(arr_reg) + } +} diff --git a/crates/oxide_compiler/src/emitter/expr/operator.rs b/crates/oxide_compiler/src/emitter/expr/operator.rs new file mode 100644 index 0000000..bc02e8f --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/operator.rs @@ -0,0 +1,307 @@ +use super::*; + +impl Compiler { + pub(in crate::emitter) fn emit_private_in_expression( + &self, pin: &oxide_parser::PrivateInExpression, ctx: &mut CompileCtx, + ) -> Result { + let obj_reg = self.emit_expression(&pin.right, ctx)?; + let key_reg = self.emit_private_id_reg(pin.left.name.as_str(), ctx)?; + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::PRIVATE_BRAND_IN, result_reg, obj_reg, key_reg)); + Ok(result_reg) + } + + pub(in crate::emitter) fn emit_binary_expression( + &self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx, + ) -> Result { + let checkpoint = ctx.reg_checkpoint(); + let left = self.emit_expression(&bin.left, ctx)?; + let right = self.emit_expression(&bin.right, ctx)?; + let op = match bin.operator { + BinaryOperator::Addition => OpCode::ADD, + BinaryOperator::Subtraction => OpCode::SUB, + BinaryOperator::Multiplication => OpCode::MUL, + BinaryOperator::Division => OpCode::DIV, + BinaryOperator::Remainder => OpCode::MOD, + BinaryOperator::BitwiseAnd => OpCode::BIT_AND, + BinaryOperator::BitwiseOR => OpCode::BIT_OR, + BinaryOperator::BitwiseXOR => OpCode::BIT_XOR, + BinaryOperator::ShiftLeft => OpCode::SHL, + BinaryOperator::ShiftRight => OpCode::SHR, + BinaryOperator::ShiftRightZeroFill => OpCode::USHR, + BinaryOperator::Equality => OpCode::EQ, + BinaryOperator::Inequality => OpCode::NEQ, + BinaryOperator::LessThan => OpCode::LT, + BinaryOperator::GreaterThan => OpCode::GT, + BinaryOperator::LessEqualThan => OpCode::LTE, + BinaryOperator::GreaterEqualThan => OpCode::GTE, + BinaryOperator::In => OpCode::IN, + BinaryOperator::Instanceof => OpCode::INSTANCEOF, + BinaryOperator::StrictEquality => OpCode::STRICT_EQ, + BinaryOperator::StrictInequality => OpCode::STRICT_NEQ, + _ => return Err(format!("unsupported binary operator: {:?}", bin.operator)), + }; + ctx.emit(opcode::encode(op, left, left, right)); + if is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right) { + ctx.restore_reg_checkpoint(checkpoint.saturating_add(1).max(left.saturating_add(1))); + } + Ok(left) + } + + pub(in crate::emitter) fn emit_unary_expression( + &self, un: &oxide_parser::UnaryExpression, ctx: &mut CompileCtx, + ) -> Result { + if matches!(un.operator, UnaryOperator::Delete) { + return match &un.argument { + Expression::Identifier(_) => { + Err("SyntaxError: delete of an unqualified identifier in strict mode".into()) + } + Expression::StaticMemberExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let prop_name = member.property.name.as_str(); + let const_idx = ctx.add_constant(Constant::String(prop_name.to_string())); + ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0)); + ctx.emit(const_idx as u32); + Ok(obj_reg) + } + Expression::ComputedMemberExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let key_reg = self.emit_expression(&member.expression, ctx)?; + ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg)); + Ok(obj_reg) + } + Expression::ChainExpression(chain) => { + let mut nullish_jumps = Vec::new(); + let result_reg = match &chain.expression { + ChainElement::StaticMemberExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + if member.optional { + let dup_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, obj_reg, 0)); + let jump_pos = ctx.bytecode.len(); + ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0)); + nullish_jumps.push(jump_pos); + } + let prop_name = member.property.name.as_str(); + let const_idx = ctx.add_constant(Constant::String(prop_name.to_string())); + ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0)); + ctx.emit(const_idx as u32); + obj_reg + } + ChainElement::ComputedMemberExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + if member.optional { + let dup_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, obj_reg, 0)); + let jump_pos = ctx.bytecode.len(); + ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0)); + nullish_jumps.push(jump_pos); + } + let key_reg = self.emit_expression(&member.expression, ctx)?; + ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg)); + obj_reg + } + _ => return Err("invalid delete target".into()), + }; + let end_jump_pos = ctx.bytecode.len(); + ctx.emit(opcode::encode_jmp(0)); + let short_pos = ctx.bytecode.len(); + for jump_pos in nullish_jumps { + let offset = (short_pos as isize) - (jump_pos as isize); + let instr = ctx.bytecode[jump_pos]; + let rd = opcode::rd(instr); + let offset = ctx.checked_jump_offset(offset); + ctx.bytecode[jump_pos] = opcode::encode_jmp_if_nullish(rd, offset); + } + let true_idx = ctx.add_constant(Constant::Boolean(true)); + ctx.emit_load_const(result_reg, true_idx); + let end_pos = ctx.bytecode.len(); + let offset = (end_pos as isize) - (end_jump_pos as isize); + let offset = ctx.checked_jump_offset(offset); + ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset); + Ok(result_reg) + } + _ => Err("invalid delete target".into()), + }; + } + let arg = self.emit_expression(&un.argument, ctx)?; + match un.operator { + UnaryOperator::UnaryNegation => { + ctx.emit(opcode::encode(OpCode::NEG, arg, arg, 0)); + Ok(arg) + } + UnaryOperator::Typeof => { + ctx.emit(opcode::encode(OpCode::TYPEOF, arg, arg, 0)); + Ok(arg) + } + UnaryOperator::Void => { + ctx.emit(opcode::encode(OpCode::VOID, arg, arg, 0)); + Ok(arg) + } + UnaryOperator::LogicalNot => { + ctx.emit(opcode::encode(OpCode::NOT, arg, arg, 0)); + Ok(arg) + } + UnaryOperator::BitwiseNot => { + ctx.emit(opcode::encode(OpCode::BIT_NOT, arg, arg, 0)); + Ok(arg) + } + UnaryOperator::UnaryPlus => { + ctx.emit(opcode::encode(OpCode::UNARY_PLUS, arg, arg, 0)); + Ok(arg) + } + UnaryOperator::Delete => Err("invalid delete target".into()), + } + } + + pub(in crate::emitter) fn emit_conditional_expression( + &self, cond: &oxide_parser::ConditionalExpression, ctx: &mut CompileCtx, + ) -> Result { + let id = ctx.next_label_id(); + let else_label = Label::TernaryElse(id); + let end_label = Label::TernaryEnd(id); + + let test_reg = self.emit_expression(&cond.test, ctx)?; + let else_pos = ctx.resolve_label(else_label)?; + let end_pos = ctx.resolve_label(end_label)?; + + let offset = (else_pos as isize) - (ctx.bytecode.len() as isize); + let offset = ctx.checked_jump_offset(offset); + ctx.emit(opcode::encode_jmp_if_false(test_reg, offset)); + + let cons_reg = self.emit_expression(&cond.consequent, ctx)?; + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, cons_reg, 0)); + + let offset = (end_pos as isize) - (ctx.bytecode.len() as isize); + let offset = ctx.checked_jump_offset(offset); + ctx.emit(opcode::encode_jmp(offset)); + + let alt_reg = self.emit_expression(&cond.alternate, ctx)?; + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, alt_reg, 0)); + + Ok(result_reg) + } + + pub(in crate::emitter) fn emit_logical_expression( + &self, log: &oxide_parser::LogicalExpression, ctx: &mut CompileCtx, + ) -> Result { + use oxide_parser::LogicalOperator; + let left_reg = self.emit_expression(&log.left, ctx)?; + + if is_side_effect_free(&log.left) && is_side_effect_free(&log.right) { + let right_reg = self.emit_expression(&log.right, ctx)?; + let r = ctx.alloc_reg(); + let op = match log.operator { + LogicalOperator::And => OpCode::AND, + LogicalOperator::Or => OpCode::OR, + LogicalOperator::Coalesce => OpCode::NULLISH, + }; + ctx.emit(opcode::encode(op, r, left_reg, right_reg)); + return Ok(r); + } + + if matches!(log.operator, LogicalOperator::Coalesce) { + let dup_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, left_reg, 0)); + let nullish_jump_pos = ctx.bytecode.len(); + ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0)); + let end_jump_pos = ctx.bytecode.len(); + ctx.emit(opcode::encode_jmp(0)); + let rhs_pos = ctx.bytecode.len(); + let right_reg = self.emit_expression(&log.right, ctx)?; + ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0)); + let end_pos = ctx.bytecode.len(); + let offset = (rhs_pos as isize) - (nullish_jump_pos as isize); + let offset = ctx.checked_jump_offset(offset); + ctx.bytecode[nullish_jump_pos] = opcode::encode_jmp_if_nullish(dup_reg, offset); + let offset = (end_pos as isize) - (end_jump_pos as isize); + let offset = ctx.checked_jump_offset(offset); + ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset); + return Ok(dup_reg); + } + + let id = ctx.next_label_id(); + let skip_label = match log.operator { + LogicalOperator::And => Label::TernaryEnd(id), + LogicalOperator::Or => Label::TernaryElse(id), + LogicalOperator::Coalesce => return Err("invalid logical operator dispatch".into()), + }; + let skip_pos = ctx.resolve_label(skip_label)?; + + let dup_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, left_reg, 0)); + + let offset = (skip_pos as isize) - (ctx.bytecode.len() as isize); + match log.operator { + LogicalOperator::And => { + let offset = ctx.checked_jump_offset(offset); + ctx.emit(opcode::encode_jmp_if_false(dup_reg, offset)); + } + LogicalOperator::Or => { + let offset = ctx.checked_jump_offset(offset); + ctx.emit(opcode::encode_jmp_if_true(dup_reg, offset)); + } + LogicalOperator::Coalesce => return Err("invalid logical operator dispatch".into()), + } + + let right_reg = self.emit_expression(&log.right, ctx)?; + ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0)); + + Ok(dup_reg) + } + + pub(in crate::emitter) fn emit_update_expression( + &self, update: &oxide_parser::UpdateExpression, ctx: &mut CompileCtx, + ) -> Result { + match &update.argument { + SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => { + let name = id.name.as_str(); + let var_reg = ctx.lookup_or_global(name); + let result_reg = ctx.alloc_reg(); + let op = match (update.operator, update.prefix) { + (UpdateOperator::Increment, true) => OpCode::INC_PRE, + (UpdateOperator::Increment, false) => OpCode::INC_POST, + (UpdateOperator::Decrement, true) => OpCode::DEC_PRE, + (UpdateOperator::Decrement, false) => OpCode::DEC_POST, + }; + ctx.emit(opcode::encode(op, var_reg, result_reg, result_reg)); + Ok(result_reg) + } + SimpleAssignmentTarget::StaticMemberExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let prop_name = member.property.name.as_str(); + let key_idx = ctx.add_constant(Constant::String(prop_name.to_string())); + let key_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode( + OpCode::LOAD_CONST, + key_reg, + (key_idx & 0xFF) as u8, + ((key_idx >> 8) & 0xFF) as u8, + )); + let val_reg = ctx.alloc_reg(); + let op = match update.operator { + UpdateOperator::Increment => OpCode::MEMBER_INC, + UpdateOperator::Decrement => OpCode::MEMBER_DEC, + }; + ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg)); + ctx.emit(0); + ctx.emit(0); + ctx.emit(0); + Ok(val_reg) + } + SimpleAssignmentTarget::ComputedMemberExpression(member) => { + let obj_reg = self.emit_expression(&member.object, ctx)?; + let key_reg = self.emit_expression(&member.expression, ctx)?; + let val_reg = ctx.alloc_reg(); + let op = match update.operator { + UpdateOperator::Increment => OpCode::DYN_MEMBER_INC, + UpdateOperator::Decrement => OpCode::DYN_MEMBER_DEC, + }; + ctx.emit(opcode::encode(op, obj_reg, key_reg, val_reg)); + Ok(val_reg) + } + _ => Err("member update not yet supported".into()), + } + } +} diff --git a/crates/oxide_compiler/src/emitter/expr/template.rs b/crates/oxide_compiler/src/emitter/expr/template.rs new file mode 100644 index 0000000..7e3ec7a --- /dev/null +++ b/crates/oxide_compiler/src/emitter/expr/template.rs @@ -0,0 +1,128 @@ +use super::*; + +impl Compiler { + pub(in crate::emitter) fn emit_template_literal_expression( + &self, tl: &oxide_parser::TemplateLiteral, ctx: &mut CompileCtx, + ) -> Result { + let r = ctx.alloc_reg(); + let quasis = &tl.quasis; + let expressions = &tl.expressions; + let segment_count = quasis.len() + expressions.len(); + + // Evaluate each expression, collecting registers + let expr_regs: Vec = expressions + .iter() + .map(|e| self.emit_expression(e, ctx)) + .collect::, _>>()?; + + // Add each quasi string to constant pool + let quasi_const_idxs: Vec = quasis + .iter() + .map(|q| { + let s = q.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default(); + ctx.add_constant(Constant::String(s)) + }) + .collect(); + + // Compute total length hint + let total_len_hint: usize = quasis + .iter() + .map(|q| q.value.cooked.as_ref().map(|c| c.len()).unwrap_or(0)) + .sum(); + + // Emit TEMPLATE_STR rd, 0, 0 + ctx.emit(opcode::encode(OpCode::TEMPLATE_STR, r, 0, 0)); + + // Ext word 0: (segment_count << 16) | (total_len_hint & 0xFFFF) + ctx.emit(((segment_count as u32) << 16) | (total_len_hint as u32 & 0xFFFF)); + + // Interleave segments: quasi[0], expr[0], quasi[1], expr[1], ... + let mut expr_iter = expr_regs.iter(); + for const_idx in quasi_const_idxs.iter() { + // Quasi: is_expression=0, bits 0-15 = const_idx + ctx.emit(*const_idx as u32 & 0x7FFF_FFFF); + + // Expression (if any remaining) + if let Some(expr_reg) = expr_iter.next() { + // Expression: is_expression=1, bits 0-15 = reg + ctx.emit(0x8000_0000u32 | (*expr_reg as u32)); + } + } + + Ok(r) + } + + pub(in crate::emitter) fn emit_tagged_template_expression( + &self, tt: &oxide_parser::TaggedTemplateExpression, ctx: &mut CompileCtx, + ) -> Result { + let quasis = &tt.quasi.quasis; + let expressions = &tt.quasi.expressions; + + // 1. Evaluate tag expression + let tag_reg = self.emit_expression(&tt.tag, ctx)?; + + // 2. Build cooked strings array (into a temp register) + let cooked_temp = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::NEW_ARRAY, cooked_temp, quasis.len() as u8, 0)); + for (i, quasi) in quasis.iter().enumerate() { + let s = quasi.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default(); + let const_idx = ctx.add_constant(Constant::String(s)); + let str_reg = ctx.alloc_reg(); + ctx.emit_load_const(str_reg, const_idx); + let idx_const = ctx.add_constant(Constant::Int(i as i32)); + let idx_reg = ctx.alloc_reg(); + ctx.emit_load_const(idx_reg, idx_const); + ctx.emit(opcode::encode(OpCode::SET_ELEM, cooked_temp, idx_reg, str_reg)); + } + + // 3. Build raw strings array (into a temp register) + let raw_temp = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::NEW_ARRAY, raw_temp, quasis.len() as u8, 0)); + for (i, quasi) in quasis.iter().enumerate() { + let raw = quasi.value.raw.to_string(); + let const_idx = ctx.add_constant(Constant::String(raw)); + let str_reg = ctx.alloc_reg(); + ctx.emit_load_const(str_reg, const_idx); + let idx_const = ctx.add_constant(Constant::Int(i as i32)); + let idx_reg = ctx.alloc_reg(); + ctx.emit_load_const(idx_reg, idx_const); + ctx.emit(opcode::encode(OpCode::SET_ELEM, raw_temp, idx_reg, str_reg)); + } + + // 4. Evaluate expression arguments (into temp registers) + let mut expr_temps = Vec::new(); + for expr in expressions { + expr_temps.push(self.emit_expression(expr, ctx)?); + } + + // 5. Allocate consecutive argument slots: cooked, raw, expr[0], expr[1], ... + let cooked_slot = ctx.alloc_reg(); + let raw_slot = ctx.alloc_reg(); + let mut expr_slots = Vec::new(); + for _ in expressions { + expr_slots.push(ctx.alloc_reg()); + } + + // Copy temps to consecutive slots using LOAD_VAR (register-to-register move) + ctx.emit(opcode::encode(OpCode::LOAD_VAR, cooked_slot, cooked_temp, 0)); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, raw_slot, raw_temp, 0)); + for (slot, temp) in expr_slots.iter().zip(expr_temps.iter()) { + ctx.emit(opcode::encode(OpCode::LOAD_VAR, *slot, *temp, 0)); + } + + // 6. Emit undefined as this_arg + let undef_idx = ctx.add_constant(Constant::Undefined); + let undef_reg = ctx.alloc_reg(); + ctx.emit_load_const(undef_reg, undef_idx); + + // 7. Emit CALL(tag, undefined, cooked_slot) + let arg_count = 2 + expressions.len(); + ctx.emit(opcode::encode(OpCode::CALL, tag_reg, undef_reg, cooked_slot)); + ctx.emit(arg_count as u32); + + // 8. Result from regs[0] + let result_reg = ctx.alloc_reg(); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, 0, 0)); + Ok(result_reg) + } +} diff --git a/crates/oxide_compiler/src/emitter/mod.rs b/crates/oxide_compiler/src/emitter/mod.rs index 2b3c377..8c8f84e 100644 --- a/crates/oxide_compiler/src/emitter/mod.rs +++ b/crates/oxide_compiler/src/emitter/mod.rs @@ -12,6 +12,7 @@ use crate::compiler::{ is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler, FunctionBodyContext, ParamSpec, }; +mod expr; mod stmt; impl Compiler { @@ -872,17 +873,17 @@ impl Compiler { return Err("computed class accessors not yet supported".into()); } let undef_reg = self.emit_undefined(ctx); - let (get_reg, set_reg) = match method.kind { - MethodDefinitionKind::Get => (accessor_reg, undef_reg), - MethodDefinitionKind::Set => (undef_reg, accessor_reg), - _ => unreachable!(), + 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 => unreachable!(), + MethodDefinitionKind::Constructor => continue, } } ClassElement::PropertyDefinition(prop) => { @@ -977,940 +978,35 @@ impl Compiler { } pub(crate) fn emit_expression(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result { match expr { - Expression::NumericLiteral(n) => { - let idx = if is_int_literal(n.value) { - ctx.add_constant(Constant::Int(n.value as i32)) - } else { - ctx.add_constant(Constant::Number(n.value)) - }; - let r = ctx.alloc_reg(); - ctx.emit_load_const(r, idx); - Ok(r) - } - Expression::StringLiteral(s) => { - let idx = ctx.add_constant(Constant::String(s.value.to_string())); - let r = ctx.alloc_reg(); - ctx.emit_load_const(r, idx); - Ok(r) - } - Expression::BooleanLiteral(b) => { - let idx = ctx.add_constant(Constant::Boolean(b.value)); - let r = ctx.alloc_reg(); - ctx.emit_load_const(r, idx); - Ok(r) - } - Expression::NullLiteral(_) => { - let idx = ctx.add_constant(Constant::Null); - let r = ctx.alloc_reg(); - ctx.emit_load_const(r, idx); - Ok(r) - } - Expression::PrivateInExpression(pin) => { - let obj_reg = self.emit_expression(&pin.right, ctx)?; - let key_reg = self.emit_private_id_reg(pin.left.name.as_str(), ctx)?; - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::PRIVATE_BRAND_IN, result_reg, obj_reg, key_reg)); - Ok(result_reg) - } - Expression::BinaryExpression(bin) => { - let checkpoint = ctx.reg_checkpoint(); - let left = self.emit_expression(&bin.left, ctx)?; - let right = self.emit_expression(&bin.right, ctx)?; - let op = match bin.operator { - BinaryOperator::Addition => OpCode::ADD, - BinaryOperator::Subtraction => OpCode::SUB, - BinaryOperator::Multiplication => OpCode::MUL, - BinaryOperator::Division => OpCode::DIV, - BinaryOperator::Remainder => OpCode::MOD, - BinaryOperator::BitwiseAnd => OpCode::BIT_AND, - BinaryOperator::BitwiseOR => OpCode::BIT_OR, - BinaryOperator::BitwiseXOR => OpCode::BIT_XOR, - BinaryOperator::ShiftLeft => OpCode::SHL, - BinaryOperator::ShiftRight => OpCode::SHR, - BinaryOperator::ShiftRightZeroFill => OpCode::USHR, - BinaryOperator::Equality => OpCode::EQ, - BinaryOperator::Inequality => OpCode::NEQ, - BinaryOperator::LessThan => OpCode::LT, - BinaryOperator::GreaterThan => OpCode::GT, - BinaryOperator::LessEqualThan => OpCode::LTE, - BinaryOperator::GreaterEqualThan => OpCode::GTE, - BinaryOperator::In => OpCode::IN, - BinaryOperator::Instanceof => OpCode::INSTANCEOF, - BinaryOperator::StrictEquality => OpCode::STRICT_EQ, - BinaryOperator::StrictInequality => OpCode::STRICT_NEQ, - _ => return Err(format!("unsupported binary operator: {:?}", bin.operator)), - }; - ctx.emit(opcode::encode(op, left, left, right)); - if is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right) { - ctx.restore_reg_checkpoint(checkpoint.saturating_add(1).max(left.saturating_add(1))); - } - Ok(left) - } - Expression::UnaryExpression(un) => { - if matches!(un.operator, UnaryOperator::Delete) { - return match &un.argument { - Expression::Identifier(_) => { - Err("SyntaxError: delete of an unqualified identifier in strict mode".into()) - } - Expression::StaticMemberExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - let prop_name = member.property.name.as_str(); - let const_idx = ctx.add_constant(Constant::String(prop_name.to_string())); - ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0)); - ctx.emit(const_idx as u32); - Ok(obj_reg) - } - Expression::ComputedMemberExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - let key_reg = self.emit_expression(&member.expression, ctx)?; - ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg)); - Ok(obj_reg) - } - Expression::ChainExpression(chain) => { - let mut nullish_jumps = Vec::new(); - let result_reg = match &chain.expression { - ChainElement::StaticMemberExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - if member.optional { - let dup_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, obj_reg, 0)); - let jump_pos = ctx.bytecode.len(); - ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0)); - nullish_jumps.push(jump_pos); - } - let prop_name = member.property.name.as_str(); - let const_idx = ctx.add_constant(Constant::String(prop_name.to_string())); - ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0)); - ctx.emit(const_idx as u32); - obj_reg - } - ChainElement::ComputedMemberExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - if member.optional { - let dup_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, obj_reg, 0)); - let jump_pos = ctx.bytecode.len(); - ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0)); - nullish_jumps.push(jump_pos); - } - let key_reg = self.emit_expression(&member.expression, ctx)?; - ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg)); - obj_reg - } - _ => return Err("invalid delete target".into()), - }; - let end_jump_pos = ctx.bytecode.len(); - ctx.emit(opcode::encode_jmp(0)); - let short_pos = ctx.bytecode.len(); - for jump_pos in nullish_jumps { - let offset = (short_pos as isize) - (jump_pos as isize); - let instr = ctx.bytecode[jump_pos]; - let rd = opcode::rd(instr); - let offset = ctx.checked_jump_offset(offset); - ctx.bytecode[jump_pos] = opcode::encode_jmp_if_nullish(rd, offset); - } - let true_idx = ctx.add_constant(Constant::Boolean(true)); - ctx.emit_load_const(result_reg, true_idx); - let end_pos = ctx.bytecode.len(); - let offset = (end_pos as isize) - (end_jump_pos as isize); - let offset = ctx.checked_jump_offset(offset); - ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset); - Ok(result_reg) - } - _ => Err("invalid delete target".into()), - }; - } - let arg = self.emit_expression(&un.argument, ctx)?; - match un.operator { - UnaryOperator::UnaryNegation => { - ctx.emit(opcode::encode(OpCode::NEG, arg, arg, 0)); - Ok(arg) - } - UnaryOperator::Typeof => { - ctx.emit(opcode::encode(OpCode::TYPEOF, arg, arg, 0)); - Ok(arg) - } - UnaryOperator::Void => { - ctx.emit(opcode::encode(OpCode::VOID, arg, arg, 0)); - Ok(arg) - } - UnaryOperator::LogicalNot => { - ctx.emit(opcode::encode(OpCode::NOT, arg, arg, 0)); - Ok(arg) - } - UnaryOperator::BitwiseNot => { - ctx.emit(opcode::encode(OpCode::BIT_NOT, arg, arg, 0)); - Ok(arg) - } - UnaryOperator::UnaryPlus => { - ctx.emit(opcode::encode(OpCode::UNARY_PLUS, arg, arg, 0)); - Ok(arg) - } - UnaryOperator::Delete => unreachable!("delete handled before argument emit"), - } - } - Expression::ConditionalExpression(cond) => { - let id = ctx.next_label_id(); - let else_label = Label::TernaryElse(id); - let end_label = Label::TernaryEnd(id); - - let test_reg = self.emit_expression(&cond.test, ctx)?; - let else_pos = ctx.resolve_label(else_label)?; - let end_pos = ctx.resolve_label(end_label)?; - - let offset = (else_pos as isize) - (ctx.bytecode.len() as isize); - let offset = ctx.checked_jump_offset(offset); - ctx.emit(opcode::encode_jmp_if_false(test_reg, offset)); - - let cons_reg = self.emit_expression(&cond.consequent, ctx)?; - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, cons_reg, 0)); - - let offset = (end_pos as isize) - (ctx.bytecode.len() as isize); - let offset = ctx.checked_jump_offset(offset); - ctx.emit(opcode::encode_jmp(offset)); - - let alt_reg = self.emit_expression(&cond.alternate, ctx)?; - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, alt_reg, 0)); - - Ok(result_reg) - } - Expression::LogicalExpression(log) => { - use oxide_parser::LogicalOperator; - let left_reg = self.emit_expression(&log.left, ctx)?; - - if is_side_effect_free(&log.left) && is_side_effect_free(&log.right) { - let right_reg = self.emit_expression(&log.right, ctx)?; - let r = ctx.alloc_reg(); - let op = match log.operator { - LogicalOperator::And => OpCode::AND, - LogicalOperator::Or => OpCode::OR, - LogicalOperator::Coalesce => OpCode::NULLISH, - }; - ctx.emit(opcode::encode(op, r, left_reg, right_reg)); - Ok(r) - } else { - let id = ctx.next_label_id(); - if matches!(log.operator, LogicalOperator::Coalesce) { - let dup_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, left_reg, 0)); - let nullish_jump_pos = ctx.bytecode.len(); - ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0)); - let end_jump_pos = ctx.bytecode.len(); - ctx.emit(opcode::encode_jmp(0)); - let rhs_pos = ctx.bytecode.len(); - let right_reg = self.emit_expression(&log.right, ctx)?; - ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0)); - let end_pos = ctx.bytecode.len(); - let offset = (rhs_pos as isize) - (nullish_jump_pos as isize); - let offset = ctx.checked_jump_offset(offset); - ctx.bytecode[nullish_jump_pos] = opcode::encode_jmp_if_nullish(dup_reg, offset); - let offset = (end_pos as isize) - (end_jump_pos as isize); - let offset = ctx.checked_jump_offset(offset); - ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset); - return Ok(dup_reg); - } - let skip_label = match log.operator { - LogicalOperator::And => Label::TernaryEnd(id), - LogicalOperator::Or => Label::TernaryElse(id), - LogicalOperator::Coalesce => unreachable!(), - }; - let skip_pos = ctx.resolve_label(skip_label)?; - - let dup_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, left_reg, 0)); - - let offset = (skip_pos as isize) - (ctx.bytecode.len() as isize); - match log.operator { - LogicalOperator::And => { - let offset = ctx.checked_jump_offset(offset); - ctx.emit(opcode::encode_jmp_if_false(dup_reg, offset)); - } - LogicalOperator::Or => { - let offset = ctx.checked_jump_offset(offset); - ctx.emit(opcode::encode_jmp_if_true(dup_reg, offset)); - } - LogicalOperator::Coalesce => unreachable!(), - } - - let right_reg = self.emit_expression(&log.right, ctx)?; - ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0)); - - Ok(dup_reg) - } - } - Expression::StaticMemberExpression(member) => { - if matches!(&member.object, Expression::Super(_)) { - if !ctx.in_instance_method && !ctx.in_static_method && !ctx.in_derived_constructor { - return Err("super property only supported in class methods".into()); - } - let prop_name = member.property.name.as_str(); - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let key_reg = ctx.alloc_reg(); - ctx.emit_load_const(key_reg, idx); - let this_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, this_reg, 254, 0)); - let result_reg = ctx.alloc_reg(); - let op = if ctx.in_static_method { - OpCode::SUPER_STATIC_GET_PROP - } else { - OpCode::SUPER_GET_PROP - }; - ctx.emit(opcode::encode(op, result_reg, this_reg, key_reg)); - return Ok(result_reg); - } - let obj_reg = self.emit_expression(&member.object, ctx)?; - let prop_name = member.property.name.as_str(); - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let key_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_CONST, key_reg, (idx & 0xFF) as u8, ((idx >> 8) & 0xFF) as u8)); - ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, obj_reg, key_reg)); - ctx.emit(0); - ctx.emit(0); - ctx.emit(0); - Ok(obj_reg) - } - Expression::ComputedMemberExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - let key_reg = self.emit_expression(&member.expression, ctx)?; - let r = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::GET_PROP_DYNAMIC, obj_reg, key_reg, r)); - Ok(r) - } - Expression::PrivateFieldExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; - let r = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::GET_PRIVATE, r, obj_reg, key_reg)); - Ok(r) - } - Expression::ChainExpression(chain) => { - let id = ctx.next_label_id(); - let short_label = Label::TernaryElse(id); - let end_label = Label::TernaryEnd(id); - let value_reg = self.emit_chain_element(&chain.expression, Some(short_label), ctx)?; - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, value_reg, 0)); - let end_pos = ctx.resolve_label(end_label)?; - let offset = (end_pos as isize) - (ctx.bytecode.len() as isize); - let offset = ctx.checked_jump_offset(offset); - ctx.emit(opcode::encode_jmp(offset)); - let undefined_idx = ctx.add_constant(Constant::Undefined); - ctx.emit_load_const(result_reg, undefined_idx); - Ok(result_reg) - } - Expression::ObjectExpression(obj) => { - let obj_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::NEW_OBJECT, obj_reg, 0, 0)); - let prop_checkpoint = ctx.reg_checkpoint(); - for prop in &obj.properties { - let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop else { - return Err("spread properties not yet supported".into()); - }; - let prop_name = match &p.key { - oxide_parser::PropertyKey::StaticIdentifier(ident) => ident.name.as_str().to_string(), - oxide_parser::PropertyKey::StringLiteral(s) => s.value.to_string(), - _ => return Err("unsupported object property key type".into()), - }; - if matches!(p.kind, PropertyKind::Get | PropertyKind::Set) { - let accessor_reg = self.emit_expression(&p.value, ctx)?; - if let Some(sub_mod) = ctx.sub_modules.last_mut() { - sub_mod.function_name = Some(prop_name.to_string()); - } - let undef_reg = self.emit_undefined(ctx); - let (get_reg, set_reg) = match p.kind { - PropertyKind::Get => (accessor_reg, undef_reg), - PropertyKind::Set => (undef_reg, accessor_reg), - _ => unreachable!(), - }; - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, obj_reg, get_reg, set_reg)); - ctx.emit(idx as u32); - ctx.restore_reg_checkpoint(prop_checkpoint); - } else { - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let key_reg = ctx.alloc_reg(); - ctx.emit_load_const(key_reg, idx); - let val_reg = self.emit_expression(&p.value, ctx)?; - // Name inference (D-04): if property value is an arrow function, - // set the compiled sub_module's function_name to the property key. - if matches!(&p.value, Expression::ArrowFunctionExpression(_)) { - if let Some(sub_mod) = ctx.sub_modules.last_mut() { - sub_mod.function_name = Some(prop_name.to_string()); - } - } - ctx.emit(opcode::encode(OpCode::SET_PROP, obj_reg, val_reg, key_reg)); - ctx.restore_reg_checkpoint(prop_checkpoint); - } - } - Ok(obj_reg) - } - Expression::ArrayExpression(arr) => { - let arr_reg = ctx.alloc_reg(); - let n = arr.elements.len() as u16; - ctx.emit(opcode::encode(OpCode::NEW_ARRAY, arr_reg, (n & 0xFF) as u8, ((n >> 8) & 0xFF) as u8)); - let elem_checkpoint = ctx.reg_checkpoint(); - for (i, elem) in arr.elements.iter().enumerate() { - let Some(e) = elem.as_expression() else { - return Err("spread not supported".into()); - }; - let val_reg = self.emit_expression(e, ctx)?; - let idx_reg = ctx.alloc_reg(); - let idx = ctx.add_constant(Constant::Int(i as i32)); - ctx.emit_load_const(idx_reg, idx); - ctx.emit(opcode::encode(OpCode::SET_ELEM, arr_reg, idx_reg, val_reg)); - ctx.restore_reg_checkpoint(elem_checkpoint); - } - Ok(arr_reg) - } - Expression::AssignmentExpression(assign) => { - if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left { - if let Some(logical_op) = assign.operator.to_logical_operator() { - let id = ctx.next_label_id(); - let store_label = Label::TernaryElse(id); - let end_label = Label::TernaryEnd(id); - let obj_reg = self.emit_expression(&member.object, ctx)?; - let prop_name = member.property.name.as_str(); - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let key_reg = ctx.alloc_reg(); - ctx.emit_load_const(key_reg, idx); - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, obj_reg, 0)); - ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, result_reg, key_reg)); - ctx.emit(0); - ctx.emit(0); - ctx.emit(0); - self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; - let val_reg = self.emit_expression(&assign.right, ctx)?; - ctx.emit(opcode::encode(OpCode::IC_SET_PROP, obj_reg, val_reg, key_reg)); - ctx.emit(0); - ctx.emit(0); - ctx.emit(0); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0)); - return Ok(result_reg); - } - let obj_reg = self.emit_expression(&member.object, ctx)?; - let val_reg = self.emit_expression(&assign.right, ctx)?; - let prop_name = member.property.name.as_str(); - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let key_reg = ctx.alloc_reg(); - ctx.emit_load_const(key_reg, idx); - if assign.operator != AssignmentOperator::Assign { - let op = match assign.operator { - AssignmentOperator::Addition => OpCode::COMPOUND_MEMBER_ADD, - AssignmentOperator::Subtraction => OpCode::COMPOUND_MEMBER_SUB, - AssignmentOperator::Multiplication => OpCode::COMPOUND_MEMBER_MUL, - AssignmentOperator::Division => OpCode::COMPOUND_MEMBER_DIV, - AssignmentOperator::Remainder => OpCode::COMPOUND_MEMBER_MOD, - AssignmentOperator::Exponential => OpCode::COMPOUND_MEMBER_EXP, - _ => { - return Err(format!("compound assignment operator {:?} not supported", assign.operator)) - } - }; - ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg)); - ctx.emit(0); - ctx.emit(0); - ctx.emit(0); - Ok(val_reg) - } else { - ctx.emit(opcode::encode(OpCode::IC_SET_PROP, obj_reg, val_reg, key_reg)); - ctx.emit(0); - ctx.emit(0); - ctx.emit(0); - Ok(val_reg) - } - } else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left { - if let Some(logical_op) = assign.operator.to_logical_operator() { - let id = ctx.next_label_id(); - let store_label = Label::TernaryElse(id); - let end_label = Label::TernaryEnd(id); - let obj_reg = self.emit_expression(&member.object, ctx)?; - let key_reg = self.emit_expression(&member.expression, ctx)?; - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::GET_PROP_DYNAMIC, obj_reg, key_reg, result_reg)); - self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; - let val_reg = self.emit_expression(&assign.right, ctx)?; - ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, obj_reg, key_reg, val_reg)); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0)); - return Ok(result_reg); - } - let obj_reg = self.emit_expression(&member.object, ctx)?; - let key_reg = self.emit_expression(&member.expression, ctx)?; - let val_reg = self.emit_expression(&assign.right, ctx)?; - ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, obj_reg, key_reg, val_reg)); - Ok(val_reg) - } else if let oxide_parser::AssignmentTarget::PrivateFieldExpression(member) = &assign.left { - if assign.operator != AssignmentOperator::Assign { - return Err("compound assignment to private fields not supported".into()); - } - let obj_reg = self.emit_expression(&member.object, ctx)?; - let val_reg = self.emit_expression(&assign.right, ctx)?; - let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; - ctx.emit(opcode::encode(OpCode::SET_PRIVATE, obj_reg, val_reg, key_reg)); - Ok(val_reg) - } else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(id_ref) = &assign.left { - if assign.operator != AssignmentOperator::Assign { - if let Some(logical_op) = assign.operator.to_logical_operator() { - let id = ctx.next_label_id(); - let store_label = Label::TernaryElse(id); - let end_label = Label::TernaryEnd(id); - let name = id_ref.name.as_str(); - let var_reg = ctx.lookup_or_global(name); - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, var_reg, 0)); - self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?; - let val_reg = self.emit_expression(&assign.right, ctx)?; - let is_const = ctx.lookup_const_flag(name); - let const_flag = if is_const { 1 } else { 0 }; - ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, const_flag)); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0)); - Ok(result_reg) - } else if assign.operator == AssignmentOperator::Addition - || assign.operator == AssignmentOperator::Subtraction - || assign.operator == AssignmentOperator::Multiplication - || assign.operator == AssignmentOperator::Division - || assign.operator == AssignmentOperator::Remainder - || assign.operator == AssignmentOperator::Exponential - || assign.operator == AssignmentOperator::BitwiseAnd - || assign.operator == AssignmentOperator::BitwiseOR - || assign.operator == AssignmentOperator::BitwiseXOR - || assign.operator == AssignmentOperator::ShiftLeft - || assign.operator == AssignmentOperator::ShiftRight - || assign.operator == AssignmentOperator::ShiftRightZeroFill - { - let rhs = self.emit_expression(&assign.right, ctx)?; - let name = id_ref.name.as_str(); - let var_reg = ctx.lookup_or_global(name); - let op = match assign.operator { - AssignmentOperator::Addition => OpCode::COMPOUND_ADD, - AssignmentOperator::Subtraction => OpCode::COMPOUND_SUB, - AssignmentOperator::Multiplication => OpCode::COMPOUND_MUL, - AssignmentOperator::Division => OpCode::COMPOUND_DIV, - AssignmentOperator::Remainder => OpCode::COMPOUND_MOD, - AssignmentOperator::Exponential => OpCode::COMPOUND_EXP, - AssignmentOperator::BitwiseAnd => OpCode::COMPOUND_AND, - AssignmentOperator::BitwiseOR => OpCode::COMPOUND_OR, - AssignmentOperator::BitwiseXOR => OpCode::COMPOUND_XOR, - AssignmentOperator::ShiftLeft => OpCode::COMPOUND_SHL, - AssignmentOperator::ShiftRight => OpCode::COMPOUND_SHR, - AssignmentOperator::ShiftRightZeroFill => OpCode::COMPOUND_USHR, - _ => { - return Err(format!( - "compound assignment operator {:?} not supported", - assign.operator - )) - } - }; - ctx.emit(opcode::encode(op, var_reg, rhs, 0)); - Ok(var_reg) - } else { - Err(format!("compound assignment operator {:?} not supported", assign.operator)) - } - } else { - let val_reg = self.emit_expression(&assign.right, ctx)?; - let name = id_ref.name.as_str(); - let var_reg = ctx.lookup_or_global(name); - let is_const = ctx.lookup_const_flag(name); - let const_flag = if is_const { 1 } else { 0 }; - ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, const_flag)); - Ok(val_reg) - } - } else if matches!( - &assign.left, - oxide_parser::AssignmentTarget::ArrayAssignmentTarget(_) - | oxide_parser::AssignmentTarget::ObjectAssignmentTarget(_) - ) { - if assign.operator != AssignmentOperator::Assign { - return Err("compound destructuring assignment not supported".into()); - } - let val_reg = self.emit_expression(&assign.right, ctx)?; - self.emit_assign_target(&assign.left, val_reg, ctx)?; - Ok(val_reg) - } else { - Err("assignment target not supported".into()) - } - } - Expression::UpdateExpression(update) => match &update.argument { - SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => { - let name = id.name.as_str(); - let var_reg = ctx.lookup_or_global(name); - let result_reg = ctx.alloc_reg(); - let op = match (update.operator, update.prefix) { - (UpdateOperator::Increment, true) => OpCode::INC_PRE, - (UpdateOperator::Increment, false) => OpCode::INC_POST, - (UpdateOperator::Decrement, true) => OpCode::DEC_PRE, - (UpdateOperator::Decrement, false) => OpCode::DEC_POST, - }; - ctx.emit(opcode::encode(op, var_reg, result_reg, result_reg)); - Ok(result_reg) - } - SimpleAssignmentTarget::StaticMemberExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - let prop_name = member.property.name.as_str(); - let key_idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let key_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode( - OpCode::LOAD_CONST, - key_reg, - (key_idx & 0xFF) as u8, - ((key_idx >> 8) & 0xFF) as u8, - )); - let val_reg = ctx.alloc_reg(); - let op = match update.operator { - UpdateOperator::Increment => OpCode::MEMBER_INC, - UpdateOperator::Decrement => OpCode::MEMBER_DEC, - }; - ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg)); - ctx.emit(0); - ctx.emit(0); - ctx.emit(0); - Ok(val_reg) - } - SimpleAssignmentTarget::ComputedMemberExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - let key_reg = self.emit_expression(&member.expression, ctx)?; - let val_reg = ctx.alloc_reg(); - let op = match update.operator { - UpdateOperator::Increment => OpCode::DYN_MEMBER_INC, - UpdateOperator::Decrement => OpCode::DYN_MEMBER_DEC, - }; - ctx.emit(opcode::encode(op, obj_reg, key_reg, val_reg)); - Ok(val_reg) - } - _ => Err("member update not yet supported".into()), - }, - Expression::Identifier(ident) => { - let var_reg = ctx.lookup_or_builtin(ident.name.as_str())?; - let r = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, var_reg, 0)); - Ok(r) - } - // TemplateLiteral: interleaved quasis + expressions via TEMPLATE_STR opcode (D-07) - Expression::TemplateLiteral(tl) => { - let r = ctx.alloc_reg(); - let quasis = &tl.quasis; - let expressions = &tl.expressions; - let segment_count = quasis.len() + expressions.len(); - - // Evaluate each expression, collecting registers - let expr_regs: Vec = expressions - .iter() - .map(|e| self.emit_expression(e, ctx)) - .collect::, _>>()?; - - // Add each quasi string to constant pool - let quasi_const_idxs: Vec = quasis - .iter() - .map(|q| { - let s = q.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default(); - ctx.add_constant(Constant::String(s)) - }) - .collect(); - - // Compute total length hint - let total_len_hint: usize = quasis - .iter() - .map(|q| q.value.cooked.as_ref().map(|c| c.len()).unwrap_or(0)) - .sum(); - - // Emit TEMPLATE_STR rd, 0, 0 - ctx.emit(opcode::encode(OpCode::TEMPLATE_STR, r, 0, 0)); - - // Ext word 0: (segment_count << 16) | (total_len_hint & 0xFFFF) - ctx.emit(((segment_count as u32) << 16) | (total_len_hint as u32 & 0xFFFF)); - - // Interleave segments: quasi[0], expr[0], quasi[1], expr[1], ... - let mut expr_iter = expr_regs.iter(); - for const_idx in quasi_const_idxs.iter() { - // Quasi: is_expression=0, bits 0-15 = const_idx - ctx.emit(*const_idx as u32 & 0x7FFF_FFFF); - - // Expression (if any remaining) - if let Some(expr_reg) = expr_iter.next() { - // Expression: is_expression=1, bits 0-15 = reg - ctx.emit(0x8000_0000u32 | (*expr_reg as u32)); - } - } - - Ok(r) - } - // TaggedTemplateExpression: tag`str ${expr}` => CALL(tag, undefined, cooked_array, raw_array, ...exprs) (D-08) - Expression::TaggedTemplateExpression(tt) => { - let quasis = &tt.quasi.quasis; - let expressions = &tt.quasi.expressions; - - // 1. Evaluate tag expression - let tag_reg = self.emit_expression(&tt.tag, ctx)?; - - // 2. Build cooked strings array (into a temp register) - let cooked_temp = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::NEW_ARRAY, cooked_temp, quasis.len() as u8, 0)); - for (i, quasi) in quasis.iter().enumerate() { - let s = quasi.value.cooked.as_ref().map(|c| c.to_string()).unwrap_or_default(); - let const_idx = ctx.add_constant(Constant::String(s)); - let str_reg = ctx.alloc_reg(); - ctx.emit_load_const(str_reg, const_idx); - let idx_const = ctx.add_constant(Constant::Int(i as i32)); - let idx_reg = ctx.alloc_reg(); - ctx.emit_load_const(idx_reg, idx_const); - ctx.emit(opcode::encode(OpCode::SET_ELEM, cooked_temp, idx_reg, str_reg)); - } - - // 3. Build raw strings array (into a temp register) - let raw_temp = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::NEW_ARRAY, raw_temp, quasis.len() as u8, 0)); - for (i, quasi) in quasis.iter().enumerate() { - let raw = quasi.value.raw.to_string(); - let const_idx = ctx.add_constant(Constant::String(raw)); - let str_reg = ctx.alloc_reg(); - ctx.emit_load_const(str_reg, const_idx); - let idx_const = ctx.add_constant(Constant::Int(i as i32)); - let idx_reg = ctx.alloc_reg(); - ctx.emit_load_const(idx_reg, idx_const); - ctx.emit(opcode::encode(OpCode::SET_ELEM, raw_temp, idx_reg, str_reg)); - } - - // 4. Evaluate expression arguments (into temp registers) - let mut expr_temps = Vec::new(); - for expr in expressions { - expr_temps.push(self.emit_expression(expr, ctx)?); - } - - // 5. Allocate consecutive argument slots: cooked, raw, expr[0], expr[1], ... - let cooked_slot = ctx.alloc_reg(); - let raw_slot = ctx.alloc_reg(); - let mut expr_slots = Vec::new(); - for _ in expressions { - expr_slots.push(ctx.alloc_reg()); - } - - // Copy temps to consecutive slots using LOAD_VAR (register-to-register move) - ctx.emit(opcode::encode(OpCode::LOAD_VAR, cooked_slot, cooked_temp, 0)); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, raw_slot, raw_temp, 0)); - for (slot, temp) in expr_slots.iter().zip(expr_temps.iter()) { - ctx.emit(opcode::encode(OpCode::LOAD_VAR, *slot, *temp, 0)); - } - - // 6. Emit undefined as this_arg - let undef_idx = ctx.add_constant(Constant::Undefined); - let undef_reg = ctx.alloc_reg(); - ctx.emit_load_const(undef_reg, undef_idx); - - // 7. Emit CALL(tag, undefined, cooked_slot) - let arg_count = 2 + expressions.len(); - ctx.emit(opcode::encode(OpCode::CALL, tag_reg, undef_reg, cooked_slot)); - ctx.emit(arg_count as u32); - - // 8. Result from regs[0] - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, 0, 0)); - Ok(result_reg) - } - // ArrowFunctionExpression: compile body, capture lexical this (D-01) - // Name inference (D-04) happens at assignment site - see VariableDeclaration/ObjectProperty. - Expression::ArrowFunctionExpression(arrow) => { - // Rest params not yet supported (D-06 placeholder) - if let Some(_rest) = &arrow.params.rest { - return Err("rest params in arrow functions not yet supported".into()); - } - - // Extract param names (same pattern as FunctionExpression) - let mut param_names = Vec::new(); - for (idx, param) in arrow.params.items.iter().enumerate() { - match ¶m.pattern { - oxide_parser::BindingPattern::BindingIdentifier(bi) => { - param_names.push(ParamSpec::Identifier(bi.name.to_string())); - } - pattern => { - param_names.push(ParamSpec::Pattern { - synthetic_name: format!("@@param_{idx}"), - pattern, - }); - } - } - } - - // Expression body: pass body statements directly with is_expression_body=true. - // Statement body: pass body statements with is_expression_body=false. - let body_stmts = &arrow.body.statements; - let is_expr_body = arrow.expression; - - let mut sub_module = self.compile_function_body(¶m_names, body_stmts, ctx, is_expr_body, true)?; - sub_module.is_arrow = true; - - ctx.sub_modules.push(sub_module); - // 1-indexed: 0 = no sub_module (sentinel) - let sub_idx = ctx.sub_modules.len() as u32; - - let const_idx = ctx.add_constant(Constant::BytecodeFunc(sub_idx)); - let r = ctx.alloc_reg(); - ctx.emit_load_const(r, const_idx); - Ok(r) - } - Expression::FunctionExpression(fe) => { - // FunctionExpression: compile body, emit LOAD_CONST(BytecodeFunc) - let mut param_names = Vec::new(); - for (idx, param) in fe.params.items.iter().enumerate() { - match ¶m.pattern { - oxide_parser::BindingPattern::BindingIdentifier(bi) => { - param_names.push(ParamSpec::Identifier(bi.name.to_string())); - } - pattern => { - param_names.push(ParamSpec::Pattern { - synthetic_name: format!("@@param_{idx}"), - pattern, - }); - } - } - } - - let body_stmts: &[Statement] = if let Some(body) = &fe.body { &body.statements } else { &[] }; - - let mut sub_module = self.compile_function_body(¶m_names, body_stmts, ctx, false, false)?; - if let Some(id) = &fe.id { - sub_module.function_name = Some(id.name.to_string()); - } - ctx.sub_modules.push(sub_module); - // 1-indexed: 0 = no sub_module (sentinel) - let sub_idx = ctx.sub_modules.len() as u32; - - let const_idx = ctx.add_constant(Constant::BytecodeFunc(sub_idx)); - let r = ctx.alloc_reg(); - ctx.emit_load_const(r, const_idx); - Ok(r) - } - Expression::ClassExpression(class) => self.emit_class(class, ctx), - Expression::NewExpression(ne) => { - let constructor_reg = self.emit_expression(&ne.callee, ctx)?; - let mut arg_regs = Vec::new(); - for arg in &ne.arguments { - if let Some(expr) = arg.as_expression() { - arg_regs.push(self.emit_expression(expr, ctx)?); - } - } - let first_arg_reg = if arg_regs.is_empty() { 0u8 } else { arg_regs[0] }; - let r = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::NEW_EXPRESSION, r, constructor_reg, first_arg_reg)); - ctx.emit(arg_regs.len() as u32); - Ok(r) - } - Expression::ParenthesizedExpression(p) => self.emit_expression(&p.expression, ctx), - Expression::CallExpression(call) => { - if matches!(&call.callee, Expression::Super(_)) { - if !ctx.in_derived_constructor { - return Err("super() only supported in derived constructors".into()); - } - let mut arg_regs = Vec::new(); - for arg in &call.arguments { - if let Some(expr) = arg.as_expression() { - arg_regs.push(self.emit_expression(expr, ctx)?); - } - } - let first_arg_reg = if arg_regs.is_empty() { 0u8 } else { arg_regs[0] }; - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::SUPER_CALL, result_reg, first_arg_reg, 0)); - ctx.emit(arg_regs.len() as u32); - if !ctx.after_super_inserted { - if let Some(field_code) = ctx.after_super_insert.clone() { - ctx.bytecode.extend(field_code); - ctx.after_super_inserted = true; - } - } - return Ok(result_reg); - } - let (callee_reg, this_reg) = match &call.callee { - Expression::PrivateFieldExpression(member) => { - let obj_reg = self.emit_expression(&member.object, ctx)?; - let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?; - let callee_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::GET_PRIVATE, callee_reg, obj_reg, key_reg)); - (callee_reg, obj_reg) - } - Expression::StaticMemberExpression(member) => { - let is_super_member = matches!(&member.object, Expression::Super(_)); - let obj_reg = if is_super_member { - let this_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, this_reg, 254, 0)); - this_reg - } else { - self.emit_expression(&member.object, ctx)? - }; - let prop_name = member.property.name.as_str(); - let idx = ctx.add_constant(Constant::String(prop_name.to_string())); - let key_reg = ctx.alloc_reg(); - ctx.emit_load_const(key_reg, idx); - let callee_reg = ctx.alloc_reg(); - if is_super_member { - if !ctx.in_instance_method && !ctx.in_static_method && !ctx.in_derived_constructor { - return Err("super property only supported in class methods".into()); - } - let op = if ctx.in_static_method { - OpCode::SUPER_STATIC_GET_PROP - } else { - OpCode::SUPER_GET_PROP - }; - ctx.emit(opcode::encode(op, callee_reg, obj_reg, key_reg)); - } else { - ctx.emit(opcode::encode(OpCode::LOAD_VAR, callee_reg, obj_reg, 0)); - ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, callee_reg, key_reg)); - ctx.emit(0); - ctx.emit(0); - ctx.emit(0); - } - (callee_reg, obj_reg) - } - _ => { - let callee_reg = self.emit_expression(&call.callee, ctx)?; - let this_idx = ctx.add_constant(Constant::Undefined); - let this_reg = ctx.alloc_reg(); - ctx.emit_load_const(this_reg, this_idx); - (callee_reg, this_reg) - } - }; - let mut arg_regs = Vec::new(); - for arg in &call.arguments { - if let Some(expr) = arg.as_expression() { - arg_regs.push(self.emit_expression(expr, ctx)?); - } - } - let first_arg_reg = if arg_regs.is_empty() { 0u8 } else { arg_regs[0] }; - let op = match &call.callee { - Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE, - _ => OpCode::CALL, - }; - ctx.emit(opcode::encode(op, callee_reg, this_reg, first_arg_reg)); - ctx.emit(arg_regs.len() as u32); - // Copy result from regs[0] into a dedicated register so multiple - // call expressions don't overwrite each other. - let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, 0, 0)); - Ok(result_reg) - } - Expression::ThisExpression(_) => { - let r = ctx.alloc_reg(); - let src = ctx.static_block_this_reg.unwrap_or(254); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, src, 0)); - Ok(r) - } - Expression::RegExpLiteral(lit) => { - if let Some(raw) = &lit.raw { - let raw_str = raw.to_string(); - if raw_str.len() >= 2 && raw_str.starts_with('/') { - let last_slash = raw_str.rfind('/').unwrap_or(raw_str.len() - 1); - let pattern = &raw_str[1..last_slash]; - let flags = &raw_str[last_slash + 1..]; - let ci = ctx.add_constant(Constant::RegExp(pattern.to_string(), flags.to_string())); - let r = ctx.alloc_reg(); - ctx.emit_load_const(r, ci); - Ok(r) - } else { - Err(format!("unsupported expression type: {:?}", expr)) - } - } else { - Err(format!("unsupported expression type: {:?}", expr)) - } - } - _ => Err(format!("unsupported expression type: {:?}", expr)), + Expression::NumericLiteral(n) => self.emit_numeric_literal_expression(n, ctx), + Expression::StringLiteral(s) => self.emit_string_literal_expression(s, ctx), + Expression::BooleanLiteral(b) => self.emit_boolean_literal_expression(b, ctx), + Expression::NullLiteral(_) => self.emit_null_literal_expression(ctx), + Expression::PrivateInExpression(pin) => self.emit_private_in_expression(pin, ctx), + Expression::BinaryExpression(bin) => self.emit_binary_expression(bin, ctx), + Expression::UnaryExpression(un) => self.emit_unary_expression(un, ctx), + Expression::ConditionalExpression(cond) => self.emit_conditional_expression(cond, ctx), + Expression::LogicalExpression(log) => self.emit_logical_expression(log, ctx), + Expression::StaticMemberExpression(member) => self.emit_static_member_expression(member, ctx), + Expression::ComputedMemberExpression(member) => self.emit_computed_member_expression(member, ctx), + Expression::PrivateFieldExpression(member) => self.emit_private_field_expression(member, ctx), + Expression::ChainExpression(chain) => self.emit_chain_expression(chain, ctx), + Expression::ObjectExpression(obj) => self.emit_object_expression(obj, ctx), + Expression::ArrayExpression(arr) => self.emit_array_expression(arr, ctx), + Expression::AssignmentExpression(assign) => self.emit_assignment_expression(assign, ctx), + Expression::UpdateExpression(update) => self.emit_update_expression(update, ctx), + Expression::Identifier(ident) => self.emit_identifier_expression(ident, ctx), + Expression::TemplateLiteral(tl) => self.emit_template_literal_expression(tl, ctx), + Expression::TaggedTemplateExpression(tt) => self.emit_tagged_template_expression(tt, ctx), + Expression::ArrowFunctionExpression(arrow) => self.emit_arrow_function_expression(arrow, ctx), + Expression::FunctionExpression(fe) => self.emit_function_expression(fe, ctx), + Expression::ClassExpression(class) => self.emit_class_expression(class, ctx), + Expression::NewExpression(ne) => self.emit_new_expression(ne, ctx), + Expression::ParenthesizedExpression(p) => self.emit_parenthesized_expression(p, ctx), + Expression::CallExpression(call) => self.emit_call_expression(call, ctx), + Expression::ThisExpression(_) => self.emit_this_expression(ctx), + Expression::RegExpLiteral(lit) => self.emit_reg_exp_literal_expression(lit, ctx), + _ => self.emit_unsupported_expression(expr, ctx), } } }