From 69e2c2eba12038c0dfb48d65ff936d3fd9eac7c3 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 6 Jun 2026 20:31:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=A2=9E?= =?UTF-8?q?=E8=87=AA=E5=87=8F=E8=BF=90=E7=AE=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/counter.rs | 11 ++- crates/oxide_compiler/src/emitter.rs | 19 ++++- crates/oxide_compiler/src/hash.rs | 22 ++++- crates/oxide_compiler/src/opcode.rs | 15 ++++ .../tests/compile_expr_tests.rs | 56 +++++++++++++ crates/oxide_vm/src/vm.rs | 26 ++++++ crates/oxide_vm/tests/update_tests.rs | 80 +++++++++++++++++++ 7 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 crates/oxide_vm/tests/update_tests.rs diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index 9aa421be..e6a2979c 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -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 diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 2a265c46..3c894a93 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -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(); diff --git a/crates/oxide_compiler/src/hash.rs b/crates/oxide_compiler/src/hash.rs index ac97f301..a77a2387 100644 --- a/crates/oxide_compiler/src/hash.rs +++ b/crates/oxide_compiler/src/hash.rs @@ -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); } diff --git a/crates/oxide_compiler/src/opcode.rs b/crates/oxide_compiler/src/opcode.rs index 55e76952..8a63b564 100644 --- a/crates/oxide_compiler/src/opcode.rs +++ b/crates/oxide_compiler/src/opcode.rs @@ -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 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", diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index ad90156d..6d9fc394 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -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"); +} diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 1de0fbac..84daa1c9 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -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() { diff --git a/crates/oxide_vm/tests/update_tests.rs b/crates/oxide_vm/tests/update_tests.rs new file mode 100644 index 00000000..42ecbf22 --- /dev/null +++ b/crates/oxide_vm/tests/update_tests.rs @@ -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"); +}