septima-lang 0.0.21 → 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/README.md +435 -0
- package/change-log.md +57 -0
- package/dist/src/ast-node.d.ts +13 -0
- package/dist/src/ast-node.js +18 -1
- package/dist/src/parser.d.ts +1 -0
- package/dist/src/parser.js +75 -5
- package/dist/src/runtime.js +20 -1
- package/dist/tests/parser.spec.d.ts +1 -0
- package/dist/tests/parser.spec.js +75 -0
- package/dist/tests/septima-compile.spec.d.ts +1 -0
- package/dist/tests/septima-compile.spec.js +118 -0
- package/dist/tests/septima.spec.d.ts +1 -0
- package/dist/tests/septima.spec.js +1090 -0
- package/dist/tests/value.spec.d.ts +1 -0
- package/dist/tests/value.spec.js +263 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +3 -3
- package/src/a.js +66 -0
- package/src/ast-node.ts +340 -0
- package/src/extract-message.ts +5 -0
- package/src/fail-me.ts +7 -0
- package/src/find-array-method.ts +124 -0
- package/src/find-string-method.ts +84 -0
- package/src/index.ts +1 -0
- package/src/location.ts +13 -0
- package/src/parser.ts +698 -0
- package/src/result.ts +54 -0
- package/src/runtime.ts +462 -0
- package/src/scanner.ts +136 -0
- package/src/septima.ts +218 -0
- package/src/should-never-happen.ts +4 -0
- package/src/source-code.ts +101 -0
- package/src/stack.ts +18 -0
- package/src/switch-on.ts +4 -0
- package/src/symbol-table.ts +9 -0
- package/src/value.ts +823 -0
- package/tests/parser.spec.ts +81 -0
- package/tests/septima-compile.spec.ts +187 -0
- package/tests/septima.spec.ts +1169 -0
- package/tests/value.spec.ts +291 -0
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
|
+
}
|