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:
@@ -10,6 +10,7 @@ description = "OxideJS - Register-based VM with epoch arena memory"
|
||||
oxide_compiler = { path = "../oxide_compiler" }
|
||||
oxide_kernel = { path = "../oxide_kernel" }
|
||||
oxide_types = { path = "../oxide_types" }
|
||||
bumpalo.workspace = true
|
||||
fast-float.workspace = true
|
||||
ryu.workspace = true
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ use oxide_compiler::opcode::{self, OpCode};
|
||||
use crate::coercion;
|
||||
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
||||
use oxide_kernel::prop_forge::PropTemplate;
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::mem::{Epoch, P};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::shape::EMPTY_SHAPE_ID;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
pub struct CallFrame {
|
||||
@@ -20,12 +20,18 @@ pub struct CallFrame {
|
||||
pub n_args: u8,
|
||||
}
|
||||
|
||||
pub struct ForInIter<'bump> {
|
||||
pub keys: bumpalo::collections::Vec<'bump, JsValue>,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
pub struct Vm {
|
||||
regs: [JsValue; 256],
|
||||
pc: usize,
|
||||
bytecode: Vec<opcode::Instr>,
|
||||
constants: Vec<JsValue>,
|
||||
frames: Vec<CallFrame>,
|
||||
pub for_in_iters: Vec<*mut u8>,
|
||||
kernel: Arc<OxideKernel>,
|
||||
interned_strings: Vec<u32>,
|
||||
pub epoch: Epoch,
|
||||
@@ -40,6 +46,7 @@ impl Vm {
|
||||
bytecode: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
frames: Vec::with_capacity(128),
|
||||
for_in_iters: Vec::new(),
|
||||
kernel: Arc::new(OxideKernel::new(KernelConfig::minimal())),
|
||||
interned_strings: Vec::new(),
|
||||
epoch: Epoch::new(),
|
||||
@@ -60,6 +67,7 @@ impl Vm {
|
||||
self.bytecode.clear();
|
||||
self.constants.clear();
|
||||
self.frames.clear();
|
||||
self.for_in_iters.clear();
|
||||
self.epoch.reset();
|
||||
self.interned_strings.clear();
|
||||
}
|
||||
@@ -104,6 +112,7 @@ impl Vm {
|
||||
self.pc = 0;
|
||||
self.regs = [JsValue::undefined(); 256];
|
||||
self.frames.clear();
|
||||
self.for_in_iters.clear();
|
||||
self.dispatch()
|
||||
}
|
||||
|
||||
@@ -566,6 +575,81 @@ impl Vm {
|
||||
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() {
|
||||
return Ok(JsValue::undefined());
|
||||
|
||||
@@ -142,3 +142,129 @@ fn eval_control_flow_complex() {
|
||||
"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