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.
- package/README.md +22 -1
- package/lib/cli.js +104 -104
- package/lib/cljs.pprint.js +184 -184
- package/lib/compiler.js +732 -726
- package/lib/compiler.node.js +40 -39
- package/lib/compiler.sci.js +1192 -1193
- package/lib/node.nrepl_server.js +28 -28
- package/lib/squint.core.umd.js +3 -1
- package/package.json +5 -3
- package/src/squint/core.js +103 -36
- package/src/squint/edn.js +341 -0
- package/src/squint/test.js +47 -47
package/src/squint/core.js
CHANGED
|
@@ -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') &&
|
|
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
|
|
90
|
-
(Array.isArray(bar) || bar
|
|
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
|
-
|
|
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) &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1495
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3364
|
+
result = `[${value.map((v) => toEDN(v, seen, readably)).join(' ')}]`;
|
|
3365
|
+
break;
|
|
3306
3366
|
case SET_TYPE:
|
|
3307
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|