squint-cljs 0.13.194 → 0.14.196

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.
@@ -70,7 +70,7 @@ function dequal(foo, bar) {
70
70
 
71
71
  // LazyIterable falls through to the sequential-equality path below; it is an
72
72
  // object but must compare element-wise, not by enumerable properties
73
- if ((!ctor || typeof foo === 'object') && !(foo instanceof LazyIterable)) {
73
+ if ((!ctor || typeof foo === 'object') && foo[TYPE_TAG] !== LAZY_ITERABLE_TYPE) {
74
74
  len = 0;
75
75
  for (const k in foo) {
76
76
  if (has.call(foo, k) && ++len && !has.call(bar, k)) return false;
@@ -86,12 +86,12 @@ function dequal(foo, bar) {
86
86
  // collections keep their fast paths.
87
87
  if (
88
88
  foo && bar &&
89
- (Array.isArray(foo) || foo instanceof LazyIterable) &&
90
- (Array.isArray(bar) || bar instanceof LazyIterable)
89
+ (Array.isArray(foo) || foo[TYPE_TAG] === LAZY_ITERABLE_TYPE) &&
90
+ (Array.isArray(bar) || bar[TYPE_TAG] === LAZY_ITERABLE_TYPE)
91
91
  ) {
92
92
  const fi = foo[Symbol.iterator]();
93
93
  const bi = bar[Symbol.iterator]();
94
- while (true) {
94
+ for (;;) {
95
95
  const a = fi.next();
96
96
  const b = bi.next();
97
97
  if (a.done || b.done) return !!(a.done && b.done);
@@ -249,6 +249,21 @@ const LIST_TYPE = 4;
249
249
  const SET_TYPE = 5;
250
250
  const LAZY_ITERABLE_TYPE = 6;
251
251
 
252
+ // type tag set in each collection ctor, read by typeConst (DCE: no instanceof).
253
+ const TYPE_TAG = Symbol('squint.lang.type');
254
+
255
+ // @__NO_SIDE_EFFECTS__ lets bundlers drop unused calls, so a computed-key class
256
+ // or IApply fn shakes out (neither does alone). See doc/dev/dce.md.
257
+ // @__NO_SIDE_EFFECTS__
258
+ function defclass(c) {
259
+ return c;
260
+ }
261
+ // @__NO_SIDE_EFFECTS__
262
+ function withApply(f, applyFn) {
263
+ f[IApply__apply] = applyFn;
264
+ return f;
265
+ }
266
+
252
267
  function emptyOfType(type) {
253
268
  switch (type) {
254
269
  case MAP_TYPE:
@@ -273,6 +288,10 @@ function isObj(coll) {
273
288
  return coll.constructor === Object;
274
289
  }
275
290
 
291
+ function isVectorArray(x) {
292
+ return Array.isArray(x) && x[TYPE_TAG] !== LIST_TYPE;
293
+ }
294
+
276
295
  export function object_QMARK_(coll) {
277
296
  return coll != null && isObj(coll);
278
297
  }
@@ -287,13 +306,10 @@ function typeConst(obj) {
287
306
  }
288
307
  if (obj instanceof Map) return MAP_TYPE;
289
308
  if (obj instanceof Set) return SET_TYPE;
290
- if (obj instanceof List) return LIST_TYPE;
291
- if (Array.isArray(obj)) return ARRAY_TYPE;
292
- if (obj instanceof LazyIterable) return LAZY_ITERABLE_TYPE;
293
- if (obj instanceof SortedSet) return SET_TYPE;
294
-
295
- // everything more specific than Object should go before this
296
- if (obj instanceof Object) return OBJECT_TYPE;
309
+ // brand, not instanceof, so dispatch does not reference the classes
310
+ const tag = obj[TYPE_TAG];
311
+ if (tag !== undefined) return tag;
312
+ if (isVectorArray(obj)) return ARRAY_TYPE;
297
313
 
298
314
  return undefined;
299
315
  }
@@ -538,8 +554,21 @@ export function dec(n) {
538
554
  return n - 1;
539
555
  }
540
556
 
557
+ export const _STAR_print_newline_STAR_ = { val: false };
558
+ export const _STAR_print_fn_STAR_ = { val: (s) => console.log(s) };
559
+ export const _STAR_print_err_fn_STAR_ = { val: (s) => console.error(s) };
560
+
561
+ export function print(...args) {
562
+ _STAR_print_fn_STAR_.val(args.map((v) => toEDN(v, undefined, false)).join(' '));
563
+ }
564
+
541
565
  export function println(...args) {
542
- console.log(...args);
566
+ print(...args);
567
+ if (_STAR_print_newline_STAR_.val) _STAR_print_fn_STAR_.val('\n');
568
+ }
569
+
570
+ export function pr(...xs) {
571
+ _STAR_print_fn_STAR_.val(pr_str(...xs));
543
572
  }
544
573
 
545
574
  export function nth(coll, idx, orElse) {
@@ -809,8 +838,8 @@ function* _reductions2(f, s) {
809
838
  }
810
839
 
811
840
  function* _reductions3(f, init, coll) {
812
- let i = init,
813
- rst = coll;
841
+ let i = init;
842
+ const rst = coll;
814
843
  while (true) {
815
844
  if (reduced_QMARK_(i)) {
816
845
  yield i.value;
@@ -850,8 +879,11 @@ const CHUNK_SIZE = 32;
850
879
 
851
880
  // One cell of a self-caching chunked seq. `step` is a thunk returning
852
881
  // [nonEmptyChunkArray, nextStep] or null at the end. See doc/dev/lazy-seqs.md.
882
+ const LazyIterable = defclass(
853
883
  class LazyIterable {
854
884
  constructor(step) {
885
+ this[TYPE_TAG] = LAZY_ITERABLE_TYPE;
886
+ this[IIterable] = true; // Closure compatibility
855
887
  this.step = step;
856
888
  this.realized = false;
857
889
  this.chunk = null; // array, or null when this is the terminal (empty) cell
@@ -888,9 +920,18 @@ class LazyIterable {
888
920
  },
889
921
  };
890
922
  }
923
+ // CLJS supports (.indexOf coll x) on seqs; mirror it so lazy seqs work like
924
+ // arrays. Uses value equality, like cljs.core, and returns -1 when absent.
925
+ indexOf(x, fromIndex = 0) {
926
+ let i = 0;
927
+ for (const v of this) {
928
+ if (i >= fromIndex && dequal(v, x)) return i;
929
+ i++;
930
+ }
931
+ return -1;
932
+ }
891
933
  }
892
-
893
- LazyIterable.prototype[IIterable] = true; // Closure compatibility
934
+ );
894
935
 
895
936
  // One-element chunks: an unchunked seq, realized one element at a time.
896
937
  function unchunkedSteps(iter) {
@@ -975,7 +1016,8 @@ function mapChunks(coll, xf) {
975
1016
  return new LazyIterable(step(src, 0));
976
1017
  }
977
1018
 
978
- export class Cons {
1019
+ export const Cons = defclass(
1020
+ class Cons {
979
1021
  constructor(x, coll) {
980
1022
  this.x = x;
981
1023
  this.coll = coll;
@@ -1003,6 +1045,7 @@ export class Cons {
1003
1045
  };
1004
1046
  }
1005
1047
  }
1048
+ );
1006
1049
 
1007
1050
  export function cons(x, coll) {
1008
1051
  return new Cons(x, coll);
@@ -1318,7 +1361,7 @@ export function vector(...args) {
1318
1361
  export const array = vector;
1319
1362
 
1320
1363
  export function vector_QMARK_(x) {
1321
- return typeConst(x) === ARRAY_TYPE;
1364
+ return isVectorArray(x);
1322
1365
  }
1323
1366
 
1324
1367
  export function mapv(...args) {
@@ -1371,7 +1414,7 @@ function toArray(coll) {
1371
1414
  }
1372
1415
 
1373
1416
  export function vec(x) {
1374
- if (array_QMARK_(x)) return x;
1417
+ if (isVectorArray(x)) return x;
1375
1418
  return pushAll([], x);
1376
1419
  }
1377
1420
 
@@ -1416,6 +1459,7 @@ export function constantly(x) {
1416
1459
  class List extends Array {
1417
1460
  constructor(...args) {
1418
1461
  super();
1462
+ this[TYPE_TAG] = LIST_TYPE;
1419
1463
  this.push(...args);
1420
1464
  }
1421
1465
  }
@@ -1487,14 +1531,10 @@ function concat1(colls) {
1487
1531
  return new LazyIterable(step(null));
1488
1532
  }
1489
1533
 
1490
- export function concat(...colls) {
1491
- return concat1(colls);
1492
- }
1493
-
1494
- // lazy seqable argument
1495
- concat[IApply__apply] = (colls) => {
1496
- return concat1(colls);
1497
- };
1534
+ export const concat = withApply(
1535
+ (...colls) => concat1(colls),
1536
+ (colls) => concat1(colls), // lazy seqable argument
1537
+ );
1498
1538
 
1499
1539
  export function mapcat(f, ...colls) {
1500
1540
  if (colls.length === 0) {
@@ -1805,7 +1845,7 @@ export function into(...args) {
1805
1845
  // vector target bulk-appends chunks (copy preserves metadata); lists conj
1806
1846
  // at the head, other targets need conj!
1807
1847
  to = args[0] ?? [];
1808
- if (Array.isArray(to) && !(to instanceof List)) {
1848
+ if (isVectorArray(to)) {
1809
1849
  return pushAll(copy(to), args[1]);
1810
1850
  }
1811
1851
  return reduce(conj_BANG_, copy(to), args[1]);
@@ -2695,6 +2735,13 @@ export function keyword(arg1, arg2) {
2695
2735
  return arg1;
2696
2736
  }
2697
2737
 
2738
+ export function symbol(arg1, arg2) {
2739
+ if (arg2 !== undefined) {
2740
+ return (arg1 != null ? arg1 + '/' : '') + arg2;
2741
+ }
2742
+ return arg1;
2743
+ }
2744
+
2698
2745
  export function keyword_QMARK_(x) {
2699
2746
  return typeof x === 'string';
2700
2747
  }
@@ -2756,6 +2803,10 @@ export function int_QMARK_(x) {
2756
2803
  return Number.isInteger(x);
2757
2804
  }
2758
2805
 
2806
+ export function double_QMARK_(x) {
2807
+ return typeof x === 'number';
2808
+ }
2809
+
2759
2810
  export const integer_QMARK_ = int_QMARK_;
2760
2811
 
2761
2812
  export function pos_int_QMARK_(x) {
@@ -2790,6 +2841,10 @@ export function with_meta(x, m) {
2790
2841
  return ret;
2791
2842
  }
2792
2843
 
2844
+ export function vary_meta(x, f, ...args) {
2845
+ return with_meta(x, f(meta(x), ...args));
2846
+ }
2847
+
2793
2848
  export function boolean_QMARK_(x) {
2794
2849
  return x === true || x === false;
2795
2850
  }
@@ -2877,9 +2932,11 @@ export function parse_long(s) {
2877
2932
 
2878
2933
  export function parse_double(s) {
2879
2934
  if (string_QMARK_(s)) {
2935
+ // eslint-disable-next-line no-control-regex -- \x00-\x20 mirrors Java trim
2880
2936
  if (/^[\x00-\x20]*[+-]?NaN[\x00-\x20]*$/.test(s)) {
2881
2937
  return NaN;
2882
2938
  } else if (
2939
+ // eslint-disable-next-line no-control-regex -- \x00-\x20 mirrors Java trim
2883
2940
  /^[\x00-\x20]*[+-]?(Infinity|((\d+\.?\d*|\.\d+)([eE][+-]?\d+)?)[dDfF]?)[\x00-\x20]*$/.test(
2884
2941
  s
2885
2942
  )
@@ -2992,8 +3049,10 @@ export function persistent_BANG_(x) {
2992
3049
  return x;
2993
3050
  }
2994
3051
 
3052
+ const SortedSet = defclass(
2995
3053
  class SortedSet {
2996
3054
  constructor(xs) {
3055
+ this[TYPE_TAG] = SET_TYPE;
2997
3056
  const isSorted = xs instanceof SortedSet;
2998
3057
  if (!isSorted) {
2999
3058
  xs = sort(xs);
@@ -3056,6 +3115,7 @@ class SortedSet {
3056
3115
  return this.keys();
3057
3116
  }
3058
3117
  }
3118
+ );
3059
3119
 
3060
3120
  export function sorted_set(...xs) {
3061
3121
  return new SortedSet(xs);
@@ -3279,7 +3339,8 @@ export function vreset_BANG_(vol, v) {
3279
3339
  return v;
3280
3340
  }
3281
3341
 
3282
- function toEDN(value, seen = new WeakSet()) {
3342
+ // readably false: strings unquoted
3343
+ function toEDN(value, seen = new WeakSet(), readably = true) {
3283
3344
  if (value == null) return 'nil';
3284
3345
  if (typeof value === 'number') {
3285
3346
  if (value === Infinity) return '##Inf';
@@ -3288,39 +3349,48 @@ function toEDN(value, seen = new WeakSet()) {
3288
3349
  return String(value);
3289
3350
  }
3290
3351
  if (typeof value === 'boolean') return String(value);
3291
- if (typeof value === 'string') return JSON.stringify(value);
3352
+ if (typeof value === 'string') return readably ? JSON.stringify(value) : value;
3292
3353
  if (typeof value === 'bigint') return `${value}N`;
3293
3354
 
3294
3355
  if (typeof value === 'object') {
3356
+ // seen tracks the current ancestor path only. A shared reference that
3357
+ // appears under sibling branches is a DAG, not a cycle, so delete on exit.
3295
3358
  if (seen.has(value)) return '#object[circular]';
3296
3359
  seen.add(value);
3297
3360
  const T = typeConst(value);
3298
- let keys;
3361
+ let keys, result;
3299
3362
  switch (T) {
3300
3363
  case ARRAY_TYPE:
3301
- return `[${value.map((v) => toEDN(v, seen)).join(' ')}]`;
3364
+ result = `[${value.map((v) => toEDN(v, seen, readably)).join(' ')}]`;
3365
+ break;
3302
3366
  case SET_TYPE:
3303
- return `#{${Array.from(value)
3304
- .map((v) => toEDN(v, seen))
3367
+ result = `#{${Array.from(value)
3368
+ .map((v) => toEDN(v, seen, readably))
3305
3369
  .join(' ')}}`;
3370
+ break;
3306
3371
  case MAP_TYPE:
3307
- return `#js/Map {${Array.from(value.entries())
3308
- .map(([k, v]) => `${toEDN(k, seen)} ${toEDN(v, seen)}`)
3372
+ result = `#js/Map {${Array.from(value.entries())
3373
+ .map(([k, v]) => `${toEDN(k, seen, readably)} ${toEDN(v, seen, readably)}`)
3309
3374
  .join(', ')}}`;
3375
+ break;
3310
3376
  case LAZY_ITERABLE_TYPE:
3311
3377
  case LIST_TYPE:
3312
- return `(${mapv((v) => `${toEDN(v, seen)}`, value).join(', ')})`;
3378
+ result = `(${mapv((v) => `${toEDN(v, seen, readably)}`, value).join(', ')})`;
3379
+ break;
3313
3380
  default:
3314
3381
  // Non-plain objects (Promise, Error, Date, class instances, ...) have a
3315
3382
  // constructor other than Object. Print them as #<Name> rather than {}
3316
3383
  // (which is what Object.keys would yield for opaque values like a
3317
3384
  // Promise).
3318
3385
  if (value.constructor && value.constructor !== Object) {
3386
+ seen.delete(value);
3319
3387
  return `#<${value.constructor.name}>`;
3320
3388
  }
3321
3389
  keys = Object.keys(value);
3322
- return `{${keys.map((k) => `:${k} ${toEDN(value[k], seen)}`).join(', ')}}`;
3390
+ result = `{${keys.map((k) => `:${k} ${toEDN(value[k], seen, readably)}`).join(', ')}}`;
3323
3391
  }
3392
+ seen.delete(value);
3393
+ return result;
3324
3394
  }
3325
3395
 
3326
3396
  return `#object[${value.constructor.name}]`;
@@ -3331,5 +3401,6 @@ export function pr_str(...xs) {
3331
3401
  }
3332
3402
 
3333
3403
  export function prn(...xs) {
3334
- return console.log(pr_str(...xs));
3404
+ _STAR_print_fn_STAR_.val(pr_str(...xs));
3405
+ if (_STAR_print_newline_STAR_.val) _STAR_print_fn_STAR_.val('\n');
3335
3406
  }
@@ -0,0 +1,341 @@
1
+ import * as core from './core.js';
2
+
3
+ const DISCARD = Symbol('discard');
4
+ const EOF = Symbol('eof');
5
+
6
+ class Reader {
7
+ constructor(s) {
8
+ this.s = s;
9
+ this.idx = 0;
10
+ }
11
+ peek() {
12
+ return this.s[this.idx];
13
+ }
14
+ read() {
15
+ return this.s[this.idx++];
16
+ }
17
+ eof() {
18
+ return this.idx >= this.s.length;
19
+ }
20
+ }
21
+
22
+ function whitespace(c) {
23
+ return c === ' ' || c === '\t' || c === '\n' || c === '\r' || c === '\f' || c === ',';
24
+ }
25
+
26
+ function macroTerminating(c) {
27
+ return c === undefined || c === '"' || c === ';' || c === '@' || c === '^' ||
28
+ c === '`' || c === '~' || c === '(' || c === ')' || c === '[' || c === ']' ||
29
+ c === '{' || c === '}' || c === '\\';
30
+ }
31
+
32
+ function terminating(c) {
33
+ return whitespace(c) || macroTerminating(c);
34
+ }
35
+
36
+ function skipWhitespace(rdr) {
37
+ while (!rdr.eof()) {
38
+ const c = rdr.peek();
39
+ if (whitespace(c)) {
40
+ rdr.idx++;
41
+ } else if (c === ';') {
42
+ while (!rdr.eof() && rdr.peek() !== '\n') rdr.idx++;
43
+ } else {
44
+ break;
45
+ }
46
+ }
47
+ }
48
+
49
+ function readToken(rdr) {
50
+ const start = rdr.idx;
51
+ while (!rdr.eof() && !terminating(rdr.peek())) rdr.idx++;
52
+ return rdr.s.slice(start, rdr.idx);
53
+ }
54
+
55
+ // int-pattern carries a trailing `0\d+` poison branch (no capture group) so
56
+ // leading-zero non-octals like 08 match here, yield no digits below, and are
57
+ // rejected as invalid before the float pattern can accept them
58
+ const intRe = /^([+-]?)(?:(0)|([1-9]\d*)|0[xX]([0-9a-fA-F]+)|0([0-7]+)|([1-9]\d?)[rR]([0-9a-zA-Z]+)|0\d+)(N)?$/;
59
+ const floatRe = /^([+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)(M)?$/;
60
+
61
+ // the radix-digit class is wider than the radix, so validate each digit
62
+ function intFromRadix(s, radix) {
63
+ let n = 0;
64
+ for (let i = 0; i < s.length; i++) {
65
+ const d = parseInt(s[i], radix);
66
+ if (Number.isNaN(d)) return undefined;
67
+ n = n * radix + d;
68
+ }
69
+ return n;
70
+ }
71
+
72
+ // numbers are plain JS numbers; like CLJS the N and M suffixes are ignored
73
+ function parseNumber(token) {
74
+ const m = intRe.exec(token);
75
+ if (m) {
76
+ const neg = m[1] === '-';
77
+ // radix digits range wider than the radix, so they never take N
78
+ if (m[7] !== undefined) {
79
+ const n = intFromRadix(m[7], parseInt(m[6], 10));
80
+ return n === undefined ? undefined : (neg ? -n : n);
81
+ }
82
+ let digits, radix;
83
+ if (m[2] !== undefined) { digits = '0'; radix = 10; }
84
+ else if (m[3] !== undefined) { digits = m[3]; radix = 10; }
85
+ else if (m[4] !== undefined) { digits = m[4]; radix = 16; }
86
+ else if (m[5] !== undefined) { digits = m[5]; radix = 8; }
87
+ else return undefined; // poison branch: leading-zero non-octal
88
+ const n = parseInt(digits, radix);
89
+ return neg ? -n : n;
90
+ }
91
+ // int matched above wins, so the float pattern never sees a leading-zero int
92
+ const f = floatRe.exec(token);
93
+ if (f) return parseFloat(f[1]);
94
+ return undefined;
95
+ }
96
+
97
+ function numberLiteral(c, c2) {
98
+ return (c >= '0' && c <= '9') || ((c === '+' || c === '-') && c2 >= '0' && c2 <= '9');
99
+ }
100
+
101
+ function parseToken(rdr) {
102
+ const token = readToken(rdr);
103
+ if (token === 'nil') return null;
104
+ if (token === 'true') return true;
105
+ if (token === 'false') return false;
106
+ if (numberLiteral(token[0], token[1])) {
107
+ const n = parseNumber(token);
108
+ if (n === undefined) throw new Error('Invalid number: ' + token);
109
+ return n;
110
+ }
111
+ return token;
112
+ }
113
+
114
+ function parseKeyword(rdr) {
115
+ rdr.idx++;
116
+ const token = readToken(rdr);
117
+ if (token === '') throw new Error('Invalid token: :');
118
+ // ::foo auto-resolved keywords are not valid edn
119
+ if (token[0] === ':') throw new Error('Invalid token: :' + token);
120
+ return token;
121
+ }
122
+
123
+ const stringEscapes = { t: '\t', r: '\r', n: '\n', '\\': '\\', '"': '"', b: '\b', f: '\f' };
124
+
125
+ function parseString(rdr) {
126
+ rdr.idx++;
127
+ let out = '';
128
+ let runStart = rdr.idx;
129
+ for (;;) {
130
+ if (rdr.eof()) throw new Error('EOF while reading string');
131
+ const c = rdr.read();
132
+ if (c === '"') return out + rdr.s.slice(runStart, rdr.idx - 1);
133
+ if (c === '\\') {
134
+ out += rdr.s.slice(runStart, rdr.idx - 1);
135
+ const e = rdr.read();
136
+ if (e === 'u') {
137
+ out += String.fromCharCode(parseInt(rdr.s.substr(rdr.idx, 4), 16));
138
+ rdr.idx += 4;
139
+ } else if (e in stringEscapes) {
140
+ out += stringEscapes[e];
141
+ } else {
142
+ throw new Error('Unsupported escape character: \\' + e);
143
+ }
144
+ runStart = rdr.idx;
145
+ }
146
+ }
147
+ }
148
+
149
+ const symbolicValues = { Inf: Infinity, '-Inf': -Infinity, NaN: NaN };
150
+
151
+ const namedChars = { newline: '\n', space: ' ', tab: '\t', return: '\r', backspace: '\b', formfeed: '\f' };
152
+
153
+ function parseChar(rdr) {
154
+ rdr.idx++;
155
+ if (rdr.eof()) throw new Error('EOF while reading character');
156
+ const start = rdr.idx;
157
+ rdr.idx++; // first char is taken literally, even if a terminator
158
+ while (!rdr.eof() && !terminating(rdr.peek())) rdr.idx++;
159
+ const token = rdr.s.slice(start, rdr.idx);
160
+ if (token.length === 1) return token;
161
+ if (token in namedChars) return namedChars[token];
162
+ if (token[0] === 'u') return String.fromCharCode(parseInt(token.slice(1), 16));
163
+ if (token[0] === 'o') return String.fromCharCode(parseInt(token.slice(1), 8));
164
+ throw new Error('Unsupported character: \\' + token);
165
+ }
166
+
167
+ function parseToDelimiter(rdr, opts, delimiter) {
168
+ rdr.idx++;
169
+ const elements = [];
170
+ for (;;) {
171
+ skipWhitespace(rdr);
172
+ if (rdr.eof()) throw new Error('EOF while reading, expected ' + delimiter);
173
+ if (rdr.peek() === delimiter) {
174
+ rdr.idx++;
175
+ return elements;
176
+ }
177
+ const val = parseNext(rdr, opts);
178
+ if (val !== DISCARD) elements.push(val);
179
+ }
180
+ }
181
+
182
+ function duplicates(coll) {
183
+ const dups = [];
184
+ for (let i = 0; i < coll.length; i++) {
185
+ for (let j = 0; j < i; j++) {
186
+ if (core._EQ_(coll[i], coll[j])) {
187
+ dups.push(coll[i]);
188
+ break;
189
+ }
190
+ }
191
+ }
192
+ return dups;
193
+ }
194
+
195
+ function duplicateKeyError(kind, dups) {
196
+ return kind + ' literal contains duplicate key' + (dups.length > 1 ? 's' : '') +
197
+ ': ' + dups.map((x) => core.pr_str(x)).join(', ');
198
+ }
199
+
200
+ function parseSet(rdr, opts) {
201
+ const elements = parseToDelimiter(rdr, opts, '}');
202
+ const dups = duplicates(elements);
203
+ if (dups.length) throw new Error(duplicateKeyError('Set', dups));
204
+ return new Set(elements);
205
+ }
206
+
207
+ function qualifyKey(ns, k) {
208
+ if (typeof k !== 'string') return k;
209
+ const i = k.indexOf('/');
210
+ if (i === -1) return ns + '/' + k;
211
+ if (k.slice(0, i) === '_') return k.slice(i + 1);
212
+ return k;
213
+ }
214
+
215
+ function buildMap(elements, ns) {
216
+ if (elements.length % 2 !== 0) {
217
+ throw new Error('The map literal starting with ' + core.pr_str(elements[0]) + ' contains ' + elements.length +
218
+ ' form(s). Map literals must contain an even number of forms.');
219
+ }
220
+ const ks = elements.filter((_, i) => i % 2 === 0);
221
+ const dups = duplicates(ks);
222
+ if (dups.length) throw new Error(duplicateKeyError('Map', dups));
223
+ const obj = {};
224
+ for (let i = 0; i < elements.length; i += 2) {
225
+ obj[ns ? qualifyKey(ns, elements[i]) : elements[i]] = elements[i + 1];
226
+ }
227
+ return obj;
228
+ }
229
+
230
+ function parseMap(rdr, opts) {
231
+ return buildMap(parseToDelimiter(rdr, opts, '}'), null);
232
+ }
233
+
234
+ function parseMeta(rdr, opts) {
235
+ rdr.idx++;
236
+ skipWhitespace(rdr);
237
+ const c = rdr.peek();
238
+ let m;
239
+ if (c === '{') {
240
+ m = parseMap(rdr, opts);
241
+ } else if (c === ':') {
242
+ m = {};
243
+ m[parseKeyword(rdr)] = true;
244
+ } else {
245
+ // a symbol or string tags the value
246
+ m = { tag: parseNext(rdr, opts) };
247
+ }
248
+ skipWhitespace(rdr);
249
+ const form = parseNext(rdr, opts);
250
+ if (form === null || typeof form !== 'object') {
251
+ throw new Error('Metadata can only be applied to IMetas');
252
+ }
253
+ // stacked metadata merges, with the outer map winning
254
+ return core.with_meta(form, Object.assign({}, core.meta(form), m));
255
+ }
256
+
257
+ function parseNamespacedMap(rdr, opts) {
258
+ const token = readToken(rdr);
259
+ if (token === ':' || token[1] === ':') throw new Error('Invalid token: ' + token);
260
+ skipWhitespace(rdr);
261
+ if (rdr.peek() !== '{') throw new Error('Namespaced map must specify a map');
262
+ return buildMap(parseToDelimiter(rdr, opts, '}'), token.slice(1));
263
+ }
264
+
265
+ const defaultReaders = {
266
+ inst: (val) => new Date(val),
267
+ uuid: (val) => val,
268
+ };
269
+
270
+ function parseTagged(rdr, opts) {
271
+ const tag = readToken(rdr);
272
+ skipWhitespace(rdr);
273
+ const val = parseNext(rdr, opts);
274
+ const readers = opts && opts.readers;
275
+ if (readers && readers[tag]) return readers[tag](val);
276
+ if (defaultReaders[tag]) return defaultReaders[tag](val);
277
+ if (opts && opts.default) return opts.default(tag, val);
278
+ throw new Error('No reader function for tag ' + tag);
279
+ }
280
+
281
+ function parseDispatch(rdr, opts) {
282
+ rdr.idx++;
283
+ const c = rdr.peek();
284
+ if (c === '_') {
285
+ rdr.idx++;
286
+ // discard one real form; skip nested discards so #_ #_ a b drops both
287
+ let v = parseNext(rdr, opts);
288
+ while (v === DISCARD) v = parseNext(rdr, opts);
289
+ return DISCARD;
290
+ }
291
+ if (c === '!') {
292
+ while (!rdr.eof() && rdr.peek() !== '\n') rdr.idx++;
293
+ return DISCARD;
294
+ }
295
+ if (c === '{') return parseSet(rdr, opts);
296
+ if (c === ':') return parseNamespacedMap(rdr, opts);
297
+ if (c === '#') {
298
+ rdr.idx++;
299
+ const token = readToken(rdr);
300
+ if (token in symbolicValues) return symbolicValues[token];
301
+ throw new Error('Invalid token: ##' + token);
302
+ }
303
+ return parseTagged(rdr, opts);
304
+ }
305
+
306
+ function parseNext(rdr, opts) {
307
+ skipWhitespace(rdr);
308
+ if (rdr.eof()) return EOF;
309
+ const c = rdr.peek();
310
+ switch (c) {
311
+ case '(': return core.list(...parseToDelimiter(rdr, opts, ')'));
312
+ case '[': return parseToDelimiter(rdr, opts, ']');
313
+ case '{': return parseMap(rdr, opts);
314
+ case ')': case ']': case '}': throw new Error('Unmatched delimiter: ' + c);
315
+ case '"': return parseString(rdr);
316
+ case '\\': return parseChar(rdr);
317
+ case ':': return parseKeyword(rdr);
318
+ case '#': return parseDispatch(rdr, opts);
319
+ case "'": rdr.idx++; return core.list('quote', parseNext(rdr, opts));
320
+ case '^': return parseMeta(rdr, opts);
321
+ case '@': case '`': case '~': throw new Error('Invalid leading character: ' + c);
322
+ default: return parseToken(rdr);
323
+ }
324
+ }
325
+
326
+ export function read_string(a, b) {
327
+ let opts, s;
328
+ if (b === undefined) {
329
+ opts = null;
330
+ s = a;
331
+ } else {
332
+ opts = a;
333
+ s = b;
334
+ }
335
+ if (s == null || s === '') return opts && 'eof' in opts ? opts.eof : null;
336
+ const rdr = new Reader(s);
337
+ let val = parseNext(rdr, opts);
338
+ while (val === DISCARD) val = parseNext(rdr, opts);
339
+ if (val === EOF) return opts && 'eof' in opts ? opts.eof : null;
340
+ return val;
341
+ }