feat: 支持复合运算符
This commit is contained in:
@@ -223,7 +223,7 @@ impl Compiler {
|
|||||||
} else {
|
} else {
|
||||||
self.count_expression(&assign.right, ctx);
|
self.count_expression(&assign.right, ctx);
|
||||||
ctx.alloc_reg();
|
ctx.alloc_reg();
|
||||||
ctx.projected_pc += 1; // STORE_VAR
|
ctx.projected_pc += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::ConditionalExpression(cond) => {
|
Expression::ConditionalExpression(cond) => {
|
||||||
|
|||||||
@@ -630,13 +630,27 @@ impl Compiler {
|
|||||||
&assign.left
|
&assign.left
|
||||||
{
|
{
|
||||||
if assign.operator != AssignmentOperator::Assign {
|
if assign.operator != AssignmentOperator::Assign {
|
||||||
return Err("compound assignment not yet supported".into());
|
let rhs = self.emit_expression(&assign.right, ctx)?;
|
||||||
|
let name = id_ref.name.as_str();
|
||||||
|
let var_reg = ctx.lookup_or_global(name);
|
||||||
|
let op = match assign.operator {
|
||||||
|
AssignmentOperator::Addition => OpCode::COMPOUND_ADD,
|
||||||
|
AssignmentOperator::Subtraction => OpCode::COMPOUND_SUB,
|
||||||
|
AssignmentOperator::Multiplication => OpCode::COMPOUND_MUL,
|
||||||
|
AssignmentOperator::Division => OpCode::COMPOUND_DIV,
|
||||||
|
AssignmentOperator::Remainder => OpCode::COMPOUND_MOD,
|
||||||
|
AssignmentOperator::Exponential => OpCode::COMPOUND_EXP,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
ctx.emit(opcode::encode(op, var_reg, rhs, 0));
|
||||||
|
Ok(var_reg)
|
||||||
|
} else {
|
||||||
|
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
||||||
|
let name = id_ref.name.as_str();
|
||||||
|
let var_reg = ctx.lookup_or_global(name);
|
||||||
|
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, 0));
|
||||||
|
Ok(val_reg)
|
||||||
}
|
}
|
||||||
let val_reg = self.emit_expression(&assign.right, ctx)?;
|
|
||||||
let name = id_ref.name.as_str();
|
|
||||||
let var_reg = ctx.lookup_or_global(name);
|
|
||||||
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, val_reg, 0));
|
|
||||||
Ok(val_reg)
|
|
||||||
} else {
|
} else {
|
||||||
Err("assignment target not supported".into())
|
Err("assignment target not supported".into())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,11 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
|
|||||||
8u8.hash(h);
|
8u8.hash(h);
|
||||||
b.value.hash(h);
|
b.value.hash(h);
|
||||||
}
|
}
|
||||||
|
Expression::AssignmentExpression(assign) => {
|
||||||
|
11u8.hash(h);
|
||||||
|
std::mem::discriminant(&assign.operator).hash(h);
|
||||||
|
hash_expression(&assign.right, h);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
std::mem::discriminant(expr).hash(h);
|
std::mem::discriminant(expr).hash(h);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,14 @@ pub enum OpCode {
|
|||||||
DIV = 0x03,
|
DIV = 0x03,
|
||||||
MOD = 0x04,
|
MOD = 0x04,
|
||||||
NEG = 0x05,
|
NEG = 0x05,
|
||||||
|
COMPOUND_ADD = 0x06,
|
||||||
|
COMPOUND_SUB = 0x07,
|
||||||
|
COMPOUND_MUL = 0x08,
|
||||||
|
COMPOUND_DIV = 0x09,
|
||||||
|
COMPOUND_MOD = 0x0A,
|
||||||
|
COMPOUND_EXP = 0x0B,
|
||||||
|
|
||||||
// ── Comparison (0x10-0x1F) ──
|
// -- Comparison (0x10-0x1F) --
|
||||||
EQ = 0x10,
|
EQ = 0x10,
|
||||||
NEQ = 0x11,
|
NEQ = 0x11,
|
||||||
LT = 0x12,
|
LT = 0x12,
|
||||||
@@ -104,6 +110,12 @@ impl TryFrom<u8> for OpCode {
|
|||||||
0x03 => Ok(OpCode::DIV),
|
0x03 => Ok(OpCode::DIV),
|
||||||
0x04 => Ok(OpCode::MOD),
|
0x04 => Ok(OpCode::MOD),
|
||||||
0x05 => Ok(OpCode::NEG),
|
0x05 => Ok(OpCode::NEG),
|
||||||
|
0x06 => Ok(OpCode::COMPOUND_ADD),
|
||||||
|
0x07 => Ok(OpCode::COMPOUND_SUB),
|
||||||
|
0x08 => Ok(OpCode::COMPOUND_MUL),
|
||||||
|
0x09 => Ok(OpCode::COMPOUND_DIV),
|
||||||
|
0x0A => Ok(OpCode::COMPOUND_MOD),
|
||||||
|
0x0B => Ok(OpCode::COMPOUND_EXP),
|
||||||
0x10 => Ok(OpCode::EQ),
|
0x10 => Ok(OpCode::EQ),
|
||||||
0x11 => Ok(OpCode::NEQ),
|
0x11 => Ok(OpCode::NEQ),
|
||||||
0x12 => Ok(OpCode::LT),
|
0x12 => Ok(OpCode::LT),
|
||||||
@@ -163,6 +175,12 @@ impl fmt::Display for OpCode {
|
|||||||
OpCode::DIV => "DIV",
|
OpCode::DIV => "DIV",
|
||||||
OpCode::MOD => "MOD",
|
OpCode::MOD => "MOD",
|
||||||
OpCode::NEG => "NEG",
|
OpCode::NEG => "NEG",
|
||||||
|
OpCode::COMPOUND_ADD => "COMPOUND_ADD",
|
||||||
|
OpCode::COMPOUND_SUB => "COMPOUND_SUB",
|
||||||
|
OpCode::COMPOUND_MUL => "COMPOUND_MUL",
|
||||||
|
OpCode::COMPOUND_DIV => "COMPOUND_DIV",
|
||||||
|
OpCode::COMPOUND_MOD => "COMPOUND_MOD",
|
||||||
|
OpCode::COMPOUND_EXP => "COMPOUND_EXP",
|
||||||
OpCode::EQ => "EQ",
|
OpCode::EQ => "EQ",
|
||||||
OpCode::NEQ => "NEQ",
|
OpCode::NEQ => "NEQ",
|
||||||
OpCode::LT => "LT",
|
OpCode::LT => "LT",
|
||||||
|
|||||||
@@ -202,3 +202,43 @@ fn compile_unary_plus_no_error() {
|
|||||||
});
|
});
|
||||||
assert!(result.is_ok(), "+'hello' should compile without error");
|
assert!(result.is_ok(), "+'hello' should compile without error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_compound_add() {
|
||||||
|
let module = compile_source("var x=0; x+=1");
|
||||||
|
assert!(
|
||||||
|
module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.any(|&i| opcode::opcode(i) == OpCode::COMPOUND_ADD),
|
||||||
|
"x+=1 should emit COMPOUND_ADD opcode"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_compound_exp() {
|
||||||
|
let module = compile_source("var x=0; x**=2");
|
||||||
|
assert!(
|
||||||
|
module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.any(|&i| opcode::opcode(i) == OpCode::COMPOUND_EXP),
|
||||||
|
"x**=2 should emit COMPOUND_EXP opcode"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_compound_assign_no_error() {
|
||||||
|
let result = std::panic::catch_unwind(|| {
|
||||||
|
compile_source("x+=1");
|
||||||
|
});
|
||||||
|
assert!(result.is_ok(), "x+=1 should compile without error");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_compound_exp_no_error() {
|
||||||
|
let result = std::panic::catch_unwind(|| {
|
||||||
|
compile_source("x**=2");
|
||||||
|
});
|
||||||
|
assert!(result.is_ok(), "x**=2 should compile without error");
|
||||||
|
}
|
||||||
|
|||||||
@@ -97,3 +97,10 @@ fn regression_conditionalexpression_hash() {
|
|||||||
"different expression types with same test must produce different hashes"
|
"different expression types with same test must produce different hashes"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash_compound_vs_simple_assign() {
|
||||||
|
let hash_a = parse_to_hash("x=1");
|
||||||
|
let hash_b = parse_to_hash("x+=1");
|
||||||
|
assert_ne!(hash_a, hash_b, "x=1 and x+=1 must produce different hashes");
|
||||||
|
}
|
||||||
|
|||||||
@@ -527,6 +527,51 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpCode::COMPOUND_ADD => {
|
||||||
|
let lhs = self.regs[rd];
|
||||||
|
let rhs = self.regs[a];
|
||||||
|
if lhs.is_string() || rhs.is_string() {
|
||||||
|
let ls = coercion::to_string(self.kernel.string_forge().as_ref(), lhs);
|
||||||
|
let rs = coercion::to_string(self.kernel.string_forge().as_ref(), rhs);
|
||||||
|
let concat = format!("{ls}{rs}");
|
||||||
|
self.regs[rd] = self.intern(&concat);
|
||||||
|
} else {
|
||||||
|
let ln = coercion::to_number(lhs, self.kernel.string_forge().as_ref());
|
||||||
|
let rn = coercion::to_number(rhs, self.kernel.string_forge().as_ref());
|
||||||
|
self.regs[rd] = JsValue::float(ln + rn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::COMPOUND_SUB => {
|
||||||
|
let l = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
|
||||||
|
let r = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||||
|
self.regs[rd] = JsValue::float(l - r);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::COMPOUND_MUL => {
|
||||||
|
let l = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
|
||||||
|
let r = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||||
|
self.regs[rd] = JsValue::float(l * r);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::COMPOUND_DIV => {
|
||||||
|
let l = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
|
||||||
|
let r = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||||
|
self.regs[rd] = JsValue::float(l / r);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::COMPOUND_MOD => {
|
||||||
|
let l = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
|
||||||
|
let r = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||||
|
self.regs[rd] = JsValue::float(l % r);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::COMPOUND_EXP => {
|
||||||
|
let l = coercion::to_number(self.regs[rd], self.kernel.string_forge().as_ref());
|
||||||
|
let r = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||||
|
self.regs[rd] = JsValue::float(l.powf(r));
|
||||||
|
}
|
||||||
|
|
||||||
OpCode::TYPEOF => {
|
OpCode::TYPEOF => {
|
||||||
let val = self.regs[a];
|
let val = self.regs[a];
|
||||||
let result = if val.is_undefined() {
|
let result = if val.is_undefined() {
|
||||||
|
|||||||
@@ -63,3 +63,43 @@ fn eval_division_by_zero() {
|
|||||||
fn eval_string_concat() {
|
fn eval_string_concat() {
|
||||||
assert_eq!(eval("1 + 2 + 3"), "6");
|
assert_eq!(eval("1 + 2 + 3"), "6");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_compound_add() {
|
||||||
|
assert_eq!(eval("var x=5; x+=3; x"), "8");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_compound_sub() {
|
||||||
|
assert_eq!(eval("var x=10; x-=4; x"), "6");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_compound_mul() {
|
||||||
|
assert_eq!(eval("var x=2; x*=3; x"), "6");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_compound_div() {
|
||||||
|
assert_eq!(eval("var x=10; x/=2; x"), "5");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_compound_mod() {
|
||||||
|
assert_eq!(eval("var x=7; x%=3; x"), "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_compound_exp() {
|
||||||
|
assert_eq!(eval("var x=2; x**=3; x"), "8");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_compound_add_expr_value() {
|
||||||
|
assert_eq!(eval("var x=5; x+=3"), "8");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_compound_undefined() {
|
||||||
|
assert_eq!(eval("var x; x+=1; x"), "NaN");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user