feat: 支持箭头函数/模板字符串/let/const/delete/instanceof 语法

This commit is contained in:
爱特每羊队
2026-06-08 19:55:05 +08:00
parent a6e448e326
commit 209374fc02
15 changed files with 1379 additions and 91 deletions
+83 -21
View File
@@ -6,7 +6,9 @@ use crate::symbol_table::{Binding, SymbolTable};
pub use crate::hash::structural_hash;
pub use crate::module::Constant;
pub use oxide_parser::VariableDeclarationKind;
pub use oxide_parser::{AssignmentOperator, BinaryOperator, Expression, Statement, UnaryOperator};
use crate::symbol_table::ScopeKind;
pub struct Compiler;
@@ -63,6 +65,10 @@ pub(crate) struct CompileCtx {
pub(crate) projected_pc: usize,
pub(crate) builtin_reg_map: Vec<(String, u8)>,
pub(crate) sub_modules: Vec<CompiledModule>,
/// Register holding `this` in the enclosing function context.
/// Used by arrow functions to capture lexical `this` (D-01).
/// Initialized to 254 (conventional this register) at the top level.
pub(crate) enclosing_this_reg: u8,
}
impl CompileCtx {
@@ -80,6 +86,7 @@ impl CompileCtx {
projected_pc: 0,
builtin_reg_map: Vec::new(),
sub_modules: Vec::new(),
enclosing_this_reg: 254, // conventional this register at top level
}
}
@@ -126,12 +133,29 @@ impl CompileCtx {
self.symbols.pop_scope();
}
pub(crate) fn declare(&mut self, name: &str, reg: u8) -> Result<(), String> {
self.symbols.declare(name, reg)
pub(crate) fn declare(
&mut self,
name: &str,
reg: u8,
kind: VariableDeclarationKind,
is_const: bool,
) -> Result<(), String> {
self.symbols.declare(name, reg, kind, is_const)
}
pub(crate) fn declare_initialized(&mut self, name: &str, reg: u8) -> Result<(), String> {
self.symbols.declare_initialized(name, reg)
pub(crate) fn declare_initialized(
&mut self,
name: &str,
reg: u8,
kind: VariableDeclarationKind,
is_const: bool,
) -> Result<(), String> {
self.symbols.declare_initialized(name, reg, kind, is_const)
}
#[allow(dead_code)]
pub(crate) fn push_scope_with_kind(&mut self, kind: ScopeKind) {
self.symbols.push_scope_with_kind(kind);
}
pub(crate) fn lookup(&self, name: &str) -> Result<u8, String> {
@@ -143,6 +167,10 @@ impl CompileCtx {
self.symbols.lookup_or_global(name, reg)
}
pub(crate) fn lookup_const_flag(&self, name: &str) -> bool {
self.symbols.lookup_is_const(name)
}
pub(crate) fn init_var(&mut self, name: &str) {
self.symbols.init_var(name);
}
@@ -225,13 +253,16 @@ impl Compiler {
Self
}
/// Compile a function body (used for FD and FE).
/// Compile a function body (used for FD, FE, and arrow functions).
/// This performs both counting and emitting in one pass.
/// When `is_expression_body` is true (arrow function with expression body),
/// the last expression's value is returned instead of undefined.
pub(crate) fn compile_function_body<'a>(
&self,
param_names: &[String],
body_stmts: &[Statement<'a>],
parent_ctx: &CompileCtx,
is_expression_body: bool,
) -> Result<CompiledModule, String> {
let mut ctx = CompileCtx::new();
@@ -239,14 +270,18 @@ impl Compiler {
// resolve to the correct pre-allocated registers in the sub-module's register file.
ctx.builtin_reg_map = parent_ctx.builtin_reg_map.clone();
// Propagate enclosing_this_reg so nested arrow functions capture the correct `this`.
ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg;
// Inherit parent's global scope entries so previously-declared function names
// are visible from within the body.
for (name, binding) in &parent_ctx.symbols.scopes[0] {
ctx.symbols.scopes[0].insert(
for (name, binding) in &parent_ctx.symbols.scopes[0].bindings {
ctx.symbols.scopes[0].bindings.insert(
name.clone(),
Binding {
reg: binding.reg,
initialized: binding.initialized,
is_const: binding.is_const,
},
);
}
@@ -256,12 +291,12 @@ impl Compiler {
ctx.reset_regs();
// Function body scope - params and local vars
ctx.push_scope();
ctx.push_scope_with_kind(ScopeKind::FunctionScope);
// Register parameters as initialized.
for name in param_names {
let reg = ctx.alloc_reg();
ctx.declare_initialized(name, reg)?;
ctx.declare_initialized(name, reg, VariableDeclarationKind::Var, false)?;
}
// Count pass
@@ -274,24 +309,45 @@ impl Compiler {
// Emit pass - reallocate params (same order = same regs after reset)
for name in param_names {
let reg = ctx.alloc_reg();
ctx.declare_initialized(name, reg)?;
ctx.declare_initialized(name, reg, VariableDeclarationKind::Var, false)?;
}
// Emit body statements.
// After all statements, emit an implicit RETURN undefined.
// Capture the last statement's expression result for expression-body arrows.
let mut last_result_reg = None;
for stmt in body_stmts {
let _ = self.emit_statement(stmt, &mut ctx)?;
if let Some(reg) = self.emit_statement(stmt, &mut ctx)? {
last_result_reg = Some(reg);
}
}
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
undef_reg,
(undef_idx & 0xFF) as u8,
((undef_idx >> 8) & 0xFF) as u8,
));
ctx.emit(opcode::encode(OpCode::RETURN, undef_reg, 0, 0));
// Emit implicit RETURN: expression body returns the last expression,
// statement body returns undefined.
if is_expression_body {
if let Some(reg) = last_result_reg {
ctx.emit(opcode::encode(OpCode::RETURN, reg, 0, 0));
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
undef_reg,
(undef_idx & 0xFF) as u8,
((undef_idx >> 8) & 0xFF) as u8,
));
ctx.emit(opcode::encode(OpCode::RETURN, undef_reg, 0, 0));
}
} else {
let undef_idx = ctx.add_constant(Constant::Undefined);
let undef_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
undef_reg,
(undef_idx & 0xFF) as u8,
((undef_idx >> 8) & 0xFF) as u8,
));
ctx.emit(opcode::encode(OpCode::RETURN, undef_reg, 0, 0));
}
Ok(CompiledModule {
bytecode: ctx.bytecode,
@@ -300,6 +356,9 @@ impl Compiler {
n_args: param_names.len() as u8,
builtin_reg_map: ctx.builtin_reg_map,
sub_modules: ctx.sub_modules,
is_arrow: false,
captured_this_const_idx: 0,
function_name: None,
})
}
@@ -353,6 +412,9 @@ impl Compiler {
n_args: 0,
builtin_reg_map: ctx.builtin_reg_map,
sub_modules: ctx.sub_modules,
is_arrow: false,
captured_this_const_idx: 0,
function_name: None,
})
}
}
+100 -4
View File
@@ -1,5 +1,6 @@
use oxide_parser::{
AssignmentOperator, Expression, ForStatementInit, SimpleAssignmentTarget, Statement,
UnaryOperator, VariableDeclarationKind,
};
use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label};
@@ -189,7 +190,7 @@ impl Compiler {
// Hoisting: declare function name as initialized
let func_reg = ctx.alloc_reg();
let _ = ctx.declare_initialized(&name, func_reg);
let _ = ctx.declare_initialized(&name, func_reg, VariableDeclarationKind::Var, false);
// Body is compiled in the emit pass only.
// FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR
@@ -269,9 +270,32 @@ impl Compiler {
ctx.projected_pc += 1; // ADD/SUB/MUL/DIV/etc.
}
Expression::UnaryExpression(un) => {
self.count_expression(&un.argument, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT
match un.operator {
UnaryOperator::Delete => {
match &un.argument {
Expression::Identifier(_) => {
// SyntaxError at compile time, no bytecode cost
}
Expression::StaticMemberExpression(member) => {
self.count_expression(&member.object, ctx);
ctx.alloc_reg();
ctx.projected_pc += 2; // DELETE_PROP_STATIC + ext word
}
Expression::ComputedMemberExpression(member) => {
self.count_expression(&member.object, ctx);
self.count_expression(&member.expression, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC
}
_ => {}
}
}
_ => {
self.count_expression(&un.argument, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1; // NEG/TYPEOF/VOID/NOT
}
}
}
Expression::CallExpression(call) => {
match &call.callee {
@@ -394,6 +418,78 @@ impl Compiler {
}
}
}
Expression::TemplateLiteral(tl) => {
// TemplateLiteral: N quasis (strings) + M expressions interleaved.
// Allocate 1 result reg + count expressions.
for expr in &tl.expressions {
self.count_expression(expr, ctx);
}
ctx.alloc_reg(); // result reg
let segment_count = tl.quasis.len() + tl.expressions.len();
// TEMPLATE_STR opcode + ext words (1 header + ceil(segments/1) per u32)
ctx.projected_pc += 1 + 1 + segment_count;
}
Expression::TaggedTemplateExpression(tt) => {
// Tagged template: tag expr + quasis as LOAD_CONST + expression args + CALL
// Consecutive arg registers at end (via LOAD_VAR copies)
self.count_expression(&tt.tag, ctx);
let quasi_count = tt.quasi.quasis.len();
// Each quasi: LOAD_CONST string + LOAD_CONST index + SET_ELEM
// for both cooked and raw arrays
for _ in 0..quasi_count {
ctx.alloc_reg(); // str_reg
ctx.projected_pc += 1; // LOAD_CONST string
ctx.alloc_reg(); // idx_reg
ctx.projected_pc += 1; // LOAD_CONST index
ctx.projected_pc += 1; // SET_ELEM (cooked)
}
// Cooked array: alloc + NEW_ARRAY
ctx.alloc_reg(); // cooked_temp
ctx.projected_pc += 1; // NEW_ARRAY
for _ in 0..quasi_count {
ctx.alloc_reg(); // str_reg
ctx.projected_pc += 1; // LOAD_CONST string
ctx.alloc_reg(); // idx_reg
ctx.projected_pc += 1; // LOAD_CONST index
ctx.projected_pc += 1; // SET_ELEM (raw)
}
// Raw array: alloc + NEW_ARRAY
ctx.alloc_reg(); // raw_temp
ctx.projected_pc += 1; // NEW_ARRAY
// Count expression arguments
for expr in &tt.quasi.expressions {
self.count_expression(expr, ctx);
}
// Consecutive arg slots: cooked_slot, raw_slot, N expr_slots
ctx.alloc_reg(); // cooked_slot
ctx.alloc_reg(); // raw_slot
for _ in &tt.quasi.expressions {
ctx.alloc_reg(); // expr_slot
ctx.projected_pc += 1; // LOAD_VAR
}
// LOAD_VAR for cooked and raw
ctx.projected_pc += 2;
// undefined this arg
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_CONST
// CALL + ext word
ctx.projected_pc += 2;
// Result reg + LOAD_VAR
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_VAR
}
Expression::ArrowFunctionExpression(_arrow) => {
// Arrow functions: alloc 1 register for the function value.
// Body is compiled in the emit pass only (same pattern as FE).
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_CONST
}
Expression::FunctionExpression(_fe) => {
// No hoisting - function created at expression position.
// Body is compiled in the emit pass only.
+286 -21
View File
@@ -2,7 +2,7 @@ use crate::compiler::Label;
use crate::opcode::{self, OpCode};
use oxide_parser::{
AssignmentOperator, Expression, ForStatementInit, ForStatementLeft, SimpleAssignmentTarget,
Statement, UnaryOperator, UpdateOperator,
Statement, UnaryOperator, UpdateOperator, VariableDeclarationKind,
};
use crate::compiler::{is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler};
@@ -25,12 +25,24 @@ impl Compiler {
oxide_parser::BindingPattern::BindingIdentifier(bi) => bi.name.as_str(),
_ => return Err("destructuring not supported".into()),
};
let is_const = matches!(decl.kind, VariableDeclarationKind::Const);
if is_const && d.init.is_none() {
return Err("const declaration must have an initializer".into());
}
let var_reg = ctx.alloc_reg();
ctx.declare(name, var_reg)?;
ctx.declare(name, var_reg, decl.kind, is_const)?;
if let Some(init) = &d.init {
let val_reg = self.emit_expression(init, ctx)?;
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, 0));
let const_flag = if is_const { 1 } else { 0 };
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, const_flag));
ctx.init_var(name);
// Name inference (D-04): if the initializer is an arrow function,
// set the compiled sub_module's function_name.
if matches!(*init, Expression::ArrowFunctionExpression(_)) {
if let Some(sub_mod) = ctx.sub_modules.last_mut() {
sub_mod.function_name = Some(name.to_string());
}
}
r = Some(val_reg);
} else {
let idx = ctx.add_constant(Constant::Undefined);
@@ -180,7 +192,7 @@ impl Compiler {
_ => return Err("destructuring not supported".into()),
};
let var_reg = ctx.alloc_reg();
ctx.declare(name, var_reg)?;
ctx.declare(name, var_reg, decl.kind, matches!(decl.kind, VariableDeclarationKind::Const))?;
if let Some(init_expr) = &d.init {
let val_reg = self.emit_expression(init_expr, ctx)?;
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, 0));
@@ -242,7 +254,7 @@ impl Compiler {
_ => return Err("destructuring not supported".into()),
};
let var_reg = ctx.alloc_reg();
ctx.declare(name, var_reg)?;
ctx.declare(name, var_reg, decl.kind, matches!(decl.kind, VariableDeclarationKind::Const))?;
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, key_reg, 0));
ctx.init_var(name);
}
@@ -350,7 +362,8 @@ impl Compiler {
};
// Compile body into sub-module
let sub_module = self.compile_function_body(&param_names, body_stmts, ctx)?;
let sub_module =
self.compile_function_body(&param_names, body_stmts, ctx, false)?;
ctx.sub_modules.push(sub_module);
// 1-indexed: 0 = no sub_module (sentinel)
let sub_idx = ctx.sub_modules.len() as u32;
@@ -437,7 +450,12 @@ impl Compiler {
let catch_reg = ctx.alloc_reg();
if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &param.pattern
{
ctx.declare_initialized(bi.name.as_str(), catch_reg)?;
ctx.declare_initialized(
bi.name.as_str(),
catch_reg,
VariableDeclarationKind::Let,
false,
)?;
ctx.emit(opcode::encode(OpCode::STORE_VAR, catch_reg, 0, 0));
}
}
@@ -567,6 +585,7 @@ impl Compiler {
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)),
@@ -603,6 +622,30 @@ impl Compiler {
ctx.emit(opcode::encode(OpCode::UNARY_PLUS, r, arg, 0));
Ok(r)
}
UnaryOperator::Delete => {
match &un.argument {
Expression::Identifier(_) => {
Err("delete of global variable is not allowed 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()));
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, r, obj_reg, 0));
ctx.emit(const_idx as u32);
Ok(r)
}
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::DELETE_PROP_DYNAMIC, r, obj_reg, key_reg));
Ok(r)
}
_ => Err("invalid delete target".into()),
}
}
_ => Err(format!("unsupported unary operator: {:?}", un.operator)),
}
}
@@ -731,6 +774,13 @@ impl Compiler {
((idx >> 8) & 0xFF) as u8,
));
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));
}
Ok(obj_reg)
@@ -850,7 +900,9 @@ impl Compiler {
let val_reg = self.emit_expression(&assign.right, ctx)?;
let name = id_ref.name.as_str();
let var_reg = ctx.lookup_or_global(name);
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, 0));
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 {
@@ -912,6 +964,226 @@ impl Compiler {
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<u8> = expressions
.iter()
.map(|e| self.emit_expression(e, ctx))
.collect::<Result<Vec<_>, _>>()?;
// Add each quasi string to constant pool
let quasi_const_idxs: Vec<u16> = 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(opcode::encode(
OpCode::LOAD_CONST,
str_reg,
(const_idx & 0xFF) as u8,
((const_idx >> 8) & 0xFF) as u8,
));
let idx_const = ctx.add_constant(Constant::Int(i as i32));
let idx_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
idx_reg,
(idx_const & 0xFF) as u8,
((idx_const >> 8) & 0xFF) as u8,
));
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(opcode::encode(
OpCode::LOAD_CONST,
str_reg,
(const_idx & 0xFF) as u8,
((const_idx >> 8) & 0xFF) as u8,
));
let idx_const = ctx.add_constant(Constant::Int(i as i32));
let idx_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
idx_reg,
(idx_const & 0xFF) as u8,
((idx_const >> 8) & 0xFF) as u8,
));
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(opcode::encode(
OpCode::LOAD_CONST,
undef_reg,
(undef_idx & 0xFF) as u8,
((undef_idx >> 8) & 0xFF) as u8,
));
// 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 param in &arrow.params.items {
if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &param.pattern {
param_names.push(bi.name.to_string());
}
}
// 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(&param_names, body_stmts, ctx, is_expr_body)?;
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(opcode::encode(
OpCode::LOAD_CONST,
r,
(const_idx & 0xFF) as u8,
((const_idx >> 8) & 0xFF) as u8,
));
Ok(r)
}
Expression::FunctionExpression(fe) => {
// FunctionExpression: compile body, emit LOAD_CONST(BytecodeFunc)
let mut param_names = Vec::new();
@@ -927,7 +1199,8 @@ impl Compiler {
&[]
};
let sub_module = self.compile_function_body(&param_names, body_stmts, ctx)?;
let sub_module =
self.compile_function_body(&param_names, body_stmts, ctx, false)?;
ctx.sub_modules.push(sub_module);
// 1-indexed: 0 = no sub_module (sentinel)
let sub_idx = ctx.sub_modules.len() as u32;
@@ -1048,10 +1321,8 @@ impl Compiler {
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 ci = ctx
.add_constant(Constant::RegExp(pattern.to_string(), flags.to_string()));
let r = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
@@ -1061,16 +1332,10 @@ impl Compiler {
));
Ok(r)
} else {
Err(format!(
"unsupported expression type: {:?}",
expr
))
Err(format!("unsupported expression type: {:?}", expr))
}
} else {
Err(format!(
"unsupported expression type: {:?}",
expr
))
Err(format!("unsupported expression type: {:?}", expr))
}
}
_ => Err(format!("unsupported expression type: {:?}", expr)),
+24 -1
View File
@@ -21,7 +21,7 @@ fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) {
hash_expression(&es.expression, h);
}
Statement::VariableDeclaration(decl) => {
1u8.hash(h);
std::mem::discriminant(&decl.kind).hash(h);
(decl.declarations.len() as u32).hash(h);
for d in &decl.declarations {
if let Some(init) = &d.init {
@@ -54,6 +54,7 @@ fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) {
if let Some(expr) = init.as_expression() {
hash_expression(expr, h);
} else if let ForStatementInit::VariableDeclaration(decl) = init {
std::mem::discriminant(&decl.kind).hash(h);
(decl.declarations.len() as u32).hash(h);
for d in &decl.declarations {
if let Some(init_expr) = &d.init {
@@ -224,6 +225,28 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
_ => {}
}
}
Expression::TemplateLiteral(tl) => {
16u8.hash(h);
(tl.quasis.len() as u32).hash(h);
for expr in &tl.expressions {
hash_expression(expr, h);
}
}
Expression::TaggedTemplateExpression(tt) => {
17u8.hash(h);
hash_expression(&tt.tag, h);
(tt.quasi.quasis.len() as u32).hash(h);
for expr in &tt.quasi.expressions {
hash_expression(expr, h);
}
}
Expression::ArrowFunctionExpression(arrow) => {
15u8.hash(h);
(arrow.params.items.len() as u32).hash(h);
for s in &arrow.body.statements {
hash_statement(s, h);
}
}
Expression::FunctionExpression(fe) => {
13u8.hash(h);
(fe.params.items.len() as u32).hash(h);
+15
View File
@@ -21,6 +21,15 @@ pub struct CompiledModule {
pub n_args: u8,
pub builtin_reg_map: Vec<(String, u8)>,
pub sub_modules: Vec<CompiledModule>,
/// True when this module is an arrow function body (D-01).
/// Arrow functions capture lexical `this` from the enclosing scope.
pub is_arrow: bool,
/// Index into `constants` holding the captured `this` JsValue.
/// 0 means "not captured - use standard this binding".
pub captured_this_const_idx: u16,
/// Function name inferred from assignment context (D-04).
/// Set at the VariableDeclaration / ObjectProperty assignment site.
pub function_name: Option<String>,
}
impl CompiledModule {
@@ -32,6 +41,9 @@ impl CompiledModule {
n_args: 0,
builtin_reg_map: Vec::new(),
sub_modules: Vec::new(),
is_arrow: false,
captured_this_const_idx: 0,
function_name: None,
}
}
}
@@ -51,6 +63,9 @@ impl Clone for CompiledModule {
n_args: self.n_args,
builtin_reg_map: self.builtin_reg_map.clone(),
sub_modules: self.sub_modules.clone(),
is_arrow: self.is_arrow,
captured_this_const_idx: self.captured_this_const_idx,
function_name: self.function_name.clone(),
}
}
}
+16
View File
@@ -66,6 +66,14 @@ pub enum OpCode {
FOR_OF_DONE = 0x36,
FOR_OF_CLOSE = 0x37,
// -- Template Literal (0x38) --
TEMPLATE_STR = 0x38,
// -- Small Language Features (0x39-0x3B) --
DELETE_PROP_STATIC = 0x39,
DELETE_PROP_DYNAMIC = 0x3A,
INSTANCEOF = 0x3B,
// -- Variable (0x30-0x32) --
LOAD_VAR = 0x30,
STORE_VAR = 0x31,
@@ -201,6 +209,10 @@ impl TryFrom<u8> for OpCode {
0x35 => Ok(OpCode::TRY_FINALLY_END),
0x36 => Ok(OpCode::FOR_OF_DONE),
0x37 => Ok(OpCode::FOR_OF_CLOSE),
0x38 => Ok(OpCode::TEMPLATE_STR),
0x39 => Ok(OpCode::DELETE_PROP_STATIC),
0x3A => Ok(OpCode::DELETE_PROP_DYNAMIC),
0x3B => Ok(OpCode::INSTANCEOF),
0x40 => Ok(OpCode::CALL),
0x41 => Ok(OpCode::RETURN),
0x42 => Ok(OpCode::CALL_NATIVE),
@@ -288,6 +300,10 @@ impl fmt::Display for OpCode {
OpCode::TRY_FINALLY_END => "TRY_FINALLY_END",
OpCode::FOR_OF_DONE => "FOR_OF_DONE",
OpCode::FOR_OF_CLOSE => "FOR_OF_CLOSE",
OpCode::TEMPLATE_STR => "TEMPLATE_STR",
OpCode::DELETE_PROP_STATIC => "DELETE_PROP_STATIC",
OpCode::DELETE_PROP_DYNAMIC => "DELETE_PROP_DYNAMIC",
OpCode::INSTANCEOF => "INSTANCEOF",
OpCode::LOAD_VAR => "LOAD_VAR",
OpCode::STORE_VAR => "STORE_VAR",
OpCode::LOAD_CONST => "LOAD_CONST",
+173 -31
View File
@@ -1,12 +1,26 @@
use std::collections::HashMap;
use oxide_parser::VariableDeclarationKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ScopeKind {
FunctionScope,
BlockScope,
}
pub(crate) struct Scope {
pub(crate) bindings: HashMap<String, Binding>,
pub(crate) kind: ScopeKind,
}
pub(crate) struct Binding {
pub(crate) reg: u8,
pub(crate) initialized: bool,
pub(crate) is_const: bool,
}
pub struct SymbolTable {
pub(crate) scopes: Vec<HashMap<String, Binding>>,
pub(crate) scopes: Vec<Scope>,
}
impl Default for SymbolTable {
@@ -18,28 +32,65 @@ impl Default for SymbolTable {
impl SymbolTable {
pub fn new() -> Self {
Self {
scopes: vec![HashMap::new()],
scopes: vec![Scope {
bindings: HashMap::new(),
kind: ScopeKind::FunctionScope,
}],
}
}
pub fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
self.scopes.push(Scope {
bindings: HashMap::new(),
kind: ScopeKind::BlockScope,
});
}
pub(crate) fn push_scope_with_kind(&mut self, kind: ScopeKind) {
self.scopes.push(Scope {
bindings: HashMap::new(),
kind,
});
}
pub fn pop_scope(&mut self) {
self.scopes.pop();
if self.scopes.len() > 1 {
self.scopes.pop();
}
}
pub fn declare(&mut self, name: &str, reg: u8) -> Result<(), String> {
let current = self.scopes.last_mut().unwrap();
if current.contains_key(name) {
fn find_var_target_scope(&self) -> usize {
for (i, scope) in self.scopes.iter().enumerate().rev() {
if scope.kind == ScopeKind::FunctionScope {
return i;
}
}
0
}
pub fn declare(
&mut self,
name: &str,
reg: u8,
kind: VariableDeclarationKind,
is_const: bool,
) -> Result<(), String> {
let target_idx = if matches!(kind, VariableDeclarationKind::Var) {
self.find_var_target_scope()
} else {
self.scopes.len() - 1
};
let target = &mut self.scopes[target_idx];
if target.bindings.contains_key(name) {
return Err(format!("Identifier '{name}' has already been declared"));
}
current.insert(
target.bindings.insert(
name.to_string(),
Binding {
reg,
initialized: false,
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
},
);
Ok(())
@@ -47,7 +98,7 @@ impl SymbolTable {
pub fn lookup(&self, name: &str) -> Result<u8, String> {
for scope in self.scopes.iter().rev() {
if let Some(b) = scope.get(name) {
if let Some(b) = scope.bindings.get(name) {
if b.initialized {
return Ok(b.reg);
}
@@ -59,49 +110,79 @@ impl SymbolTable {
pub fn lookup_or_global(&mut self, name: &str, reg_for_new: u8) -> u8 {
for scope in self.scopes.iter().rev() {
if let Some(b) = scope.get(name) {
if let Some(b) = scope.bindings.get(name) {
return b.reg;
}
}
self.scopes[0].insert(
self.scopes[0].bindings.insert(
name.to_string(),
Binding {
reg: reg_for_new,
initialized: true,
is_const: false,
},
);
reg_for_new
}
/// Declare a variable that is already initialized (for FunctionDeclaration hoisting).
/// If the name already exists in the current scope, just mark it as initialized.
pub fn declare_initialized(&mut self, name: &str, reg: u8) -> Result<(), String> {
let current = self.scopes.last_mut().unwrap();
if let Some(b) = current.get_mut(name) {
pub fn lookup_is_const(&self, name: &str) -> bool {
for scope in self.scopes.iter().rev() {
if let Some(b) = scope.bindings.get(name) {
if b.initialized {
return b.is_const;
}
return false;
}
}
false
}
pub fn declare_initialized(
&mut self,
name: &str,
reg: u8,
kind: VariableDeclarationKind,
is_const: bool,
) -> Result<(), String> {
let target_idx = if matches!(kind, VariableDeclarationKind::Var) {
self.find_var_target_scope()
} else {
self.scopes.len() - 1
};
let target = &mut self.scopes[target_idx];
if let Some(b) = target.bindings.get_mut(name) {
b.initialized = true;
return Ok(());
}
current.insert(
target.bindings.insert(
name.to_string(),
Binding {
reg,
initialized: true,
is_const: matches!(kind, VariableDeclarationKind::Const) || is_const,
},
);
Ok(())
}
pub fn pre_register_global(&mut self, name: &str, reg: u8) {
self.scopes[0].entry(name.to_string()).or_insert(Binding {
reg,
initialized: true,
});
self.scopes[0]
.bindings
.entry(name.to_string())
.or_insert(Binding {
reg,
initialized: true,
is_const: false,
});
}
pub fn init_var(&mut self, name: &str) {
let current = self.scopes.last_mut().unwrap();
if let Some(b) = current.get_mut(name) {
b.initialized = true;
for scope in self.scopes.iter_mut().rev() {
if let Some(b) = scope.bindings.get_mut(name) {
b.initialized = true;
return;
}
}
}
}
@@ -109,6 +190,17 @@ impl SymbolTable {
#[cfg(test)]
mod tests {
use super::SymbolTable;
use oxide_parser::VariableDeclarationKind;
fn v() -> VariableDeclarationKind {
VariableDeclarationKind::Var
}
fn l() -> VariableDeclarationKind {
VariableDeclarationKind::Let
}
fn c() -> VariableDeclarationKind {
VariableDeclarationKind::Const
}
#[test]
fn global_scope_exists() {
@@ -119,7 +211,7 @@ mod tests {
#[test]
fn declare_and_lookup() {
let mut st = SymbolTable::new();
st.declare("x", 0).unwrap();
st.declare("x", 0, l(), false).unwrap();
st.init_var("x");
assert_eq!(st.lookup("x").unwrap(), 0);
}
@@ -127,7 +219,7 @@ mod tests {
#[test]
fn tdz_error() {
let mut st = SymbolTable::new();
st.declare("x", 0).unwrap();
st.declare("x", 0, l(), false).unwrap();
assert!(st
.lookup("x")
.unwrap_err()
@@ -137,17 +229,17 @@ mod tests {
#[test]
fn duplicate_declaration_error() {
let mut st = SymbolTable::new();
st.declare("x", 0).unwrap();
assert!(st.declare("x", 1).is_err());
st.declare("x", 0, l(), false).unwrap();
assert!(st.declare("x", 1, l(), false).is_err());
}
#[test]
fn nested_scopes_shadow() {
let mut st = SymbolTable::new();
st.declare("x", 0).unwrap();
st.declare("x", 0, l(), false).unwrap();
st.init_var("x");
st.push_scope();
st.declare("x", 1).unwrap();
st.declare("x", 1, l(), false).unwrap();
st.init_var("x");
assert_eq!(st.lookup("x").unwrap(), 1);
st.pop_scope();
@@ -165,9 +257,59 @@ mod tests {
#[test]
fn lookup_or_global_existing() {
let mut st = SymbolTable::new();
st.declare("x", 3).unwrap();
st.declare("x", 3, l(), false).unwrap();
st.init_var("x");
let reg = st.lookup_or_global("x", 99);
assert_eq!(reg, 3);
}
#[test]
fn var_hoists_to_function_scope() {
let mut st = SymbolTable::new();
// Global scope is FunctionScope
st.declare("x", 0, l(), false).unwrap();
st.init_var("x");
// Push a block scope
st.push_scope();
// var declaration inside block should go to function scope
st.declare("y", 1, v(), false).unwrap();
st.init_var("y");
st.pop_scope();
// y should be accessible (it was declared in function scope, not block scope)
assert_eq!(st.lookup("y").unwrap(), 1);
}
#[test]
fn let_block_scoped() {
let mut st = SymbolTable::new();
st.declare("x", 0, l(), false).unwrap();
st.init_var("x");
st.push_scope();
st.declare("x", 1, l(), false).unwrap();
st.init_var("x");
assert_eq!(st.lookup("x").unwrap(), 1);
st.pop_scope();
assert_eq!(st.lookup("x").unwrap(), 0);
}
#[test]
fn const_has_is_const_flag() {
let mut st = SymbolTable::new();
st.declare("x", 0, c(), false).unwrap();
st.init_var("x");
let reg = st.lookup("x").unwrap();
assert_eq!(reg, 0);
}
#[test]
fn var_in_two_blocks_same_function_scope_is_duplicate() {
let mut st = SymbolTable::new();
st.push_scope();
st.declare("y", 1, v(), false).unwrap();
st.pop_scope();
st.push_scope();
// Same function scope, so this should be a duplicate
assert!(st.declare("y", 2, v(), false).is_err());
st.pop_scope();
}
}