squint-cljs 0.12.193 → 0.13.194

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,8 @@ function dequal(foo, bar) {
24
24
  // supports primitives, Array, Set, Map and plain objects
25
25
  // like CLJS: does not support NaN
26
26
  if (foo === bar) return true;
27
+ // null and undefined are both nil in CLJS, so they compare equal
28
+ if (foo == null) return bar == null;
27
29
  var ctor, len, tmp;
28
30
 
29
31
  if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
@@ -66,7 +68,9 @@ function dequal(foo, bar) {
66
68
  return true;
67
69
  }
68
70
 
69
- if (!ctor || typeof foo === 'object') {
71
+ // LazyIterable falls through to the sequential-equality path below; it is an
72
+ // object but must compare element-wise, not by enumerable properties
73
+ if ((!ctor || typeof foo === 'object') && !(foo instanceof LazyIterable)) {
70
74
  len = 0;
71
75
  for (const k in foo) {
72
76
  if (has.call(foo, k) && ++len && !has.call(bar, k)) return false;
@@ -75,6 +79,26 @@ function dequal(foo, bar) {
75
79
  return Object.keys(bar).length === len;
76
80
  }
77
81
  }
82
+
83
+ // Cross-type sequential equality, like CLJS `(= '(1 2) [1 2])`: vectors,
84
+ // lists and lazy seqs compare element-wise regardless of concrete type. Only
85
+ // reached when the same-constructor paths above did not apply, so equal-typed
86
+ // collections keep their fast paths.
87
+ if (
88
+ foo && bar &&
89
+ (Array.isArray(foo) || foo instanceof LazyIterable) &&
90
+ (Array.isArray(bar) || bar instanceof LazyIterable)
91
+ ) {
92
+ const fi = foo[Symbol.iterator]();
93
+ const bi = bar[Symbol.iterator]();
94
+ while (true) {
95
+ const a = fi.next();
96
+ const b = bi.next();
97
+ if (a.done || b.done) return !!(a.done && b.done);
98
+ if (!dequal(a.value, b.value)) return false;
99
+ }
100
+ }
101
+
78
102
  return false;
79
103
  }
80
104
  // end inlined version of dequals
@@ -179,16 +203,31 @@ export function assoc_BANG_(m, k, v, ...kvs) {
179
203
  return m;
180
204
  }
181
205
 
206
+ const _metaSym = Symbol('meta');
207
+
208
+ // Copies metadata from `from` onto `to` (when present) and returns `to`. Used
209
+ // to make value-producing operations (copy, empty, conj, into) carry metadata
210
+ // like Clojure does, instead of dropping it on the freshly built structure.
211
+ // Kept branch-light for the hot path: reads an absent symbol property (cheap,
212
+ // no hidden-class change) and only writes when metadata is actually present.
213
+ function copyMeta(from, to) {
214
+ const m = from?.[_metaSym];
215
+ if (m !== undefined) to[_metaSym] = m;
216
+ return to;
217
+ }
218
+
182
219
  function copy(o) {
183
220
  switch (typeConst(o)) {
184
221
  case MAP_TYPE:
185
- return new Map(o);
222
+ return copyMeta(o, new Map(o));
186
223
  case SET_TYPE:
187
- return new o.constructor(o);
224
+ return copyMeta(o, new o.constructor(o));
188
225
  case ARRAY_TYPE:
189
- return [...o];
226
+ return copyMeta(o, [...o]);
190
227
  case OBJECT_TYPE:
191
- return { ...o };
228
+ return copyMeta(o, { ...o });
229
+ case LIST_TYPE:
230
+ return copyMeta(o, new List(...o));
192
231
  default:
193
232
  throw new Error(`Don't know how to copy object of type ${typeof o}.`);
194
233
  }
@@ -399,14 +438,14 @@ export function conj(...xs) {
399
438
  case SET_TYPE:
400
439
  if (o instanceof SortedSet) {
401
440
  // prevent re-sorting of collection
402
- return conj_BANG_set(new o.constructor(o), rest);
441
+ return copyMeta(o, conj_BANG_set(new o.constructor(o), rest));
403
442
  } else {
404
- return new o.constructor([...o, ...rest]);
443
+ return copyMeta(o, new o.constructor([...o, ...rest]));
405
444
  }
406
445
  case LIST_TYPE:
407
- return new List(...rest.reverse(), ...o);
446
+ return copyMeta(o, new List(...rest.reverse(), ...o));
408
447
  case ARRAY_TYPE:
409
- return [...o, ...rest];
448
+ return copyMeta(o, [...o, ...rest]);
410
449
  case MAP_TYPE:
411
450
  m = new Map(o);
412
451
  for (const x of rest) {
@@ -417,7 +456,7 @@ export function conj(...xs) {
417
456
  else m.set(x[0], x[1]);
418
457
  }
419
458
 
420
- return m;
459
+ return copyMeta(o, m);
421
460
  case LAZY_ITERABLE_TYPE:
422
461
  return lazy(function* () {
423
462
  yield* rest;
@@ -431,7 +470,7 @@ export function conj(...xs) {
431
470
  else o2[x[0]] = x[1];
432
471
  }
433
472
 
434
- return o2;
473
+ return copyMeta(o, o2);
435
474
  default:
436
475
  throw new Error(
437
476
  'Illegal argument: conj expects a Set, Array, List, Map, or Object as the first argument.'
@@ -447,6 +486,7 @@ export function disj_BANG_(s, ...xs) {
447
486
  }
448
487
 
449
488
  export function disj(s, ...xs) {
489
+ if (s == null) return s;
450
490
  const s1 = new s.constructor([...s]);
451
491
  return disj_BANG_(s1, ...xs);
452
492
  }
@@ -503,26 +543,29 @@ export function println(...args) {
503
543
  }
504
544
 
505
545
  export function nth(coll, idx, orElse) {
506
- if (coll) {
507
- // "found" must be decided by the index bound, not by the value: an
508
- // in-bounds element that happens to be `undefined` is still found.
509
- if (Array.isArray(coll)) {
510
- if (idx >= 0 && idx < coll.length) {
511
- return coll[idx];
512
- }
513
- } else {
514
- // iterables may lack .length and can be infinite, so iterate and stop
515
- // at idx rather than computing a bound.
516
- const iter = iterable(coll);
517
- let i = 0;
518
- for (const value of iter) {
519
- if (i++ === idx) {
520
- return value;
521
- }
522
- }
546
+ const hasDefault = arguments.length > 2;
547
+ // nil coll puns to nil, like Clojure
548
+ if (coll == null) return hasDefault ? orElse : null;
549
+ // "found" is decided by the index bound, not the value. An in-bounds element
550
+ // that happens to be undefined is still found.
551
+ if (Array.isArray(coll)) {
552
+ if (idx >= 0 && idx < coll.length) {
553
+ return coll[idx];
554
+ }
555
+ } else if (idx >= 0) {
556
+ // non-array: skip whole chunks instead of counting elements (handles
557
+ // infinite seqs since it stops once idx is reached)
558
+ const next = chunkCursor(coll);
559
+ let base = 0;
560
+ let ch;
561
+ while ((ch = next()) !== null) {
562
+ if (idx < base + ch.length) return ch[idx - base];
563
+ base += ch.length;
523
564
  }
524
565
  }
525
- return orElse;
566
+ // out of bounds. With a default return it, otherwise throw like Clojure
567
+ if (hasDefault) return orElse;
568
+ throw new Error('Index out of bounds: ' + idx);
526
569
  }
527
570
 
528
571
  export function get(coll, key, otherwise = undefined) {
@@ -586,6 +629,21 @@ export function seqable_QMARK_(x) {
586
629
  );
587
630
  }
588
631
 
632
+ // squint has no distinct MapEntry type (map entries are plain 2-element
633
+ // arrays). We tag entries produced from a map with this marker symbol so
634
+ // map-entry? can tell them apart from ordinary vectors. Symbol-keyed props are
635
+ // invisible to =, into, iteration and JSON, so the effect is contained.
636
+ const MAP_ENTRY = Symbol('squint.lang.map-entry');
637
+
638
+ function tagMapEntry(e) {
639
+ e[MAP_ENTRY] = true;
640
+ return e;
641
+ }
642
+
643
+ export function map_entry_QMARK_(x) {
644
+ return Array.isArray(x) && x[MAP_ENTRY] === true;
645
+ }
646
+
589
647
  export function iterable(x) {
590
648
  // nil puns to empty iterable, support passing nil to first/rest/reduce, etc.
591
649
  if (x === null || x === undefined) {
@@ -597,7 +655,7 @@ export function iterable(x) {
597
655
  if (x[Symbol.iterator]) {
598
656
  return x;
599
657
  }
600
- if (x instanceof Object) return Object.entries(x);
658
+ if (x instanceof Object) return Object.entries(x).map(tagMapEntry);
601
659
  throw new TypeError(`${x} is not iterable`);
602
660
  }
603
661
 
@@ -624,12 +682,24 @@ export function seq(x) {
624
682
  }
625
683
 
626
684
  export function first(coll) {
685
+ if (coll == null) return undefined;
686
+ if (Array.isArray(coll)) return coll[0];
687
+ if (coll instanceof LazyIterable) {
688
+ coll.force();
689
+ return coll.chunk === null ? undefined : coll.chunk[0];
690
+ }
627
691
  // destructuring uses iterable protocol
628
692
  const [first] = iterable(coll);
629
693
  return first;
630
694
  }
631
695
 
632
696
  export function second(coll) {
697
+ if (coll instanceof LazyIterable) {
698
+ coll.force();
699
+ const ch = coll.chunk;
700
+ if (ch === null) return undefined;
701
+ return ch.length > 1 ? ch[1] : first(coll._rest);
702
+ }
633
703
  const [_, v] = iterable(coll);
634
704
  return v;
635
705
  }
@@ -639,13 +709,20 @@ export function ffirst(coll) {
639
709
  }
640
710
 
641
711
  export function rest(coll) {
642
- return lazy(function* () {
643
- let first = true;
644
- for (const x of iterable(coll)) {
645
- if (first) first = false;
646
- else yield x;
647
- }
648
- });
712
+ // chunk-aware: drop the first element of the first chunk, keep the rest of
713
+ // the chain chunked (preserves chunkedness, unlike re-iterating element-wise)
714
+ const cell = chunkCells(coll);
715
+ cell.force();
716
+ const ch = cell.chunk;
717
+ if (ch === null) return cell; // (rest ()) is ()
718
+ if (ch.length > 1) {
719
+ const c = new LazyIterable(null);
720
+ c.realized = true;
721
+ c.chunk = ch.slice(1);
722
+ c._rest = cell._rest;
723
+ return c;
724
+ }
725
+ return cell._rest; // first chunk had one element; the next cell is the rest
649
726
  }
650
727
 
651
728
  class Reduced {
@@ -660,16 +737,15 @@ class Reduced {
660
737
 
661
738
  export function last(coll) {
662
739
  coll = iterable(coll);
663
- let lastEl;
664
- switch (typeConst(coll)) {
665
- case ARRAY_TYPE:
666
- return coll[coll.length - 1];
667
- default:
668
- for (const x of coll) {
669
- lastEl = x;
670
- }
671
- return lastEl;
740
+ if (Array.isArray(coll)) {
741
+ return coll[coll.length - 1];
672
742
  }
743
+ // non-array: walk chunks, keep the last chunk's last element
744
+ const next = chunkCursor(coll);
745
+ let lastEl;
746
+ let ch;
747
+ while ((ch = next()) !== null) lastEl = ch[ch.length - 1];
748
+ return lastEl;
673
749
  }
674
750
 
675
751
  export function reduced(x) {
@@ -682,31 +758,43 @@ export function reduced_QMARK_(x) {
682
758
 
683
759
  export function reduce(f, arg1, arg2) {
684
760
  f = __toFn(f);
685
- let coll, val;
686
- if (arguments.length === 2) {
687
- // (reduce f coll)
688
- const iter = iterable(arg1)[Symbol.iterator]();
689
- const vd = iter.next();
690
- if (vd.done) {
691
- val = f();
692
- } else {
693
- val = vd.value;
761
+ const hasInit = arguments.length !== 2;
762
+ const coll = hasInit ? arg2 : arg1;
763
+ let val = hasInit ? arg1 : undefined;
764
+
765
+ // fast path: index loop over an array
766
+ if (Array.isArray(coll)) {
767
+ let i = 0;
768
+ if (!hasInit) {
769
+ if (coll.length === 0) return f();
770
+ val = coll[0];
771
+ i = 1;
694
772
  }
695
- coll = iter;
696
- } else {
697
- // (reduce f val coll)
698
- val = arg1;
699
- coll = iterable(arg2);
773
+ if (val instanceof Reduced) return val.value;
774
+ for (; i < coll.length; i++) {
775
+ val = f(val, coll[i]);
776
+ if (val instanceof Reduced) return val.value;
777
+ }
778
+ return val;
700
779
  }
701
- if (val instanceof Reduced) {
702
- return val.value;
780
+
781
+ // non-array: walk chunks (chunked cell or any other seqable)
782
+ const next = chunkCursor(coll);
783
+ let ch = next();
784
+ let i = 0;
785
+ if (!hasInit) {
786
+ if (ch === null) return f();
787
+ val = ch[0];
788
+ i = 1;
703
789
  }
704
- for (const x of coll) {
705
- val = f(val, x);
706
- if (val instanceof Reduced) {
707
- val = val.value;
708
- break;
790
+ if (val instanceof Reduced) return val.value;
791
+ while (ch !== null) {
792
+ for (; i < ch.length; i++) {
793
+ val = f(val, ch[i]);
794
+ if (val instanceof Reduced) return val.value;
709
795
  }
796
+ ch = next();
797
+ i = 0;
710
798
  }
711
799
  return val;
712
800
  }
@@ -739,42 +827,152 @@ function* _reductions3(f, init, coll) {
739
827
  export function reductions(f, arg1, arg2) {
740
828
  f = __toFn(f);
741
829
  if (arguments.length === 2) {
830
+ const it = es6_iterator(iterable(arg1));
742
831
  return lazy(function* () {
743
- yield* _reductions2(f, iterable(arg1)[Symbol.iterator]());
832
+ yield* _reductions2(f, it);
744
833
  });
745
834
  }
835
+ const it = es6_iterator(iterable(arg2));
746
836
  return lazy(function* () {
747
- yield* _reductions3(f, arg1, iterable(arg2)[Symbol.iterator]());
837
+ yield* _reductions3(f, arg1, it);
748
838
  });
749
839
  }
750
840
 
751
- var tolr = false;
841
+ // Deprecated: lazy values are now cached, so reuse no longer recomputes. Kept
842
+ // for API compatibility; remove in a future release.
752
843
  export function warn_on_lazy_reusage_BANG_() {
753
- tolr = true;
844
+ console.warn(
845
+ 'warn-on-lazy-reusage! is deprecated and does nothing: lazy values are now cached.',
846
+ );
754
847
  }
755
848
 
849
+ const CHUNK_SIZE = 32;
850
+
851
+ // One cell of a self-caching chunked seq. `step` is a thunk returning
852
+ // [nonEmptyChunkArray, nextStep] or null at the end. See doc/dev/lazy-seqs.md.
756
853
  class LazyIterable {
757
- constructor(gen) {
758
- this.gen = gen;
759
- this.usages = 0;
760
- }
761
- [Symbol.iterator]() {
762
- this.usages++;
763
- if (this.usages >= 2 && tolr) {
764
- try {
765
- throw new Error();
766
- } catch (e) {
767
- console.warn('Re-use of lazy value', e.stack);
854
+ constructor(step) {
855
+ this.step = step;
856
+ this.realized = false;
857
+ this.chunk = null; // array, or null when this is the terminal (empty) cell
858
+ this._rest = null;
859
+ }
860
+ force() {
861
+ if (!this.realized) {
862
+ this.realized = true;
863
+ const r = this.step();
864
+ this.step = null;
865
+ if (r !== null && r !== undefined) {
866
+ this.chunk = r[0];
867
+ this._rest = new LazyIterable(r[1]);
768
868
  }
769
869
  }
770
- return this.gen();
870
+ return this;
871
+ }
872
+ [Symbol.iterator]() {
873
+ let cell = this;
874
+ let i = 0;
875
+ return {
876
+ next() {
877
+ for (;;) {
878
+ cell.force();
879
+ const ch = cell.chunk;
880
+ if (ch === null) return { value: undefined, done: true };
881
+ if (i < ch.length) return { value: ch[i++], done: false };
882
+ cell = cell._rest;
883
+ i = 0;
884
+ }
885
+ },
886
+ [Symbol.iterator]() {
887
+ return this;
888
+ },
889
+ };
771
890
  }
772
891
  }
773
892
 
774
893
  LazyIterable.prototype[IIterable] = true; // Closure compatibility
775
894
 
895
+ // One-element chunks: an unchunked seq, realized one element at a time.
896
+ function unchunkedSteps(iter) {
897
+ const step = () => {
898
+ const r = iter.next();
899
+ return r.done ? null : [[r.value], step];
900
+ };
901
+ return step;
902
+ }
903
+
904
+ // f is a zero-arg generator function; its result is an unchunked lazy seq.
776
905
  export function lazy(f) {
777
- return new LazyIterable(f);
906
+ return new LazyIterable(unchunkedSteps(f()));
907
+ }
908
+
909
+ // gen(it) over a hoisted iterator: keeps the input head unpinned (streaming)
910
+ function lazyIter(coll, gen) {
911
+ const it = es6_iterator(iterable(coll));
912
+ return lazy(() => gen(it));
913
+ }
914
+
915
+ // A chunked view of any seqable for chunk-aware ops, preserving chunkedness:
916
+ // cells pass through, arrays slice into CHUNK_SIZE batches, others stay unchunked.
917
+ function chunkCells(coll) {
918
+ if (coll instanceof LazyIterable) return coll;
919
+ if (Array.isArray(coll)) {
920
+ const step = (pos) => () => {
921
+ if (pos >= coll.length) return null;
922
+ const end = Math.min(pos + CHUNK_SIZE, coll.length);
923
+ return [coll.slice(pos, end), step(end)];
924
+ };
925
+ return new LazyIterable(step(0));
926
+ }
927
+ return new LazyIterable(unchunkedSteps(es6_iterator(iterable(coll))));
928
+ }
929
+
930
+ // A cursor over a seq's chunks for realizers: returns a function yielding the
931
+ // next chunk array or null. Drive it with an inline loop (keeps an accumulator a
932
+ // plain local, unlike a callback). Callers keep their own array shortcut.
933
+ function chunkCursor(coll) {
934
+ if (coll instanceof LazyIterable) {
935
+ let cell = coll;
936
+ return () => {
937
+ if (cell === null) return null;
938
+ cell.force();
939
+ const ch = cell.chunk;
940
+ cell = ch === null ? null : cell._rest;
941
+ return ch;
942
+ };
943
+ }
944
+ const it = es6_iterator(iterable(coll));
945
+ return () => {
946
+ const b = [];
947
+ for (let i = 0; i < CHUNK_SIZE; i++) {
948
+ const r = it.next();
949
+ if (r.done) break;
950
+ b.push(r.value);
951
+ }
952
+ return b.length === 0 ? null : b;
953
+ };
954
+ }
955
+
956
+ // Build a lazy seq transforming each input chunk via xf(chunk, baseIndex) -> new
957
+ // chunk array, preserving chunkedness. Empty results are skipped (e.g. filter).
958
+ // baseIndex is the input element count before the chunk, for indexed ops.
959
+ function mapChunks(coll, xf) {
960
+ const src = chunkCells(coll);
961
+ const step = (cell, base) => () => {
962
+ let c = cell;
963
+ let b = base;
964
+ for (;;) {
965
+ c.force();
966
+ const ch = c.chunk;
967
+ if (ch === null) return null;
968
+ const out = xf(ch, b);
969
+ const rest = c._rest;
970
+ b += ch.length;
971
+ if (out.length !== 0) return [out, step(rest, b)];
972
+ c = rest;
973
+ }
974
+ };
975
+ return new LazyIterable(step(src, 0));
778
976
  }
779
977
 
780
978
  export class Cons {
@@ -782,24 +980,35 @@ export class Cons {
782
980
  this.x = x;
783
981
  this.coll = coll;
784
982
  }
785
- *[Symbol.iterator]() {
786
- yield this.x;
787
- yield* iterable(this.coll);
983
+ [Symbol.iterator]() {
984
+ const x = this.x;
985
+ let coll = this.coll;
986
+ let started = false;
987
+ let it = null;
988
+ return {
989
+ next() {
990
+ if (!started) {
991
+ started = true;
992
+ return { value: x, done: false };
993
+ }
994
+ if (!it) {
995
+ it = es6_iterator(iterable(coll));
996
+ coll = null; // release the tail head so a single pass streams
997
+ }
998
+ return it.next();
999
+ },
1000
+ [Symbol.iterator]() {
1001
+ return this;
1002
+ },
1003
+ };
788
1004
  }
789
1005
  }
790
1006
 
791
1007
  export function cons(x, coll) {
792
1008
  return new Cons(x, coll);
793
- // return lazy(function* () {
794
- // yield x;
795
- // yield* iterable(coll);
796
- // });
797
1009
  }
798
1010
 
799
1011
  export function map(f, ...colls) {
800
- // if (! (f instanceof Function)) {
801
- // throw new Error(`Argument f must be a function but is ${typeof(f)}`);
802
- // }
803
1012
  f = __toFn(f);
804
1013
  switch (colls.length) {
805
1014
  case 0:
@@ -822,14 +1031,14 @@ export function map(f, ...colls) {
822
1031
  };
823
1032
  };
824
1033
  case 1:
825
- return lazy(function* () {
826
- for (const x of iterable(colls[0])) {
827
- yield f(x);
828
- }
1034
+ return mapChunks(colls[0], (ch) => {
1035
+ const out = new Array(ch.length);
1036
+ for (let i = 0; i < ch.length; i++) out[i] = f(ch[i]);
1037
+ return out;
829
1038
  });
830
- default:
1039
+ default: {
1040
+ const iters = colls.map((coll) => es6_iterator(iterable(coll)));
831
1041
  return lazy(function* () {
832
- const iters = colls.map((coll) => es6_iterator(iterable(coll)));
833
1042
  while (true) {
834
1043
  const args = [];
835
1044
  for (const i of iters) {
@@ -842,45 +1051,41 @@ export function map(f, ...colls) {
842
1051
  yield f(...args);
843
1052
  }
844
1053
  });
1054
+ }
845
1055
  }
846
1056
  }
847
1057
 
848
- function filter1(pred) {
1058
+ // 0/1 arities pass through to rf; step(rf) is the 2-arity reducer
1059
+ function transducer(step) {
849
1060
  return (rf) => {
850
- return (...args) => {
851
- switch (args.length) {
852
- case 0:
853
- return rf();
854
- case 1:
855
- return rf(args[0]);
856
- case 2: {
857
- const result = args[0];
858
- const input = args[1];
859
- if (truth_(pred(input))) {
860
- return rf(result, input);
861
- } else return result;
862
- }
863
- }
864
- };
1061
+ const s = step(rf);
1062
+ return (...args) =>
1063
+ args.length === 0 ? rf() : args.length === 1 ? rf(args[0]) : s(args[0], args[1]);
865
1064
  };
866
1065
  }
867
1066
 
1067
+ function filter1(pred) {
1068
+ return transducer((rf) => (r, x) => (truth_(pred(x)) ? rf(r, x) : r));
1069
+ }
1070
+
868
1071
  export function filter(pred, coll) {
869
1072
  if (arguments.length === 1) {
870
1073
  return filter1(pred);
871
1074
  }
872
1075
  pred = __toFn(pred);
873
- return lazy(function* () {
874
- for (const x of iterable(coll)) {
875
- if (truth_(pred(x))) {
876
- yield x;
877
- }
1076
+ return mapChunks(coll, (ch) => {
1077
+ const out = [];
1078
+ for (let i = 0; i < ch.length; i++) {
1079
+ const x = ch[i];
1080
+ if (truth_(pred(x))) out.push(x);
878
1081
  }
1082
+ return out;
879
1083
  });
880
1084
  }
881
1085
 
882
1086
  export function filterv(pred, coll) {
883
- return [...filter(pred, coll)];
1087
+ // filter is chunked; vec bulk-appends its chunks
1088
+ return pushAll([], filter(pred, coll));
884
1089
  }
885
1090
 
886
1091
  export function remove(pred, coll) {
@@ -891,19 +1096,10 @@ export function remove(pred, coll) {
891
1096
  }
892
1097
 
893
1098
  function map_indexed1(f) {
894
- return (rf) => {
1099
+ return transducer((rf) => {
895
1100
  let i = -1;
896
- return (...args) => {
897
- switch (args.length) {
898
- case 0:
899
- return rf();
900
- case 1:
901
- return rf(args[0]);
902
- case 2:
903
- return rf(args[0], f(((i = i + 1), i), args[1]));
904
- }
905
- };
906
- };
1101
+ return (r, x) => rf(r, f(++i, x));
1102
+ });
907
1103
  }
908
1104
 
909
1105
  export function map_indexed(f, coll) {
@@ -911,50 +1107,33 @@ export function map_indexed(f, coll) {
911
1107
  if (arguments.length === 1) {
912
1108
  return map_indexed1(f);
913
1109
  }
914
- return lazy(function* () {
915
- let idx = 0;
916
- for (const i of iterable(coll)) {
917
- yield f(idx, i);
918
- idx++;
919
- }
1110
+ return mapChunks(coll, (ch, base) => {
1111
+ const out = new Array(ch.length);
1112
+ for (let i = 0; i < ch.length; i++) out[i] = f(base + i, ch[i]);
1113
+ return out;
920
1114
  });
921
1115
  }
922
1116
 
923
1117
  function keep_indexed2(f, coll) {
924
1118
  f = __toFn(f);
925
- return lazy(function* () {
926
- let idx = 0;
927
- for (const i of iterable(coll)) {
928
- const v = f(idx, i);
929
- if (truth_(v)) yield v;
930
- idx++;
1119
+ return mapChunks(coll, (ch, base) => {
1120
+ const out = [];
1121
+ for (let i = 0; i < ch.length; i++) {
1122
+ const v = f(base + i, ch[i]);
1123
+ if (truth_(v)) out.push(v);
931
1124
  }
1125
+ return out;
932
1126
  });
933
1127
  }
934
1128
 
935
1129
  function keep_indexed1(f) {
936
- return (rf) => {
1130
+ return transducer((rf) => {
937
1131
  let ia = -1;
938
- return (...args) => {
939
- const al = args.length;
940
- if (al === 0) {
941
- return rf();
942
- }
943
- if (al === 1) {
944
- return rf(args[0]);
945
- }
946
- if (al === 2) {
947
- const result = args[0];
948
- const input = args[1];
949
- ia++;
950
- const v = f(ia, input);
951
- if (v == null) {
952
- return result;
953
- }
954
- return rf(result, v);
955
- }
1132
+ return (r, x) => {
1133
+ const v = f(++ia, x);
1134
+ return v == null ? r : rf(r, v);
956
1135
  };
957
- };
1136
+ });
958
1137
  }
959
1138
 
960
1139
  export function keep_indexed(f, coll) {
@@ -970,7 +1149,12 @@ export function str(...xs) {
970
1149
  }
971
1150
 
972
1151
  export function name(x) {
973
- if (typeof x === 'string') return x;
1152
+ if (typeof x === 'string') {
1153
+ // keywords/symbols are strings in squint; name is the part after the "/"
1154
+ // ns separator (consistent with `namespace`, which returns the part before)
1155
+ const i = x.indexOf('/');
1156
+ return i >= 1 ? x.slice(i + 1) : x;
1157
+ }
974
1158
  throw new Error("Doesn't support name: " + typeof x);
975
1159
  }
976
1160
 
@@ -1055,22 +1239,29 @@ export function compare_and_set_BANG_(atm, oldv, newv) {
1055
1239
  }
1056
1240
 
1057
1241
  export function range(begin, end, step) {
1058
- return lazy(function* () {
1059
- let b = begin,
1060
- e = end,
1061
- s = step;
1062
- if (end === undefined) {
1063
- b = 0;
1064
- e = begin;
1065
- }
1066
- let i = b || 0;
1067
- s = step ?? 1;
1068
- const ascending = s >= 0;
1069
- while (e === undefined || (ascending && i < e) || (!ascending && e < i)) {
1070
- yield i;
1242
+ // range is a chunked source: it realizes CHUNK_SIZE values at a time
1243
+ let b = begin,
1244
+ e = end,
1245
+ s = step;
1246
+ if (end === undefined) {
1247
+ b = 0;
1248
+ e = begin;
1249
+ }
1250
+ const start = b || 0;
1251
+ s = step ?? 1;
1252
+ const ascending = s >= 0;
1253
+ const more = (i) => e === undefined || (ascending && i < e) || (!ascending && e < i);
1254
+ const mkStep = (from) => () => {
1255
+ if (!more(from)) return null;
1256
+ const out = [];
1257
+ let i = from;
1258
+ while (out.length < CHUNK_SIZE && more(i)) {
1259
+ out.push(i);
1071
1260
  i += s;
1072
1261
  }
1073
- });
1262
+ return [out, mkStep(i)];
1263
+ };
1264
+ return new LazyIterable(mkStep(start));
1074
1265
  }
1075
1266
 
1076
1267
  export function re_matches(re, s) {
@@ -1143,9 +1334,12 @@ export function mapv(...args) {
1143
1334
  }
1144
1335
  return ret;
1145
1336
  } else {
1146
- var ret = [];
1147
- for (const x of iter) {
1148
- ret.push(f(x));
1337
+ // non-array: map whole chunks straight into the result array
1338
+ const ret = [];
1339
+ const next = chunkCursor(iter);
1340
+ let ch;
1341
+ while ((ch = next()) !== null) {
1342
+ for (let i = 0; i < ch.length; i++) ret.push(f(ch[i]));
1149
1343
  }
1150
1344
  return ret;
1151
1345
  }
@@ -1153,12 +1347,32 @@ export function mapv(...args) {
1153
1347
  return [...map(...args)];
1154
1348
  }
1155
1349
 
1156
- export function vec(x) {
1157
- if (array_QMARK_(x)) {
1158
- // return original, no need to clone the entire thing
1159
- return x;
1350
+ // Append every element of `from` to array `out`, bulk-appending whole chunks
1351
+ // for a chunked seq. Returns out.
1352
+ function pushAll(out, from) {
1353
+ if (from instanceof LazyIterable) {
1354
+ let cell = from;
1355
+ for (;;) {
1356
+ cell.force();
1357
+ const ch = cell.chunk;
1358
+ if (ch === null) return out;
1359
+ Array.prototype.push.apply(out, ch);
1360
+ cell = cell._rest;
1361
+ }
1160
1362
  }
1161
- return [...iterable(x)];
1363
+ for (const x of iterable(from)) out.push(x);
1364
+ return out;
1365
+ }
1366
+
1367
+ // Materialize a seq into a fresh, mutable array (bulk-copies chunked seqs).
1368
+ function toArray(coll) {
1369
+ if (coll instanceof LazyIterable) return pushAll([], coll);
1370
+ return [...iterable(coll)];
1371
+ }
1372
+
1373
+ export function vec(x) {
1374
+ if (array_QMARK_(x)) return x;
1375
+ return pushAll([], x);
1162
1376
  }
1163
1377
 
1164
1378
  export function set(coll) {
@@ -1223,12 +1437,54 @@ export function array_QMARK_(x) {
1223
1437
  return Array.isArray(x);
1224
1438
  }
1225
1439
 
1440
+ const CONCAT_DONE = Symbol('concat-done');
1441
+
1226
1442
  function concat1(colls) {
1227
- return lazy(function* () {
1228
- for (const coll of colls) {
1229
- yield* iterable(coll);
1443
+ // chunk-aware: pass each coll's chunks through, preserving chunkedness. Each
1444
+ // coll head is released once consumed so a single pass streams.
1445
+ const isArr = Array.isArray(colls);
1446
+ const arr = isArr ? colls.slice() : null;
1447
+ const collIter = isArr ? null : es6_iterator(iterable(colls));
1448
+ let idx = 0;
1449
+ const nextColl = () => {
1450
+ if (isArr) {
1451
+ if (idx >= arr.length) return CONCAT_DONE;
1452
+ const c = arr[idx];
1453
+ arr[idx] = null;
1454
+ idx++;
1455
+ return c;
1230
1456
  }
1231
- });
1457
+ const r = collIter.next();
1458
+ return r.done ? CONCAT_DONE : r.value;
1459
+ };
1460
+ // src is null, a chunked cell, or {a, pos} for an array (raw-array fast path:
1461
+ // no per-coll wrapper). Continuations carry immutable state, so a single pass
1462
+ // streams.
1463
+ const step = (src) => () => {
1464
+ for (;;) {
1465
+ if (src !== null) {
1466
+ if (src instanceof LazyIterable) {
1467
+ src.force();
1468
+ if (src.chunk !== null) return [src.chunk, step(src._rest)];
1469
+ src = null;
1470
+ } else {
1471
+ const a = src.a;
1472
+ const pos = src.pos;
1473
+ if (pos < a.length) {
1474
+ const end = Math.min(pos + CHUNK_SIZE, a.length);
1475
+ // slice (copy) so a cached chunk never aliases the caller's array
1476
+ return [a.slice(pos, end), step({ a, pos: end })];
1477
+ }
1478
+ src = null;
1479
+ }
1480
+ }
1481
+ const nc = nextColl();
1482
+ if (nc === CONCAT_DONE) return null;
1483
+ src =
1484
+ nc instanceof LazyIterable ? nc : Array.isArray(nc) ? { a: nc, pos: 0 } : chunkCells(nc);
1485
+ }
1486
+ };
1487
+ return new LazyIterable(step(null));
1232
1488
  }
1233
1489
 
1234
1490
  export function concat(...colls) {
@@ -1243,10 +1499,8 @@ concat[IApply__apply] = (colls) => {
1243
1499
  export function mapcat(f, ...colls) {
1244
1500
  if (colls.length === 0) {
1245
1501
  return comp(map(f), cat);
1246
- } else {
1247
- const mapped = map(f, ...colls);
1248
- return concat1(mapped);
1249
1502
  }
1503
+ return concat1(map(f, ...colls));
1250
1504
  }
1251
1505
 
1252
1506
  export function identity(x) {
@@ -1254,8 +1508,8 @@ export function identity(x) {
1254
1508
  }
1255
1509
 
1256
1510
  export function interleave(...colls) {
1511
+ const iters = colls.map((coll) => es6_iterator(iterable(coll)));
1257
1512
  return lazy(function* () {
1258
- const iters = colls.map((coll) => es6_iterator(iterable(coll)));
1259
1513
  while (true) {
1260
1514
  const res = [];
1261
1515
  for (const i of iters) {
@@ -1271,30 +1525,17 @@ export function interleave(...colls) {
1271
1525
  }
1272
1526
 
1273
1527
  function interpose1(sep) {
1274
- return (rf) => {
1528
+ return transducer((rf) => {
1275
1529
  let started = false;
1276
- return (...args) => {
1277
- switch (args.length) {
1278
- case 0:
1279
- return rf();
1280
- case 1:
1281
- return rf(args[0]);
1282
- case 2: {
1283
- if (started) {
1284
- const sepr = rf(args[0], sep);
1285
- if (reduced_QMARK_(sepr)) {
1286
- return sepr;
1287
- } else {
1288
- return rf(sepr, args[1]);
1289
- }
1290
- } else {
1291
- started = true;
1292
- return rf(args[0], args[1]);
1293
- }
1294
- }
1530
+ return (r, x) => {
1531
+ if (!started) {
1532
+ started = true;
1533
+ return rf(r, x);
1295
1534
  }
1535
+ const sepr = rf(r, sep);
1536
+ return reduced_QMARK_(sepr) ? sepr : rf(sepr, x);
1296
1537
  };
1297
- };
1538
+ });
1298
1539
  }
1299
1540
 
1300
1541
  export function interpose(sep, coll) {
@@ -1386,10 +1627,10 @@ export const partitionv = partition; // partition already returns a lazy of arra
1386
1627
  export const partitionv_all = partition_all;
1387
1628
 
1388
1629
  function partitionInternal(n, step, pad, coll, all) {
1389
- return lazy(function* () {
1630
+ return lazyIter(coll, function* (it) {
1390
1631
  let p = [];
1391
1632
  let i = 0;
1392
- for (const x of iterable(coll)) {
1633
+ for (const x of it) {
1393
1634
  if (i < n) {
1394
1635
  p.push(x);
1395
1636
  if (p.length === n) {
@@ -1461,8 +1702,8 @@ export function partition_by(f, coll) {
1461
1702
  if (arguments.length === 1) {
1462
1703
  return partition_by1(f);
1463
1704
  }
1705
+ const iter = es6_iterator(coll);
1464
1706
  return lazy(function* () {
1465
- const iter = es6_iterator(coll);
1466
1707
  const _fst = iter.next();
1467
1708
  if (_fst.done) {
1468
1709
  yield* null;
@@ -1495,7 +1736,7 @@ export function partition_by(f, coll) {
1495
1736
  export function empty(coll) {
1496
1737
  const type = typeConst(coll);
1497
1738
  if (type != null) {
1498
- return emptyOfType(type);
1739
+ return copyMeta(coll, emptyOfType(type));
1499
1740
  } else {
1500
1741
  throw new Error(`Can't create empty of ${typeof coll}`);
1501
1742
  }
@@ -1561,7 +1802,13 @@ export function into(...args) {
1561
1802
  case 1:
1562
1803
  return args[0];
1563
1804
  case 2:
1564
- return conj(args[0] ?? [], ...iterable(args[1]));
1805
+ // vector target bulk-appends chunks (copy preserves metadata); lists conj
1806
+ // at the head, other targets need conj!
1807
+ to = args[0] ?? [];
1808
+ if (Array.isArray(to) && !(to instanceof List)) {
1809
+ return pushAll(copy(to), args[1]);
1810
+ }
1811
+ return reduce(conj_BANG_, copy(to), args[1]);
1565
1812
  case 3:
1566
1813
  to = args[0];
1567
1814
  xform = args[1];
@@ -1612,42 +1859,23 @@ export function ensure_reduced(x) {
1612
1859
  }
1613
1860
 
1614
1861
  function take1(n) {
1615
- return (rf) => {
1862
+ return transducer((rf) => {
1616
1863
  let na = n;
1617
- return (...args) => {
1618
- const al = args.length;
1619
- if (al === 0) {
1620
- return rf();
1621
- }
1622
- if (al === 1) {
1623
- const result = args[0];
1624
- return rf(result);
1625
- }
1626
- if (al === 2) {
1627
- let result = args[0];
1628
- const input = args[1];
1629
- const n = na;
1630
- const nn = ((na = na - 1), na);
1631
- if (n > 0) {
1632
- result = rf(result, input);
1633
- }
1634
- if (!(nn > 0)) {
1635
- return ensure_reduced(result);
1636
- } else {
1637
- return result;
1638
- }
1639
- }
1864
+ return (r, x) => {
1865
+ const nn = --na + 1;
1866
+ if (nn > 0) r = rf(r, x);
1867
+ return nn > 1 ? r : ensure_reduced(r);
1640
1868
  };
1641
- };
1869
+ });
1642
1870
  }
1643
1871
 
1644
1872
  export function take(n, coll) {
1645
1873
  if (arguments.length === 1) {
1646
1874
  return take1(n);
1647
1875
  }
1648
- return lazy(function* () {
1876
+ return lazyIter(coll, function* (it) {
1649
1877
  let i = n - 1;
1650
- for (const x of iterable(coll)) {
1878
+ for (const x of it) {
1651
1879
  if (i-- >= 0) {
1652
1880
  yield x;
1653
1881
  }
@@ -1681,22 +1909,7 @@ export function take_last(n, coll) {
1681
1909
  }
1682
1910
 
1683
1911
  function take_while1(pred) {
1684
- return (rf) => {
1685
- return (...args) => {
1686
- const al = args.length;
1687
- if (al === 0) return rf();
1688
- if (al === 1) return rf(args[0]);
1689
- if (al === 2) {
1690
- const result = args[0];
1691
- const input = args[1];
1692
- if (truth_(pred(input))) {
1693
- return rf(result, input);
1694
- } else {
1695
- return reduced(result);
1696
- }
1697
- }
1698
- };
1699
- };
1912
+ return transducer((rf) => (r, x) => (truth_(pred(x)) ? rf(r, x) : reduced(r)));
1700
1913
  }
1701
1914
 
1702
1915
  export function take_while(pred, coll) {
@@ -1704,8 +1917,8 @@ export function take_while(pred, coll) {
1704
1917
  if (arguments.length === 1) {
1705
1918
  return take_while1(pred);
1706
1919
  }
1707
- return lazy(function* () {
1708
- for (const o of iterable(coll)) {
1920
+ return lazyIter(coll, function* (it) {
1921
+ for (const o of it) {
1709
1922
  if (truth_(pred(o))) yield o;
1710
1923
  else return;
1711
1924
  }
@@ -1713,23 +1926,10 @@ export function take_while(pred, coll) {
1713
1926
  }
1714
1927
 
1715
1928
  function take_nth1(n) {
1716
- return (rf) => {
1929
+ return transducer((rf) => {
1717
1930
  let ia = -1;
1718
- return (...args) => {
1719
- const al = args.length;
1720
- if (al === 0) return rf();
1721
- if (al === 1) return rf(args[0]);
1722
- if (al === 2) {
1723
- const result = args[0];
1724
- const input = args[1];
1725
- ia++;
1726
- const i = ia;
1727
- if (rem(i, n) === 0) {
1728
- return rf(result, input);
1729
- } else return result;
1730
- }
1731
- };
1732
- };
1931
+ return (r, x) => (rem(++ia, n) === 0 ? rf(r, x) : r);
1932
+ });
1733
1933
  }
1734
1934
 
1735
1935
  export function take_nth(n, coll) {
@@ -1738,9 +1938,9 @@ export function take_nth(n, coll) {
1738
1938
  return repeat(first(coll));
1739
1939
  }
1740
1940
 
1741
- return lazy(function* () {
1941
+ return lazyIter(coll, function* (it) {
1742
1942
  let i = 0;
1743
- for (const x of iterable(coll)) {
1943
+ for (const x of it) {
1744
1944
  if (i % n === 0) {
1745
1945
  yield x;
1746
1946
  }
@@ -1763,33 +1963,15 @@ export function cycle(coll) {
1763
1963
  }
1764
1964
 
1765
1965
  function drop1(n) {
1766
- return (rf) => {
1966
+ return transducer((rf) => {
1767
1967
  let na = n;
1768
- return (...args) => {
1769
- const al = args.length;
1770
- if (al === 0) {
1771
- return rf();
1772
- }
1773
- if (al === 1) {
1774
- return rf(args[0]);
1775
- }
1776
- if (al === 2) {
1777
- const result = args[0];
1778
- const input = args[1];
1779
- const n = na;
1780
- na--;
1781
- if (n > 0) {
1782
- return result;
1783
- } else return rf(result, input);
1784
- }
1785
- };
1786
- };
1968
+ return (r, x) => (na-- > 0 ? r : rf(r, x));
1969
+ });
1787
1970
  }
1788
1971
 
1789
1972
  export function drop(n, xs) {
1790
1973
  if (arguments.length === 1) return drop1(n);
1791
- return lazy(function* () {
1792
- const iter = _iterator(iterable(xs));
1974
+ return lazyIter(xs, function* (iter) {
1793
1975
  for (let x = 0; x < n; x++) {
1794
1976
  iter.next();
1795
1977
  }
@@ -1798,36 +1980,20 @@ export function drop(n, xs) {
1798
1980
  }
1799
1981
 
1800
1982
  function drop_while1(pred) {
1801
- return (rf) => {
1983
+ return transducer((rf) => {
1802
1984
  let da = true;
1803
- return (...args) => {
1804
- const al = args.length;
1805
- if (al === 0) {
1806
- return rf();
1807
- }
1808
- if (al === 1) {
1809
- return rf(args[0]);
1810
- }
1811
- if (al === 2) {
1812
- const isDrop = da;
1813
- const result = args[0];
1814
- const input = args[1];
1815
- if (isDrop && truth_(pred(input))) {
1816
- return result;
1817
- } else {
1818
- da = null;
1819
- return rf(result, input);
1820
- }
1821
- }
1985
+ return (r, x) => {
1986
+ if (da && truth_(pred(x))) return r;
1987
+ da = false;
1988
+ return rf(r, x);
1822
1989
  };
1823
- };
1990
+ });
1824
1991
  }
1825
1992
 
1826
1993
  export function drop_while(pred, xs) {
1827
1994
  pred = __toFn(pred);
1828
1995
  if (arguments.length === 1) return drop_while1(pred);
1829
- return lazy(function* () {
1830
- const iter = _iterator(iterable(xs));
1996
+ return lazyIter(xs, function* (iter) {
1831
1997
  while (true) {
1832
1998
  const nextItem = iter.next();
1833
1999
  if (nextItem.done) {
@@ -1844,28 +2010,21 @@ export function drop_while(pred, xs) {
1844
2010
  }
1845
2011
 
1846
2012
  function distinct1() {
1847
- return (rf) => {
2013
+ return transducer((rf) => {
1848
2014
  const seen = new Set();
1849
- return (...args) => {
1850
- const al = args.length;
1851
- if (al === 0) return rf();
1852
- if (al === 1) return rf(args[0]);
1853
- if (al === 2) {
1854
- const result = args[0];
1855
- const input = args[1];
1856
- if (seen.has(input)) return result;
1857
- seen.add(input);
1858
- return rf(result, input);
1859
- }
2015
+ return (r, x) => {
2016
+ if (seen.has(x)) return r;
2017
+ seen.add(x);
2018
+ return rf(r, x);
1860
2019
  };
1861
- };
2020
+ });
1862
2021
  }
1863
2022
 
1864
2023
  export function distinct(coll) {
1865
2024
  if (arguments.length === 0) return distinct1();
1866
- return lazy(function* () {
2025
+ return lazyIter(coll, function* (it) {
1867
2026
  const seen = new Set();
1868
- for (const x of iterable(coll)) {
2027
+ for (const x of it) {
1869
2028
  if (!seen.has(x)) yield x;
1870
2029
  seen.add(x);
1871
2030
  }
@@ -1876,28 +2035,21 @@ export function distinct(coll) {
1876
2035
  const DEDUPE_NONE = Symbol('dedupe-none');
1877
2036
 
1878
2037
  function dedupe1() {
1879
- return (rf) => {
2038
+ return transducer((rf) => {
1880
2039
  let prev = DEDUPE_NONE;
1881
- return (...args) => {
1882
- const al = args.length;
1883
- if (al === 0) return rf();
1884
- if (al === 1) return rf(args[0]);
1885
- if (al === 2) {
1886
- const result = args[0];
1887
- const input = args[1];
1888
- const skip = prev !== DEDUPE_NONE && truth_(_EQ_(prev, input));
1889
- prev = input;
1890
- return skip ? result : rf(result, input);
1891
- }
2040
+ return (r, x) => {
2041
+ const skip = prev !== DEDUPE_NONE && truth_(_EQ_(prev, x));
2042
+ prev = x;
2043
+ return skip ? r : rf(r, x);
1892
2044
  };
1893
- };
2045
+ });
1894
2046
  }
1895
2047
 
1896
2048
  export function dedupe(coll) {
1897
2049
  if (arguments.length === 0) return dedupe1();
1898
- return lazy(function* () {
2050
+ return lazyIter(coll, function* (it) {
1899
2051
  let prev = DEDUPE_NONE;
1900
- for (const x of iterable(coll)) {
2052
+ for (const x of it) {
1901
2053
  if (prev === DEDUPE_NONE || !truth_(_EQ_(prev, x))) yield x;
1902
2054
  prev = x;
1903
2055
  }
@@ -1937,8 +2089,14 @@ export function fnil(f, x, ...xs) {
1937
2089
 
1938
2090
  export function every_QMARK_(pred, coll) {
1939
2091
  pred = __toFn(pred);
1940
- for (const x of iterable(coll)) {
1941
- if (!pred(x)) return false;
2092
+ if (Array.isArray(coll)) {
2093
+ for (let i = 0; i < coll.length; i++) if (!pred(coll[i])) return false;
2094
+ return true;
2095
+ }
2096
+ const next = chunkCursor(coll);
2097
+ let ch;
2098
+ while ((ch = next()) !== null) {
2099
+ for (let i = 0; i < ch.length; i++) if (!pred(ch[i])) return false;
1942
2100
  }
1943
2101
  return true;
1944
2102
  }
@@ -1948,36 +2106,27 @@ export function not_every_QMARK_(pred, coll) {
1948
2106
  }
1949
2107
 
1950
2108
  function keep1(pred) {
1951
- return (rf) => {
1952
- return (...args) => {
1953
- const al = args.length;
1954
- if (al === 0) return rf();
1955
- if (al === 1) return rf(args[0]);
1956
- if (al === 2) {
1957
- const result = args[0];
1958
- const input = args[1];
1959
- const v = pred(input);
1960
- if (v == null) return result;
1961
- return rf(result, v);
1962
- }
1963
- };
1964
- };
2109
+ return transducer((rf) => (r, x) => {
2110
+ const v = pred(x);
2111
+ return v == null ? r : rf(r, v);
2112
+ });
1965
2113
  }
1966
2114
 
1967
2115
  export function keep(pred, coll) {
1968
2116
  pred = __toFn(pred);
1969
2117
  if (arguments.length === 1) return keep1(pred);
1970
- return lazy(function* () {
1971
- for (const o of iterable(coll)) {
1972
- const res = pred(o);
1973
- if (truth_(res)) yield res;
2118
+ return mapChunks(coll, (ch) => {
2119
+ const out = [];
2120
+ for (let i = 0; i < ch.length; i++) {
2121
+ const res = pred(ch[i]);
2122
+ if (truth_(res)) out.push(res);
1974
2123
  }
2124
+ return out;
1975
2125
  });
1976
2126
  }
1977
2127
 
1978
2128
  export function reverse(coll) {
1979
- coll = iterable(coll);
1980
- return [...coll].reverse();
2129
+ return toArray(coll).reverse();
1981
2130
  }
1982
2131
 
1983
2132
  export function sort(f, coll) {
@@ -1986,9 +2135,8 @@ export function sort(f, coll) {
1986
2135
  f = undefined;
1987
2136
  }
1988
2137
  f = __toFn(f);
1989
- coll = iterable(coll);
1990
- // we need to clone coll since .sort works in place and .toSorted isn't available on Node < 20
1991
- const clone = [...coll];
2138
+ // need a copy since .sort works in place and .toSorted isn't on Node < 20
2139
+ const clone = toArray(coll);
1992
2140
  // result is guaranteed to be stable since ES2019, like CLJS
1993
2141
  return clone.sort(f || compare);
1994
2142
  }
@@ -2028,7 +2176,7 @@ export function sort_by(keyfn, comp, coll) {
2028
2176
  }
2029
2177
 
2030
2178
  export function shuffle(coll) {
2031
- const result = [...coll];
2179
+ const result = toArray(coll);
2032
2180
  let remaining = result.length;
2033
2181
  while (remaining) {
2034
2182
  const i = Math.floor(Math.random() * remaining--);
@@ -2042,9 +2190,20 @@ export function shuffle(coll) {
2042
2190
 
2043
2191
  export function some(pred, coll) {
2044
2192
  pred = __toFn(pred);
2045
- for (const o of iterable(coll)) {
2046
- const res = pred(o);
2047
- if (truth_(res)) return res;
2193
+ if (Array.isArray(coll)) {
2194
+ for (let i = 0; i < coll.length; i++) {
2195
+ const res = pred(coll[i]);
2196
+ if (truth_(res)) return res;
2197
+ }
2198
+ return undefined;
2199
+ }
2200
+ const next = chunkCursor(coll);
2201
+ let ch;
2202
+ while ((ch = next()) !== null) {
2203
+ for (let i = 0; i < ch.length; i++) {
2204
+ const res = pred(ch[i]);
2205
+ if (truth_(res)) return res;
2206
+ }
2048
2207
  }
2049
2208
  return undefined;
2050
2209
  }
@@ -2130,22 +2289,16 @@ export function frequencies(coll) {
2130
2289
  return res;
2131
2290
  }
2132
2291
 
2133
- export class LazySeq {
2292
+ // The lazy-seq macro emits `new LazySeq(() => body)`: a LazyIterable whose step
2293
+ // evaluates the body thunk on first force and reads it unchunked.
2294
+ export class LazySeq extends LazyIterable {
2134
2295
  constructor(f) {
2135
- this.f = f;
2136
- this.res = undefined;
2137
- }
2138
- *[Symbol.iterator]() {
2139
- if (this.res === undefined) {
2140
- this.res = this.f();
2141
- this.f = null;
2142
- }
2143
- yield* iterable(this.res);
2296
+ super(() => unchunkedSteps(es6_iterator(iterable(f())))());
2144
2297
  }
2145
2298
  }
2146
2299
 
2147
2300
  export function butlast(coll) {
2148
- const x = [...iterable(coll)];
2301
+ const x = toArray(coll);
2149
2302
  x.pop();
2150
2303
  return x.length > 0 ? x : null;
2151
2304
  }
@@ -2169,10 +2322,11 @@ export function count(coll) {
2169
2322
  if (typeof len === 'number') {
2170
2323
  return len;
2171
2324
  }
2325
+ // sum chunk lengths instead of counting elements one by one
2326
+ const next = chunkCursor(coll);
2172
2327
  let ret = 0;
2173
- for (const _ of iterable(coll)) {
2174
- ret++;
2175
- }
2328
+ let ch;
2329
+ while ((ch = next()) !== null) ret += ch.length;
2176
2330
  return ret;
2177
2331
  }
2178
2332
 
@@ -2240,6 +2394,12 @@ export function aset(arr, idx, val, ...more) {
2240
2394
  }
2241
2395
 
2242
2396
  export function dorun(x) {
2397
+ // only a lazy seq needs forcing; realized colls are walked natively
2398
+ if (x instanceof LazyIterable) {
2399
+ const next = chunkCursor(x);
2400
+ while (next() !== null);
2401
+ return null;
2402
+ }
2243
2403
  for (const _ of iterable(x)) {
2244
2404
  // nothing here, just consume for side effects
2245
2405
  }
@@ -2501,6 +2661,62 @@ export function string_QMARK_(s) {
2501
2661
  return typeof s === 'string';
2502
2662
  }
2503
2663
 
2664
+ export function unchecked_int(x) {
2665
+ return Math.trunc(x);
2666
+ }
2667
+
2668
+ export function unchecked_inc_int(x) {
2669
+ return x + 1;
2670
+ }
2671
+
2672
+ export function unchecked_dec_int(x) {
2673
+ return x - 1;
2674
+ }
2675
+
2676
+ export function unchecked_add_int(x, y) {
2677
+ return x + y;
2678
+ }
2679
+
2680
+ // keywords/symbols are strings in squint; namespace is the part before "/"
2681
+ export function namespace(x) {
2682
+ // keywords/symbols are strings in squint; namespace is the part before the
2683
+ // "/" ns separator (consistent with `name`, which returns the part after).
2684
+ // i >= 1 so a leading "/" yields a nil namespace rather than an empty one.
2685
+ const i = x.indexOf('/');
2686
+ return i >= 1 ? x.slice(0, i) : null;
2687
+ }
2688
+
2689
+ // squint has no keyword type; keywords are strings, so keyword/keyword? are
2690
+ // string-based. (keyword name) or (keyword ns name).
2691
+ export function keyword(arg1, arg2) {
2692
+ if (arg2 !== undefined) {
2693
+ return (arg1 != null ? arg1 + '/' : '') + arg2;
2694
+ }
2695
+ return arg1;
2696
+ }
2697
+
2698
+ export function keyword_QMARK_(x) {
2699
+ return typeof x === 'string';
2700
+ }
2701
+
2702
+ // squint has no symbol type either; symbols are strings
2703
+ export function symbol_QMARK_(x) {
2704
+ return typeof x === 'string';
2705
+ }
2706
+
2707
+ // squint has no first-class Var objects (#'x emits the value), so var? is false
2708
+ export function var_QMARK_(_x) {
2709
+ return false;
2710
+ }
2711
+
2712
+ export function simple_keyword_QMARK_(x) {
2713
+ return typeof x === 'string' && !x.includes('/');
2714
+ }
2715
+
2716
+ export function qualified_keyword_QMARK_(x) {
2717
+ return typeof x === 'string' && x.includes('/');
2718
+ }
2719
+
2504
2720
  export function coll_QMARK_(coll) {
2505
2721
  return typeConst(coll) != undefined;
2506
2722
  }
@@ -2554,8 +2770,6 @@ export function neg_int_QMARK_(x) {
2554
2770
  return int_QMARK_(x) && x < 0;
2555
2771
  }
2556
2772
 
2557
- const _metaSym = Symbol('meta');
2558
-
2559
2773
  export function meta(x) {
2560
2774
  if (x instanceof Object) {
2561
2775
  return x[_metaSym];
@@ -2604,7 +2818,7 @@ export function bounded_count(n, coll) {
2604
2818
  export function find(m, k) {
2605
2819
  const v = get(m, k);
2606
2820
  if (v !== undefined) {
2607
- return [k, v];
2821
+ return tagMapEntry([k, v]);
2608
2822
  }
2609
2823
  }
2610
2824
 
@@ -2773,7 +2987,9 @@ export function transient$(x) {
2773
2987
  }
2774
2988
 
2775
2989
  export function persistent_BANG_(x) {
2776
- return Object.freeze(x);
2990
+ // no Object.freeze: persistent structures stay extensible so symbol-keyed
2991
+ // metadata can be attached
2992
+ return x;
2777
2993
  }
2778
2994
 
2779
2995
  class SortedSet {
@@ -3060,6 +3276,7 @@ export function volatile_BANG_(x) {
3060
3276
 
3061
3277
  export function vreset_BANG_(vol, v) {
3062
3278
  vol.v = v;
3279
+ return v;
3063
3280
  }
3064
3281
 
3065
3282
  function toEDN(value, seen = new WeakSet()) {