use crate::{
exec::Interpreter,
js::{
function::NativeFunctionData,
object::{ObjectKind, Property, PROTOTYPE},
value::{from_value, to_value, ResultValue, Value, ValueData},
},
};
use gc::Gc;
fn create_array_object(array_obj: &Value, array_contents: &[Value]) -> ResultValue {
let array_obj_ptr = array_obj.clone();
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());
}
array_obj_ptr.set_field_slice("length", to_value(array_contents.len() as i32));
for (n, value) in array_contents.iter().enumerate() {
array_obj_ptr.set_field(n.to_string(), value.clone());
}
Ok(array_obj_ptr)
}
fn add_to_array_object(array_ptr: &Value, add_values: &[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.clone())
}
pub fn make_array(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
this.set_field_slice("length", to_value(0_i32));
this.set_kind(ObjectKind::Array);
match args.len() {
0 => create_array_object(this, &[]),
1 => {
let array = create_array_object(this, &[]).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),
}
}
pub fn get_array_length(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
Ok(this.get_field_slice("length"))
}
pub fn concat(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
if args.is_empty() {
return Ok(this.clone());
}
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)
}
pub fn push(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let new_array = add_to_array_object(this, args)?;
Ok(new_array.get_field_slice("length"))
}
pub fn pop(this: &Value, _: &[Value], _: &Interpreter) -> 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)
}
pub fn join(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let separator = if args.is_empty() {
String::from(",")
} else {
args[0].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)))
}
pub fn reverse(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
let len: i32 = from_value(this.get_field_slice("length")).unwrap();
let middle: i32 = len / 2;
for lower in 0..middle {
let upper = len - lower - 1;
let upper_exists = this.has_field(&upper.to_string());
let lower_exists = this.has_field(&lower.to_string());
let upper_value = this.get_field(&upper.to_string());
let lower_value = this.get_field(&lower.to_string());
if upper_exists && lower_exists {
this.set_field(upper.to_string(), lower_value);
this.set_field(lower.to_string(), upper_value);
} else if upper_exists {
this.set_field(lower.to_string(), upper_value);
this.remove_prop(&upper.to_string());
} else if lower_exists {
this.set_field(upper.to_string(), lower_value);
this.remove_prop(&lower.to_string());
}
}
Ok(this.clone())
}
pub fn shift(this: &Value, _: &[Value], _: &Interpreter) -> ResultValue {
let len: i32 = from_value(this.get_field_slice("length")).unwrap();
if len == 0 {
this.set_field_slice("length", to_value(0_i32));
return Ok(this.get_field(&0.to_string()));
}
let first: Value = this.get_field(&0.to_string());
for k in 1..len {
let from = k.to_string();
let to = (k - 1).to_string();
let from_value = this.get_field(&from);
if from_value == Gc::new(ValueData::Undefined) {
this.remove_prop(&to);
} else {
this.set_field(to, from_value);
}
}
this.remove_prop(&(len - 1).to_string());
this.set_field_slice("length", to_value(len - 1));
Ok(first)
}
pub fn unshift(this: &Value, args: &[Value], _: &Interpreter) -> ResultValue {
let len: i32 = from_value(this.get_field_slice("length")).unwrap();
let arg_c: i32 = args.len() as i32;
if arg_c > 0 {
for k in (1..=len).rev() {
let from = (k - 1).to_string();
let to = (k + arg_c - 1).to_string();
let from_value = this.get_field(&from);
if from_value == Gc::new(ValueData::Undefined) {
this.remove_prop(&to);
} else {
this.set_field(to, from_value);
}
}
for j in 0..arg_c {
this.set_field_slice(&j.to_string(), args[j as usize].clone());
}
}
this.set_field_slice("length", to_value(len + arg_c));
Ok(to_value(len + arg_c))
}
pub fn _create(global: &Value) -> Value {
let array = to_value(make_array as NativeFunctionData);
let proto = ValueData::new_obj(Some(global));
let length = Property {
configurable: false,
enumerable: false,
writable: false,
value: Gc::new(ValueData::Undefined),
get: to_value(get_array_length as NativeFunctionData),
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_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_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));
proto.set_field_slice("reverse", to_value(reverse as NativeFunctionData));
proto.set_field_slice("shift", to_value(shift as NativeFunctionData));
proto.set_field_slice("unshift", to_value(unshift as NativeFunctionData));
array.set_field_slice(PROTOTYPE, proto);
array
}
pub fn init(global: &Value) {
global.set_field_slice("Array", _create(global));
}
#[cfg(test)]
mod tests {
use crate::exec::Executor;
use crate::forward;
#[test]
fn concat() {
let mut engine = Executor::new();
let init = r#"
let empty = new Array();
let one = new Array(1);
"#;
forward(&mut engine, init);
let ee = forward(&mut engine, "empty.concat(empty)");
let en = forward(&mut engine, "empty.concat(one)");
let ne = forward(&mut engine, "one.concat(empty)");
let nn = forward(&mut engine, "one.concat(one)");
}
#[test]
fn join() {
let mut engine = Executor::new();
let init = r#"
let empty = [ ];
let one = ["a"];
let many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
let empty = forward(&mut engine, "empty.join('.')");
assert_eq!(empty, String::from(""));
let one = forward(&mut engine, "one.join('.')");
assert_eq!(one, String::from("a"));
let many = forward(&mut engine, "many.join('.')");
assert_eq!(many, String::from("a.b.c"));
}
}