pub mod array_iterator;
#[cfg(test)]
mod tests;
use crate::{
builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator},
builtins::BuiltIn,
gc::GcObject,
object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE},
property::{Attribute, DataDescriptor},
value::{same_value_zero, IntegerOrInfinity, Value},
BoaProfiler, Context, Result,
};
use num_traits::*;
use std::{
cmp::{max, min},
convert::{TryFrom, TryInto},
};
#[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::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
)
.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_method(Self::is_array, "isArray", 1)
.build();
(Self::NAME, array.into(), Self::attribute())
}
}
impl Array {
const LENGTH: usize = 1;
fn constructor(new_target: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().array_object().prototype());
match args.len() {
0 => Array::construct_array_empty(prototype, context),
1 => Array::construct_array_length(prototype, &args[0], context),
_ => Array::construct_array_values(prototype, args, context),
}
}
fn construct_array_empty(proto: GcObject, context: &mut Context) -> Result<Value> {
Array::array_create(0, Some(proto), context)
}
fn construct_array_length(
prototype: GcObject,
length: &Value,
context: &mut Context,
) -> Result<Value> {
let array = Array::array_create(0, Some(prototype), context)?;
if !length.is_number() {
array.set_property(0, DataDescriptor::new(length, Attribute::all()));
array.set_field("length", 1, context)?;
} else {
if length.is_double() {
return context.throw_range_error("Invalid array length");
}
array.set_field("length", length.to_u32(context).unwrap(), context)?;
}
Ok(array)
}
fn construct_array_values(
prototype: GcObject,
items: &[Value],
context: &mut Context,
) -> Result<Value> {
let items_len = items.len().try_into().map_err(interror_to_value)?;
let array = Array::array_create(items_len, Some(prototype), context)?;
for (k, item) in items.iter().enumerate() {
array.set_property(k, DataDescriptor::new(item.clone(), Attribute::all()));
}
Ok(array)
}
fn array_create(
length: u32,
prototype: Option<GcObject>,
context: &mut Context,
) -> Result<Value> {
let prototype = match prototype {
Some(prototype) => prototype,
None => context.standard_objects().array_object().prototype(),
};
let array = Value::new_object(context);
array
.as_object()
.expect("'array' should be an object")
.set_prototype_instance(prototype.into());
array.set_data(ObjectData::Array);
let length = DataDescriptor::new(
length,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
array.set_property("length", length);
Ok(array)
}
pub(crate) fn new_array(context: &Context) -> Result<Value> {
let array = Value::new_object(context);
array.set_data(ObjectData::Array);
array
.as_object()
.expect("'array' should be an object")
.set_prototype_instance(context.standard_objects().array_object().prototype().into());
let length = DataDescriptor::new(
Value::from(0),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
array.set_property("length", length);
Ok(array)
}
pub(crate) fn construct_array(
array_obj: &Value,
array_contents: &[Value],
context: &mut Context,
) -> Result<Value> {
let array_obj_ptr = array_obj.clone();
let orig_length = array_obj.get_field("length", context)?.to_length(context)?;
for n in 0..orig_length {
array_obj_ptr.remove_property(n);
}
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_property(n, DataDescriptor::new(value, Attribute::all()));
}
Ok(array_obj_ptr)
}
pub(crate) fn add_to_array_object(
array_ptr: &Value,
add_values: &[Value],
context: &mut Context,
) -> Result<Value> {
let orig_length = array_ptr.get_field("length", context)?.to_length(context)?;
for (n, value) in add_values.iter().enumerate() {
let new_index = orig_length.wrapping_add(n);
array_ptr.set_property(new_index, DataDescriptor::new(value, Attribute::all()));
}
array_ptr.set_field(
"length",
Value::from(orig_length.wrapping_add(add_values.len())),
context,
)?;
Ok(array_ptr.clone())
}
pub(crate) fn is_array(_: &Value, args: &[Value], _: &mut Context) -> Result<Value> {
match args.get(0).and_then(|x| x.as_object()) {
Some(object) => Ok(Value::from(object.borrow().is_array())),
None => Ok(Value::from(false)),
}
}
pub(crate) fn concat(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
if args.is_empty() {
return Ok(this.clone());
}
let mut new_values: Vec<Value> = Vec::new();
let this_length = this.get_field("length", context)?.to_length(context)?;
for n in 0..this_length {
new_values.push(this.get_field(n, context)?);
}
for concat_array in args {
let concat_length = concat_array
.get_field("length", context)?
.to_length(context)?;
for n in 0..concat_length {
new_values.push(concat_array.get_field(n, context)?);
}
}
Self::construct_array(this, &new_values, context)
}
pub(crate) fn push(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let new_array = Self::add_to_array_object(this, args, context)?;
Ok(new_array.get_field("length", context)?)
}
pub(crate) fn pop(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
let curr_length = this.get_field("length", context)?.to_length(context)?;
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(), context)?;
this.remove_property(pop_index);
this.set_field("length", Value::from(pop_index), context)?;
Ok(pop_value)
}
pub(crate) fn for_each(this: &Value, args: &[Value], context: &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", context)?.to_length(context)?;
for i in 0..length {
let element = this.get_field(i, context)?;
let arguments = [element, Value::from(i), this.clone()];
context.call(callback_arg, &this_arg, &arguments)?;
}
Ok(Value::undefined())
}
pub(crate) fn join(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let separator = if args.is_empty() {
String::from(",")
} else {
args.get(0)
.expect("Could not get argument")
.to_string(context)?
.to_string()
};
let mut elem_strs = Vec::new();
let length = this.get_field("length", context)?.to_length(context)?;
for n in 0..length {
let elem_str = this.get_field(n, context)?.to_string(context)?.to_string();
elem_strs.push(elem_str);
}
Ok(Value::from(elem_strs.join(&separator)))
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
let method_name = "join";
let mut arguments = vec![Value::from(",")];
let mut method = this.get_field(method_name, context)?;
if !method.is_function() {
let object_prototype: Value = context
.standard_objects()
.object_object()
.prototype()
.into();
method = object_prototype.get_field("toString", context)?;
arguments = Vec::new();
}
let join = context.call(&method, this, &arguments)?;
let string = if let Value::String(ref s) = join {
Value::from(s.as_str())
} else {
Value::from("")
};
Ok(string)
}
#[allow(clippy::else_if_without_else)]
pub(crate) fn reverse(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
let len = this.get_field("length", context)?.to_length(context)?;
let middle = 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, context)?;
let lower_value = this.get_field(lower, context)?;
if upper_exists && lower_exists {
this.set_property(upper, DataDescriptor::new(lower_value, Attribute::all()));
this.set_property(lower, DataDescriptor::new(upper_value, Attribute::all()));
} else if upper_exists {
this.set_property(lower, DataDescriptor::new(upper_value, Attribute::all()));
this.remove_property(upper);
} else if lower_exists {
this.set_property(upper, DataDescriptor::new(lower_value, Attribute::all()));
this.remove_property(lower);
}
}
Ok(this.clone())
}
pub(crate) fn shift(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
let len = this.get_field("length", context)?.to_length(context)?;
if len == 0 {
this.set_field("length", 0, context)?;
return Ok(Value::undefined());
}
let first: Value = this.get_field(0, context)?;
for k in 1..len {
let from = k;
let to = k.wrapping_sub(1);
let from_value = this.get_field(from, context)?;
if from_value.is_undefined() {
this.remove_property(to);
} else {
this.set_property(to, DataDescriptor::new(from_value, Attribute::all()));
}
}
let final_index = len.wrapping_sub(1);
this.remove_property(final_index);
this.set_field("length", Value::from(final_index), context)?;
Ok(first)
}
pub(crate) fn unshift(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let len = this.get_field("length", context)?.to_length(context)?;
let arg_c = args.len();
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, context)?;
if from_value.is_undefined() {
this.remove_property(to);
} else {
this.set_property(to, DataDescriptor::new(from_value, Attribute::all()));
}
}
for j in 0..arg_c {
this.set_property(
j,
DataDescriptor::new(
args.get(j).expect("Could not get argument").clone(),
Attribute::all(),
),
);
}
}
let temp = len.wrapping_add(arg_c);
this.set_field("length", Value::from(temp), context)?;
Ok(Value::from(temp))
}
pub(crate) fn every(this: &Value, args: &[Value], context: &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", context)?.to_length(context)?;
let mut len = max_len;
while i < len {
let element = this.get_field(i, context)?;
let arguments = [element, Value::from(i), this.clone()];
let result = context.call(callback, &this_arg, &arguments)?;
if !result.to_boolean() {
return Ok(Value::from(false));
}
len = min(
max_len,
this.get_field("length", context)?.to_length(context)?,
);
i += 1;
}
Ok(Value::from(true))
}
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", context)?.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 = (0..length)
.map(|idx| {
let element = this.get_field(idx, context)?;
let args = [element, Value::from(idx), new.clone()];
context.call(&callback, &this_val, &args)
})
.collect::<Result<Vec<Value>>>()?;
Self::construct_array(&new, &values, context)
}
pub(crate) fn index_of(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
if args.is_empty() {
return Ok(Value::from(-1));
}
let search_element = args[0].clone();
let len = this.get_field("length", context)?.to_length(context)?;
let mut idx = match args.get(1) {
Some(from_idx_ptr) => {
let from_idx = from_idx_ptr.to_number(context)?;
if !from_idx.is_finite() {
return Ok(Value::from(-1));
} else if from_idx < 0.0 {
let k =
isize::try_from(len).map_err(interror_to_value)? + f64_to_isize(from_idx)?;
usize::try_from(max(0, k)).map_err(interror_to_value)?
} else {
f64_to_usize(from_idx)?
}
}
None => 0,
};
while idx < len {
let check_element = this.get_field(idx, context)?.clone();
if check_element.strict_equals(&search_element) {
return Ok(Value::from(idx));
}
idx += 1;
}
Ok(Value::from(-1))
}
pub(crate) fn last_index_of(
this: &Value,
args: &[Value],
context: &mut Context,
) -> Result<Value> {
if args.is_empty() {
return Ok(Value::from(-1));
}
let search_element = args[0].clone();
let len: isize = this
.get_field("length", context)?
.to_length(context)?
.try_into()
.map_err(interror_to_value)?;
let mut idx = match args.get(1) {
Some(from_idx_ptr) => {
let from_idx = from_idx_ptr.to_integer(context)?;
if !from_idx.is_finite() {
return Ok(Value::from(-1));
} else if from_idx < 0.0 {
len + f64_to_isize(from_idx)?
} else {
min(f64_to_isize(from_idx)?, len - 1)
}
}
None => len - 1,
};
while idx >= 0 {
let check_element = this.get_field(idx, context)?.clone();
if check_element.strict_equals(&search_element) {
return Ok(Value::from(i32::try_from(idx).map_err(interror_to_value)?));
}
idx -= 1;
}
Ok(Value::from(-1))
}
pub(crate) fn find(this: &Value, args: &[Value], context: &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", context)?.to_length(context)?;
for i in 0..len {
let element = this.get_field(i, context)?;
let arguments = [element.clone(), Value::from(i), this.clone()];
let result = context.call(callback, &this_arg, &arguments)?;
if result.to_boolean() {
return Ok(element);
}
}
Ok(Value::undefined())
}
pub(crate) fn find_index(this: &Value, args: &[Value], context: &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", context)?.to_length(context)?;
for i in 0..length {
let element = this.get_field(i, context)?;
let arguments = [element, Value::from(i), this.clone()];
let result = context.call(predicate_arg, &this_arg, &arguments)?;
if result.to_boolean() {
let result = i32::try_from(i).map_err(interror_to_value)?;
return Ok(Value::integer(result));
}
}
Ok(Value::integer(-1))
}
pub(crate) fn fill(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let len = this.get_field("length", context)?.to_length(context)?;
let default_value = Value::undefined();
let value = args.get(0).unwrap_or(&default_value);
let start = Self::get_relative_start(context, args.get(1), len)?;
let fin = Self::get_relative_end(context, args.get(2), len)?;
for i in start..fin {
this.set_property(i, DataDescriptor::new(value.clone(), Attribute::all()));
}
Ok(this.clone())
}
pub(crate) fn includes_value(
this: &Value,
args: &[Value],
context: &mut Context,
) -> Result<Value> {
let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined);
let length = this.get_field("length", context)?.to_length(context)?;
for idx in 0..length {
let check_element = this.get_field(idx, context)?.clone();
if same_value_zero(&check_element, &search_element) {
return Ok(Value::from(true));
}
}
Ok(Value::from(false))
}
pub(crate) fn slice(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let new_array = Self::new_array(context)?;
let len = this.get_field("length", context)?.to_length(context)?;
let from = Self::get_relative_start(context, args.get(0), len)?;
let to = Self::get_relative_end(context, args.get(1), len)?;
let span = max(to.saturating_sub(from), 0);
if span > 2usize.pow(32) - 1 {
return context.throw_range_error("Invalid array length");
}
let mut new_array_len: i32 = 0;
for i in from..from.saturating_add(span) {
new_array.set_property(
new_array_len,
DataDescriptor::new(this.get_field(i, context)?, Attribute::all()),
);
new_array_len = new_array_len.saturating_add(1);
}
new_array.set_field("length", Value::from(new_array_len), context)?;
Ok(new_array)
}
pub(crate) fn filter(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.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", context)?.to_length(context)?;
let new = Self::new_array(context)?;
let values = (0..length)
.map(|idx| {
let element = this.get_field(idx, context)?;
let args = [element.clone(), Value::from(idx), new.clone()];
let callback_result = context.call(&callback, &this_val, &args)?;
if callback_result.to_boolean() {
Ok(Some(element))
} else {
Ok(None)
}
})
.collect::<Result<Vec<Option<Value>>>>()?;
let values = values.into_iter().filter_map(|v| v).collect::<Vec<_>>();
Self::construct_array(&new, &values, context)
}
pub(crate) fn some(this: &Value, args: &[Value], context: &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", context)?.to_length(context)?;
let mut len = max_len;
while i < len {
let element = this.get_field(i, context)?;
let arguments = [element, Value::from(i), this.clone()];
let result = context.call(callback, &this_arg, &arguments)?;
if result.to_boolean() {
return Ok(Value::from(true));
}
len = min(
max_len,
this.get_field("length", context)?.to_length(context)?,
);
i += 1;
}
Ok(Value::from(false))
}
pub(crate) fn reduce(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let this: Value = this.to_object(context)?.into();
let callback = match args.get(0) {
Some(value) if value.is_function() => value,
_ => return context.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", context)?.to_length(context)?;
if length == 0 && initial_value.is_undefined() {
return context
.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 context.throw_type_error(
"Reduce was called on an empty array and with no initial value",
);
}
let result = this.get_field(k, context)?;
k += 1;
result
} else {
initial_value
};
while k < length {
if this.has_field(k) {
let arguments = [
accumulator,
this.get_field(k, context)?,
Value::from(k),
this.clone(),
];
accumulator = context.call(&callback, &Value::undefined(), &arguments)?;
length = min(
length,
this.get_field("length", context)?.to_length(context)?,
);
}
k += 1;
}
Ok(accumulator)
}
pub(crate) fn reduce_right(
this: &Value,
args: &[Value],
context: &mut Context,
) -> Result<Value> {
let this: Value = this.to_object(context)?.into();
let callback = match args.get(0) {
Some(value) if value.is_function() => value,
_ => return context.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", context)?.to_length(context)?;
if length == 0 {
return if initial_value.is_undefined() {
context.throw_type_error(
"reduceRight was called on an empty array and with no initial value",
)
} else {
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;
}
if k == 0 {
break;
}
k -= 1;
}
if !k_present {
return context.throw_type_error(
"reduceRight was called on an empty array and with no initial value",
);
}
let result = this.get_field(k, context)?;
k = k.overflowing_sub(1).0;
result
} else {
initial_value
};
while k != usize::MAX {
if this.has_field(k) {
let arguments = [
accumulator,
this.get_field(k, context)?,
Value::from(k),
this.clone(),
];
accumulator = context.call(&callback, &Value::undefined(), &arguments)?;
length = min(
length,
this.get_field("length", context)?.to_length(context)?,
);
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)
}
pub(crate) fn values(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
ArrayIterator::create_array_iterator(context, this.clone(), ArrayIterationKind::Value)
}
pub(crate) fn keys(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
ArrayIterator::create_array_iterator(context, this.clone(), ArrayIterationKind::Key)
}
pub(crate) fn entries(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
ArrayIterator::create_array_iterator(context, this.clone(), ArrayIterationKind::KeyAndValue)
}
pub(super) fn get_relative_start(
context: &mut Context,
arg: Option<&Value>,
len: usize,
) -> Result<usize> {
let default_value = Value::undefined();
let relative_start = arg
.unwrap_or(&default_value)
.to_integer_or_infinity(context)?;
match relative_start {
IntegerOrInfinity::NegativeInfinity => Ok(0),
IntegerOrInfinity::Integer(i) if i < 0 => Self::offset(len as u64, i)
.try_into()
.map_err(interror_to_value),
IntegerOrInfinity::Integer(i) => (i as u64)
.min(len as u64)
.try_into()
.map_err(interror_to_value),
IntegerOrInfinity::PositiveInfinity => Ok(len),
}
}
pub(super) fn get_relative_end(
context: &mut Context,
arg: Option<&Value>,
len: usize,
) -> Result<usize> {
let default_value = Value::undefined();
let value = arg.unwrap_or(&default_value);
if value.is_undefined() {
Ok(len)
} else {
let relative_end = value.to_integer_or_infinity(context)?;
match relative_end {
IntegerOrInfinity::NegativeInfinity => Ok(0),
IntegerOrInfinity::Integer(i) if i < 0 => Self::offset(len as u64, i)
.try_into()
.map_err(interror_to_value),
IntegerOrInfinity::Integer(i) => (i as u64)
.min(len as u64)
.try_into()
.map_err(interror_to_value),
IntegerOrInfinity::PositiveInfinity => Ok(len),
}
}
}
fn offset(len: u64, i: i64) -> u64 {
debug_assert!(i < 0 && i != i64::MIN);
len.saturating_sub(i.saturating_neg() as u64)
}
}
fn f64_to_isize(v: f64) -> Result<isize> {
v.to_isize()
.ok_or_else(|| Value::string("cannot convert f64 to isize - out of range"))
}
fn f64_to_usize(v: f64) -> Result<usize> {
v.to_usize()
.ok_or_else(|| Value::string("cannot convert f64 to usize - out of range"))
}
fn interror_to_value(err: std::num::TryFromIntError) -> Value {
Value::string(format!("{}", err))
}