mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
refactor: 统一调用调度并安全化 IC 缓存
This commit is contained in:
@@ -4,12 +4,15 @@ use oxide_types::value::JsValue;
|
||||
|
||||
use crate::coercion;
|
||||
use crate::native::{NativeFn, NativeResult};
|
||||
use crate::vm::Vm;
|
||||
use crate::vm::{native_fn_ptr_to_fn, Vm};
|
||||
|
||||
macro_rules! array_ptr {
|
||||
($vm:expr, $args:expr) => {
|
||||
get_this_array_ref($vm.reg($args[0]))?
|
||||
};
|
||||
($vm:expr, $args:expr) => {{
|
||||
match get_this_array_ref($vm.reg($args[0])) {
|
||||
Ok(ptr) => ptr,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! array_ptr_len {
|
||||
@@ -43,17 +46,19 @@ fn create_new_array(vm: &mut Vm, n: usize) -> *mut JsObject {
|
||||
|
||||
fn invoke_native_callback(vm: &mut Vm, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue]) -> NativeResult {
|
||||
if !callback_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let cb_ptr = callback_val.as_js_object_ptr();
|
||||
if cb_ptr.is_null() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let cb = unsafe { &*cb_ptr };
|
||||
if !cb.is_function() || cb.native_fn().is_none() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let func: NativeFn = unsafe { std::mem::transmute(cb.native_fn().unwrap()) };
|
||||
// SAFETY: native_fn was set via set_native_fn with a valid NativeFn pointer;
|
||||
// native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn.
|
||||
let func: NativeFn = unsafe { native_fn_ptr_to_fn(cb.native_fn().unwrap()) };
|
||||
|
||||
let base = 240u8;
|
||||
let n = cb_args.len().min(15);
|
||||
@@ -79,14 +84,14 @@ pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let arr = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n, vm.epoch().bump()));
|
||||
return Ok(JsValue::from_js_object(arr));
|
||||
return NativeResult::Ok(JsValue::from_js_object(arr));
|
||||
}
|
||||
if val.is_double() {
|
||||
let n = val.as_double() as usize;
|
||||
let arr = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n, vm.epoch().bump()));
|
||||
return Ok(JsValue::from_js_object(arr));
|
||||
return NativeResult::Ok(JsValue::from_js_object(arr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,22 +107,22 @@ pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
unsafe {
|
||||
(*arr).set_prop_count(n_elems);
|
||||
}
|
||||
Ok(JsValue::from_js_object(arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(arr))
|
||||
}
|
||||
|
||||
pub fn array_is_array(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_object() {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let ptr = val.as_js_object_ptr();
|
||||
if ptr.is_null() {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
Ok(JsValue::bool(unsafe { &*ptr }.is_array()))
|
||||
NativeResult::Ok(JsValue::bool(unsafe { &*ptr }.is_array()))
|
||||
}
|
||||
|
||||
pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -130,7 +135,7 @@ pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
len += 1;
|
||||
}
|
||||
arr.set_prop_count(len);
|
||||
Ok(JsValue::int(len as i32))
|
||||
NativeResult::Ok(JsValue::int(len as i32))
|
||||
}
|
||||
|
||||
pub fn array_pop(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -138,11 +143,11 @@ pub fn array_pop(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let arr = unsafe { &mut *arr_ptr };
|
||||
let len = arr.prop_count();
|
||||
if len == 0 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let last = arr.get_prop_at(len - 1);
|
||||
arr.set_prop_count(len - 1);
|
||||
Ok(last)
|
||||
NativeResult::Ok(last)
|
||||
}
|
||||
|
||||
pub fn array_slice(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -170,7 +175,7 @@ pub fn array_slice(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
(*new_arr).set_prop_count(count);
|
||||
}
|
||||
Ok(JsValue::from_js_object(new_arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||||
}
|
||||
|
||||
pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -232,7 +237,7 @@ pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
(*removed_arr).set_prop_count(removed.len());
|
||||
}
|
||||
Ok(JsValue::from_js_object(removed_arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(removed_arr))
|
||||
}
|
||||
|
||||
pub fn array_concat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -267,7 +272,7 @@ pub fn array_concat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
(*new_arr).set_prop_count(all.len());
|
||||
}
|
||||
Ok(JsValue::from_js_object(new_arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||||
}
|
||||
|
||||
pub fn array_join(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -292,7 +297,7 @@ pub fn array_join(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
.collect();
|
||||
let joined = parts.join(&sep);
|
||||
let result_val = vm.intern(&joined);
|
||||
Ok(result_val)
|
||||
NativeResult::Ok(result_val)
|
||||
}
|
||||
|
||||
pub fn array_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -300,15 +305,15 @@ pub fn array_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let arr = unsafe { &*arr_ptr };
|
||||
let n = arr.prop_count() as usize;
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::int(-1));
|
||||
return NativeResult::Ok(JsValue::int(-1));
|
||||
}
|
||||
let target = vm.reg(args[1]);
|
||||
for i in 0..n {
|
||||
if coercion::strict_equality(arr.get_prop_at(i), target) {
|
||||
return Ok(JsValue::int(i as i32));
|
||||
return NativeResult::Ok(JsValue::int(i as i32));
|
||||
}
|
||||
}
|
||||
Ok(JsValue::int(-1))
|
||||
NativeResult::Ok(JsValue::int(-1))
|
||||
}
|
||||
|
||||
pub fn array_includes(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -316,7 +321,7 @@ pub fn array_includes(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let arr = unsafe { &*arr_ptr };
|
||||
let n = arr.prop_count() as usize;
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let target = vm.reg(args[1]);
|
||||
for i in 0..n {
|
||||
@@ -325,14 +330,14 @@ pub fn array_includes(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let ea = elem.as_double();
|
||||
let ta = target.as_double();
|
||||
if ea.is_nan() && ta.is_nan() {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
}
|
||||
if coercion::strict_equality(elem, target) {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
}
|
||||
Ok(JsValue::bool(false))
|
||||
NativeResult::Ok(JsValue::bool(false))
|
||||
}
|
||||
|
||||
pub fn array_reverse(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -348,7 +353,7 @@ pub fn array_reverse(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
i += 1;
|
||||
j = j.saturating_sub(1);
|
||||
}
|
||||
Ok(vm.reg(args[0]))
|
||||
NativeResult::Ok(vm.reg(args[0]))
|
||||
}
|
||||
|
||||
pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -391,73 +396,73 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
(*new_arr).set_prop_count(flat.len());
|
||||
}
|
||||
Ok(JsValue::from_js_object(new_arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||||
}
|
||||
|
||||
pub fn array_for_each(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
if !callback_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in 0..n {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
let _ = invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]);
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn array_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
if !callback_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
let new_arr = create_new_array(vm, n);
|
||||
for i in 0..n {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) {
|
||||
Ok(mapped) => unsafe {
|
||||
NativeResult::Ok(mapped) => unsafe {
|
||||
(*new_arr).set_prop_at(i, mapped);
|
||||
},
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
(*new_arr).set_prop_count(n);
|
||||
}
|
||||
Ok(JsValue::from_js_object(new_arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||||
}
|
||||
|
||||
pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
if !callback_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
let mut kept: Vec<JsValue> = Vec::new();
|
||||
for i in 0..n {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) {
|
||||
Ok(result_val) => {
|
||||
NativeResult::Ok(result_val) => {
|
||||
let sf = vm.kernel().string_forge().as_ref();
|
||||
if coercion::to_boolean(result_val, sf) {
|
||||
kept.push(elem);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
let new_arr = create_new_array(vm, kept.len());
|
||||
@@ -467,20 +472,20 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
(*new_arr).set_prop_count(kept.len());
|
||||
}
|
||||
Ok(JsValue::from_js_object(new_arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||||
}
|
||||
|
||||
pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if n == 0 && args.len() < 3 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
if args.len() < 2 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
if !callback_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let has_initial = args.len() > 2;
|
||||
let mut accumulator;
|
||||
@@ -501,103 +506,103 @@ pub fn array_reduce(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
this_val,
|
||||
&[accumulator, elem, JsValue::int(i as i32), vm.reg(args[0])],
|
||||
) {
|
||||
Ok(result) => accumulator = result,
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Ok(result) => accumulator = result,
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
Ok(accumulator)
|
||||
NativeResult::Ok(accumulator)
|
||||
}
|
||||
|
||||
pub fn array_find(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
if !callback_val.is_object() {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in 0..n {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) {
|
||||
Ok(result_val) => {
|
||||
NativeResult::Ok(result_val) => {
|
||||
let sf = vm.kernel().string_forge().as_ref();
|
||||
if coercion::to_boolean(result_val, sf) {
|
||||
return Ok(elem);
|
||||
return NativeResult::Ok(elem);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn array_some(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
if !callback_val.is_object() {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in 0..n {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) {
|
||||
Ok(result_val) => {
|
||||
NativeResult::Ok(result_val) => {
|
||||
let sf = vm.kernel().string_forge().as_ref();
|
||||
if coercion::to_boolean(result_val, sf) {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
Ok(JsValue::bool(false))
|
||||
NativeResult::Ok(JsValue::bool(false))
|
||||
}
|
||||
|
||||
pub fn array_every(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
if !callback_val.is_object() {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in 0..n {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) {
|
||||
Ok(result_val) => {
|
||||
NativeResult::Ok(result_val) => {
|
||||
let sf = vm.kernel().string_forge().as_ref();
|
||||
if !coercion::to_boolean(result_val, sf) {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
Ok(JsValue::bool(true))
|
||||
NativeResult::Ok(JsValue::bool(true))
|
||||
}
|
||||
|
||||
pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
if !callback_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
let mut flat: Vec<JsValue> = Vec::new();
|
||||
for i in 0..n {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) {
|
||||
Ok(result) => {
|
||||
NativeResult::Ok(result) => {
|
||||
if result.is_object() {
|
||||
let r_ptr = result.as_js_object_ptr();
|
||||
if !r_ptr.is_null() {
|
||||
@@ -613,7 +618,7 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
flat.push(result);
|
||||
}
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
let new_arr = create_new_array(vm, flat.len());
|
||||
@@ -623,7 +628,7 @@ pub fn array_flat_map(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
(*new_arr).set_prop_count(flat.len());
|
||||
}
|
||||
Ok(JsValue::from_js_object(new_arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||||
}
|
||||
|
||||
pub fn array_shift(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -631,7 +636,7 @@ pub fn array_shift(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let arr = unsafe { &mut *arr_ptr };
|
||||
let len = arr.prop_count();
|
||||
if len == 0 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let first = arr.get_prop_at(0);
|
||||
for i in 1..len {
|
||||
@@ -639,7 +644,7 @@ pub fn array_shift(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
arr.set_prop_at(i - 1, v);
|
||||
}
|
||||
arr.set_prop_count(len - 1);
|
||||
Ok(first)
|
||||
NativeResult::Ok(first)
|
||||
}
|
||||
|
||||
pub fn array_unshift(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -656,7 +661,7 @@ pub fn array_unshift(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
let new_len = len as usize + n_items;
|
||||
arr.set_prop_count(new_len);
|
||||
Ok(JsValue::int(new_len as i32))
|
||||
NativeResult::Ok(JsValue::int(new_len as i32))
|
||||
}
|
||||
|
||||
pub fn array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -678,7 +683,7 @@ pub fn array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
for i in start..end {
|
||||
arr.set_prop_at(i, value);
|
||||
}
|
||||
Ok(vm.reg(args[0]))
|
||||
NativeResult::Ok(vm.reg(args[0]))
|
||||
}
|
||||
|
||||
pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -686,7 +691,7 @@ pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let arr = unsafe { &mut *arr_ptr };
|
||||
let len = arr.prop_count() as usize;
|
||||
if len == 0 {
|
||||
return Ok(vm.reg(args[0]));
|
||||
return NativeResult::Ok(vm.reg(args[0]));
|
||||
}
|
||||
let target = if args.len() > 1 {
|
||||
(coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32) as usize
|
||||
@@ -712,7 +717,7 @@ pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
arr.set_prop_at(to, v);
|
||||
to += 1;
|
||||
}
|
||||
Ok(vm.reg(args[0]))
|
||||
NativeResult::Ok(vm.reg(args[0]))
|
||||
}
|
||||
|
||||
pub fn array_at(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -728,9 +733,9 @@ pub fn array_at(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
index += len;
|
||||
}
|
||||
if index < 0 || index >= len {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
Ok(arr.get_prop_at(index))
|
||||
NativeResult::Ok(arr.get_prop_at(index))
|
||||
}
|
||||
|
||||
pub fn array_last_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -740,31 +745,31 @@ pub fn array_last_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let search = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
for i in (0..len).rev() {
|
||||
if coercion::strict_equality(arr.get_prop_at(i), search) {
|
||||
return Ok(JsValue::int(i));
|
||||
return NativeResult::Ok(JsValue::int(i));
|
||||
}
|
||||
}
|
||||
Ok(JsValue::int(-1))
|
||||
NativeResult::Ok(JsValue::int(-1))
|
||||
}
|
||||
|
||||
pub fn array_find_index(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::int(-1));
|
||||
return NativeResult::Ok(JsValue::int(-1));
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in 0..n {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) {
|
||||
Ok(r) => {
|
||||
NativeResult::Ok(r) => {
|
||||
if coercion::to_boolean(r, vm.kernel().string_forge().as_ref()) {
|
||||
return Ok(JsValue::int(i as i32));
|
||||
return NativeResult::Ok(JsValue::int(i as i32));
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
Ok(JsValue::int(-1))
|
||||
NativeResult::Ok(JsValue::int(-1))
|
||||
}
|
||||
|
||||
pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -773,35 +778,35 @@ pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
(arr_ptr, len as i32)
|
||||
};
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
for i in (0..n).rev() {
|
||||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i), vm.reg(args[0])]) {
|
||||
Ok(r) => {
|
||||
NativeResult::Ok(r) => {
|
||||
if coercion::to_boolean(r, vm.kernel().string_forge().as_ref()) {
|
||||
return Ok(elem);
|
||||
return NativeResult::Ok(elem);
|
||||
}
|
||||
}
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn array_reduce_right(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (arr_ptr, n) = array_ptr_len!(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let callback_val = vm.reg(args[1]);
|
||||
let (mut acc, start_idx): (JsValue, i32) = if args.len() > 2 {
|
||||
(vm.reg(args[2]), n as i32 - 1)
|
||||
} else {
|
||||
if n == 0 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
(unsafe { (*arr_ptr).get_prop_at(n - 1) }, n as i32 - 2)
|
||||
};
|
||||
@@ -813,11 +818,11 @@ pub fn array_reduce_right(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
JsValue::undefined(),
|
||||
&[acc, elem, JsValue::int(i), vm.reg(args[0])],
|
||||
) {
|
||||
Ok(r) => acc = r,
|
||||
Err(_) => return Err(JsValue::undefined()),
|
||||
NativeResult::Ok(r) => acc = r,
|
||||
NativeResult::Err(_) | NativeResult::TailCall { .. } => return NativeResult::Err(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
Ok(acc)
|
||||
NativeResult::Ok(acc)
|
||||
}
|
||||
|
||||
pub fn array_sort(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
@@ -833,5 +838,5 @@ pub fn array_sort(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
for (i, &v) in vals.iter().enumerate() {
|
||||
arr.set_prop_at(i, v);
|
||||
}
|
||||
Ok(vm.reg(_args[0]))
|
||||
NativeResult::Ok(vm.reg(_args[0]))
|
||||
}
|
||||
|
||||
@@ -42,18 +42,18 @@ pub fn boolean_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
|
||||
if !is_ctor {
|
||||
return Ok(JsValue::bool(bool_val));
|
||||
return NativeResult::Ok(JsValue::bool(bool_val));
|
||||
}
|
||||
|
||||
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
|
||||
obj.push_prop(JsValue::bool(bool_val));
|
||||
Ok(this_val)
|
||||
NativeResult::Ok(this_val)
|
||||
}
|
||||
|
||||
pub fn boolean_prototype_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if !this_val.is_object() {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Boolean.prototype.valueOf called on non-Boolean object",
|
||||
));
|
||||
@@ -62,19 +62,19 @@ pub fn boolean_prototype_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let boolean_proto = vm.kernel().builtin_world().boolean_proto.as_ptr() as *mut JsObject;
|
||||
let proto_ptr = obj.proto().as_js_object_ptr();
|
||||
if proto_ptr.is_null() || !std::ptr::eq(proto_ptr, boolean_proto) {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Boolean.prototype.valueOf called on non-Boolean object",
|
||||
));
|
||||
}
|
||||
let val = obj.get_prop_at(0);
|
||||
Ok(val)
|
||||
NativeResult::Ok(val)
|
||||
}
|
||||
|
||||
pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if !this_val.is_object() {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Boolean.prototype.toString called on non-Boolean object",
|
||||
));
|
||||
@@ -83,7 +83,7 @@ pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let boolean_proto = vm.kernel().builtin_world().boolean_proto.as_ptr() as *mut JsObject;
|
||||
let proto_ptr = obj.proto().as_js_object_ptr();
|
||||
if proto_ptr.is_null() || !std::ptr::eq(proto_ptr, boolean_proto) {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Boolean.prototype.toString called on non-Boolean object",
|
||||
));
|
||||
@@ -95,8 +95,8 @@ pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
!val.is_null() && !val.is_undefined()
|
||||
};
|
||||
if is_true {
|
||||
Ok(vm.intern("true"))
|
||||
NativeResult::Ok(vm.intern("true"))
|
||||
} else {
|
||||
Ok(vm.intern("false"))
|
||||
NativeResult::Ok(vm.intern("false"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,20 @@ fn is_date(vm: &Vm, obj: &JsObject) -> bool {
|
||||
std::ptr::eq(proto_ptr, date_proto)
|
||||
}
|
||||
|
||||
fn ensure_date(vm: &mut Vm, obj: &JsObject) -> NativeResult {
|
||||
fn ensure_date(vm: &mut Vm, obj: &JsObject) -> Result<(), JsValue> {
|
||||
if !is_date(vm, obj) {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "called on incompatible receiver"));
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! native_try {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(value) => value,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn date_this_mut(vm: &mut Vm, args: &[u8]) -> Result<*mut JsObject, JsValue> {
|
||||
@@ -185,7 +194,7 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
} else {
|
||||
"Invalid Date".to_string()
|
||||
};
|
||||
return Ok(vm.intern(&s));
|
||||
return NativeResult::Ok(vm.intern(&s));
|
||||
}
|
||||
|
||||
let mut obj = JsObject::new_empty(
|
||||
@@ -195,20 +204,20 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
obj.set_prop_at(0, JsValue::float(timestamp));
|
||||
|
||||
let ptr = vm.epoch().alloc(obj);
|
||||
Ok(JsValue::from_js_object(ptr))
|
||||
NativeResult::Ok(JsValue::from_js_object(ptr))
|
||||
}
|
||||
|
||||
pub fn date_now(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
Ok(JsValue::float(Utc::now().timestamp_millis() as f64))
|
||||
NativeResult::Ok(JsValue::float(Utc::now().timestamp_millis() as f64))
|
||||
}
|
||||
|
||||
pub fn date_parse(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_string() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let s = vm.kernel().string_forge().lookup(val.as_string_index()).unwrap_or_default();
|
||||
let formats = ["%Y-%m-%dT%H:%M:%S%.fZ", "%Y-%m-%dT%H:%M:%S%.f"];
|
||||
@@ -226,24 +235,24 @@ pub fn date_parse(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(JsValue::float(ts))
|
||||
NativeResult::Ok(JsValue::float(ts))
|
||||
}
|
||||
|
||||
pub fn date_utc(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
Ok(JsValue::float(f64::NAN))
|
||||
NativeResult::Ok(JsValue::float(f64::NAN))
|
||||
}
|
||||
|
||||
macro_rules! make_getter {
|
||||
($name:ident, $f:expr, $df:expr) => {
|
||||
pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj = unsafe { &*date_this(vm, args)? };
|
||||
let obj = unsafe { &*native_try!(date_this(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float($df));
|
||||
return NativeResult::Ok(JsValue::float($df));
|
||||
}
|
||||
match dt_from_ms(ms) {
|
||||
Some(dt) => Ok(JsValue::float(($f)(dt))),
|
||||
None => Ok(JsValue::float($df)),
|
||||
Some(dt) => NativeResult::Ok(JsValue::float(($f)(dt))),
|
||||
None => NativeResult::Ok(JsValue::float($df)),
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -252,14 +261,14 @@ macro_rules! make_getter {
|
||||
macro_rules! make_utc_getter {
|
||||
($name:ident, $f:expr, $df:expr) => {
|
||||
pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj = unsafe { &*date_this(vm, args)? };
|
||||
let obj = unsafe { &*native_try!(date_this(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float($df));
|
||||
return NativeResult::Ok(JsValue::float($df));
|
||||
}
|
||||
match naive_from_ms(ms) {
|
||||
Some(ndt) => Ok(JsValue::float(($f)(ndt))),
|
||||
None => Ok(JsValue::float($df)),
|
||||
Some(ndt) => NativeResult::Ok(JsValue::float(($f)(ndt))),
|
||||
None => NativeResult::Ok(JsValue::float($df)),
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -293,9 +302,9 @@ make_utc_getter!(
|
||||
);
|
||||
|
||||
pub fn date_get_timezone_offset(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let _obj = unsafe { &*date_this(vm, args)? };
|
||||
let _obj = unsafe { &*native_try!(date_this(vm, args)) };
|
||||
let offset_min = local_offset_minutes();
|
||||
Ok(JsValue::float(offset_min as f64))
|
||||
NativeResult::Ok(JsValue::float(offset_min as f64))
|
||||
}
|
||||
|
||||
fn local_offset_minutes() -> i32 {
|
||||
@@ -311,27 +320,27 @@ fn get_opt_arg(vm: &Vm, args: &[u8], idx: usize, default: u32) -> u32 {
|
||||
}
|
||||
|
||||
pub fn date_set_time(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj = unsafe { &mut *date_this_mut(vm, args)? };
|
||||
let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) };
|
||||
let val = if args.len() > 1 {
|
||||
coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref())
|
||||
} else {
|
||||
f64::NAN
|
||||
};
|
||||
set_timestamp(obj, val);
|
||||
Ok(JsValue::float(val))
|
||||
NativeResult::Ok(JsValue::float(val))
|
||||
}
|
||||
|
||||
pub fn date_set_full_year(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let v = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
let obj = unsafe { &mut *date_this_mut(vm, args)? };
|
||||
let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let dt = match dt_from_ms(ms) {
|
||||
Some(d) => d,
|
||||
None => return Ok(JsValue::float(f64::NAN)),
|
||||
None => return NativeResult::Ok(JsValue::float(f64::NAN)),
|
||||
};
|
||||
let m = get_opt_arg(vm, args, 2, dt.month0());
|
||||
let d = get_opt_arg(vm, args, 3, dt.day0());
|
||||
@@ -342,54 +351,54 @@ pub fn date_set_full_year(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
.unwrap_or(dt);
|
||||
let ts = nd.timestamp_millis() as f64;
|
||||
set_timestamp(obj, ts);
|
||||
Ok(JsValue::float(ts))
|
||||
NativeResult::Ok(JsValue::float(ts))
|
||||
}
|
||||
pub fn date_set_month(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let v = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
let obj = unsafe { &mut *date_this_mut(vm, args)? };
|
||||
let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let dt = match dt_from_ms(ms) {
|
||||
Some(d) => d,
|
||||
None => return Ok(JsValue::float(f64::NAN)),
|
||||
None => return NativeResult::Ok(JsValue::float(f64::NAN)),
|
||||
};
|
||||
let d = get_opt_arg(vm, args, 2, dt.day0());
|
||||
let nd = dt.with_month0(v as u32).and_then(|x| x.with_day0(d)).unwrap_or(dt);
|
||||
let ts = nd.timestamp_millis() as f64;
|
||||
set_timestamp(obj, ts);
|
||||
Ok(JsValue::float(ts))
|
||||
NativeResult::Ok(JsValue::float(ts))
|
||||
}
|
||||
pub fn date_set_date(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let v = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
let obj = unsafe { &mut *date_this_mut(vm, args)? };
|
||||
let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let dt = match dt_from_ms(ms) {
|
||||
Some(d) => d,
|
||||
None => return Ok(JsValue::float(f64::NAN)),
|
||||
None => return NativeResult::Ok(JsValue::float(f64::NAN)),
|
||||
};
|
||||
let nd = dt.with_day0(v as u32).unwrap_or(dt);
|
||||
let ts = nd.timestamp_millis() as f64;
|
||||
set_timestamp(obj, ts);
|
||||
Ok(JsValue::float(ts))
|
||||
NativeResult::Ok(JsValue::float(ts))
|
||||
}
|
||||
pub fn date_set_hours(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let v = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
let obj = unsafe { &mut *date_this_mut(vm, args)? };
|
||||
let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let dt = match dt_from_ms(ms) {
|
||||
Some(d) => d,
|
||||
None => return Ok(JsValue::float(f64::NAN)),
|
||||
None => return NativeResult::Ok(JsValue::float(f64::NAN)),
|
||||
};
|
||||
let min = get_opt_arg(vm, args, 2, dt.minute());
|
||||
let sec = get_opt_arg(vm, args, 3, dt.second());
|
||||
@@ -402,19 +411,19 @@ pub fn date_set_hours(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
.unwrap_or(dt);
|
||||
let ts = nd.timestamp_millis() as f64;
|
||||
set_timestamp(obj, ts);
|
||||
Ok(JsValue::float(ts))
|
||||
NativeResult::Ok(JsValue::float(ts))
|
||||
}
|
||||
pub fn date_set_minutes(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let v = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
let obj = unsafe { &mut *date_this_mut(vm, args)? };
|
||||
let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let dt = match dt_from_ms(ms) {
|
||||
Some(d) => d,
|
||||
None => return Ok(JsValue::float(f64::NAN)),
|
||||
None => return NativeResult::Ok(JsValue::float(f64::NAN)),
|
||||
};
|
||||
let sec = get_opt_arg(vm, args, 2, dt.second());
|
||||
let ms_arg = get_opt_arg(vm, args, 3, dt.timestamp_subsec_millis());
|
||||
@@ -425,19 +434,19 @@ pub fn date_set_minutes(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
.unwrap_or(dt);
|
||||
let ts = nd.timestamp_millis() as f64;
|
||||
set_timestamp(obj, ts);
|
||||
Ok(JsValue::float(ts))
|
||||
NativeResult::Ok(JsValue::float(ts))
|
||||
}
|
||||
pub fn date_set_seconds(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let v = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
let obj = unsafe { &mut *date_this_mut(vm, args)? };
|
||||
let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let dt = match dt_from_ms(ms) {
|
||||
Some(d) => d,
|
||||
None => return Ok(JsValue::float(f64::NAN)),
|
||||
None => return NativeResult::Ok(JsValue::float(f64::NAN)),
|
||||
};
|
||||
let ms_arg = get_opt_arg(vm, args, 2, dt.timestamp_subsec_millis());
|
||||
let nd = dt
|
||||
@@ -446,37 +455,37 @@ pub fn date_set_seconds(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
.unwrap_or(dt);
|
||||
let ts = nd.timestamp_millis() as f64;
|
||||
set_timestamp(obj, ts);
|
||||
Ok(JsValue::float(ts))
|
||||
NativeResult::Ok(JsValue::float(ts))
|
||||
}
|
||||
pub fn date_set_milliseconds(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let v = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
let obj = unsafe { &mut *date_this_mut(vm, args)? };
|
||||
let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let dt = match dt_from_ms(ms) {
|
||||
Some(d) => d,
|
||||
None => return Ok(JsValue::float(f64::NAN)),
|
||||
None => return NativeResult::Ok(JsValue::float(f64::NAN)),
|
||||
};
|
||||
let nd = dt.with_nanosecond(v as u32 * 1_000_000).unwrap_or(dt);
|
||||
let ts = nd.timestamp_millis() as f64;
|
||||
set_timestamp(obj, ts);
|
||||
Ok(JsValue::float(ts))
|
||||
NativeResult::Ok(JsValue::float(ts))
|
||||
}
|
||||
|
||||
fn date_to_string_inner(vm: &mut Vm, args: &[u8], format_str: &str, invalid: &str) -> NativeResult {
|
||||
let obj = unsafe { &*date_this(vm, args)? };
|
||||
let obj = unsafe { &*native_try!(date_this(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(vm.intern(invalid));
|
||||
return NativeResult::Ok(vm.intern(invalid));
|
||||
}
|
||||
let s = match dt_from_ms(ms) {
|
||||
Some(dt) => dt.format(format_str).to_string(),
|
||||
None => invalid.to_string(),
|
||||
};
|
||||
Ok(vm.intern(&s))
|
||||
NativeResult::Ok(vm.intern(&s))
|
||||
}
|
||||
|
||||
pub fn date_to_iso_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -484,16 +493,16 @@ pub fn date_to_iso_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
|
||||
pub fn date_to_json(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj = unsafe { &*date_this(vm, args)? };
|
||||
let obj = unsafe { &*native_try!(date_this(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return Ok(JsValue::null());
|
||||
return NativeResult::Ok(JsValue::null());
|
||||
}
|
||||
let s = match dt_from_ms(ms) {
|
||||
Some(dt) => dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(),
|
||||
None => return Ok(JsValue::null()),
|
||||
None => return NativeResult::Ok(JsValue::null()),
|
||||
};
|
||||
Ok(vm.intern(&s))
|
||||
NativeResult::Ok(vm.intern(&s))
|
||||
}
|
||||
|
||||
pub fn date_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -513,7 +522,7 @@ pub fn date_to_utc_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
|
||||
pub fn date_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj = unsafe { &*date_this(vm, args)? };
|
||||
let obj = unsafe { &*native_try!(date_this(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
Ok(JsValue::float(ms))
|
||||
NativeResult::Ok(JsValue::float(ms))
|
||||
}
|
||||
|
||||
@@ -72,43 +72,43 @@ fn get_msg(vm: &mut Vm, args: &[u8]) -> String {
|
||||
pub fn error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let msg = get_msg(vm, args);
|
||||
let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().error_proto) as *mut JsObject;
|
||||
Ok(create_error_object(vm, proto_ptr, "Error", &msg))
|
||||
NativeResult::Ok(create_error_object(vm, proto_ptr, "Error", &msg))
|
||||
}
|
||||
|
||||
pub fn type_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let msg = get_msg(vm, args);
|
||||
let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().type_error_proto) as *mut JsObject;
|
||||
Ok(create_error_object(vm, proto_ptr, "TypeError", &msg))
|
||||
NativeResult::Ok(create_error_object(vm, proto_ptr, "TypeError", &msg))
|
||||
}
|
||||
|
||||
pub fn reference_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let msg = get_msg(vm, args);
|
||||
let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().reference_error_proto) as *mut JsObject;
|
||||
Ok(create_error_object(vm, proto_ptr, "ReferenceError", &msg))
|
||||
NativeResult::Ok(create_error_object(vm, proto_ptr, "ReferenceError", &msg))
|
||||
}
|
||||
|
||||
pub fn range_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let msg = get_msg(vm, args);
|
||||
let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().range_error_proto) as *mut JsObject;
|
||||
Ok(create_error_object(vm, proto_ptr, "RangeError", &msg))
|
||||
NativeResult::Ok(create_error_object(vm, proto_ptr, "RangeError", &msg))
|
||||
}
|
||||
|
||||
pub fn syntax_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let msg = get_msg(vm, args);
|
||||
let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().syntax_error_proto) as *mut JsObject;
|
||||
Ok(create_error_object(vm, proto_ptr, "SyntaxError", &msg))
|
||||
NativeResult::Ok(create_error_object(vm, proto_ptr, "SyntaxError", &msg))
|
||||
}
|
||||
|
||||
pub fn uri_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let msg = get_msg(vm, args);
|
||||
let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().uri_error_proto) as *mut JsObject;
|
||||
Ok(create_error_object(vm, proto_ptr, "URIError", &msg))
|
||||
NativeResult::Ok(create_error_object(vm, proto_ptr, "URIError", &msg))
|
||||
}
|
||||
|
||||
pub fn eval_error_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let msg = get_msg(vm, args);
|
||||
let proto_ptr = P::as_ptr(&vm.kernel().builtin_world().eval_error_proto) as *mut JsObject;
|
||||
Ok(create_error_object(vm, proto_ptr, "EvalError", &msg))
|
||||
NativeResult::Ok(create_error_object(vm, proto_ptr, "EvalError", &msg))
|
||||
}
|
||||
|
||||
pub fn error_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -134,7 +134,7 @@ pub fn error_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let result = if msg.is_empty() { name } else { format!("{}: {}", name, msg) };
|
||||
let si = vm.kernel().string_forge().intern(&result).0;
|
||||
Ok(JsValue::string(si, 0))
|
||||
NativeResult::Ok(JsValue::string(si, 0))
|
||||
}
|
||||
|
||||
pub fn error_stack_getter(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
@@ -150,13 +150,13 @@ pub fn error_stack_getter(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
lines.push(" at <anonymous> (native)".to_string());
|
||||
let result = lines.join("\n");
|
||||
let si = vm.kernel().string_forge().intern(&result).0;
|
||||
Ok(JsValue::string(si, 0))
|
||||
NativeResult::Ok(JsValue::string(si, 0))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::error_stack_getter;
|
||||
use crate::vm::{CallFrame, Vm};
|
||||
use crate::vm::{CallFrame, FrameContinuation, Vm};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
#[test]
|
||||
@@ -174,6 +174,7 @@ mod tests {
|
||||
construct_result_reg: None,
|
||||
constructed_this: None,
|
||||
is_derived_constructor: false,
|
||||
continuation: FrameContinuation::None,
|
||||
});
|
||||
|
||||
let stack = error_stack_getter(&mut vm, &[0]).unwrap();
|
||||
|
||||
@@ -1,43 +1,17 @@
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::object::{JsObject, NativeFnPtr};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::native::NativeResult;
|
||||
use crate::vm::Vm;
|
||||
|
||||
/// Rebuild a typed JS error object from `call_function_sync`'s `String` error.
|
||||
///
|
||||
/// `call_function_sync` collapses thrown exceptions to a `String` shaped like
|
||||
/// `"TypeError: message"`. Native builtins must return `Err(JsValue)`, and
|
||||
/// test262 negative tests match on the error's `name`, so we parse the prefix
|
||||
/// back into the right error kind instead of flattening everything to `Error`.
|
||||
fn error_from_text(vm: &mut Vm, text: &str) -> JsValue {
|
||||
use crate::builtins::error;
|
||||
let text = text.strip_prefix("uncaught ").unwrap_or(text);
|
||||
let (kind, msg) = match text.split_once(": ") {
|
||||
Some((k, m)) => (k, m),
|
||||
None => ("", text),
|
||||
};
|
||||
match kind {
|
||||
"TypeError" => error::create_type_error(vm, msg),
|
||||
"ReferenceError" => error::create_reference_error(vm, msg),
|
||||
"RangeError" => error::create_range_error(vm, msg),
|
||||
"SyntaxError" => error::create_syntax_error(vm, msg),
|
||||
_ => error::create_error(vm, text),
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke any callee (native or user/bytecode function) with an explicit
|
||||
/// receiver and register-sourced arguments.
|
||||
///
|
||||
/// Delegates to `Vm::call_function_sync`, the engine's unified synchronous call
|
||||
/// path (also used by getter/setter dispatch). The previous hand-rolled version
|
||||
/// only supported native targets, so `Function.prototype.call/apply/bind` threw
|
||||
/// on user functions — a major test262 harness blocker.
|
||||
fn invoke_target(vm: &mut Vm, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult {
|
||||
let args: Vec<JsValue> = arg_regs.iter().map(|&r| vm.reg(r)).collect();
|
||||
vm.call_function_sync(target_val, this_val, &args)
|
||||
.map_err(|msg| error_from_text(vm, &msg))
|
||||
NativeResult::TailCall {
|
||||
callee: target_val,
|
||||
this: this_val,
|
||||
args,
|
||||
}
|
||||
}
|
||||
|
||||
fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -59,7 +33,7 @@ fn bind_dispatcher(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
pub fn function_call(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.is_empty() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let target_val = vm.reg(args[0]);
|
||||
let this_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
@@ -69,7 +43,7 @@ pub fn function_call(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
pub fn function_apply(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.is_empty() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let target_val = vm.reg(args[0]);
|
||||
let this_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
@@ -82,7 +56,8 @@ pub fn function_apply(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if !arr_ptr.is_null() {
|
||||
let arr = unsafe { &*arr_ptr };
|
||||
if arr.is_array() {
|
||||
let n = arr.hash_props_vec().map_or(0, |v| v.len());
|
||||
let max_args = 55usize; // base=200, max safe register is 255.
|
||||
let n = arr.hash_props_vec().map_or(0, |v| v.len()).min(max_args);
|
||||
let base = 200u8;
|
||||
arg_regs = (0..n).map(|i| base + i as u8).collect();
|
||||
for i in 0..n {
|
||||
@@ -108,30 +83,31 @@ pub fn function_apply(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
pub fn function_bind(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.is_empty() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let target_val = vm.reg(args[0]);
|
||||
if !target_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let bound_this = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
|
||||
let wrapper = vm.epoch().alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
unsafe {
|
||||
(*wrapper).set_function(true);
|
||||
(*wrapper).set_native_fn(Some(bind_dispatcher as *const ()));
|
||||
// SAFETY: bind_dispatcher is a NativeFn fn-item; valid to store as NativeFnPtr.
|
||||
(*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher as *const ())));
|
||||
(*wrapper).set_native_arg_count(0);
|
||||
(*wrapper).ensure_hash_props().push(Box::new(target_val));
|
||||
(*wrapper).ensure_hash_props().push(Box::new(bound_this));
|
||||
}
|
||||
|
||||
Ok(JsValue::from_js_object(wrapper))
|
||||
NativeResult::Ok(JsValue::from_js_object(wrapper))
|
||||
}
|
||||
|
||||
pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if !this_val.is_object() {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Function.prototype.toString called on non-function",
|
||||
));
|
||||
@@ -139,7 +115,7 @@ pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
let func = unsafe { &*this_val.as_js_object_ptr() };
|
||||
if !func.is_function() {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Function.prototype.toString called on non-function",
|
||||
));
|
||||
@@ -171,5 +147,5 @@ pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
format!("function {name}() {{ {body} }}")
|
||||
};
|
||||
let si = vm.kernel().string_forge().intern(&result).0;
|
||||
Ok(JsValue::string(si, 0))
|
||||
NativeResult::Ok(JsValue::string(si, 0))
|
||||
}
|
||||
|
||||
@@ -11,21 +11,26 @@ use crate::vm::Vm;
|
||||
|
||||
pub fn json_parse(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Err(crate::builtins::error::create_syntax_error(vm, "JSON.parse requires 1 argument"));
|
||||
return NativeResult::Err(crate::builtins::error::create_syntax_error(vm, "JSON.parse requires 1 argument"));
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_string() {
|
||||
return Err(crate::builtins::error::create_syntax_error(vm, "JSON.parse: argument is not a string"));
|
||||
return NativeResult::Err(crate::builtins::error::create_syntax_error(
|
||||
vm,
|
||||
"JSON.parse: argument is not a string",
|
||||
));
|
||||
}
|
||||
let text = {
|
||||
let si = val.as_string_index();
|
||||
vm.kernel().string_forge().lookup(si).unwrap_or_default()
|
||||
};
|
||||
|
||||
let parsed: serde_json::Value =
|
||||
serde_json::from_str(&text).map_err(|e| crate::builtins::error::create_syntax_error(vm, &format!("{}", e)))?;
|
||||
let parsed: serde_json::Value = match serde_json::from_str(&text) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(crate::builtins::error::create_syntax_error(vm, &format!("{}", e))),
|
||||
};
|
||||
|
||||
Ok(value_to_jsvalue(vm, &parsed))
|
||||
NativeResult::Ok(value_to_jsvalue(vm, &parsed))
|
||||
}
|
||||
|
||||
fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue {
|
||||
@@ -75,17 +80,18 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue {
|
||||
|
||||
pub fn json_stringify(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let value = vm.reg(args[1]);
|
||||
if value.is_undefined() {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let mut visited = HashSet::new();
|
||||
let mut output = String::new();
|
||||
jsvalue_to_json(vm, value, &mut visited, &mut output)
|
||||
.map_err(|_| crate::builtins::error::create_type_error(vm, "cyclic object value"))?;
|
||||
Ok(vm.intern(&output))
|
||||
if jsvalue_to_json(vm, value, &mut visited, &mut output).is_err() {
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(vm, "cyclic object value"));
|
||||
};
|
||||
NativeResult::Ok(vm.intern(&output))
|
||||
}
|
||||
|
||||
fn jsvalue_to_json(vm: &Vm, val: JsValue, visited: &mut HashSet<*const u8>, out: &mut String) -> Result<(), ()> {
|
||||
|
||||
@@ -8,6 +8,25 @@ use crate::vm::Vm;
|
||||
|
||||
const MAP_PROP_INDEX: u8 = 0;
|
||||
|
||||
macro_rules! native_try {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(value) => value,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Retrieve the `IndexMap` pointer stored at `MAP_PROP_INDEX` of a Map object.
|
||||
///
|
||||
/// # Safety contract maintained by callers
|
||||
///
|
||||
/// The pointer is valid as long as the Map `JsObject` itself is alive, which is guaranteed
|
||||
/// because the `JsObject` lives in the current `Epoch` arena and `Epoch::reset()` is never
|
||||
/// called while a native builtin is executing. The `Box<IndexMap>` that owns the allocation
|
||||
/// is created in `new_map_inner()` and is never freed until the process exits (intentional
|
||||
/// leak — lifetime is tied to the epoch). Only one live `*mut` alias exists at a time
|
||||
/// per Map object because native calls are single-threaded.
|
||||
fn get_map_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut indexmap::IndexMap<SetKey, JsValue>, JsValue> {
|
||||
if !this_val.is_object() {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "called on non-Map object"));
|
||||
@@ -16,11 +35,25 @@ fn get_map_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut indexmap::IndexM
|
||||
if map_ptr.is_null() {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "Map internal state invalid"));
|
||||
}
|
||||
// SAFETY: map_ptr is a non-null, aligned pointer to a JsObject bump-allocated in the
|
||||
// current Epoch. It remains valid for the duration of this call (epoch is not reset
|
||||
// during native execution).
|
||||
let map_obj = unsafe { &*map_ptr };
|
||||
if !map_obj.is_map() {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Map.prototype method called on incompatible receiver",
|
||||
));
|
||||
}
|
||||
let inner_val = map_obj.get_prop_at(MAP_PROP_INDEX);
|
||||
if !inner_val.is_object() {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "Map internal state invalid"));
|
||||
}
|
||||
// SAFETY: inner_val holds the raw pointer written by `alloc_map` as
|
||||
// `JsValue::object(Box::into_raw(Box::new(IndexMap::new())) as *mut u8)`.
|
||||
// The pointer is a valid, heap-allocated `Box<IndexMap<SetKey, JsValue>>` and is
|
||||
// never freed during the Map object's lifetime (see module safety contract above).
|
||||
// Alignment: IndexMap requires at most 8-byte alignment; the global allocator satisfies this.
|
||||
let inner_ptr = inner_val.as_js_object_ptr() as *mut indexmap::IndexMap<SetKey, JsValue>;
|
||||
if inner_ptr.is_null() {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "Map internal state invalid"));
|
||||
@@ -28,6 +61,9 @@ fn get_map_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut indexmap::IndexM
|
||||
Ok(inner_ptr)
|
||||
}
|
||||
|
||||
/// Allocate a new `IndexMap` on the global heap.
|
||||
/// The caller is responsible for storing the pointer in the Map `JsObject` at `MAP_PROP_INDEX`
|
||||
/// via `JsValue::object(ptr as *mut u8)` and ensuring it is never double-freed.
|
||||
fn new_map_inner() -> *mut indexmap::IndexMap<SetKey, JsValue> {
|
||||
Box::into_raw(Box::new(indexmap::IndexMap::new()))
|
||||
}
|
||||
@@ -35,6 +71,9 @@ fn new_map_inner() -> *mut indexmap::IndexMap<SetKey, JsValue> {
|
||||
fn alloc_map(vm: &mut Vm) -> *mut JsObject {
|
||||
let map_proto = vm.kernel().builtin_world().map_proto.as_ptr() as *mut JsObject;
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(map_proto));
|
||||
obj.set_map(true);
|
||||
// SAFETY: new_map_inner() returns a valid Box<IndexMap> pointer; stored as a JsValue
|
||||
// object tag so get_map_inner() can retrieve and cast it back (same provenance).
|
||||
let inner = new_map_inner();
|
||||
obj.set_prop_at(MAP_PROP_INDEX, JsValue::object(inner as *mut u8));
|
||||
vm.epoch().alloc(obj)
|
||||
@@ -42,55 +81,55 @@ fn alloc_map(vm: &mut Vm) -> *mut JsObject {
|
||||
|
||||
pub fn map_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
let map_obj = alloc_map(vm);
|
||||
Ok(JsValue::from_js_object(map_obj))
|
||||
NativeResult::Ok(JsValue::from_js_object(map_obj))
|
||||
}
|
||||
|
||||
pub fn map_set(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_map_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_map_inner(vm, this_val));
|
||||
let key = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
let val = vm.reg(if args.len() > 2 { args[2] } else { 0 });
|
||||
unsafe {
|
||||
(*inner).insert(SetKey(key), val);
|
||||
}
|
||||
Ok(this_val)
|
||||
NativeResult::Ok(this_val)
|
||||
}
|
||||
|
||||
pub fn map_get(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_map_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_map_inner(vm, this_val));
|
||||
let key = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
let found = unsafe { (*inner).get(&SetKey(key)).copied() };
|
||||
Ok(found.unwrap_or(JsValue::undefined()))
|
||||
NativeResult::Ok(found.unwrap_or(JsValue::undefined()))
|
||||
}
|
||||
|
||||
pub fn map_has(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_map_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_map_inner(vm, this_val));
|
||||
let key = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
let found = unsafe { (*inner).contains_key(&SetKey(key)) };
|
||||
Ok(JsValue::bool(found))
|
||||
NativeResult::Ok(JsValue::bool(found))
|
||||
}
|
||||
|
||||
pub fn map_delete(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_map_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_map_inner(vm, this_val));
|
||||
let key = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
let removed = unsafe { (*inner).shift_remove(&SetKey(key)) };
|
||||
Ok(JsValue::bool(removed.is_some()))
|
||||
NativeResult::Ok(JsValue::bool(removed.is_some()))
|
||||
}
|
||||
|
||||
pub fn map_clear(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_map_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_map_inner(vm, this_val));
|
||||
unsafe {
|
||||
(*inner).clear();
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn map_size(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_map_inner(vm, this_val)?;
|
||||
Ok(JsValue::float(unsafe { (*inner).len() } as f64))
|
||||
let inner = native_try!(get_map_inner(vm, this_val));
|
||||
NativeResult::Ok(JsValue::float(unsafe { (*inner).len() } as f64))
|
||||
}
|
||||
|
||||
@@ -25,20 +25,20 @@ fn arg2(vm: &Vm, args: &[u8]) -> (f64, f64) {
|
||||
|
||||
pub fn math_abs(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let x = vm.reg(args[1]);
|
||||
if x.is_int() {
|
||||
Ok(JsValue::int(x.as_int().abs()))
|
||||
NativeResult::Ok(JsValue::int(x.as_int().abs()))
|
||||
} else {
|
||||
Ok(JsValue::float(num(vm, args[1]).abs()))
|
||||
NativeResult::Ok(JsValue::float(num(vm, args[1]).abs()))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! math_unary {
|
||||
($name:ident, $op:ident) => {
|
||||
pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
Ok(JsValue::float(arg1(vm, args).$op()))
|
||||
NativeResult::Ok(JsValue::float(arg1(vm, args).$op()))
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -69,87 +69,87 @@ math_unary!(math_trunc, trunc);
|
||||
|
||||
pub fn math_atan2(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (a, b) = arg2(vm, args);
|
||||
Ok(JsValue::float(a.atan2(b)))
|
||||
NativeResult::Ok(JsValue::float(a.atan2(b)))
|
||||
}
|
||||
|
||||
pub fn math_round(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let x = arg1(vm, args);
|
||||
let r = if x < 0.0 { (x - 0.5).ceil() } else { (x + 0.5).floor() };
|
||||
Ok(JsValue::float(r))
|
||||
NativeResult::Ok(JsValue::float(r))
|
||||
}
|
||||
|
||||
pub fn math_sign(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let x = arg1(vm, args);
|
||||
if x.is_nan() {
|
||||
Ok(JsValue::float(f64::NAN))
|
||||
NativeResult::Ok(JsValue::float(f64::NAN))
|
||||
} else if x > 0.0 {
|
||||
Ok(JsValue::int(1))
|
||||
NativeResult::Ok(JsValue::int(1))
|
||||
} else if x < 0.0 {
|
||||
Ok(JsValue::int(-1))
|
||||
NativeResult::Ok(JsValue::int(-1))
|
||||
} else {
|
||||
Ok(JsValue::float(x))
|
||||
NativeResult::Ok(JsValue::float(x))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn math_clz32(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let n = arg1(vm, args) as u32;
|
||||
Ok(JsValue::int(n.leading_zeros() as i32))
|
||||
NativeResult::Ok(JsValue::int(n.leading_zeros() as i32))
|
||||
}
|
||||
|
||||
pub fn math_fround(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
Ok(JsValue::float(arg1(vm, args) as f32 as f64))
|
||||
NativeResult::Ok(JsValue::float(arg1(vm, args) as f32 as f64))
|
||||
}
|
||||
|
||||
pub fn math_hypot(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (a, b) = arg2(vm, args);
|
||||
Ok(JsValue::float(a.hypot(b)))
|
||||
NativeResult::Ok(JsValue::float(a.hypot(b)))
|
||||
}
|
||||
|
||||
pub fn math_imul(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (a, b) = arg2(vm, args);
|
||||
Ok(JsValue::int((a as i32).wrapping_mul(b as i32)))
|
||||
NativeResult::Ok(JsValue::int((a as i32).wrapping_mul(b as i32)))
|
||||
}
|
||||
|
||||
pub fn math_pow(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (a, b) = arg2(vm, args);
|
||||
Ok(JsValue::float(a.powf(b)))
|
||||
NativeResult::Ok(JsValue::float(a.powf(b)))
|
||||
}
|
||||
|
||||
pub fn math_max(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::float(f64::NEG_INFINITY));
|
||||
return NativeResult::Ok(JsValue::float(f64::NEG_INFINITY));
|
||||
}
|
||||
let mut m = f64::NEG_INFINITY;
|
||||
for &r in args.iter().skip(1) {
|
||||
let x = num(vm, r);
|
||||
if x.is_nan() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
if x > m {
|
||||
m = x;
|
||||
}
|
||||
}
|
||||
Ok(JsValue::float(m))
|
||||
NativeResult::Ok(JsValue::float(m))
|
||||
}
|
||||
|
||||
pub fn math_min(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::float(f64::INFINITY));
|
||||
return NativeResult::Ok(JsValue::float(f64::INFINITY));
|
||||
}
|
||||
let mut m = f64::INFINITY;
|
||||
for &r in args.iter().skip(1) {
|
||||
let x = num(vm, r);
|
||||
if x.is_nan() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
if x < m {
|
||||
m = x;
|
||||
}
|
||||
}
|
||||
Ok(JsValue::float(m))
|
||||
NativeResult::Ok(JsValue::float(m))
|
||||
}
|
||||
|
||||
pub fn math_random(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
vm.step_rng();
|
||||
Ok(JsValue::float(vm.math_rng_value()))
|
||||
NativeResult::Ok(JsValue::float(vm.math_rng_value()))
|
||||
}
|
||||
|
||||
@@ -11,49 +11,49 @@ pub fn number_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
0.0
|
||||
};
|
||||
if n.fract() == 0.0 && n.is_finite() && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
|
||||
Ok(JsValue::int(n as i32))
|
||||
NativeResult::Ok(JsValue::int(n as i32))
|
||||
} else {
|
||||
Ok(JsValue::float(n))
|
||||
NativeResult::Ok(JsValue::float(n))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn number_is_nan(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if val.is_double() {
|
||||
Ok(JsValue::bool(val.as_double().is_nan()))
|
||||
NativeResult::Ok(JsValue::bool(val.as_double().is_nan()))
|
||||
} else {
|
||||
Ok(JsValue::bool(false))
|
||||
NativeResult::Ok(JsValue::bool(false))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn number_is_finite(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if val.is_int() {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
if val.is_double() {
|
||||
return Ok(JsValue::bool(val.as_double().is_finite()));
|
||||
return NativeResult::Ok(JsValue::bool(val.as_double().is_finite()));
|
||||
}
|
||||
let n = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
Ok(JsValue::bool(n.is_finite()))
|
||||
NativeResult::Ok(JsValue::bool(n.is_finite()))
|
||||
}
|
||||
|
||||
pub fn number_parse_int(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let sf = vm.kernel().string_forge().as_ref();
|
||||
let s = coercion::to_string(sf, vm.reg(args[1]));
|
||||
let s = s.trim();
|
||||
|
||||
if s.is_empty() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
|
||||
let radix = if args.len() > 2 {
|
||||
@@ -80,27 +80,27 @@ pub fn number_parse_int(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let actual_radix = if hex { 16u32 } else { radix as u32 };
|
||||
|
||||
if let Ok(n) = i32::from_str_radix(&rest, actual_radix) {
|
||||
return Ok(JsValue::int(n));
|
||||
return NativeResult::Ok(JsValue::int(n));
|
||||
}
|
||||
|
||||
Ok(JsValue::float(f64::NAN))
|
||||
NativeResult::Ok(JsValue::float(f64::NAN))
|
||||
}
|
||||
|
||||
pub fn number_parse_float(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
let sf = vm.kernel().string_forge().as_ref();
|
||||
let s = coercion::to_string(sf, vm.reg(args[1]));
|
||||
let s = s.trim();
|
||||
|
||||
if s.is_empty() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
|
||||
match fast_float::parse::<f64, _>(&s) {
|
||||
Ok(v) => Ok(JsValue::float(v)),
|
||||
Err(_) => Ok(JsValue::float(f64::NAN)),
|
||||
Ok(v) => NativeResult::Ok(JsValue::float(v)),
|
||||
Err(_) => NativeResult::Ok(JsValue::float(f64::NAN)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,22 +115,22 @@ pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
if radix == 10 {
|
||||
if n.is_nan() {
|
||||
return Ok(vm.intern("NaN"));
|
||||
return NativeResult::Ok(vm.intern("NaN"));
|
||||
}
|
||||
if n.is_infinite() {
|
||||
if n.is_sign_positive() {
|
||||
return Ok(vm.intern("Infinity"));
|
||||
return NativeResult::Ok(vm.intern("Infinity"));
|
||||
}
|
||||
return Ok(vm.intern("-Infinity"));
|
||||
return NativeResult::Ok(vm.intern("-Infinity"));
|
||||
}
|
||||
if n.fract() == 0.0 && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
|
||||
return Ok(vm.intern(&(n as i64).to_string()));
|
||||
return NativeResult::Ok(vm.intern(&(n as i64).to_string()));
|
||||
}
|
||||
let mut buf = ryu::Buffer::new();
|
||||
Ok(vm.intern(buf.format(n)))
|
||||
NativeResult::Ok(vm.intern(buf.format(n)))
|
||||
} else {
|
||||
if n.is_nan() {
|
||||
return Ok(vm.intern("NaN"));
|
||||
return NativeResult::Ok(vm.intern("NaN"));
|
||||
}
|
||||
let nn = n as i64;
|
||||
let mut result = String::new();
|
||||
@@ -151,7 +151,7 @@ pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if nn < 0 {
|
||||
result.insert(0, '-');
|
||||
}
|
||||
Ok(vm.intern(&result))
|
||||
NativeResult::Ok(vm.intern(&result))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,20 +165,20 @@ pub fn number_to_fixed(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
.min(20);
|
||||
|
||||
let formatted = format!("{:.digits$}", n);
|
||||
Ok(vm.intern(&formatted))
|
||||
NativeResult::Ok(vm.intern(&formatted))
|
||||
}
|
||||
|
||||
pub fn number_is_integer(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] });
|
||||
let n = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
Ok(JsValue::bool(n.trunc() == n && n.is_finite()))
|
||||
NativeResult::Ok(JsValue::bool(n.trunc() == n && n.is_finite()))
|
||||
}
|
||||
|
||||
pub fn number_is_safe_integer(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] });
|
||||
let n = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||
let safe = n.trunc() == n && n.is_finite() && n >= -9007199254740991i64 as f64 && n <= 9007199254740991i64 as f64;
|
||||
Ok(JsValue::bool(safe))
|
||||
NativeResult::Ok(JsValue::bool(safe))
|
||||
}
|
||||
|
||||
pub fn number_to_exponential(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -190,13 +190,13 @@ pub fn number_to_exponential(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
.min(100);
|
||||
if n.is_nan() {
|
||||
return Ok(vm.intern("NaN"));
|
||||
return NativeResult::Ok(vm.intern("NaN"));
|
||||
}
|
||||
if n.is_infinite() {
|
||||
return Ok(vm.intern(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
|
||||
return NativeResult::Ok(vm.intern(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
|
||||
}
|
||||
let formatted = format!("{:.digits$e}", n);
|
||||
Ok(vm.intern(&formatted))
|
||||
NativeResult::Ok(vm.intern(&formatted))
|
||||
}
|
||||
|
||||
pub fn number_to_precision(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -208,19 +208,19 @@ pub fn number_to_precision(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
.min(21);
|
||||
if n.is_nan() {
|
||||
return Ok(vm.intern("NaN"));
|
||||
return NativeResult::Ok(vm.intern("NaN"));
|
||||
}
|
||||
if n.is_infinite() {
|
||||
return Ok(vm.intern(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
|
||||
return NativeResult::Ok(vm.intern(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
|
||||
}
|
||||
let formatted = format!("{:.precision$}", n);
|
||||
Ok(vm.intern(&formatted))
|
||||
NativeResult::Ok(vm.intern(&formatted))
|
||||
}
|
||||
|
||||
pub fn number_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(args[0]);
|
||||
if this_val.is_int() || this_val.is_double() {
|
||||
return Ok(this_val);
|
||||
return NativeResult::Ok(this_val);
|
||||
}
|
||||
Ok(JsValue::float(f64::NAN))
|
||||
NativeResult::Ok(JsValue::float(f64::NAN))
|
||||
}
|
||||
|
||||
@@ -39,20 +39,20 @@ pub(crate) fn walk_own_keys(vm: &Vm, obj: &JsObject) -> Vec<(u32, u32)> {
|
||||
|
||||
pub fn object_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
let obj = vm.epoch().alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
Ok(JsValue::from_js_object(obj))
|
||||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||||
}
|
||||
|
||||
pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let obj_ptr = val.as_js_object_ptr();
|
||||
if obj_ptr.is_null() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
|
||||
let key_names: Vec<String>;
|
||||
@@ -82,31 +82,33 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
unsafe {
|
||||
(*arr).set_prop_count(n);
|
||||
}
|
||||
Ok(JsValue::from_js_object(arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(arr))
|
||||
}
|
||||
|
||||
pub fn object_create(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let proto_val = vm.reg(args[1]);
|
||||
if !proto_val.is_null() && !proto_val.is_object() {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let obj = vm.epoch().alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val));
|
||||
Ok(JsValue::from_js_object(obj))
|
||||
NativeResult::Ok(JsValue::from_js_object(obj))
|
||||
}
|
||||
|
||||
pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "Object.assign requires a target"));
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(vm, "Object.assign requires a target"));
|
||||
}
|
||||
let target_val = vm.reg(args[1]);
|
||||
let target_val =
|
||||
coercion::to_object(target_val, vm).map_err(|msg| crate::builtins::error::create_type_error(vm, msg))?;
|
||||
let target_val = match coercion::to_object(target_val, vm) {
|
||||
Ok(val) => val,
|
||||
Err(msg) => return NativeResult::Err(crate::builtins::error::create_type_error(vm, msg)),
|
||||
};
|
||||
let target_ptr = target_val.as_js_object_ptr();
|
||||
if target_ptr.is_null() {
|
||||
return Ok(target_val);
|
||||
return NativeResult::Ok(target_val);
|
||||
}
|
||||
{
|
||||
let target = unsafe { &mut *target_ptr };
|
||||
@@ -127,34 +129,34 @@ pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(target_val)
|
||||
NativeResult::Ok(target_val)
|
||||
}
|
||||
|
||||
pub fn object_is(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let lhs = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let rhs = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
Ok(JsValue::bool(coercion::same_value(lhs, rhs)))
|
||||
NativeResult::Ok(JsValue::bool(coercion::same_value(lhs, rhs)))
|
||||
}
|
||||
|
||||
pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 4 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let obj_val = vm.reg(args[1]);
|
||||
if !obj_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let obj_ptr = obj_val.as_js_object_ptr();
|
||||
if obj_ptr.is_null() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let desc_val = vm.reg(args[3]);
|
||||
if !desc_val.is_object() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let desc_ptr = desc_val.as_js_object_ptr();
|
||||
if desc_ptr.is_null() {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let prop_name_str = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[2]));
|
||||
let si = vm.kernel().string_forge().intern(&prop_name_str).0;
|
||||
@@ -181,7 +183,7 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let has_data = value_field.is_some() || writable_field.is_some();
|
||||
let has_accessor = get_field.is_some() || set_field.is_some();
|
||||
if has_data && has_accessor {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Invalid property descriptor: cannot mix data and accessor fields",
|
||||
));
|
||||
@@ -192,35 +194,43 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let get = get_field.unwrap_or(JsValue::undefined());
|
||||
let set = set_field.unwrap_or(JsValue::undefined());
|
||||
if (!get.is_undefined() && !is_callable(get)) || (!set.is_undefined() && !is_callable(set)) {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"accessor descriptor get/set must be callable or undefined",
|
||||
));
|
||||
}
|
||||
vm.define_accessor_property(obj, si, get, set, PropAttributes::new(false, enumerable, configurable))
|
||||
.map_err(|_| crate::builtins::error::create_type_error(vm, "Cannot define property"))?;
|
||||
if vm
|
||||
.define_accessor_property(obj, si, get, set, PropAttributes::new(false, enumerable, configurable))
|
||||
.is_err()
|
||||
{
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(vm, "Cannot define property"));
|
||||
}
|
||||
} else {
|
||||
let value = value_field.unwrap_or(JsValue::undefined());
|
||||
let writable = writable_field
|
||||
.map(|v| coercion::to_boolean(v, vm.kernel().string_forge().as_ref()))
|
||||
.unwrap_or(false);
|
||||
vm.define_data_property(obj, si, value, PropAttributes::new(writable, enumerable, configurable))
|
||||
.map_err(|_| crate::builtins::error::create_type_error(vm, "Cannot define property"))?;
|
||||
if vm
|
||||
.define_data_property(obj, si, value, PropAttributes::new(writable, enumerable, configurable))
|
||||
.is_err()
|
||||
{
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(vm, "Cannot define property"));
|
||||
}
|
||||
}
|
||||
Ok(obj_val)
|
||||
NativeResult::Ok(obj_val)
|
||||
}
|
||||
|
||||
pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 3 {
|
||||
return Err(JsValue::undefined());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let obj_val = vm.reg(args[1]);
|
||||
if !obj_val.is_object() {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let obj_ptr = obj_val.as_js_object_ptr();
|
||||
if obj_ptr.is_null() {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let prop_name_str = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[2]));
|
||||
let si = vm.kernel().string_forge().intern(&prop_name_str).0;
|
||||
@@ -243,7 +253,7 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes
|
||||
};
|
||||
|
||||
if !found {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
|
||||
let sf_ptr = vm.kernel().string_forge().as_ref() as *const StringForge;
|
||||
@@ -269,7 +279,7 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes
|
||||
push_desc_prop(d, sh, sf.intern("configurable").0, JsValue::bool(meta.attributes.configurable()));
|
||||
}
|
||||
|
||||
Ok(JsValue::from_js_object(desc))
|
||||
NativeResult::Ok(JsValue::from_js_object(desc))
|
||||
}
|
||||
|
||||
fn own_field(vm: &Vm, obj: &JsObject, prop_si: u32) -> Option<JsValue> {
|
||||
@@ -319,68 +329,77 @@ fn require_obj_arg(vm: &mut Vm, args: &[u8], fn_name: &str) -> Result<*mut JsObj
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
macro_rules! native_try {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(value) => value,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn object_freeze(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_object() || val.as_js_object_ptr().is_null() {
|
||||
return Ok(val);
|
||||
return NativeResult::Ok(val);
|
||||
}
|
||||
Ok(val)
|
||||
NativeResult::Ok(val)
|
||||
}
|
||||
|
||||
pub fn object_seal(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_object() || val.as_js_object_ptr().is_null() {
|
||||
return Ok(val);
|
||||
return NativeResult::Ok(val);
|
||||
}
|
||||
Ok(val)
|
||||
NativeResult::Ok(val)
|
||||
}
|
||||
|
||||
pub fn object_prevent_extensions(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_object() || val.as_js_object_ptr().is_null() {
|
||||
return Ok(val);
|
||||
return NativeResult::Ok(val);
|
||||
}
|
||||
Ok(val)
|
||||
NativeResult::Ok(val)
|
||||
}
|
||||
|
||||
pub fn object_is_frozen(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_object() || val.as_js_object_ptr().is_null() {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
Ok(JsValue::bool(false))
|
||||
NativeResult::Ok(JsValue::bool(false))
|
||||
}
|
||||
|
||||
pub fn object_is_sealed(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_object() || val.as_js_object_ptr().is_null() {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
Ok(JsValue::bool(false))
|
||||
NativeResult::Ok(JsValue::bool(false))
|
||||
}
|
||||
|
||||
pub fn object_is_extensible(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
require_obj_arg(vm, args, "isExtensible")?;
|
||||
Ok(JsValue::bool(true))
|
||||
native_try!(require_obj_arg(vm, args, "isExtensible"));
|
||||
NativeResult::Ok(JsValue::bool(true))
|
||||
}
|
||||
|
||||
pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj_ptr = require_obj_arg(vm, args, "getOwnPropertyNames")?;
|
||||
let obj_ptr = native_try!(require_obj_arg(vm, args, "getOwnPropertyNames"));
|
||||
|
||||
let key_names: Vec<String> = {
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
@@ -407,74 +426,74 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
unsafe {
|
||||
(*arr).set_prop_count(n);
|
||||
}
|
||||
Ok(JsValue::from_js_object(arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(arr))
|
||||
}
|
||||
|
||||
pub fn object_define_properties(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 3 {
|
||||
return Ok(vm.reg(args[1]));
|
||||
return NativeResult::Ok(vm.reg(args[1]));
|
||||
}
|
||||
let _obj_ptr = require_obj_arg(vm, args, "defineProperties")?;
|
||||
let _obj_ptr = native_try!(require_obj_arg(vm, args, "defineProperties"));
|
||||
let desc_val = vm.reg(args[2]);
|
||||
if !desc_val.is_object() {
|
||||
return Ok(vm.reg(args[1]));
|
||||
return NativeResult::Ok(vm.reg(args[1]));
|
||||
}
|
||||
Ok(vm.reg(args[1]))
|
||||
NativeResult::Ok(vm.reg(args[1]))
|
||||
}
|
||||
|
||||
pub fn object_from_entries(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::null());
|
||||
return NativeResult::Ok(JsValue::null());
|
||||
}
|
||||
let entries = vm.reg(args[1]);
|
||||
if !entries.is_object() {
|
||||
return Ok(JsValue::null());
|
||||
return NativeResult::Ok(JsValue::null());
|
||||
}
|
||||
let obj_ptr = vm.epoch().alloc(JsObject::new_empty(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(vm.kernel().builtin_world().object_proto.as_ptr() as *mut JsObject),
|
||||
));
|
||||
Ok(JsValue::from_js_object(obj_ptr))
|
||||
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
||||
}
|
||||
|
||||
pub fn object_get_prototype_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj_ptr = require_obj_arg(vm, args, "getPrototypeOf")?;
|
||||
Ok(unsafe { (*obj_ptr).proto() })
|
||||
let obj_ptr = native_try!(require_obj_arg(vm, args, "getPrototypeOf"));
|
||||
NativeResult::Ok(unsafe { (*obj_ptr).proto() })
|
||||
}
|
||||
|
||||
pub fn object_has_own(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 3 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let obj_ptr = require_obj_arg(vm, args, "hasOwn")?;
|
||||
let obj_ptr = native_try!(require_obj_arg(vm, args, "hasOwn"));
|
||||
let key_si = vm.property_key_si(vm.reg(args[2]));
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some()))
|
||||
NativeResult::Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some()))
|
||||
}
|
||||
|
||||
pub fn object_proto_has_own_property(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let this_val = vm.reg(args[0]);
|
||||
if !this_val.is_object() || this_val.as_js_object_ptr().is_null() {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Object.prototype.hasOwnProperty called on non-object",
|
||||
));
|
||||
}
|
||||
let key_si = vm.property_key_si(vm.reg(args[1]));
|
||||
let obj = unsafe { &*this_val.as_js_object_ptr() };
|
||||
Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some()))
|
||||
NativeResult::Ok(JsValue::bool(vm.get_own_property_slot(obj, key_si).is_some()))
|
||||
}
|
||||
|
||||
pub fn object_proto_property_is_enumerable(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let this_val = vm.reg(args[0]);
|
||||
if !this_val.is_object() || this_val.as_js_object_ptr().is_null() {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
return NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Object.prototype.propertyIsEnumerable called on non-object",
|
||||
));
|
||||
@@ -482,17 +501,17 @@ pub fn object_proto_property_is_enumerable(vm: &mut Vm, args: &[u8]) -> NativeRe
|
||||
let key_si = vm.property_key_si(vm.reg(args[1]));
|
||||
let obj = unsafe { &*this_val.as_js_object_ptr() };
|
||||
let Some(pos) = vm.get_own_property_slot(obj, key_si) else {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
};
|
||||
let enumerable = obj
|
||||
.prop_meta_at(pos)
|
||||
.map(|meta| meta.attributes.enumerable())
|
||||
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable());
|
||||
Ok(JsValue::bool(enumerable))
|
||||
NativeResult::Ok(JsValue::bool(enumerable))
|
||||
}
|
||||
|
||||
pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let obj_ptr = require_obj_arg(vm, args, "entries")?;
|
||||
let obj_ptr = native_try!(require_obj_arg(vm, args, "entries"));
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
let n = keys.len();
|
||||
@@ -523,11 +542,11 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
unsafe {
|
||||
(*arr).set_prop_count(n);
|
||||
}
|
||||
Ok(JsValue::from_js_object(arr))
|
||||
NativeResult::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_ptr = native_try!(require_obj_arg(vm, args, "values"));
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
let keys = walk_own_keys(vm, obj);
|
||||
let n = keys.len();
|
||||
@@ -547,5 +566,5 @@ pub fn object_values(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
unsafe {
|
||||
(*arr).set_prop_count(n);
|
||||
}
|
||||
Ok(JsValue::from_js_object(arr))
|
||||
NativeResult::Ok(JsValue::from_js_object(arr))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::object::{JsObject, NativeFnPtr};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::coercion;
|
||||
@@ -91,11 +91,15 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
match compiled {
|
||||
Ok(re) => {
|
||||
let re_ptr = Box::into_raw(Box::new(re));
|
||||
obj.set_native_fn(Some(re_ptr as *const ()));
|
||||
// SAFETY: re_ptr is a Box<regex::Regex> pointer stored by the constructor via
|
||||
// NativeFnPtr::from_raw(re_ptr as *const ()). RegExp objects repurpose the native_fn
|
||||
// field to hold the compiled Regex — not a NativeFn pointer. Valid for the object's
|
||||
// lifetime because the Box is never freed (intentional leak tied to epoch).
|
||||
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(re_ptr as *const ()) }));
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("SyntaxError: Invalid regular expression: {}", e);
|
||||
return Err(JsValue::string(vm.kernel().string_forge().intern(&msg).0, 0));
|
||||
return NativeResult::Err(JsValue::string(vm.kernel().string_forge().intern(&msg).0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,21 +114,28 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
set_prop(&mut obj, "unicode", JsValue::bool(false), vm);
|
||||
|
||||
let obj_ptr = vm.epoch().alloc(obj);
|
||||
Ok(JsValue::from_js_object(obj_ptr))
|
||||
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
||||
}
|
||||
|
||||
pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re_ptr = get_regexp_ptr(vm, args)?;
|
||||
let re_ptr = match get_regexp_ptr(vm, args) {
|
||||
Ok(ptr) => ptr,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
};
|
||||
let re = unsafe { &*re_ptr };
|
||||
|
||||
let fn_ptr = match re.native_fn() {
|
||||
None => {
|
||||
return Err(JsValue::string(vm.kernel().string_forge().intern("TypeError: invalid RegExp").0, 0));
|
||||
return NativeResult::Err(JsValue::string(
|
||||
vm.kernel().string_forge().intern("TypeError: invalid RegExp").0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
let haystack = coercion::to_string(
|
||||
vm.kernel().string_forge().as_ref(),
|
||||
vm.reg(if args.len() > 1 { args[1] } else { args[0] }),
|
||||
@@ -134,26 +145,33 @@ pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
if is_global {
|
||||
let result = regex.find_at(&haystack, last_index).is_some();
|
||||
Ok(JsValue::bool(result))
|
||||
NativeResult::Ok(JsValue::bool(result))
|
||||
} else {
|
||||
Ok(JsValue::bool(regex.is_match(&haystack)))
|
||||
NativeResult::Ok(JsValue::bool(regex.is_match(&haystack)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re_ptr = get_regexp_ptr(vm, args)?;
|
||||
let re_ptr = match get_regexp_ptr(vm, args) {
|
||||
Ok(ptr) => ptr,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
};
|
||||
|
||||
let fn_ptr = {
|
||||
let re = unsafe { &*re_ptr };
|
||||
match re.native_fn() {
|
||||
None => {
|
||||
return Err(JsValue::string(vm.kernel().string_forge().intern("TypeError: invalid RegExp").0, 0));
|
||||
return NativeResult::Err(JsValue::string(
|
||||
vm.kernel().string_forge().intern("TypeError: invalid RegExp").0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
Some(p) => p,
|
||||
}
|
||||
};
|
||||
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
let haystack = coercion::to_string(
|
||||
vm.kernel().string_forge().as_ref(),
|
||||
vm.reg(if args.len() > 1 { args[1] } else { args[0] }),
|
||||
@@ -168,7 +186,7 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
|
||||
let match_result = if is_global {
|
||||
if last_index > haystack.len() {
|
||||
return Ok(JsValue::null());
|
||||
return NativeResult::Ok(JsValue::null());
|
||||
}
|
||||
regex.find_at(&haystack, last_index)
|
||||
} else {
|
||||
@@ -204,17 +222,20 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
|
||||
let obj_ptr = vm.epoch().alloc(match_obj);
|
||||
Ok(JsValue::from_js_object(obj_ptr))
|
||||
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
||||
} else {
|
||||
if is_global {
|
||||
set_prop_at(re_ptr, 0, JsValue::int(0));
|
||||
}
|
||||
Ok(JsValue::null())
|
||||
NativeResult::Ok(JsValue::null())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn regexp_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re_ptr = get_regexp_ptr(vm, args)?;
|
||||
let re_ptr = match get_regexp_ptr(vm, args) {
|
||||
Ok(ptr) => ptr,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
};
|
||||
let re = unsafe { &*re_ptr };
|
||||
let source = {
|
||||
let val = get_prop(re, 1);
|
||||
@@ -226,5 +247,5 @@ pub fn regexp_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let result = format!("/{}/{}", source, flags);
|
||||
let si = vm.kernel().string_forge().intern(&result).0;
|
||||
Ok(JsValue::string(si, 0))
|
||||
NativeResult::Ok(JsValue::string(si, 0))
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@ use crate::vm::Vm;
|
||||
|
||||
const SET_PROP_INDEX: u8 = 0;
|
||||
|
||||
macro_rules! native_try {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(value) => value,
|
||||
Err(err) => return NativeResult::Err(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SetKey(pub JsValue);
|
||||
|
||||
@@ -25,6 +34,7 @@ impl PartialEq for SetKey {
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
// SAFETY: JsValue is an 8-byte NaN-boxed Copy value; raw bits define non-double identity here.
|
||||
unsafe { std::mem::transmute::<JsValue, u64>(self.0) == std::mem::transmute::<JsValue, u64>(other.0) }
|
||||
}
|
||||
}
|
||||
@@ -43,11 +53,21 @@ impl Hash for SetKey {
|
||||
d.to_bits().hash(state);
|
||||
}
|
||||
} else {
|
||||
// SAFETY: JsValue is an 8-byte NaN-boxed Copy value; hashing raw bits matches equality above.
|
||||
unsafe { std::mem::transmute::<JsValue, u64>(self.0).hash(state) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the `IndexSet` pointer stored at `SET_PROP_INDEX` of a Set object.
|
||||
///
|
||||
/// # Safety contract maintained by callers
|
||||
///
|
||||
/// The pointer is valid as long as the Set `JsObject` itself is alive. The `JsObject`
|
||||
/// lives in the current `Epoch` arena and `Epoch::reset()` is never called while a
|
||||
/// native builtin is executing. The `Box<IndexSet>` is allocated in `new_set_inner()`
|
||||
/// and is never freed during the Set's lifetime. Only one live `*mut` alias exists per
|
||||
/// Set object at a time because native calls are single-threaded.
|
||||
fn get_set_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut indexmap::IndexSet<SetKey>, JsValue> {
|
||||
if !this_val.is_object() {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "called on non-Set object"));
|
||||
@@ -56,11 +76,24 @@ fn get_set_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut indexmap::IndexS
|
||||
if set_ptr.is_null() {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "Set internal state invalid"));
|
||||
}
|
||||
// SAFETY: set_ptr is a non-null, aligned pointer to a JsObject bump-allocated in the
|
||||
// current Epoch. Remains valid for the duration of this call.
|
||||
let set_obj = unsafe { &*set_ptr };
|
||||
if !set_obj.is_set() {
|
||||
return Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"Set.prototype.add called on incompatible receiver",
|
||||
));
|
||||
}
|
||||
let inner_val = set_obj.get_prop_at(SET_PROP_INDEX);
|
||||
if !inner_val.is_object() {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "Set internal state invalid"));
|
||||
}
|
||||
// SAFETY: inner_val holds the raw pointer written by `alloc_set` as
|
||||
// `JsValue::object(Box::into_raw(Box::new(IndexSet::new())) as *mut u8)`.
|
||||
// The pointer is a valid, heap-allocated `Box<IndexSet<SetKey>>` and is never freed
|
||||
// during the Set object's lifetime (see module safety contract above).
|
||||
// Alignment: IndexSet requires at most 8-byte alignment; the global allocator satisfies this.
|
||||
let inner_ptr = inner_val.as_js_object_ptr() as *mut indexmap::IndexSet<SetKey>;
|
||||
if inner_ptr.is_null() {
|
||||
return Err(crate::builtins::error::create_type_error(vm, "Set internal state invalid"));
|
||||
@@ -68,6 +101,9 @@ fn get_set_inner(vm: &mut Vm, this_val: JsValue) -> Result<*mut indexmap::IndexS
|
||||
Ok(inner_ptr)
|
||||
}
|
||||
|
||||
/// Allocate a new `IndexSet` on the global heap.
|
||||
/// The caller is responsible for storing the pointer in the Set `JsObject` at `SET_PROP_INDEX`
|
||||
/// via `JsValue::object(ptr as *mut u8)` and ensuring it is never double-freed.
|
||||
fn new_set_inner() -> *mut indexmap::IndexSet<SetKey> {
|
||||
Box::into_raw(Box::new(indexmap::IndexSet::new()))
|
||||
}
|
||||
@@ -75,6 +111,9 @@ fn new_set_inner() -> *mut indexmap::IndexSet<SetKey> {
|
||||
fn alloc_set(vm: &mut Vm) -> *mut JsObject {
|
||||
let set_proto = vm.kernel().builtin_world().set_proto.as_ptr() as *mut JsObject;
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(set_proto));
|
||||
obj.set_set(true);
|
||||
// SAFETY: new_set_inner() returns a valid Box<IndexSet> pointer; stored as a JsValue
|
||||
// object tag so get_set_inner() can retrieve and cast it back (same provenance).
|
||||
let inner = new_set_inner();
|
||||
obj.set_prop_at(SET_PROP_INDEX, JsValue::object(inner as *mut u8));
|
||||
vm.epoch().alloc(obj)
|
||||
@@ -82,46 +121,46 @@ fn alloc_set(vm: &mut Vm) -> *mut JsObject {
|
||||
|
||||
pub fn set_constructor(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
let set_obj = alloc_set(vm);
|
||||
Ok(JsValue::from_js_object(set_obj))
|
||||
NativeResult::Ok(JsValue::from_js_object(set_obj))
|
||||
}
|
||||
|
||||
pub fn set_add(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_set_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_set_inner(vm, this_val));
|
||||
let val = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
unsafe {
|
||||
(*inner).insert(SetKey(val));
|
||||
}
|
||||
Ok(this_val)
|
||||
NativeResult::Ok(this_val)
|
||||
}
|
||||
|
||||
pub fn set_has(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_set_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_set_inner(vm, this_val));
|
||||
let val = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
let found = unsafe { (*inner).contains(&SetKey(val)) };
|
||||
Ok(JsValue::bool(found))
|
||||
NativeResult::Ok(JsValue::bool(found))
|
||||
}
|
||||
|
||||
pub fn set_delete(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_set_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_set_inner(vm, this_val));
|
||||
let val = vm.reg(if args.len() > 1 { args[1] } else { 0 });
|
||||
let removed = unsafe { (*inner).shift_remove(&SetKey(val)) };
|
||||
Ok(JsValue::bool(removed))
|
||||
NativeResult::Ok(JsValue::bool(removed))
|
||||
}
|
||||
|
||||
pub fn set_clear(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_set_inner(vm, this_val)?;
|
||||
let inner = native_try!(get_set_inner(vm, this_val));
|
||||
unsafe {
|
||||
(*inner).clear();
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn set_size(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
let inner = get_set_inner(vm, this_val)?;
|
||||
Ok(JsValue::float(unsafe { (*inner).len() } as f64))
|
||||
let inner = native_try!(get_set_inner(vm, this_val));
|
||||
NativeResult::Ok(JsValue::float(unsafe { (*inner).len() } as f64))
|
||||
}
|
||||
|
||||
@@ -31,11 +31,15 @@ fn boxed_primitive_value(this_val: JsValue) -> Option<JsValue> {
|
||||
pub fn string_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(args[0]);
|
||||
if this_val.is_string() {
|
||||
return Ok(this_val);
|
||||
return NativeResult::Ok(this_val);
|
||||
}
|
||||
match boxed_primitive_value(this_val) {
|
||||
Some(v) => NativeResult::Ok(v),
|
||||
None => NativeResult::Err(crate::builtins::error::create_type_error(
|
||||
vm,
|
||||
"String.prototype.valueOf called on non-String object",
|
||||
)),
|
||||
}
|
||||
boxed_primitive_value(this_val).ok_or_else(|| {
|
||||
crate::builtins::error::create_type_error(vm, "String.prototype.valueOf called on non-String object")
|
||||
})
|
||||
}
|
||||
|
||||
fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue {
|
||||
@@ -58,6 +62,27 @@ fn as_string(vm: &Vm, val: JsValue) -> String {
|
||||
coercion::to_string(vm.kernel().string_forge().as_ref(), val)
|
||||
}
|
||||
|
||||
fn char_len(s: &str) -> usize {
|
||||
s.chars().count()
|
||||
}
|
||||
|
||||
fn byte_index_at_char(s: &str, char_pos: usize) -> usize {
|
||||
if char_pos == 0 {
|
||||
return 0;
|
||||
}
|
||||
s.char_indices().nth(char_pos).map(|(idx, _)| idx).unwrap_or(s.len())
|
||||
}
|
||||
|
||||
fn char_slice(s: &str, start: usize, end: usize) -> &str {
|
||||
let start_byte = byte_index_at_char(s, start);
|
||||
let end_byte = byte_index_at_char(s, end);
|
||||
&s[start_byte..end_byte]
|
||||
}
|
||||
|
||||
fn take_chars(s: &str, count: usize) -> String {
|
||||
s.chars().take(count).collect()
|
||||
}
|
||||
|
||||
fn is_regexp_obj(val: JsValue, vm: &Vm) -> bool {
|
||||
if !val.is_object() {
|
||||
return false;
|
||||
@@ -81,52 +106,57 @@ fn is_regexp_obj(val: JsValue, vm: &Vm) -> bool {
|
||||
|
||||
pub fn string_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = char_len(&s);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::int(-1));
|
||||
return NativeResult::Ok(JsValue::int(-1));
|
||||
}
|
||||
let search = as_string(vm, vm.reg(args[1]));
|
||||
let pos = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize).min(s.len())
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize).min(n)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if search.is_empty() {
|
||||
return Ok(JsValue::int(pos as i32));
|
||||
return NativeResult::Ok(JsValue::int(pos as i32));
|
||||
}
|
||||
|
||||
let haystack = &s.as_bytes()[pos..];
|
||||
let start_byte = byte_index_at_char(&s, pos);
|
||||
let haystack = &s[start_byte..];
|
||||
if search.len() == 1 {
|
||||
if let Some(idx) = memchr(search.as_bytes()[0], haystack) {
|
||||
return Ok(JsValue::int((pos + idx) as i32));
|
||||
if let Some(idx) = memchr(search.as_bytes()[0], haystack.as_bytes()) {
|
||||
let matched_byte = start_byte + idx;
|
||||
return NativeResult::Ok(JsValue::int(char_len(&s[..matched_byte]) as i32));
|
||||
}
|
||||
} else if let Some(idx) = s[pos..].find(&search) {
|
||||
return Ok(JsValue::int((pos + idx) as i32));
|
||||
} else if let Some(idx) = haystack.find(&search) {
|
||||
let matched_byte = start_byte + idx;
|
||||
return NativeResult::Ok(JsValue::int(char_len(&s[..matched_byte]) as i32));
|
||||
}
|
||||
Ok(JsValue::int(-1))
|
||||
NativeResult::Ok(JsValue::int(-1))
|
||||
}
|
||||
|
||||
pub fn string_includes(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = char_len(&s);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let search = as_string(vm, vm.reg(args[1]));
|
||||
let pos = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize).min(s.len())
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize).min(n)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if search.is_empty() {
|
||||
return Ok(JsValue::bool(true));
|
||||
return NativeResult::Ok(JsValue::bool(true));
|
||||
}
|
||||
|
||||
let haystack = &s.as_bytes()[pos..];
|
||||
let haystack = &s[byte_index_at_char(&s, pos)..];
|
||||
if search.len() == 1 {
|
||||
Ok(JsValue::bool(memchr(search.as_bytes()[0], haystack).is_some()))
|
||||
NativeResult::Ok(JsValue::bool(memchr(search.as_bytes()[0], haystack.as_bytes()).is_some()))
|
||||
} else {
|
||||
Ok(JsValue::bool(s[pos..].contains(&search)))
|
||||
NativeResult::Ok(JsValue::bool(haystack.contains(&search)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,31 +164,31 @@ pub fn string_char_at(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
if s.is_empty() {
|
||||
return Ok(vm.intern(""));
|
||||
return NativeResult::Ok(vm.intern(""));
|
||||
}
|
||||
return Ok(vm.intern(&s[0..1]));
|
||||
return NativeResult::Ok(vm.intern(&take_chars(&s, 1)));
|
||||
}
|
||||
let idx = coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32;
|
||||
if idx < 0 || idx as usize >= s.len() {
|
||||
return Ok(vm.intern(""));
|
||||
if idx < 0 || idx as usize >= char_len(&s) {
|
||||
return NativeResult::Ok(vm.intern(""));
|
||||
}
|
||||
let ch = &s[(idx as usize)..(idx as usize + 1)];
|
||||
Ok(vm.intern(ch))
|
||||
let ch = s.chars().nth(idx as usize).map(|c| c.to_string()).unwrap_or_default();
|
||||
NativeResult::Ok(vm.intern(&ch))
|
||||
}
|
||||
|
||||
pub fn string_char_code_at(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
if s.is_empty() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
return Ok(JsValue::int(s.as_bytes()[0] as i32));
|
||||
return NativeResult::Ok(JsValue::int(s.chars().next().unwrap() as i32));
|
||||
}
|
||||
let idx = coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32;
|
||||
if idx < 0 || idx as usize >= s.len() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
if idx < 0 || idx as usize >= char_len(&s) {
|
||||
return NativeResult::Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
Ok(JsValue::int(s.as_bytes()[idx as usize] as i32))
|
||||
NativeResult::Ok(JsValue::int(s.chars().nth(idx as usize).unwrap() as i32))
|
||||
}
|
||||
|
||||
pub fn string_concat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -167,12 +197,12 @@ pub fn string_concat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
for &arg_reg in args.iter().skip(1) {
|
||||
result.push_str(&coercion::to_string(sf, vm.reg(arg_reg)));
|
||||
}
|
||||
Ok(vm.intern(&result))
|
||||
NativeResult::Ok(vm.intern(&result))
|
||||
}
|
||||
|
||||
pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = s.len() as i32;
|
||||
let n = char_len(&s) as i32;
|
||||
let start = if args.len() > 1 {
|
||||
let v = coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32;
|
||||
if v < 0 {
|
||||
@@ -195,13 +225,13 @@ pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let start = start as usize;
|
||||
let end = end as usize;
|
||||
let result = if start < end { &s[start..end] } else { "" };
|
||||
Ok(vm.intern(result))
|
||||
let result = if start < end { char_slice(&s, start, end) } else { "" };
|
||||
NativeResult::Ok(vm.intern(result))
|
||||
}
|
||||
|
||||
pub fn string_substring(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = s.len() as i32;
|
||||
let n = char_len(&s) as i32;
|
||||
let mut start = if args.len() > 1 {
|
||||
(coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32)
|
||||
.max(0)
|
||||
@@ -219,23 +249,23 @@ pub fn string_substring(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if start > end {
|
||||
std::mem::swap(&mut start, &mut end);
|
||||
}
|
||||
let result = &s[start as usize..end as usize];
|
||||
Ok(vm.intern(result))
|
||||
let result = char_slice(&s, start as usize, end as usize);
|
||||
NativeResult::Ok(vm.intern(result))
|
||||
}
|
||||
|
||||
pub fn string_to_upper_case(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
Ok(vm.intern(&s.to_uppercase()))
|
||||
NativeResult::Ok(vm.intern(&s.to_uppercase()))
|
||||
}
|
||||
|
||||
pub fn string_to_lower_case(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
Ok(vm.intern(&s.to_lowercase()))
|
||||
NativeResult::Ok(vm.intern(&s.to_lowercase()))
|
||||
}
|
||||
|
||||
pub fn string_trim(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
Ok(vm.intern(s.trim()))
|
||||
NativeResult::Ok(vm.intern(s.trim()))
|
||||
}
|
||||
|
||||
pub fn string_repeat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -245,80 +275,84 @@ pub fn string_repeat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
} else {
|
||||
1
|
||||
};
|
||||
Ok(vm.intern(&s.repeat(n)))
|
||||
NativeResult::Ok(vm.intern(&s.repeat(n)))
|
||||
}
|
||||
|
||||
pub fn string_pad_start(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let s_len = char_len(&s);
|
||||
let target = if args.len() > 1 {
|
||||
coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as usize
|
||||
} else {
|
||||
s.len()
|
||||
s_len
|
||||
};
|
||||
let pad = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() };
|
||||
if s.len() >= target || pad.is_empty() {
|
||||
return Ok(vm.intern(&s));
|
||||
if s_len >= target || pad.is_empty() {
|
||||
return NativeResult::Ok(vm.intern(&s));
|
||||
}
|
||||
let needed = target - s.len();
|
||||
let reps = needed.div_ceil(pad.len());
|
||||
let mut out = pad.repeat(reps);
|
||||
out.truncate(needed);
|
||||
let needed = target - s_len;
|
||||
let pad_len = char_len(&pad).max(1);
|
||||
let reps = needed.div_ceil(pad_len);
|
||||
let mut out = take_chars(&pad.repeat(reps), needed);
|
||||
out.push_str(&s);
|
||||
Ok(vm.intern(&out))
|
||||
NativeResult::Ok(vm.intern(&out))
|
||||
}
|
||||
|
||||
pub fn string_pad_end(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let s_len = char_len(&s);
|
||||
let target = if args.len() > 1 {
|
||||
coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as usize
|
||||
} else {
|
||||
s.len()
|
||||
s_len
|
||||
};
|
||||
let pad = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() };
|
||||
if s.len() >= target || pad.is_empty() {
|
||||
return Ok(vm.intern(&s));
|
||||
if s_len >= target || pad.is_empty() {
|
||||
return NativeResult::Ok(vm.intern(&s));
|
||||
}
|
||||
let needed = target - s.len();
|
||||
let reps = needed.div_ceil(pad.len());
|
||||
let needed = target - s_len;
|
||||
let pad_len = char_len(&pad).max(1);
|
||||
let reps = needed.div_ceil(pad_len);
|
||||
let mut out = s;
|
||||
let pad_repeated = pad.repeat(reps);
|
||||
out.push_str(&pad_repeated[..needed]);
|
||||
Ok(vm.intern(&out))
|
||||
out.push_str(&take_chars(&pad.repeat(reps), needed));
|
||||
NativeResult::Ok(vm.intern(&out))
|
||||
}
|
||||
|
||||
pub fn string_starts_with(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = char_len(&s);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let search = as_string(vm, vm.reg(args[1]));
|
||||
let pos = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize).min(s.len())
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize).min(n)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
Ok(JsValue::bool(s[pos..].starts_with(&search)))
|
||||
NativeResult::Ok(JsValue::bool(s[byte_index_at_char(&s, pos)..].starts_with(&search)))
|
||||
}
|
||||
|
||||
pub fn string_ends_with(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = char_len(&s);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let search = as_string(vm, vm.reg(args[1]));
|
||||
let end_pos = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize).min(s.len())
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize).min(n)
|
||||
} else {
|
||||
s.len()
|
||||
n
|
||||
};
|
||||
Ok(JsValue::bool(s[..end_pos].ends_with(&search)))
|
||||
NativeResult::Ok(JsValue::bool(s[..byte_index_at_char(&s, end_pos)].ends_with(&search)))
|
||||
}
|
||||
|
||||
pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
let parts = vec![s.clone()];
|
||||
return Ok(make_string_array(vm, &parts));
|
||||
return NativeResult::Ok(make_string_array(vm, &parts));
|
||||
}
|
||||
let sep_val = vm.reg(args[1]);
|
||||
let limit = if args.len() > 2 {
|
||||
@@ -334,13 +368,14 @@ pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
let parts = vec![s.clone()];
|
||||
return Ok(make_string_array(vm, &parts));
|
||||
return NativeResult::Ok(make_string_array(vm, &parts));
|
||||
}
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
let raw: Vec<&str> = regex.split(&s).collect();
|
||||
let parts: Vec<String> = raw.iter().map(|p| p.to_string()).take(limit).collect();
|
||||
return Ok(make_string_array(vm, &parts));
|
||||
return NativeResult::Ok(make_string_array(vm, &parts));
|
||||
}
|
||||
|
||||
let sep = as_string(vm, sep_val);
|
||||
@@ -349,13 +384,13 @@ pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
} else {
|
||||
s.split(&sep).map(|p| p.to_string()).take(limit).collect()
|
||||
};
|
||||
Ok(make_string_array(vm, &parts))
|
||||
NativeResult::Ok(make_string_array(vm, &parts))
|
||||
}
|
||||
|
||||
pub fn string_replace(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(vm.intern(&s));
|
||||
return NativeResult::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() };
|
||||
@@ -365,22 +400,23 @@ pub fn string_replace(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return Ok(vm.intern(&s)),
|
||||
None => return NativeResult::Ok(vm.intern(&s)),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
let result = regex.replace_all(&s, replacement.as_str()).to_string();
|
||||
return Ok(vm.intern(&result));
|
||||
return NativeResult::Ok(vm.intern(&result));
|
||||
}
|
||||
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
let result = s.replacen(&pattern, &replacement, 1);
|
||||
Ok(vm.intern(&result))
|
||||
NativeResult::Ok(vm.intern(&result))
|
||||
}
|
||||
|
||||
pub fn string_match_fn(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
|
||||
@@ -389,24 +425,25 @@ pub fn string_match_fn(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return Ok(JsValue::undefined()),
|
||||
None => return NativeResult::Ok(JsValue::undefined()),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
let regex = unsafe { &*(fn_ptr.as_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));
|
||||
return NativeResult::Ok(make_string_array(vm, &matches));
|
||||
}
|
||||
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
if let Some(pos) = s.find(&pattern) {
|
||||
return Ok(make_string_array(vm, &[s[pos..pos + pattern.len()].to_string()]));
|
||||
return NativeResult::Ok(make_string_array(vm, &[s[pos..pos + pattern.len()].to_string()]));
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn string_search(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::int(-1));
|
||||
return NativeResult::Ok(JsValue::int(-1));
|
||||
}
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
|
||||
@@ -415,30 +452,31 @@ pub fn string_search(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return Ok(JsValue::int(-1)),
|
||||
None => return NativeResult::Ok(JsValue::int(-1)),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
if let Some(m) = regex.find(&s) {
|
||||
return Ok(JsValue::int(m.start() as i32));
|
||||
return NativeResult::Ok(JsValue::int(m.start() as i32));
|
||||
}
|
||||
return Ok(JsValue::int(-1));
|
||||
return NativeResult::Ok(JsValue::int(-1));
|
||||
}
|
||||
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
if let Some(pos) = s.find(&pattern) {
|
||||
return Ok(JsValue::int(pos as i32));
|
||||
return NativeResult::Ok(JsValue::int(pos as i32));
|
||||
}
|
||||
Ok(JsValue::int(-1))
|
||||
NativeResult::Ok(JsValue::int(-1))
|
||||
}
|
||||
|
||||
pub fn string_trim_start(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
Ok(vm.intern(s.trim_start()))
|
||||
NativeResult::Ok(vm.intern(s.trim_start()))
|
||||
}
|
||||
|
||||
pub fn string_trim_end(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
Ok(vm.intern(s.trim_end()))
|
||||
NativeResult::Ok(vm.intern(s.trim_end()))
|
||||
}
|
||||
|
||||
pub fn string_code_point_at(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -450,17 +488,17 @@ pub fn string_code_point_at(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
};
|
||||
let chars: Vec<char> = s.chars().collect();
|
||||
if pos >= chars.len() {
|
||||
return Ok(JsValue::undefined());
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let c = chars[pos] as u32;
|
||||
if (0xD800..=0xDBFF).contains(&c) && pos + 1 < chars.len() {
|
||||
let next = chars[pos + 1] as u32;
|
||||
if (0xDC00..=0xDFFF).contains(&next) {
|
||||
let cp = 0x10000 + ((c - 0xD800) << 10) + (next - 0xDC00);
|
||||
return Ok(JsValue::int(cp as i32));
|
||||
return NativeResult::Ok(JsValue::int(cp as i32));
|
||||
}
|
||||
}
|
||||
Ok(JsValue::int(c as i32))
|
||||
NativeResult::Ok(JsValue::int(c as i32))
|
||||
}
|
||||
|
||||
pub fn string_normalize(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
@@ -473,13 +511,13 @@ pub fn string_normalize(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
"NFKD" => s.nfkd().collect(),
|
||||
_ => s.nfc().collect(),
|
||||
};
|
||||
Ok(vm.intern(&result))
|
||||
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());
|
||||
return NativeResult::Err(JsValue::undefined());
|
||||
}
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
if is_regexp_obj(pattern_val, vm) {
|
||||
@@ -487,19 +525,20 @@ pub fn string_match_all(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return Ok(JsValue::undefined()),
|
||||
None => return NativeResult::Ok(JsValue::undefined()),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
let regex = unsafe { &*(fn_ptr.as_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));
|
||||
return NativeResult::Ok(make_string_array(vm, &matches));
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
NativeResult::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));
|
||||
return NativeResult::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() };
|
||||
@@ -508,13 +547,14 @@ pub fn string_replace_all(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return Ok(vm.intern(&s)),
|
||||
None => return NativeResult::Ok(vm.intern(&s)),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
let result = regex.replace_all(&s, replacement.as_str()).to_string();
|
||||
return Ok(vm.intern(&result));
|
||||
return NativeResult::Ok(vm.intern(&result));
|
||||
}
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
let result = s.replace(&pattern, &replacement);
|
||||
Ok(vm.intern(&result))
|
||||
NativeResult::Ok(vm.intern(&result))
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn symbol_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if !proto_ptr.is_null() {
|
||||
let sp = vm.kernel().builtin_world().symbol_proto.as_ptr() as *mut JsObject;
|
||||
if std::ptr::eq(proto_ptr, sp) {
|
||||
return Err(JsValue::string(
|
||||
return NativeResult::Err(JsValue::string(
|
||||
vm.kernel().string_forge().intern("TypeError: Symbol is not a constructor").0,
|
||||
0,
|
||||
));
|
||||
@@ -36,13 +36,13 @@ pub fn symbol_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
vm.symbol_counter = vm.symbol_counter.wrapping_add(1);
|
||||
let idx = vm.symbol_descriptions.len() as u32;
|
||||
vm.symbol_descriptions.push(description);
|
||||
Ok(JsValue::symbol(idx))
|
||||
NativeResult::Ok(JsValue::symbol(idx))
|
||||
}
|
||||
|
||||
pub fn symbol_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(args[0]);
|
||||
if !this_val.is_symbol() {
|
||||
return Err(JsValue::string(
|
||||
return NativeResult::Err(JsValue::string(
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.intern("TypeError: Symbol.prototype.toString requires a Symbol")
|
||||
@@ -55,5 +55,5 @@ pub fn symbol_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let desc = vm.symbol_descriptions.get(idx as usize).cloned().unwrap_or_default();
|
||||
let result = format!("Symbol({})", desc);
|
||||
let si = vm.kernel().string_forge().intern(&result).0;
|
||||
Ok(JsValue::string(si, 0))
|
||||
NativeResult::Ok(JsValue::string(si, 0))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user