Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(boa): adds splice method #1365

Merged
merged 17 commits into from
Sep 6, 2021
177 changes: 176 additions & 1 deletion boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl BuiltIn for Array {
.method(Self::flat_map, "flatMap", 1)
.method(Self::slice, "slice", 2)
.method(Self::some, "some", 2)
.method(Self::splice, "splice", 3)
.method(Self::reduce, "reduce", 2)
.method(Self::reduce_right, "reduceRight", 2)
.method(Self::keys, "keys", 0)
Expand Down Expand Up @@ -166,7 +167,7 @@ impl Array {
// i. Let intLen be ! ToUint32(len).
let int_len = len.to_u32(context).unwrap();
// ii. If SameValueZero(intLen, len) is false, throw a RangeError exception.
if !Value::same_value_zero(&int_len.into(), &len) {
if !Value::same_value_zero(&int_len.into(), len) {
return Err(context.construct_range_error("invalid array length"));
}
int_len
Expand Down Expand Up @@ -1511,6 +1512,180 @@ impl Array {
new_array.set_field("length", Value::from(new_array_len), true, context)?;
Ok(new_array)
}
/// `Array.prototype.splice ( start, [deleteCount[, ...items]] )`
neeldug marked this conversation as resolved.
Show resolved Hide resolved
///
/// Splices an array by following
/// The deleteCount elements of the array starting at integer index start are replaced by the elements of items.
/// An Array object containing the deleted elements (if any) is returned.

neeldug marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn splice(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 3. Let relativeStart be ? ToIntegerOrInfinity(start).
// 4. If relativeStart is -∞, let actualStart be 0.
// 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, 0).
// 6. Else, let actualStart be min(relativeStart, len).
let actual_start = Self::get_relative_start(context, args.get(0), len)?;
// 7. If start is not present, then
let insert_count = if args.get(0).is_none() || args.get(1).is_none() {
neeldug marked this conversation as resolved.
Show resolved Hide resolved
neeldug marked this conversation as resolved.
Show resolved Hide resolved
// 7a. Let insertCount be 0.
// 8. Else if deleteCount is not present, then
// a. Let insertCount be 0.
0
// 9. Else,
} else {
// 9a. Let insertCount be the number of elements in items.
args.len().saturating_sub(2)
};
let actual_delete_count = if args.get(0).is_none() {
// 7b. Let actualDeleteCount be 0.
0
// 8. Else if deleteCount is not present, then
} else if args.get(1).is_none() {
// 8b. Let actualDeleteCount be len - actualStart.
len - actual_start
// 9. Else,
} else {
// b. Let dc be ? ToIntegerOrInfinity(deleteCount).
let dc = args.get(1).ok_or(0)?.to_integer_or_infinity(context)?;
neeldug marked this conversation as resolved.
Show resolved Hide resolved
// c. Let actualDeleteCount be the result of clamping dc between 0 and len - actualStart.
let max = len - actual_start;
match dc {
IntegerOrInfinity::Integer(i) => {
if i < 0 {
0
} else if i as usize > max {
max
} else {
i as usize
}
neeldug marked this conversation as resolved.
Show resolved Hide resolved
}
IntegerOrInfinity::PositiveInfinity => max,
IntegerOrInfinity::NegativeInfinity => 0,
}
};

// 10. If len + insertCount - actualDeleteCount > 253 - 1, throw a TypeError exception.
neeldug marked this conversation as resolved.
Show resolved Hide resolved
if len + insert_count - actual_delete_count > Number::MAX_SAFE_INTEGER as usize {
return context.throw_type_error("Target splice exceeded max safe integer value");
}

// 11. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
let arr = Self::array_species_create(&o, actual_delete_count, context)?;
// 12. Let k be 0.
// 13. Repeat, while k < actualDeleteCount,
for k in 0..actual_delete_count {
// a. Let from be ! ToString(𝔽(actualStart + k)).
// b. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(actual_start + k, context)?;
// c. If fromPresent is true, then
if from_present {
// i. Let fromValue be ? Get(O, from).
let from_value = o.get(actual_start + k, context)?;
// ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(k)), fromValue).
arr.create_data_property_or_throw(actual_start + k, from_value, context)?;
neeldug marked this conversation as resolved.
Show resolved Hide resolved
}
// d. Set k to k + 1.
}

// 14. Perform ? Set(A, "length", 𝔽(actualDeleteCount), true).
arr.set("length", actual_delete_count, true, context)?;

// 15. Let itemCount be the number of elements in items.
let item_count = args.len().saturating_sub(2);

match item_count {
neeldug marked this conversation as resolved.
Show resolved Hide resolved
// 16. If itemCount < actualDeleteCount, then
ic if ic < actual_delete_count => {
// a. Set k to actualStart.
// b. Repeat, while k < (len - actualDeleteCount),
for k in actual_start..(len - actual_delete_count) {
// i. Let from be ! ToString(𝔽(k + actualDeleteCount)).
let from = k + actual_delete_count;
// ii. Let to be ! ToString(𝔽(k + itemCount)).
let to = k + item_count;
// iii. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(from, context)?;
// iv. If fromPresent is true, then
if from_present {
// 1. Let fromValue be ? Get(O, from).
let from_value = o.get(from, context)?;
// 2. Perform ? Set(O, to, fromValue, true).
o.set(to, from_value, true, context)?;
// v. Else,
} else {
// 1. Assert: fromPresent is false.
debug_assert!(!from_present);
// 2. Perform ? DeletePropertyOrThrow(O, to).
o.delete_property_or_throw(to, context)?;
}
// vi. Set k to k + 1.
}
// c. Set k to len.
// d. Repeat, while k > (len - actualDeleteCount + itemCount),
for k in ((len - actual_delete_count + item_count + 1)..=len).rev() {
// i. Perform ? DeletePropertyOrThrow(O, ! ToString(𝔽(k - 1))).
o.delete_property_or_throw(k - 1, context)?;
// ii. Set k to k - 1.
}
neeldug marked this conversation as resolved.
Show resolved Hide resolved
}
// 17. Else if itemCount > actualDeleteCount, then
ic if ic > actual_delete_count => {
// a. Set k to (len - actualDeleteCount).
// b. Repeat, while k > actualStart,
for k in (actual_start + 1..=len - actual_delete_count).rev() {
// i. Let from be ! ToString(𝔽(k + actualDeleteCount - 1)).
let from = k + actual_delete_count - 1;
// ii. Let to be ! ToString(𝔽(k + itemCount - 1)).
let to = k + item_count - 1;
neeldug marked this conversation as resolved.
Show resolved Hide resolved
// iii. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(from, context)?;
// iv. If fromPresent is true, then
if from_present {
// 1. Let fromValue be ? Get(O, from).
let from_value = o.get(from, context)?;
// 2. Perform ? Set(O, to, fromValue, true).
o.set(to, from_value, true, context)?;
// v. Else,
} else {
// 1. Assert: fromPresent is false.
debug_assert!(!from_present);
// 2. Perform ? DeletePropertyOrThrow(O, to).
o.delete_property_or_throw(to, context)?;
}
// vi. Set k to k - 1.
}
}
_ => {}
};

// 18. Set k to actualStart.
let mut k = actual_start;

// 19. For each element E of items, do
if item_count > 0 {
let items = args.split_at(2).1;
for item in items {
neeldug marked this conversation as resolved.
Show resolved Hide resolved
// a. Perform ? Set(O, ! ToString(𝔽(k)), E, true).
o.set(k, item, true, context)?;
// b. Set k to k + 1.
k += 1;
}
}

// 20. Perform ? Set(O, "length", 𝔽(len - actualDeleteCount + itemCount), true).
o.set(
"length",
len - actual_delete_count + item_count,
true,
context,
)?;

// 21. Return A.
Ok(Value::from(arr))
}

/// `Array.prototype.filter( callback, [ thisArg ] )`
///
Expand Down
4 changes: 2 additions & 2 deletions boa_tester/src/exec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl Test {
Outcome::Positive => {
// TODO: implement async and add `harness/doneprintHandle.js` to the includes.

match self.set_up_env(&harness, strict) {
match self.set_up_env(harness, strict) {
Ok(mut context) => {
let res = context.eval(&self.content.as_ref());

Expand Down Expand Up @@ -183,7 +183,7 @@ impl Test {
if let Err(e) = parse(&self.content.as_ref(), strict) {
(false, format!("Uncaught {}", e))
} else {
match self.set_up_env(&harness, strict) {
match self.set_up_env(harness, strict) {
Ok(mut context) => match context.eval(&self.content.as_ref()) {
Ok(res) => (false, format!("{}", res.display())),
Err(e) => {
Expand Down