septima-lang 0.0.9 → 0.0.10

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