1use crate::host::{call_method, is_callable, with_host, JsObj};
15use fusevm::Value;
16use indexmap::IndexMap;
17
18pub const LAZY: &[&str] = &["map", "filter", "take", "drop", "flatMap"];
20
21pub const TERMINAL: &[&str] = &["reduce", "toArray", "forEach", "some", "every", "find"];
23
24pub const METHODS: &[&str] = &[
26 "map",
27 "filter",
28 "take",
29 "drop",
30 "flatMap",
31 "reduce",
32 "toArray",
33 "forEach",
34 "some",
35 "every",
36 "find",
37 "next",
38 "return",
39 "@@iterator",
40];
41
42pub const STATIC_METHODS: &[&str] = &["from"];
44
45pub fn is_helper(name: &str) -> bool {
47 LAZY.contains(&name) || TERMINAL.contains(&name)
48}
49
50fn helper(src: &Value, op: &str, arg: Value) -> Value {
52 with_host(|h| {
53 let mut m = IndexMap::new();
54 m.insert("@@native".into(), h.new_str("IteratorHelper"));
55 m.insert("@@src".into(), src.clone());
56 m.insert("@@op".into(), h.new_str(op));
57 m.insert("@@arg".into(), arg);
58 m.insert("@@count".into(), Value::Float(0.0));
60 m.insert("@@done".into(), Value::Bool(false));
61 h.new_object(m)
62 })
63}
64
65fn slot(recv: &Value, k: &str) -> Option<Value> {
66 with_host(|h| match h.get(recv) {
67 Some(JsObj::Object(p)) => p.get(k).cloned(),
68 _ => None,
69 })
70}
71
72fn set_slot(recv: &Value, k: &str, v: Value) {
73 with_host(|h| {
74 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
75 p.insert(k.to_string(), v);
76 }
77 });
78}
79
80fn step(value: Value, done: bool) -> Value {
82 with_host(|h| {
83 let mut m = IndexMap::new();
84 m.insert("value".into(), value);
85 m.insert("done".into(), Value::Bool(done));
86 h.new_object(m)
87 })
88}
89
90pub fn helper_return(recv: &Value) -> Value {
93 let src = slot(recv, "@@src").unwrap_or(Value::Undef);
94 let already = slot(recv, "@@done").is_some_and(|v| with_host(|h| h.truthy(&v)));
95 set_slot(recv, "@@done", Value::Bool(true));
96 if !already {
97 close(&src);
98 }
99 done_step()
100}
101
102pub fn done_step() -> Value {
104 step(Value::Undef, true)
105}
106
107fn pull(it: &Value) -> Result<(Value, bool), String> {
109 let r = call_method(it, "next", Vec::new())?;
110 let done = crate::builtins::get_property(&r, "done")?;
111 let done = with_host(|h| h.truthy(&done));
112 let value = crate::builtins::get_property(&r, "value")?;
113 Ok((value, done))
114}
115
116fn close(it: &Value) {
119 let f = crate::builtins::get_property(it, "return").unwrap_or(Value::Undef);
123 if with_host(|h| is_callable(h, &f)) {
124 let _ = call_method(it, "return", Vec::new());
125 }
126}
127
128fn limit_arg(args: &[Value]) -> Result<f64, String> {
132 let raw = args.first().cloned().unwrap_or(Value::Undef);
133 let n = with_host(|h| h.to_number(&raw));
134 if n.is_nan() {
135 return Err(crate::host::range_error("NaN must be positive"));
136 }
137 if n < 0.0 {
138 let shown = with_host(|h| h.inspect(&Value::Float(n)));
139 return Err(crate::host::range_error(&format!(
140 "{shown} must be positive"
141 )));
142 }
143 Ok(n.trunc())
144}
145
146fn fn_arg(args: &[Value]) -> Result<Value, String> {
148 let f = args.first().cloned().unwrap_or(Value::Undef);
149 if !with_host(|h| is_callable(h, &f)) {
150 return Err(crate::host::type_error(
151 &crate::host::not_a_function_message(&f),
152 ));
153 }
154 Ok(f)
155}
156
157pub fn call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
159 match method {
160 "map" | "filter" | "flatMap" => Ok(helper(recv, method, fn_arg(args)?)),
161 "take" | "drop" => Ok(helper(recv, method, Value::Float(limit_arg(args)?))),
162 "toArray" => {
163 let mut out = Vec::new();
164 loop {
165 let (v, done) = pull(recv)?;
166 if done {
167 break;
168 }
169 out.push(v);
170 }
171 Ok(with_host(|h| h.new_array(out)))
172 }
173 "forEach" => {
174 let f = fn_arg(args)?;
175 let mut i = 0.0;
176 loop {
177 let (v, done) = pull(recv)?;
178 if done {
179 break;
180 }
181 crate::host::invoke(&f, vec![v, Value::Float(i)], None)?;
182 i += 1.0;
183 }
184 Ok(Value::Undef)
185 }
186 "reduce" => {
187 let f = fn_arg(args)?;
188 let mut acc = args.get(1).cloned();
189 let mut i = 0.0;
190 loop {
191 let (v, done) = pull(recv)?;
192 if done {
193 break;
194 }
195 acc = Some(match acc {
196 None => v,
199 Some(a) => crate::host::invoke(&f, vec![a, v, Value::Float(i)], None)?,
200 });
201 i += 1.0;
202 }
203 acc.ok_or_else(|| {
204 crate::host::type_error("Reduce of a done iterator with no initial value")
205 })
206 }
207 "some" | "every" | "find" => {
208 let f = fn_arg(args)?;
209 let mut i = 0.0;
210 loop {
211 let (v, done) = pull(recv)?;
212 if done {
213 break;
214 }
215 let r = crate::host::invoke(&f, vec![v.clone(), Value::Float(i)], None)?;
216 let hit = with_host(|h| h.truthy(&r));
217 match method {
220 "some" if hit => {
221 close(recv);
222 return Ok(Value::Bool(true));
223 }
224 "every" if !hit => {
225 close(recv);
226 return Ok(Value::Bool(false));
227 }
228 "find" if hit => {
229 close(recv);
230 return Ok(v);
231 }
232 _ => {}
233 }
234 i += 1.0;
235 }
236 Ok(match method {
237 "some" => Value::Bool(false),
238 "every" => Value::Bool(true),
239 _ => Value::Undef,
240 })
241 }
242 _ => Err(crate::host::type_error(&format!(
243 "{method} is not a function"
244 ))),
245 }
246}
247
248pub fn helper_next(recv: &Value) -> Result<Value, String> {
251 if slot(recv, "@@done").is_some_and(|v| with_host(|h| h.truthy(&v))) {
252 return Ok(step(Value::Undef, true));
253 }
254 let src = slot(recv, "@@src").unwrap_or(Value::Undef);
255 let op = slot(recv, "@@op")
256 .map(|v| with_host(|h| h.str_of(&v)))
257 .unwrap_or_default();
258 let arg = slot(recv, "@@arg").unwrap_or(Value::Undef);
259 let finish = || {
260 set_slot(recv, "@@done", Value::Bool(true));
261 step(Value::Undef, true)
262 };
263 match op.as_str() {
264 "take" => {
265 let limit = with_host(|h| h.to_number(&arg));
266 let seen = slot(recv, "@@count")
267 .map(|v| with_host(|h| h.to_number(&v)))
268 .unwrap_or(0.0);
269 if seen >= limit {
270 close(&src);
272 return Ok(finish());
273 }
274 let (v, done) = pull(&src)?;
275 if done {
276 return Ok(finish());
277 }
278 set_slot(recv, "@@count", Value::Float(seen + 1.0));
279 Ok(step(v, false))
280 }
281 "drop" => {
282 let limit = with_host(|h| h.to_number(&arg));
283 let mut dropped = slot(recv, "@@count")
284 .map(|v| with_host(|h| h.to_number(&v)))
285 .unwrap_or(0.0);
286 while dropped < limit {
287 let (_, done) = pull(&src)?;
288 dropped += 1.0;
289 set_slot(recv, "@@count", Value::Float(dropped));
290 if done {
291 return Ok(finish());
292 }
293 }
294 let (v, done) = pull(&src)?;
295 if done {
296 return Ok(finish());
297 }
298 Ok(step(v, false))
299 }
300 "map" => {
301 let (v, done) = pull(&src)?;
302 if done {
303 return Ok(finish());
304 }
305 let i = slot(recv, "@@count")
306 .map(|x| with_host(|h| h.to_number(&x)))
307 .unwrap_or(0.0);
308 set_slot(recv, "@@count", Value::Float(i + 1.0));
309 let out = crate::host::invoke(&arg, vec![v, Value::Float(i)], None)?;
310 Ok(step(out, false))
311 }
312 "filter" => loop {
313 let (v, done) = pull(&src)?;
314 if done {
315 return Ok(finish());
316 }
317 let i = slot(recv, "@@count")
318 .map(|x| with_host(|h| h.to_number(&x)))
319 .unwrap_or(0.0);
320 set_slot(recv, "@@count", Value::Float(i + 1.0));
321 let keep = crate::host::invoke(&arg, vec![v.clone(), Value::Float(i)], None)?;
322 if with_host(|h| h.truthy(&keep)) {
323 return Ok(step(v, false));
324 }
325 },
326 "flatMap" => loop {
327 if let Some(inner) = slot(recv, "@@inner") {
329 if !matches!(inner, Value::Undef) {
330 let (v, done) = pull(&inner)?;
331 if !done {
332 return Ok(step(v, false));
333 }
334 set_slot(recv, "@@inner", Value::Undef);
335 }
336 }
337 let (v, done) = pull(&src)?;
338 if done {
339 return Ok(finish());
340 }
341 let i = slot(recv, "@@count")
342 .map(|x| with_host(|h| h.to_number(&x)))
343 .unwrap_or(0.0);
344 set_slot(recv, "@@count", Value::Float(i + 1.0));
345 let mapped = crate::host::invoke(&arg, vec![v, Value::Float(i)], None)?;
346 let inner = iterator_of(&mapped)?;
347 set_slot(recv, "@@inner", inner);
348 },
349 "wrap" => {
351 let (v, done) = pull(&src)?;
352 if done {
353 return Ok(finish());
354 }
355 Ok(step(v, false))
356 }
357 _ => Ok(finish()),
358 }
359}
360
361fn iterator_of(v: &Value) -> Result<Value, String> {
364 let f = crate::builtins::get_property(v, "@@iterator").unwrap_or(Value::Undef);
368 if with_host(|h| is_callable(h, &f)) {
369 return call_method(v, "@@iterator", Vec::new());
370 }
371 let next = crate::builtins::get_property(v, "next").unwrap_or(Value::Undef);
374 if with_host(|h| is_callable(h, &next)) {
375 return Ok(v.clone());
376 }
377 Err(crate::host::type_error(&format!(
378 "{} is not iterable",
379 with_host(|h| h.inspect(v))
380 )))
381}
382
383pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
385 match method {
386 "from" => Some(
391 iterator_of(&args.first().cloned().unwrap_or(Value::Undef)).map(|it| {
392 if super::native_tag(&it).as_deref() == Some("IteratorHelper")
393 || matches!(
394 with_host(|h| h.kind_of(&it)),
395 Some(crate::host::ObjKind::Generator) | Some(crate::host::ObjKind::Iter)
396 )
397 {
398 it
399 } else {
400 helper(&it, "wrap", Value::Undef)
401 }
402 }),
403 ),
404 _ => None,
405 }
406}