refactor(compiler): 拆分 oxide_emit 为独立 emit pass crate(依赖倒置)
This commit is contained in:
Generated
+10
@@ -1111,6 +1111,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_code_cache",
|
||||
"oxide_emit",
|
||||
"oxide_ir",
|
||||
"oxide_log",
|
||||
"oxide_parser",
|
||||
@@ -1119,6 +1120,15 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_ir",
|
||||
"oxide_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -6,6 +6,7 @@ members = [
|
||||
"crates/oxide_code_cache",
|
||||
"crates/oxide_log",
|
||||
"crates/oxide_compiler",
|
||||
"crates/oxide_emit",
|
||||
"crates/oxide_ir",
|
||||
"crates/oxide_types",
|
||||
"crates/oxide_vm",
|
||||
@@ -31,6 +32,7 @@ oxc_span = "0.134.0"
|
||||
oxc_diagnostics = "0.134.0"
|
||||
oxc_syntax = "0.134.0"
|
||||
oxide_ir = { path = "crates/oxide_ir" }
|
||||
oxide_emit = { path = "crates/oxide_emit" }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
rustyline = "14"
|
||||
ansi_term = "0.12"
|
||||
|
||||
@@ -11,6 +11,7 @@ oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
oxide_log = { path = "../oxide_log" }
|
||||
oxide_ir.workspace = true
|
||||
oxide_emit.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
|
||||
@@ -1,900 +1,20 @@
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
use crate::emit_ctx::{LabelCtx, ScopeCtx};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::lower::lower;
|
||||
use oxide_ir::operand::{LabelId, Operand};
|
||||
use oxide_ir::IRFunction;
|
||||
use crate::symbol_table::{Binding, SymbolTable};
|
||||
use oxide_bytecode::module::CompiledModule;
|
||||
use oxide_bytecode::module::UpvalueCapture;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_emit::Emitter;
|
||||
use oxide_ir::lower::lower;
|
||||
|
||||
pub use crate::hash::{compiled_module_hash, structural_hash};
|
||||
use crate::symbol_table::ScopeKind;
|
||||
pub use oxide_bytecode::module::Constant;
|
||||
pub use oxide_parser::VariableDeclarationKind;
|
||||
pub use oxide_parser::{AssignmentOperator, BinaryOperator, Expression, Statement, UnaryOperator};
|
||||
|
||||
pub struct Compiler;
|
||||
|
||||
pub(crate) fn is_int_literal(value: f64) -> bool {
|
||||
value.fract() == 0.0 && value >= i32::MIN as f64 && value <= i32::MAX as f64
|
||||
}
|
||||
|
||||
pub(crate) fn is_side_effect_free(expr: &Expression) -> bool {
|
||||
let mut stack = vec![expr];
|
||||
while let Some(expr) = stack.pop() {
|
||||
match expr {
|
||||
Expression::NumericLiteral(_)
|
||||
| Expression::StringLiteral(_)
|
||||
| Expression::BooleanLiteral(_)
|
||||
| Expression::NullLiteral(_)
|
||||
| Expression::Identifier(_)
|
||||
| Expression::RegExpLiteral(_)
|
||||
| Expression::ThisExpression(_) => {}
|
||||
Expression::ParenthesizedExpression(p) => stack.push(&p.expression),
|
||||
Expression::BinaryExpression(bin) => {
|
||||
stack.push(&bin.left);
|
||||
stack.push(&bin.right);
|
||||
}
|
||||
Expression::UnaryExpression(un) if !matches!(un.operator, UnaryOperator::Delete) => {
|
||||
stack.push(&un.argument);
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
const BUILTIN_GLOBALS: &[&str] = &[
|
||||
"NaN",
|
||||
"undefined",
|
||||
"Infinity",
|
||||
"globalThis",
|
||||
"Object",
|
||||
"Array",
|
||||
"String",
|
||||
"Number",
|
||||
"Boolean",
|
||||
"Function",
|
||||
"Error",
|
||||
"TypeError",
|
||||
"ReferenceError",
|
||||
"RangeError",
|
||||
"SyntaxError",
|
||||
"URIError",
|
||||
"EvalError",
|
||||
"Math",
|
||||
"JSON",
|
||||
"Promise",
|
||||
"Date",
|
||||
"Set",
|
||||
"Map",
|
||||
"RegExp",
|
||||
"Symbol",
|
||||
"parseInt",
|
||||
"parseFloat",
|
||||
"isNaN",
|
||||
"isFinite",
|
||||
"Proxy",
|
||||
"WeakMap",
|
||||
"WeakSet",
|
||||
"WeakRef",
|
||||
"FinalizationRegistry",
|
||||
"Atomics",
|
||||
"SharedArrayBuffer",
|
||||
"ArrayBuffer",
|
||||
"DataView",
|
||||
"Iterator",
|
||||
"BigInt",
|
||||
"Int8Array",
|
||||
"Uint8Array",
|
||||
"Uint8ClampedArray",
|
||||
"Int16Array",
|
||||
"Uint16Array",
|
||||
"Int32Array",
|
||||
"Uint32Array",
|
||||
"Float32Array",
|
||||
"Float64Array",
|
||||
"BigInt64Array",
|
||||
"BigUint64Array",
|
||||
"Reflect",
|
||||
"escape",
|
||||
"unescape",
|
||||
"encodeURI",
|
||||
"decodeURI",
|
||||
"encodeURIComponent",
|
||||
"decodeURIComponent",
|
||||
];
|
||||
|
||||
pub(crate) struct FieldBuffer {
|
||||
pub(crate) insts: Vec<Inst>,
|
||||
pub(crate) labels: Vec<(LabelId, usize)>,
|
||||
}
|
||||
|
||||
/// A labeled-statement scope active during emission. `break label` targets
|
||||
/// `break_label`; `continue label` targets `continue_label` (only set when the
|
||||
/// labeled statement directly wraps an iteration statement).
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct LabelScope {
|
||||
pub(crate) name: String,
|
||||
pub(crate) break_label: LabelId,
|
||||
pub(crate) continue_label: Option<LabelId>,
|
||||
}
|
||||
|
||||
pub(crate) struct CompileCtx {
|
||||
pub(crate) insts: Vec<Inst>,
|
||||
pub(crate) constants: Vec<Constant>,
|
||||
constant_map: HashMap<ConstantKey, u16>,
|
||||
next_reg: u8,
|
||||
pub(crate) max_regs: u8,
|
||||
reserved_reg_start: u8,
|
||||
pub(crate) labels: LabelCtx,
|
||||
pub(crate) scopes: ScopeCtx,
|
||||
pub(crate) nested: Vec<IRFunction>,
|
||||
/// Register holding `this` in the enclosing function context.
|
||||
/// Used by arrow functions to capture lexical `this`.
|
||||
/// Initialized to 254 (conventional this register) at the top level.
|
||||
pub(crate) enclosing_this_reg: u8,
|
||||
pub(crate) in_derived_constructor: bool,
|
||||
pub(crate) in_instance_method: bool,
|
||||
pub(crate) in_static_method: bool,
|
||||
pub(crate) static_block_this_reg: Option<u8>,
|
||||
pub(crate) field_buffer: Option<FieldBuffer>,
|
||||
pub(crate) current_upvalue_captures: Vec<UpvalueCapture>,
|
||||
/// 本函数作用域声明的绑定名(参数 + 变量/函数声明,AST 收集,emit 前确定)。
|
||||
pub(crate) own_bindings: HashSet<String>,
|
||||
/// 本函数被嵌套函数捕获的绑定名 → cell_idx(名字排序分配,稳定跨 run)。
|
||||
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
|
||||
/// 消除符号表时序依赖与 cell 索引错位。
|
||||
pub(crate) captured_bindings: BTreeMap<String, u8>,
|
||||
/// Set when alloc_reg() overflows into the reserved this/new.target range (≥254).
|
||||
/// Checked after each emit phase to produce a compile error rather than silent corruption.
|
||||
pub(crate) reg_overflow: bool,
|
||||
pub(crate) const_overflow: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum FunctionBodyContext {
|
||||
Ordinary,
|
||||
Arrow,
|
||||
ClassElement,
|
||||
}
|
||||
|
||||
pub(crate) enum ParamSpec<'a> {
|
||||
Identifier(String),
|
||||
Pattern {
|
||||
synthetic_name: String,
|
||||
pattern: &'a oxide_parser::BindingPattern<'a>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ParamSpec<'_> {
|
||||
pub(crate) fn register_name(&self) -> &str {
|
||||
match self {
|
||||
Self::Identifier(name) => name,
|
||||
Self::Pattern { synthetic_name, .. } => synthetic_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum ConstantKey {
|
||||
Number(u64),
|
||||
Int(i32),
|
||||
String(String),
|
||||
Boolean(bool),
|
||||
Null,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
impl CompileCtx {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
insts: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
constant_map: HashMap::new(),
|
||||
next_reg: 1,
|
||||
max_regs: 1,
|
||||
reserved_reg_start: 1,
|
||||
labels: LabelCtx {
|
||||
label_pos: Vec::new(),
|
||||
loop_stack: Vec::new(),
|
||||
switch_stack: Vec::new(),
|
||||
label_scopes: Vec::new(),
|
||||
pending_loop_labels: Vec::new(),
|
||||
label_counter: 0,
|
||||
},
|
||||
scopes: ScopeCtx {
|
||||
symbols: SymbolTable::new(),
|
||||
builtin_reg_map: Vec::new(),
|
||||
private_name_map: Vec::new(),
|
||||
next_private_name_id: 1,
|
||||
},
|
||||
nested: Vec::new(),
|
||||
enclosing_this_reg: 254, // conventional this register at top level
|
||||
in_derived_constructor: false,
|
||||
in_instance_method: false,
|
||||
in_static_method: false,
|
||||
static_block_this_reg: None,
|
||||
field_buffer: None,
|
||||
current_upvalue_captures: Vec::new(),
|
||||
own_bindings: HashSet::new(),
|
||||
captured_bindings: BTreeMap::new(),
|
||||
reg_overflow: false,
|
||||
const_overflow: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inst(&mut self, inst: Inst) {
|
||||
self.insts.push(inst);
|
||||
}
|
||||
|
||||
pub(crate) fn alloc_reg(&mut self) -> u8 {
|
||||
let r = self.next_reg;
|
||||
// Registers 254 (this) and 255 (new.target) are reserved by the VM.
|
||||
// Allocating into them silently corrupts the call convention, turning
|
||||
// method calls' `this` into garbage. Clamp to 253 and set a flag so
|
||||
// the compiler can surface a proper error after the emit pass.
|
||||
if r >= 254 {
|
||||
self.reg_overflow = true;
|
||||
return 253; // clamp to last safe register; emit continues but reg_overflow triggers error
|
||||
}
|
||||
self.next_reg = self.next_reg.wrapping_add(1);
|
||||
if self.next_reg > self.max_regs {
|
||||
self.max_regs = self.next_reg;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
pub(crate) fn reset_regs(&mut self) {
|
||||
self.next_reg = self.builtin_reg_floor().max(self.reserved_reg_start);
|
||||
self.labels.label_counter = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn reg_checkpoint(&self) -> u8 {
|
||||
self.next_reg
|
||||
}
|
||||
|
||||
pub(crate) fn restore_reg_checkpoint(&mut self, checkpoint: u8) {
|
||||
self.next_reg = checkpoint;
|
||||
}
|
||||
|
||||
pub(crate) fn reserve_reg(&mut self, reg: u8) {
|
||||
let next = reg.wrapping_add(1);
|
||||
if self.next_reg <= reg {
|
||||
self.next_reg = next;
|
||||
}
|
||||
if self.max_regs < next {
|
||||
self.max_regs = next;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_constant(&mut self, c: Constant) -> u16 {
|
||||
if let Some(key) = ConstantKey::from_constant(&c) {
|
||||
if let Some(&idx) = self.constant_map.get(&key) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
if self.constants.len() >= u16::MAX as usize {
|
||||
self.const_overflow = true;
|
||||
return u16::MAX;
|
||||
}
|
||||
let idx = self.constants.len() as u16;
|
||||
self.constants.push(c);
|
||||
self.constant_map.insert(key, idx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
let idx = self.constants.len();
|
||||
if idx >= u16::MAX as usize {
|
||||
self.const_overflow = true;
|
||||
return u16::MAX;
|
||||
}
|
||||
self.constants.push(c);
|
||||
idx as u16
|
||||
}
|
||||
|
||||
pub(crate) fn push_scope(&mut self) {
|
||||
self.scopes.symbols.push_scope();
|
||||
}
|
||||
|
||||
pub(crate) fn pop_scope(&mut self) {
|
||||
self.scopes.symbols.pop_scope();
|
||||
}
|
||||
|
||||
pub(crate) fn declare(
|
||||
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
|
||||
) -> Result<(), String> {
|
||||
self.scopes.symbols.declare(name, reg, kind, is_const)
|
||||
}
|
||||
|
||||
pub(crate) fn declare_initialized(
|
||||
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
|
||||
) -> Result<(), String> {
|
||||
self.scopes.symbols.declare_initialized(name, reg, kind, is_const)
|
||||
}
|
||||
|
||||
pub(crate) fn push_scope_with_kind(&mut self, kind: ScopeKind) {
|
||||
self.scopes.symbols.push_scope_with_kind(kind);
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, name: &str) -> Result<u8, String> {
|
||||
self.scopes.symbols.lookup(name)
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_or_builtin(&mut self, name: &str) -> Result<u8, String> {
|
||||
match self.scopes.symbols.lookup(name) {
|
||||
Ok(reg) => Ok(reg),
|
||||
Err(err) if Self::is_known_builtin(name) && err.contains("is not defined") => {
|
||||
let reg = self.alloc_reg();
|
||||
self.scopes.symbols.pre_register_global(name, reg);
|
||||
self.scopes.builtin_reg_map.push((name.to_string(), reg));
|
||||
Ok(reg)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_or_global(&mut self, name: &str) -> u8 {
|
||||
if let Some(reg) = self.scopes.symbols.lookup_any(name) {
|
||||
return reg;
|
||||
}
|
||||
let reg = self.alloc_reg();
|
||||
self.scopes.symbols.lookup_or_global(name, reg)
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_const_flag(&self, name: &str) -> bool {
|
||||
self.scopes.symbols.lookup_is_const(name)
|
||||
}
|
||||
|
||||
pub(crate) fn init_var(&mut self, name: &str) {
|
||||
self.scopes.symbols.init_var(name);
|
||||
}
|
||||
|
||||
pub(crate) fn next_label_id(&mut self) -> u32 {
|
||||
let id = self.labels.label_counter;
|
||||
self.labels.label_counter += 1;
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn push_loop(&mut self, break_label: LabelId, continue_label: LabelId) {
|
||||
self.labels.loop_stack.push((break_label, continue_label));
|
||||
}
|
||||
|
||||
pub(crate) fn pop_loop(&mut self) {
|
||||
self.labels.loop_stack.pop();
|
||||
}
|
||||
|
||||
pub(crate) fn current_loop(&self) -> Option<&(LabelId, LabelId)> {
|
||||
self.labels.loop_stack.last()
|
||||
}
|
||||
|
||||
pub(crate) fn push_switch(&mut self, break_label: LabelId) {
|
||||
self.labels.switch_stack.push(break_label);
|
||||
}
|
||||
|
||||
pub(crate) fn pop_switch(&mut self) {
|
||||
self.labels.switch_stack.pop();
|
||||
}
|
||||
|
||||
pub(crate) fn current_switch(&self) -> Option<&LabelId> {
|
||||
self.labels.switch_stack.last()
|
||||
}
|
||||
|
||||
pub(crate) fn push_label_scope(
|
||||
&mut self, name: &str, break_label: LabelId, continue_label: Option<LabelId>,
|
||||
) -> Result<(), String> {
|
||||
if self.labels.label_scopes.iter().any(|s| s.name == name) {
|
||||
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
|
||||
}
|
||||
self.labels.label_scopes.push(LabelScope {
|
||||
name: name.to_string(),
|
||||
break_label,
|
||||
continue_label,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn pop_label_scope(&mut self) {
|
||||
self.labels.label_scopes.pop();
|
||||
}
|
||||
|
||||
pub(crate) fn find_label(&self, name: &str) -> Option<&LabelScope> {
|
||||
self.labels.label_scopes.iter().rev().find(|s| s.name == name)
|
||||
}
|
||||
|
||||
/// Queue a label name to be bound to the continue target of the next loop
|
||||
/// emitted as the labeled statement's body. Rejects duplicates in the active
|
||||
/// or pending sets.
|
||||
pub(crate) fn queue_loop_label(&mut self, name: &str) -> Result<(), String> {
|
||||
if self.labels.label_scopes.iter().any(|s| s.name == name)
|
||||
|| self.labels.pending_loop_labels.iter().any(|n| n == name)
|
||||
{
|
||||
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
|
||||
}
|
||||
self.labels.pending_loop_labels.push(name.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Drain queued loop labels into active scopes bound to this loop's break and
|
||||
/// continue targets. Returns how many scopes were pushed (to pop after).
|
||||
pub(crate) fn take_pending_loop_labels(&mut self, break_label: LabelId, continue_label: LabelId) -> usize {
|
||||
let names = std::mem::take(&mut self.labels.pending_loop_labels);
|
||||
let count = names.len();
|
||||
for name in names {
|
||||
self.labels.label_scopes.push(LabelScope {
|
||||
name,
|
||||
break_label,
|
||||
continue_label: Some(continue_label),
|
||||
});
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
pub(crate) fn pop_label_scopes(&mut self, n: usize) {
|
||||
for _ in 0..n {
|
||||
self.labels.label_scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_builtin(&self, name: &str) -> bool {
|
||||
self.scopes.builtin_reg_map.iter().any(|(n, _)| n == name)
|
||||
}
|
||||
|
||||
pub(crate) fn is_known_builtin(name: &str) -> bool {
|
||||
BUILTIN_GLOBALS.contains(&name)
|
||||
}
|
||||
|
||||
fn builtin_reg_floor(&self) -> u8 {
|
||||
self.scopes
|
||||
.builtin_reg_map
|
||||
.iter()
|
||||
.map(|(_, reg)| reg.saturating_add(1))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub(crate) fn pre_register_builtins(&mut self) {
|
||||
// Builtin globals are resolved lazily by lookup_or_builtin(). Keeping this
|
||||
// hook preserves the compile pipeline without reserving ~60 registers in
|
||||
// every module.
|
||||
}
|
||||
|
||||
/// 组装 IRFunction(两出口共用),take 走编译产物状态。
|
||||
/// `parent_ctx` 用于补全 upvalue_captures 的 enclosing_reg(父符号表在父 emit 完成后完整)。
|
||||
fn assemble_ir(&mut self, param_layout: oxide_ir::ParamLayout, parent_ctx: Option<&CompileCtx>) -> IRFunction {
|
||||
let upvalue_captures = self
|
||||
.current_upvalue_captures
|
||||
.iter()
|
||||
.map(|u| {
|
||||
let enclosing_reg = parent_ctx
|
||||
.and_then(|p| p.scopes.symbols.lookup_any(u.name.as_str()))
|
||||
.unwrap_or(0);
|
||||
UpvalueCapture {
|
||||
name: u.name.clone(),
|
||||
enclosing_reg,
|
||||
cell_idx: u.cell_idx,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
IRFunction {
|
||||
insts: std::mem::take(&mut self.insts),
|
||||
label_pos: std::mem::take(&mut self.labels.label_pos),
|
||||
label_count: self.labels.label_counter,
|
||||
constants: std::mem::take(&mut self.constants),
|
||||
param_layout,
|
||||
builtin_reg_map: std::mem::take(&mut self.scopes.builtin_reg_map),
|
||||
upvalue_captures,
|
||||
cells_needed: self.captured_bindings.len() as u8,
|
||||
n_registers: self.max_regs,
|
||||
is_arrow: false,
|
||||
is_class_constructor: false,
|
||||
is_derived_constructor: false,
|
||||
needs_home_object: false,
|
||||
captured_this_const_idx: 0,
|
||||
function_name: None,
|
||||
reg_overflow: self.reg_overflow,
|
||||
const_overflow: self.const_overflow,
|
||||
nested: std::mem::take(&mut self.nested),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantKey {
|
||||
fn from_constant(value: &Constant) -> Option<Self> {
|
||||
match value {
|
||||
Constant::Number(v) => Some(Self::Number(v.to_bits())),
|
||||
Constant::Int(v) => Some(Self::Int(*v)),
|
||||
Constant::String(v) => Some(Self::String(v.clone())),
|
||||
Constant::Boolean(v) => Some(Self::Boolean(*v)),
|
||||
Constant::Null => Some(Self::Null),
|
||||
Constant::Undefined => Some(Self::Undefined),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
// ── 闭包捕获分析(AST 级,时序无关)──
|
||||
|
||||
/// 收集函数参数的绑定名(BindingIdentifier 形态)。
|
||||
|
||||
|
||||
pub(crate) fn extract_function_parts<'a>(
|
||||
&self, function: &'a oxide_parser::Function<'a>,
|
||||
) -> Result<(Vec<ParamSpec<'a>>, &'a [Statement<'a>]), String> {
|
||||
let mut param_specs = Vec::new();
|
||||
for (idx, param) in function.params.items.iter().enumerate() {
|
||||
match ¶m.pattern {
|
||||
oxide_parser::BindingPattern::BindingIdentifier(bi) => {
|
||||
param_specs.push(ParamSpec::Identifier(bi.name.to_string()));
|
||||
}
|
||||
pattern => {
|
||||
param_specs.push(ParamSpec::Pattern {
|
||||
synthetic_name: format!("@@param_{idx}"),
|
||||
pattern,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let body_stmts: &[Statement] = if let Some(body) = &function.body { &body.statements } else { &[] };
|
||||
Ok((param_specs, body_stmts))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// `is_arrow` controls whether super flags are inherited from the parent scope
|
||||
/// (true for arrow functions, which have lexical super) or reset to false
|
||||
/// (false for regular functions, which create a new super scope).
|
||||
pub(crate) fn compile_function_body<'a>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
is_expression_body: bool, is_arrow: bool,
|
||||
) -> Result<IRFunction, String> {
|
||||
let body_context = if is_arrow {
|
||||
FunctionBodyContext::Arrow
|
||||
} else {
|
||||
FunctionBodyContext::Ordinary
|
||||
};
|
||||
self.compile_function_body_with_bindings(
|
||||
param_specs,
|
||||
body_stmts,
|
||||
parent_ctx,
|
||||
is_expression_body,
|
||||
&[],
|
||||
body_context,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn compile_function_body_with_bindings<'a>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext,
|
||||
) -> Result<IRFunction, String> {
|
||||
self.compile_function_body_with_field_hooks(
|
||||
param_specs,
|
||||
body_stmts,
|
||||
parent_ctx,
|
||||
is_expression_body,
|
||||
extra_bindings,
|
||||
body_context,
|
||||
None::<fn(&Compiler, &mut CompileCtx) -> Result<(), String>>,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/// Pre-register all builtin identifiers referenced anywhere in this body
|
||||
/// (expressions, member objects, call args, class fields, etc.) so their
|
||||
/// register slots are reserved *before* any temporary register is emitted.
|
||||
///
|
||||
/// Without this, single-pass emission lazily allocates builtin slots via
|
||||
/// `lookup_or_builtin` at first use, which can collide with a temporary
|
||||
/// register already reused by `restore_reg_checkpoint`. Pre-scanning moves
|
||||
/// builtin slot allocation ahead of the temporary register pool.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compile_function_body_with_field_hooks<'a, E>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext,
|
||||
mut emit_fields: Option<E>, fields_after_super: bool,
|
||||
) -> Result<IRFunction, String>
|
||||
where
|
||||
E: FnMut(&Compiler, &mut CompileCtx) -> Result<(), String>,
|
||||
{
|
||||
let mut ctx = CompileCtx::new();
|
||||
|
||||
// Inherit parent's builtin_reg_map so builtin identifiers (Math, Object, etc.)
|
||||
// resolve to the correct pre-allocated registers in the sub-module's register file.
|
||||
ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone();
|
||||
ctx.scopes.private_name_map = parent_ctx.scopes.private_name_map.clone();
|
||||
ctx.scopes.next_private_name_id = parent_ctx.scopes.next_private_name_id;
|
||||
|
||||
// Propagate enclosing_this_reg so nested arrow functions capture the correct `this`.
|
||||
ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg;
|
||||
// Arrow functions inherit lexical super. Class method bodies also need the
|
||||
// class-provided super context for their top-level body compilation.
|
||||
if matches!(body_context, FunctionBodyContext::Arrow | FunctionBodyContext::ClassElement) {
|
||||
ctx.in_derived_constructor = parent_ctx.in_derived_constructor;
|
||||
ctx.in_instance_method = parent_ctx.in_instance_method;
|
||||
ctx.in_static_method = parent_ctx.in_static_method;
|
||||
} else {
|
||||
ctx.in_derived_constructor = false;
|
||||
ctx.in_instance_method = false;
|
||||
ctx.in_static_method = false;
|
||||
}
|
||||
|
||||
// Inherit parent's global scope entries so previously-declared function names
|
||||
// are visible from within the body.
|
||||
let mut inherited_reg_start = 1u8.max(ctx.builtin_reg_floor());
|
||||
for (name, binding) in &parent_ctx.scopes.symbols.scopes[0].bindings {
|
||||
ctx.scopes.symbols.scopes[0].bindings.insert(
|
||||
name.clone(),
|
||||
Binding {
|
||||
reg: binding.reg,
|
||||
initialized: binding.initialized,
|
||||
is_const: binding.is_const,
|
||||
},
|
||||
);
|
||||
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
|
||||
}
|
||||
for (name, reg) in extra_bindings {
|
||||
ctx.scopes.symbols.scopes[0].bindings.insert(
|
||||
(*name).to_string(),
|
||||
Binding {
|
||||
reg: *reg,
|
||||
initialized: true,
|
||||
is_const: true,
|
||||
},
|
||||
);
|
||||
inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1));
|
||||
}
|
||||
ctx.reserved_reg_start = inherited_reg_start.max(1);
|
||||
|
||||
// Align next_reg with builtin count so both count and emit passes start at the
|
||||
// same register offset (params go after builtin slots).
|
||||
ctx.reset_regs();
|
||||
|
||||
let param_base = self.emit_params_prologue(param_specs, body_stmts, parent_ctx, &mut ctx, body_context)?;
|
||||
|
||||
self.predeclare_function_declarations(body_stmts, &mut ctx);
|
||||
|
||||
// Pre-register builtin identifier references before emitting any temporary
|
||||
// register, so builtin slots never collide with reused temporaries.
|
||||
self.pre_register_builtin_references(body_stmts, &mut ctx);
|
||||
|
||||
// Pre-declare `var` names so hoisted function declarations (emitted in the
|
||||
// first sub-pass below) can resolve the outer vars they close over.
|
||||
self.predeclare_var_declarations(body_stmts, &mut ctx);
|
||||
|
||||
if let Some(emit) = emit_fields.as_mut() {
|
||||
if fields_after_super {
|
||||
let mut parent_insts = Vec::new();
|
||||
let mut parent_label_pos = Vec::new();
|
||||
std::mem::swap(&mut ctx.insts, &mut parent_insts);
|
||||
std::mem::swap(&mut ctx.labels.label_pos, &mut parent_label_pos);
|
||||
emit(self, &mut ctx)?;
|
||||
let field_buffer = FieldBuffer {
|
||||
insts: std::mem::take(&mut ctx.insts),
|
||||
labels: std::mem::take(&mut ctx.labels.label_pos)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(id, pos)| pos.map(|p| (id as LabelId, p)))
|
||||
.collect(),
|
||||
};
|
||||
ctx.insts = parent_insts;
|
||||
ctx.labels.label_pos = parent_label_pos;
|
||||
ctx.field_buffer = Some(field_buffer);
|
||||
} else {
|
||||
emit(self, &mut ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Emit body statements(先函数声明 hoisting,再其余)。
|
||||
let last_result_reg = self.emit_body_stmts(body_stmts, &mut ctx)?;
|
||||
|
||||
// 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.inst(Inst::new(OpCode::RETURN, Operand::Reg(reg as u32), Operand::None, Operand::None));
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
let undef_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(undef_reg as u32), undef_idx));
|
||||
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg as u32), Operand::None, Operand::None));
|
||||
}
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
let undef_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(undef_reg as u32), undef_idx));
|
||||
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg as u32), Operand::None, Operand::None));
|
||||
}
|
||||
|
||||
let ir = ctx.assemble_ir(
|
||||
oxide_ir::ParamLayout {
|
||||
base: param_base as u32,
|
||||
count: param_specs.len() as u32,
|
||||
},
|
||||
Some(parent_ctx),
|
||||
);
|
||||
Ok(ir)
|
||||
}
|
||||
|
||||
/// 参数 prologue:函数作用域 + 参数声明/解构 + 闭包捕获与 upvalue 分析。返回 param_base。
|
||||
fn emit_params_prologue<'a>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
ctx: &mut CompileCtx, body_context: FunctionBodyContext,
|
||||
) -> Result<u8, String> {
|
||||
ctx.push_scope_with_kind(ScopeKind::FunctionScope);
|
||||
let param_base = ctx.next_reg;
|
||||
|
||||
// Emit parameters and destructuring prologue.
|
||||
for spec in param_specs {
|
||||
let name = spec.register_name();
|
||||
let reg = ctx.alloc_reg();
|
||||
ctx.declare_initialized(name, reg, VariableDeclarationKind::Var, false)?;
|
||||
}
|
||||
|
||||
for spec in param_specs {
|
||||
if let ParamSpec::Pattern { synthetic_name, pattern } = spec {
|
||||
let src_reg = ctx.lookup(synthetic_name)?;
|
||||
self.emit_binding_pattern(pattern, src_reg, VariableDeclarationKind::Var, false, ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 闭包捕获分析(AST 级,emit 前确定,时序无关)
|
||||
let param_names: Vec<&str> = param_specs.iter().map(|s| s.register_name()).collect();
|
||||
ctx.own_bindings = self.collect_own_binding_names(¶m_names, body_stmts);
|
||||
ctx.captured_bindings = self.collect_captured_bindings(body_stmts, &ctx.own_bindings);
|
||||
|
||||
// Free variable analysis for upvalue capture (Ordinary + Arrow functions only)
|
||||
if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) {
|
||||
ctx.current_upvalue_captures =
|
||||
self.collect_upvalue_names(body_stmts, &parent_ctx.captured_bindings, &ctx.own_bindings);
|
||||
}
|
||||
|
||||
Ok(param_base)
|
||||
}
|
||||
|
||||
/// 双 sub-pass emit body:先函数声明(hoisting),再其余语句。返回最后结果寄存器。
|
||||
fn emit_body_stmts(&self, body_stmts: &[Statement], ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let mut last_result_reg = None;
|
||||
for stmt in body_stmts {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
if let Some(reg) = self.emit_statement(stmt, ctx)? {
|
||||
last_result_reg = Some(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
for stmt in body_stmts {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
continue;
|
||||
}
|
||||
if let Some(reg) = self.emit_statement(stmt, ctx)? {
|
||||
last_result_reg = Some(reg);
|
||||
}
|
||||
}
|
||||
Ok(last_result_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),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(&self, program: &oxide_parser::Program) -> Result<CompiledModule, String> {
|
||||
crate::compiler_debug!("compile: starting...");
|
||||
let mut ctx = CompileCtx::new();
|
||||
ctx.pre_register_builtins();
|
||||
self.predeclare_function_declarations(&program.body, &mut ctx);
|
||||
|
||||
// Pre-register builtin identifier references before any temporary register
|
||||
// is emitted, keeping builtin slots clear of the temporary register pool.
|
||||
self.pre_register_builtin_references(&program.body, &mut ctx);
|
||||
|
||||
// Pre-declare top-level `var` names so hoisted function declarations
|
||||
// (emitted in the first sub-pass below) can resolve the outer vars.
|
||||
self.predeclare_var_declarations(&program.body, &mut ctx);
|
||||
|
||||
// 闭包捕获分析(AST 级,emit 前确定)
|
||||
ctx.own_bindings = self.collect_own_binding_names(&[], &program.body);
|
||||
ctx.captured_bindings = self.collect_captured_bindings(&program.body, &ctx.own_bindings);
|
||||
|
||||
// First sub-pass: emit FunctionDeclarations (hoisting)
|
||||
// This ensures function objects are available before any code runs.
|
||||
for stmt in &program.body {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
self.emit_statement(stmt, &mut ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Second sub-pass: emit all other statements
|
||||
let mut last_result: Option<u8> = None;
|
||||
for stmt in &program.body {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
continue; // Already emitted above
|
||||
}
|
||||
match self.emit_statement(stmt, &mut ctx)? {
|
||||
Some(r) => last_result = Some(r),
|
||||
None => last_result = None,
|
||||
}
|
||||
}
|
||||
if let Some(r) = last_result {
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::None, Operand::Reg(r as u32), Operand::None));
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.inst(Inst::load_const(Operand::None, undef_idx));
|
||||
}
|
||||
ctx.inst(Inst::new(OpCode::HALT, Operand::None, Operand::None, Operand::None));
|
||||
|
||||
crate::compiler_debug!("compile: done, {} instructions, {} constants", ctx.insts.len(), ctx.constants.len());
|
||||
|
||||
let ir = ctx.assemble_ir(
|
||||
oxide_ir::ParamLayout {
|
||||
base: ctx.scopes.builtin_reg_map.len() as u32,
|
||||
count: 0,
|
||||
},
|
||||
None,
|
||||
);
|
||||
let ir = Emitter::new().emit_program(program)?;
|
||||
crate::compiler_debug!("compile: done, {} instructions", ir.insts.len());
|
||||
lower(&ir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
#![doc = "OxideJS - AST to bytecode compiler"]
|
||||
|
||||
pub mod class;
|
||||
pub mod closure;
|
||||
pub mod compiler;
|
||||
pub mod compiler_log;
|
||||
pub mod emit_ctx;
|
||||
pub mod expr;
|
||||
pub mod hash;
|
||||
pub mod prepass;
|
||||
pub mod shared;
|
||||
pub mod stmt;
|
||||
pub mod symbol_table;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "oxide_emit"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "OxideJS - AST to IR emit pass"
|
||||
|
||||
[dependencies]
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_ir = { path = "../oxide_ir" }
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler, FunctionBodyContext};
|
||||
use crate::{CompileCtx, Emitter, FunctionBodyContext};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{Class, ClassElement, MethodDefinitionKind, PropertyKey};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
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();
|
||||
@@ -59,7 +59,7 @@ impl Compiler {
|
||||
ctx.in_derived_constructor = is_derived;
|
||||
ctx.scopes.private_name_map = private_names.clone();
|
||||
|
||||
let emit_instance_fields = |compiler: &Compiler, field_ctx: &mut CompileCtx| -> Result<(), String> {
|
||||
let emit_instance_fields = |compiler: &Emitter, field_ctx: &mut CompileCtx| -> Result<(), String> {
|
||||
for field in &instance_fields {
|
||||
if let PropertyKey::PrivateIdentifier(private) = &field.key {
|
||||
compiler.emit_private_field_init(Operand::This, private.name.as_str(), field.value.as_ref(), field_ctx)?;
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_parser::{ClassElement, PropertyKey};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
/// Emit static field initializers and static blocks in source order.
|
||||
/// JS class semantics interleave them (e.g. `static x = 1; static {
|
||||
/// this.y = 1 } static z = this.y + 1` must run in that order). Emitting
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_class_header(
|
||||
&self, class: &oxide_parser::Class, ctx: &mut CompileCtx,
|
||||
) -> Result<(u8, u8, Option<u8>), String> {
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler, FunctionBodyContext};
|
||||
use crate::{CompileCtx, Emitter, FunctionBodyContext};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{ClassElement, MethodDefinitionKind, PropertyKey};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_class_methods(
|
||||
&self, elements: &[ClassElement], ctor_reg: u8, proto_reg: u8, self_binding: &[(&str, u8)],
|
||||
ctx: &mut CompileCtx,
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_class_prototype(
|
||||
&self, ctor_reg: u8, proto_reg: u8, super_reg: Option<u8>, sub_idx: u16, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
@@ -3,9 +3,9 @@ use std::collections::{BTreeMap, HashSet};
|
||||
use oxide_bytecode::module::UpvalueCapture;
|
||||
use oxide_parser::{Expression, Statement};
|
||||
|
||||
use crate::compiler::Compiler;
|
||||
use crate::Emitter;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
|
||||
fn collect_fn_param_names(&self, params: &oxide_parser::FormalParameters) -> HashSet<String> {
|
||||
let mut names = HashSet::new();
|
||||
@@ -515,4 +515,3 @@ impl Compiler {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,903 @@
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
use oxide_bytecode::module::UpvalueCapture;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::{LabelId, Operand};
|
||||
use oxide_ir::IRFunction;
|
||||
|
||||
use crate::emit_ctx::{LabelCtx, ScopeCtx};
|
||||
use crate::symbol_table::{Binding, ScopeKind, SymbolTable};
|
||||
|
||||
pub use oxide_bytecode::module::Constant;
|
||||
pub use oxide_parser::VariableDeclarationKind;
|
||||
pub use oxide_parser::{AssignmentOperator, BinaryOperator, Expression, Statement, UnaryOperator};
|
||||
|
||||
pub struct Emitter;
|
||||
|
||||
pub fn is_int_literal(value: f64) -> bool {
|
||||
value.fract() == 0.0 && value >= i32::MIN as f64 && value <= i32::MAX as f64
|
||||
}
|
||||
|
||||
pub fn is_side_effect_free(expr: &Expression) -> bool {
|
||||
let mut stack = vec![expr];
|
||||
while let Some(expr) = stack.pop() {
|
||||
match expr {
|
||||
Expression::NumericLiteral(_)
|
||||
| Expression::StringLiteral(_)
|
||||
| Expression::BooleanLiteral(_)
|
||||
| Expression::NullLiteral(_)
|
||||
| Expression::Identifier(_)
|
||||
| Expression::RegExpLiteral(_)
|
||||
| Expression::ThisExpression(_) => {}
|
||||
Expression::ParenthesizedExpression(p) => stack.push(&p.expression),
|
||||
Expression::BinaryExpression(bin) => {
|
||||
stack.push(&bin.left);
|
||||
stack.push(&bin.right);
|
||||
}
|
||||
Expression::UnaryExpression(un) if !matches!(un.operator, UnaryOperator::Delete) => {
|
||||
stack.push(&un.argument);
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
const BUILTIN_GLOBALS: &[&str] = &[
|
||||
"NaN",
|
||||
"undefined",
|
||||
"Infinity",
|
||||
"globalThis",
|
||||
"Object",
|
||||
"Array",
|
||||
"String",
|
||||
"Number",
|
||||
"Boolean",
|
||||
"Function",
|
||||
"Error",
|
||||
"TypeError",
|
||||
"ReferenceError",
|
||||
"RangeError",
|
||||
"SyntaxError",
|
||||
"URIError",
|
||||
"EvalError",
|
||||
"Math",
|
||||
"JSON",
|
||||
"Promise",
|
||||
"Date",
|
||||
"Set",
|
||||
"Map",
|
||||
"RegExp",
|
||||
"Symbol",
|
||||
"parseInt",
|
||||
"parseFloat",
|
||||
"isNaN",
|
||||
"isFinite",
|
||||
"Proxy",
|
||||
"WeakMap",
|
||||
"WeakSet",
|
||||
"WeakRef",
|
||||
"FinalizationRegistry",
|
||||
"Atomics",
|
||||
"SharedArrayBuffer",
|
||||
"ArrayBuffer",
|
||||
"DataView",
|
||||
"Iterator",
|
||||
"BigInt",
|
||||
"Int8Array",
|
||||
"Uint8Array",
|
||||
"Uint8ClampedArray",
|
||||
"Int16Array",
|
||||
"Uint16Array",
|
||||
"Int32Array",
|
||||
"Uint32Array",
|
||||
"Float32Array",
|
||||
"Float64Array",
|
||||
"BigInt64Array",
|
||||
"BigUint64Array",
|
||||
"Reflect",
|
||||
"escape",
|
||||
"unescape",
|
||||
"encodeURI",
|
||||
"decodeURI",
|
||||
"encodeURIComponent",
|
||||
"decodeURIComponent",
|
||||
];
|
||||
|
||||
pub(crate) struct FieldBuffer {
|
||||
pub(crate) insts: Vec<Inst>,
|
||||
pub(crate) labels: Vec<(LabelId, usize)>,
|
||||
}
|
||||
|
||||
/// A labeled-statement scope active during emission. `break label` targets
|
||||
/// `break_label`; `continue label` targets `continue_label` (only set when the
|
||||
/// labeled statement directly wraps an iteration statement).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LabelScope {
|
||||
pub(crate) name: String,
|
||||
pub(crate) break_label: LabelId,
|
||||
pub(crate) continue_label: Option<LabelId>,
|
||||
}
|
||||
|
||||
pub struct CompileCtx {
|
||||
pub(crate) insts: Vec<Inst>,
|
||||
pub(crate) constants: Vec<Constant>,
|
||||
constant_map: HashMap<ConstantKey, u16>,
|
||||
next_reg: u8,
|
||||
pub(crate) max_regs: u8,
|
||||
reserved_reg_start: u8,
|
||||
pub(crate) labels: LabelCtx,
|
||||
pub(crate) scopes: ScopeCtx,
|
||||
pub(crate) nested: Vec<IRFunction>,
|
||||
/// Register holding `this` in the enclosing function context.
|
||||
/// Used by arrow functions to capture lexical `this`.
|
||||
/// Initialized to 254 (conventional this register) at the top level.
|
||||
pub(crate) enclosing_this_reg: u8,
|
||||
pub(crate) in_derived_constructor: bool,
|
||||
pub(crate) in_instance_method: bool,
|
||||
pub(crate) in_static_method: bool,
|
||||
pub(crate) static_block_this_reg: Option<u8>,
|
||||
pub(crate) field_buffer: Option<FieldBuffer>,
|
||||
pub(crate) current_upvalue_captures: Vec<UpvalueCapture>,
|
||||
/// 本函数作用域声明的绑定名(参数 + 变量/函数声明,AST 收集,emit 前确定)。
|
||||
pub(crate) own_bindings: HashSet<String>,
|
||||
/// 本函数被嵌套函数捕获的绑定名 → cell_idx(名字排序分配,稳定跨 run)。
|
||||
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
|
||||
/// 消除符号表时序依赖与 cell 索引错位。
|
||||
pub(crate) captured_bindings: BTreeMap<String, u8>,
|
||||
/// Set when alloc_reg() overflows into the reserved this/new.target range (≥254).
|
||||
/// Checked after each emit phase to produce a compile error rather than silent corruption.
|
||||
pub(crate) reg_overflow: bool,
|
||||
pub(crate) const_overflow: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FunctionBodyContext {
|
||||
Ordinary,
|
||||
Arrow,
|
||||
ClassElement,
|
||||
}
|
||||
|
||||
pub enum ParamSpec<'a> {
|
||||
Identifier(String),
|
||||
Pattern {
|
||||
synthetic_name: String,
|
||||
pattern: &'a oxide_parser::BindingPattern<'a>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ParamSpec<'_> {
|
||||
pub(crate) fn register_name(&self) -> &str {
|
||||
match self {
|
||||
Self::Identifier(name) => name,
|
||||
Self::Pattern { synthetic_name, .. } => synthetic_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum ConstantKey {
|
||||
Number(u64),
|
||||
Int(i32),
|
||||
String(String),
|
||||
Boolean(bool),
|
||||
Null,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
impl CompileCtx {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
insts: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
constant_map: HashMap::new(),
|
||||
next_reg: 1,
|
||||
max_regs: 1,
|
||||
reserved_reg_start: 1,
|
||||
labels: LabelCtx {
|
||||
label_pos: Vec::new(),
|
||||
loop_stack: Vec::new(),
|
||||
switch_stack: Vec::new(),
|
||||
label_scopes: Vec::new(),
|
||||
pending_loop_labels: Vec::new(),
|
||||
label_counter: 0,
|
||||
},
|
||||
scopes: ScopeCtx {
|
||||
symbols: SymbolTable::new(),
|
||||
builtin_reg_map: Vec::new(),
|
||||
private_name_map: Vec::new(),
|
||||
next_private_name_id: 1,
|
||||
},
|
||||
nested: Vec::new(),
|
||||
enclosing_this_reg: 254, // conventional this register at top level
|
||||
in_derived_constructor: false,
|
||||
in_instance_method: false,
|
||||
in_static_method: false,
|
||||
static_block_this_reg: None,
|
||||
field_buffer: None,
|
||||
current_upvalue_captures: Vec::new(),
|
||||
own_bindings: HashSet::new(),
|
||||
captured_bindings: BTreeMap::new(),
|
||||
reg_overflow: false,
|
||||
const_overflow: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inst(&mut self, inst: Inst) {
|
||||
self.insts.push(inst);
|
||||
}
|
||||
|
||||
pub(crate) fn alloc_reg(&mut self) -> u8 {
|
||||
let r = self.next_reg;
|
||||
// Registers 254 (this) and 255 (new.target) are reserved by the VM.
|
||||
// Allocating into them silently corrupts the call convention, turning
|
||||
// method calls' `this` into garbage. Clamp to 253 and set a flag so
|
||||
// the compiler can surface a proper error after the emit pass.
|
||||
if r >= 254 {
|
||||
self.reg_overflow = true;
|
||||
return 253; // clamp to last safe register; emit continues but reg_overflow triggers error
|
||||
}
|
||||
self.next_reg = self.next_reg.wrapping_add(1);
|
||||
if self.next_reg > self.max_regs {
|
||||
self.max_regs = self.next_reg;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
pub(crate) fn reset_regs(&mut self) {
|
||||
self.next_reg = self.builtin_reg_floor().max(self.reserved_reg_start);
|
||||
self.labels.label_counter = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn reg_checkpoint(&self) -> u8 {
|
||||
self.next_reg
|
||||
}
|
||||
|
||||
pub(crate) fn restore_reg_checkpoint(&mut self, checkpoint: u8) {
|
||||
self.next_reg = checkpoint;
|
||||
}
|
||||
|
||||
pub(crate) fn reserve_reg(&mut self, reg: u8) {
|
||||
let next = reg.wrapping_add(1);
|
||||
if self.next_reg <= reg {
|
||||
self.next_reg = next;
|
||||
}
|
||||
if self.max_regs < next {
|
||||
self.max_regs = next;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_constant(&mut self, c: Constant) -> u16 {
|
||||
if let Some(key) = ConstantKey::from_constant(&c) {
|
||||
if let Some(&idx) = self.constant_map.get(&key) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
if self.constants.len() >= u16::MAX as usize {
|
||||
self.const_overflow = true;
|
||||
return u16::MAX;
|
||||
}
|
||||
let idx = self.constants.len() as u16;
|
||||
self.constants.push(c);
|
||||
self.constant_map.insert(key, idx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
let idx = self.constants.len();
|
||||
if idx >= u16::MAX as usize {
|
||||
self.const_overflow = true;
|
||||
return u16::MAX;
|
||||
}
|
||||
self.constants.push(c);
|
||||
idx as u16
|
||||
}
|
||||
|
||||
pub(crate) fn push_scope(&mut self) {
|
||||
self.scopes.symbols.push_scope();
|
||||
}
|
||||
|
||||
pub(crate) fn pop_scope(&mut self) {
|
||||
self.scopes.symbols.pop_scope();
|
||||
}
|
||||
|
||||
pub(crate) fn declare(
|
||||
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
|
||||
) -> Result<(), String> {
|
||||
self.scopes.symbols.declare(name, reg, kind, is_const)
|
||||
}
|
||||
|
||||
pub(crate) fn declare_initialized(
|
||||
&mut self, name: &str, reg: u8, kind: VariableDeclarationKind, is_const: bool,
|
||||
) -> Result<(), String> {
|
||||
self.scopes.symbols.declare_initialized(name, reg, kind, is_const)
|
||||
}
|
||||
|
||||
pub(crate) fn push_scope_with_kind(&mut self, kind: ScopeKind) {
|
||||
self.scopes.symbols.push_scope_with_kind(kind);
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, name: &str) -> Result<u8, String> {
|
||||
self.scopes.symbols.lookup(name)
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_or_builtin(&mut self, name: &str) -> Result<u8, String> {
|
||||
match self.scopes.symbols.lookup(name) {
|
||||
Ok(reg) => Ok(reg),
|
||||
Err(err) if Self::is_known_builtin(name) && err.contains("is not defined") => {
|
||||
let reg = self.alloc_reg();
|
||||
self.scopes.symbols.pre_register_global(name, reg);
|
||||
self.scopes.builtin_reg_map.push((name.to_string(), reg));
|
||||
Ok(reg)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_or_global(&mut self, name: &str) -> u8 {
|
||||
if let Some(reg) = self.scopes.symbols.lookup_any(name) {
|
||||
return reg;
|
||||
}
|
||||
let reg = self.alloc_reg();
|
||||
self.scopes.symbols.lookup_or_global(name, reg)
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_const_flag(&self, name: &str) -> bool {
|
||||
self.scopes.symbols.lookup_is_const(name)
|
||||
}
|
||||
|
||||
pub(crate) fn init_var(&mut self, name: &str) {
|
||||
self.scopes.symbols.init_var(name);
|
||||
}
|
||||
|
||||
pub(crate) fn next_label_id(&mut self) -> u32 {
|
||||
let id = self.labels.label_counter;
|
||||
self.labels.label_counter += 1;
|
||||
id
|
||||
}
|
||||
|
||||
pub(crate) fn push_loop(&mut self, break_label: LabelId, continue_label: LabelId) {
|
||||
self.labels.loop_stack.push((break_label, continue_label));
|
||||
}
|
||||
|
||||
pub(crate) fn pop_loop(&mut self) {
|
||||
self.labels.loop_stack.pop();
|
||||
}
|
||||
|
||||
pub(crate) fn current_loop(&self) -> Option<&(LabelId, LabelId)> {
|
||||
self.labels.loop_stack.last()
|
||||
}
|
||||
|
||||
pub(crate) fn push_switch(&mut self, break_label: LabelId) {
|
||||
self.labels.switch_stack.push(break_label);
|
||||
}
|
||||
|
||||
pub(crate) fn pop_switch(&mut self) {
|
||||
self.labels.switch_stack.pop();
|
||||
}
|
||||
|
||||
pub(crate) fn current_switch(&self) -> Option<&LabelId> {
|
||||
self.labels.switch_stack.last()
|
||||
}
|
||||
|
||||
pub(crate) fn push_label_scope(
|
||||
&mut self, name: &str, break_label: LabelId, continue_label: Option<LabelId>,
|
||||
) -> Result<(), String> {
|
||||
if self.labels.label_scopes.iter().any(|s| s.name == name) {
|
||||
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
|
||||
}
|
||||
self.labels.label_scopes.push(LabelScope {
|
||||
name: name.to_string(),
|
||||
break_label,
|
||||
continue_label,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn pop_label_scope(&mut self) {
|
||||
self.labels.label_scopes.pop();
|
||||
}
|
||||
|
||||
pub(crate) fn find_label(&self, name: &str) -> Option<&LabelScope> {
|
||||
self.labels.label_scopes.iter().rev().find(|s| s.name == name)
|
||||
}
|
||||
|
||||
/// Queue a label name to be bound to the continue target of the next loop
|
||||
/// emitted as the labeled statement's body. Rejects duplicates in the active
|
||||
/// or pending sets.
|
||||
pub(crate) fn queue_loop_label(&mut self, name: &str) -> Result<(), String> {
|
||||
if self.labels.label_scopes.iter().any(|s| s.name == name)
|
||||
|| self.labels.pending_loop_labels.iter().any(|n| n == name)
|
||||
{
|
||||
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
|
||||
}
|
||||
self.labels.pending_loop_labels.push(name.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Drain queued loop labels into active scopes bound to this loop's break and
|
||||
/// continue targets. Returns how many scopes were pushed (to pop after).
|
||||
pub(crate) fn take_pending_loop_labels(&mut self, break_label: LabelId, continue_label: LabelId) -> usize {
|
||||
let names = std::mem::take(&mut self.labels.pending_loop_labels);
|
||||
let count = names.len();
|
||||
for name in names {
|
||||
self.labels.label_scopes.push(LabelScope {
|
||||
name,
|
||||
break_label,
|
||||
continue_label: Some(continue_label),
|
||||
});
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
pub(crate) fn pop_label_scopes(&mut self, n: usize) {
|
||||
for _ in 0..n {
|
||||
self.labels.label_scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_builtin(&self, name: &str) -> bool {
|
||||
self.scopes.builtin_reg_map.iter().any(|(n, _)| n == name)
|
||||
}
|
||||
|
||||
pub(crate) fn is_known_builtin(name: &str) -> bool {
|
||||
BUILTIN_GLOBALS.contains(&name)
|
||||
}
|
||||
|
||||
fn builtin_reg_floor(&self) -> u8 {
|
||||
self.scopes
|
||||
.builtin_reg_map
|
||||
.iter()
|
||||
.map(|(_, reg)| reg.saturating_add(1))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub(crate) fn pre_register_builtins(&mut self) {
|
||||
// Builtin globals are resolved lazily by lookup_or_builtin(). Keeping this
|
||||
// hook preserves the compile pipeline without reserving ~60 registers in
|
||||
// every module.
|
||||
}
|
||||
|
||||
/// 组装 IRFunction(两出口共用),take 走编译产物状态。
|
||||
/// `parent_ctx` 用于补全 upvalue_captures 的 enclosing_reg(父符号表在父 emit 完成后完整)。
|
||||
fn assemble_ir(&mut self, param_layout: oxide_ir::ParamLayout, parent_ctx: Option<&CompileCtx>) -> IRFunction {
|
||||
let upvalue_captures = self
|
||||
.current_upvalue_captures
|
||||
.iter()
|
||||
.map(|u| {
|
||||
let enclosing_reg = parent_ctx
|
||||
.and_then(|p| p.scopes.symbols.lookup_any(u.name.as_str()))
|
||||
.unwrap_or(0);
|
||||
UpvalueCapture {
|
||||
name: u.name.clone(),
|
||||
enclosing_reg,
|
||||
cell_idx: u.cell_idx,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
IRFunction {
|
||||
insts: std::mem::take(&mut self.insts),
|
||||
label_pos: std::mem::take(&mut self.labels.label_pos),
|
||||
label_count: self.labels.label_counter,
|
||||
constants: std::mem::take(&mut self.constants),
|
||||
param_layout,
|
||||
builtin_reg_map: std::mem::take(&mut self.scopes.builtin_reg_map),
|
||||
upvalue_captures,
|
||||
cells_needed: self.captured_bindings.len() as u8,
|
||||
n_registers: self.max_regs,
|
||||
is_arrow: false,
|
||||
is_class_constructor: false,
|
||||
is_derived_constructor: false,
|
||||
needs_home_object: false,
|
||||
captured_this_const_idx: 0,
|
||||
function_name: None,
|
||||
reg_overflow: self.reg_overflow,
|
||||
const_overflow: self.const_overflow,
|
||||
nested: std::mem::take(&mut self.nested),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantKey {
|
||||
fn from_constant(value: &Constant) -> Option<Self> {
|
||||
match value {
|
||||
Constant::Number(v) => Some(Self::Number(v.to_bits())),
|
||||
Constant::Int(v) => Some(Self::Int(*v)),
|
||||
Constant::String(v) => Some(Self::String(v.clone())),
|
||||
Constant::Boolean(v) => Some(Self::Boolean(*v)),
|
||||
Constant::Null => Some(Self::Null),
|
||||
Constant::Undefined => Some(Self::Undefined),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
// ── 闭包捕获分析(AST 级,时序无关)──
|
||||
|
||||
/// 收集函数参数的绑定名(BindingIdentifier 形态)。
|
||||
|
||||
|
||||
pub(crate) fn extract_function_parts<'a>(
|
||||
&self, function: &'a oxide_parser::Function<'a>,
|
||||
) -> Result<(Vec<ParamSpec<'a>>, &'a [Statement<'a>]), String> {
|
||||
let mut param_specs = Vec::new();
|
||||
for (idx, param) in function.params.items.iter().enumerate() {
|
||||
match ¶m.pattern {
|
||||
oxide_parser::BindingPattern::BindingIdentifier(bi) => {
|
||||
param_specs.push(ParamSpec::Identifier(bi.name.to_string()));
|
||||
}
|
||||
pattern => {
|
||||
param_specs.push(ParamSpec::Pattern {
|
||||
synthetic_name: format!("@@param_{idx}"),
|
||||
pattern,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let body_stmts: &[Statement] = if let Some(body) = &function.body { &body.statements } else { &[] };
|
||||
Ok((param_specs, body_stmts))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// `is_arrow` controls whether super flags are inherited from the parent scope
|
||||
/// (true for arrow functions, which have lexical super) or reset to false
|
||||
/// (false for regular functions, which create a new super scope).
|
||||
pub(crate) fn compile_function_body<'a>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
is_expression_body: bool, is_arrow: bool,
|
||||
) -> Result<IRFunction, String> {
|
||||
let body_context = if is_arrow {
|
||||
FunctionBodyContext::Arrow
|
||||
} else {
|
||||
FunctionBodyContext::Ordinary
|
||||
};
|
||||
self.compile_function_body_with_bindings(
|
||||
param_specs,
|
||||
body_stmts,
|
||||
parent_ctx,
|
||||
is_expression_body,
|
||||
&[],
|
||||
body_context,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn compile_function_body_with_bindings<'a>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext,
|
||||
) -> Result<IRFunction, String> {
|
||||
self.compile_function_body_with_field_hooks(
|
||||
param_specs,
|
||||
body_stmts,
|
||||
parent_ctx,
|
||||
is_expression_body,
|
||||
extra_bindings,
|
||||
body_context,
|
||||
None::<fn(&Emitter, &mut CompileCtx) -> Result<(), String>>,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/// Pre-register all builtin identifiers referenced anywhere in this body
|
||||
/// (expressions, member objects, call args, class fields, etc.) so their
|
||||
/// register slots are reserved *before* any temporary register is emitted.
|
||||
///
|
||||
/// Without this, single-pass emission lazily allocates builtin slots via
|
||||
/// `lookup_or_builtin` at first use, which can collide with a temporary
|
||||
/// register already reused by `restore_reg_checkpoint`. Pre-scanning moves
|
||||
/// builtin slot allocation ahead of the temporary register pool.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compile_function_body_with_field_hooks<'a, E>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext,
|
||||
mut emit_fields: Option<E>, fields_after_super: bool,
|
||||
) -> Result<IRFunction, String>
|
||||
where
|
||||
E: FnMut(&Emitter, &mut CompileCtx) -> Result<(), String>,
|
||||
{
|
||||
let mut ctx = CompileCtx::new();
|
||||
|
||||
// Inherit parent's builtin_reg_map so builtin identifiers (Math, Object, etc.)
|
||||
// resolve to the correct pre-allocated registers in the sub-module's register file.
|
||||
ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone();
|
||||
ctx.scopes.private_name_map = parent_ctx.scopes.private_name_map.clone();
|
||||
ctx.scopes.next_private_name_id = parent_ctx.scopes.next_private_name_id;
|
||||
|
||||
// Propagate enclosing_this_reg so nested arrow functions capture the correct `this`.
|
||||
ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg;
|
||||
// Arrow functions inherit lexical super. Class method bodies also need the
|
||||
// class-provided super context for their top-level body compilation.
|
||||
if matches!(body_context, FunctionBodyContext::Arrow | FunctionBodyContext::ClassElement) {
|
||||
ctx.in_derived_constructor = parent_ctx.in_derived_constructor;
|
||||
ctx.in_instance_method = parent_ctx.in_instance_method;
|
||||
ctx.in_static_method = parent_ctx.in_static_method;
|
||||
} else {
|
||||
ctx.in_derived_constructor = false;
|
||||
ctx.in_instance_method = false;
|
||||
ctx.in_static_method = false;
|
||||
}
|
||||
|
||||
// Inherit parent's global scope entries so previously-declared function names
|
||||
// are visible from within the body.
|
||||
let mut inherited_reg_start = 1u8.max(ctx.builtin_reg_floor());
|
||||
for (name, binding) in &parent_ctx.scopes.symbols.scopes[0].bindings {
|
||||
ctx.scopes.symbols.scopes[0].bindings.insert(
|
||||
name.clone(),
|
||||
Binding {
|
||||
reg: binding.reg,
|
||||
initialized: binding.initialized,
|
||||
is_const: binding.is_const,
|
||||
},
|
||||
);
|
||||
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
|
||||
}
|
||||
for (name, reg) in extra_bindings {
|
||||
ctx.scopes.symbols.scopes[0].bindings.insert(
|
||||
(*name).to_string(),
|
||||
Binding {
|
||||
reg: *reg,
|
||||
initialized: true,
|
||||
is_const: true,
|
||||
},
|
||||
);
|
||||
inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1));
|
||||
}
|
||||
ctx.reserved_reg_start = inherited_reg_start.max(1);
|
||||
|
||||
// Align next_reg with builtin count so both count and emit passes start at the
|
||||
// same register offset (params go after builtin slots).
|
||||
ctx.reset_regs();
|
||||
|
||||
let param_base = self.emit_params_prologue(param_specs, body_stmts, parent_ctx, &mut ctx, body_context)?;
|
||||
|
||||
self.predeclare_function_declarations(body_stmts, &mut ctx);
|
||||
|
||||
// Pre-register builtin identifier references before emitting any temporary
|
||||
// register, so builtin slots never collide with reused temporaries.
|
||||
self.pre_register_builtin_references(body_stmts, &mut ctx);
|
||||
|
||||
// Pre-declare `var` names so hoisted function declarations (emitted in the
|
||||
// first sub-pass below) can resolve the outer vars they close over.
|
||||
self.predeclare_var_declarations(body_stmts, &mut ctx);
|
||||
|
||||
if let Some(emit) = emit_fields.as_mut() {
|
||||
if fields_after_super {
|
||||
let mut parent_insts = Vec::new();
|
||||
let mut parent_label_pos = Vec::new();
|
||||
std::mem::swap(&mut ctx.insts, &mut parent_insts);
|
||||
std::mem::swap(&mut ctx.labels.label_pos, &mut parent_label_pos);
|
||||
emit(self, &mut ctx)?;
|
||||
let field_buffer = FieldBuffer {
|
||||
insts: std::mem::take(&mut ctx.insts),
|
||||
labels: std::mem::take(&mut ctx.labels.label_pos)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(id, pos)| pos.map(|p| (id as LabelId, p)))
|
||||
.collect(),
|
||||
};
|
||||
ctx.insts = parent_insts;
|
||||
ctx.labels.label_pos = parent_label_pos;
|
||||
ctx.field_buffer = Some(field_buffer);
|
||||
} else {
|
||||
emit(self, &mut ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Emit body statements(先函数声明 hoisting,再其余)。
|
||||
let last_result_reg = self.emit_body_stmts(body_stmts, &mut ctx)?;
|
||||
|
||||
// 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.inst(Inst::new(OpCode::RETURN, Operand::Reg(reg as u32), Operand::None, Operand::None));
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
let undef_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(undef_reg as u32), undef_idx));
|
||||
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg as u32), Operand::None, Operand::None));
|
||||
}
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
let undef_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(undef_reg as u32), undef_idx));
|
||||
ctx.inst(Inst::new(OpCode::RETURN, Operand::Reg(undef_reg as u32), Operand::None, Operand::None));
|
||||
}
|
||||
|
||||
let ir = ctx.assemble_ir(
|
||||
oxide_ir::ParamLayout {
|
||||
base: param_base as u32,
|
||||
count: param_specs.len() as u32,
|
||||
},
|
||||
Some(parent_ctx),
|
||||
);
|
||||
Ok(ir)
|
||||
}
|
||||
|
||||
/// 参数 prologue:函数作用域 + 参数声明/解构 + 闭包捕获与 upvalue 分析。返回 param_base。
|
||||
fn emit_params_prologue<'a>(
|
||||
&self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx,
|
||||
ctx: &mut CompileCtx, body_context: FunctionBodyContext,
|
||||
) -> Result<u8, String> {
|
||||
ctx.push_scope_with_kind(ScopeKind::FunctionScope);
|
||||
let param_base = ctx.next_reg;
|
||||
|
||||
// Emit parameters and destructuring prologue.
|
||||
for spec in param_specs {
|
||||
let name = spec.register_name();
|
||||
let reg = ctx.alloc_reg();
|
||||
ctx.declare_initialized(name, reg, VariableDeclarationKind::Var, false)?;
|
||||
}
|
||||
|
||||
for spec in param_specs {
|
||||
if let ParamSpec::Pattern { synthetic_name, pattern } = spec {
|
||||
let src_reg = ctx.lookup(synthetic_name)?;
|
||||
self.emit_binding_pattern(pattern, src_reg, VariableDeclarationKind::Var, false, ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 闭包捕获分析(AST 级,emit 前确定,时序无关)
|
||||
let param_names: Vec<&str> = param_specs.iter().map(|s| s.register_name()).collect();
|
||||
ctx.own_bindings = self.collect_own_binding_names(¶m_names, body_stmts);
|
||||
ctx.captured_bindings = self.collect_captured_bindings(body_stmts, &ctx.own_bindings);
|
||||
|
||||
// Free variable analysis for upvalue capture (Ordinary + Arrow functions only)
|
||||
if matches!(body_context, FunctionBodyContext::Ordinary | FunctionBodyContext::Arrow) {
|
||||
ctx.current_upvalue_captures =
|
||||
self.collect_upvalue_names(body_stmts, &parent_ctx.captured_bindings, &ctx.own_bindings);
|
||||
}
|
||||
|
||||
Ok(param_base)
|
||||
}
|
||||
|
||||
/// 双 sub-pass emit body:先函数声明(hoisting),再其余语句。返回最后结果寄存器。
|
||||
fn emit_body_stmts(&self, body_stmts: &[Statement], ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let mut last_result_reg = None;
|
||||
for stmt in body_stmts {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
if let Some(reg) = self.emit_statement(stmt, ctx)? {
|
||||
last_result_reg = Some(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
for stmt in body_stmts {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
continue;
|
||||
}
|
||||
if let Some(reg) = self.emit_statement(stmt, ctx)? {
|
||||
last_result_reg = Some(reg);
|
||||
}
|
||||
}
|
||||
Ok(last_result_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),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a full program into an IRFunction (top-level module body).
|
||||
/// Split from the original `Compiler::compile`: this is the emit half;
|
||||
/// oxide_compiler::Compiler::compile calls this then `oxide_ir::lower::lower`.
|
||||
pub fn emit_program(&self, program: &oxide_parser::Program) -> Result<IRFunction, String> {
|
||||
let mut ctx = CompileCtx::new();
|
||||
ctx.pre_register_builtins();
|
||||
self.predeclare_function_declarations(&program.body, &mut ctx);
|
||||
|
||||
// Pre-register builtin identifier references before any temporary register
|
||||
// is emitted, keeping builtin slots clear of the temporary register pool.
|
||||
self.pre_register_builtin_references(&program.body, &mut ctx);
|
||||
|
||||
// Pre-declare top-level `var` names so hoisted function declarations
|
||||
// (emitted in the first sub-pass below) can resolve the outer vars.
|
||||
self.predeclare_var_declarations(&program.body, &mut ctx);
|
||||
|
||||
// 闭包捕获分析(AST 级,emit 前确定)
|
||||
ctx.own_bindings = self.collect_own_binding_names(&[], &program.body);
|
||||
ctx.captured_bindings = self.collect_captured_bindings(&program.body, &ctx.own_bindings);
|
||||
|
||||
// First sub-pass: emit FunctionDeclarations (hoisting)
|
||||
// This ensures function objects are available before any code runs.
|
||||
for stmt in &program.body {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
self.emit_statement(stmt, &mut ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Second sub-pass: emit all other statements
|
||||
let mut last_result: Option<u8> = None;
|
||||
for stmt in &program.body {
|
||||
if matches!(stmt, Statement::FunctionDeclaration(_)) {
|
||||
continue; // Already emitted above
|
||||
}
|
||||
match self.emit_statement(stmt, &mut ctx)? {
|
||||
Some(r) => last_result = Some(r),
|
||||
None => last_result = None,
|
||||
}
|
||||
}
|
||||
if let Some(r) = last_result {
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::None, Operand::Reg(r as u32), Operand::None));
|
||||
} else {
|
||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.inst(Inst::load_const(Operand::None, undef_idx));
|
||||
}
|
||||
ctx.inst(Inst::new(OpCode::HALT, Operand::None, Operand::None, Operand::None));
|
||||
|
||||
let ir = ctx.assemble_ir(
|
||||
oxide_ir::ParamLayout {
|
||||
base: ctx.scopes.builtin_reg_map.len() as u32,
|
||||
count: 0,
|
||||
},
|
||||
None,
|
||||
);
|
||||
Ok(ir)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Emitter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
//! Execution-stream fields (insts, registers, pc, …) stay flat on
|
||||
//! `CompileCtx`.
|
||||
|
||||
use crate::compiler::LabelScope;
|
||||
use oxide_ir::operand::LabelId;
|
||||
use crate::symbol_table::SymbolTable;
|
||||
use crate::LabelScope;
|
||||
use oxide_ir::operand::LabelId;
|
||||
|
||||
/// Jump-target / labeled-statement resolution state.
|
||||
pub(crate) struct LabelCtx {
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::AssignmentOperator;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_assignment_expression(
|
||||
&self, assign: &oxide_parser::AssignmentExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::Expression;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
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 {
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
use crate::compiler::{CompileCtx, Compiler, ParamSpec};
|
||||
use crate::{CompileCtx, Emitter, ParamSpec};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_parser::{Class, Expression, Statement};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_arrow_function_expression(
|
||||
&self, arrow: &oxide_parser::ArrowFunctionExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_identifier_expression(
|
||||
&self, ident: &oxide_parser::IdentifierReference, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{is_int_literal, CompileCtx, Compiler};
|
||||
use crate::{is_int_literal, CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::Expression;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_literal(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
match expr {
|
||||
Expression::NumericLiteral(n) => self.emit_numeric_literal_expression(n, ctx),
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::Expression;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_static_member_expression(
|
||||
&self, member: &oxide_parser::StaticMemberExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Expression;
|
||||
|
||||
pub mod assignment;
|
||||
@@ -13,7 +13,7 @@ pub mod parenthesized;
|
||||
pub mod template;
|
||||
pub mod this;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_unsupported_expression(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
let _ = ctx;
|
||||
Err(format!("unsupported expression type: {:?}", expr))
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_array_expression(
|
||||
&self, arr: &oxide_parser::ArrayExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Expression;
|
||||
|
||||
mod array;
|
||||
mod object_literal;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_object_domain(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
match expr {
|
||||
Expression::ObjectExpression(obj) => self.emit_object_expression(obj, ctx),
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{Expression, ObjectPropertyKind, PropertyKey, PropertyKind};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_object_expression(
|
||||
&self, obj: &oxide_parser::ObjectExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{is_side_effect_free, BinaryOperator, CompileCtx, Compiler};
|
||||
use crate::{is_side_effect_free, BinaryOperator, CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{ChainElement, Expression, LogicalOperator, SimpleAssignmentTarget, UnaryOperator, UpdateOperator};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_private_in_expression(
|
||||
&self, pin: &oxide_parser::PrivateInExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_parenthesized_expression(
|
||||
&self, p: &oxide_parser::ParenthesizedExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Expression;
|
||||
|
||||
mod tagged;
|
||||
mod template_literal;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_template_domain(&self, expr: &Expression, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
match expr {
|
||||
Expression::TemplateLiteral(tl) => self.emit_template_literal_expression(tl, ctx),
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_tagged_template_expression(
|
||||
&self, tt: &oxide_parser::TaggedTemplateExpression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_template_literal_expression(
|
||||
&self, tl: &oxide_parser::TemplateLiteral, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) 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);
|
||||
@@ -0,0 +1,14 @@
|
||||
pub mod class;
|
||||
pub mod closure;
|
||||
pub mod emit;
|
||||
pub mod emit_ctx;
|
||||
pub mod expr;
|
||||
pub mod prepass;
|
||||
pub mod shared;
|
||||
pub mod stmt;
|
||||
pub mod symbol_table;
|
||||
|
||||
pub use emit::{CompileCtx, Emitter, FunctionBodyContext, LabelScope, ParamSpec};
|
||||
pub use emit::{is_int_literal, is_side_effect_free};
|
||||
pub use oxide_bytecode::module::Constant;
|
||||
pub use oxide_parser::{AssignmentOperator, BinaryOperator, UnaryOperator};
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::{Expression, Statement, VariableDeclarationKind};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
|
||||
/// builtin slot allocation ahead of the temporary register pool.
|
||||
pub(crate) fn pre_register_builtin_references(&self, stmts: &[Statement], ctx: &mut CompileCtx) {
|
||||
@@ -340,4 +340,3 @@ impl Compiler {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
@@ -8,7 +8,7 @@ use oxide_parser::{
|
||||
BindingPattern, Expression, ObjectAssignmentTarget, ObjectPattern, VariableDeclarationKind,
|
||||
};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_default_if_undefined(
|
||||
&self, val_reg: u8, default_expr: &Expression, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::{LabelId, Operand};
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{ChainElement, Expression, LogicalOperator, PropertyKey};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_optional_guard(&self, reg: u8, short_label: LabelId, ctx: &mut CompileCtx) -> Result<(), String> {
|
||||
let dup_reg = ctx.alloc_reg();
|
||||
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(dup_reg as u32), Operand::Reg(reg as u32), Operand::None));
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{Expression, PropertyKey};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_public_field_init(
|
||||
&self, target: Operand, key: &PropertyKey, computed: bool, value: Option<&Expression>, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_parser::PropertyKey;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result<u32, String> {
|
||||
ctx.scopes
|
||||
.private_name_map
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_break_statement(
|
||||
&self, stmt: &oxide_parser::BreakStatement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_expression_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::ExpressionStatement(es) = stmt else {
|
||||
return Ok(None);
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn is_iteration_statement(stmt: &Statement) -> bool {
|
||||
matches!(
|
||||
stmt,
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Statement;
|
||||
|
||||
mod break_continue;
|
||||
@@ -6,7 +6,7 @@ mod expression;
|
||||
mod labeled;
|
||||
mod return_stmt;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_basic_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::ExpressionStatement(_) => self.emit_basic_expression(stmt, ctx),
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_return_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::ReturnStatement(ret) = stmt else {
|
||||
return Ok(None);
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_block_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::BlockStatement(block) = stmt else {
|
||||
return Ok(None);
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_if_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::IfStatement(ifs) = stmt else {
|
||||
return Ok(None);
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{Statement, VariableDeclarationKind};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_class_declaration_statement(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler, ParamSpec};
|
||||
use crate::{CompileCtx, Emitter, ParamSpec};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_function_declaration_statement(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Statement;
|
||||
|
||||
mod class_decl;
|
||||
mod function_decl;
|
||||
mod var;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_declaration_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::VariableDeclaration(_) => self.emit_variable_declaration_statement(stmt, ctx),
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{BindingPattern, Expression, Statement, VariableDeclarationKind};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_variable_declaration_statement(
|
||||
&self, stmt: &Statement, ctx: &mut CompileCtx,
|
||||
) -> Result<Option<u8>, String> {
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Statement;
|
||||
|
||||
mod throw;
|
||||
mod try_catch;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_exception_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::ThrowStatement(_) => self.emit_throw_statement(stmt, ctx),
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_throw_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::ThrowStatement(ts) = stmt else {
|
||||
return Ok(None);
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{Statement, VariableDeclarationKind};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_try_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::TryStatement(ts) = stmt else {
|
||||
return Ok(None);
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_do_while_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::DoWhileStatement(dw) = stmt else {
|
||||
return Ok(None);
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::module::Constant;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{BindingPattern, ForStatementInit, Statement, VariableDeclarationKind};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_for_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::ForStatement(fr) = stmt else {
|
||||
return Ok(None);
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{ForStatementLeft, Statement, VariableDeclarationKind};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_for_in_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::ForInStatement(fi) = stmt else {
|
||||
return Ok(None);
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::{ForStatementLeft, Statement};
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_for_of_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::ForOfStatement(fo) = stmt else {
|
||||
return Ok(None);
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_parser::Statement;
|
||||
|
||||
mod do_while;
|
||||
@@ -7,7 +7,7 @@ mod for_in;
|
||||
mod for_of;
|
||||
mod while_loop;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_iteration_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::WhileStatement(_) => self.emit_while_statement(stmt, ctx),
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
pub(crate) fn emit_while_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::WhileStatement(wh) = stmt else {
|
||||
return Ok(None);
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::compiler::{CompileCtx, Compiler};
|
||||
use crate::{CompileCtx, Emitter};
|
||||
use oxide_ir::inst::Inst;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_parser::Statement;
|
||||
|
||||
impl Compiler {
|
||||
impl Emitter {
|
||||
fn emit_switch_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
let Statement::SwitchStatement(sw) = stmt else {
|
||||
return Ok(None);
|
||||
Reference in New Issue
Block a user