Skip to main content

graphix_package_list/
lib.rs

1#![doc(
2    html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
3    html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
4)]
5use anyhow::{bail, Result};
6use arcstr::literal;
7use compact_str::format_compact;
8use graphix_compiler::{
9    expr::ExprId,
10    node::genn,
11    typ::{FnType, Type},
12    Apply, BindId, BuiltIn, Event, ExecCtx, LambdaId, Node, Refs, Rt, Scope,
13    TypecheckPhase, UserEvent,
14};
15use graphix_package_core::{
16    CachedArgs, CachedVals, EvalCached, FoldFn, FoldQ, MapCollection, MapFn, MapQ, Slot,
17};
18use graphix_rt::GXRt;
19use netidx::{publisher::Typ, subscriber::Value};
20use netidx_value::ValArray;
21use smallvec::SmallVec;
22use std::{collections::hash_map::Entry, collections::VecDeque, fmt::Debug};
23use triomphe::Arc as TArc;
24
25// ── Value-level list helpers ─────────────────────────────────────
26
27fn make_nil() -> Value {
28    Value::String(literal!("Nil"))
29}
30
31fn make_cons(head: Value, tail: Value) -> Value {
32    Value::Array(ValArray::from_iter_exact(
33        [Value::String(literal!("Cons")), head, tail].into_iter(),
34    ))
35}
36
37/// Extract head and tail from a Cons cell, or None if Nil/invalid.
38fn get_cons(v: &Value) -> Option<(&Value, &Value)> {
39    match v {
40        Value::Array(a) if a.len() == 3 => match &a[0] {
41            Value::String(s) if &**s == "Cons" => Some((&a[1], &a[2])),
42            _ => None,
43        },
44        _ => None,
45    }
46}
47
48fn is_nil(v: &Value) -> bool {
49    matches!(v, Value::String(s) if &**s == "Nil")
50}
51
52fn is_list(v: &Value) -> bool {
53    is_nil(v) || get_cons(v).is_some()
54}
55
56/// Count the number of elements in a list value.
57fn count_list(v: &Value) -> Option<usize> {
58    let mut len = 0;
59    let mut cur = v.clone();
60    loop {
61        if is_nil(&cur) {
62            return Some(len);
63        }
64        match get_cons(&cur) {
65            Some((_, tail)) => {
66                len += 1;
67                cur = tail.clone();
68            }
69            None => return None,
70        }
71    }
72}
73
74/// Build a list from an iterator by collecting to a buffer and folding
75/// right with cons. O(n) time, O(n) temporary space via SmallVec.
76fn from_iter_back(iter: impl Iterator<Item = Value>) -> Value {
77    let mut buf: SmallVec<[Value; 32]> = iter.collect();
78    let mut result = make_nil();
79    while let Some(v) = buf.pop() {
80        result = make_cons(v, result);
81    }
82    result
83}
84
85/// Iterator over list elements. Clones the Arc inside each Value::Array
86/// on each step (O(1) per step).
87struct ListIter {
88    cur: Value,
89}
90
91impl Iterator for ListIter {
92    type Item = Value;
93
94    fn next(&mut self) -> Option<Value> {
95        let cur = self.cur.clone();
96        match get_cons(&cur) {
97            Some((head, tail)) => {
98                let head = head.clone();
99                self.cur = tail.clone();
100                Some(head)
101            }
102            None => None,
103        }
104    }
105}
106
107// ── MapCollection for lists ──────────────────────────────────────
108
109/// Thin wrapper used by MapQ/FoldQ. Not stored in Values — only lives
110/// inside the reactive node machinery.
111#[derive(Debug, Clone)]
112struct ListColl {
113    value: Value,
114    len: usize,
115}
116
117impl Default for ListColl {
118    fn default() -> Self {
119        Self { value: make_nil(), len: 0 }
120    }
121}
122
123impl MapCollection for ListColl {
124    fn len(&self) -> usize {
125        self.len
126    }
127
128    fn iter_values(&self) -> impl Iterator<Item = Value> {
129        ListIter { cur: self.value.clone() }
130    }
131
132    fn select(v: Value) -> Option<Self> {
133        let len = count_list(&v)?;
134        Some(ListColl { value: v, len })
135    }
136
137    fn project(self) -> Value {
138        self.value
139    }
140
141    fn etyp(ft: &FnType) -> Result<Type> {
142        // When called from outside the module the first arg is
143        // Type::Abstract { params: [elem], .. }
144        if let Type::Abstract { params, .. } = &ft.args[0].typ {
145            if !params.is_empty() {
146                return Ok(params[0].clone());
147            }
148        }
149        // Inside the module the abstract type is resolved, so fall
150        // back to extracting the element type from the last argument
151        // of whichever function argument we can find (map: fn('a)->...,
152        // fold: fn('b,'a)->..., filter: fn('a)->bool, etc.). The list
153        // element type is always the last parameter of that function.
154        for arg in ft.args.iter() {
155            if let Type::Fn(inner) = &arg.typ {
156                if let Some(last) = inner.args.last() {
157                    return Ok(last.typ.clone());
158                }
159            }
160        }
161        bail!("cannot extract list element type from {:?}", ft.args[0].typ)
162    }
163}
164
165// ── MapFn implementations ────────────────────────────────────────
166
167#[derive(Debug, Default)]
168struct ListMapImpl;
169
170impl<R: Rt, E: UserEvent> MapFn<R, E> for ListMapImpl {
171    type Collection = ListColl;
172    const NAME: &str = "list_map";
173
174    fn finish(&mut self, slots: &[Slot<R, E>], _: &ListColl) -> Option<Value> {
175        Some(from_iter_back(slots.iter().map(|s| s.cur.clone().unwrap())))
176    }
177}
178
179type ListMap<R, E> = MapQ<R, E, ListMapImpl>;
180
181#[derive(Debug, Default)]
182struct ListFilterImpl;
183
184impl<R: Rt, E: UserEvent> MapFn<R, E> for ListFilterImpl {
185    type Collection = ListColl;
186    const NAME: &str = "list_filter";
187
188    fn finish(&mut self, slots: &[Slot<R, E>], a: &ListColl) -> Option<Value> {
189        Some(from_iter_back(
190            slots.iter().zip(ListIter { cur: a.value.clone() }).filter_map(|(p, v)| {
191                match p.cur {
192                    Some(Value::Bool(true)) => Some(v),
193                    _ => None,
194                }
195            }),
196        ))
197    }
198}
199
200type ListFilter<R, E> = MapQ<R, E, ListFilterImpl>;
201
202#[derive(Debug, Default)]
203struct ListFilterMapImpl;
204
205impl<R: Rt, E: UserEvent> MapFn<R, E> for ListFilterMapImpl {
206    type Collection = ListColl;
207    const NAME: &str = "list_filter_map";
208
209    fn finish(&mut self, slots: &[Slot<R, E>], _: &ListColl) -> Option<Value> {
210        Some(from_iter_back(slots.iter().filter_map(|s| match s.cur.as_ref().unwrap() {
211            Value::Null => None,
212            v => Some(v.clone()),
213        })))
214    }
215}
216
217type ListFilterMap<R, E> = MapQ<R, E, ListFilterMapImpl>;
218
219#[derive(Debug, Default)]
220struct ListFlatMapImpl;
221
222impl<R: Rt, E: UserEvent> MapFn<R, E> for ListFlatMapImpl {
223    type Collection = ListColl;
224    const NAME: &str = "list_flat_map";
225
226    fn finish(&mut self, slots: &[Slot<R, E>], _: &ListColl) -> Option<Value> {
227        Some(from_iter_back(slots.iter().flat_map(|s| {
228            let v = s.cur.as_ref().unwrap();
229            if is_list(v) {
230                let items: SmallVec<[Value; 32]> = ListIter { cur: v.clone() }.collect();
231                items.into_iter()
232            } else {
233                let mut one: SmallVec<[Value; 32]> = SmallVec::new();
234                one.push(v.clone());
235                one.into_iter()
236            }
237        })))
238    }
239}
240
241type ListFlatMap<R, E> = MapQ<R, E, ListFlatMapImpl>;
242
243#[derive(Debug, Default)]
244struct ListFindImpl;
245
246impl<R: Rt, E: UserEvent> MapFn<R, E> for ListFindImpl {
247    type Collection = ListColl;
248    const NAME: &str = "list_find";
249
250    fn finish(&mut self, slots: &[Slot<R, E>], a: &ListColl) -> Option<Value> {
251        let r = slots
252            .iter()
253            .zip(ListIter { cur: a.value.clone() })
254            .find(|(s, _)| matches!(s.cur.as_ref(), Some(Value::Bool(true))))
255            .map(|(_, v)| v)
256            .unwrap_or(Value::Null);
257        Some(r)
258    }
259}
260
261type ListFind<R, E> = MapQ<R, E, ListFindImpl>;
262
263#[derive(Debug, Default)]
264struct ListFindMapImpl;
265
266impl<R: Rt, E: UserEvent> MapFn<R, E> for ListFindMapImpl {
267    type Collection = ListColl;
268    const NAME: &str = "list_find_map";
269
270    fn finish(&mut self, slots: &[Slot<R, E>], _: &ListColl) -> Option<Value> {
271        let r = slots
272            .iter()
273            .find_map(|s| match s.cur.as_ref().unwrap() {
274                Value::Null => None,
275                v => Some(v.clone()),
276            })
277            .unwrap_or(Value::Null);
278        Some(r)
279    }
280}
281
282type ListFindMap<R, E> = MapQ<R, E, ListFindMapImpl>;
283
284// ── FoldFn implementation ────────────────────────────────────────
285
286#[derive(Debug)]
287struct ListFoldImpl;
288
289impl<R: Rt, E: UserEvent> FoldFn<R, E> for ListFoldImpl {
290    type Collection = ListColl;
291    const NAME: &str = "list_fold";
292}
293
294type ListFold<R, E> = FoldQ<R, E, ListFoldImpl>;
295
296// ── EvalCached implementations ───────────────────────────────────
297
298#[derive(Debug, Default)]
299struct NilEv;
300
301impl<R: Rt, E: UserEvent> EvalCached<R, E> for NilEv {
302    const NAME: &str = "list_nil";
303    const NEEDS_CALLSITE: bool = false;
304
305    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
306        from.0[0].as_ref()?;
307        Some(make_nil())
308    }
309}
310
311type Nil = CachedArgs<NilEv>;
312
313#[derive(Debug, Default)]
314struct ConsEv;
315
316impl<R: Rt, E: UserEvent> EvalCached<R, E> for ConsEv {
317    const NAME: &str = "list_cons";
318    const NEEDS_CALLSITE: bool = false;
319
320    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
321        let head = from.0[0].as_ref()?;
322        let tail = from.0[1].as_ref()?;
323        Some(make_cons(head.clone(), tail.clone()))
324    }
325}
326
327type Cons = CachedArgs<ConsEv>;
328
329#[derive(Debug, Default)]
330struct SingletonEv;
331
332impl<R: Rt, E: UserEvent> EvalCached<R, E> for SingletonEv {
333    const NAME: &str = "list_singleton";
334    const NEEDS_CALLSITE: bool = false;
335
336    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
337        let v = from.0[0].as_ref()?;
338        Some(make_cons(v.clone(), make_nil()))
339    }
340}
341
342type Singleton = CachedArgs<SingletonEv>;
343
344#[derive(Debug, Default)]
345struct HeadEv;
346
347impl<R: Rt, E: UserEvent> EvalCached<R, E> for HeadEv {
348    const NAME: &str = "list_head";
349    const NEEDS_CALLSITE: bool = false;
350
351    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
352        let list = from.0[0].as_ref()?;
353        match get_cons(list) {
354            Some((head, _)) => Some(head.clone()),
355            None => Some(Value::Null),
356        }
357    }
358}
359
360type Head = CachedArgs<HeadEv>;
361
362#[derive(Debug, Default)]
363struct TailEv;
364
365impl<R: Rt, E: UserEvent> EvalCached<R, E> for TailEv {
366    const NAME: &str = "list_tail";
367    const NEEDS_CALLSITE: bool = false;
368
369    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
370        let list = from.0[0].as_ref()?;
371        match get_cons(list) {
372            Some((_, tail)) => Some(tail.clone()),
373            None => Some(Value::Null),
374        }
375    }
376}
377
378type Tail = CachedArgs<TailEv>;
379
380#[derive(Debug, Default)]
381struct UnconsEv;
382
383impl<R: Rt, E: UserEvent> EvalCached<R, E> for UnconsEv {
384    const NAME: &str = "list_uncons";
385    const NEEDS_CALLSITE: bool = false;
386
387    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
388        let list = from.0[0].as_ref()?;
389        match get_cons(list) {
390            Some((head, tail)) => Some(Value::Array(ValArray::from_iter_exact(
391                [head.clone(), tail.clone()].into_iter(),
392            ))),
393            None => Some(Value::Null),
394        }
395    }
396}
397
398type Uncons = CachedArgs<UnconsEv>;
399
400#[derive(Debug, Default)]
401struct IsEmptyEv;
402
403impl<R: Rt, E: UserEvent> EvalCached<R, E> for IsEmptyEv {
404    const NAME: &str = "list_is_empty";
405    const NEEDS_CALLSITE: bool = false;
406
407    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
408        let list = from.0[0].as_ref()?;
409        Some(Value::Bool(is_nil(list)))
410    }
411}
412
413type IsEmpty = CachedArgs<IsEmptyEv>;
414
415#[derive(Debug, Default)]
416struct NthEv;
417
418impl<R: Rt, E: UserEvent> EvalCached<R, E> for NthEv {
419    const NAME: &str = "list_nth";
420    const NEEDS_CALLSITE: bool = false;
421
422    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
423        let list = from.0[0].as_ref()?;
424        let n = match from.0[1].as_ref()? {
425            Value::I64(n) => *n,
426            _ => return None,
427        };
428        if n < 0 {
429            return Some(Value::Null);
430        }
431        let mut cur = list.clone();
432        for _ in 0..n {
433            match get_cons(&cur) {
434                Some((_, tail)) => cur = tail.clone(),
435                None => return Some(Value::Null),
436            }
437        }
438        match get_cons(&cur) {
439            Some((head, _)) => Some(head.clone()),
440            None => Some(Value::Null),
441        }
442    }
443}
444
445type Nth = CachedArgs<NthEv>;
446
447#[derive(Debug, Default)]
448struct LenEv;
449
450impl<R: Rt, E: UserEvent> EvalCached<R, E> for LenEv {
451    const NAME: &str = "list_len";
452    const NEEDS_CALLSITE: bool = false;
453
454    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
455        let list = from.0[0].as_ref()?;
456        Some(Value::I64(count_list(list)? as i64))
457    }
458}
459
460type Len = CachedArgs<LenEv>;
461
462#[derive(Debug, Default)]
463struct ReverseEv;
464
465impl<R: Rt, E: UserEvent> EvalCached<R, E> for ReverseEv {
466    const NAME: &str = "list_reverse";
467    const NEEDS_CALLSITE: bool = false;
468
469    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
470        let list = from.0[0].as_ref()?;
471        if !is_list(list) {
472            return None;
473        }
474        let mut result = make_nil();
475        for v in (ListIter { cur: list.clone() }) {
476            result = make_cons(v, result);
477        }
478        Some(result)
479    }
480}
481
482type Reverse = CachedArgs<ReverseEv>;
483
484#[derive(Debug, Default)]
485struct TakeEv;
486
487impl<R: Rt, E: UserEvent> EvalCached<R, E> for TakeEv {
488    const NAME: &str = "list_take";
489    const NEEDS_CALLSITE: bool = false;
490
491    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
492        let n = match from.0[0].as_ref()? {
493            Value::I64(n) => (*n).max(0) as usize,
494            _ => return None,
495        };
496        let list = from.0[1].as_ref()?;
497        if !is_list(list) {
498            return None;
499        }
500        Some(from_iter_back(ListIter { cur: list.clone() }.take(n)))
501    }
502}
503
504type Take = CachedArgs<TakeEv>;
505
506#[derive(Debug, Default)]
507struct DropEv;
508
509impl<R: Rt, E: UserEvent> EvalCached<R, E> for DropEv {
510    const NAME: &str = "list_drop";
511    const NEEDS_CALLSITE: bool = false;
512
513    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
514        let n = match from.0[0].as_ref()? {
515            Value::I64(n) => (*n).max(0) as usize,
516            _ => return None,
517        };
518        let list = from.0[1].as_ref()?;
519        if !is_list(list) {
520            return None;
521        }
522        let mut cur = list.clone();
523        for _ in 0..n {
524            match get_cons(&cur) {
525                Some((_, tail)) => cur = tail.clone(),
526                None => return Some(make_nil()),
527            }
528        }
529        Some(cur)
530    }
531}
532
533type Drop_ = CachedArgs<DropEv>;
534
535#[derive(Debug, Default)]
536struct ToArrayEv;
537
538impl<R: Rt, E: UserEvent> EvalCached<R, E> for ToArrayEv {
539    const NAME: &str = "list_to_array";
540    const NEEDS_CALLSITE: bool = false;
541
542    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
543        let list = from.0[0].as_ref()?;
544        if !is_list(list) {
545            return None;
546        }
547        Some(Value::Array(ValArray::from_iter(ListIter { cur: list.clone() })))
548    }
549}
550
551type ToArray = CachedArgs<ToArrayEv>;
552
553#[derive(Debug, Default)]
554struct FromArrayEv;
555
556impl<R: Rt, E: UserEvent> EvalCached<R, E> for FromArrayEv {
557    const NAME: &str = "list_from_array";
558    const NEEDS_CALLSITE: bool = false;
559
560    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
561        match from.0[0].as_ref()? {
562            Value::Array(a) => Some(from_iter_back(a.iter().cloned())),
563            _ => None,
564        }
565    }
566}
567
568type FromArray = CachedArgs<FromArrayEv>;
569
570#[derive(Debug, Default)]
571struct ConcatEv(SmallVec<[Value; 32]>);
572
573impl<R: Rt, E: UserEvent> EvalCached<R, E> for ConcatEv {
574    const NAME: &str = "list_concat";
575    const NEEDS_CALLSITE: bool = false;
576
577    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
578        // Collect all lists into a flat buffer, then build from back.
579        // This handles variadic concat: concat(l1, l2, l3, ...) = l1 ++ l2 ++ l3 ++ ...
580        let mut present = true;
581        for v in from.0.iter() {
582            match v {
583                Some(v) if is_list(v) => {
584                    self.0.extend(ListIter { cur: v.clone() });
585                }
586                _ => present = false,
587            }
588        }
589        if present {
590            let result = from_iter_back(self.0.drain(..));
591            Some(result)
592        } else {
593            self.0.clear();
594            None
595        }
596    }
597}
598
599type Concat = CachedArgs<ConcatEv>;
600
601#[derive(Debug, Default)]
602struct FlattenEv(SmallVec<[Value; 32]>);
603
604impl<R: Rt, E: UserEvent> EvalCached<R, E> for FlattenEv {
605    const NAME: &str = "list_flatten";
606    const NEEDS_CALLSITE: bool = false;
607
608    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
609        let list = from.0[0].as_ref()?;
610        if !is_list(list) {
611            return None;
612        }
613        for inner in (ListIter { cur: list.clone() }) {
614            self.0.extend(ListIter { cur: inner });
615        }
616        let result = from_iter_back(self.0.drain(..));
617        Some(result)
618    }
619}
620
621type Flatten = CachedArgs<FlattenEv>;
622
623#[derive(Debug, Default)]
624struct SortEv(SmallVec<[Value; 32]>);
625
626impl<R: Rt, E: UserEvent> EvalCached<R, E> for SortEv {
627    const NAME: &str = "list_sort";
628    const NEEDS_CALLSITE: bool = false;
629
630    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
631        fn cn(v: &Value) -> Value {
632            v.clone().cast(Typ::F64).unwrap_or_else(|| v.clone())
633        }
634        match &from.0[..] {
635            [Some(Value::String(dir)), Some(Value::Bool(numeric)), Some(list)]
636                if is_list(list) =>
637            {
638                match &**dir {
639                    "Ascending" => {
640                        self.0.extend(ListIter { cur: list.clone() });
641                        if *numeric {
642                            self.0.sort_by(|v0, v1| cn(v0).cmp(&cn(v1)))
643                        } else {
644                            self.0.sort();
645                        }
646                        Some(from_iter_back(self.0.drain(..)))
647                    }
648                    "Descending" => {
649                        self.0.extend(ListIter { cur: list.clone() });
650                        if *numeric {
651                            self.0.sort_by(|a0, a1| cn(a1).cmp(&cn(a0)))
652                        } else {
653                            self.0.sort_by(|a0, a1| a1.cmp(a0));
654                        }
655                        Some(from_iter_back(self.0.drain(..)))
656                    }
657                    _ => None,
658                }
659            }
660            _ => None,
661        }
662    }
663}
664
665type Sort = CachedArgs<SortEv>;
666
667#[derive(Debug, Default)]
668struct EnumerateEv;
669
670impl<R: Rt, E: UserEvent> EvalCached<R, E> for EnumerateEv {
671    const NAME: &str = "list_enumerate";
672    const NEEDS_CALLSITE: bool = false;
673
674    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
675        let list = from.0[0].as_ref()?;
676        if !is_list(list) {
677            return None;
678        }
679        Some(from_iter_back(
680            ListIter { cur: list.clone() }.enumerate().map(|(i, v)| (i, v).into()),
681        ))
682    }
683}
684
685type Enumerate_ = CachedArgs<EnumerateEv>;
686
687#[derive(Debug, Default)]
688struct ZipEv;
689
690impl<R: Rt, E: UserEvent> EvalCached<R, E> for ZipEv {
691    const NAME: &str = "list_zip";
692    const NEEDS_CALLSITE: bool = false;
693
694    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
695        let l0 = from.0[0].as_ref()?;
696        let l1 = from.0[1].as_ref()?;
697        if !is_list(l0) || !is_list(l1) {
698            return None;
699        }
700        Some(from_iter_back(
701            ListIter { cur: l0.clone() }
702                .zip(ListIter { cur: l1.clone() })
703                .map(|p| p.into()),
704        ))
705    }
706}
707
708type Zip = CachedArgs<ZipEv>;
709
710#[derive(Debug, Default)]
711struct UnzipEv {
712    t0: SmallVec<[Value; 32]>,
713    t1: SmallVec<[Value; 32]>,
714}
715
716impl<R: Rt, E: UserEvent> EvalCached<R, E> for UnzipEv {
717    const NAME: &str = "list_unzip";
718    const NEEDS_CALLSITE: bool = false;
719
720    fn eval(&mut self, _ctx: &mut ExecCtx<R, E>, from: &CachedVals) -> Option<Value> {
721        let list = from.0[0].as_ref()?;
722        if !is_list(list) {
723            return None;
724        }
725        for v in (ListIter { cur: list.clone() }) {
726            if let Value::Array(a) = v {
727                if a.len() == 2 {
728                    self.t0.push(a[0].clone());
729                    self.t1.push(a[1].clone());
730                }
731            }
732        }
733        let v0 = from_iter_back(self.t0.drain(..));
734        let v1 = from_iter_back(self.t1.drain(..));
735        Some(Value::Array(ValArray::from_iter_exact([v0, v1].into_iter())))
736    }
737}
738
739type Unzip = CachedArgs<UnzipEv>;
740
741// ── Custom BuiltIn/Apply implementations ─────────────────────────
742
743#[derive(Debug)]
744struct ListIterBI(BindId, ExprId);
745
746impl<R: Rt, E: UserEvent> BuiltIn<R, E> for ListIterBI {
747    const NAME: &str = "list_iter";
748    const NEEDS_CALLSITE: bool = false;
749
750    fn init<'a, 'b, 'c, 'd>(
751        ctx: &'a mut ExecCtx<R, E>,
752        _typ: &'a FnType,
753        _resolved: Option<&'d FnType>,
754        _scope: &'b Scope,
755        _from: &'c [Node<R, E>],
756        top_id: ExprId,
757    ) -> Result<Box<dyn Apply<R, E>>> {
758        let id = BindId::new();
759        ctx.rt.ref_var(id, top_id);
760        Ok(Box::new(ListIterBI(id, top_id)))
761    }
762}
763
764impl<R: Rt, E: UserEvent> Apply<R, E> for ListIterBI {
765    fn update(
766        &mut self,
767        ctx: &mut ExecCtx<R, E>,
768        from: &mut [Node<R, E>],
769        event: &mut Event<E>,
770    ) -> Option<Value> {
771        if let Some(list) = from[0].update(ctx, event) {
772            for v in (ListIter { cur: list }) {
773                ctx.rt.set_var(self.0, v);
774            }
775        }
776        event.variables.get(&self.0).cloned()
777    }
778
779    fn delete(&mut self, ctx: &mut ExecCtx<R, E>) {
780        ctx.rt.unref_var(self.0, self.1)
781    }
782
783    fn sleep(&mut self, ctx: &mut ExecCtx<R, E>) {
784        ctx.rt.unref_var(self.0, self.1);
785        self.0 = BindId::new();
786        ctx.rt.ref_var(self.0, self.1);
787    }
788}
789
790#[derive(Debug)]
791struct ListIterQ {
792    triggered: usize,
793    queue: VecDeque<(usize, Vec<Value>)>,
794    id: BindId,
795    top_id: ExprId,
796}
797
798impl<R: Rt, E: UserEvent> BuiltIn<R, E> for ListIterQ {
799    const NAME: &str = "list_iterq";
800    const NEEDS_CALLSITE: bool = false;
801
802    fn init<'a, 'b, 'c, 'd>(
803        ctx: &'a mut ExecCtx<R, E>,
804        _typ: &'a FnType,
805        _resolved: Option<&'d FnType>,
806        _scope: &'b Scope,
807        _from: &'c [Node<R, E>],
808        top_id: ExprId,
809    ) -> Result<Box<dyn Apply<R, E>>> {
810        let id = BindId::new();
811        ctx.rt.ref_var(id, top_id);
812        Ok(Box::new(ListIterQ { triggered: 0, queue: VecDeque::new(), id, top_id }))
813    }
814}
815
816impl<R: Rt, E: UserEvent> Apply<R, E> for ListIterQ {
817    fn update(
818        &mut self,
819        ctx: &mut ExecCtx<R, E>,
820        from: &mut [Node<R, E>],
821        event: &mut Event<E>,
822    ) -> Option<Value> {
823        if from[0].update(ctx, event).is_some() {
824            self.triggered += 1;
825        }
826        if let Some(list) = from[1].update(ctx, event) {
827            if is_list(&list) {
828                let elems: Vec<Value> = ListIter { cur: list }.collect();
829                if !elems.is_empty() {
830                    self.queue.push_back((0, elems));
831                }
832            }
833        }
834        while self.triggered > 0 && !self.queue.is_empty() {
835            let (i, elems) = self.queue.front_mut().unwrap();
836            while self.triggered > 0 && *i < elems.len() {
837                ctx.rt.set_var(self.id, elems[*i].clone());
838                *i += 1;
839                self.triggered -= 1;
840            }
841            if *i == elems.len() {
842                self.queue.pop_front();
843            }
844        }
845        event.variables.get(&self.id).cloned()
846    }
847
848    fn delete(&mut self, ctx: &mut ExecCtx<R, E>) {
849        ctx.rt.unref_var(self.id, self.top_id)
850    }
851
852    fn sleep(&mut self, ctx: &mut ExecCtx<R, E>) {
853        ctx.rt.unref_var(self.id, self.top_id);
854        self.id = BindId::new();
855        self.queue.clear();
856        self.triggered = 0;
857    }
858}
859
860#[derive(Debug)]
861struct ListInit<R: Rt, E: UserEvent> {
862    scope: Scope,
863    fid: BindId,
864    top_id: ExprId,
865    mftyp: TArc<FnType>,
866    slots: Vec<Slot<R, E>>,
867}
868
869impl<R: Rt, E: UserEvent> BuiltIn<R, E> for ListInit<R, E> {
870    const NAME: &str = "list_init";
871    const NEEDS_CALLSITE: bool = false;
872
873    fn init<'a, 'b, 'c, 'd>(
874        _ctx: &'a mut ExecCtx<R, E>,
875        typ: &'a FnType,
876        resolved: Option<&'d FnType>,
877        scope: &'b Scope,
878        from: &'c [Node<R, E>],
879        top_id: ExprId,
880    ) -> Result<Box<dyn Apply<R, E>>> {
881        match from {
882            [_, _] => {
883                let typ = resolved.unwrap_or(typ);
884                Ok(Box::new(Self {
885                    scope: scope
886                        .append(&format_compact!("fn{}", LambdaId::new().inner())),
887                    fid: BindId::new(),
888                    top_id,
889                    mftyp: match &typ.args[1].typ {
890                        Type::Fn(ft) => ft.clone(),
891                        t => bail!("expected a function not {t}"),
892                    },
893                    slots: vec![],
894                }))
895            }
896            _ => bail!("expected two arguments"),
897        }
898    }
899}
900
901impl<R: Rt, E: UserEvent> Apply<R, E> for ListInit<R, E> {
902    fn update(
903        &mut self,
904        ctx: &mut ExecCtx<R, E>,
905        from: &mut [Node<R, E>],
906        event: &mut Event<E>,
907    ) -> Option<Value> {
908        let slen = self.slots.len();
909        if let Some(v) = from[1].update(ctx, event) {
910            ctx.cached.insert(self.fid, v.clone());
911            event.variables.insert(self.fid, v);
912        }
913        let (size_fired, resized) = match from[0].update(ctx, event) {
914            Some(Value::I64(n)) => {
915                let n = n.max(0) as usize;
916                if n == slen {
917                    (true, false)
918                } else if n < slen {
919                    while self.slots.len() > n {
920                        if let Some(mut s) = self.slots.pop() {
921                            s.delete(ctx)
922                        }
923                    }
924                    (true, true)
925                } else {
926                    let i_typ = Type::Primitive(Typ::I64.into());
927                    while self.slots.len() < n {
928                        let i = self.slots.len();
929                        let (id, node) = genn::bind(
930                            ctx,
931                            &self.scope.lexical,
932                            "i",
933                            i_typ.clone(),
934                            self.top_id,
935                        );
936                        ctx.cached.insert(id, Value::I64(i as i64));
937                        let fnode = genn::reference(
938                            ctx,
939                            self.fid,
940                            Type::Fn(self.mftyp.clone()),
941                            self.top_id,
942                        );
943                        let pred = genn::apply(
944                            fnode,
945                            self.scope.clone(),
946                            vec![node],
947                            &self.mftyp,
948                            self.top_id,
949                        );
950                        self.slots.push(Slot { id, pred, cur: None });
951                    }
952                    (true, true)
953                }
954            }
955            _ => (false, false),
956        };
957        if resized && self.slots.len() > slen {
958            for i in slen..self.slots.len() {
959                let id = self.slots[i].id;
960                event.variables.insert(id, Value::I64(i as i64));
961            }
962        }
963        if size_fired && self.slots.is_empty() {
964            return Some(make_nil());
965        }
966        let init = event.init;
967        let mut up = resized;
968        for (i, s) in self.slots.iter_mut().enumerate() {
969            if i == slen {
970                event.init = true;
971                if let Entry::Vacant(e) = event.variables.entry(self.fid)
972                    && let Some(v) = ctx.cached.get(&self.fid)
973                {
974                    e.insert(v.clone());
975                }
976            }
977            if let Some(v) = s.pred.update(ctx, event) {
978                s.cur = Some(v);
979                up = true;
980            }
981        }
982        event.init = init;
983        if up && self.slots.iter().all(|s| s.cur.is_some()) {
984            Some(from_iter_back(self.slots.iter().map(|s| s.cur.clone().unwrap())))
985        } else {
986            None
987        }
988    }
989
990    fn typecheck(
991        &mut self,
992        ctx: &mut ExecCtx<R, E>,
993        _from: &mut [Node<R, E>],
994        _phase: TypecheckPhase,
995    ) -> anyhow::Result<()> {
996        let i_typ = Type::Primitive(Typ::I64.into());
997        let (_, node) = genn::bind(ctx, &self.scope.lexical, "i", i_typ, self.top_id);
998        let ft = self.mftyp.clone();
999        let fnode = genn::reference(ctx, self.fid, Type::Fn(ft.clone()), self.top_id);
1000        let mut node =
1001            genn::apply(fnode, self.scope.clone(), vec![node], &ft, self.top_id);
1002        let r = node.typecheck(ctx);
1003        node.delete(ctx);
1004        r?;
1005        Ok(())
1006    }
1007
1008    fn refs(&self, refs: &mut Refs) {
1009        for s in &self.slots {
1010            s.pred.refs(refs)
1011        }
1012    }
1013
1014    fn delete(&mut self, ctx: &mut ExecCtx<R, E>) {
1015        ctx.cached.remove(&self.fid);
1016        for sl in &mut self.slots {
1017            sl.delete(ctx)
1018        }
1019    }
1020
1021    fn sleep(&mut self, ctx: &mut ExecCtx<R, E>) {
1022        for sl in &mut self.slots {
1023            sl.cur = None;
1024            sl.pred.sleep(ctx);
1025        }
1026    }
1027}
1028
1029// ── Package registration ─────────────────────────────────────────
1030
1031graphix_derive::defpackage! {
1032    builtins => [
1033        Concat,
1034        Cons,
1035        Drop_ as Drop_,
1036        Enumerate_ as Enumerate_,
1037        Flatten,
1038        FromArray,
1039        Head,
1040        IsEmpty,
1041        Len,
1042        ListFilter as ListFilter<GXRt<X>, X::UserEvent>,
1043        ListFilterMap as ListFilterMap<GXRt<X>, X::UserEvent>,
1044        ListFind as ListFind<GXRt<X>, X::UserEvent>,
1045        ListFindMap as ListFindMap<GXRt<X>, X::UserEvent>,
1046        ListFlatMap as ListFlatMap<GXRt<X>, X::UserEvent>,
1047        ListFold as ListFold<GXRt<X>, X::UserEvent>,
1048        ListInit as ListInit<GXRt<X>, X::UserEvent>,
1049        ListIterBI,
1050        ListIterQ,
1051        ListMap as ListMap<GXRt<X>, X::UserEvent>,
1052        Nil,
1053        Nth,
1054        Reverse,
1055        Singleton,
1056        Sort,
1057        Tail,
1058        Take,
1059        ToArray,
1060        Uncons,
1061        Unzip,
1062        Zip,
1063    ],
1064}