use std::collections::BTreeMap;
use boa_cat::Value;
use boa_cat::fuel::Fuel;
use boa_cat::heap::Heap;
use boa_cat::outcome::{EvalResult, Outcome};
use boa_cat::value::Object;
use crate::coercion::first_arg;
#[must_use]
pub fn build(heap: Heap) -> (Value, Heap) {
let mut props = BTreeMap::new();
let _ = props.insert("keys".to_owned(), Value::Native(keys_impl));
let _ = props.insert("values".to_owned(), Value::Native(values_impl));
let _ = props.insert("entries".to_owned(), Value::Native(entries_impl));
let _ = props.insert("assign".to_owned(), Value::Native(assign_impl));
let (id, heap) = heap.alloc_object(Object::from_properties(props));
(Value::Object(id), heap)
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn keys_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let arg = first_arg(&args);
let entries = collect_entries(&arg, &heap);
let keys: Vec<Value> = entries
.into_iter()
.map(|(k, _v)| Value::String(k))
.collect();
let (value, heap) = build_array_object(keys, heap);
Ok((Outcome::Normal(value), heap, fuel))
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn values_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let arg = first_arg(&args);
let entries = collect_entries(&arg, &heap);
let values: Vec<Value> = entries.into_iter().map(|(_k, v)| v).collect();
let (value, heap) = build_array_object(values, heap);
Ok((Outcome::Normal(value), heap, fuel))
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn entries_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let arg = first_arg(&args);
let kv = collect_entries(&arg, &heap);
let (entry_values, heap) = kv
.into_iter()
.fold((Vec::new(), heap), |(acc, heap), (k, v)| {
let (pair, heap) = build_array_object(vec![Value::String(k), v], heap);
let extended: Vec<Value> = acc.into_iter().chain(std::iter::once(pair)).collect();
(extended, heap)
});
let (value, heap) = build_array_object(entry_values, heap);
Ok((Outcome::Normal(value), heap, fuel))
}
#[allow(clippy::needless_pass_by_value, clippy::unnecessary_wraps)]
fn assign_impl(args: Vec<Value>, _this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let target = first_arg(&args);
let sources: Vec<Value> = args.iter().skip(1).cloned().collect();
if let Some(id) = object_id_of(&target) {
let heap = sources
.into_iter()
.fold(heap, |heap, source| merge_source_into(id, &source, heap));
Ok((Outcome::Normal(target), heap, fuel))
} else {
Ok((Outcome::Normal(target), heap, fuel))
}
}
fn merge_source_into(target_id: boa_cat::value::ObjectId, source: &Value, heap: Heap) -> Heap {
let source_id_opt = object_id_of(source);
source_id_opt.map_or(heap.clone(), |source_id| {
let source_obj = heap.object(source_id).cloned();
let target_obj = heap.object(target_id).cloned();
source_obj
.zip(target_obj)
.map_or(heap.clone(), |(src, tgt)| {
let merged_data = src
.properties()
.iter()
.fold(tgt, |obj, (k, v)| obj.with(k.clone(), v.clone()));
let merged_full = src.accessors().iter().fold(merged_data, |obj, (k, pair)| {
obj.with_accessor(k.clone(), pair.clone())
});
heap.store_object(target_id, merged_full)
.unwrap_or_else(|h| h)
})
})
}
fn collect_entries(value: &Value, heap: &Heap) -> Vec<(String, Value)> {
let id_opt = object_id_of(value);
id_opt
.and_then(|id| heap.object(id))
.map(|obj| {
obj.properties()
.iter()
.filter(|(k, _)| !is_array_length_key(obj, k))
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
})
.unwrap_or_default()
}
fn is_array_length_key(obj: &Object, key: &str) -> bool {
key == "length" && looks_like_array(obj)
}
fn looks_like_array(obj: &Object) -> bool {
obj.get("length")
.is_some_and(|v| matches!(v, Value::Number(_)))
&& obj
.properties()
.keys()
.all(|k| k == "length" || k.parse::<u32>().is_ok())
}
fn object_id_of(value: &Value) -> Option<boa_cat::value::ObjectId> {
match value {
Value::Object(id) => Some(*id),
Value::Undefined
| Value::Null
| Value::Boolean(_)
| Value::Number(_)
| Value::String(_)
| Value::Function(_)
| Value::Native(_)
| Value::Promise(_) => None,
}
}
fn build_array_object(values: Vec<Value>, heap: Heap) -> (Value, Heap) {
let length = u32::try_from(values.len()).unwrap_or(u32::MAX);
let map: BTreeMap<String, Value> = values
.into_iter()
.enumerate()
.map(|(i, v)| (format!("{i}"), v))
.chain(std::iter::once((
"length".to_owned(),
Value::Number(f64::from(length)),
)))
.collect();
let (id, heap) = heap.alloc_object(Object::from_properties(map));
(Value::Object(id), heap)
}