use std::cmp::{max, min}; use std::rc::Rc; use num_bigint::BigInt; use crate::array_higher_functions::{ array_every::EVERY, array_filter::FILTER, array_find::FIND, array_find_index::FIND_INDEX, array_flat_map::FLAT_MAP, array_map::MAP, array_reduce::REDUCE, array_reduce_right::REDUCE_RIGHT, array_some::SOME, array_sort::SORT, }; use crate::format_err; use crate::helpers::{to_wrapping_index, to_wrapping_index_clamped}; use crate::native_function::NativeFunction; use crate::operations::op_triple_eq_impl; use crate::vs_class::VsClass; use crate::vs_object::VsObject; use crate::vs_value::{LoadFunctionResult, Val, ValTrait, VsType}; #[derive(Clone, Debug)] pub struct VsArray { pub elements: Vec, pub object: VsObject, } impl VsArray { pub fn from(vals: Vec) -> VsArray { return VsArray { elements: vals, object: VsObject { string_map: Default::default(), prototype: Some(Val::Static(&ARRAY_PROTOTYPE)), }, }; } pub fn new() -> VsArray { return VsArray { elements: vec![], object: VsObject { string_map: Default::default(), prototype: Some(Val::Static(&ARRAY_PROTOTYPE)), }, }; } } pub struct ArrayPrototype {} static ARRAY_PROTOTYPE: ArrayPrototype = ArrayPrototype {}; impl ValTrait for ArrayPrototype { fn typeof_(&self) -> VsType { VsType::Object } fn val_to_string(&self) -> String { "".to_string() } fn to_number(&self) -> f64 { 0_f64 } fn to_index(&self) -> Option { None } fn is_primitive(&self) -> bool { false } fn to_primitive(&self) -> Val { Val::String(Rc::new("".to_string())) } fn is_truthy(&self) -> bool { true } fn is_nullish(&self) -> bool { false } fn bind(&self, _params: Vec) -> Option { None } fn as_bigint_data(&self) -> Option { None } fn as_array_data(&self) -> Option> { None } fn as_object_data(&self) -> Option> { None } fn as_class_data(&self) -> Option> { None } fn load_function(&self) -> LoadFunctionResult { LoadFunctionResult::NotAFunction } fn sub(&self, key: Val) -> Result { Ok(Val::Static(match key.val_to_string().as_str() { "at" => &AT, "concat" => &CONCAT, "copyWithin" => ©_WITHIN, "entries" => &ENTRIES, "every" => &EVERY, "fill" => &FILL, "filter" => &FILTER, "find" => &FIND, "findIndex" => &FIND_INDEX, "flat" => &FLAT, "flatMap" => &FLAT_MAP, // forEach: Not included because it cannot work as expected in ValueScript // (Use a for..of loop) "includes" => &INCLUDES, "indexOf" => &INDEX_OF, "join" => &JOIN, "keys" => &KEYS, "lastIndexOf" => &LAST_INDEX_OF, "map" => &MAP, "pop" => &POP, "push" => &PUSH, "reduce" => &REDUCE, "reduceRight" => &REDUCE_RIGHT, "reverse" => &REVERSE, "shift" => &SHIFT, "slice" => &SLICE, "some" => &SOME, "sort" => &SORT, "splice" => &SPLICE, "toLocaleString" => &TO_LOCALE_STRING, "toString" => &TO_STRING, "unshift" => &UNSHIFT, "values" => &VALUES, _ => return Ok(Val::Undefined), })) } fn submov(&mut self, _key: Val, _value: Val) -> Result<(), Val> { format_err!("TypeError: Cannot assign to subscript of Array.prototype") } fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "\x1b[36m[Array Prototype]\x1b[39m") } fn codify(&self) -> String { "Array.prototype".into() } } static AT: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => match to_wrapping_index(params.get(0), array_data.elements.len()) { None => Val::Undefined, Some(i) => array_data.elements[i].clone(), }, _ => return format_err!("array indirection"), }) }, }; static CONCAT: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let mut new_array = array_data.as_ref().clone(); for p in params { match &p.as_array_data() { None => { new_array.elements.push(p); } Some(p_array_data) => { for elem in &p_array_data.elements { new_array.elements.push(elem.clone()); } } } } Val::Array(Rc::new(new_array)) } _ => return format_err!("array indirection"), }) }, }; static COPY_WITHIN: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let array_data_mut = Rc::make_mut(array_data); let ulen = array_data_mut.elements.len(); if ulen > isize::MAX as usize { return format_err!("TODO: array len exceeds isize"); } let mut target = match params.get(0) { None => 0, Some(p) => to_wrapping_index_clamped(p, ulen), }; let mut start = match params.get(1) { None => 0, Some(p) => to_wrapping_index_clamped(p, ulen), }; let ilen = ulen as isize; let mut end = match params.get(2) { None => ilen, // FIXME: undefined -> len (and others in this file) Some(p) => to_wrapping_index_clamped(p, ulen), }; let copy_len = end - start; if copy_len <= 0 { return Ok(this.clone()); } if target <= start || target >= end { while target < ilen && start < end { array_data_mut.elements[target as usize] = array_data_mut.elements[start as usize].clone(); target += 1; start += 1; } } else { // The target is after the start. If we copied from start to target // and worked forwards we'd overwrite the values we needed later. // Instead we simply do the copies in the reverse order. target += copy_len - 1; end -= 1; if target >= ilen { end -= target - ilen + 1; target = ilen - 1; } while target >= 0 && end >= start { array_data_mut.elements[target as usize] = array_data_mut.elements[end as usize].clone(); target -= 1; end -= 1; } } this.clone() } _ => return format_err!("array indirection"), }) }, }; static ENTRIES: NativeFunction = NativeFunction { fn_: |this: &mut Val, _params: Vec| -> Result { match this { Val::Array(_array_data) => return format_err!("TODO: iterators"), _ => return format_err!("array indirection"), }; }, }; static FILL: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let array_data_mut = Rc::make_mut(array_data); let len = array_data_mut.elements.len(); let fill_val = params.get(0).unwrap_or(&Val::Undefined); let start = match params.get(1) { None => 0, Some(v) => to_wrapping_index_clamped(v, len), }; let end = match params.get(2) { None => len as isize, Some(v) => to_wrapping_index_clamped(v, len), }; for i in start..end { array_data_mut.elements[i as usize] = fill_val.clone(); } this.clone() } _ => return format_err!("array indirection"), }) }, }; static FLAT: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { if params.len() > 0 { return format_err!("TODO: .flat depth parameter"); } let mut new_elems = Vec::::new(); for el in &array_data.elements { match &el.as_array_data() { None => { new_elems.push(el.clone()); } Some(p_array_data) => { for elem in &p_array_data.elements { new_elems.push(elem.clone()); } } } } Val::Array(Rc::new(VsArray::from(new_elems))) } _ => return format_err!("array indirection"), }) }, }; static INCLUDES: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); for elem in &array_data.elements { let is_eq = op_triple_eq_impl(elem.clone(), search_param.clone()) .map_err(|e| e.val_to_string()) .unwrap(); // TODO: Exception if is_eq { return Ok(Val::Bool(true)); } } Val::Bool(false) } _ => return format_err!("array indirection"), }) }, }; static INDEX_OF: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); for i in 0..array_data.elements.len() { let is_eq = op_triple_eq_impl(array_data.elements[i].clone(), search_param.clone()) .map_err(|e| e.val_to_string()) .unwrap(); // TODO: Exception if is_eq { return Ok(Val::Number(i as f64)); } } Val::Number(-1.0) } _ => return format_err!("array indirection"), }) }, }; static JOIN: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(vals) => { if vals.elements.len() == 0 { return Ok(Val::String(Rc::new("".to_string()))); } if vals.elements.len() == 1 { return Ok(Val::String(Rc::new(vals.elements[0].val_to_string()))); } let separator = params.get(0).unwrap_or(&Val::Undefined); let separator_str = match separator.typeof_() { VsType::Undefined => ",".to_string(), _ => separator.val_to_string(), }; let mut iter = vals.elements.iter(); let mut res = iter.next().unwrap().val_to_string(); for val in iter { res += &separator_str; match val.typeof_() { VsType::Undefined => {} _ => { res += &val.val_to_string(); } }; } Val::String(Rc::new(res)) } _ => return format_err!("array indirection"), }) }, }; static KEYS: NativeFunction = NativeFunction { fn_: |this: &mut Val, _params: Vec| -> Result { // TODO: Ok(...) match this { Val::Array(_array_data) => { return format_err!("TODO: KEYS"); } _ => return format_err!("array indirection"), }; }, }; static LAST_INDEX_OF: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let search_param = params.get(0).unwrap_or(&Val::Undefined).clone(); for i in (0..array_data.elements.len()).rev() { let is_eq = op_triple_eq_impl(array_data.elements[i].clone(), search_param.clone()) .map_err(|e| e.val_to_string()) .unwrap(); // TODO: Exception if is_eq { return Ok(Val::Number(i as f64)); } } Val::Number(-1_f64) } _ => return format_err!("array indirection"), }) }, }; static POP: NativeFunction = NativeFunction { fn_: |this: &mut Val, _params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { if array_data.elements.len() == 0 { return Ok(Val::Undefined); } let array_data_mut = Rc::make_mut(array_data); let removed_el = array_data_mut .elements .remove(array_data_mut.elements.len() - 1); match removed_el { Val::Void => Val::Undefined, _ => removed_el, } } _ => return format_err!("array indirection"), }) }, }; static PUSH: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let array_data_mut = Rc::make_mut(array_data); for p in params { array_data_mut.elements.push(p); } Val::Number(array_data_mut.elements.len() as f64) } _ => return format_err!("array indirection"), }) }, }; static REVERSE: NativeFunction = NativeFunction { fn_: |this: &mut Val, _params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { if array_data.elements.len() == 0 { // Treating this as an edge case because rust protects us from // underflow when computing last below. return Ok(this.clone()); } let array_data_mut = Rc::make_mut(array_data); let last = array_data_mut.elements.len() - 1; for i in 0..(array_data_mut.elements.len() / 2) { let tmp = array_data_mut.elements[i].clone(); array_data_mut.elements[i] = array_data_mut.elements[last - i].clone(); array_data_mut.elements[last - i] = tmp; } this.clone() } _ => return format_err!("array indirection"), }) }, }; static SHIFT: NativeFunction = NativeFunction { fn_: |this: &mut Val, _params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { if array_data.elements.len() == 0 { return Ok(Val::Undefined); } let array_data_mut = Rc::make_mut(array_data); array_data_mut.elements.remove(0) } _ => return format_err!("array indirection"), }) }, }; static SLICE: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let mut new_elems = Vec::::new(); let start = match params.get(0) { None => 0, Some(v) => to_wrapping_index_clamped(v, array_data.elements.len()), }; let end = match params.get(1) { None => array_data.elements.len() as isize, Some(v) => to_wrapping_index_clamped(v, array_data.elements.len()), }; for i in start..end { new_elems.push(array_data.elements[i as usize].clone()); } Val::Array(Rc::new(VsArray::from(new_elems))) } _ => return format_err!("array indirection"), }) }, }; static SPLICE: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let array_data_mut = Rc::make_mut(array_data); let len = array_data_mut.elements.len(); let start = match params.get(0) { None => 0, Some(v) => to_wrapping_index_clamped(v, len), } as usize; let delete_count_f64 = match params.get(1) { None => len as f64, Some(v) => match v.typeof_() { VsType::Undefined => len as f64, _ => v.to_number(), }, }; let delete_count = match delete_count_f64 < 0_f64 { true => 0, false => min(delete_count_f64.floor() as usize, len - start), }; let mut deleted_elements = Vec::::new(); for i in 0..delete_count { deleted_elements.push(array_data_mut.elements[start + i].clone()); } let insert_len = max(2, params.len()) - 2; let replace_len = min(insert_len, delete_count); if insert_len > replace_len { for i in 0..replace_len { array_data_mut.elements[start + i] = params[i + 2].clone(); } let gap = insert_len - replace_len; for _ in 0..gap { array_data_mut.elements.push(Val::Void); } for i in ((start + replace_len)..len).rev() { array_data_mut.elements[i + gap] = array_data_mut.elements[i].clone(); } for i in replace_len..insert_len { array_data_mut.elements[start + i] = params[i + 2].clone(); } } else { for i in 0..insert_len { array_data_mut.elements[start + i] = params[i + 2].clone(); } let gap = delete_count - insert_len; if gap != 0 { for i in (start + insert_len)..(len - gap) { array_data_mut.elements[i] = array_data_mut.elements[i + gap].clone(); } for _ in 0..gap { array_data_mut.elements.pop(); } } } Val::Array(Rc::new(VsArray::from(deleted_elements))) } _ => return format_err!("array indirection"), }) }, }; static TO_LOCALE_STRING: NativeFunction = NativeFunction { fn_: |this: &mut Val, _params: Vec| -> Result { // TODO: Ok(...) match this { Val::Array(_array_data) => { return format_err!("TODO: TO_LOCALE_STRING"); } _ => return format_err!("array indirection"), }; }, }; // TODO: Share this? (JS doesn't?) static TO_STRING: NativeFunction = NativeFunction { fn_: |this: &mut Val, _params: Vec| -> Result { Ok(Val::String(Rc::new(this.val_to_string()))) }, }; static UNSHIFT: NativeFunction = NativeFunction { fn_: |this: &mut Val, params: Vec| -> Result { Ok(match this { Val::Array(array_data) => { let array_data_mut = Rc::make_mut(array_data); let mut i = 0; for p in params { array_data_mut.elements.insert(i, p); i += 1; } Val::Number(array_data_mut.elements.len() as f64) } _ => return format_err!("array indirection"), }) }, }; static VALUES: NativeFunction = NativeFunction { fn_: |this: &mut Val, _params: Vec| -> Result { // TODO: Ok(...) match this { Val::Array(_array_data) => { return format_err!("TODO: VALUES"); } _ => return format_err!("array indirection"), }; }, };