feat: 支持自增自减运算符

This commit is contained in:
爱特每羊队
2026-06-06 20:31:57 +08:00
parent 3c728686fc
commit b812cfae01
7 changed files with 226 additions and 3 deletions
+10 -1
View File
@@ -1,4 +1,4 @@
use oxide_parser::{Expression, ForStatementInit, Statement};
use oxide_parser::{Expression, ForStatementInit, SimpleAssignmentTarget, Statement};
use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label};
@@ -314,6 +314,15 @@ impl Compiler {
Expression::ParenthesizedExpression(p) => {
self.count_expression(&p.expression, ctx);
}
Expression::UpdateExpression(update) => match &update.argument {
SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => {
ctx.alloc_reg();
ctx.projected_pc += 1;
}
_ => {
ctx.alloc_reg();
}
},
_ => {
ctx.alloc_reg();
ctx.projected_pc += 1; // LOAD_CONST or LOAD_VAR
+18 -1
View File
@@ -1,7 +1,8 @@
use crate::compiler::Label;
use crate::opcode::{self, OpCode};
use oxide_parser::{
AssignmentOperator, Expression, ForStatementInit, ForStatementLeft, Statement, UnaryOperator,
AssignmentOperator, Expression, ForStatementInit, ForStatementLeft, SimpleAssignmentTarget,
Statement, UnaryOperator, UpdateOperator,
};
use crate::compiler::{is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler};
@@ -655,6 +656,22 @@ impl Compiler {
Err("assignment target not supported".into())
}
}
Expression::UpdateExpression(update) => match &update.argument {
SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => {
let name = id.name.as_str();
let var_reg = ctx.lookup_or_global(name);
let result_reg = ctx.alloc_reg();
let op = match (update.operator, update.prefix) {
(UpdateOperator::Increment, true) => OpCode::INC_PRE,
(UpdateOperator::Increment, false) => OpCode::INC_POST,
(UpdateOperator::Decrement, true) => OpCode::DEC_PRE,
(UpdateOperator::Decrement, false) => OpCode::DEC_POST,
};
ctx.emit(opcode::encode(op, var_reg, result_reg, result_reg));
Ok(result_reg)
}
_ => Err("member update not yet supported".into()),
},
Expression::Identifier(ident) => {
let var_reg = ctx.lookup(ident.name.as_str())?;
let r = ctx.alloc_reg();
+21 -1
View File
@@ -1,4 +1,4 @@
use oxide_parser::{Expression, ForStatementInit, Statement};
use oxide_parser::{Expression, ForStatementInit, SimpleAssignmentTarget, Statement};
pub fn structural_hash(program: &oxide_parser::Program) -> u64 {
use std::hash::Hasher;
@@ -160,6 +160,26 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
std::mem::discriminant(&assign.operator).hash(h);
hash_expression(&assign.right, h);
}
Expression::UpdateExpression(update) => {
12u8.hash(h);
std::mem::discriminant(&update.operator).hash(h);
update.prefix.hash(h);
match &update.argument {
SimpleAssignmentTarget::AssignmentTargetIdentifier(_) => {
0u8.hash(h);
}
SimpleAssignmentTarget::StaticMemberExpression(member) => {
1u8.hash(h);
hash_expression(&member.object, h);
}
SimpleAssignmentTarget::ComputedMemberExpression(member) => {
2u8.hash(h);
hash_expression(&member.object, h);
hash_expression(&member.expression, h);
}
_ => {}
}
}
_ => {
std::mem::discriminant(expr).hash(h);
}
+15
View File
@@ -42,6 +42,13 @@ pub enum OpCode {
JMP = 0x20,
JMP_IF_FALSE = 0x21,
JMP_IF_TRUE = 0x22,
// -- Update (0x25-0x28) --
INC_PRE = 0x25,
INC_POST = 0x26,
DEC_PRE = 0x27,
DEC_POST = 0x28,
FOR_IN_INIT = 0x29,
FOR_IN_NEXT = 0x2A,
FOR_IN_DONE = 0x2B,
@@ -132,6 +139,10 @@ impl TryFrom<u8> for OpCode {
0x20 => Ok(OpCode::JMP),
0x21 => Ok(OpCode::JMP_IF_FALSE),
0x22 => Ok(OpCode::JMP_IF_TRUE),
0x25 => Ok(OpCode::INC_PRE),
0x26 => Ok(OpCode::INC_POST),
0x27 => Ok(OpCode::DEC_PRE),
0x28 => Ok(OpCode::DEC_POST),
0x29 => Ok(OpCode::FOR_IN_INIT),
0x2A => Ok(OpCode::FOR_IN_NEXT),
0x2B => Ok(OpCode::FOR_IN_DONE),
@@ -197,6 +208,10 @@ impl fmt::Display for OpCode {
OpCode::JMP => "JMP",
OpCode::JMP_IF_FALSE => "JMP_IF_FALSE",
OpCode::JMP_IF_TRUE => "JMP_IF_TRUE",
OpCode::INC_PRE => "INC_PRE",
OpCode::INC_POST => "INC_POST",
OpCode::DEC_PRE => "DEC_PRE",
OpCode::DEC_POST => "DEC_POST",
OpCode::FOR_IN_INIT => "FOR_IN_INIT",
OpCode::FOR_IN_NEXT => "FOR_IN_NEXT",
OpCode::FOR_IN_DONE => "FOR_IN_DONE",
@@ -242,3 +242,59 @@ fn compile_compound_exp_no_error() {
});
assert!(result.is_ok(), "x**=2 should compile without error");
}
#[test]
fn compile_inc_pre() {
let module = compile_source("++x");
assert!(
module
.bytecode
.iter()
.any(|&i| opcode::opcode(i) == OpCode::INC_PRE),
"++x should emit INC_PRE opcode"
);
}
#[test]
fn compile_inc_post() {
let module = compile_source("x++");
assert!(
module
.bytecode
.iter()
.any(|&i| opcode::opcode(i) == OpCode::INC_POST),
"x++ should emit INC_POST opcode"
);
}
#[test]
fn compile_inc_no_error() {
let result = std::panic::catch_unwind(|| {
compile_source("x++");
});
assert!(result.is_ok(), "x++ should compile without error");
}
#[test]
fn compile_dec_no_error() {
let result = std::panic::catch_unwind(|| {
compile_source("x--");
});
assert!(result.is_ok(), "x-- should compile without error");
}
#[test]
fn compile_inc_dec_diff_opcodes() {
let m1 = compile_source("++x");
let m2 = compile_source("--x");
let has_inc_pre = m1
.bytecode
.iter()
.any(|&i| opcode::opcode(i) == OpCode::INC_PRE);
let has_dec_pre = m2
.bytecode
.iter()
.any(|&i| opcode::opcode(i) == OpCode::DEC_PRE);
assert!(has_inc_pre, "++x should emit INC_PRE");
assert!(has_dec_pre, "--x should emit DEC_PRE");
}
+26
View File
@@ -635,6 +635,32 @@ impl Vm {
self.regs[rd] = if cond { self.regs[a] } else { self.regs[b] };
}
OpCode::INC_PRE => {
let n = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
let result = JsValue::float(n + 1.0);
self.regs[rd] = result;
self.regs[a] = result;
}
OpCode::INC_POST => {
let n = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
self.regs[a] = JsValue::float(n);
self.regs[rd] = JsValue::float(n + 1.0);
}
OpCode::DEC_PRE => {
let n = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
let result = JsValue::float(n - 1.0);
self.regs[rd] = result;
self.regs[a] = result;
}
OpCode::DEC_POST => {
let n = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
self.regs[a] = JsValue::float(n);
self.regs[rd] = JsValue::float(n - 1.0);
}
OpCode::FOR_IN_INIT => {
let obj_val = self.regs[a];
if !obj_val.is_object() {
+80
View File
@@ -0,0 +1,80 @@
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> String {
let allocator = Allocator::default();
let program = match oxide_parser::parse(&allocator, source) {
Ok(p) => p,
Err(e) => return format!("parse error: {}", e[0].message),
};
let module = match Compiler::new().compile(&program) {
Ok(m) => m,
Err(e) => return format!("compile error: {e}"),
};
let mut vm = Vm::new();
match vm.run(&module) {
Ok(result) => format!("{result}"),
Err(e) => format!("vm error: {e}"),
}
}
#[test]
fn eval_inc_pre() {
assert_eq!(eval("var x=0; ++x; x"), "1");
}
#[test]
fn eval_inc_pre_expr() {
assert_eq!(eval("var x=0; ++x"), "1");
}
#[test]
fn eval_inc_post_var() {
assert_eq!(eval("var x=0; x++; x"), "1");
}
#[test]
fn eval_inc_post_expr() {
assert_eq!(eval("var x=0; x++"), "0");
}
#[test]
fn eval_dec_pre() {
assert_eq!(eval("var x=5; --x; x"), "4");
}
#[test]
fn eval_dec_pre_expr() {
assert_eq!(eval("var x=5; --x"), "4");
}
#[test]
fn eval_dec_post_var() {
assert_eq!(eval("var x=5; x--; x"), "4");
}
#[test]
fn eval_dec_post_expr() {
assert_eq!(eval("var x=5; x--"), "5");
}
#[test]
fn eval_inc_string_coerce() {
assert_eq!(eval("var x='5'; ++x"), "6");
}
#[test]
fn eval_inc_nan() {
assert_eq!(eval("var x='hello'; ++x"), "NaN");
}
#[test]
fn eval_multi_update() {
assert_eq!(eval("var x=1; x++; x; ++x"), "3");
}
#[test]
fn eval_inc_post_in_assign() {
assert_eq!(eval("var x=0; var y=x++; y"), "0");
}