feat: 搭建 Iterator 与 for-of 语法基础设施
This commit is contained in:
@@ -42,6 +42,8 @@ pub enum OpCode {
|
||||
JMP = 0x20,
|
||||
JMP_IF_FALSE = 0x21,
|
||||
JMP_IF_TRUE = 0x22,
|
||||
FOR_OF_INIT = 0x23,
|
||||
FOR_OF_NEXT = 0x24,
|
||||
|
||||
// -- Update (0x25-0x28) --
|
||||
INC_PRE = 0x25,
|
||||
@@ -61,6 +63,8 @@ pub enum OpCode {
|
||||
TRY_END = 0x33,
|
||||
TRY_FINALLY_BEGIN = 0x34,
|
||||
TRY_FINALLY_END = 0x35,
|
||||
FOR_OF_DONE = 0x36,
|
||||
FOR_OF_CLOSE = 0x37,
|
||||
|
||||
// -- Variable (0x30-0x32) --
|
||||
LOAD_VAR = 0x30,
|
||||
@@ -176,6 +180,8 @@ impl TryFrom<u8> for OpCode {
|
||||
0x20 => Ok(OpCode::JMP),
|
||||
0x21 => Ok(OpCode::JMP_IF_FALSE),
|
||||
0x22 => Ok(OpCode::JMP_IF_TRUE),
|
||||
0x23 => Ok(OpCode::FOR_OF_INIT),
|
||||
0x24 => Ok(OpCode::FOR_OF_NEXT),
|
||||
0x25 => Ok(OpCode::INC_PRE),
|
||||
0x26 => Ok(OpCode::INC_POST),
|
||||
0x27 => Ok(OpCode::DEC_PRE),
|
||||
@@ -193,6 +199,8 @@ impl TryFrom<u8> for OpCode {
|
||||
0x33 => Ok(OpCode::TRY_END),
|
||||
0x34 => Ok(OpCode::TRY_FINALLY_BEGIN),
|
||||
0x35 => Ok(OpCode::TRY_FINALLY_END),
|
||||
0x36 => Ok(OpCode::FOR_OF_DONE),
|
||||
0x37 => Ok(OpCode::FOR_OF_CLOSE),
|
||||
0x40 => Ok(OpCode::CALL),
|
||||
0x41 => Ok(OpCode::RETURN),
|
||||
0x42 => Ok(OpCode::CALL_NATIVE),
|
||||
@@ -262,6 +270,8 @@ impl fmt::Display for OpCode {
|
||||
OpCode::JMP => "JMP",
|
||||
OpCode::JMP_IF_FALSE => "JMP_IF_FALSE",
|
||||
OpCode::JMP_IF_TRUE => "JMP_IF_TRUE",
|
||||
OpCode::FOR_OF_INIT => "FOR_OF_INIT",
|
||||
OpCode::FOR_OF_NEXT => "FOR_OF_NEXT",
|
||||
OpCode::INC_PRE => "INC_PRE",
|
||||
OpCode::INC_POST => "INC_POST",
|
||||
OpCode::DEC_PRE => "DEC_PRE",
|
||||
@@ -276,6 +286,8 @@ impl fmt::Display for OpCode {
|
||||
OpCode::TRY_END => "TRY_END",
|
||||
OpCode::TRY_FINALLY_BEGIN => "TRY_FINALLY_BEGIN",
|
||||
OpCode::TRY_FINALLY_END => "TRY_FINALLY_END",
|
||||
OpCode::FOR_OF_DONE => "FOR_OF_DONE",
|
||||
OpCode::FOR_OF_CLOSE => "FOR_OF_CLOSE",
|
||||
OpCode::LOAD_VAR => "LOAD_VAR",
|
||||
OpCode::STORE_VAR => "STORE_VAR",
|
||||
OpCode::LOAD_CONST => "LOAD_CONST",
|
||||
|
||||
@@ -168,6 +168,7 @@ pub struct BuiltinWorld {
|
||||
pub sym_replace: P<JsObject>,
|
||||
pub sym_search: P<JsObject>,
|
||||
pub sym_split: P<JsObject>,
|
||||
pub sym_iterator: P<JsObject>,
|
||||
}
|
||||
|
||||
fn intern_label(string_forge: &StringForge, label: &str) -> u32 {
|
||||
@@ -337,6 +338,7 @@ impl BuiltinWorld {
|
||||
let sym_replace = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let sym_search = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let sym_split = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let sym_iterator = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
|
||||
/// Overwrite placeholder slots (set up by make_pair) with real values.
|
||||
/// ctor.vec[0] = constructor.prototype -> proto
|
||||
@@ -406,6 +408,7 @@ impl BuiltinWorld {
|
||||
sym_replace,
|
||||
sym_search,
|
||||
sym_split,
|
||||
sym_iterator,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,6 +486,8 @@ impl BuiltinWorld {
|
||||
let _ = self.bind_method(ctor, shape_forge, string_forge, "fromEntries", methods.from_entries, 1);
|
||||
let _ = self.bind_method(ctor, shape_forge, string_forge, "getPrototypeOf", methods.get_prototype_of, 1);
|
||||
let _ = self.bind_method(ctor, shape_forge, string_forge, "hasOwn", methods.has_own, 2);
|
||||
let _ = self.bind_method(ctor, shape_forge, string_forge, "entries", methods.entries, 1);
|
||||
let _ = self.bind_method(ctor, shape_forge, string_forge, "values", methods.values, 1);
|
||||
}
|
||||
|
||||
pub fn bind_array_methods(
|
||||
@@ -791,6 +796,8 @@ impl BuiltinWorld {
|
||||
let _ = self.bind_method(proto, shape_forge, string_forge, "trimEnd", methods.trim_end, 0);
|
||||
let _ = self.bind_method(proto, shape_forge, string_forge, "codePointAt", methods.code_point_at, 1);
|
||||
let _ = self.bind_method(proto, shape_forge, string_forge, "normalize", methods.normalize, 0);
|
||||
let _ = self.bind_method(proto, shape_forge, string_forge, "matchAll", methods.match_all, 1);
|
||||
let _ = self.bind_method(proto, shape_forge, string_forge, "replaceAll", methods.replace_all, 2);
|
||||
}
|
||||
|
||||
pub fn bind_number_methods(
|
||||
|
||||
@@ -24,8 +24,8 @@ pub fn bind_object(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
from_entries: crate::builtins::object::object_from_entries as *const (),
|
||||
get_prototype_of: crate::builtins::object::object_get_prototype_of as *const (),
|
||||
has_own: crate::builtins::object::object_has_own as *const (),
|
||||
entries: std::ptr::null(),
|
||||
values: std::ptr::null(),
|
||||
entries: crate::builtins::object::object_entries as *const (),
|
||||
values: crate::builtins::object::object_values as *const (),
|
||||
};
|
||||
kernel.builtin_world().bind_object_methods(
|
||||
&methods,
|
||||
|
||||
@@ -30,8 +30,8 @@ pub fn bind_string(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
trim_end: crate::builtins::string::string_trim_end as *const (),
|
||||
code_point_at: crate::builtins::string::string_code_point_at as *const (),
|
||||
normalize: crate::builtins::string::string_normalize as *const (),
|
||||
match_all: std::ptr::null(),
|
||||
replace_all: std::ptr::null(),
|
||||
match_all: crate::builtins::string::string_match_all as *const (),
|
||||
replace_all: crate::builtins::string::string_replace_all as *const (),
|
||||
};
|
||||
kernel.builtin_world().bind_string_methods(
|
||||
&string_methods,
|
||||
|
||||
@@ -45,6 +45,11 @@ pub fn bind_symbol(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
);
|
||||
bind_well_known_symbol(kernel, ctor, sf, sh, "split", sym_split_val);
|
||||
|
||||
let sym_iterator_val = JsValue::from_js_object(
|
||||
kernel.builtin_world().sym_iterator.as_ptr() as *mut JsObject,
|
||||
);
|
||||
bind_well_known_symbol(kernel, ctor, sf, sh, "iterator", sym_iterator_val);
|
||||
|
||||
bind_constructor_hash!(
|
||||
kernel,
|
||||
global,
|
||||
|
||||
@@ -379,3 +379,40 @@ pub fn object_has_own(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if !key_val.is_string() { return Ok(JsValue::bool(false)); }
|
||||
Ok(JsValue::bool(false))
|
||||
}
|
||||
|
||||
pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj_ptr = require_obj_arg(vm, args, "entries")?;
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
let n = keys.len().min(255);
|
||||
let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.epoch().alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), n, vm.epoch().bump()));
|
||||
for (_i, (si, offset)) in keys.iter().enumerate().take(n) {
|
||||
let key_str = vm.kernel().string_forge().lookup(*si).unwrap_or_default();
|
||||
let key_val = vm.intern(&key_str);
|
||||
let val = obj.get_prop_at(*offset);
|
||||
let pair = vm.epoch().alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), 2, vm.epoch().bump()));
|
||||
unsafe {
|
||||
(*pair).set_prop_at(0, key_val);
|
||||
(*pair).set_prop_at(1, val);
|
||||
(*pair).set_prop_count(2);
|
||||
(*arr).ensure_hash_props().push(Box::new(JsValue::from_js_object(pair)));
|
||||
}
|
||||
}
|
||||
Ok(JsValue::from_js_object(arr))
|
||||
}
|
||||
|
||||
pub fn object_values(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj_ptr = require_obj_arg(vm, args, "values")?;
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
let n = keys.len().min(255);
|
||||
let array_proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.epoch().alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(array_proto), n, vm.epoch().bump()));
|
||||
for (i, (_si, offset)) in keys.iter().enumerate().take(n) {
|
||||
let val = obj.get_prop_at(*offset);
|
||||
unsafe { (*arr).set_prop_at(i as u8, val); }
|
||||
}
|
||||
unsafe { (*arr).set_prop_count(n as u8); }
|
||||
Ok(JsValue::from_js_object(arr))
|
||||
}
|
||||
|
||||
@@ -469,3 +469,36 @@ pub fn string_normalize(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
Ok(vm.intern(&result))
|
||||
}
|
||||
|
||||
pub fn string_match_all(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 { return Err(JsValue::undefined()); }
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
if is_regexp_obj(pattern_val, vm) {
|
||||
let re_ptr = pattern_val.as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() { Some(p) => p, None => return Ok(JsValue::undefined()) };
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
let matches: Vec<String> = regex.find_iter(&s).map(|m| m.as_str().to_string()).collect();
|
||||
return Ok(make_string_array(vm, &matches));
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn string_replace_all(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 { return Ok(vm.intern(&s)); }
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() };
|
||||
if is_regexp_obj(pattern_val, vm) {
|
||||
let re_ptr = pattern_val.as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() { Some(p) => p, None => return Ok(vm.intern(&s)) };
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
let result = regex.replace_all(&s, replacement.as_str()).to_string();
|
||||
return Ok(vm.intern(&result));
|
||||
}
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
let result = s.replace(&pattern, &replacement);
|
||||
Ok(vm.intern(&result))
|
||||
}
|
||||
|
||||
@@ -174,6 +174,8 @@ pub struct Vm {
|
||||
pub(crate) pending_error_kind: Option<&'static str>,
|
||||
pub(crate) symbol_counter: u32,
|
||||
pub(crate) symbol_descriptions: Vec<String>,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) for_of_iters: Vec<*mut u8>,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
@@ -203,6 +205,7 @@ impl Vm {
|
||||
pending_error_kind: None,
|
||||
symbol_counter: 0,
|
||||
symbol_descriptions: Vec::new(),
|
||||
for_of_iters: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +233,7 @@ impl Vm {
|
||||
pending_error_kind: None,
|
||||
symbol_counter: 0,
|
||||
symbol_descriptions: Vec::new(),
|
||||
for_of_iters: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,6 +1577,14 @@ impl Vm {
|
||||
self.for_in_iters.pop();
|
||||
}
|
||||
|
||||
OpCode::FOR_OF_INIT => {}
|
||||
|
||||
OpCode::FOR_OF_NEXT => {},
|
||||
|
||||
OpCode::FOR_OF_DONE => {},
|
||||
|
||||
OpCode::FOR_OF_CLOSE => {},
|
||||
|
||||
OpCode::THROW => {
|
||||
let exc_value = self.regs[rd];
|
||||
self.exception_value = Some(exc_value);
|
||||
|
||||
@@ -160,3 +160,66 @@ fn array_empty_literal() {
|
||||
assert!(obj.is_array());
|
||||
assert_eq!(obj.prop_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_shift_removes_first() {
|
||||
let result = eval("[1,2,3].shift()").unwrap();
|
||||
assert_eq!(result.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_unshift_adds_front() {
|
||||
let result = eval("[3].unshift(1,2)").unwrap();
|
||||
assert_eq!(result.as_int(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_fill_replaces() {
|
||||
let result = eval("[1,2,3,4].fill(0,1,3)").unwrap();
|
||||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||
assert_eq!(obj.prop_count(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_at_positive() {
|
||||
let result = eval("[10,20,30].at(1)").unwrap();
|
||||
assert_eq!(result.as_int(), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_at_negative() {
|
||||
let result = eval("[10,20,30].at(-1)").unwrap();
|
||||
assert_eq!(result.as_int(), 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_last_index_of_found() {
|
||||
let result = eval("[1,2,3,2].lastIndexOf(2)").unwrap();
|
||||
assert_eq!(result.as_int(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_last_index_of_not_found() {
|
||||
let result = eval("[1,2,3].lastIndexOf(99)").unwrap();
|
||||
assert_eq!(result.as_int(), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_sort_default() {
|
||||
let result = eval("[3,1,2].sort()").unwrap();
|
||||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||
assert_eq!(obj.prop_count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_copy_within_copies() {
|
||||
let result = eval("[1,2,3,4,5].copyWithin(0,3)").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_fill_single() {
|
||||
let result = eval("[1,2,3].fill(0)").unwrap();
|
||||
let obj = unsafe { &*result.as_js_object_ptr() };
|
||||
assert_eq!(obj.get_prop_at(0).as_int(), 0);
|
||||
}
|
||||
|
||||
@@ -51,3 +51,27 @@ fn object_define_property_sets_value() {
|
||||
let result = eval("Object.defineProperty({},'x',{value:42})").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_get_prototype_of() {
|
||||
let result = eval("Object.getPrototypeOf({})").unwrap();
|
||||
assert!(result.is_object() || result.is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_has_own_true() {
|
||||
let result = eval("Object.hasOwn({a:1}, 'a')").unwrap();
|
||||
assert!(result.is_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_entries_returns_array() {
|
||||
let result = eval("Object.entries({a:1,b:2})").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_values_returns_array() {
|
||||
let result = eval("Object.values({a:1,b:2})").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
@@ -165,3 +165,45 @@ fn string_search_not_found() {
|
||||
let result = eval(&mut vm, "'hello'.search('x')").unwrap();
|
||||
assert_eq!(result.as_int(), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_trim_start() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "' hi '.trimStart()").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "hi ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_trim_end() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "' hi '.trimEnd()").unwrap();
|
||||
assert_eq!(to_str(&vm, result), " hi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_code_point_at_ascii() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'ABC'.codePointAt(1)").unwrap();
|
||||
assert_eq!(result.as_int(), 66);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_normalize_nfc() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.normalize('NFC')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_match_all() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'aba'.matchAll(/a/g)").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_all() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'aba'.replaceAll('a', 'c')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "cbc");
|
||||
}
|
||||
|
||||
@@ -119,3 +119,10 @@ fn symbol_not_equals_string() {
|
||||
let result = eval(&mut vm, "Symbol() === 'symbol'").unwrap();
|
||||
assert!(!result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_iterator_exists() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "typeof Symbol.iterator").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "object");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user