use crate::host::{call_method, is_callable, with_host, JsObj};
use fusevm::Value;
use indexmap::IndexMap;
pub const LAZY: &[&str] = &["map", "filter", "take", "drop", "flatMap"];
pub const TERMINAL: &[&str] = &["reduce", "toArray", "forEach", "some", "every", "find"];
pub const METHODS: &[&str] = &[
"map",
"filter",
"take",
"drop",
"flatMap",
"reduce",
"toArray",
"forEach",
"some",
"every",
"find",
"next",
"return",
"@@iterator",
];
pub const STATIC_METHODS: &[&str] = &["from"];
pub fn is_helper(name: &str) -> bool {
LAZY.contains(&name) || TERMINAL.contains(&name)
}
fn helper(src: &Value, op: &str, arg: Value) -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str("IteratorHelper"));
m.insert("@@src".into(), src.clone());
m.insert("@@op".into(), h.new_str(op));
m.insert("@@arg".into(), arg);
m.insert("@@count".into(), Value::Float(0.0));
m.insert("@@done".into(), Value::Bool(false));
h.new_object(m)
})
}
fn slot(recv: &Value, k: &str) -> Option<Value> {
with_host(|h| match h.get(recv) {
Some(JsObj::Object(p)) => p.get(k).cloned(),
_ => None,
})
}
fn set_slot(recv: &Value, k: &str, v: Value) {
with_host(|h| {
if let Some(JsObj::Object(p)) = h.get_mut(recv) {
p.insert(k.to_string(), v);
}
});
}
fn step(value: Value, done: bool) -> Value {
with_host(|h| {
let mut m = IndexMap::new();
m.insert("value".into(), value);
m.insert("done".into(), Value::Bool(done));
h.new_object(m)
})
}
pub fn helper_return(recv: &Value) -> Value {
let src = slot(recv, "@@src").unwrap_or(Value::Undef);
let already = slot(recv, "@@done").is_some_and(|v| with_host(|h| h.truthy(&v)));
set_slot(recv, "@@done", Value::Bool(true));
if !already {
close(&src);
}
done_step()
}
pub fn done_step() -> Value {
step(Value::Undef, true)
}
fn pull(it: &Value) -> Result<(Value, bool), String> {
let r = call_method(it, "next", Vec::new())?;
let done = crate::builtins::get_property(&r, "done")?;
let done = with_host(|h| h.truthy(&done));
let value = crate::builtins::get_property(&r, "value")?;
Ok((value, done))
}
fn close(it: &Value) {
let f = crate::builtins::get_property(it, "return").unwrap_or(Value::Undef);
if with_host(|h| is_callable(h, &f)) {
let _ = call_method(it, "return", Vec::new());
}
}
fn limit_arg(args: &[Value]) -> Result<f64, String> {
let raw = args.first().cloned().unwrap_or(Value::Undef);
let n = with_host(|h| h.to_number(&raw));
if n.is_nan() {
return Err(crate::host::range_error("NaN must be positive"));
}
if n < 0.0 {
let shown = with_host(|h| h.inspect(&Value::Float(n)));
return Err(crate::host::range_error(&format!(
"{shown} must be positive"
)));
}
Ok(n.trunc())
}
fn fn_arg(args: &[Value]) -> Result<Value, String> {
let f = args.first().cloned().unwrap_or(Value::Undef);
if !with_host(|h| is_callable(h, &f)) {
return Err(crate::host::type_error(
&crate::host::not_a_function_message(&f),
));
}
Ok(f)
}
pub fn call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
match method {
"map" | "filter" | "flatMap" => Ok(helper(recv, method, fn_arg(args)?)),
"take" | "drop" => Ok(helper(recv, method, Value::Float(limit_arg(args)?))),
"toArray" => {
let mut out = Vec::new();
loop {
let (v, done) = pull(recv)?;
if done {
break;
}
out.push(v);
}
Ok(with_host(|h| h.new_array(out)))
}
"forEach" => {
let f = fn_arg(args)?;
let mut i = 0.0;
loop {
let (v, done) = pull(recv)?;
if done {
break;
}
crate::host::invoke(&f, vec![v, Value::Float(i)], None)?;
i += 1.0;
}
Ok(Value::Undef)
}
"reduce" => {
let f = fn_arg(args)?;
let mut acc = args.get(1).cloned();
let mut i = 0.0;
loop {
let (v, done) = pull(recv)?;
if done {
break;
}
acc = Some(match acc {
None => v,
Some(a) => crate::host::invoke(&f, vec![a, v, Value::Float(i)], None)?,
});
i += 1.0;
}
acc.ok_or_else(|| {
crate::host::type_error("Reduce of a done iterator with no initial value")
})
}
"some" | "every" | "find" => {
let f = fn_arg(args)?;
let mut i = 0.0;
loop {
let (v, done) = pull(recv)?;
if done {
break;
}
let r = crate::host::invoke(&f, vec![v.clone(), Value::Float(i)], None)?;
let hit = with_host(|h| h.truthy(&r));
match method {
"some" if hit => {
close(recv);
return Ok(Value::Bool(true));
}
"every" if !hit => {
close(recv);
return Ok(Value::Bool(false));
}
"find" if hit => {
close(recv);
return Ok(v);
}
_ => {}
}
i += 1.0;
}
Ok(match method {
"some" => Value::Bool(false),
"every" => Value::Bool(true),
_ => Value::Undef,
})
}
_ => Err(crate::host::type_error(&format!(
"{method} is not a function"
))),
}
}
pub fn helper_next(recv: &Value) -> Result<Value, String> {
if slot(recv, "@@done").is_some_and(|v| with_host(|h| h.truthy(&v))) {
return Ok(step(Value::Undef, true));
}
let src = slot(recv, "@@src").unwrap_or(Value::Undef);
let op = slot(recv, "@@op")
.map(|v| with_host(|h| h.str_of(&v)))
.unwrap_or_default();
let arg = slot(recv, "@@arg").unwrap_or(Value::Undef);
let finish = || {
set_slot(recv, "@@done", Value::Bool(true));
step(Value::Undef, true)
};
match op.as_str() {
"take" => {
let limit = with_host(|h| h.to_number(&arg));
let seen = slot(recv, "@@count")
.map(|v| with_host(|h| h.to_number(&v)))
.unwrap_or(0.0);
if seen >= limit {
close(&src);
return Ok(finish());
}
let (v, done) = pull(&src)?;
if done {
return Ok(finish());
}
set_slot(recv, "@@count", Value::Float(seen + 1.0));
Ok(step(v, false))
}
"drop" => {
let limit = with_host(|h| h.to_number(&arg));
let mut dropped = slot(recv, "@@count")
.map(|v| with_host(|h| h.to_number(&v)))
.unwrap_or(0.0);
while dropped < limit {
let (_, done) = pull(&src)?;
dropped += 1.0;
set_slot(recv, "@@count", Value::Float(dropped));
if done {
return Ok(finish());
}
}
let (v, done) = pull(&src)?;
if done {
return Ok(finish());
}
Ok(step(v, false))
}
"map" => {
let (v, done) = pull(&src)?;
if done {
return Ok(finish());
}
let i = slot(recv, "@@count")
.map(|x| with_host(|h| h.to_number(&x)))
.unwrap_or(0.0);
set_slot(recv, "@@count", Value::Float(i + 1.0));
let out = crate::host::invoke(&arg, vec![v, Value::Float(i)], None)?;
Ok(step(out, false))
}
"filter" => loop {
let (v, done) = pull(&src)?;
if done {
return Ok(finish());
}
let i = slot(recv, "@@count")
.map(|x| with_host(|h| h.to_number(&x)))
.unwrap_or(0.0);
set_slot(recv, "@@count", Value::Float(i + 1.0));
let keep = crate::host::invoke(&arg, vec![v.clone(), Value::Float(i)], None)?;
if with_host(|h| h.truthy(&keep)) {
return Ok(step(v, false));
}
},
"flatMap" => loop {
if let Some(inner) = slot(recv, "@@inner") {
if !matches!(inner, Value::Undef) {
let (v, done) = pull(&inner)?;
if !done {
return Ok(step(v, false));
}
set_slot(recv, "@@inner", Value::Undef);
}
}
let (v, done) = pull(&src)?;
if done {
return Ok(finish());
}
let i = slot(recv, "@@count")
.map(|x| with_host(|h| h.to_number(&x)))
.unwrap_or(0.0);
set_slot(recv, "@@count", Value::Float(i + 1.0));
let mapped = crate::host::invoke(&arg, vec![v, Value::Float(i)], None)?;
let inner = iterator_of(&mapped)?;
set_slot(recv, "@@inner", inner);
},
"wrap" => {
let (v, done) = pull(&src)?;
if done {
return Ok(finish());
}
Ok(step(v, false))
}
_ => Ok(finish()),
}
}
fn iterator_of(v: &Value) -> Result<Value, String> {
let f = crate::builtins::get_property(v, "@@iterator").unwrap_or(Value::Undef);
if with_host(|h| is_callable(h, &f)) {
return call_method(v, "@@iterator", Vec::new());
}
let next = crate::builtins::get_property(v, "next").unwrap_or(Value::Undef);
if with_host(|h| is_callable(h, &next)) {
return Ok(v.clone());
}
Err(crate::host::type_error(&format!(
"{} is not iterable",
with_host(|h| h.inspect(v))
)))
}
pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
match method {
"from" => Some(
iterator_of(&args.first().cloned().unwrap_or(Value::Undef)).map(|it| {
if super::native_tag(&it).as_deref() == Some("IteratorHelper")
|| matches!(
with_host(|h| h.kind_of(&it)),
Some(crate::host::ObjKind::Generator) | Some(crate::host::ObjKind::Iter)
)
{
it
} else {
helper(&it, "wrap", Value::Undef)
}
}),
),
_ => None,
}
}