feat: 支持基础 class 声明表达式与原型方法
This commit is contained in:
@@ -259,12 +259,35 @@ impl Compiler {
|
||||
Self
|
||||
}
|
||||
|
||||
pub(crate) fn extract_function_parts<'a>(
|
||||
&self, function: &'a oxide_parser::Function<'a>,
|
||||
) -> Result<(Vec<String>, &'a [Statement<'a>]), String> {
|
||||
let mut param_names = Vec::new();
|
||||
for param in &function.params.items {
|
||||
match ¶m.pattern {
|
||||
oxide_parser::BindingPattern::BindingIdentifier(bi) => {
|
||||
param_names.push(bi.name.to_string());
|
||||
}
|
||||
_ => return Err("destructuring parameters not supported".into()),
|
||||
}
|
||||
}
|
||||
let body_stmts: &[Statement] = if let Some(body) = &function.body { &body.statements } else { &[] };
|
||||
Ok((param_names, 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.
|
||||
pub(crate) fn compile_function_body<'a>(
|
||||
&self, param_names: &[String], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, is_expression_body: bool,
|
||||
) -> Result<CompiledModule, String> {
|
||||
self.compile_function_body_with_bindings(param_names, body_stmts, parent_ctx, is_expression_body, &[])
|
||||
}
|
||||
|
||||
pub(crate) fn compile_function_body_with_bindings<'a>(
|
||||
&self, param_names: &[String], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, is_expression_body: bool,
|
||||
extra_bindings: &[(&str, u8)],
|
||||
) -> Result<CompiledModule, String> {
|
||||
let mut ctx = CompileCtx::new();
|
||||
|
||||
@@ -289,6 +312,17 @@ impl Compiler {
|
||||
);
|
||||
inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1));
|
||||
}
|
||||
for (name, reg) in extra_bindings {
|
||||
ctx.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;
|
||||
|
||||
// Align next_reg with builtin count so both count and emit passes start at the
|
||||
@@ -367,6 +401,7 @@ impl Compiler {
|
||||
is_arrow: false,
|
||||
captured_this_const_idx: 0,
|
||||
function_name: None,
|
||||
is_class_constructor: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -424,6 +459,7 @@ impl Compiler {
|
||||
is_arrow: false,
|
||||
captured_this_const_idx: 0,
|
||||
function_name: None,
|
||||
is_class_constructor: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
use oxide_parser::{
|
||||
AssignmentOperator, Expression, ForStatementInit, SimpleAssignmentTarget, Statement, UnaryOperator,
|
||||
VariableDeclarationKind,
|
||||
AssignmentOperator, ClassElement, Expression, ForStatementInit, MethodDefinitionKind, SimpleAssignmentTarget,
|
||||
Statement, UnaryOperator, VariableDeclarationKind,
|
||||
};
|
||||
|
||||
use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label};
|
||||
|
||||
impl Compiler {
|
||||
fn count_class(&self, class: &oxide_parser::Class, ctx: &mut CompileCtx) {
|
||||
ctx.alloc_reg(); // ctor_reg
|
||||
ctx.alloc_reg(); // proto_reg
|
||||
ctx.projected_pc += 2; // LOAD_CONST ctor + NEW_OBJECT proto
|
||||
|
||||
for element in &class.body.body {
|
||||
let ClassElement::MethodDefinition(method) = element else {
|
||||
continue;
|
||||
};
|
||||
let method = method.as_ref();
|
||||
match method.kind {
|
||||
MethodDefinitionKind::Constructor => {}
|
||||
MethodDefinitionKind::Method => {
|
||||
ctx.alloc_reg(); // method_reg
|
||||
ctx.alloc_reg(); // key_reg
|
||||
ctx.projected_pc += 3; // LOAD_CONST method + LOAD_CONST key + SET_PROP
|
||||
}
|
||||
MethodDefinitionKind::Get | MethodDefinitionKind::Set => {}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.alloc_reg(); // constructor key
|
||||
ctx.projected_pc += 1; // LOAD_CONST "constructor"
|
||||
ctx.projected_pc += 1; // SET_PROP proto.constructor = ctor
|
||||
|
||||
ctx.alloc_reg(); // prototype key
|
||||
ctx.projected_pc += 1; // LOAD_CONST "prototype"
|
||||
ctx.projected_pc += 1; // SET_PROP ctor.prototype = proto
|
||||
}
|
||||
|
||||
pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
||||
match stmt {
|
||||
Statement::ExpressionStatement(es) => {
|
||||
@@ -198,6 +228,11 @@ impl Compiler {
|
||||
// FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR
|
||||
ctx.projected_pc += 2;
|
||||
}
|
||||
Statement::ClassDeclaration(class) => {
|
||||
ctx.alloc_reg(); // class binding reg
|
||||
self.count_class(class, ctx);
|
||||
ctx.projected_pc += 1; // STORE_VAR binding <- ctor
|
||||
}
|
||||
Statement::ThrowStatement(ts) => {
|
||||
self.count_expression(&ts.argument, ctx);
|
||||
ctx.projected_pc += 1; // THROW
|
||||
@@ -484,6 +519,9 @@ impl Compiler {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST
|
||||
}
|
||||
Expression::ClassExpression(class) => {
|
||||
self.count_class(class, ctx);
|
||||
}
|
||||
Expression::NewExpression(ne) => {
|
||||
// Count callee expression
|
||||
self.count_expression(&ne.callee, ctx);
|
||||
|
||||
@@ -1,14 +1,109 @@
|
||||
use crate::compiler::Label;
|
||||
use crate::opcode::{self, OpCode};
|
||||
use oxide_parser::{
|
||||
AssignmentOperator, Expression, ForStatementInit, ForStatementLeft, SimpleAssignmentTarget, Statement,
|
||||
UnaryOperator, UpdateOperator, VariableDeclarationKind,
|
||||
AssignmentOperator, Class, ClassElement, Expression, ForStatementInit, ForStatementLeft, MethodDefinitionKind,
|
||||
PropertyKey, SimpleAssignmentTarget, Statement, UnaryOperator, UpdateOperator, VariableDeclarationKind,
|
||||
};
|
||||
|
||||
use crate::compiler::{is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler};
|
||||
use crate::module::Constant;
|
||||
|
||||
impl Compiler {
|
||||
fn class_property_name(&self, key: &PropertyKey) -> Result<String, String> {
|
||||
match key {
|
||||
PropertyKey::StaticIdentifier(ident) => Ok(ident.name.as_str().to_string()),
|
||||
PropertyKey::StringLiteral(s) => Ok(s.value.to_string()),
|
||||
PropertyKey::PrivateIdentifier(_) => Err("private class elements not yet supported".into()),
|
||||
_ => Err("unsupported class property key type".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
if class.super_class.is_some() {
|
||||
return Err("class extends not yet supported".into());
|
||||
}
|
||||
|
||||
let mut constructor_method = None;
|
||||
let mut instance_methods = Vec::new();
|
||||
|
||||
for element in &class.body.body {
|
||||
let ClassElement::MethodDefinition(method) = element else {
|
||||
return Err("class fields/accessors/static blocks not yet supported".into());
|
||||
};
|
||||
let method = method.as_ref();
|
||||
if method.r#static {
|
||||
return Err("static methods not yet supported".into());
|
||||
}
|
||||
if method.computed {
|
||||
return Err("computed class methods not yet supported".into());
|
||||
}
|
||||
|
||||
match method.kind {
|
||||
MethodDefinitionKind::Constructor => {
|
||||
if constructor_method.is_some() {
|
||||
return Err("duplicate class constructor".into());
|
||||
}
|
||||
constructor_method = Some(method);
|
||||
}
|
||||
MethodDefinitionKind::Method => {
|
||||
let name = self.class_property_name(&method.key)?;
|
||||
instance_methods.push((method, name));
|
||||
}
|
||||
MethodDefinitionKind::Get | MethodDefinitionKind::Set => {
|
||||
return Err("getters/setters not yet supported".into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ctor_name = class.id.as_ref().map(|id| id.name.to_string());
|
||||
let ctor_reg = ctx.alloc_reg();
|
||||
let proto_reg = ctx.alloc_reg();
|
||||
let self_binding = ctor_name.as_deref().map(|name| vec![(name, ctor_reg)]).unwrap_or_default();
|
||||
|
||||
let mut ctor_module = if let Some(method) = constructor_method {
|
||||
let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?;
|
||||
self.compile_function_body_with_bindings(¶m_names, body_stmts, ctx, false, &self_binding)?
|
||||
} else {
|
||||
self.compile_function_body_with_bindings(&[], &[], ctx, false, &self_binding)?
|
||||
};
|
||||
ctor_module.is_class_constructor = true;
|
||||
ctor_module.function_name = ctor_name.clone();
|
||||
ctx.sub_modules.push(ctor_module);
|
||||
|
||||
let ctor_idx = ctx.add_constant(Constant::BytecodeFunc(ctx.sub_modules.len() as u32));
|
||||
ctx.emit_load_const(ctor_reg, ctor_idx);
|
||||
ctx.emit(opcode::encode(OpCode::NEW_OBJECT, proto_reg, 0, 0));
|
||||
|
||||
for (method, method_name) in instance_methods {
|
||||
let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?;
|
||||
let mut method_module =
|
||||
self.compile_function_body_with_bindings(¶m_names, body_stmts, ctx, false, &self_binding)?;
|
||||
method_module.function_name = Some(method_name.clone());
|
||||
ctx.sub_modules.push(method_module);
|
||||
|
||||
let method_idx = ctx.add_constant(Constant::BytecodeFunc(ctx.sub_modules.len() as u32));
|
||||
let method_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(method_reg, method_idx);
|
||||
|
||||
let key_idx = ctx.add_constant(Constant::String(method_name));
|
||||
let key_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(key_reg, key_idx);
|
||||
ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, method_reg, key_reg));
|
||||
}
|
||||
|
||||
let ctor_key_idx = ctx.add_constant(Constant::String("constructor".to_string()));
|
||||
let ctor_key_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(ctor_key_reg, ctor_key_idx);
|
||||
ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, ctor_reg, ctor_key_reg));
|
||||
|
||||
let proto_key_idx = ctx.add_constant(Constant::String("prototype".to_string()));
|
||||
let proto_key_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(proto_key_reg, proto_key_idx);
|
||||
ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, proto_reg, proto_key_reg));
|
||||
|
||||
Ok(ctor_reg)
|
||||
}
|
||||
|
||||
pub(crate) fn emit_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||
match stmt {
|
||||
Statement::ExpressionStatement(es) => Ok(Some(self.emit_expression(&es.expression, ctx)?)),
|
||||
@@ -348,6 +443,19 @@ impl Compiler {
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
Statement::ClassDeclaration(class) => {
|
||||
let name = class
|
||||
.id
|
||||
.as_ref()
|
||||
.map(|id| id.name.to_string())
|
||||
.ok_or_else(|| "ClassDeclaration without name".to_string())?;
|
||||
let var_reg = ctx.alloc_reg();
|
||||
ctx.declare(&name, var_reg, VariableDeclarationKind::Let, false)?;
|
||||
ctx.init_var(&name);
|
||||
let ctor_reg = self.emit_class(class, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, ctor_reg, 0));
|
||||
Ok(None)
|
||||
}
|
||||
Statement::ThrowStatement(ts) => {
|
||||
let exc_reg = self.emit_expression(&ts.argument, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::THROW, exc_reg, 0, 0));
|
||||
@@ -1014,6 +1122,7 @@ impl Compiler {
|
||||
ctx.emit_load_const(r, const_idx);
|
||||
Ok(r)
|
||||
}
|
||||
Expression::ClassExpression(class) => self.emit_class(class, ctx),
|
||||
Expression::NewExpression(ne) => {
|
||||
let constructor_reg = self.emit_expression(&ne.callee, ctx)?;
|
||||
let mut arg_regs = Vec::new();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use oxide_parser::{Expression, ForStatementInit, SimpleAssignmentTarget, Statement};
|
||||
use oxide_parser::{ClassElement, Expression, ForStatementInit, PropertyKey, SimpleAssignmentTarget, Statement};
|
||||
|
||||
pub fn structural_hash(program: &oxide_parser::Program) -> u64 {
|
||||
use std::hash::Hasher;
|
||||
@@ -117,6 +117,16 @@ fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Statement::ClassDeclaration(class) => {
|
||||
13u8.hash(h);
|
||||
if let Some(id) = &class.id {
|
||||
id.name.as_str().hash(h);
|
||||
}
|
||||
class.super_class.is_some().hash(h);
|
||||
for element in &class.body.body {
|
||||
hash_class_element(element, h);
|
||||
}
|
||||
}
|
||||
Statement::ThrowStatement(ts) => {
|
||||
14u8.hash(h);
|
||||
hash_expression(&ts.argument, h);
|
||||
@@ -148,6 +158,53 @@ fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) {
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_property_key(key: &PropertyKey, h: &mut rustc_hash::FxHasher) {
|
||||
use std::hash::Hash;
|
||||
|
||||
match key {
|
||||
PropertyKey::StaticIdentifier(ident) => {
|
||||
0u8.hash(h);
|
||||
ident.name.as_str().hash(h);
|
||||
}
|
||||
PropertyKey::StringLiteral(s) => {
|
||||
1u8.hash(h);
|
||||
s.value.hash(h);
|
||||
}
|
||||
PropertyKey::PrivateIdentifier(pi) => {
|
||||
2u8.hash(h);
|
||||
pi.name.as_str().hash(h);
|
||||
}
|
||||
_ => {
|
||||
3u8.hash(h);
|
||||
std::mem::discriminant(key).hash(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_class_element(element: &ClassElement, h: &mut rustc_hash::FxHasher) {
|
||||
use std::hash::Hash;
|
||||
|
||||
match element {
|
||||
ClassElement::MethodDefinition(method) => {
|
||||
0u8.hash(h);
|
||||
method.r#static.hash(h);
|
||||
method.computed.hash(h);
|
||||
std::mem::discriminant(&method.kind).hash(h);
|
||||
hash_property_key(&method.key, h);
|
||||
(method.value.params.items.len() as u32).hash(h);
|
||||
if let Some(body) = &method.value.body {
|
||||
for stmt in &body.statements {
|
||||
hash_statement(stmt, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
1u8.hash(h);
|
||||
std::mem::discriminant(element).hash(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
|
||||
use std::hash::Hash;
|
||||
|
||||
@@ -256,6 +313,16 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::ClassExpression(class) => {
|
||||
18u8.hash(h);
|
||||
if let Some(id) = &class.id {
|
||||
id.name.as_str().hash(h);
|
||||
}
|
||||
class.super_class.is_some().hash(h);
|
||||
for element in &class.body.body {
|
||||
hash_class_element(element, h);
|
||||
}
|
||||
}
|
||||
Expression::NewExpression(ne) => {
|
||||
14u8.hash(h);
|
||||
hash_expression(&ne.callee, h);
|
||||
|
||||
@@ -31,6 +31,9 @@ pub struct CompiledModule {
|
||||
/// Function name inferred from assignment context (D-04).
|
||||
/// Set at the VariableDeclaration / ObjectProperty assignment site.
|
||||
pub function_name: Option<String>,
|
||||
/// True when this bytecode function is a class constructor.
|
||||
/// Ordinary CALL must reject it, while NEW_EXPRESSION may construct through it.
|
||||
pub is_class_constructor: bool,
|
||||
}
|
||||
|
||||
impl CompiledModule {
|
||||
@@ -46,6 +49,7 @@ impl CompiledModule {
|
||||
is_arrow: false,
|
||||
captured_this_const_idx: 0,
|
||||
function_name: None,
|
||||
is_class_constructor: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +73,7 @@ impl Clone for CompiledModule {
|
||||
is_arrow: self.is_arrow,
|
||||
captured_this_const_idx: self.captured_this_const_idx,
|
||||
function_name: self.function_name.clone(),
|
||||
is_class_constructor: self.is_class_constructor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user