feat: 支持自增自减运算符
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user