septima-lang 0.1.0 → 0.2.0

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/src/value.ts ADDED
@@ -0,0 +1,823 @@
1
+ import { AstNode, FormalArg, Lambda, show } from './ast-node'
2
+ import { failMe } from './fail-me'
3
+ import { CallEvaluator, findArrayMethod } from './find-array-method'
4
+ import { findStringMethod } from './find-string-method'
5
+ import { shouldNeverHappen } from './should-never-happen'
6
+ import { switchOn } from './switch-on'
7
+ import { SymbolTable } from './symbol-table'
8
+
9
+ type LambdaEvaluator = (formals: FormalArg[], ast: AstNode, table: SymbolTable) => Value
10
+
11
+ type Inner =
12
+ | { tag: 'arr'; val: Value[] }
13
+ | { tag: 'bool'; val: boolean }
14
+ | { tag: 'foreign'; val: (...args: Value[]) => unknown }
15
+ | { tag: 'lambda'; val: { ast: Lambda; table: SymbolTable } }
16
+ | { tag: 'num'; val: number }
17
+ | { tag: 'undef'; val: undefined }
18
+ | { tag: 'obj'; val: Record<string, Value> }
19
+ | { tag: 'str'; val: string }
20
+
21
+ type InferTag<Q> = Q extends { tag: infer B } ? (B extends string ? B : never) : never
22
+ type Tag = InferTag<Inner>
23
+
24
+ interface Cases<R> {
25
+ arr: (arg: Value[], tag: Tag, v: Value) => R
26
+ bool: (arg: boolean, tag: Tag, v: Value) => R
27
+ foreign: (arg: (...args: Value[]) => unknown, tag: Tag, v: Value) => R
28
+ lambda: (arg: { ast: Lambda; table: SymbolTable }, tag: Tag, v: Value) => R
29
+ num: (arg: number, tag: Tag, v: Value) => R
30
+ undef: (arg: unknown, tag: Tag, v: Value) => R
31
+ obj: (arg: Record<string, Value>, tag: Tag, v: Value) => R
32
+ str: (arg: string, tag: Tag, v: Value) => R
33
+ }
34
+
35
+ type RawCases<R> = Cases<R>
36
+
37
+ function inspectValue(u: unknown) {
38
+ return JSON.stringify(u)
39
+ }
40
+
41
+ const badType =
42
+ (expected: Tag, ...moreExpected: Tag[]) =>
43
+ (_u: unknown, _actual: Tag, v: Value) => {
44
+ if (moreExpected.length === 0) {
45
+ throw new Error(`value type error: expected ${expected} but found ${inspectValue(v)}`)
46
+ }
47
+
48
+ throw new Error(
49
+ `value type error: expected either ${moreExpected.join(', ')} or ${expected} but found ${inspectValue(v)}`,
50
+ )
51
+ }
52
+
53
+ /**
54
+ * Allows the caller to "see" the native value held by a Value object. The caller supplies the `cases` object
55
+ * which maps a function for each possible Value tag. Returns the return value of the function associated with the tag
56
+ * of `v`.
57
+ *
58
+ * @param v the Value object to look into
59
+ * @param cases an object which maps Tag values to functions
60
+ * @returns the return value of the function mapped to the tag of `v`
61
+ */
62
+ function selectRaw<R>(v: Value, cases: RawCases<R>): R {
63
+ const inner = v.inner
64
+ if (inner.tag === 'arr') {
65
+ return cases.arr(inner.val, inner.tag, v)
66
+ }
67
+ if (inner.tag === 'bool') {
68
+ return cases.bool(inner.val, inner.tag, v)
69
+ }
70
+ if (inner.tag === 'foreign') {
71
+ return cases.foreign(inner.val, inner.tag, v)
72
+ }
73
+ if (inner.tag === 'lambda') {
74
+ return cases.lambda(inner.val, inner.tag, v)
75
+ }
76
+ if (inner.tag === 'num') {
77
+ return cases.num(inner.val, inner.tag, v)
78
+ }
79
+ if (inner.tag === 'undef') {
80
+ return cases.undef(undefined, inner.tag, v)
81
+ }
82
+ if (inner.tag === 'obj') {
83
+ return cases.obj(inner.val, inner.tag, v)
84
+ }
85
+ if (inner.tag === 'str') {
86
+ return cases.str(inner.val, inner.tag, v)
87
+ }
88
+
89
+ shouldNeverHappen(inner)
90
+ }
91
+
92
+ /**
93
+ * Allows the caller to "see" the native value held by a Value object. The caller supplies the `cases` object
94
+ * which maps a function for each possible Value tag. Returns the return value of the function associated with the tag
95
+ * of `v`.
96
+ *
97
+ * @param v the Value object to look into
98
+ * @param cases an object which maps Tag values to functions
99
+ * @returns the return value of the function mapped to the tag of `v`
100
+ */
101
+ function select<R>(v: Value, cases: Cases<R>): Value {
102
+ return Value.from(selectRaw(v, cases))
103
+ }
104
+
105
+ export class Value {
106
+ private constructor(readonly inner: Inner) {}
107
+
108
+ static bool(val: boolean): Value {
109
+ return new Value({ val, tag: 'bool' })
110
+ }
111
+ static num(val: number): Value {
112
+ return new Value({ val, tag: 'num' })
113
+ }
114
+ static str(val: string): Value {
115
+ return new Value({ val, tag: 'str' })
116
+ }
117
+ static undef(): Value {
118
+ return new Value({ tag: 'undef', val: undefined })
119
+ }
120
+ static arr(val: Value[]): Value {
121
+ return new Value({ val, tag: 'arr' })
122
+ }
123
+ static obj(val: Record<string, Value>): Value {
124
+ return new Value({ val, tag: 'obj' })
125
+ }
126
+ static lambda(ast: Lambda, table: SymbolTable): Value {
127
+ return new Value({ val: { ast, table }, tag: 'lambda' })
128
+ }
129
+
130
+ static foreign(f: (...args: Value[]) => unknown) {
131
+ return new Value({ tag: 'foreign', val: f })
132
+ }
133
+
134
+ unwrap(): unknown {
135
+ return this.inner.val
136
+ }
137
+
138
+ assertBool(): boolean {
139
+ const err = badType('bool')
140
+ return selectRaw(this, {
141
+ arr: err,
142
+ bool: a => a,
143
+ foreign: err,
144
+ lambda: err,
145
+ num: err,
146
+ undef: err,
147
+ obj: err,
148
+ str: err,
149
+ })
150
+ }
151
+
152
+ assertNum(): number {
153
+ const err = badType('num')
154
+ return selectRaw(this, {
155
+ arr: err,
156
+ bool: err,
157
+ foreign: err,
158
+ lambda: err,
159
+ num: a => a,
160
+ undef: err,
161
+ obj: err,
162
+ str: err,
163
+ })
164
+ }
165
+
166
+ assertStr(): string {
167
+ const err = badType('str')
168
+ return selectRaw(this, {
169
+ arr: err,
170
+ bool: err,
171
+ foreign: err,
172
+ lambda: err,
173
+ num: err,
174
+ undef: err,
175
+ obj: err,
176
+ str: a => a,
177
+ })
178
+ }
179
+
180
+ assertArr(): Value[] {
181
+ const err = badType('arr')
182
+ return selectRaw(this, {
183
+ arr: a => a,
184
+ bool: err,
185
+ foreign: err,
186
+ lambda: err,
187
+ num: err,
188
+ undef: err,
189
+ obj: err,
190
+ str: err,
191
+ })
192
+ }
193
+
194
+ assertObj(): Record<string, Value> {
195
+ const err = badType('obj')
196
+ return selectRaw(this, {
197
+ arr: err,
198
+ bool: err,
199
+ foreign: err,
200
+ lambda: err,
201
+ num: err,
202
+ undef: err,
203
+ obj: a => a,
204
+ str: err,
205
+ })
206
+ }
207
+
208
+ assertLambda() {
209
+ const err = badType('lambda')
210
+ return selectRaw(this, {
211
+ arr: err,
212
+ bool: err,
213
+ foreign: err,
214
+ lambda: a => a,
215
+ num: err,
216
+ undef: err,
217
+ obj: err,
218
+ str: err,
219
+ })
220
+ }
221
+
222
+ isLambda() {
223
+ return this.inner.tag === 'lambda'
224
+ }
225
+
226
+ isArray() {
227
+ return this.inner.tag === 'arr'
228
+ }
229
+
230
+ isUndefined() {
231
+ return this.inner.tag === 'undef'
232
+ }
233
+
234
+ ifElse(positive: () => Value, negative: () => Value) {
235
+ const err = badType('bool')
236
+ return select(this, {
237
+ arr: err,
238
+ bool: c => (c ? positive() : negative()),
239
+ foreign: err,
240
+ lambda: err,
241
+ num: err,
242
+ undef: err,
243
+ obj: err,
244
+ str: err,
245
+ })
246
+ }
247
+
248
+ // bindToSpan(span: Span, unitId?: UnitId) {
249
+ // const inner = this.inner
250
+ // if (inner.tag !== 'sink') {
251
+ // throw new Error(`Not supported on type ${this.inner.tag}`)
252
+ // }
253
+
254
+ // return Value.sink(span, inner.trace, inner.symbols, unitId)
255
+ // }
256
+
257
+ // trace() {
258
+ // const inner = this.inner
259
+ // if (inner.tag !== 'sink') {
260
+ // return undefined
261
+ // }
262
+
263
+ // const ret: AstNode[] = []
264
+ // for (let curr = inner.trace; curr !== undefined; curr = curr?.next) {
265
+ // ret.push(curr.ast)
266
+ // }
267
+ // return ret.length === 0 ? undefined : ret
268
+ // }
269
+
270
+ // symbols() {
271
+ // const inner = this.inner
272
+ // if (inner.tag !== 'sink') {
273
+ // return undefined
274
+ // }
275
+
276
+ // return inner.symbols
277
+ // }
278
+
279
+ // where() {
280
+ // const inner = this.inner
281
+ // if (inner.tag !== 'sink') {
282
+ // return undefined
283
+ // }
284
+
285
+ // if (inner.span && inner.unitId) {
286
+ // return { span: inner.span, unitId: inner.unitId }
287
+ // }
288
+ // return undefined
289
+ // }
290
+
291
+ coalesce(that: () => Value): Value {
292
+ return select(this, {
293
+ arr: () => this,
294
+ bool: () => this,
295
+ foreign: () => this,
296
+ lambda: () => this,
297
+ num: () => this,
298
+ undef: () => that(),
299
+ obj: () => this,
300
+ str: () => this,
301
+ })
302
+ }
303
+
304
+ or(that: () => Value) {
305
+ const err = badType('bool')
306
+ return select(this, {
307
+ arr: err,
308
+ bool: lhs =>
309
+ lhs ||
310
+ select(that(), {
311
+ arr: err,
312
+ bool: rhs => rhs,
313
+ foreign: err,
314
+ lambda: err,
315
+ num: err,
316
+ undef: err,
317
+ obj: err,
318
+ str: err,
319
+ }),
320
+ foreign: err,
321
+ lambda: err,
322
+ num: err,
323
+ undef: err,
324
+ obj: err,
325
+ str: err,
326
+ })
327
+ }
328
+
329
+ and(that: () => Value) {
330
+ const err = badType('bool')
331
+ return select(this, {
332
+ arr: err,
333
+ bool: lhs =>
334
+ lhs &&
335
+ select(that(), {
336
+ arr: err,
337
+ bool: rhs => rhs,
338
+ foreign: err,
339
+ lambda: err,
340
+ num: err,
341
+ undef: err,
342
+ obj: err,
343
+ str: err,
344
+ }),
345
+ foreign: err,
346
+ lambda: err,
347
+ num: err,
348
+ undef: err,
349
+ obj: err,
350
+ str: err,
351
+ })
352
+ }
353
+ equalsTo(that: Value) {
354
+ if (this.inner.tag !== that.inner.tag) {
355
+ return Value.bool(false)
356
+ }
357
+
358
+ const no = () => Value.bool(false)
359
+ return select(this, {
360
+ str: () => Value.bool(this.inner.val === that.inner.val),
361
+ num: () => Value.bool(this.inner.val === that.inner.val),
362
+ bool: () => Value.bool(this.inner.val === that.inner.val),
363
+ undef: () => Value.bool(that.isUndefined()),
364
+ foreign: () => Value.bool(this.inner.val === that.inner.val),
365
+ lambda: () => Value.bool(this.inner.val === that.inner.val),
366
+ arr: lhs =>
367
+ select(that, {
368
+ bool: no,
369
+ num: no,
370
+ str: no,
371
+ lambda: no,
372
+ foreign: no,
373
+ undef: no,
374
+ obj: no,
375
+ arr: rhs => {
376
+ if (lhs.length !== rhs.length) {
377
+ return Value.bool(false)
378
+ }
379
+
380
+ for (let i = 0; i < lhs.length; ++i) {
381
+ if (!lhs[i].equalsTo(rhs[i]).isTrue()) {
382
+ return Value.bool(false)
383
+ }
384
+ }
385
+
386
+ return Value.bool(true)
387
+ },
388
+ }),
389
+ obj: lhs =>
390
+ select(that, {
391
+ bool: no,
392
+ num: no,
393
+ str: no,
394
+ lambda: no,
395
+ foreign: no,
396
+ undef: no,
397
+ arr: no,
398
+ obj: rhs => {
399
+ const ks = Object.keys(lhs)
400
+ if (ks.length !== Object.keys(rhs).length) {
401
+ return Value.bool(false)
402
+ }
403
+
404
+ for (const k of ks) {
405
+ if (!lhs[k].equalsTo(rhs[k]).isTrue()) {
406
+ return Value.bool(false)
407
+ }
408
+ }
409
+
410
+ return Value.bool(true)
411
+ },
412
+ }),
413
+ })
414
+ }
415
+
416
+ not() {
417
+ const err = badType('bool')
418
+ return select(this, {
419
+ arr: err,
420
+ bool: lhs => !lhs,
421
+ foreign: err,
422
+ lambda: err,
423
+ num: err,
424
+ undef: err,
425
+ obj: err,
426
+ str: err,
427
+ })
428
+ }
429
+
430
+ private binaryNumericOperator(a: Value, b: Value, f: (lhs: number, rhs: number) => number) {
431
+ const err = badType('num')
432
+ return select(a, {
433
+ arr: err,
434
+ bool: err,
435
+ foreign: err,
436
+ lambda: err,
437
+ num: lhs =>
438
+ select(b, {
439
+ arr: err,
440
+ bool: err,
441
+ foreign: err,
442
+ lambda: err,
443
+ num: rhs => f(lhs, rhs),
444
+ undef: err,
445
+ obj: err,
446
+ str: err,
447
+ }),
448
+ undef: err,
449
+ obj: err,
450
+ str: err,
451
+ })
452
+ }
453
+
454
+ plus(that: Value) {
455
+ const errNum = badType('num')
456
+ return select(this, {
457
+ arr: errNum,
458
+ bool: errNum,
459
+ foreign: errNum,
460
+ lambda: errNum,
461
+ num: lhs =>
462
+ select(that, {
463
+ arr: errNum,
464
+ bool: errNum,
465
+ foreign: errNum,
466
+ lambda: errNum,
467
+ num: rhs => lhs + rhs,
468
+ undef: errNum,
469
+ obj: errNum,
470
+ str: errNum,
471
+ }),
472
+ undef: errNum,
473
+ obj: errNum,
474
+ str: lhs => Value.str(lhs + that.toString()),
475
+ })
476
+ }
477
+ minus(that: Value) {
478
+ return this.binaryNumericOperator(this, that, (a, b) => a - b)
479
+ }
480
+ times(that: Value) {
481
+ return this.binaryNumericOperator(this, that, (a, b) => a * b)
482
+ }
483
+ power(that: Value) {
484
+ return this.binaryNumericOperator(this, that, (a, b) => a ** b)
485
+ }
486
+ over(that: Value) {
487
+ return this.binaryNumericOperator(this, that, (a, b) => a / b)
488
+ }
489
+ modulo(that: Value) {
490
+ return this.binaryNumericOperator(this, that, (a, b) => a % b)
491
+ }
492
+
493
+ negate() {
494
+ return Value.num(0).minus(this)
495
+ }
496
+
497
+ isToZero(comparator: '<' | '<=' | '==' | '!=' | '>=' | '>'): Value {
498
+ const err = badType('num')
499
+ return select(this, {
500
+ arr: err,
501
+ bool: err,
502
+ foreign: err,
503
+ lambda: err,
504
+ num: n =>
505
+ switchOn(comparator, {
506
+ '<': () => Value.bool(n < 0),
507
+ '<=': () => Value.bool(n <= 0),
508
+ '==': () => Value.bool(n == 0),
509
+ '!=': () => Value.bool(n !== 0),
510
+ '>=': () => Value.bool(n >= 0),
511
+ '>': () => Value.bool(n > 0),
512
+ }),
513
+ undef: err,
514
+ obj: err,
515
+ str: err,
516
+ })
517
+ }
518
+
519
+ isTrue(): boolean {
520
+ const err = badType('bool')
521
+ return selectRaw(this, {
522
+ arr: err,
523
+ bool: b => b,
524
+ foreign: err,
525
+ lambda: err,
526
+ num: err,
527
+ undef: err,
528
+ obj: err,
529
+ str: err,
530
+ })
531
+ }
532
+
533
+ isZero(): boolean {
534
+ const err = badType('num')
535
+ return selectRaw(this, {
536
+ arr: err,
537
+ bool: err,
538
+ foreign: err,
539
+ lambda: err,
540
+ num: n => n === 0,
541
+ undef: err,
542
+ obj: err,
543
+ str: err,
544
+ })
545
+ }
546
+
547
+ /**
548
+ * Determines the order beteween `this` and the given argument (`that`). The behavior of this method is dictated by
549
+ * the following principles:
550
+ *
551
+ * (i) if a < b then b >= a (i.e., it provides a consistent answer regardless of the whether `this` is `a`
552
+ * and `that` is `b` or vice versa)
553
+ * (ii) ordering two values of different type result in runtime error.
554
+ * (iii) orderingg a value with itself evaluates to `0`
555
+ *
556
+ * Principles (i) and (iii) realizes the intuitive behavior of comparisons. (ii) realizes the idea that
557
+ * "one cannot compare oranges and apples". This is essentially a design decision. We could have gone with defining
558
+ * some order between types (the tag of a Value object), but we feel that a saner approach is to say "we do not know
559
+ * how to sort an array containing numbers and booleans".
560
+ *
561
+ * IMPORTANT: these principles overpower other principles. In parituclar, the principles that "an expression with
562
+ * sink evaluates to sink" is trumped by comparison principle (ii)
563
+ *
564
+ * @param that
565
+ * @returns
566
+ */
567
+ order(that: Value): Value {
568
+ const err = (_u: unknown, t: Tag) => {
569
+ throw new Error(`Cannot compare a value of type ${t}`)
570
+ }
571
+
572
+ if (this.inner.tag !== that.inner.tag) {
573
+ throw new Error(`Cannot compare a ${this.inner.tag} value with a value of another type (${that.inner.tag})`)
574
+ }
575
+
576
+ return select(this, {
577
+ arr: err,
578
+ bool: lhs => {
579
+ const rhs = that.assertBool()
580
+
581
+ return lhs && !rhs ? 1 : !lhs && rhs ? -1 : 0
582
+ },
583
+ foreign: err,
584
+ lambda: err,
585
+ num: () => {
586
+ const d = this.minus(that).assertNum()
587
+ return d < 0 ? -1 : d > 0 ? 1 : 0
588
+ },
589
+ undef: err,
590
+ obj: err,
591
+ str: a => {
592
+ const rhs = that.assertStr()
593
+
594
+ const d = a.localeCompare(rhs)
595
+ return d < 0 ? -1 : d > 0 ? 1 : 0
596
+ },
597
+ })
598
+ }
599
+
600
+ static toStringOrNumber(input: string | Value): string | number {
601
+ if (typeof input === 'string') {
602
+ return input
603
+ }
604
+
605
+ const err = badType('str', 'num')
606
+ return selectRaw<string | number>(input, {
607
+ arr: err,
608
+ bool: err,
609
+ foreign: err,
610
+ lambda: err,
611
+ num: a => a,
612
+ undef: err,
613
+ obj: err,
614
+ str: a => a,
615
+ })
616
+ }
617
+
618
+ access(indexValue: string | Value, callEvaluator: CallEvaluator): Value {
619
+ const err = badType('obj', 'str', 'arr')
620
+
621
+ const ctor = 'constructor'
622
+ if ((typeof indexValue === 'object' && indexValue.toString() === ctor) || indexValue == ctor) {
623
+ return Value.from({
624
+ name: select(this, {
625
+ arr: () => Value.from('Array'),
626
+ bool: () => Value.from('Boolean'),
627
+ foreign: () => Value.from('Foreign'),
628
+ lambda: () => Value.from('Function'),
629
+ num: () => Value.from('Number'),
630
+ obj: () => Value.from('Object'),
631
+ str: () => Value.from('String'),
632
+ undef: () => Value.from('Undefined'),
633
+ }),
634
+ })
635
+ }
636
+ return select(this, {
637
+ arr: a => {
638
+ if (typeof indexValue === 'string') {
639
+ return findArrayMethod(a, indexValue, callEvaluator)
640
+ }
641
+
642
+ const i: number = indexValue.assertNum()
643
+ if (i < 0 || i > a.length) {
644
+ throw new Error(`array index (${i}) is out of bounds (length = ${a.length})`)
645
+ }
646
+ return a[i]
647
+ },
648
+ bool: err,
649
+ foreign: err,
650
+ lambda: err,
651
+ num: err,
652
+ undef: err,
653
+ obj: o => o[Value.toStringOrNumber(indexValue)],
654
+ str: s => findStringMethod(s, indexValue),
655
+ })
656
+ }
657
+
658
+ call(args: Value[], evaluator: LambdaEvaluator) {
659
+ const err = badType('lambda', 'foreign')
660
+ return select(this, {
661
+ arr: err,
662
+ bool: err,
663
+ foreign: f => from(f(...args)),
664
+ lambda: l => evaluator(l.ast.formalArgs, l.ast.body, l.table),
665
+ num: err,
666
+ undef: err,
667
+ obj: err,
668
+ str: err,
669
+ })
670
+ }
671
+
672
+ keys() {
673
+ const err = badType('obj')
674
+ return select(this, {
675
+ arr: err,
676
+ bool: err,
677
+ foreign: err,
678
+ lambda: err,
679
+ num: err,
680
+ undef: err,
681
+ obj: a => Object.keys(a),
682
+ str: err,
683
+ })
684
+ }
685
+
686
+ entries() {
687
+ const err = badType('obj')
688
+ return select(this, {
689
+ arr: err,
690
+ bool: err,
691
+ foreign: err,
692
+ lambda: err,
693
+ num: err,
694
+ undef: err,
695
+ obj: a => Object.entries(a),
696
+ str: err,
697
+ })
698
+ }
699
+
700
+ fromEntries() {
701
+ const err = badType('arr')
702
+ return select(this, {
703
+ arr: a =>
704
+ Object.fromEntries(
705
+ a
706
+ .map(x =>
707
+ selectRaw(x, {
708
+ arr: pair => {
709
+ pair.length === 2 || failMe(`each entry must be a [key, value] pair`)
710
+ return [pair[0].assertStr(), pair[1]]
711
+ },
712
+ bool: err,
713
+ foreign: err,
714
+ lambda: err,
715
+ num: err,
716
+ undef: err,
717
+ obj: err,
718
+ str: err,
719
+ }),
720
+ )
721
+ .filter(([_, v]) => !Value.from(v).isUndefined()),
722
+ ),
723
+ bool: err,
724
+ foreign: err,
725
+ lambda: err,
726
+ num: err,
727
+ undef: err,
728
+ obj: err,
729
+ str: err,
730
+ })
731
+ }
732
+
733
+ toString() {
734
+ return selectRaw(this, {
735
+ arr: a => JSON.stringify(a),
736
+ bool: a => String(a),
737
+ num: a => String(a),
738
+ str: a => a,
739
+ lambda: a => String(a),
740
+ foreign: a => String(a),
741
+ obj: a => JSON.stringify(a),
742
+ undef: () => 'undefined',
743
+ })
744
+ }
745
+
746
+ toBoolean() {
747
+ return selectRaw(this, {
748
+ arr: () => true,
749
+ bool: a => a,
750
+ num: a => Boolean(a),
751
+ str: a => Boolean(a),
752
+ lambda: () => true,
753
+ foreign: () => true,
754
+ obj: () => true,
755
+ undef: () => false,
756
+ })
757
+ }
758
+
759
+ toNumber() {
760
+ return selectRaw(this, {
761
+ arr: () => NaN,
762
+ bool: a => (a ? 1 : 0),
763
+ num: a => a,
764
+ str: a => Number(a),
765
+ lambda: () => NaN,
766
+ foreign: () => NaN,
767
+ obj: () => NaN,
768
+ undef: () => NaN,
769
+ })
770
+ }
771
+
772
+ toJSON(): unknown {
773
+ const copy: (v: Value) => unknown = (v: Value) => {
774
+ return selectRaw<unknown>(v, {
775
+ arr: a => a.map(x => copy(x)),
776
+ bool: a => a,
777
+ foreign: a => a.toString(),
778
+ lambda: a => show(a.ast),
779
+ num: a => a,
780
+ undef: () => undefined,
781
+ obj: a => Object.fromEntries(Object.entries(a).map(([k, x]) => [k, copy(x)])),
782
+ str: a => a,
783
+ })
784
+ }
785
+
786
+ return copy(this)
787
+ }
788
+
789
+ export(): unknown {
790
+ return this.toJSON()
791
+ }
792
+
793
+ static from(u: unknown) {
794
+ return from(u)
795
+ }
796
+ }
797
+
798
+ function from(u: unknown): Value {
799
+ if (u instanceof Value) {
800
+ return u
801
+ }
802
+ if (typeof u === 'boolean') {
803
+ return Value.bool(u)
804
+ }
805
+ if (typeof u === 'number') {
806
+ return Value.num(u)
807
+ }
808
+ if (typeof u === 'undefined' || u === null) {
809
+ return Value.undef()
810
+ }
811
+ if (typeof u === 'string') {
812
+ return Value.str(u)
813
+ }
814
+ if (Array.isArray(u)) {
815
+ return Value.arr(u.map(curr => from(curr)))
816
+ }
817
+
818
+ if (u && typeof u === 'object') {
819
+ return Value.obj(Object.fromEntries(Object.entries(u).map(([k, v]) => [k, from(v)])))
820
+ }
821
+
822
+ throw new Error(`cannot convert ${JSON.stringify(u)} to Value`)
823
+ }