|
|
|
//! This module implements the global `Array` object.
|
|
|
|
//!
|
|
|
|
//! The JavaScript `Array` class is a global object that is used in the construction of arrays; which are high-level, list-like objects.
|
|
|
|
//!
|
|
|
|
//! More information:
|
|
|
|
//! - [ECMAScript reference][spec]
|
|
|
|
//! - [MDN documentation][mdn]
|
|
|
|
//!
|
|
|
|
//! [spec]: https://tc39.es/ecma262/#sec-array-objects
|
|
|
|
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
|
|
|
|
|
Implementation of for...of loops (#704)
* Initial implementation of for...of loop
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Initial implementation of for...of loop
* Nest use statements
* Add string iterator
* Clean up merge
* Use ctx.global_iterator()
* Merge upstream
* Use u32 as array next index
* Use into
* Use boa::Result
* Tidy up use statement
* Tidy up use statement
4 years ago
|
|
|
pub mod array_iterator;
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
|
|
|
|
|
|
|
use crate::{
|
Implementation of for...of loops (#704)
* Initial implementation of for...of loop
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Initial implementation of for...of loop
* Nest use statements
* Add string iterator
* Clean up merge
* Use ctx.global_iterator()
* Merge upstream
* Use u32 as array next index
* Use into
* Use boa::Result
* Tidy up use statement
* Tidy up use statement
4 years ago
|
|
|
builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator},
|
|
|
|
builtins::BuiltIn,
|
|
|
|
gc::GcObject,
|
|
|
|
object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE},
|
|
|
|
property::{Attribute, DataDescriptor},
|
|
|
|
value::{same_value_zero, Value},
|
|
|
|
BoaProfiler, Context, Result,
|
|
|
|
};
|
|
|
|
use std::cmp::{max, min};
|
|
|
|
|
|
|
|
/// JavaScript `Array` built-in implementation.
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
pub(crate) struct Array;
|
|
|
|
|
|
|
|
impl BuiltIn for Array {
|
|
|
|
const NAME: &'static str = "Array";
|
|
|
|
|
|
|
|
fn attribute() -> Attribute {
|
|
|
|
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
|
|
|
|
}
|
|
|
|
|
|
|
|
fn init(context: &mut Context) -> (&'static str, Value, Attribute) {
|
|
|
|
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
|
|
|
|
|
|
|
|
let symbol_iterator = context.well_known_symbols().iterator_symbol();
|
|
|
|
|
|
|
|
let values_function = FunctionBuilder::new(context, Self::values)
|
|
|
|
.name("values")
|
|
|
|
.length(0)
|
|
|
|
.callable(true)
|
|
|
|
.constructable(false)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
let array = ConstructorBuilder::with_standard_object(
|
|
|
|
context,
|
|
|
|
Self::constructor,
|
|
|
|
context.standard_objects().array_object().clone(),
|
|
|
|
)
|
|
|
|
.name(Self::NAME)
|
|
|
|
.length(Self::LENGTH)
|
|
|
|
.property("length", 0, Attribute::all())
|
|
|
|
.property(
|
|
|
|
"values",
|
|
|
|
values_function.clone(),
|
|
|
|
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
|
|
|
)
|
|
|
|
.property(
|
|
|
|
symbol_iterator,
|
|
|
|
values_function,
|
|
|
|
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
|
|
|
)
|
|
|
|
.method(Self::concat, "concat", 1)
|
|
|
|
.method(Self::push, "push", 1)
|
|
|
|
.method(Self::index_of, "indexOf", 1)
|
|
|
|
.method(Self::last_index_of, "lastIndexOf", 1)
|
|
|
|
.method(Self::includes_value, "includes", 1)
|
|
|
|
.method(Self::map, "map", 1)
|
|
|
|
.method(Self::fill, "fill", 1)
|
|
|
|
.method(Self::for_each, "forEach", 1)
|
|
|
|
.method(Self::filter, "filter", 1)
|
|
|
|
.method(Self::pop, "pop", 0)
|
|
|
|
.method(Self::join, "join", 1)
|
|
|
|
.method(Self::to_string, "toString", 0)
|
|
|
|
.method(Self::reverse, "reverse", 0)
|
|
|
|
.method(Self::shift, "shift", 0)
|
|
|
|
.method(Self::unshift, "unshift", 1)
|
|
|
|
.method(Self::every, "every", 1)
|
|
|
|
.method(Self::find, "find", 1)
|
|
|
|
.method(Self::find_index, "findIndex", 1)
|
|
|
|
.method(Self::slice, "slice", 2)
|
|
|
|
.method(Self::some, "some", 2)
|
|
|
|
.method(Self::reduce, "reduce", 2)
|
|
|
|
.method(Self::reduce_right, "reduceRight", 2)
|
|
|
|
.method(Self::keys, "keys", 0)
|
|
|
|
.method(Self::entries, "entries", 0)
|
|
|
|
// Static Methods
|
|
|
|
.static_method(Self::is_array, "isArray", 1)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
(Self::NAME, array.into(), Self::attribute())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Array {
|
|
|
|
const LENGTH: usize = 1;
|
|
|
|
|
|
|
|
fn constructor(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
|
|
|
|
// Delegate to the appropriate constructor based on the number of arguments
|
|
|
|
match args.len() {
|
|
|
|
0 => Array::construct_array_empty(this, context),
|
|
|
|
1 => Array::construct_array_length(this, &args[0], context),
|
|
|
|
_ => Array::construct_array_values(this, args, context),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// No argument constructor for `Array`.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array-constructor-array
|
|
|
|
fn construct_array_empty(this: &Value, context: &mut Context) -> Result<Value> {
|
|
|
|
let prototype = context.standard_objects().array_object().prototype();
|
|
|
|
|
|
|
|
Array::array_create(this, 0, Some(prototype), context)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// By length constructor for `Array`.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array-len
|
|
|
|
fn construct_array_length(
|
|
|
|
this: &Value,
|
|
|
|
length: &Value,
|
|
|
|
context: &mut Context,
|
|
|
|
) -> Result<Value> {
|
|
|
|
let prototype = context.standard_objects().array_object().prototype();
|
|
|
|
let array = Array::array_create(this, 0, Some(prototype), context)?;
|
|
|
|
|
|
|
|
if !length.is_number() {
|
|
|
|
array.set_field(0, Value::from(length));
|
|
|
|
array.set_field("length", Value::from(1));
|
|
|
|
} else {
|
|
|
|
if length.is_double() {
|
|
|
|
return context.throw_range_error("Invalid array length");
|
|
|
|
}
|
|
|
|
array.set_field("length", Value::from(length.to_u32(context).unwrap()));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(array)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// From items constructor for `Array`.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array-items
|
|
|
|
fn construct_array_values(
|
|
|
|
this: &Value,
|
|
|
|
items: &[Value],
|
|
|
|
context: &mut Context,
|
|
|
|
) -> Result<Value> {
|
|
|
|
let prototype = context.standard_objects().array_object().prototype();
|
|
|
|
let array = Array::array_create(this, items.len() as u32, Some(prototype), context)?;
|
|
|
|
|
|
|
|
for (k, item) in items.iter().enumerate() {
|
|
|
|
array.set_field(k, item.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(array)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Utility for constructing `Array` objects.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-arraycreate
|
|
|
|
fn array_create(
|
|
|
|
this: &Value,
|
|
|
|
length: u32,
|
|
|
|
prototype: Option<GcObject>,
|
|
|
|
context: &mut Context,
|
|
|
|
) -> Result<Value> {
|
|
|
|
let prototype = match prototype {
|
|
|
|
Some(prototype) => prototype,
|
|
|
|
None => context.standard_objects().array_object().prototype(),
|
|
|
|
};
|
|
|
|
|
|
|
|
this.as_object_mut()
|
|
|
|
.expect("this should be an array object")
|
|
|
|
.set_prototype_instance(prototype.into());
|
|
|
|
// This value is used by console.log and other routines to match Object type
|
|
|
|
// to its Javascript Identifier (global constructor method name)
|
|
|
|
this.set_data(ObjectData::Array);
|
|
|
|
|
|
|
|
let length = DataDescriptor::new(
|
|
|
|
length,
|
|
|
|
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
|
|
|
|
);
|
|
|
|
this.set_property("length", length);
|
|
|
|
|
|
|
|
Ok(this.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a new `Array` instance.
|
|
|
|
pub(crate) fn new_array(context: &Context) -> Result<Value> {
|
|
|
|
let array = Value::new_object(Some(
|
|
|
|
&context
|
|
|
|
.realm()
|
|
|
|
.environment
|
|
|
|
.get_global_object()
|
|
|
|
.expect("Could not get global object"),
|
|
|
|
));
|
|
|
|
array.set_data(ObjectData::Array);
|
|
|
|
array
|
|
|
|
.as_object_mut()
|
|
|
|
.expect("array object")
|
|
|
|
.set_prototype_instance(context.standard_objects().array_object().prototype().into());
|
|
|
|
array.set_field("length", Value::from(0));
|
|
|
|
Ok(array)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Utility function for creating array objects.
|
|
|
|
///
|
|
|
|
/// `array_obj` can be any array with prototype already set (it will be wiped and
|
|
|
|
/// recreated from `array_contents`)
|
|
|
|
pub(crate) fn construct_array(array_obj: &Value, array_contents: &[Value]) -> Result<Value> {
|
|
|
|
let array_obj_ptr = array_obj.clone();
|
|
|
|
|
|
|
|
// Wipe existing contents of the array object
|
|
|
|
let orig_length = array_obj.get_field("length").as_number().unwrap() as i32;
|
|
|
|
for n in 0..orig_length {
|
|
|
|
array_obj_ptr.remove_property(n);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create length
|
|
|
|
let length = DataDescriptor::new(
|
|
|
|
array_contents.len(),
|
|
|
|
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
|
|
|
|
);
|
|
|
|
array_obj_ptr.set_property("length".to_string(), length);
|
|
|
|
|
|
|
|
for (n, value) in array_contents.iter().enumerate() {
|
|
|
|
array_obj_ptr.set_field(n, value);
|
|
|
|
}
|
|
|
|
Ok(array_obj_ptr)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Utility function which takes an existing array object and puts additional
|
|
|
|
/// values on the end, correctly rewriting the length
|
|
|
|
pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> Result<Value> {
|
|
|
|
let orig_length = array_ptr.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
for (n, value) in add_values.iter().enumerate() {
|
|
|
|
let new_index = orig_length.wrapping_add(n as i32);
|
|
|
|
array_ptr.set_field(new_index, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
array_ptr.set_field(
|
|
|
|
"length",
|
|
|
|
Value::from(orig_length.wrapping_add(add_values.len() as i32)),
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(array_ptr.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.isArray( arg )`
|
|
|
|
///
|
|
|
|
/// The isArray function takes one argument arg, and returns the Boolean value true
|
|
|
|
/// if the argument is an object whose class internal property is "Array"; otherwise it returns false.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.isarray
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
|
|
|
|
pub(crate) fn is_array(
|
|
|
|
_this: &Value,
|
|
|
|
args: &[Value],
|
|
|
|
_interpreter: &mut Context,
|
|
|
|
) -> Result<Value> {
|
|
|
|
match args.get(0).and_then(|x| x.as_object()) {
|
|
|
|
Some(object) => Ok(Value::from(object.is_array())),
|
|
|
|
None => Ok(Value::from(false)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.concat(...arguments)`
|
|
|
|
///
|
|
|
|
/// When the concat method is called with zero or more arguments, it returns an
|
|
|
|
/// array containing the array elements of the object followed by the array
|
|
|
|
/// elements of each argument in order.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
|
|
|
|
pub(crate) fn concat(this: &Value, args: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
if args.is_empty() {
|
|
|
|
// If concat is called with no arguments, it returns the original array
|
|
|
|
return Ok(this.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a new array (using this object as the prototype basis for the new
|
|
|
|
// one)
|
|
|
|
let mut new_values: Vec<Value> = Vec::new();
|
|
|
|
|
|
|
|
let this_length = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
for n in 0..this_length {
|
|
|
|
new_values.push(this.get_field(n));
|
|
|
|
}
|
|
|
|
|
|
|
|
for concat_array in args {
|
|
|
|
let concat_length = concat_array.get_field("length").as_number().unwrap() as i32;
|
|
|
|
for n in 0..concat_length {
|
|
|
|
new_values.push(concat_array.get_field(n));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::construct_array(this, &new_values)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.push( ...items )`
|
|
|
|
///
|
|
|
|
/// The arguments are appended to the end of the array, in the order in which
|
|
|
|
/// they appear. The new length of the array is returned as the result of the
|
|
|
|
/// call.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
|
|
|
|
pub(crate) fn push(this: &Value, args: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
let new_array = Self::add_to_array_object(this, args)?;
|
|
|
|
Ok(new_array.get_field("length"))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.pop()`
|
|
|
|
///
|
|
|
|
/// The last element of the array is removed from the array and returned.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop
|
|
|
|
pub(crate) fn pop(this: &Value, _: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
let curr_length = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
if curr_length < 1 {
|
|
|
|
return Ok(Value::undefined());
|
|
|
|
}
|
|
|
|
let pop_index = curr_length.wrapping_sub(1);
|
|
|
|
let pop_value: Value = this.get_field(pop_index.to_string());
|
|
|
|
this.remove_property(pop_index);
|
|
|
|
this.set_field("length", Value::from(pop_index));
|
|
|
|
Ok(pop_value)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.forEach( callbackFn [ , thisArg ] )`
|
|
|
|
///
|
|
|
|
/// This method executes the provided callback function for each element in the array.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
|
|
|
|
pub(crate) fn for_each(this: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
|
|
|
|
if args.is_empty() {
|
|
|
|
return Err(Value::from("Missing argument for Array.prototype.forEach"));
|
|
|
|
}
|
|
|
|
|
|
|
|
let callback_arg = args.get(0).expect("Could not get `callbackFn` argument.");
|
|
|
|
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
|
|
|
|
let length = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
for i in 0..length {
|
|
|
|
let element = this.get_field(i);
|
|
|
|
let arguments = [element, Value::from(i), this.clone()];
|
|
|
|
|
|
|
|
ctx.call(callback_arg, &this_arg, &arguments)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::undefined())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.join( separator )`
|
|
|
|
///
|
|
|
|
/// The elements of the array are converted to Strings, and these Strings are
|
|
|
|
/// then concatenated, separated by occurrences of the separator. If no
|
|
|
|
/// separator is provided, a single comma is used as the separator.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
|
|
|
|
pub(crate) fn join(this: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
|
|
|
|
let separator = if args.is_empty() {
|
|
|
|
String::from(",")
|
|
|
|
} else {
|
|
|
|
args.get(0)
|
|
|
|
.expect("Could not get argument")
|
|
|
|
.to_string(ctx)?
|
|
|
|
.to_string()
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut elem_strs = Vec::new();
|
|
|
|
let length = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
for n in 0..length {
|
|
|
|
let elem_str = this.get_field(n).to_string(ctx)?.to_string();
|
|
|
|
elem_strs.push(elem_str);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::from(elem_strs.join(&separator)))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.toString( separator )`
|
|
|
|
///
|
|
|
|
/// The toString function is intentionally generic; it does not require that
|
|
|
|
/// its this value be an Array object. Therefore it can be transferred to
|
|
|
|
/// other kinds of objects for use as a method.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tostring
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString
|
|
|
|
#[allow(clippy::wrong_self_convention)]
|
|
|
|
pub(crate) fn to_string(this: &Value, _args: &[Value], ctx: &mut Context) -> Result<Value> {
|
|
|
|
let method_name = "join";
|
|
|
|
let mut arguments = vec![Value::from(",")];
|
|
|
|
// 2.
|
|
|
|
let mut method = this.get_field(method_name);
|
|
|
|
// 3.
|
|
|
|
if !method.is_function() {
|
|
|
|
method = ctx
|
|
|
|
.global_object()
|
|
|
|
.get_field("Object")
|
|
|
|
.get_field(PROTOTYPE)
|
|
|
|
.get_field("toString");
|
|
|
|
|
|
|
|
arguments = Vec::new();
|
|
|
|
}
|
|
|
|
// 4.
|
|
|
|
let join = ctx.call(&method, this, &arguments)?;
|
|
|
|
|
|
|
|
let string = if let Value::String(ref s) = join {
|
|
|
|
Value::from(s.as_str())
|
|
|
|
} else {
|
|
|
|
Value::from("")
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.reverse()`
|
|
|
|
///
|
|
|
|
/// The elements of the array are rearranged so as to reverse their order.
|
|
|
|
/// The object is returned as the result of the call.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
|
|
|
|
#[allow(clippy::else_if_without_else)]
|
|
|
|
pub(crate) fn reverse(this: &Value, _: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
let len = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
let middle: i32 = len.wrapping_div(2);
|
|
|
|
|
|
|
|
for lower in 0..middle {
|
|
|
|
let upper = len.wrapping_sub(lower).wrapping_sub(1);
|
|
|
|
|
|
|
|
let upper_exists = this.has_field(upper);
|
|
|
|
let lower_exists = this.has_field(lower);
|
|
|
|
|
|
|
|
let upper_value = this.get_field(upper);
|
|
|
|
let lower_value = this.get_field(lower);
|
|
|
|
|
|
|
|
if upper_exists && lower_exists {
|
|
|
|
this.set_field(upper, lower_value);
|
|
|
|
this.set_field(lower, upper_value);
|
|
|
|
} else if upper_exists {
|
|
|
|
this.set_field(lower, upper_value);
|
|
|
|
this.remove_property(upper);
|
|
|
|
} else if lower_exists {
|
|
|
|
this.set_field(upper, lower_value);
|
|
|
|
this.remove_property(lower);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(this.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.shift()`
|
|
|
|
///
|
|
|
|
/// The first element of the array is removed from the array and returned.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
|
|
|
|
pub(crate) fn shift(this: &Value, _: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
let len = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
if len == 0 {
|
|
|
|
this.set_field("length", 0);
|
|
|
|
return Ok(Value::undefined());
|
|
|
|
}
|
|
|
|
|
|
|
|
let first: Value = this.get_field(0);
|
|
|
|
|
|
|
|
for k in 1..len {
|
|
|
|
let from = k;
|
|
|
|
let to = k.wrapping_sub(1);
|
|
|
|
|
|
|
|
let from_value = this.get_field(from);
|
|
|
|
if from_value.is_undefined() {
|
|
|
|
this.remove_property(to);
|
|
|
|
} else {
|
|
|
|
this.set_field(to, from_value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let final_index = len.wrapping_sub(1);
|
|
|
|
this.remove_property(final_index);
|
|
|
|
this.set_field("length", Value::from(final_index));
|
|
|
|
|
|
|
|
Ok(first)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.unshift( ...items )`
|
|
|
|
///
|
|
|
|
/// The arguments are prepended to the start of the array, such that their order
|
|
|
|
/// within the array is the same as the order in which they appear in the
|
|
|
|
/// argument list.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
|
|
|
|
pub(crate) fn unshift(this: &Value, args: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
let len = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
let arg_c: i32 = args.len() as i32;
|
|
|
|
|
|
|
|
if arg_c > 0 {
|
|
|
|
for k in (1..=len).rev() {
|
|
|
|
let from = k.wrapping_sub(1);
|
|
|
|
let to = k.wrapping_add(arg_c).wrapping_sub(1);
|
|
|
|
|
|
|
|
let from_value = this.get_field(from);
|
|
|
|
if from_value.is_undefined() {
|
|
|
|
this.remove_property(to);
|
|
|
|
} else {
|
|
|
|
this.set_field(to, from_value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for j in 0..arg_c {
|
|
|
|
this.set_field(
|
|
|
|
j,
|
|
|
|
args.get(j as usize)
|
|
|
|
.expect("Could not get argument")
|
|
|
|
.clone(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let temp = len.wrapping_add(arg_c);
|
|
|
|
this.set_field("length", Value::from(temp));
|
|
|
|
Ok(Value::from(temp))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.every( callback, [ thisArg ] )`
|
|
|
|
///
|
|
|
|
/// The every method executes the provided callback function once for each
|
|
|
|
/// element present in the array until it finds the one where callback returns
|
|
|
|
/// a falsy value. It returns `false` if it finds such element, otherwise it
|
|
|
|
/// returns `true`.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
|
|
|
|
pub(crate) fn every(this: &Value, args: &[Value], interpreter: &mut Context) -> Result<Value> {
|
|
|
|
if args.is_empty() {
|
|
|
|
return Err(Value::from(
|
|
|
|
"missing callback when calling function Array.prototype.every",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
let callback = &args[0];
|
|
|
|
let this_arg = if args.len() > 1 {
|
|
|
|
args[1].clone()
|
|
|
|
} else {
|
|
|
|
Value::undefined()
|
|
|
|
};
|
|
|
|
let mut i = 0;
|
|
|
|
let max_len = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
let mut len = max_len;
|
|
|
|
while i < len {
|
|
|
|
let element = this.get_field(i);
|
|
|
|
let arguments = [element, Value::from(i), this.clone()];
|
|
|
|
let result = interpreter.call(callback, &this_arg, &arguments)?;
|
|
|
|
if !result.to_boolean() {
|
|
|
|
return Ok(Value::from(false));
|
|
|
|
}
|
|
|
|
len = min(
|
|
|
|
max_len,
|
|
|
|
this.get_field("length").as_number().unwrap() as i32,
|
|
|
|
);
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
Ok(Value::from(true))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.map( callback, [ thisArg ] )`
|
|
|
|
///
|
|
|
|
/// For each element in the array the callback function is called, and a new
|
|
|
|
/// array is constructed from the return values of these calls.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.map
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
|
|
|
|
pub(crate) fn map(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
|
|
|
|
if args.is_empty() {
|
|
|
|
return Err(Value::from(
|
|
|
|
"missing argument 0 when calling function Array.prototype.map",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
let callback = args.get(0).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
|
|
|
|
let length = this.get_field("length").to_length(context)?;
|
|
|
|
|
|
|
|
if length > 2usize.pow(32) - 1 {
|
|
|
|
return context.throw_range_error("Invalid array length");
|
|
|
|
}
|
|
|
|
|
|
|
|
let new = Self::new_array(context)?;
|
|
|
|
|
|
|
|
let values: Vec<Value> = (0..length)
|
|
|
|
.map(|idx| {
|
|
|
|
let element = this.get_field(idx);
|
|
|
|
let args = [element, Value::from(idx), new.clone()];
|
|
|
|
|
|
|
|
context
|
|
|
|
.call(&callback, &this_val, &args)
|
|
|
|
.unwrap_or_else(|_| Value::undefined())
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
Self::construct_array(&new, &values)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.indexOf( searchElement[, fromIndex ] )`
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// indexOf compares searchElement to the elements of the array, in ascending order,
|
|
|
|
/// using the Strict Equality Comparison algorithm, and if found at one or more indices,
|
|
|
|
/// returns the smallest such index; otherwise, -1 is returned.
|
|
|
|
///
|
|
|
|
/// The optional second argument fromIndex defaults to 0 (i.e. the whole array is searched).
|
|
|
|
/// If it is greater than or equal to the length of the array, -1 is returned,
|
|
|
|
/// i.e. the array will not be searched. If it is negative, it is used as the offset
|
|
|
|
/// from the end of the array to compute fromIndex. If the computed index is less than 0,
|
|
|
|
/// the whole array will be searched.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
|
|
|
|
pub(crate) fn index_of(this: &Value, args: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
// If no arguments, return -1. Not described in spec, but is what chrome does.
|
|
|
|
if args.is_empty() {
|
|
|
|
return Ok(Value::from(-1));
|
|
|
|
}
|
|
|
|
|
|
|
|
let search_element = args[0].clone();
|
|
|
|
let len = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
let mut idx = match args.get(1) {
|
|
|
|
Some(from_idx_ptr) => {
|
|
|
|
let from_idx = from_idx_ptr.as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
if from_idx < 0 {
|
|
|
|
len + from_idx
|
|
|
|
} else {
|
|
|
|
from_idx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
while idx < len {
|
|
|
|
let check_element = this.get_field(idx).clone();
|
|
|
|
|
|
|
|
if check_element.strict_equals(&search_element) {
|
|
|
|
return Ok(Value::from(idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
idx += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::from(-1))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.lastIndexOf( searchElement[, fromIndex ] )`
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// lastIndexOf compares searchElement to the elements of the array in descending order
|
|
|
|
/// using the Strict Equality Comparison algorithm, and if found at one or more indices,
|
|
|
|
/// returns the largest such index; otherwise, -1 is returned.
|
|
|
|
///
|
|
|
|
/// The optional second argument fromIndex defaults to the array's length minus one
|
|
|
|
/// (i.e. the whole array is searched). If it is greater than or equal to the length of the array,
|
|
|
|
/// the whole array will be searched. If it is negative, it is used as the offset from the end
|
|
|
|
/// of the array to compute fromIndex. If the computed index is less than 0, -1 is returned.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf
|
|
|
|
pub(crate) fn last_index_of(this: &Value, args: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
// If no arguments, return -1. Not described in spec, but is what chrome does.
|
|
|
|
if args.is_empty() {
|
|
|
|
return Ok(Value::from(-1));
|
|
|
|
}
|
|
|
|
|
|
|
|
let search_element = args[0].clone();
|
|
|
|
let len = this
|
|
|
|
.get_field("length")
|
|
|
|
.as_number()
|
|
|
|
.expect("length was not a number") as i32;
|
|
|
|
|
|
|
|
let mut idx = match args.get(1) {
|
|
|
|
Some(from_idx_ptr) => {
|
|
|
|
let from_idx = from_idx_ptr.as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
if from_idx >= 0 {
|
|
|
|
min(from_idx, len - 1)
|
|
|
|
} else {
|
|
|
|
len + from_idx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => len - 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
while idx >= 0 {
|
|
|
|
let check_element = this.get_field(idx).clone();
|
|
|
|
|
|
|
|
if check_element.strict_equals(&search_element) {
|
|
|
|
return Ok(Value::from(idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
idx -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::from(-1))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.find( callback, [thisArg] )`
|
|
|
|
///
|
|
|
|
/// The find method executes the callback function once for each index of the array
|
|
|
|
/// until the callback returns a truthy value. If so, find immediately returns the value
|
|
|
|
/// of that element. Otherwise, find returns undefined.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.find
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
|
|
|
|
pub(crate) fn find(this: &Value, args: &[Value], interpreter: &mut Context) -> Result<Value> {
|
|
|
|
if args.is_empty() {
|
|
|
|
return Err(Value::from(
|
|
|
|
"missing callback when calling function Array.prototype.find",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
let callback = &args[0];
|
|
|
|
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
let len = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
for i in 0..len {
|
|
|
|
let element = this.get_field(i);
|
|
|
|
let arguments = [element.clone(), Value::from(i), this.clone()];
|
|
|
|
let result = interpreter.call(callback, &this_arg, &arguments)?;
|
|
|
|
if result.to_boolean() {
|
|
|
|
return Ok(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(Value::undefined())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.findIndex( predicate [ , thisArg ] )`
|
|
|
|
///
|
|
|
|
/// This method executes the provided predicate function for each element of the array.
|
|
|
|
/// If the predicate function returns `true` for an element, this method returns the index of the element.
|
|
|
|
/// If all elements return `false`, the value `-1` is returned.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.findindex
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
|
|
|
|
pub(crate) fn find_index(
|
|
|
|
this: &Value,
|
|
|
|
args: &[Value],
|
|
|
|
interpreter: &mut Context,
|
|
|
|
) -> Result<Value> {
|
|
|
|
if args.is_empty() {
|
|
|
|
return Err(Value::from(
|
|
|
|
"Missing argument for Array.prototype.findIndex",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
let predicate_arg = args.get(0).expect("Could not get `predicate` argument.");
|
|
|
|
|
|
|
|
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
|
|
|
|
let length = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
for i in 0..length {
|
|
|
|
let element = this.get_field(i);
|
|
|
|
let arguments = [element, Value::from(i), this.clone()];
|
|
|
|
|
|
|
|
let result = interpreter.call(predicate_arg, &this_arg, &arguments)?;
|
|
|
|
|
|
|
|
if result.to_boolean() {
|
|
|
|
return Ok(Value::rational(f64::from(i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::rational(-1_f64))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.fill( value[, start[, end]] )`
|
|
|
|
///
|
|
|
|
/// The method fills (modifies) all the elements of an array from start index (default 0)
|
|
|
|
/// to an end index (default array length) with a static value. It returns the modified array.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
|
|
|
|
pub(crate) fn fill(this: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
|
|
|
|
let len: i32 = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
let default_value = Value::undefined();
|
|
|
|
let value = args.get(0).unwrap_or(&default_value);
|
|
|
|
let relative_start = args.get(1).unwrap_or(&default_value).to_number(ctx)? as i32;
|
|
|
|
let relative_end_val = args.get(2).unwrap_or(&default_value);
|
|
|
|
let relative_end = if relative_end_val.is_undefined() {
|
|
|
|
len
|
|
|
|
} else {
|
|
|
|
relative_end_val.to_number(ctx)? as i32
|
|
|
|
};
|
|
|
|
let start = if relative_start < 0 {
|
|
|
|
max(len + relative_start, 0)
|
|
|
|
} else {
|
|
|
|
min(relative_start, len)
|
|
|
|
};
|
|
|
|
let fin = if relative_end < 0 {
|
|
|
|
max(len + relative_end, 0)
|
|
|
|
} else {
|
|
|
|
min(relative_end, len)
|
|
|
|
};
|
|
|
|
|
|
|
|
for i in start..fin {
|
|
|
|
this.set_field(i, value.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(this.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.includes( valueToFind [, fromIndex] )`
|
|
|
|
///
|
|
|
|
/// Determines whether an array includes a certain value among its entries, returning `true` or `false` as appropriate.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
|
|
|
|
pub(crate) fn includes_value(this: &Value, args: &[Value], _: &mut Context) -> Result<Value> {
|
|
|
|
let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
|
|
|
|
let length = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
for idx in 0..length {
|
|
|
|
let check_element = this.get_field(idx).clone();
|
|
|
|
|
|
|
|
if same_value_zero(&check_element, &search_element) {
|
|
|
|
return Ok(Value::from(true));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::from(false))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.slice( [begin[, end]] )`
|
|
|
|
///
|
|
|
|
/// The slice method takes two arguments, start and end, and returns an array containing the
|
|
|
|
/// elements of the array from element start up to, but not including, element end (or through the
|
|
|
|
/// end of the array if end is undefined). If start is negative, it is treated as length + start
|
|
|
|
/// where length is the length of the array. If end is negative, it is treated as length + end where
|
|
|
|
/// length is the length of the array.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.slice
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
|
|
|
|
pub(crate) fn slice(this: &Value, args: &[Value], interpreter: &mut Context) -> Result<Value> {
|
|
|
|
let new_array = Self::new_array(interpreter)?;
|
|
|
|
let len = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
let start = match args.get(0) {
|
|
|
|
Some(v) => v.as_number().unwrap() as i32,
|
|
|
|
None => 0,
|
|
|
|
};
|
|
|
|
let end = match args.get(1) {
|
|
|
|
Some(v) => v.as_number().unwrap() as i32,
|
|
|
|
None => len,
|
|
|
|
};
|
|
|
|
|
|
|
|
let from = if start < 0 {
|
|
|
|
max(len.wrapping_add(start), 0)
|
|
|
|
} else {
|
|
|
|
min(start, len)
|
|
|
|
};
|
|
|
|
let to = if end < 0 {
|
|
|
|
max(len.wrapping_add(end), 0)
|
|
|
|
} else {
|
|
|
|
min(end, len)
|
|
|
|
};
|
|
|
|
|
|
|
|
let span = max(to.wrapping_sub(from), 0);
|
|
|
|
let mut new_array_len: i32 = 0;
|
|
|
|
for i in from..from.wrapping_add(span) {
|
|
|
|
new_array.set_field(new_array_len, this.get_field(i));
|
|
|
|
new_array_len = new_array_len.wrapping_add(1);
|
|
|
|
}
|
|
|
|
new_array.set_field("length", Value::from(new_array_len));
|
|
|
|
Ok(new_array)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.filter( callback, [ thisArg ] )`
|
|
|
|
///
|
|
|
|
/// For each element in the array the callback function is called, and a new
|
|
|
|
/// array is constructed for every value whose callback returned a truthy value.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
|
|
|
|
pub(crate) fn filter(this: &Value, args: &[Value], interpreter: &mut Context) -> Result<Value> {
|
|
|
|
if args.is_empty() {
|
|
|
|
return Err(Value::from(
|
|
|
|
"missing argument 0 when calling function Array.prototype.filter",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
let callback = args.get(0).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
|
|
|
|
let length = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
|
|
|
|
let new = Self::new_array(interpreter)?;
|
|
|
|
|
|
|
|
let values = (0..length)
|
|
|
|
.filter_map(|idx| {
|
|
|
|
let element = this.get_field(idx);
|
|
|
|
|
|
|
|
let args = [element.clone(), Value::from(idx), new.clone()];
|
|
|
|
|
|
|
|
let callback_result = interpreter
|
|
|
|
.call(&callback, &this_val, &args)
|
|
|
|
.unwrap_or_else(|_| Value::undefined());
|
|
|
|
|
|
|
|
if callback_result.to_boolean() {
|
|
|
|
Some(element)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<Value>>();
|
|
|
|
|
|
|
|
Self::construct_array(&new, &values)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Array.prototype.some ( callbackfn [ , thisArg ] )
|
|
|
|
///
|
|
|
|
/// The some method tests whether at least one element in the array passes
|
|
|
|
/// the test implemented by the provided callback function. It returns a Boolean value,
|
|
|
|
/// true if the callback function returns a truthy value for at least one element
|
|
|
|
/// in the array. Otherwise, false.
|
|
|
|
///
|
|
|
|
/// Caution: Calling this method on an empty array returns false for any condition!
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
|
|
|
|
pub(crate) fn some(this: &Value, args: &[Value], interpreter: &mut Context) -> Result<Value> {
|
|
|
|
if args.is_empty() {
|
|
|
|
return Err(Value::from(
|
|
|
|
"missing callback when calling function Array.prototype.some",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
let callback = &args[0];
|
|
|
|
let this_arg = if args.len() > 1 {
|
|
|
|
args[1].clone()
|
|
|
|
} else {
|
|
|
|
Value::undefined()
|
|
|
|
};
|
|
|
|
let mut i = 0;
|
|
|
|
let max_len = this.get_field("length").as_number().unwrap() as i32;
|
|
|
|
let mut len = max_len;
|
|
|
|
while i < len {
|
|
|
|
let element = this.get_field(i);
|
|
|
|
let arguments = [element, Value::from(i), this.clone()];
|
|
|
|
let result = interpreter.call(callback, &this_arg, &arguments)?;
|
|
|
|
if result.to_boolean() {
|
|
|
|
return Ok(Value::from(true));
|
|
|
|
}
|
|
|
|
// the length of the array must be updated because the callback can mutate it.
|
|
|
|
len = min(
|
|
|
|
max_len,
|
|
|
|
this.get_field("length").as_number().unwrap() as i32,
|
|
|
|
);
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
Ok(Value::from(false))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.reduce( callbackFn [ , initialValue ] )`
|
|
|
|
///
|
|
|
|
/// The reduce method traverses left to right starting from the first defined value in the array,
|
|
|
|
/// accumulating a value using a given callback function. It returns the accumulated value.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduce
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
|
|
|
|
pub(crate) fn reduce(this: &Value, args: &[Value], interpreter: &mut Context) -> Result<Value> {
|
|
|
|
let this: Value = this.to_object(interpreter)?.into();
|
|
|
|
let callback = match args.get(0) {
|
|
|
|
Some(value) if value.is_function() => value,
|
|
|
|
_ => return interpreter.throw_type_error("Reduce was called without a callback"),
|
|
|
|
};
|
|
|
|
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
let mut length = this.get_field("length").to_length(interpreter)?;
|
|
|
|
if length == 0 && initial_value.is_undefined() {
|
|
|
|
return interpreter
|
|
|
|
.throw_type_error("Reduce was called on an empty array and with no initial value");
|
|
|
|
}
|
|
|
|
let mut k = 0;
|
|
|
|
let mut accumulator = if initial_value.is_undefined() {
|
|
|
|
let mut k_present = false;
|
|
|
|
while k < length {
|
|
|
|
if this.has_field(k) {
|
|
|
|
k_present = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
k += 1;
|
|
|
|
}
|
|
|
|
if !k_present {
|
|
|
|
return interpreter.throw_type_error(
|
|
|
|
"Reduce was called on an empty array and with no initial value",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let result = this.get_field(k);
|
|
|
|
k += 1;
|
|
|
|
result
|
|
|
|
} else {
|
|
|
|
initial_value
|
|
|
|
};
|
|
|
|
while k < length {
|
|
|
|
if this.has_field(k) {
|
|
|
|
let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()];
|
|
|
|
accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?;
|
|
|
|
/* We keep track of possibly shortened length in order to prevent unnecessary iteration.
|
|
|
|
It may also be necessary to do this since shortening the array length does not
|
|
|
|
delete array elements. See: https://github.com/boa-dev/boa/issues/557 */
|
|
|
|
length = min(length, this.get_field("length").to_length(interpreter)?);
|
|
|
|
}
|
|
|
|
k += 1;
|
|
|
|
}
|
|
|
|
Ok(accumulator)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.reduceRight( callbackFn [ , initialValue ] )`
|
|
|
|
///
|
|
|
|
/// The reduceRight method traverses right to left starting from the last defined value in the array,
|
|
|
|
/// accumulating a value using a given callback function. It returns the accumulated value.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduceright
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight
|
|
|
|
pub(crate) fn reduce_right(
|
|
|
|
this: &Value,
|
|
|
|
args: &[Value],
|
|
|
|
interpreter: &mut Context,
|
|
|
|
) -> Result<Value> {
|
|
|
|
let this: Value = this.to_object(interpreter)?.into();
|
|
|
|
let callback = match args.get(0) {
|
|
|
|
Some(value) if value.is_function() => value,
|
|
|
|
_ => return interpreter.throw_type_error("reduceRight was called without a callback"),
|
|
|
|
};
|
|
|
|
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined);
|
|
|
|
let mut length = this.get_field("length").to_length(interpreter)?;
|
|
|
|
if length == 0 {
|
|
|
|
return if initial_value.is_undefined() {
|
|
|
|
interpreter.throw_type_error(
|
|
|
|
"reduceRight was called on an empty array and with no initial value",
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// early return to prevent usize subtraction errors
|
|
|
|
Ok(initial_value)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
let mut k = length - 1;
|
|
|
|
let mut accumulator = if initial_value.is_undefined() {
|
|
|
|
let mut k_present = false;
|
|
|
|
loop {
|
|
|
|
if this.has_field(k) {
|
|
|
|
k_present = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// check must be done at the end to prevent usize subtraction error
|
|
|
|
if k == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
k -= 1;
|
|
|
|
}
|
|
|
|
if !k_present {
|
|
|
|
return interpreter.throw_type_error(
|
|
|
|
"reduceRight was called on an empty array and with no initial value",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let result = this.get_field(k);
|
|
|
|
k = k.overflowing_sub(1).0;
|
|
|
|
result
|
|
|
|
} else {
|
|
|
|
initial_value
|
|
|
|
};
|
|
|
|
// usize::MAX is bigger than the maximum array size so we can use it check for integer undeflow
|
|
|
|
while k != usize::MAX {
|
|
|
|
if this.has_field(k) {
|
|
|
|
let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()];
|
|
|
|
accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?;
|
|
|
|
/* We keep track of possibly shortened length in order to prevent unnecessary iteration.
|
|
|
|
It may also be necessary to do this since shortening the array length does not
|
|
|
|
delete array elements. See: https://github.com/boa-dev/boa/issues/557 */
|
|
|
|
length = min(length, this.get_field("length").to_length(interpreter)?);
|
|
|
|
|
|
|
|
// move k to the last defined element if necessary or return if the length was set to 0
|
|
|
|
if k >= length {
|
|
|
|
if length == 0 {
|
|
|
|
return Ok(accumulator);
|
|
|
|
} else {
|
|
|
|
k = length - 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if k == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
k = k.overflowing_sub(1).0;
|
|
|
|
}
|
|
|
|
Ok(accumulator)
|
|
|
|
}
|
|
|
|
|
Implementation of for...of loops (#704)
* Initial implementation of for...of loop
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Initial implementation of for...of loop
* Nest use statements
* Add string iterator
* Clean up merge
* Use ctx.global_iterator()
* Merge upstream
* Use u32 as array next index
* Use into
* Use boa::Result
* Tidy up use statement
* Tidy up use statement
4 years ago
|
|
|
/// `Array.prototype.values( )`
|
|
|
|
///
|
|
|
|
/// The values method returns an iterable that iterates over the values in the array.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
|
|
|
|
pub(crate) fn values(this: &Value, _: &[Value], ctx: &mut Context) -> Result<Value> {
|
|
|
|
ArrayIterator::create_array_iterator(ctx, this.clone(), ArrayIterationKind::Value)
|
Implementation of for...of loops (#704)
* Initial implementation of for...of loop
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Initial implementation of for...of loop
* Nest use statements
* Add string iterator
* Clean up merge
* Use ctx.global_iterator()
* Merge upstream
* Use u32 as array next index
* Use into
* Use boa::Result
* Tidy up use statement
* Tidy up use statement
4 years ago
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.keys( )`
|
|
|
|
///
|
|
|
|
/// The keys method returns an iterable that iterates over the indexes in the array.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
|
|
|
|
pub(crate) fn keys(this: &Value, _: &[Value], ctx: &mut Context) -> Result<Value> {
|
|
|
|
ArrayIterator::create_array_iterator(ctx, this.clone(), ArrayIterationKind::Key)
|
Implementation of for...of loops (#704)
* Initial implementation of for...of loop
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Nest use statements
* Nest use statements
* Add tests.
* Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use.
* Initial implementation of for...of loop
* Extend for...of to support var, let, and const lhs
* Use cached well known symbols
* Initial implementation of for...of loop
* Nest use statements
* Add string iterator
* Clean up merge
* Use ctx.global_iterator()
* Merge upstream
* Use u32 as array next index
* Use into
* Use boa::Result
* Tidy up use statement
* Tidy up use statement
4 years ago
|
|
|
}
|
|
|
|
|
|
|
|
/// `Array.prototype.entries( )`
|
|
|
|
///
|
|
|
|
/// The entries method returns an iterable that iterates over the key-value pairs in the array.
|
|
|
|
///
|
|
|
|
/// More information:
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
/// - [MDN documentation][mdn]
|
|
|
|
///
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
|
|
|
|
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
|
|
|
|
pub(crate) fn entries(this: &Value, _: &[Value], ctx: &mut Context) -> Result<Value> {
|
|
|
|
ArrayIterator::create_array_iterator(ctx, this.clone(), ArrayIterationKind::KeyAndValue)
|
|
|
|
}
|
|
|
|
}
|