squint-cljs 0.13.195 → 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:
@@ -274,7 +289,7 @@ function isObj(coll) {
274
289
  }
275
290
 
276
291
  function isVectorArray(x) {
277
- return Array.isArray(x) && !(x instanceof List);
292
+ return Array.isArray(x) && x[TYPE_TAG] !== LIST_TYPE;
278
293
  }
279
294
 
280
295
  export function object_QMARK_(coll) {
@@ -291,13 +306,10 @@ function typeConst(obj) {
291
306
  }
292
307
  if (obj instanceof Map) return MAP_TYPE;
293
308
  if (obj instanceof Set) return SET_TYPE;
294
- if (obj instanceof List) return LIST_TYPE;
309
+ // brand, not instanceof, so dispatch does not reference the classes
310
+ const tag = obj[TYPE_TAG];
311
+ if (tag !== undefined) return tag;
295
312
  if (isVectorArray(obj)) return ARRAY_TYPE;
296
- if (obj instanceof LazyIterable) return LAZY_ITERABLE_TYPE;
297
- if (obj instanceof SortedSet) return SET_TYPE;
298
-
299
- // everything more specific than Object should go before this
300
- if (obj instanceof Object) return OBJECT_TYPE;
301
313
 
302
314
  return undefined;
303
315
  }
@@ -542,8 +554,21 @@ export function dec(n) {
542
554
  return n - 1;
543
555
  }
544
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
+
545
565
  export function println(...args) {
546
- 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));
547
572
  }
548
573
 
549
574
  export function nth(coll, idx, orElse) {
@@ -813,8 +838,8 @@ function* _reductions2(f, s) {
813
838
  }
814
839
 
815
840
  function* _reductions3(f, init, coll) {
816
- let i = init,
817
- rst = coll;
841
+ let i = init;
842
+ const rst = coll;
818
843
  while (true) {
819
844
  if (reduced_QMARK_(i)) {
820
845
  yield i.value;
@@ -854,8 +879,11 @@ const CHUNK_SIZE = 32;
854
879
 
855
880
  // One cell of a self-caching chunked seq. `step` is a thunk returning
856
881
  // [nonEmptyChunkArray, nextStep] or null at the end. See doc/dev/lazy-seqs.md.
882
+ const LazyIterable = defclass(
857
883
  class LazyIterable {
858
884
  constructor(step) {
885
+ this[TYPE_TAG] = LAZY_ITERABLE_TYPE;
886
+ this[IIterable] = true; // Closure compatibility
859
887
  this.step = step;
860
888
  this.realized = false;
861
889
  this.chunk = null; // array, or null when this is the terminal (empty) cell
@@ -892,9 +920,18 @@ class LazyIterable {
892
920
  },
893
921
  };
894
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
+ }
895
933
  }
896
-
897
- LazyIterable.prototype[IIterable] = true; // Closure compatibility
934
+ );
898
935
 
899
936
  // One-element chunks: an unchunked seq, realized one element at a time.
900
937
  function unchunkedSteps(iter) {
@@ -979,7 +1016,8 @@ function mapChunks(coll, xf) {
979
1016
  return new LazyIterable(step(src, 0));
980
1017
  }
981
1018
 
982
- export class Cons {
1019
+ export const Cons = defclass(
1020
+ class Cons {
983
1021
  constructor(x, coll) {
984
1022
  this.x = x;
985
1023
  this.coll = coll;
@@ -1007,6 +1045,7 @@ export class Cons {
1007
1045
  };
1008
1046
  }
1009
1047
  }
1048
+ );
1010
1049
 
1011
1050
  export function cons(x, coll) {
1012
1051
  return new Cons(x, coll);
@@ -1420,6 +1459,7 @@ export function constantly(x) {
1420
1459
  class List extends Array {
1421
1460
  constructor(...args) {
1422
1461
  super();
1462
+ this[TYPE_TAG] = LIST_TYPE;
1423
1463
  this.push(...args);
1424
1464
  }
1425
1465
  }
@@ -1491,14 +1531,10 @@ function concat1(colls) {
1491
1531
  return new LazyIterable(step(null));
1492
1532
  }
1493
1533
 
1494
- export function concat(...colls) {
1495
- return concat1(colls);
1496
- }
1497
-
1498
- // lazy seqable argument
1499
- concat[IApply__apply] = (colls) => {
1500
- return concat1(colls);
1501
- };
1534
+ export const concat = withApply(
1535
+ (...colls) => concat1(colls),
1536
+ (colls) => concat1(colls), // lazy seqable argument
1537
+ );
1502
1538
 
1503
1539
  export function mapcat(f, ...colls) {
1504
1540
  if (colls.length === 0) {
@@ -2699,6 +2735,13 @@ export function keyword(arg1, arg2) {
2699
2735
  return arg1;
2700
2736
  }
2701
2737
 
2738
+ export function symbol(arg1, arg2) {
2739
+ if (arg2 !== undefined) {
2740
+ return (arg1 != null ? arg1 + '/' : '') + arg2;
2741
+ }
2742
+ return arg1;
2743
+ }
2744
+
2702
2745
  export function keyword_QMARK_(x) {
2703
2746
  return typeof x === 'string';
2704
2747
  }
@@ -2760,6 +2803,10 @@ export function int_QMARK_(x) {
2760
2803
  return Number.isInteger(x);
2761
2804
  }
2762
2805
 
2806
+ export function double_QMARK_(x) {
2807
+ return typeof x === 'number';
2808
+ }
2809
+
2763
2810
  export const integer_QMARK_ = int_QMARK_;
2764
2811
 
2765
2812
  export function pos_int_QMARK_(x) {
@@ -2794,6 +2841,10 @@ export function with_meta(x, m) {
2794
2841
  return ret;
2795
2842
  }
2796
2843
 
2844
+ export function vary_meta(x, f, ...args) {
2845
+ return with_meta(x, f(meta(x), ...args));
2846
+ }
2847
+
2797
2848
  export function boolean_QMARK_(x) {
2798
2849
  return x === true || x === false;
2799
2850
  }
@@ -2881,9 +2932,11 @@ export function parse_long(s) {
2881
2932
 
2882
2933
  export function parse_double(s) {
2883
2934
  if (string_QMARK_(s)) {
2935
+ // eslint-disable-next-line no-control-regex -- \x00-\x20 mirrors Java trim
2884
2936
  if (/^[\x00-\x20]*[+-]?NaN[\x00-\x20]*$/.test(s)) {
2885
2937
  return NaN;
2886
2938
  } else if (
2939
+ // eslint-disable-next-line no-control-regex -- \x00-\x20 mirrors Java trim
2887
2940
  /^[\x00-\x20]*[+-]?(Infinity|((\d+\.?\d*|\.\d+)([eE][+-]?\d+)?)[dDfF]?)[\x00-\x20]*$/.test(
2888
2941
  s
2889
2942
  )
@@ -2996,8 +3049,10 @@ export function persistent_BANG_(x) {
2996
3049
  return x;
2997
3050
  }
2998
3051
 
3052
+ const SortedSet = defclass(
2999
3053
  class SortedSet {
3000
3054
  constructor(xs) {
3055
+ this[TYPE_TAG] = SET_TYPE;
3001
3056
  const isSorted = xs instanceof SortedSet;
3002
3057
  if (!isSorted) {
3003
3058
  xs = sort(xs);
@@ -3060,6 +3115,7 @@ class SortedSet {
3060
3115
  return this.keys();
3061
3116
  }
3062
3117
  }
3118
+ );
3063
3119
 
3064
3120
  export function sorted_set(...xs) {
3065
3121
  return new SortedSet(xs);
@@ -3283,7 +3339,8 @@ export function vreset_BANG_(vol, v) {
3283
3339
  return v;
3284
3340
  }
3285
3341
 
3286
- function toEDN(value, seen = new WeakSet()) {
3342
+ // readably false: strings unquoted
3343
+ function toEDN(value, seen = new WeakSet(), readably = true) {
3287
3344
  if (value == null) return 'nil';
3288
3345
  if (typeof value === 'number') {
3289
3346
  if (value === Infinity) return '##Inf';
@@ -3292,39 +3349,48 @@ function toEDN(value, seen = new WeakSet()) {
3292
3349
  return String(value);
3293
3350
  }
3294
3351
  if (typeof value === 'boolean') return String(value);
3295
- if (typeof value === 'string') return JSON.stringify(value);
3352
+ if (typeof value === 'string') return readably ? JSON.stringify(value) : value;
3296
3353
  if (typeof value === 'bigint') return `${value}N`;
3297
3354
 
3298
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.
3299
3358
  if (seen.has(value)) return '#object[circular]';
3300
3359
  seen.add(value);
3301
3360
  const T = typeConst(value);
3302
- let keys;
3361
+ let keys, result;
3303
3362
  switch (T) {
3304
3363
  case ARRAY_TYPE:
3305
- return `[${value.map((v) => toEDN(v, seen)).join(' ')}]`;
3364
+ result = `[${value.map((v) => toEDN(v, seen, readably)).join(' ')}]`;
3365
+ break;
3306
3366
  case SET_TYPE:
3307
- return `#{${Array.from(value)
3308
- .map((v) => toEDN(v, seen))
3367
+ result = `#{${Array.from(value)
3368
+ .map((v) => toEDN(v, seen, readably))
3309
3369
  .join(' ')}}`;
3370
+ break;
3310
3371
  case MAP_TYPE:
3311
- return `#js/Map {${Array.from(value.entries())
3312
- .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)}`)
3313
3374
  .join(', ')}}`;
3375
+ break;
3314
3376
  case LAZY_ITERABLE_TYPE:
3315
3377
  case LIST_TYPE:
3316
- return `(${mapv((v) => `${toEDN(v, seen)}`, value).join(', ')})`;
3378
+ result = `(${mapv((v) => `${toEDN(v, seen, readably)}`, value).join(', ')})`;
3379
+ break;
3317
3380
  default:
3318
3381
  // Non-plain objects (Promise, Error, Date, class instances, ...) have a
3319
3382
  // constructor other than Object. Print them as #<Name> rather than {}
3320
3383
  // (which is what Object.keys would yield for opaque values like a
3321
3384
  // Promise).
3322
3385
  if (value.constructor && value.constructor !== Object) {
3386
+ seen.delete(value);
3323
3387
  return `#<${value.constructor.name}>`;
3324
3388
  }
3325
3389
  keys = Object.keys(value);
3326
- return `{${keys.map((k) => `:${k} ${toEDN(value[k], seen)}`).join(', ')}}`;
3390
+ result = `{${keys.map((k) => `:${k} ${toEDN(value[k], seen, readably)}`).join(', ')}}`;
3327
3391
  }
3392
+ seen.delete(value);
3393
+ return result;
3328
3394
  }
3329
3395
 
3330
3396
  return `#object[${value.constructor.name}]`;
@@ -3335,5 +3401,6 @@ export function pr_str(...xs) {
3335
3401
  }
3336
3402
 
3337
3403
  export function prn(...xs) {
3338
- 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');
3339
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
+ }