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.
- package/README.md +22 -1
- package/lib/cli.js +105 -105
- package/lib/cljs.pprint.js +184 -184
- package/lib/compiler.js +862 -851
- package/lib/compiler.node.js +40 -39
- package/lib/compiler.sci.js +1209 -1209
- 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 +110 -39
- 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:
|
|
@@ -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
|
-
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
if (obj
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
1491
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
3364
|
+
result = `[${value.map((v) => toEDN(v, seen, readably)).join(' ')}]`;
|
|
3365
|
+
break;
|
|
3302
3366
|
case SET_TYPE:
|
|
3303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|