septima-lang 0.0.7 → 0.0.8
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/dist/src/ast-node.d.ts +103 -0
- package/dist/src/ast-node.js +156 -0
- package/dist/src/extract-message.d.ts +1 -0
- package/dist/src/extract-message.js +10 -0
- package/dist/src/fail-me.d.ts +1 -0
- package/dist/src/fail-me.js +11 -0
- package/dist/src/find-array-method.d.ts +15 -0
- package/dist/src/find-array-method.js +104 -0
- package/dist/src/find-string-method.d.ts +2 -0
- package/dist/src/find-string-method.js +88 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +18 -0
- package/dist/src/location.d.ts +11 -0
- package/dist/src/location.js +3 -0
- package/dist/src/parser.d.ts +45 -0
- package/dist/src/parser.js +483 -0
- package/dist/src/result.d.ts +24 -0
- package/dist/src/result.js +29 -0
- package/dist/src/runtime.d.ts +28 -0
- package/dist/src/runtime.js +351 -0
- package/dist/src/scanner.d.ts +23 -0
- package/dist/src/scanner.js +88 -0
- package/dist/src/septima.d.ts +32 -0
- package/dist/src/septima.js +91 -0
- package/dist/src/should-never-happen.d.ts +1 -0
- package/dist/src/should-never-happen.js +9 -0
- package/dist/src/source-code.d.ts +19 -0
- package/dist/src/source-code.js +90 -0
- package/dist/src/stack.d.ts +11 -0
- package/dist/src/stack.js +19 -0
- package/dist/src/switch-on.d.ts +1 -0
- package/dist/src/switch-on.js +9 -0
- package/dist/src/symbol-table.d.ts +6 -0
- package/dist/src/symbol-table.js +3 -0
- package/dist/src/value.d.ts +128 -0
- package/dist/src/value.js +634 -0
- package/dist/tests/parser.spec.d.ts +1 -0
- package/dist/tests/parser.spec.js +35 -0
- package/dist/tests/septima-compute-module.spec.d.ts +1 -0
- package/dist/tests/septima-compute-module.spec.js +36 -0
- package/dist/tests/septima.spec.d.ts +1 -0
- package/dist/tests/septima.spec.js +845 -0
- package/dist/tests/value.spec.d.ts +1 -0
- package/dist/tests/value.spec.js +355 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +3 -3
- package/src/a.js +66 -0
- package/src/ast-node.ts +269 -0
- package/src/extract-message.ts +5 -0
- package/src/fail-me.ts +7 -0
- package/src/find-array-method.ts +115 -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 +554 -0
- package/src/result.ts +45 -0
- package/src/runtime.ts +370 -0
- package/src/scanner.ts +106 -0
- package/src/septima.ts +121 -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 +7 -0
- package/src/value.ts +742 -0
- package/tests/parser.spec.ts +36 -0
- package/tests/septima-compute-module.spec.ts +41 -0
- package/tests/septima.spec.ts +921 -0
- package/tests/value.spec.ts +387 -0
- package/main.js +0 -1
package/src/parser.ts
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { ArrayLiteralPart, AstNode, Ident, Import, Let, Literal, ObjectLiteralPart, span, Unit } from './ast-node'
|
|
2
|
+
import { Scanner, Token } from './scanner'
|
|
3
|
+
import { switchOn } from './switch-on'
|
|
4
|
+
|
|
5
|
+
export class Parser {
|
|
6
|
+
constructor(private readonly scanner: Scanner) {}
|
|
7
|
+
|
|
8
|
+
parse(): Unit {
|
|
9
|
+
const ret = this.unit()
|
|
10
|
+
if (!this.scanner.eof()) {
|
|
11
|
+
throw new Error(`Loitering input ${this.scanner.sourceRef}`)
|
|
12
|
+
}
|
|
13
|
+
return ret
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
unit(): Unit {
|
|
17
|
+
const imports = this.imports()
|
|
18
|
+
const expression = this.expression('TOP_LEVEL')
|
|
19
|
+
return { tag: 'unit', imports, expression }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
imports(): Import[] {
|
|
23
|
+
const ret: Import[] = []
|
|
24
|
+
while (true) {
|
|
25
|
+
const start = this.scanner.consumeIf('import')
|
|
26
|
+
if (!start) {
|
|
27
|
+
return ret
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.scanner.consume('*')
|
|
31
|
+
this.scanner.consume('as')
|
|
32
|
+
const ident = this.identifier()
|
|
33
|
+
this.scanner.consume('from')
|
|
34
|
+
const pathToImportFrom = this.maybePrimitiveLiteral()
|
|
35
|
+
if (pathToImportFrom === undefined) {
|
|
36
|
+
throw new Error(`Expected a literal ${this.scanner.sourceRef}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const notString = () => {
|
|
40
|
+
throw new Error(`Expected a string literal ${this.scanner.sourceCode.sourceRef(span(pathToImportFrom))}`)
|
|
41
|
+
}
|
|
42
|
+
switchOn(pathToImportFrom.type, {
|
|
43
|
+
bool: notString,
|
|
44
|
+
num: notString,
|
|
45
|
+
str: () => {},
|
|
46
|
+
sink: notString,
|
|
47
|
+
'sink!': notString,
|
|
48
|
+
'sink!!': notString,
|
|
49
|
+
})
|
|
50
|
+
ret.push({ start, ident, pathToImportFrom: pathToImportFrom.t })
|
|
51
|
+
|
|
52
|
+
this.scanner.consumeIf(';')
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
definitions(kind: 'TOP_LEVEL' | 'NESTED'): Let[] {
|
|
57
|
+
const ret: Let[] = []
|
|
58
|
+
while (true) {
|
|
59
|
+
if (kind === 'NESTED') {
|
|
60
|
+
if (this.scanner.headMatches('export ')) {
|
|
61
|
+
throw new Error(`non-top-level definition cannot be export ${this.scanner.sourceRef}`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let start = this.scanner.consumeIf('let ')
|
|
65
|
+
if (!start && kind === 'TOP_LEVEL') {
|
|
66
|
+
start = this.scanner.consumeIf('export let ')
|
|
67
|
+
}
|
|
68
|
+
if (!start) {
|
|
69
|
+
return ret
|
|
70
|
+
}
|
|
71
|
+
const ident = this.identifier()
|
|
72
|
+
this.scanner.consume('=')
|
|
73
|
+
const value = this.lambda()
|
|
74
|
+
ret.push({ start, ident, value })
|
|
75
|
+
|
|
76
|
+
this.scanner.consumeIf(';')
|
|
77
|
+
if (!this.scanner.headMatches('let ')) {
|
|
78
|
+
return ret
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
expression(kind: 'TOP_LEVEL' | 'NESTED' = 'NESTED'): AstNode {
|
|
84
|
+
const definitions = this.definitions(kind)
|
|
85
|
+
if (kind === 'TOP_LEVEL' && this.scanner.eof()) {
|
|
86
|
+
return { tag: 'topLevelExpression', definitions }
|
|
87
|
+
}
|
|
88
|
+
this.scanner.consumeIf('return')
|
|
89
|
+
const computation = this.lambda()
|
|
90
|
+
|
|
91
|
+
if (definitions.length === 0) {
|
|
92
|
+
return computation
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { tag: 'topLevelExpression', definitions, computation }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lambda(): AstNode {
|
|
99
|
+
const start = this.scanner.consumeIf('fun')
|
|
100
|
+
if (!start) {
|
|
101
|
+
return this.arrowFunction()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.scanner.consume('(')
|
|
105
|
+
const args: Ident[] = []
|
|
106
|
+
|
|
107
|
+
if (this.scanner.consumeIf(')')) {
|
|
108
|
+
// no formal args
|
|
109
|
+
} else {
|
|
110
|
+
while (true) {
|
|
111
|
+
const arg = this.identifier()
|
|
112
|
+
args.push(arg)
|
|
113
|
+
if (this.scanner.consumeIf(')')) {
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.scanner.consume(',')
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const body = this.expression()
|
|
122
|
+
return { tag: 'lambda', start, formalArgs: args, body }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
arrowFunction(): AstNode {
|
|
126
|
+
if (this.scanner.headMatches('(', ')', '=>')) {
|
|
127
|
+
const start = this.scanner.consume('(')
|
|
128
|
+
this.scanner.consume(')')
|
|
129
|
+
this.scanner.consume('=>')
|
|
130
|
+
const body = this.lambdaBody()
|
|
131
|
+
return { tag: 'lambda', start, formalArgs: [], body }
|
|
132
|
+
}
|
|
133
|
+
if (this.scanner.headMatches(IDENT_PATTERN, '=>')) {
|
|
134
|
+
const ident = this.identifier()
|
|
135
|
+
this.scanner.consume('=>')
|
|
136
|
+
const body = this.lambdaBody()
|
|
137
|
+
return { tag: 'lambda', start: ident.t, formalArgs: [ident], body }
|
|
138
|
+
}
|
|
139
|
+
if (this.scanner.headMatches('(', IDENT_PATTERN, ')', '=>')) {
|
|
140
|
+
const start = this.scanner.consume('(')
|
|
141
|
+
const ident = this.identifier()
|
|
142
|
+
this.scanner.consume(')')
|
|
143
|
+
this.scanner.consume('=>')
|
|
144
|
+
const body = this.lambdaBody()
|
|
145
|
+
return { tag: 'lambda', start, formalArgs: [ident], body }
|
|
146
|
+
}
|
|
147
|
+
if (this.scanner.headMatches('(', IDENT_PATTERN, ',')) {
|
|
148
|
+
const start = this.scanner.consume('(')
|
|
149
|
+
const formalArgs: Ident[] = []
|
|
150
|
+
while (true) {
|
|
151
|
+
const ident = this.identifier()
|
|
152
|
+
formalArgs.push(ident)
|
|
153
|
+
|
|
154
|
+
if (this.scanner.consumeIf(')')) {
|
|
155
|
+
break
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.scanner.consume(',')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.scanner.consume('=>')
|
|
162
|
+
const body = this.lambdaBody()
|
|
163
|
+
return { tag: 'lambda', start, formalArgs, body }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return this.ifExpression()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private lambdaBody() {
|
|
170
|
+
if (this.scanner.consumeIf('{')) {
|
|
171
|
+
const ret = this.expression()
|
|
172
|
+
this.scanner.consume('}')
|
|
173
|
+
return ret
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return this.expression()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
ifExpression(): AstNode {
|
|
180
|
+
if (!this.scanner.consumeIf('if')) {
|
|
181
|
+
return this.ternary()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.scanner.consume('(')
|
|
185
|
+
const condition = this.expression()
|
|
186
|
+
this.scanner.consume(')')
|
|
187
|
+
|
|
188
|
+
const positive = this.expression()
|
|
189
|
+
|
|
190
|
+
this.scanner.consume('else')
|
|
191
|
+
|
|
192
|
+
const negative = this.expression()
|
|
193
|
+
|
|
194
|
+
return { tag: 'if', condition, positive, negative }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
ternary(): AstNode {
|
|
198
|
+
const condition = this.unsink()
|
|
199
|
+
if (!this.scanner.consumeIf('? ')) {
|
|
200
|
+
return condition
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const positive = this.expression()
|
|
204
|
+
this.scanner.consume(':')
|
|
205
|
+
const negative = this.expression()
|
|
206
|
+
|
|
207
|
+
return { tag: 'ternary', condition, positive, negative }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
unsink(): AstNode {
|
|
211
|
+
const lhs = this.or()
|
|
212
|
+
if (this.scanner.consumeIf('??')) {
|
|
213
|
+
return { tag: 'binaryOperator', operator: '??', lhs, rhs: this.unsink() }
|
|
214
|
+
}
|
|
215
|
+
return lhs
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
or(): AstNode {
|
|
219
|
+
const lhs = this.and()
|
|
220
|
+
if (this.scanner.consumeIf('||')) {
|
|
221
|
+
return { tag: 'binaryOperator', operator: '||', lhs, rhs: this.or() }
|
|
222
|
+
}
|
|
223
|
+
return lhs
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
and(): AstNode {
|
|
227
|
+
const lhs = this.equality()
|
|
228
|
+
if (this.scanner.consumeIf('&&')) {
|
|
229
|
+
return { tag: 'binaryOperator', operator: '&&', lhs, rhs: this.and() }
|
|
230
|
+
}
|
|
231
|
+
return lhs
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
equality(): AstNode {
|
|
235
|
+
const lhs = this.comparison()
|
|
236
|
+
if (this.scanner.consumeIf('==')) {
|
|
237
|
+
return { tag: 'binaryOperator', operator: '==', lhs, rhs: this.equality() }
|
|
238
|
+
}
|
|
239
|
+
if (this.scanner.consumeIf('!=')) {
|
|
240
|
+
return { tag: 'binaryOperator', operator: '!=', lhs, rhs: this.equality() }
|
|
241
|
+
}
|
|
242
|
+
return lhs
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
comparison(): AstNode {
|
|
246
|
+
const lhs = this.addition()
|
|
247
|
+
if (this.scanner.consumeIf('>=')) {
|
|
248
|
+
return { tag: 'binaryOperator', operator: '>=', lhs, rhs: this.comparison() }
|
|
249
|
+
}
|
|
250
|
+
if (this.scanner.consumeIf('<=')) {
|
|
251
|
+
return { tag: 'binaryOperator', operator: '<=', lhs, rhs: this.comparison() }
|
|
252
|
+
}
|
|
253
|
+
if (this.scanner.consumeIf('>')) {
|
|
254
|
+
return { tag: 'binaryOperator', operator: '>', lhs, rhs: this.comparison() }
|
|
255
|
+
}
|
|
256
|
+
if (this.scanner.consumeIf('<')) {
|
|
257
|
+
return { tag: 'binaryOperator', operator: '<', lhs, rhs: this.comparison() }
|
|
258
|
+
}
|
|
259
|
+
return lhs
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
addition(): AstNode {
|
|
263
|
+
const lhs = this.multiplication()
|
|
264
|
+
if (this.scanner.consumeIf('+')) {
|
|
265
|
+
return { tag: 'binaryOperator', operator: '+', lhs, rhs: this.addition() }
|
|
266
|
+
}
|
|
267
|
+
if (this.scanner.consumeIf('-')) {
|
|
268
|
+
return { tag: 'binaryOperator', operator: '-', lhs, rhs: this.addition() }
|
|
269
|
+
}
|
|
270
|
+
return lhs
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
multiplication(): AstNode {
|
|
274
|
+
const lhs = this.power()
|
|
275
|
+
if (this.scanner.consumeIf('*')) {
|
|
276
|
+
return { tag: 'binaryOperator', operator: '*', lhs, rhs: this.multiplication() }
|
|
277
|
+
}
|
|
278
|
+
if (this.scanner.consumeIf('/')) {
|
|
279
|
+
return { tag: 'binaryOperator', operator: '/', lhs, rhs: this.multiplication() }
|
|
280
|
+
}
|
|
281
|
+
if (this.scanner.consumeIf('%')) {
|
|
282
|
+
return { tag: 'binaryOperator', operator: '%', lhs, rhs: this.multiplication() }
|
|
283
|
+
}
|
|
284
|
+
return lhs
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
power(): AstNode {
|
|
288
|
+
const lhs = this.unary()
|
|
289
|
+
if (this.scanner.consumeIf('**')) {
|
|
290
|
+
return { tag: 'binaryOperator', operator: '**', lhs, rhs: this.power() }
|
|
291
|
+
}
|
|
292
|
+
return lhs
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
unary(): AstNode {
|
|
296
|
+
let operatorToken = this.scanner.consumeIf('!')
|
|
297
|
+
if (operatorToken) {
|
|
298
|
+
return { tag: 'unaryOperator', operand: this.unary(), operator: '!', operatorToken }
|
|
299
|
+
}
|
|
300
|
+
operatorToken = this.scanner.consumeIf('+')
|
|
301
|
+
if (operatorToken) {
|
|
302
|
+
return { tag: 'unaryOperator', operand: this.unary(), operator: '+', operatorToken }
|
|
303
|
+
}
|
|
304
|
+
operatorToken = this.scanner.consumeIf('-')
|
|
305
|
+
if (operatorToken) {
|
|
306
|
+
return { tag: 'unaryOperator', operand: this.unary(), operator: '-', operatorToken }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return this.call()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
call(): AstNode {
|
|
313
|
+
const callee = this.memberAccess()
|
|
314
|
+
|
|
315
|
+
if (!this.scanner.consumeIf('(')) {
|
|
316
|
+
return callee
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const { actualArgs, end } = this.actualArgList()
|
|
320
|
+
return { tag: 'functionCall', actualArgs, callee, end }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private actualArgList() {
|
|
324
|
+
const actualArgs: AstNode[] = []
|
|
325
|
+
const endEmpty = this.scanner.consumeIf(')')
|
|
326
|
+
if (endEmpty) {
|
|
327
|
+
// no actual args
|
|
328
|
+
return { actualArgs, end: endEmpty }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
while (true) {
|
|
332
|
+
const arg = this.expression()
|
|
333
|
+
actualArgs.push(arg)
|
|
334
|
+
let end = this.scanner.consumeIf(')')
|
|
335
|
+
if (end) {
|
|
336
|
+
return { actualArgs, end }
|
|
337
|
+
}
|
|
338
|
+
this.scanner.consume(',')
|
|
339
|
+
end = this.scanner.consumeIf(')')
|
|
340
|
+
if (end) {
|
|
341
|
+
return { actualArgs, end }
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
memberAccess(): AstNode {
|
|
347
|
+
let ret = this.parenthesized()
|
|
348
|
+
|
|
349
|
+
while (true) {
|
|
350
|
+
if (this.scanner.consumeIf('.')) {
|
|
351
|
+
ret = { tag: 'dot', receiver: ret, ident: this.identifier() }
|
|
352
|
+
continue
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (this.scanner.consumeIf('[')) {
|
|
356
|
+
ret = { tag: 'indexAccess', receiver: ret, index: this.expression() }
|
|
357
|
+
this.scanner.consume(']')
|
|
358
|
+
continue
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (this.scanner.consumeIf('(')) {
|
|
362
|
+
const { actualArgs, end } = this.actualArgList()
|
|
363
|
+
ret = { tag: 'functionCall', actualArgs, callee: ret, end }
|
|
364
|
+
continue
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return ret
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
parenthesized(): AstNode {
|
|
372
|
+
if (this.scanner.consumeIf('(')) {
|
|
373
|
+
const ret = this.expression()
|
|
374
|
+
this.scanner.consume(')')
|
|
375
|
+
return ret
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return this.literalOrIdent()
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
literalOrIdent(): AstNode {
|
|
382
|
+
const ret = this.maybeLiteral() ?? this.maybeIdentifier()
|
|
383
|
+
if (!ret) {
|
|
384
|
+
throw new Error(`Unparsable input ${this.scanner.sourceRef}`)
|
|
385
|
+
}
|
|
386
|
+
return ret
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
maybeLiteral(): AstNode | undefined {
|
|
390
|
+
return this.maybePrimitiveLiteral() ?? this.maybeCompositeLiteral()
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
maybePrimitiveLiteral(): Literal | undefined {
|
|
394
|
+
let t = this.scanner.consumeIf('sink!!') || this.scanner.consumeIf('undefined!!')
|
|
395
|
+
if (t) {
|
|
396
|
+
return { tag: 'literal', type: 'sink!!', t }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
t = this.scanner.consumeIf('sink!') || this.scanner.consumeIf('undefined!')
|
|
400
|
+
if (t) {
|
|
401
|
+
return { tag: 'literal', type: 'sink!', t }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
t = this.scanner.consumeIf('sink') || this.scanner.consumeIf('undefined')
|
|
405
|
+
if (t) {
|
|
406
|
+
return { tag: 'literal', type: 'sink', t }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
t = this.scanner.consumeIf('true')
|
|
410
|
+
if (t) {
|
|
411
|
+
return { tag: 'literal', type: 'bool', t }
|
|
412
|
+
}
|
|
413
|
+
t = this.scanner.consumeIf('false')
|
|
414
|
+
if (t) {
|
|
415
|
+
return { tag: 'literal', type: 'bool', t }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
t = this.scanner.consumeIf(/([0-9]*[.])?[0-9]+/)
|
|
419
|
+
if (t) {
|
|
420
|
+
return { tag: 'literal', type: 'num', t }
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// double-quotes-enclosd string
|
|
424
|
+
if (this.scanner.consumeIf(`"`, false)) {
|
|
425
|
+
t = this.scanner.consume(/[^"]*/)
|
|
426
|
+
this.scanner.consume(`"`)
|
|
427
|
+
return { tag: 'literal', type: 'str', t }
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// single-quotes-enclosd string
|
|
431
|
+
if (this.scanner.consumeIf(`'`, false)) {
|
|
432
|
+
t = this.scanner.consume(/[^']*/)
|
|
433
|
+
this.scanner.consume(`'`)
|
|
434
|
+
return { tag: 'literal', type: 'str', t }
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return undefined
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
maybeCompositeLiteral(): AstNode | undefined {
|
|
441
|
+
let t = this.scanner.consumeIf('[')
|
|
442
|
+
if (t) {
|
|
443
|
+
return this.arrayBody(t)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
t = this.scanner.consumeIf('{')
|
|
447
|
+
if (t) {
|
|
448
|
+
return this.objectBody(t)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return undefined
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* This method assumes that the caller consumed the opening '[' token. It consumes the array's elements
|
|
456
|
+
* (comma-separated list of expressions) as well as the closing ']' token.
|
|
457
|
+
*/
|
|
458
|
+
arrayBody(start: Token): AstNode {
|
|
459
|
+
const t = this.scanner.consumeIf(']')
|
|
460
|
+
if (t) {
|
|
461
|
+
// an empty array literal
|
|
462
|
+
return { tag: 'arrayLiteral', start, parts: [], end: t }
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const parts: ArrayLiteralPart[] = []
|
|
466
|
+
while (true) {
|
|
467
|
+
if (this.scanner.consumeIf(',')) {
|
|
468
|
+
const end = this.scanner.consumeIf(']')
|
|
469
|
+
if (end) {
|
|
470
|
+
return { tag: 'arrayLiteral', start, parts, end }
|
|
471
|
+
}
|
|
472
|
+
continue
|
|
473
|
+
}
|
|
474
|
+
if (this.scanner.consumeIf('...')) {
|
|
475
|
+
parts.push({ tag: 'spread', v: this.expression() })
|
|
476
|
+
} else {
|
|
477
|
+
const exp = this.expression()
|
|
478
|
+
parts.push({ tag: 'element', v: exp })
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
let end = this.scanner.consumeIf(']')
|
|
482
|
+
if (end) {
|
|
483
|
+
return { tag: 'arrayLiteral', start, parts, end }
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
this.scanner.consume(',')
|
|
487
|
+
end = this.scanner.consumeIf(']')
|
|
488
|
+
if (end) {
|
|
489
|
+
return { tag: 'arrayLiteral', start, parts, end }
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* This method assumes that the caller consumed the opening '{' token. It consumes the object's attributes
|
|
496
|
+
* (comma-separated list of key:value parirs) as well as the closing '}' token.
|
|
497
|
+
*/
|
|
498
|
+
objectBody(start: Token): AstNode {
|
|
499
|
+
const t = this.scanner.consumeIf('}')
|
|
500
|
+
if (t) {
|
|
501
|
+
// an empty array literal
|
|
502
|
+
return { tag: 'objectLiteral', start, parts: [], end: t }
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const parts: ObjectLiteralPart[] = []
|
|
506
|
+
while (true) {
|
|
507
|
+
if (this.scanner.consumeIf('...')) {
|
|
508
|
+
parts.push({ tag: 'spread', o: this.expression() })
|
|
509
|
+
} else if (this.scanner.consumeIf('[')) {
|
|
510
|
+
const k = this.expression()
|
|
511
|
+
this.scanner.consume(']')
|
|
512
|
+
this.scanner.consume(':')
|
|
513
|
+
const v = this.expression()
|
|
514
|
+
parts.push({ tag: 'computedName', k, v })
|
|
515
|
+
} else {
|
|
516
|
+
const k = this.identifier()
|
|
517
|
+
this.scanner.consume(':')
|
|
518
|
+
const v = this.expression()
|
|
519
|
+
parts.push({ tag: 'hardName', k, v })
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
let end = this.scanner.consumeIf('}')
|
|
523
|
+
if (end) {
|
|
524
|
+
return { tag: 'objectLiteral', start, parts, end }
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
this.scanner.consume(',')
|
|
528
|
+
end = this.scanner.consumeIf('}')
|
|
529
|
+
if (end) {
|
|
530
|
+
return { tag: 'objectLiteral', start, parts, end }
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private identifier(): Ident {
|
|
536
|
+
const ret = this.maybeIdentifier()
|
|
537
|
+
if (!ret) {
|
|
538
|
+
throw new Error(`Expected an identifier ${this.scanner.sourceRef}`)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return ret
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private maybeIdentifier(): Ident | undefined {
|
|
545
|
+
const t = this.scanner.consumeIf(IDENT_PATTERN)
|
|
546
|
+
if (t) {
|
|
547
|
+
return { tag: 'ident', t }
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return undefined
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const IDENT_PATTERN = /[a-zA-Z][0-9A-Za-z_]*/
|
package/src/result.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Span } from './location'
|
|
2
|
+
import { SourceCode } from './source-code'
|
|
3
|
+
import { Value } from './value'
|
|
4
|
+
|
|
5
|
+
export type ResultSink = {
|
|
6
|
+
tag: 'sink'
|
|
7
|
+
where: Span | undefined
|
|
8
|
+
trace: string | undefined
|
|
9
|
+
symbols: Record<string, unknown> | undefined
|
|
10
|
+
message: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Result =
|
|
14
|
+
| {
|
|
15
|
+
tag: 'ok'
|
|
16
|
+
value: unknown
|
|
17
|
+
}
|
|
18
|
+
| ResultSink
|
|
19
|
+
|
|
20
|
+
export class ResultSinkImpl {
|
|
21
|
+
readonly tag = 'sink'
|
|
22
|
+
constructor(private readonly sink: Value, private readonly sourceCode: SourceCode) {}
|
|
23
|
+
|
|
24
|
+
get where(): Span | undefined {
|
|
25
|
+
return this.sink.span()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get trace() {
|
|
29
|
+
const trace = this.sink.trace()
|
|
30
|
+
if (trace) {
|
|
31
|
+
return this.sourceCode.formatTrace(trace)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get symbols() {
|
|
38
|
+
return this.sink.symbols()?.export()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get message(): string {
|
|
42
|
+
const at = this.trace ?? this.sourceCode.sourceRef(this.where)
|
|
43
|
+
return `Evaluated to sink: ${at}`
|
|
44
|
+
}
|
|
45
|
+
}
|