mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-27 12:11:12 +08:00
refactor: 拆分编译流水线过程以避免逻辑漂移
This commit is contained in:
@@ -1,183 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
pub(in crate::emitter) fn emit_assignment_expression(
|
||||
&self, assign: &oxide_parser::AssignmentExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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();
|
||||
// Check if target is an upvalue reference
|
||||
if let Some(uv_idx) = ctx.current_upvalue_captures.iter().position(|u| u.name == name) {
|
||||
ctx.emit(opcode::encode(OpCode::STORE_UPVALUE, 0, val_reg, uv_idx as u8));
|
||||
return Ok(val_reg);
|
||||
}
|
||||
// Check if target is a captured cell
|
||||
if ctx.scopes.symbols.lookup_is_captured(name) {
|
||||
let cell_idx = ctx
|
||||
.scopes
|
||||
.cell_registry
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
.map(|(_, idx)| *idx)
|
||||
.unwrap_or(0);
|
||||
ctx.emit(opcode::encode(OpCode::CELL_SET, 0, val_reg, cell_idx));
|
||||
return Ok(val_reg);
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_call_expression(&self, call: &oxide_parser::CallExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_call_domain(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
match expr {
|
||||
Expression::CallExpression(call) => self.emit_call_expression(call, ctx),
|
||||
_ => self.emit_unsupported_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_arrow_function_expression(
|
||||
&self, arrow: &oxide_parser::ArrowFunctionExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
// Rest params not yet supported
|
||||
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 r = ctx.alloc_reg();
|
||||
ctx.emit_create_closure(r, sub_idx);
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn emit_function_expression(&self, fe: &oxide_parser::Function, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
// 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 r = ctx.alloc_reg();
|
||||
ctx.emit_create_closure(r, sub_idx);
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn emit_class_expression(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
self.emit_class(class, ctx)
|
||||
}
|
||||
|
||||
fn emit_new_expression(&self, ne: &oxide_parser::NewExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_function_domain(
|
||||
&self, expr: &Expression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
match expr {
|
||||
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),
|
||||
_ => self.emit_unsupported_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
/// Domain entry point: emit any literal expression. The central
|
||||
/// `emit_expression` match delegates all literal variants here in one arm;
|
||||
/// adding a new literal kind only touches this file plus that one arm.
|
||||
pub(in crate::emitter) fn emit_literal(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
match 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::RegExpLiteral(lit) => self.emit_reg_exp_literal_expression(lit, ctx),
|
||||
_ => self.emit_unsupported_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_numeric_literal_expression(
|
||||
&self, n: &oxide_parser::NumericLiteral, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_string_literal_expression(
|
||||
&self, s: &oxide_parser::StringLiteral, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
let idx = ctx.add_constant(Constant::String(s.value.to_string()));
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.emit_load_const(r, idx);
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn emit_boolean_literal_expression(
|
||||
&self, b: &oxide_parser::BooleanLiteral, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
let idx = ctx.add_constant(Constant::Boolean(b.value));
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.emit_load_const(r, idx);
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn emit_null_literal_expression(&self, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
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<u8, String> {
|
||||
let name = ident.name.as_str();
|
||||
|
||||
// Check if this is an upvalue reference (nested closure)
|
||||
for (uv_idx, up) in ctx.current_upvalue_captures.iter().enumerate() {
|
||||
if up.name == name {
|
||||
let r = ctx.alloc_reg();
|
||||
let idx = uv_idx as u8;
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_UPVALUE, r, idx, 0));
|
||||
return Ok(r);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the owner's binding is captured (cell access)
|
||||
if ctx.scopes.symbols.lookup_is_captured(name) {
|
||||
let cell_idx = ctx
|
||||
.scopes
|
||||
.cell_registry
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
.map(|(_, idx)| *idx)
|
||||
.unwrap_or(0);
|
||||
let r = ctx.alloc_reg();
|
||||
if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) {
|
||||
ctx.emit(opcode::encode(OpCode::CELL_GET, r, binding.reg, cell_idx));
|
||||
} else {
|
||||
ctx.emit(opcode::encode(OpCode::CELL_GET, r, 0, cell_idx));
|
||||
}
|
||||
return Ok(r);
|
||||
}
|
||||
|
||||
let var_reg = ctx.lookup_or_builtin(name)?;
|
||||
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<u8, String> {
|
||||
self.emit_expression(&p.expression, ctx)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_this_expression(&self, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_reg_exp_literal_expression(
|
||||
&self, lit: &oxide_parser::RegExpLiteral, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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].to_string();
|
||||
let flags = raw_str[last_slash + 1..].to_string();
|
||||
let pat_ci = ctx.add_constant(Constant::String(pattern));
|
||||
let pat_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(pat_reg, pat_ci);
|
||||
let flags_ci = ctx.add_constant(Constant::String(flags));
|
||||
let flags_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(flags_reg, flags_ci);
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::CREATE_REGEXP, r, pat_reg, flags_reg));
|
||||
Ok(r)
|
||||
} else {
|
||||
Err(format!("unsupported regexp literal: {:?}", lit))
|
||||
}
|
||||
} else {
|
||||
Err(format!("unsupported regexp literal: {:?}", lit))
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_sequence_expression(
|
||||
&self, seq: &oxide_parser::SequenceExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
// Comma operator: evaluate each expression in order, value is the last.
|
||||
// Emits exactly one expression per element (no extra instruction), so the
|
||||
// projected-PC count in count_sequence_expression stays aligned.
|
||||
let mut last = 0u8;
|
||||
for e in &seq.expressions {
|
||||
last = self.emit_expression(e, ctx)?;
|
||||
}
|
||||
Ok(last)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_unsupported_expression(
|
||||
&self, expr: &Expression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
let _ = ctx;
|
||||
Err(format!("unsupported expression type: {:?}", expr))
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_static_member_expression(
|
||||
&self, member: &oxide_parser::StaticMemberExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_computed_member_expression(
|
||||
&self, member: &oxide_parser::ComputedMemberExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_private_field_expression(
|
||||
&self, member: &oxide_parser::PrivateFieldExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_chain_expression(&self, chain: &oxide_parser::ChainExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_member_domain(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
match expr {
|
||||
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),
|
||||
_ => self.emit_unsupported_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
mod assignment;
|
||||
mod call;
|
||||
mod function;
|
||||
mod literal;
|
||||
mod member;
|
||||
mod object;
|
||||
mod operator;
|
||||
mod template;
|
||||
@@ -1,361 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_private_in_expression(
|
||||
&self, pin: &oxide_parser::PrivateInExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_binary_expression(&self, bin: &oxide_parser::BinaryExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_unary_expression(&self, un: &oxide_parser::UnaryExpression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_conditional_expression(
|
||||
&self, cond: &oxide_parser::ConditionalExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_logical_expression(
|
||||
&self, log: &oxide_parser::LogicalExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn emit_update_expression(
|
||||
&self, update: &oxide_parser::UpdateExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
match &update.argument {
|
||||
SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => {
|
||||
let name = id.name.as_str();
|
||||
// Check if this is an upvalue or captured cell reference
|
||||
let uv_idx = ctx.current_upvalue_captures.iter().position(|u| u.name == name);
|
||||
let is_captured = ctx.scopes.symbols.lookup_is_captured(name);
|
||||
if let Some(uv) = uv_idx {
|
||||
// Upvalue: LOAD_UPVALUE + CONST(1) + ADD/SUB + STORE_UPVALUE
|
||||
let val_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_UPVALUE, val_reg, uv as u8, 0));
|
||||
let one_idx = ctx.add_constant(Constant::Int(1));
|
||||
let one_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(one_reg, one_idx);
|
||||
let op = if update.operator == UpdateOperator::Increment {
|
||||
OpCode::ADD
|
||||
} else {
|
||||
OpCode::SUB
|
||||
};
|
||||
ctx.emit(opcode::encode(op, val_reg, val_reg, one_reg));
|
||||
ctx.emit(opcode::encode(OpCode::STORE_UPVALUE, 0, val_reg, uv as u8));
|
||||
Ok(val_reg)
|
||||
} else if is_captured {
|
||||
// Captured cell: CELL_GET + CONST(1) + ADD/SUB + CELL_SET
|
||||
let cell_idx = ctx
|
||||
.scopes
|
||||
.cell_registry
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
.map(|(_, idx)| *idx)
|
||||
.unwrap_or(0);
|
||||
let val_reg = ctx.alloc_reg();
|
||||
if let Some((binding, _)) = ctx.scopes.symbols.lookup_any_binding(name) {
|
||||
ctx.emit(opcode::encode(OpCode::CELL_GET, val_reg, binding.reg, cell_idx));
|
||||
} else {
|
||||
ctx.emit(opcode::encode(OpCode::CELL_GET, val_reg, 0, cell_idx));
|
||||
}
|
||||
let one_idx = ctx.add_constant(Constant::Int(1));
|
||||
let one_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(one_reg, one_idx);
|
||||
let op = if update.operator == UpdateOperator::Increment {
|
||||
OpCode::ADD
|
||||
} else {
|
||||
OpCode::SUB
|
||||
};
|
||||
ctx.emit(opcode::encode(op, val_reg, val_reg, one_reg));
|
||||
ctx.emit(opcode::encode(OpCode::CELL_SET, 0, val_reg, cell_idx));
|
||||
Ok(val_reg)
|
||||
} else {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_operator(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
match expr {
|
||||
Expression::BinaryExpression(bin) => self.emit_binary_expression(bin, ctx),
|
||||
Expression::PrivateInExpression(pin) => self.emit_private_in_expression(pin, 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::UpdateExpression(update) => self.emit_update_expression(update, ctx),
|
||||
_ => self.emit_unsupported_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(unused_imports)]
|
||||
use crate::compiler::Label;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::{self, OpCode};
|
||||
@@ -12,9 +13,6 @@ use crate::compiler::{
|
||||
is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler, FunctionBodyContext, ParamSpec,
|
||||
};
|
||||
|
||||
mod expr;
|
||||
mod stmt;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_optional_guard(&self, reg: u8, short_label: Label, ctx: &mut CompileCtx) -> Result<(), String> {
|
||||
let short_pos = ctx.resolve_label(short_label)?;
|
||||
@@ -157,7 +155,7 @@ impl Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_chain_element(
|
||||
pub(crate) fn emit_chain_element(
|
||||
&self, element: &ChainElement, short_label: Option<Label>, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
match element {
|
||||
@@ -186,7 +184,7 @@ impl Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_logical_assign_test(
|
||||
pub(crate) fn emit_logical_assign_test(
|
||||
&self, op: LogicalOperator, test_reg: u8, store_label: Label, end_label: Label, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
match op {
|
||||
@@ -224,7 +222,7 @@ impl Compiler {
|
||||
.ok_or_else(|| format!("private name #{name} is not defined"))
|
||||
}
|
||||
|
||||
fn emit_private_id_reg(&self, name: &str, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
pub(crate) fn emit_private_id_reg(&self, name: &str, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
let id = self.private_name_id(name, ctx)?;
|
||||
let idx = ctx.add_constant(Constant::Int(id as i32));
|
||||
let reg = ctx.alloc_reg();
|
||||
@@ -324,7 +322,7 @@ impl Compiler {
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
|
||||
fn static_property_name(&self, key: &PropertyKey) -> Result<String, String> {
|
||||
pub(crate) fn static_property_name(&self, key: &PropertyKey) -> Result<String, String> {
|
||||
match key {
|
||||
PropertyKey::StaticIdentifier(ident) => Ok(ident.name.as_str().to_string()),
|
||||
PropertyKey::Identifier(ident) => Ok(ident.name.as_str().to_string()),
|
||||
@@ -375,7 +373,9 @@ impl Compiler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_assign_target(&self, target: &AssignmentTarget, src_reg: u8, ctx: &mut CompileCtx) -> Result<(), String> {
|
||||
pub(crate) fn emit_assign_target(
|
||||
&self, target: &AssignmentTarget, src_reg: u8, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
match target {
|
||||
AssignmentTarget::AssignmentTargetIdentifier(id) => {
|
||||
let name = id.name.as_str();
|
||||
@@ -566,7 +566,7 @@ impl Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_array_assignment(
|
||||
pub(crate) fn emit_array_assignment(
|
||||
&self, ap: &oxide_parser::ArrayAssignmentTarget, src_reg: u8, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
ctx.emit(opcode::encode(OpCode::FOR_OF_INIT, 0, src_reg, 0));
|
||||
@@ -587,7 +587,7 @@ impl Compiler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_object_assignment(
|
||||
pub(crate) fn emit_object_assignment(
|
||||
&self, op: &ObjectAssignmentTarget, src_reg: u8, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
let mut excluded = Vec::new();
|
||||
@@ -640,7 +640,7 @@ impl Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
pub(crate) fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
let mut constructor_method = None;
|
||||
let mut instance_fields = Vec::new();
|
||||
let mut private_names = Vec::<(String, u32)>::new();
|
||||
@@ -931,7 +931,7 @@ impl Compiler {
|
||||
Ok(ctor_reg)
|
||||
}
|
||||
|
||||
fn emit_undefined(&self, ctx: &mut CompileCtx) -> u8 {
|
||||
pub(crate) fn emit_undefined(&self, ctx: &mut CompileCtx) -> u8 {
|
||||
let idx = ctx.add_constant(Constant::Undefined);
|
||||
let reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(reg, idx);
|
||||
@@ -966,62 +966,4 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::SET_HOME_OBJECT, method_reg, home_reg, 0));
|
||||
Ok(method_reg)
|
||||
}
|
||||
|
||||
pub(crate) fn emit_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::ExpressionStatement(_) | Statement::ReturnStatement(_) | Statement::EmptyStatement(_) => {
|
||||
self.emit_basic_domain(stmt, ctx)
|
||||
}
|
||||
Statement::BlockStatement(_) => self.emit_block_domain(stmt, ctx),
|
||||
Statement::VariableDeclaration(_) | Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => {
|
||||
self.emit_declaration_domain(stmt, ctx)
|
||||
}
|
||||
Statement::IfStatement(_) => self.emit_control_domain(stmt, ctx),
|
||||
Statement::WhileStatement(_)
|
||||
| Statement::DoWhileStatement(_)
|
||||
| Statement::ForStatement(_)
|
||||
| Statement::ForInStatement(_)
|
||||
| Statement::ForOfStatement(_) => self.emit_iteration_domain(stmt, ctx),
|
||||
Statement::SwitchStatement(_) => self.emit_switch_domain(stmt, ctx),
|
||||
Statement::ThrowStatement(_) | Statement::TryStatement(_) => self.emit_exception_domain(stmt, ctx),
|
||||
Statement::BreakStatement(b) => self.emit_break_statement(b, ctx),
|
||||
Statement::ContinueStatement(c) => self.emit_continue_statement(c, ctx),
|
||||
Statement::LabeledStatement(ls) => self.emit_labeled_statement(ls, ctx),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
pub(crate) fn emit_expression(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
match expr {
|
||||
Expression::NumericLiteral(_)
|
||||
| Expression::StringLiteral(_)
|
||||
| Expression::BooleanLiteral(_)
|
||||
| Expression::NullLiteral(_)
|
||||
| Expression::RegExpLiteral(_) => self.emit_literal(expr, ctx),
|
||||
Expression::BinaryExpression(_)
|
||||
| Expression::PrivateInExpression(_)
|
||||
| Expression::UnaryExpression(_)
|
||||
| Expression::ConditionalExpression(_)
|
||||
| Expression::LogicalExpression(_)
|
||||
| Expression::UpdateExpression(_) => self.emit_operator(expr, ctx),
|
||||
Expression::StaticMemberExpression(_)
|
||||
| Expression::ComputedMemberExpression(_)
|
||||
| Expression::PrivateFieldExpression(_)
|
||||
| Expression::ChainExpression(_) => self.emit_member_domain(expr, ctx),
|
||||
Expression::ObjectExpression(_) | Expression::ArrayExpression(_) => self.emit_object_domain(expr, ctx),
|
||||
Expression::AssignmentExpression(assign) => self.emit_assignment_expression(assign, ctx),
|
||||
Expression::TemplateLiteral(_) | Expression::TaggedTemplateExpression(_) => {
|
||||
self.emit_template_domain(expr, ctx)
|
||||
}
|
||||
Expression::ArrowFunctionExpression(_)
|
||||
| Expression::FunctionExpression(_)
|
||||
| Expression::ClassExpression(_)
|
||||
| Expression::NewExpression(_) => self.emit_function_domain(expr, ctx),
|
||||
Expression::Identifier(ident) => self.emit_identifier_expression(ident, ctx),
|
||||
Expression::CallExpression(_) => self.emit_call_domain(expr, ctx),
|
||||
Expression::ThisExpression(_) => self.emit_this_expression(ctx),
|
||||
Expression::SequenceExpression(seq) => self.emit_sequence_expression(seq, ctx),
|
||||
Expression::ParenthesizedExpression(p) => self.emit_parenthesized_expression(p, ctx),
|
||||
_ => self.emit_unsupported_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_expression_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::ExpressionStatement(es) = stmt else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(self.emit_expression(&es.expression, ctx)?))
|
||||
}
|
||||
|
||||
fn emit_return_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::ReturnStatement(ret) = stmt else {
|
||||
return Ok(None);
|
||||
};
|
||||
match &ret.argument {
|
||||
Some(expr) => {
|
||||
let r = self.emit_expression(expr, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::RETURN, r, 0, 0));
|
||||
}
|
||||
None => {
|
||||
ctx.emit(opcode::encode(OpCode::RETURN, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_break_statement(
|
||||
&self, stmt: &oxide_parser::BreakStatement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
let break_label = if let Some(label) = &stmt.label {
|
||||
let name = label.name.as_str();
|
||||
let scope = ctx
|
||||
.find_label(name)
|
||||
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
|
||||
scope.break_label
|
||||
} else if let Some(sw_label) = ctx.current_switch() {
|
||||
*sw_label
|
||||
} else {
|
||||
let (bl, _) = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
|
||||
*bl
|
||||
};
|
||||
let break_pos = ctx.resolve_label(break_label)?;
|
||||
let offset = (break_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_continue_statement(
|
||||
&self, stmt: &oxide_parser::ContinueStatement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
let continue_label = if let Some(label) = &stmt.label {
|
||||
let name = label.name.as_str();
|
||||
let scope = ctx
|
||||
.find_label(name)
|
||||
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
|
||||
scope.continue_label.ok_or_else(|| {
|
||||
format!("SyntaxError: Illegal continue statement: '{name}' does not denote an iteration statement")
|
||||
})?
|
||||
} else {
|
||||
let (_, cl) = ctx.current_loop().ok_or("continue outside loop".to_string())?;
|
||||
*cl
|
||||
};
|
||||
let continue_pos = ctx.resolve_label(continue_label)?;
|
||||
let offset = (continue_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp(offset));
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_labeled_statement(
|
||||
&self, stmt: &oxide_parser::LabeledStatement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
let name = stmt.label.name.as_str();
|
||||
if Self::is_iteration_statement(&stmt.body) {
|
||||
// Bind the label to the loop's own continue/break targets (the loop
|
||||
// drains the queue and pops the scope after its body).
|
||||
ctx.queue_loop_label(name)?;
|
||||
self.emit_statement(&stmt.body, ctx)?;
|
||||
} else {
|
||||
// Non-loop labeled statement: only `break label` is valid; it targets
|
||||
// the LabeledEnd placed (by the count pass) right after the body.
|
||||
let id = ctx.next_label_id();
|
||||
ctx.push_label_scope(name, Label::LabeledEnd(id), None)?;
|
||||
self.emit_statement(&stmt.body, ctx)?;
|
||||
ctx.pop_label_scope();
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn is_iteration_statement(stmt: &Statement) -> bool {
|
||||
matches!(
|
||||
stmt,
|
||||
Statement::WhileStatement(_)
|
||||
| Statement::DoWhileStatement(_)
|
||||
| Statement::ForStatement(_)
|
||||
| Statement::ForInStatement(_)
|
||||
| Statement::ForOfStatement(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_basic_domain(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::ExpressionStatement(_) => self.emit_expression_statement(stmt, ctx),
|
||||
Statement::ReturnStatement(_) => self.emit_return_statement(stmt, ctx),
|
||||
Statement::EmptyStatement(_) => Ok(None),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_block_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::BlockStatement(block) = stmt else {
|
||||
return Ok(None);
|
||||
};
|
||||
ctx.push_scope();
|
||||
let mut r = None;
|
||||
for s in &block.body {
|
||||
if let Some(rr) = self.emit_statement(s, ctx)? {
|
||||
r = Some(rr);
|
||||
}
|
||||
}
|
||||
ctx.pop_scope();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_block_domain(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::BlockStatement(_) => self.emit_block_statement(stmt, ctx),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_if_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::IfStatement(ifs) = stmt else {
|
||||
return Ok(None);
|
||||
};
|
||||
let id = ctx.next_label_id();
|
||||
let else_label = Label::IfElse(id);
|
||||
let end_label = Label::IfEnd(id);
|
||||
|
||||
let test_reg = self.emit_expression(&ifs.test, ctx)?;
|
||||
|
||||
let else_pos = ctx.resolve_label(else_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_statement(&ifs.consequent, ctx)?;
|
||||
let result_reg = ctx.alloc_reg();
|
||||
if let Some(r) = cons_reg {
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, r, 0));
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.emit_load_const(result_reg, undef_idx);
|
||||
}
|
||||
|
||||
if ifs.alternate.is_some() {
|
||||
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));
|
||||
}
|
||||
|
||||
if let Some(alt) = &ifs.alternate {
|
||||
let alt_reg = self.emit_statement(alt, ctx)?;
|
||||
if let Some(r) = alt_reg {
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, r, 0));
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.emit_load_const(result_reg, undef_idx);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(result_reg))
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_control_domain(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::IfStatement(_) => self.emit_if_statement(stmt, ctx),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
mod basic;
|
||||
mod block;
|
||||
mod control;
|
||||
mod declaration;
|
||||
mod exception;
|
||||
mod iteration;
|
||||
mod switch;
|
||||
@@ -1,57 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_switch_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::SwitchStatement(sw) = stmt else {
|
||||
return Ok(None);
|
||||
};
|
||||
let id = ctx.next_label_id();
|
||||
let end_label = Label::SwitchEnd(id);
|
||||
ctx.push_switch(end_label);
|
||||
|
||||
let disc_reg = self.emit_expression(&sw.discriminant, ctx)?;
|
||||
let compare_reg_checkpoint = ctx.reg_checkpoint();
|
||||
let cases = &sw.cases;
|
||||
|
||||
for (case_idx, case) in cases.iter().enumerate() {
|
||||
if let Some(test) = &case.test {
|
||||
let test_reg = self.emit_expression(test, ctx)?;
|
||||
let eq_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::EQ, eq_reg, disc_reg, test_reg));
|
||||
|
||||
let case_label = Label::SwitchCase(id, case_idx as u32);
|
||||
let body_pos = ctx.resolve_label(case_label)?;
|
||||
let offset = (body_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
let offset = ctx.checked_jump_offset(offset);
|
||||
ctx.emit(opcode::encode_jmp_if_true(eq_reg, offset));
|
||||
ctx.restore_reg_checkpoint(compare_reg_checkpoint);
|
||||
}
|
||||
}
|
||||
|
||||
let has_default = cases.iter().any(|c| c.test.is_none());
|
||||
if !has_default {
|
||||
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));
|
||||
}
|
||||
|
||||
for case in cases.iter() {
|
||||
for s in &case.consequent {
|
||||
self.emit_statement(s, ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.pop_switch();
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(in crate::emitter) fn emit_switch_domain(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::SwitchStatement(_) => self.emit_switch_statement(stmt, ctx),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user