septima-lang 0.0.9 → 0.0.11

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