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.
Files changed (70) hide show
  1. package/dist/src/ast-node.d.ts +103 -0
  2. package/dist/src/ast-node.js +156 -0
  3. package/dist/src/extract-message.d.ts +1 -0
  4. package/dist/src/extract-message.js +10 -0
  5. package/dist/src/fail-me.d.ts +1 -0
  6. package/dist/src/fail-me.js +11 -0
  7. package/dist/src/find-array-method.d.ts +15 -0
  8. package/dist/src/find-array-method.js +104 -0
  9. package/dist/src/find-string-method.d.ts +2 -0
  10. package/dist/src/find-string-method.js +88 -0
  11. package/dist/src/index.d.ts +1 -0
  12. package/dist/src/index.js +18 -0
  13. package/dist/src/location.d.ts +11 -0
  14. package/dist/src/location.js +3 -0
  15. package/dist/src/parser.d.ts +45 -0
  16. package/dist/src/parser.js +483 -0
  17. package/dist/src/result.d.ts +24 -0
  18. package/dist/src/result.js +29 -0
  19. package/dist/src/runtime.d.ts +28 -0
  20. package/dist/src/runtime.js +351 -0
  21. package/dist/src/scanner.d.ts +23 -0
  22. package/dist/src/scanner.js +88 -0
  23. package/dist/src/septima.d.ts +32 -0
  24. package/dist/src/septima.js +91 -0
  25. package/dist/src/should-never-happen.d.ts +1 -0
  26. package/dist/src/should-never-happen.js +9 -0
  27. package/dist/src/source-code.d.ts +19 -0
  28. package/dist/src/source-code.js +90 -0
  29. package/dist/src/stack.d.ts +11 -0
  30. package/dist/src/stack.js +19 -0
  31. package/dist/src/switch-on.d.ts +1 -0
  32. package/dist/src/switch-on.js +9 -0
  33. package/dist/src/symbol-table.d.ts +6 -0
  34. package/dist/src/symbol-table.js +3 -0
  35. package/dist/src/value.d.ts +128 -0
  36. package/dist/src/value.js +634 -0
  37. package/dist/tests/parser.spec.d.ts +1 -0
  38. package/dist/tests/parser.spec.js +35 -0
  39. package/dist/tests/septima-compute-module.spec.d.ts +1 -0
  40. package/dist/tests/septima-compute-module.spec.js +36 -0
  41. package/dist/tests/septima.spec.d.ts +1 -0
  42. package/dist/tests/septima.spec.js +845 -0
  43. package/dist/tests/value.spec.d.ts +1 -0
  44. package/dist/tests/value.spec.js +355 -0
  45. package/dist/tsconfig.tsbuildinfo +1 -0
  46. package/package.json +3 -3
  47. package/src/a.js +66 -0
  48. package/src/ast-node.ts +269 -0
  49. package/src/extract-message.ts +5 -0
  50. package/src/fail-me.ts +7 -0
  51. package/src/find-array-method.ts +115 -0
  52. package/src/find-string-method.ts +84 -0
  53. package/src/index.ts +1 -0
  54. package/src/location.ts +13 -0
  55. package/src/parser.ts +554 -0
  56. package/src/result.ts +45 -0
  57. package/src/runtime.ts +370 -0
  58. package/src/scanner.ts +106 -0
  59. package/src/septima.ts +121 -0
  60. package/src/should-never-happen.ts +4 -0
  61. package/src/source-code.ts +101 -0
  62. package/src/stack.ts +18 -0
  63. package/src/switch-on.ts +4 -0
  64. package/src/symbol-table.ts +7 -0
  65. package/src/value.ts +742 -0
  66. package/tests/parser.spec.ts +36 -0
  67. package/tests/septima-compute-module.spec.ts +41 -0
  68. package/tests/septima.spec.ts +921 -0
  69. package/tests/value.spec.ts +387 -0
  70. 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
+ }