mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 添加 do while 与 for-in 语法编译与执行支持
This commit is contained in:
@@ -57,6 +57,17 @@ impl Compiler {
|
|||||||
ctx.projected_pc += 1; // JMP (backward)
|
ctx.projected_pc += 1; // JMP (backward)
|
||||||
ctx.label_map.insert(end_label, ctx.projected_pc);
|
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||||
}
|
}
|
||||||
|
Statement::DoWhileStatement(dw) => {
|
||||||
|
let id = ctx.next_label_id();
|
||||||
|
let start_label = Label::DoWhileStart(id);
|
||||||
|
let end_label = Label::DoWhileEnd(id);
|
||||||
|
|
||||||
|
ctx.label_map.insert(start_label, ctx.projected_pc);
|
||||||
|
self.count_statement(&dw.body, ctx);
|
||||||
|
self.count_expression(&dw.test, ctx);
|
||||||
|
ctx.projected_pc += 1; // JMP_IF_TRUE (backward)
|
||||||
|
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||||
|
}
|
||||||
Statement::ForStatement(fr) => {
|
Statement::ForStatement(fr) => {
|
||||||
let id = ctx.next_label_id();
|
let id = ctx.next_label_id();
|
||||||
let start_label = Label::ForStart(id);
|
let start_label = Label::ForStart(id);
|
||||||
@@ -91,6 +102,71 @@ impl Compiler {
|
|||||||
ctx.projected_pc += 1; // JMP (backward)
|
ctx.projected_pc += 1; // JMP (backward)
|
||||||
ctx.label_map.insert(end_label, ctx.projected_pc);
|
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||||
}
|
}
|
||||||
|
Statement::ForInStatement(fi) => {
|
||||||
|
let id = ctx.next_label_id();
|
||||||
|
let start_label = Label::ForInStart(id);
|
||||||
|
let end_label = Label::ForInEnd(id);
|
||||||
|
|
||||||
|
self.count_expression(&fi.right, ctx);
|
||||||
|
ctx.projected_pc += 1; // FOR_IN_INIT
|
||||||
|
|
||||||
|
ctx.label_map.insert(start_label, ctx.projected_pc);
|
||||||
|
ctx.projected_pc += 3; // FOR_IN_DONE + JMP_IF_FALSE + JMP cleanup
|
||||||
|
|
||||||
|
ctx.projected_pc += 1; // FOR_IN_NEXT
|
||||||
|
match &fi.left {
|
||||||
|
oxide_parser::ForStatementLeft::VariableDeclaration(decl) => {
|
||||||
|
for _d in &decl.declarations {
|
||||||
|
ctx.alloc_reg();
|
||||||
|
ctx.projected_pc += 1; // STORE_VAR (value from FOR_IN_NEXT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oxide_parser::ForStatementLeft::AssignmentTargetIdentifier(_) => {
|
||||||
|
ctx.alloc_reg(); // key register
|
||||||
|
ctx.projected_pc += 1; // STORE_VAR
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.count_statement(&fi.body, ctx);
|
||||||
|
ctx.projected_pc += 1; // JMP back to start
|
||||||
|
|
||||||
|
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||||
|
ctx.projected_pc += 1; // FOR_IN_CLEANUP
|
||||||
|
}
|
||||||
|
Statement::SwitchStatement(sw) => {
|
||||||
|
let id = ctx.next_label_id();
|
||||||
|
let end_label = Label::SwitchEnd(id);
|
||||||
|
ctx.push_switch(end_label);
|
||||||
|
|
||||||
|
self.count_expression(&sw.discriminant, ctx);
|
||||||
|
|
||||||
|
let cases = &sw.cases;
|
||||||
|
for case in cases.iter() {
|
||||||
|
if let Some(test) = &case.test {
|
||||||
|
self.count_expression(test, ctx);
|
||||||
|
ctx.projected_pc += 1; // EQ
|
||||||
|
ctx.alloc_reg(); // eq result
|
||||||
|
ctx.projected_pc += 1; // JMP_IF_TRUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_default = cases.iter().any(|c| c.test.is_none());
|
||||||
|
if !has_default {
|
||||||
|
ctx.projected_pc += 1; // JMP to SwitchEnd (no match)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (case_idx, case) in cases.iter().enumerate() {
|
||||||
|
let case_label = Label::SwitchCase(id * 256 + case_idx as u32);
|
||||||
|
ctx.label_map.insert(case_label, ctx.projected_pc);
|
||||||
|
for s in &case.consequent {
|
||||||
|
self.count_statement(s, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.label_map.insert(end_label, ctx.projected_pc);
|
||||||
|
ctx.pop_switch();
|
||||||
|
}
|
||||||
Statement::BreakStatement(_) => {
|
Statement::BreakStatement(_) => {
|
||||||
ctx.projected_pc += 1; // JMP
|
ctx.projected_pc += 1; // JMP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use crate::compiler::Label;
|
use crate::compiler::Label;
|
||||||
use crate::opcode::{self, OpCode};
|
use crate::opcode::{self, OpCode};
|
||||||
use oxide_parser::{AssignmentOperator, Expression, ForStatementInit, Statement, UnaryOperator};
|
use oxide_parser::{
|
||||||
|
AssignmentOperator, Expression, ForStatementInit, ForStatementLeft, Statement, UnaryOperator,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::compiler::{is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler};
|
use crate::compiler::{is_int_literal, is_side_effect_free, BinaryOperator, CompileCtx, Compiler};
|
||||||
use crate::module::Constant;
|
use crate::module::Constant;
|
||||||
@@ -139,6 +141,24 @@ impl Compiler {
|
|||||||
ctx.pop_loop();
|
ctx.pop_loop();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
Statement::DoWhileStatement(dw) => {
|
||||||
|
let id = ctx.next_label_id();
|
||||||
|
let start_label = Label::DoWhileStart(id);
|
||||||
|
let end_label = Label::DoWhileEnd(id);
|
||||||
|
|
||||||
|
ctx.push_loop(end_label, start_label);
|
||||||
|
|
||||||
|
self.emit_statement(&dw.body, ctx)?;
|
||||||
|
|
||||||
|
let test_reg = self.emit_expression(&dw.test, ctx)?;
|
||||||
|
|
||||||
|
let start_pos = ctx.resolve_label(start_label)?;
|
||||||
|
let offset = (start_pos as isize) - (ctx.bytecode.len() as isize);
|
||||||
|
ctx.emit(opcode::encode_jmp_if_true(test_reg, offset as i16));
|
||||||
|
|
||||||
|
ctx.pop_loop();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
Statement::ForStatement(fr) => {
|
Statement::ForStatement(fr) => {
|
||||||
let id = ctx.next_label_id();
|
let id = ctx.next_label_id();
|
||||||
let start_label = Label::ForStart(id);
|
let start_label = Label::ForStart(id);
|
||||||
@@ -190,10 +210,106 @@ impl Compiler {
|
|||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
Statement::ForInStatement(fi) => {
|
||||||
|
let id = ctx.next_label_id();
|
||||||
|
let start_label = Label::ForInStart(id);
|
||||||
|
let end_label = Label::ForInEnd(id);
|
||||||
|
|
||||||
|
let obj_reg = self.emit_expression(&fi.right, ctx)?;
|
||||||
|
ctx.emit(opcode::encode(OpCode::FOR_IN_INIT, 0, obj_reg, 0));
|
||||||
|
|
||||||
|
ctx.push_loop(end_label, start_label);
|
||||||
|
|
||||||
|
let done_reg = ctx.alloc_reg();
|
||||||
|
ctx.emit(opcode::encode(OpCode::FOR_IN_DONE, done_reg, 0, 0));
|
||||||
|
|
||||||
|
let end_pos = ctx.resolve_label(end_label)?;
|
||||||
|
let cleanup_jmp_offset = (end_pos as isize) - (ctx.bytecode.len() as isize);
|
||||||
|
ctx.emit(opcode::encode_jmp_if_false(done_reg, 2));
|
||||||
|
ctx.emit(opcode::encode_jmp(cleanup_jmp_offset as i16));
|
||||||
|
|
||||||
|
let key_reg = ctx.alloc_reg();
|
||||||
|
ctx.emit(opcode::encode(OpCode::FOR_IN_NEXT, key_reg, 0, 0));
|
||||||
|
|
||||||
|
match &fi.left {
|
||||||
|
ForStatementLeft::VariableDeclaration(decl) => {
|
||||||
|
for d in &decl.declarations {
|
||||||
|
let name = match &d.id {
|
||||||
|
oxide_parser::BindingPattern::BindingIdentifier(bi) => {
|
||||||
|
bi.name.as_str()
|
||||||
|
}
|
||||||
|
_ => return Err("destructuring not supported".into()),
|
||||||
|
};
|
||||||
|
let var_reg = ctx.alloc_reg();
|
||||||
|
ctx.declare(name, var_reg)?;
|
||||||
|
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, key_reg, 0));
|
||||||
|
ctx.init_var(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ForStatementLeft::AssignmentTargetIdentifier(id_ref) => {
|
||||||
|
let name = id_ref.name.as_str();
|
||||||
|
let var_reg = ctx.lookup_or_global(name);
|
||||||
|
ctx.emit(opcode::encode(OpCode::STORE_VAR, var_reg, key_reg, 0));
|
||||||
|
}
|
||||||
|
_ => return Err("unsupported for-in left-hand side".into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit_statement(&fi.body, ctx)?;
|
||||||
|
|
||||||
|
let start_pos = ctx.resolve_label(start_label)?;
|
||||||
|
let offset = (start_pos as isize) - (ctx.bytecode.len() as isize);
|
||||||
|
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||||
|
|
||||||
|
ctx.emit(opcode::encode(OpCode::FOR_IN_CLEANUP, 0, 0, 0));
|
||||||
|
|
||||||
|
ctx.pop_loop();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Statement::SwitchStatement(sw) => {
|
||||||
|
let id = ctx.next_label_id();
|
||||||
|
let end_label = Label::SwitchEnd(id);
|
||||||
|
ctx.push_switch(end_label);
|
||||||
|
|
||||||
|
let disc_reg = self.emit_expression(&sw.discriminant, ctx)?;
|
||||||
|
let cases = &sw.cases;
|
||||||
|
|
||||||
|
for (case_idx, case) in cases.iter().enumerate() {
|
||||||
|
if let Some(test) = &case.test {
|
||||||
|
let test_reg = self.emit_expression(test, ctx)?;
|
||||||
|
let eq_reg = ctx.alloc_reg();
|
||||||
|
ctx.emit(opcode::encode(OpCode::EQ, eq_reg, disc_reg, test_reg));
|
||||||
|
|
||||||
|
let case_label = Label::SwitchCase(id * 256 + case_idx as u32);
|
||||||
|
let body_pos = ctx.resolve_label(case_label)?;
|
||||||
|
let offset = (body_pos as isize) - (ctx.bytecode.len() as isize);
|
||||||
|
ctx.emit(opcode::encode_jmp_if_true(eq_reg, offset as i16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_default = cases.iter().any(|c| c.test.is_none());
|
||||||
|
if !has_default {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
for case in cases.iter() {
|
||||||
|
for s in &case.consequent {
|
||||||
|
self.emit_statement(s, ctx)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.pop_switch();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
Statement::BreakStatement(_) => {
|
Statement::BreakStatement(_) => {
|
||||||
let (break_label, _) =
|
let break_label = if let Some(sw_label) = ctx.current_switch() {
|
||||||
ctx.current_loop().ok_or("break outside loop".to_string())?;
|
*sw_label
|
||||||
let break_pos = ctx.resolve_label(*break_label)?;
|
} else {
|
||||||
|
let (bl, _) = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
|
||||||
|
*bl
|
||||||
|
};
|
||||||
|
let break_pos = ctx.resolve_label(break_label)?;
|
||||||
let offset = (break_pos as isize) - (ctx.bytecode.len() as isize);
|
let offset = (break_pos as isize) - (ctx.bytecode.len() as isize);
|
||||||
ctx.emit(opcode::encode_jmp(offset as i16));
|
ctx.emit(opcode::encode_jmp(offset as i16));
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|||||||
@@ -176,3 +176,102 @@ fn regression_redundant_jmp_if_without_else() {
|
|||||||
"final opcode should be HALT, not dangling JMP"
|
"final opcode should be HALT, not dangling JMP"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_do_while_emits_jmp_if_true() {
|
||||||
|
let module = compile_source("do { 1 } while (true)");
|
||||||
|
assert!(
|
||||||
|
module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.any(|&i| opcode::opcode(i) == OpCode::JMP_IF_TRUE),
|
||||||
|
"do-while should contain JMP_IF_TRUE"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_do_while_emits_body_before_test() {
|
||||||
|
let module = compile_source("var x = true; do { 1 } while (x)");
|
||||||
|
let has_opcodes_before_jmp = module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.take_while(|&&i| opcode::opcode(i) != OpCode::JMP_IF_TRUE)
|
||||||
|
.count()
|
||||||
|
> 1;
|
||||||
|
assert!(has_opcodes_before_jmp, "body should emit before test");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_do_while_break() {
|
||||||
|
let module = compile_source("do { break; } while (true)");
|
||||||
|
assert!(!module.bytecode.is_empty(), "do-while with break should compile");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_do_while_continue() {
|
||||||
|
let module = compile_source("var x = 0; do { continue; } while (x < 5)");
|
||||||
|
assert!(
|
||||||
|
!module.bytecode.is_empty(),
|
||||||
|
"do-while with continue should compile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_for_in_emits_opcodes() {
|
||||||
|
let module = compile_source("var obj={a:1}; for (k in obj) {}");
|
||||||
|
assert!(
|
||||||
|
module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.any(|&i| opcode::opcode(i) == OpCode::FOR_IN_INIT),
|
||||||
|
"for-in should emit FOR_IN_INIT"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.any(|&i| opcode::opcode(i) == OpCode::FOR_IN_NEXT),
|
||||||
|
"for-in should emit FOR_IN_NEXT"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_for_in_var_decl() {
|
||||||
|
let module = compile_source("var obj={a:1}; for (var k in obj) { k }");
|
||||||
|
assert!(!module.bytecode.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_for_in_let_decl() {
|
||||||
|
let module = compile_source("var obj={a:1}; for (let k in obj) { k }");
|
||||||
|
assert!(!module.bytecode.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_switch_emits_jmp_if_true() {
|
||||||
|
let module = compile_source("var x=0; switch(x){case 1:1;case 2:2;}");
|
||||||
|
assert!(
|
||||||
|
module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::JMP_IF_TRUE),
|
||||||
|
"switch should emit JMP_IF_TRUE"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_switch_default() {
|
||||||
|
let module = compile_source("var x=0; switch(x){default:0;}");
|
||||||
|
assert!(!module.bytecode.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_switch_break() {
|
||||||
|
let module = compile_source("var x=0; switch(x){case 1:break;}");
|
||||||
|
assert!(!module.bytecode.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_continue_in_switch_errors() {
|
||||||
|
let result = std::panic::catch_unwind(|| {
|
||||||
|
compile_source("var x=0; switch(x){case 1:continue;}");
|
||||||
|
});
|
||||||
|
assert!(result.is_err(), "continue inside switch should error");
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ description = "OxideJS - Register-based VM with epoch arena memory"
|
|||||||
oxide_compiler = { path = "../oxide_compiler" }
|
oxide_compiler = { path = "../oxide_compiler" }
|
||||||
oxide_kernel = { path = "../oxide_kernel" }
|
oxide_kernel = { path = "../oxide_kernel" }
|
||||||
oxide_types = { path = "../oxide_types" }
|
oxide_types = { path = "../oxide_types" }
|
||||||
|
bumpalo.workspace = true
|
||||||
fast-float.workspace = true
|
fast-float.workspace = true
|
||||||
ryu.workspace = true
|
ryu.workspace = true
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ use oxide_compiler::opcode::{self, OpCode};
|
|||||||
use crate::coercion;
|
use crate::coercion;
|
||||||
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
||||||
use oxide_kernel::prop_forge::PropTemplate;
|
use oxide_kernel::prop_forge::PropTemplate;
|
||||||
|
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||||
use oxide_types::mem::{Epoch, P};
|
use oxide_types::mem::{Epoch, P};
|
||||||
use oxide_types::object::JsObject;
|
use oxide_types::object::JsObject;
|
||||||
use oxide_types::shape::EMPTY_SHAPE_ID;
|
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
pub struct CallFrame {
|
pub struct CallFrame {
|
||||||
@@ -20,12 +20,18 @@ pub struct CallFrame {
|
|||||||
pub n_args: u8,
|
pub n_args: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ForInIter<'bump> {
|
||||||
|
pub keys: bumpalo::collections::Vec<'bump, JsValue>,
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Vm {
|
pub struct Vm {
|
||||||
regs: [JsValue; 256],
|
regs: [JsValue; 256],
|
||||||
pc: usize,
|
pc: usize,
|
||||||
bytecode: Vec<opcode::Instr>,
|
bytecode: Vec<opcode::Instr>,
|
||||||
constants: Vec<JsValue>,
|
constants: Vec<JsValue>,
|
||||||
frames: Vec<CallFrame>,
|
frames: Vec<CallFrame>,
|
||||||
|
pub for_in_iters: Vec<*mut u8>,
|
||||||
kernel: Arc<OxideKernel>,
|
kernel: Arc<OxideKernel>,
|
||||||
interned_strings: Vec<u32>,
|
interned_strings: Vec<u32>,
|
||||||
pub epoch: Epoch,
|
pub epoch: Epoch,
|
||||||
@@ -40,6 +46,7 @@ impl Vm {
|
|||||||
bytecode: Vec::new(),
|
bytecode: Vec::new(),
|
||||||
constants: Vec::new(),
|
constants: Vec::new(),
|
||||||
frames: Vec::with_capacity(128),
|
frames: Vec::with_capacity(128),
|
||||||
|
for_in_iters: Vec::new(),
|
||||||
kernel: Arc::new(OxideKernel::new(KernelConfig::minimal())),
|
kernel: Arc::new(OxideKernel::new(KernelConfig::minimal())),
|
||||||
interned_strings: Vec::new(),
|
interned_strings: Vec::new(),
|
||||||
epoch: Epoch::new(),
|
epoch: Epoch::new(),
|
||||||
@@ -60,6 +67,7 @@ impl Vm {
|
|||||||
self.bytecode.clear();
|
self.bytecode.clear();
|
||||||
self.constants.clear();
|
self.constants.clear();
|
||||||
self.frames.clear();
|
self.frames.clear();
|
||||||
|
self.for_in_iters.clear();
|
||||||
self.epoch.reset();
|
self.epoch.reset();
|
||||||
self.interned_strings.clear();
|
self.interned_strings.clear();
|
||||||
}
|
}
|
||||||
@@ -104,6 +112,7 @@ impl Vm {
|
|||||||
self.pc = 0;
|
self.pc = 0;
|
||||||
self.regs = [JsValue::undefined(); 256];
|
self.regs = [JsValue::undefined(); 256];
|
||||||
self.frames.clear();
|
self.frames.clear();
|
||||||
|
self.for_in_iters.clear();
|
||||||
self.dispatch()
|
self.dispatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,6 +575,81 @@ impl Vm {
|
|||||||
self.regs[rd] = if cond { self.regs[a] } else { self.regs[b] };
|
self.regs[rd] = if cond { self.regs[a] } else { self.regs[b] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpCode::FOR_IN_INIT => {
|
||||||
|
let obj_val = self.regs[a];
|
||||||
|
if !obj_val.is_object() {
|
||||||
|
return Err("TypeError: for-in right-hand side is not an object".into());
|
||||||
|
}
|
||||||
|
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
||||||
|
let shape_id = obj.shape_id();
|
||||||
|
|
||||||
|
let mut keys_vec =
|
||||||
|
bumpalo::collections::Vec::new_in(self.epoch.bump());
|
||||||
|
let mut cursor = Some(shape_id);
|
||||||
|
while let Some(id) = cursor {
|
||||||
|
if id == oxide_kernel::shape_forge::EMPTY_SHAPE_ID {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if let Some(shape) = self.kernel.shape_forge().get_shape(id) {
|
||||||
|
if shape.property_name != u32::MAX {
|
||||||
|
let hash = self
|
||||||
|
.kernel
|
||||||
|
.string_forge()
|
||||||
|
.get_hash(shape.property_name)
|
||||||
|
.unwrap_or(0);
|
||||||
|
keys_vec.push(JsValue::string(shape.property_name, hash));
|
||||||
|
}
|
||||||
|
cursor = shape.parent;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let iter = self.epoch.alloc(ForInIter {
|
||||||
|
keys: keys_vec,
|
||||||
|
index: 0,
|
||||||
|
});
|
||||||
|
self.for_in_iters.push(iter as *mut u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::FOR_IN_NEXT => {
|
||||||
|
let iter_ptr = self
|
||||||
|
.for_in_iters
|
||||||
|
.last()
|
||||||
|
.copied()
|
||||||
|
.map(|p| p as *mut ForInIter)
|
||||||
|
.unwrap_or(std::ptr::null_mut());
|
||||||
|
if iter_ptr.is_null() {
|
||||||
|
return Err("FOR_IN_NEXT without active iterator".into());
|
||||||
|
}
|
||||||
|
let iter = unsafe { &mut *iter_ptr };
|
||||||
|
if iter.index < iter.keys.len() {
|
||||||
|
self.regs[rd] = iter.keys[iter.index];
|
||||||
|
iter.index += 1;
|
||||||
|
} else {
|
||||||
|
self.regs[rd] = JsValue::undefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::FOR_IN_DONE => {
|
||||||
|
let iter_ptr = self
|
||||||
|
.for_in_iters
|
||||||
|
.last()
|
||||||
|
.copied()
|
||||||
|
.map(|p| p as *mut ForInIter)
|
||||||
|
.unwrap_or(std::ptr::null_mut());
|
||||||
|
if iter_ptr.is_null() {
|
||||||
|
self.regs[rd] = JsValue::bool(true);
|
||||||
|
} else {
|
||||||
|
let iter = unsafe { &*iter_ptr };
|
||||||
|
self.regs[rd] = JsValue::bool(iter.index >= iter.keys.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::FOR_IN_CLEANUP => {
|
||||||
|
self.for_in_iters.pop();
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
if !op.is_implemented() {
|
if !op.is_implemented() {
|
||||||
return Ok(JsValue::undefined());
|
return Ok(JsValue::undefined());
|
||||||
|
|||||||
@@ -142,3 +142,129 @@ fn eval_control_flow_complex() {
|
|||||||
"8"
|
"8"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_do_while_body_executes_once() {
|
||||||
|
assert_eq!(eval("var x = 0; do { x = 1; } while (false); x"), "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_do_while_multi_iteration() {
|
||||||
|
assert_eq!(eval("var i = 0; do { i = i + 1; } while (i < 3); i"), "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_do_while_break() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var i = 0; do { i = i + 1; if (i >= 3) break; } while (true); i"),
|
||||||
|
"3"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_do_while_continue() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var i = 0; var r = 0; do { i = i + 1; if (i == 3) continue; r = r + 1; } while (i < 5); r"),
|
||||||
|
"4"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_for_in_enumerates_keys() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var n=0; for (k in {a:1,b:2}) { n=n+1; } n"),
|
||||||
|
"2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_for_in_empty_object() {
|
||||||
|
assert_eq!(eval("var r=0; for (k in {}) { r=1; } r"), "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_for_in_non_object_throws() {
|
||||||
|
let result = eval("for (k in 42) {}");
|
||||||
|
assert!(
|
||||||
|
result.contains("TypeError"),
|
||||||
|
"expected TypeError, got: {result}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_for_in_nested() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var r=0; for (k in {a:{x:1}}) { for (j in {y:2}) { r=r+1; } } r"),
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_for_in_break() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var r=0; for (k in {a:1,b:2,c:3}) { if (k=='b') break; r=r+1; } r"),
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_for_in_continue() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var r=0; for (k in {a:1,b:2}) { if (k=='a') continue; r=r+1; } r"),
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_for_in_let_scoping() {
|
||||||
|
let result = eval("var r=0; for (let k in {a:1}) { r=1; } r");
|
||||||
|
assert_eq!(result, "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_switch_basic_match() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var x=0;switch(2){case 1:x=10;case 2:x=20;break;default:x=30;}x"),
|
||||||
|
"20"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_switch_fallthrough() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var x=0;switch(1){case 1:case 2:x=42;}x"),
|
||||||
|
"42"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_switch_default() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var x=0;switch(99){case 1:x=10;default:x=30;}x"),
|
||||||
|
"30"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_switch_no_match_no_default() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var x=0;switch(99){case 1:x=10;}x"),
|
||||||
|
"0"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_switch_break() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var x=0;switch(1){case 1:x=10;break;case 2:x=20;}x"),
|
||||||
|
"10"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_break_in_switch_in_loop() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var r=0;while(true){switch(1){case 1:r=1;break;}r=2;break;}r"),
|
||||||
|
"2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user