Skip to content

Commit

Permalink
add Array.prototype.splice
Browse files Browse the repository at this point in the history
  • Loading branch information
y21 committed Dec 24, 2024
1 parent 2a2c102 commit e137f39
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 17 deletions.
1 change: 1 addition & 0 deletions crates/dash_middle/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ pub mod sym {
sort,
unshift,
slice,
splice,
asyncIterator,
hasInstance,
iterator,
Expand Down
2 changes: 1 addition & 1 deletion crates/dash_optimizer/src/type_infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ impl<'s> TypeInferCtx<'s> {
ExprKind::Class(class) => self.visit_class_expression(class),
ExprKind::Array(expr) => self.visit_array_expression(expr),
ExprKind::Object(expr) => self.visit_object_expression(expr),
ExprKind::Chaining(OptionalChainingExpression { base, components: _ }) => self.visit(&**base),
ExprKind::Chaining(OptionalChainingExpression { base, components: _ }) => self.visit(base),
ExprKind::Compiled(..) => None,
ExprKind::Empty => None,
ExprKind::YieldStar(e) => {
Expand Down
109 changes: 98 additions & 11 deletions crates/dash_vm/src/js_std/array.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use ControlFlow::{Break, Continue};
use std::cmp;
use std::convert::Infallible;
use std::ops::{ControlFlow, Range};
use ControlFlow::{Break, Continue};

use crate::frame::This;
use crate::localscope::LocalScope;
use crate::throw;
use crate::value::array::{Array, ArrayIterator};
use crate::value::array::{Array, ArrayIterator, require_valid_array_length};
use crate::value::function::native::CallContext;
use crate::value::object::{Object as _, PropertyValue};
use crate::value::object::{Object as _, PropertyKey, PropertyValue};
use crate::value::ops::conversions::ValueConversion;
use crate::value::ops::equality::strict_eq;
use crate::value::root_ext::RootErrExt;
use crate::value::string::JsString;
use crate::value::{array, Root, Unpack, Value, ValueContext};
use crate::value::{Root, Unpack, Value, ValueContext, array};
use dash_middle::interner::sym;

pub fn constructor(cx: CallContext) -> Result<Value, Value> {
Expand All @@ -22,6 +22,17 @@ pub fn constructor(cx: CallContext) -> Result<Value, Value> {
Ok(cx.scope.register(array).into())
}

fn wrapping_index_val(start: Value, cx: &mut CallContext, len: usize) -> Result<usize, Value> {
let start = start.to_integer_or_infinity(cx.scope)?;
Ok(if start == f64::NEG_INFINITY {
0
} else if start < 0.0 {
cmp::max(len as isize + start as isize, 0) as usize
} else {
cmp::min(start as isize, len as isize) as usize
})
}

fn join_inner(sc: &mut LocalScope, array: Value, separator: JsString) -> Result<Value, Value> {
let length = array.length_of_array_like(sc)?;

Expand Down Expand Up @@ -168,17 +179,12 @@ pub fn some(cx: CallContext) -> Result<Value, Value> {
Ok(Value::boolean(any_true))
}

pub fn fill(cx: CallContext) -> Result<Value, Value> {
pub fn fill(mut cx: CallContext) -> Result<Value, Value> {
let this = Value::object(cx.this.to_object(cx.scope)?);
let len = this.length_of_array_like(cx.scope)?;
let value = cx.args.first().unwrap_or_undefined();

let relative_start = cx.args.get(1).unwrap_or_undefined().to_integer_or_infinity(cx.scope)?;
let k = if relative_start < 0.0 {
cmp::max(len as isize + relative_start as isize, 0) as usize
} else {
cmp::min(relative_start as isize, len as isize) as usize
};
let k = wrapping_index_val(cx.args.get(1).unwrap_or_undefined(), &mut cx, len)?;

let relative_end = cx
.args
Expand Down Expand Up @@ -529,6 +535,11 @@ fn shift_array(
shift_by: isize,
range: Range<usize>,
) -> Result<(), Value> {
if shift_by == 0 {
// No shifting needs to happen (and can't, this short circuit is required by splice)
return Ok(());
}

let range = range.start as isize..range.end as isize;

let new_len = (range.end + shift_by) as usize;
Expand Down Expand Up @@ -752,3 +763,79 @@ pub fn sort(cx: CallContext) -> Result<Value, Value> {

Ok(cx.this)
}

pub fn splice(mut cx: CallContext) -> Result<Value, Value> {
let this = Value::object(cx.this.to_object(cx.scope)?);
let len = this.length_of_array_like(cx.scope)?;

let start = wrapping_index_val(cx.args.first().unwrap_or_undefined(), &mut cx, len)?;
let delete_count = match *cx.args {
// 8. If start is not present, then
[] => 0,
// 9. Else if deleteCount is not present, then
[_] => len - start,
// 10. Else, ...
[_, v, ..] => isize::clamp(v.to_integer_or_infinity(cx.scope)? as isize, 0, (len - start) as isize) as usize,
};
let item_count = cx.args.len().saturating_sub(2);

// TODO: often the returned array is unused; it may be possible to pass a "return value is used" flag to CallContexts

require_valid_array_length(cx.scope, delete_count)?;
let mut values = Vec::with_capacity(delete_count);
for k in 0..delete_count {
let from = cx.scope.intern_usize(start + k);

if let Some(delete_value) = this
.get_property_descriptor(cx.scope, PropertyKey::String(from.into()))
.root_err(cx.scope)?
{
values.push(delete_value);
}
}

if item_count <= delete_count {
// Since we delete more than we insert, overwrite elements at the delete index
for (i, value) in cx.args.iter().skip(2).enumerate() {
let i = cx.scope.intern_usize(i + start);
this.set_property(
cx.scope,
PropertyKey::String(i.into()),
PropertyValue::static_default(*value),
)?;
}

// Now shift the rest to the left and update the length
shift_array(
cx.scope,
&this,
len,
-(delete_count as isize - item_count as isize),
start + delete_count..len,
)?;
} else {
let items_to_insert = item_count - delete_count;
require_valid_array_length(cx.scope, len + items_to_insert)?;

// We can overwrite `delete_count` number of items without shifting to the right
// IOW, we only need to shift by `item_count - delete_count` to the right
shift_array(
cx.scope,
&this,
len,
items_to_insert as isize,
start + delete_count..len,
)?;

for (i, value) in cx.args.iter().skip(2).enumerate() {
let i = cx.scope.intern_usize(i + start);
this.set_property(
cx.scope,
PropertyKey::String(i.into()),
PropertyValue::static_default(*value),
)?;
}
}

Ok(cx.scope.register(Array::from_vec(cx.scope, values)).into())
}
1 change: 1 addition & 0 deletions crates/dash_vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ impl Vm {
(sym::reverse, scope.statics.array_reverse),
(sym::shift, scope.statics.array_shift),
(sym::sort, scope.statics.array_sort),
(sym::splice, scope.statics.array_splice),
(sym::unshift, scope.statics.array_unshift),
(sym::slice, scope.statics.array_slice),
(sym::lastIndexOf, scope.statics.array_last_index_of),
Expand Down
4 changes: 3 additions & 1 deletion crates/dash_vm/src/statics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use dash_proc_macro::Trace;

use crate::gc::{Allocator, ObjectId};
use crate::js_std;
use crate::value::PureBuiltin;
use crate::value::error::{AggregateError, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError};
use crate::value::function::{Function, FunctionKind};
use crate::value::map::Map;
use crate::value::regex::RegExp;
use crate::value::set::Set;
use crate::value::PureBuiltin;
use dash_middle::interner::{self, sym};

use super::value::array::{Array, ArrayIterator};
Expand Down Expand Up @@ -181,6 +181,7 @@ pub struct Statics {
pub array_reverse: ObjectId,
pub array_shift: ObjectId,
pub array_sort: ObjectId,
pub array_splice: ObjectId,
pub array_unshift: ObjectId,
pub array_slice: ObjectId,
pub array_last_index_of: ObjectId,
Expand Down Expand Up @@ -448,6 +449,7 @@ impl Statics {
array_sort: function(gc, sym::sort, js_std::array::sort),
array_unshift: function(gc, sym::unshift, js_std::array::unshift),
array_slice: function(gc, sym::slice, js_std::array::slice),
array_splice: function(gc, sym::splice, js_std::array::splice),
array_last_index_of: function(gc, sym::lastIndexOf, js_std::array::last_index_of),
array_from: function(gc, sym::from, js_std::array::from),
array_is_array: function(gc, sym::isArray, js_std::array::is_array),
Expand Down
13 changes: 11 additions & 2 deletions crates/dash_vm/src/value/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ use dash_proc_macro::Trace;
use table::ArrayTable;

use crate::frame::This;
use crate::gc::trace::Trace;
use crate::gc::ObjectId;
use crate::gc::trace::Trace;
use crate::localscope::LocalScope;
use crate::value::object::PropertyDataDescriptor;
use crate::{delegate, extract, throw, Vm};
use crate::{Vm, delegate, extract, throw};
use dash_middle::interner::sym;

use super::object::{NamedObject, Object, PropertyKey, PropertyValue, PropertyValueKind};
Expand All @@ -23,10 +23,19 @@ use super::{Root, Unpack, Unrooted, Value};
pub mod table;

pub const MAX_LENGTH: u32 = 4294967295;
pub const MAX_INDEX: u32 = MAX_LENGTH - 1;

pub fn require_valid_array_length(scope: &mut LocalScope<'_>, len: usize) -> Result<(), Value> {
if len > MAX_LENGTH as usize {
throw!(scope, RangeError, "Invalid array length");
}
Ok(())
}

#[derive(Debug)]
pub enum ArrayInner {
// TODO: store Value, also support holes
// TODO: move away from `Vec`? we don't need a `usize` for the length as the max size fits in a u32
Dense(Vec<PropertyValue>),
Table(ArrayTable),
}
Expand Down
4 changes: 2 additions & 2 deletions testrunner/src/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::panic;
use std::sync::atomic::AtomicU32;
use std::sync::{atomic, Mutex};
use std::sync::{Mutex, atomic};

use clap::ArgMatches;
use dash_vm::Vm;
use dash_vm::eval::EvalError;
use dash_vm::params::VmParams;
use dash_vm::Vm;
use once_cell::sync::Lazy;
use serde::Deserialize;

Expand Down

0 comments on commit e137f39

Please sign in to comment.