mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: 支持可选链空值合并与逻辑赋值
This commit is contained in:
@@ -17,21 +17,28 @@ pub(crate) fn is_int_literal(value: f64) -> bool {
|
||||
}
|
||||
|
||||
pub(crate) fn is_side_effect_free(expr: &Expression) -> bool {
|
||||
match expr {
|
||||
Expression::NumericLiteral(_)
|
||||
| Expression::StringLiteral(_)
|
||||
| Expression::BooleanLiteral(_)
|
||||
| Expression::NullLiteral(_)
|
||||
| Expression::Identifier(_)
|
||||
| Expression::RegExpLiteral(_)
|
||||
| Expression::ThisExpression(_) => true,
|
||||
Expression::ParenthesizedExpression(p) => is_side_effect_free(&p.expression),
|
||||
Expression::BinaryExpression(bin) => is_side_effect_free(&bin.left) && is_side_effect_free(&bin.right),
|
||||
Expression::UnaryExpression(un) => {
|
||||
!matches!(un.operator, UnaryOperator::Delete) && is_side_effect_free(&un.argument)
|
||||
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,
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
const BUILTIN_GLOBALS: &[&str] = &[
|
||||
|
||||
@@ -1,11 +1,146 @@
|
||||
use oxide_parser::{
|
||||
AssignmentOperator, ClassElement, Expression, ForStatementInit, MethodDefinitionKind, SimpleAssignmentTarget,
|
||||
Statement, UnaryOperator, VariableDeclarationKind,
|
||||
AssignmentOperator, ChainElement, ClassElement, Expression, ForStatementInit, LogicalOperator,
|
||||
MethodDefinitionKind, SimpleAssignmentTarget, Statement, UnaryOperator, VariableDeclarationKind,
|
||||
};
|
||||
|
||||
use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label};
|
||||
|
||||
impl Compiler {
|
||||
fn count_optional_guard(&self, ctx: &mut CompileCtx) {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2; // LOAD_VAR dup + JMP_IF_NULLISH
|
||||
}
|
||||
|
||||
fn count_static_member_get_preserve_base(
|
||||
&self, member: &oxide_parser::StaticMemberExpression, short_chain: bool, ctx: &mut CompileCtx,
|
||||
) {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST key
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 5; // LOAD_VAR copy + IC_GET_PROP + 3 ext words
|
||||
}
|
||||
|
||||
fn count_computed_member_get_preserve_base(
|
||||
&self, member: &oxide_parser::ComputedMemberExpression, short_chain: bool, ctx: &mut CompileCtx,
|
||||
) {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // GET_PROP_DYNAMIC
|
||||
}
|
||||
|
||||
fn count_chain_call(&self, call: &oxide_parser::CallExpression, short_chain: bool, ctx: &mut CompileCtx) {
|
||||
match &call.callee {
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
self.count_static_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
self.count_computed_member_get_preserve_base(member, short_chain, ctx);
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2;
|
||||
}
|
||||
_ => {
|
||||
self.count_chainable_expression(&call.callee, short_chain, ctx);
|
||||
if short_chain && call.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST undefined this
|
||||
}
|
||||
}
|
||||
if short_chain
|
||||
&& call.optional
|
||||
&& matches!(
|
||||
&call.callee,
|
||||
Expression::StaticMemberExpression(_)
|
||||
| Expression::ComputedMemberExpression(_)
|
||||
| Expression::PrivateFieldExpression(_)
|
||||
)
|
||||
{
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
for arg in &call.arguments {
|
||||
if let Some(expr) = arg.as_expression() {
|
||||
self.count_expression(expr, ctx);
|
||||
}
|
||||
}
|
||||
ctx.projected_pc += 2; // CALL + arg_count ext word
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- regs[0]
|
||||
}
|
||||
|
||||
fn count_chainable_expression(&self, expr: &Expression, short_chain: bool, ctx: &mut CompileCtx) {
|
||||
match expr {
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
self.count_static_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
self.count_computed_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2;
|
||||
}
|
||||
Expression::CallExpression(call) => self.count_chain_call(call, short_chain, ctx),
|
||||
Expression::ChainExpression(chain) => self.count_chain_element(&chain.expression, short_chain, ctx),
|
||||
_ => self.count_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
fn count_chain_element(&self, element: &ChainElement, short_chain: bool, ctx: &mut CompileCtx) {
|
||||
match element {
|
||||
ChainElement::StaticMemberExpression(member) => {
|
||||
self.count_static_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
ChainElement::ComputedMemberExpression(member) => {
|
||||
self.count_computed_member_get_preserve_base(member, short_chain, ctx)
|
||||
}
|
||||
ChainElement::PrivateFieldExpression(member) => {
|
||||
self.count_chainable_expression(&member.object, short_chain, ctx);
|
||||
if short_chain && member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.alloc_reg();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2;
|
||||
}
|
||||
ChainElement::CallExpression(call) => self.count_chain_call(call, short_chain, ctx),
|
||||
ChainElement::TSNonNullExpression(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn count_logical_assign_test(&self, op: LogicalOperator, id: u32, ctx: &mut CompileCtx) {
|
||||
match op {
|
||||
LogicalOperator::And | LogicalOperator::Or => {
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
LogicalOperator::Coalesce => {
|
||||
ctx.projected_pc += 1; // JMP_IF_NULLISH to store body
|
||||
ctx.projected_pc += 1; // JMP to end on non-nullish
|
||||
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn count_binding_pattern(&self, pattern: &oxide_parser::BindingPattern, ctx: &mut CompileCtx) {
|
||||
match pattern {
|
||||
oxide_parser::BindingPattern::BindingIdentifier(_) => {
|
||||
@@ -509,6 +644,28 @@ impl Compiler {
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC
|
||||
}
|
||||
Expression::ChainExpression(chain) => {
|
||||
match &chain.expression {
|
||||
ChainElement::StaticMemberExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
if member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
ctx.projected_pc += 2; // DELETE_PROP_STATIC + ext word
|
||||
}
|
||||
ChainElement::ComputedMemberExpression(member) => {
|
||||
self.count_expression(&member.object, ctx);
|
||||
if member.optional {
|
||||
self.count_optional_guard(ctx);
|
||||
}
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.projected_pc += 1; // DELETE_PROP_DYNAMIC
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ctx.projected_pc += 1; // JMP over nullish true writer
|
||||
ctx.projected_pc += 1; // LOAD_CONST true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -570,6 +727,20 @@ impl Compiler {
|
||||
}
|
||||
Expression::AssignmentExpression(assign) => {
|
||||
if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left {
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
self.count_expression(&member.object, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST key
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 5; // LOAD_VAR + IC_GET_PROP + 3 ext
|
||||
self.count_logical_assign_test(logical_op, id, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.projected_pc += 4; // IC_SET_PROP + 3 ext
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
|
||||
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
|
||||
return;
|
||||
}
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
if assign.operator != AssignmentOperator::Assign {
|
||||
@@ -581,6 +752,19 @@ impl Compiler {
|
||||
ctx.projected_pc += 1; // IC_SET_PROP or COMPOUND_MEMBER_*
|
||||
ctx.projected_pc += 3; // 3 IC ext words
|
||||
} else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left {
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&member.expression, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // GET_PROP_DYNAMIC
|
||||
self.count_logical_assign_test(logical_op, id, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.projected_pc += 1; // SET_PROP_DYNAMIC
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
|
||||
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
|
||||
return;
|
||||
}
|
||||
self.count_expression(&member.object, ctx);
|
||||
self.count_expression(&member.expression, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
@@ -591,6 +775,25 @@ impl Compiler {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 2; // LOAD_CONST private id + SET_PRIVATE
|
||||
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(_) = &assign.left {
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- current var
|
||||
self.count_logical_assign_test(logical_op, id, ctx);
|
||||
self.count_expression(&assign.right, ctx);
|
||||
ctx.projected_pc += 1; // STORE_VAR
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- rhs
|
||||
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
|
||||
} else {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
if assign.operator != AssignmentOperator::Assign {
|
||||
ctx.projected_pc += 1; // COMPOUND_* on var
|
||||
} else {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // STORE_VAR
|
||||
}
|
||||
}
|
||||
} else if let oxide_parser::AssignmentTarget::ArrayAssignmentTarget(ap) = &assign.left {
|
||||
self.count_expression(&assign.right, ctx);
|
||||
self.count_array_assignment(ap, ctx);
|
||||
@@ -638,7 +841,11 @@ impl Compiler {
|
||||
let id = ctx.next_label_id();
|
||||
ctx.alloc_reg(); // dup register
|
||||
ctx.projected_pc += 1; // LOAD_VAR (DUP)
|
||||
ctx.projected_pc += 1; // JMP_IF_FALSE or JMP_IF_TRUE
|
||||
ctx.projected_pc += 1; // JMP_IF_FALSE, JMP_IF_TRUE, or JMP_IF_NULLISH
|
||||
if matches!(log.operator, LogicalOperator::Coalesce) {
|
||||
ctx.projected_pc += 1; // JMP over RHS on non-nullish
|
||||
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
|
||||
}
|
||||
self.count_expression(&log.right, ctx);
|
||||
ctx.projected_pc += 1; // LOAD_VAR (overwrite)
|
||||
let skip_label = match log.operator {
|
||||
@@ -649,6 +856,16 @@ impl Compiler {
|
||||
ctx.label_map.insert(skip_label, ctx.projected_pc);
|
||||
}
|
||||
}
|
||||
Expression::ChainExpression(chain) => {
|
||||
let id = ctx.next_label_id();
|
||||
self.count_chain_element(&chain.expression, true, ctx);
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_VAR result <- chain value
|
||||
ctx.projected_pc += 1; // JMP over short-circuit writer
|
||||
ctx.label_map.insert(Label::TernaryElse(id), ctx.projected_pc);
|
||||
ctx.projected_pc += 1; // LOAD_CONST undefined
|
||||
ctx.label_map.insert(Label::TernaryEnd(id), ctx.projected_pc);
|
||||
}
|
||||
Expression::ObjectExpression(obj) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // NEW_OBJECT
|
||||
|
||||
@@ -2,9 +2,9 @@ use crate::compiler::Label;
|
||||
use crate::opcode::{self, OpCode};
|
||||
use oxide_parser::{
|
||||
AssignmentOperator, AssignmentTarget, AssignmentTargetMaybeDefault, AssignmentTargetProperty, BindingPattern,
|
||||
Class, ClassElement, Expression, ForStatementInit, ForStatementLeft, MethodDefinitionKind, ObjectAssignmentTarget,
|
||||
PropertyKey, PropertyKind, SimpleAssignmentTarget, Statement, UnaryOperator, UpdateOperator,
|
||||
VariableDeclarationKind,
|
||||
ChainElement, Class, ClassElement, Expression, ForStatementInit, ForStatementLeft, LogicalOperator,
|
||||
MethodDefinitionKind, ObjectAssignmentTarget, PropertyKey, PropertyKind, SimpleAssignmentTarget, Statement,
|
||||
UnaryOperator, UpdateOperator, VariableDeclarationKind,
|
||||
};
|
||||
|
||||
use crate::compiler::{
|
||||
@@ -13,6 +13,201 @@ use crate::compiler::{
|
||||
use crate::module::Constant;
|
||||
|
||||
impl Compiler {
|
||||
fn emit_optional_guard(&self, reg: u8, short_label: Label, ctx: &mut CompileCtx) -> Result<(), String> {
|
||||
let short_pos = ctx.resolve_label(short_label)?;
|
||||
let dup_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, reg, 0));
|
||||
let offset = (short_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, offset as i16));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_static_member_get_preserve_base(
|
||||
&self, member: &oxide_parser::StaticMemberExpression, short_label: Option<Label>, ctx: &mut CompileCtx,
|
||||
) -> Result<(u8, u8), String> {
|
||||
let obj_reg = self.emit_chainable_expression(&member.object, short_label, ctx)?;
|
||||
if member.optional {
|
||||
if let Some(label) = short_label {
|
||||
self.emit_optional_guard(obj_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let idx = ctx.add_constant(Constant::String(member.property.name.as_str().to_string()));
|
||||
let key_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(key_reg, idx);
|
||||
let value_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, value_reg, obj_reg, 0));
|
||||
ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, value_reg, key_reg));
|
||||
ctx.emit(0);
|
||||
ctx.emit(0);
|
||||
ctx.emit(0);
|
||||
Ok((value_reg, obj_reg))
|
||||
}
|
||||
|
||||
fn emit_computed_member_get_preserve_base(
|
||||
&self, member: &oxide_parser::ComputedMemberExpression, short_label: Option<Label>, ctx: &mut CompileCtx,
|
||||
) -> Result<(u8, u8), String> {
|
||||
let obj_reg = self.emit_chainable_expression(&member.object, short_label, ctx)?;
|
||||
if member.optional {
|
||||
if let Some(label) = short_label {
|
||||
self.emit_optional_guard(obj_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let key_reg = self.emit_expression(&member.expression, ctx)?;
|
||||
let value_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::GET_PROP_DYNAMIC, obj_reg, key_reg, value_reg));
|
||||
Ok((value_reg, obj_reg))
|
||||
}
|
||||
|
||||
fn emit_chain_call(
|
||||
&self, call: &oxide_parser::CallExpression, short_label: Option<Label>, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
let (callee_reg, this_reg) = match &call.callee {
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
self.emit_static_member_get_preserve_base(member, short_label, ctx)?
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
self.emit_computed_member_get_preserve_base(member, short_label, ctx)?
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
let obj_reg = self.emit_chainable_expression(&member.object, short_label, ctx)?;
|
||||
if member.optional {
|
||||
if let Some(label) = short_label {
|
||||
self.emit_optional_guard(obj_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let callee_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::GET_PRIVATE, callee_reg, obj_reg, key_reg));
|
||||
(callee_reg, obj_reg)
|
||||
}
|
||||
_ => {
|
||||
let callee_reg = self.emit_chainable_expression(&call.callee, short_label, ctx)?;
|
||||
if call.optional {
|
||||
if let Some(label) = short_label {
|
||||
self.emit_optional_guard(callee_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let this_idx = ctx.add_constant(Constant::Undefined);
|
||||
let this_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(this_reg, this_idx);
|
||||
(callee_reg, this_reg)
|
||||
}
|
||||
};
|
||||
if call.optional
|
||||
&& matches!(
|
||||
&call.callee,
|
||||
Expression::StaticMemberExpression(_)
|
||||
| Expression::ComputedMemberExpression(_)
|
||||
| Expression::PrivateFieldExpression(_)
|
||||
)
|
||||
{
|
||||
if let Some(label) = short_label {
|
||||
self.emit_optional_guard(callee_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let mut arg_regs = Vec::new();
|
||||
for arg in &call.arguments {
|
||||
if let Some(expr) = arg.as_expression() {
|
||||
arg_regs.push(self.emit_expression(expr, ctx)?);
|
||||
}
|
||||
}
|
||||
let first_arg_reg = if arg_regs.is_empty() { 0u8 } else { arg_regs[0] };
|
||||
let op = match &call.callee {
|
||||
Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => OpCode::CALL_NATIVE,
|
||||
_ => OpCode::CALL,
|
||||
};
|
||||
ctx.emit(opcode::encode(op, callee_reg, this_reg, first_arg_reg));
|
||||
ctx.emit(arg_regs.len() as u32);
|
||||
let result_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, 0, 0));
|
||||
Ok(result_reg)
|
||||
}
|
||||
|
||||
fn emit_chainable_expression(
|
||||
&self, expr: &Expression, short_label: Option<Label>, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
match expr {
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
let (value_reg, _) = self.emit_static_member_get_preserve_base(member, short_label, ctx)?;
|
||||
Ok(value_reg)
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
let (value_reg, _) = self.emit_computed_member_get_preserve_base(member, short_label, ctx)?;
|
||||
Ok(value_reg)
|
||||
}
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
let obj_reg = self.emit_chainable_expression(&member.object, short_label, ctx)?;
|
||||
if member.optional {
|
||||
if let Some(label) = short_label {
|
||||
self.emit_optional_guard(obj_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let value_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::GET_PRIVATE, value_reg, obj_reg, key_reg));
|
||||
Ok(value_reg)
|
||||
}
|
||||
Expression::CallExpression(call) => self.emit_chain_call(call, short_label, ctx),
|
||||
Expression::ChainExpression(chain) => self.emit_chain_element(&chain.expression, short_label, ctx),
|
||||
_ => self.emit_expression(expr, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_chain_element(
|
||||
&self, element: &ChainElement, short_label: Option<Label>, ctx: &mut CompileCtx,
|
||||
) -> Result<u8, String> {
|
||||
match element {
|
||||
ChainElement::StaticMemberExpression(member) => {
|
||||
let (value_reg, _) = self.emit_static_member_get_preserve_base(member, short_label, ctx)?;
|
||||
Ok(value_reg)
|
||||
}
|
||||
ChainElement::ComputedMemberExpression(member) => {
|
||||
let (value_reg, _) = self.emit_computed_member_get_preserve_base(member, short_label, ctx)?;
|
||||
Ok(value_reg)
|
||||
}
|
||||
ChainElement::PrivateFieldExpression(member) => {
|
||||
let obj_reg = self.emit_chainable_expression(&member.object, short_label, ctx)?;
|
||||
if member.optional {
|
||||
if let Some(label) = short_label {
|
||||
self.emit_optional_guard(obj_reg, label, ctx)?;
|
||||
}
|
||||
}
|
||||
let key_reg = self.emit_private_id_reg(member.field.name.as_str(), ctx)?;
|
||||
let value_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::GET_PRIVATE, value_reg, obj_reg, key_reg));
|
||||
Ok(value_reg)
|
||||
}
|
||||
ChainElement::CallExpression(call) => self.emit_chain_call(call, short_label, ctx),
|
||||
ChainElement::TSNonNullExpression(_) => Err("TS non-null expressions are not supported in JS mode".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_logical_assign_test(
|
||||
&self, op: LogicalOperator, test_reg: u8, store_label: Label, end_label: Label, ctx: &mut CompileCtx,
|
||||
) -> Result<(), String> {
|
||||
match op {
|
||||
LogicalOperator::And => {
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_false(test_reg, offset as i16));
|
||||
}
|
||||
LogicalOperator::Or => {
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset as i16));
|
||||
}
|
||||
LogicalOperator::Coalesce => {
|
||||
let store_pos = ctx.resolve_label(store_label)?;
|
||||
let offset = (store_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(test_reg, offset as i16));
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn private_name_id(&self, name: &str, ctx: &CompileCtx) -> Result<u32, String> {
|
||||
ctx.private_name_map
|
||||
.iter()
|
||||
@@ -1335,6 +1530,77 @@ impl Compiler {
|
||||
Ok(left)
|
||||
}
|
||||
Expression::UnaryExpression(un) => {
|
||||
if matches!(un.operator, UnaryOperator::Delete) {
|
||||
return match &un.argument {
|
||||
Expression::Identifier(_) => {
|
||||
Err("SyntaxError: delete of an unqualified identifier in strict mode".into())
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let prop_name = member.property.name.as_str();
|
||||
let const_idx = ctx.add_constant(Constant::String(prop_name.to_string()));
|
||||
ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0));
|
||||
ctx.emit(const_idx as u32);
|
||||
Ok(obj_reg)
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let key_reg = self.emit_expression(&member.expression, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg));
|
||||
Ok(obj_reg)
|
||||
}
|
||||
Expression::ChainExpression(chain) => {
|
||||
let mut nullish_jumps = Vec::new();
|
||||
let result_reg = match &chain.expression {
|
||||
ChainElement::StaticMemberExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
if member.optional {
|
||||
let dup_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, obj_reg, 0));
|
||||
let jump_pos = ctx.bytecode.len();
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0));
|
||||
nullish_jumps.push(jump_pos);
|
||||
}
|
||||
let prop_name = member.property.name.as_str();
|
||||
let const_idx = ctx.add_constant(Constant::String(prop_name.to_string()));
|
||||
ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0));
|
||||
ctx.emit(const_idx as u32);
|
||||
obj_reg
|
||||
}
|
||||
ChainElement::ComputedMemberExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
if member.optional {
|
||||
let dup_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, obj_reg, 0));
|
||||
let jump_pos = ctx.bytecode.len();
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0));
|
||||
nullish_jumps.push(jump_pos);
|
||||
}
|
||||
let key_reg = self.emit_expression(&member.expression, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg));
|
||||
obj_reg
|
||||
}
|
||||
_ => return Err("invalid delete target".into()),
|
||||
};
|
||||
let end_jump_pos = ctx.bytecode.len();
|
||||
ctx.emit(opcode::encode_jmp(0));
|
||||
let short_pos = ctx.bytecode.len();
|
||||
for jump_pos in nullish_jumps {
|
||||
let offset = (short_pos as isize) - (jump_pos as isize);
|
||||
let instr = ctx.bytecode[jump_pos];
|
||||
let rd = opcode::rd(instr);
|
||||
ctx.bytecode[jump_pos] = opcode::encode_jmp_if_nullish(rd, offset as i16);
|
||||
}
|
||||
let true_idx = ctx.add_constant(Constant::Boolean(true));
|
||||
ctx.emit_load_const(result_reg, true_idx);
|
||||
let end_pos = ctx.bytecode.len();
|
||||
let offset = (end_pos as isize) - (end_jump_pos as isize);
|
||||
ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset as i16);
|
||||
Ok(result_reg)
|
||||
}
|
||||
_ => Err("invalid delete target".into()),
|
||||
};
|
||||
}
|
||||
let arg = self.emit_expression(&un.argument, ctx)?;
|
||||
match un.operator {
|
||||
UnaryOperator::UnaryNegation => {
|
||||
@@ -1361,26 +1627,7 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::UNARY_PLUS, arg, arg, 0));
|
||||
Ok(arg)
|
||||
}
|
||||
UnaryOperator::Delete => match &un.argument {
|
||||
Expression::Identifier(_) => {
|
||||
Err("SyntaxError: delete of an unqualified identifier in strict mode".into())
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let prop_name = member.property.name.as_str();
|
||||
let const_idx = ctx.add_constant(Constant::String(prop_name.to_string()));
|
||||
ctx.emit(opcode::encode(OpCode::DELETE_PROP_STATIC, obj_reg, obj_reg, 0));
|
||||
ctx.emit(const_idx as u32);
|
||||
Ok(obj_reg)
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let key_reg = self.emit_expression(&member.expression, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::DELETE_PROP_DYNAMIC, obj_reg, obj_reg, key_reg));
|
||||
Ok(obj_reg)
|
||||
}
|
||||
_ => Err("invalid delete target".into()),
|
||||
},
|
||||
UnaryOperator::Delete => unreachable!("delete handled before argument emit"),
|
||||
}
|
||||
}
|
||||
Expression::ConditionalExpression(cond) => {
|
||||
@@ -1417,20 +1664,33 @@ impl Compiler {
|
||||
let op = match log.operator {
|
||||
LogicalOperator::And => OpCode::AND,
|
||||
LogicalOperator::Or => OpCode::OR,
|
||||
LogicalOperator::Coalesce => {
|
||||
return Err("nullish coalescing not supported".into());
|
||||
}
|
||||
LogicalOperator::Coalesce => OpCode::NULLISH,
|
||||
};
|
||||
ctx.emit(opcode::encode(op, r, left_reg, right_reg));
|
||||
Ok(r)
|
||||
} else {
|
||||
let id = ctx.next_label_id();
|
||||
if matches!(log.operator, LogicalOperator::Coalesce) {
|
||||
let dup_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, left_reg, 0));
|
||||
let nullish_jump_pos = ctx.bytecode.len();
|
||||
ctx.emit(opcode::encode_jmp_if_nullish(dup_reg, 0));
|
||||
let end_jump_pos = ctx.bytecode.len();
|
||||
ctx.emit(opcode::encode_jmp(0));
|
||||
let rhs_pos = ctx.bytecode.len();
|
||||
let right_reg = self.emit_expression(&log.right, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, dup_reg, right_reg, 0));
|
||||
let end_pos = ctx.bytecode.len();
|
||||
let offset = (rhs_pos as isize) - (nullish_jump_pos as isize);
|
||||
ctx.bytecode[nullish_jump_pos] = opcode::encode_jmp_if_nullish(dup_reg, offset as i16);
|
||||
let offset = (end_pos as isize) - (end_jump_pos as isize);
|
||||
ctx.bytecode[end_jump_pos] = opcode::encode_jmp(offset as i16);
|
||||
return Ok(dup_reg);
|
||||
}
|
||||
let skip_label = match log.operator {
|
||||
LogicalOperator::And => Label::TernaryEnd(id),
|
||||
LogicalOperator::Or => Label::TernaryElse(id),
|
||||
LogicalOperator::Coalesce => {
|
||||
return Err("nullish coalescing not supported".into());
|
||||
}
|
||||
LogicalOperator::Coalesce => unreachable!(),
|
||||
};
|
||||
let skip_pos = ctx.resolve_label(skip_label)?;
|
||||
|
||||
@@ -1445,9 +1705,7 @@ impl Compiler {
|
||||
LogicalOperator::Or => {
|
||||
ctx.emit(opcode::encode_jmp_if_true(dup_reg, offset as i16));
|
||||
}
|
||||
LogicalOperator::Coalesce => {
|
||||
return Err("nullish coalescing not supported".into());
|
||||
}
|
||||
LogicalOperator::Coalesce => unreachable!(),
|
||||
}
|
||||
|
||||
let right_reg = self.emit_expression(&log.right, ctx)?;
|
||||
@@ -1501,6 +1759,20 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::GET_PRIVATE, r, obj_reg, key_reg));
|
||||
Ok(r)
|
||||
}
|
||||
Expression::ChainExpression(chain) => {
|
||||
let id = ctx.next_label_id();
|
||||
let short_label = Label::TernaryElse(id);
|
||||
let end_label = Label::TernaryEnd(id);
|
||||
let value_reg = self.emit_chain_element(&chain.expression, Some(short_label), ctx)?;
|
||||
let result_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, value_reg, 0));
|
||||
let end_pos = ctx.resolve_label(end_label)?;
|
||||
let offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||
let undefined_idx = ctx.add_constant(Constant::Undefined);
|
||||
ctx.emit_load_const(result_reg, undefined_idx);
|
||||
Ok(result_reg)
|
||||
}
|
||||
Expression::ObjectExpression(obj) => {
|
||||
let obj_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::NEW_OBJECT, obj_reg, 0, 0));
|
||||
@@ -1567,6 +1839,30 @@ impl Compiler {
|
||||
}
|
||||
Expression::AssignmentExpression(assign) => {
|
||||
if let oxide_parser::AssignmentTarget::StaticMemberExpression(member) = &assign.left {
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
let store_label = Label::TernaryElse(id);
|
||||
let end_label = Label::TernaryEnd(id);
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let prop_name = member.property.name.as_str();
|
||||
let idx = ctx.add_constant(Constant::String(prop_name.to_string()));
|
||||
let key_reg = ctx.alloc_reg();
|
||||
ctx.emit_load_const(key_reg, idx);
|
||||
let result_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, obj_reg, 0));
|
||||
ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, result_reg, key_reg));
|
||||
ctx.emit(0);
|
||||
ctx.emit(0);
|
||||
ctx.emit(0);
|
||||
self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?;
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::IC_SET_PROP, obj_reg, val_reg, key_reg));
|
||||
ctx.emit(0);
|
||||
ctx.emit(0);
|
||||
ctx.emit(0);
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0));
|
||||
return Ok(result_reg);
|
||||
}
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
let prop_name = member.property.name.as_str();
|
||||
@@ -1598,6 +1894,20 @@ impl Compiler {
|
||||
Ok(val_reg)
|
||||
}
|
||||
} else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) = &assign.left {
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
let store_label = Label::TernaryElse(id);
|
||||
let end_label = Label::TernaryEnd(id);
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let key_reg = self.emit_expression(&member.expression, ctx)?;
|
||||
let result_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::GET_PROP_DYNAMIC, obj_reg, key_reg, result_reg));
|
||||
self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?;
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, obj_reg, key_reg, val_reg));
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0));
|
||||
return Ok(result_reg);
|
||||
}
|
||||
let obj_reg = self.emit_expression(&member.object, ctx)?;
|
||||
let key_reg = self.emit_expression(&member.expression, ctx)?;
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
@@ -1614,7 +1924,22 @@ impl Compiler {
|
||||
Ok(val_reg)
|
||||
} else if let oxide_parser::AssignmentTarget::AssignmentTargetIdentifier(id_ref) = &assign.left {
|
||||
if assign.operator != AssignmentOperator::Assign {
|
||||
if assign.operator == AssignmentOperator::Addition
|
||||
if let Some(logical_op) = assign.operator.to_logical_operator() {
|
||||
let id = ctx.next_label_id();
|
||||
let store_label = Label::TernaryElse(id);
|
||||
let end_label = Label::TernaryEnd(id);
|
||||
let name = id_ref.name.as_str();
|
||||
let var_reg = ctx.lookup_or_global(name);
|
||||
let result_reg = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, var_reg, 0));
|
||||
self.emit_logical_assign_test(logical_op, result_reg, store_label, end_label, ctx)?;
|
||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||
let is_const = ctx.lookup_const_flag(name);
|
||||
let const_flag = if is_const { 1 } else { 0 };
|
||||
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, const_flag));
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, result_reg, val_reg, 0));
|
||||
Ok(result_reg)
|
||||
} else if assign.operator == AssignmentOperator::Addition
|
||||
|| assign.operator == AssignmentOperator::Subtraction
|
||||
|| assign.operator == AssignmentOperator::Multiplication
|
||||
|| assign.operator == AssignmentOperator::Division
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use oxide_parser::{
|
||||
BindingPattern, ClassElement, Expression, ForStatementInit, ObjectPropertyKind, PropertyKey,
|
||||
BindingPattern, ChainElement, ClassElement, Expression, ForStatementInit, ObjectPropertyKind, PropertyKey,
|
||||
SimpleAssignmentTarget, Statement,
|
||||
};
|
||||
use std::hash::Hash;
|
||||
@@ -387,6 +387,20 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, include_bind
|
||||
Expression::PrivateFieldExpression(member) => {
|
||||
hash_expression(&member.object, h, include_binding_names);
|
||||
member.field.name.as_str().hash(h);
|
||||
member.optional.hash(h);
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
hash_expression(&member.object, h, include_binding_names);
|
||||
member.property.name.as_str().hash(h);
|
||||
member.optional.hash(h);
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) => {
|
||||
hash_expression(&member.object, h, include_binding_names);
|
||||
hash_expression(&member.expression, h, include_binding_names);
|
||||
member.optional.hash(h);
|
||||
}
|
||||
Expression::ChainExpression(chain) => {
|
||||
hash_chain_element(&chain.expression, h, include_binding_names);
|
||||
}
|
||||
Expression::Super(_) => {}
|
||||
Expression::RegExpLiteral(lit) => {
|
||||
@@ -398,6 +412,40 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, include_bind
|
||||
});
|
||||
}
|
||||
|
||||
fn hash_chain_element(element: &ChainElement, h: &mut rustc_hash::FxHasher, include_binding_names: bool) {
|
||||
std::mem::discriminant(element).hash(h);
|
||||
match element {
|
||||
ChainElement::StaticMemberExpression(member) => {
|
||||
hash_expression(&member.object, h, include_binding_names);
|
||||
member.property.name.as_str().hash(h);
|
||||
member.optional.hash(h);
|
||||
}
|
||||
ChainElement::ComputedMemberExpression(member) => {
|
||||
hash_expression(&member.object, h, include_binding_names);
|
||||
hash_expression(&member.expression, h, include_binding_names);
|
||||
member.optional.hash(h);
|
||||
}
|
||||
ChainElement::PrivateFieldExpression(member) => {
|
||||
hash_expression(&member.object, h, include_binding_names);
|
||||
member.field.name.as_str().hash(h);
|
||||
member.optional.hash(h);
|
||||
}
|
||||
ChainElement::CallExpression(call) => {
|
||||
hash_expression(&call.callee, h, include_binding_names);
|
||||
call.optional.hash(h);
|
||||
(call.arguments.len() as u32).hash(h);
|
||||
for arg in &call.arguments {
|
||||
if let Some(expr) = arg.as_expression() {
|
||||
hash_expression(expr, h, include_binding_names);
|
||||
}
|
||||
}
|
||||
}
|
||||
ChainElement::TSNonNullExpression(expr) => {
|
||||
hash_expression(&expr.expression, h, include_binding_names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_object_property_kind(prop: &ObjectPropertyKind, h: &mut rustc_hash::FxHasher, include_binding_names: bool) {
|
||||
hash_match!(HashDomain::ObjectPropertyKind, prop, h, {
|
||||
ObjectPropertyKind::ObjectProperty(p) => {
|
||||
|
||||
@@ -142,6 +142,8 @@ pub enum OpCode {
|
||||
COMPOUND_SHL = 0x8B,
|
||||
COMPOUND_SHR = 0x8C,
|
||||
COMPOUND_USHR = 0x8D,
|
||||
NULLISH = 0x8E,
|
||||
JMP_IF_NULLISH = 0x8F,
|
||||
|
||||
// ── Misc (0xF0-0xFF) ──
|
||||
NOP = 0xF0,
|
||||
@@ -290,6 +292,8 @@ impl TryFrom<u8> for OpCode {
|
||||
0x8B => Ok(OpCode::COMPOUND_SHL),
|
||||
0x8C => Ok(OpCode::COMPOUND_SHR),
|
||||
0x8D => Ok(OpCode::COMPOUND_USHR),
|
||||
0x8E => Ok(OpCode::NULLISH),
|
||||
0x8F => Ok(OpCode::JMP_IF_NULLISH),
|
||||
0xF0 => Ok(OpCode::NOP),
|
||||
0xF1 => Ok(OpCode::HALT),
|
||||
0xF2 => Ok(OpCode::TYPEOF),
|
||||
@@ -407,6 +411,8 @@ impl fmt::Display for OpCode {
|
||||
OpCode::COMPOUND_SHL => "COMPOUND_SHL",
|
||||
OpCode::COMPOUND_SHR => "COMPOUND_SHR",
|
||||
OpCode::COMPOUND_USHR => "COMPOUND_USHR",
|
||||
OpCode::NULLISH => "NULLISH",
|
||||
OpCode::JMP_IF_NULLISH => "JMP_IF_NULLISH",
|
||||
OpCode::NOP => "NOP",
|
||||
OpCode::HALT => "HALT",
|
||||
OpCode::TYPEOF => "TYPEOF",
|
||||
@@ -470,6 +476,12 @@ pub fn encode_jmp_if_true(rd: u8, offset: i16) -> Instr {
|
||||
encode(OpCode::JMP_IF_TRUE, rd, lo, hi)
|
||||
}
|
||||
|
||||
pub fn encode_jmp_if_nullish(rd: u8, offset: i16) -> Instr {
|
||||
let lo = (offset as u16 & 0xFF) as u8;
|
||||
let hi = ((offset as u16 >> 8) & 0xFF) as u8;
|
||||
encode(OpCode::JMP_IF_NULLISH, rd, lo, hi)
|
||||
}
|
||||
|
||||
pub fn encode_try_begin(offset: i16) -> Instr {
|
||||
let lo = (offset as u16 & 0xFF) as u8;
|
||||
let hi = ((offset as u16 >> 8) & 0xFF) as u8;
|
||||
|
||||
Reference in New Issue
Block a user