Browse Source

Implement Array.prototype: concat(), push(), pop() and join() (#52)

* First draft of Array prototype object

* First draft of Array prototype object

* Update to working Array object

* Remove unneeded HashMap import

* Fix indexing by removing private internal HashMap

* Fix formatting issues with cargo fmt

* Implement Array.prototype.concat()

* Implement Array.prototype.concat()

* Cargo format fix

* Implement push, pop and join

* Add documenation links to new methods
pull/62/head
Callum Ward 5 years ago committed by Jason Williams
parent
commit
1c69da1dfa
  1. 148
      src/lib/js/array.rs

148
src/lib/js/array.rs

@ -3,28 +3,55 @@ use crate::js::object::{Property, PROTOTYPE};
use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData};
use gc::Gc;
/// 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)
fn create_array_object(array_obj: Value, array_contents: Vec<Value>) -> ResultValue {
let array_obj_ptr = array_obj.clone();
// Wipe existing contents of the array object
let orig_length: i32 = from_value(array_obj.get_field_slice("length")).unwrap();
for n in 0..orig_length {
array_obj_ptr.remove_prop(&n.to_string());
}
for (n, value) in array_contents.iter().enumerate() {
array_obj_ptr.set_field(n.to_string(), value.clone());
}
array_obj_ptr.set_field_slice("length", to_value(array_contents.len() as i32));
Ok(array_obj_ptr)
}
/// Utility function which takes an existing array object and puts additional
/// values on the end, correctly rewriting the length
fn add_to_array_object(array_ptr: Value, add_values: Vec<Value>) -> ResultValue {
let orig_length: i32 = from_value(array_ptr.get_field_slice("length")).unwrap();
for (n, value) in add_values.iter().enumerate() {
let new_index = orig_length + (n as i32);
array_ptr.set_field(new_index.to_string(), value.clone());
}
array_ptr.set_field_slice("length", to_value(orig_length + add_values.len() as i32));
Ok(array_ptr)
}
/// Create a new array
pub fn make_array(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let this_ptr = this.clone();
// Make a new Object which will internally represent the Array (mapping
// between indices and values): this creates an Object with no prototype
this.set_field_slice("length", to_value(0i32));
match args.len() {
0 => {
this_ptr.set_field_slice("length", to_value(0i32));
}
0 => create_array_object(this, Vec::new()),
1 => {
let length_chosen: i32 = from_value(args[0].clone()).unwrap();
this_ptr.set_field_slice("length", to_value(length_chosen));
}
n => {
this_ptr.set_field_slice("length", to_value(n));
for k in 0..n {
let index_str = k.to_string();
this_ptr.set_field(index_str, args[k].clone());
let array = create_array_object(this, Vec::new()).unwrap();
let size: i32 = from_value(args[0].clone()).unwrap();
array.set_field_slice("length", to_value(size));
Ok(array)
}
_ => create_array_object(this, args),
}
}
Ok(this_ptr)
}
/// Get an array's length
@ -34,6 +61,91 @@ pub fn get_array_length(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
Ok(this.get_field_slice("length"))
}
/// 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.
/// https://tc39.es/ecma262/#sec-array.prototype.concat
pub fn concat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
if args.len() == 0 {
// 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: i32 = from_value(this.get_field_slice("length")).unwrap();
for n in 0..this_length {
new_values.push(this.get_field(n.to_string()));
}
for concat_array in args {
let concat_length: i32 = from_value(concat_array.get_field_slice("length")).unwrap();
for n in 0..concat_length {
new_values.push(concat_array.get_field(n.to_string()));
}
}
create_array_object(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.
/// https://tc39.es/ecma262/#sec-array.prototype.push
pub fn push(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let new_array = add_to_array_object(this, args)?;
Ok(new_array.get_field_slice("length"))
}
/// Array.prototype.pop ( )
///
/// The last element of the array is removed from the array and returned.
/// https://tc39.es/ecma262/#sec-array.prototype.pop
pub fn pop(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let curr_length: i32 = from_value(this.get_field_slice("length")).unwrap();
if curr_length < 1 {
return Err(to_value(
"Cannot pop() on an array with zero length".to_string(),
));
}
let pop_index = curr_length - 1;
let pop_value: Value = this.get_field(pop_index.to_string());
this.remove_prop(&pop_index.to_string());
this.set_field_slice("length", to_value(pop_index));
Ok(pop_value)
}
/// 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.
/// https://tc39.es/ecma262/#sec-array.prototype.join
pub fn join(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let separator: String;
if args.len() > 0 {
separator = args[0].to_string();
} else {
separator = ",".to_string();
}
let mut elem_strs: Vec<String> = Vec::new();
let length: i32 = from_value(this.get_field_slice("length")).unwrap();
for n in 0..length {
let elem_str: String = this.get_field(n.to_string()).to_string();
elem_strs.push(elem_str);
}
Ok(to_value(elem_strs.join(&separator)))
}
/// Create a new `Array` object
pub fn _create(global: &Value) -> Value {
let array = to_value(make_array as NativeFunctionData);
@ -47,6 +159,14 @@ pub fn _create(global: &Value) -> Value {
set: Gc::new(ValueData::Undefined),
};
proto.set_prop_slice("length", length);
let concat_func = to_value(concat as NativeFunctionData);
concat_func.set_field_slice("length", to_value(1 as i32));
proto.set_field_slice("concat", concat_func);
let push_func = to_value(push as NativeFunctionData);
push_func.set_field_slice("length", to_value(1 as i32));
proto.set_field_slice("push", push_func);
proto.set_field_slice("pop", to_value(pop as NativeFunctionData));
proto.set_field_slice("join", to_value(join as NativeFunctionData));
array.set_field_slice(PROTOTYPE, proto);
array
}

Loading…
Cancel
Save