septima-lang 0.0.5 → 0.0.7

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 (72) hide show
  1. package/main.js +1 -0
  2. package/package.json +3 -3
  3. package/dist/src/ast-node.d.ts +0 -98
  4. package/dist/src/ast-node.js +0 -139
  5. package/dist/src/extract-message.d.ts +0 -1
  6. package/dist/src/extract-message.js +0 -10
  7. package/dist/src/fail-me.d.ts +0 -1
  8. package/dist/src/fail-me.js +0 -11
  9. package/dist/src/find-array-method.d.ts +0 -15
  10. package/dist/src/find-array-method.js +0 -104
  11. package/dist/src/find-string-method.d.ts +0 -2
  12. package/dist/src/find-string-method.js +0 -88
  13. package/dist/src/index.d.ts +0 -1
  14. package/dist/src/index.js +0 -18
  15. package/dist/src/location.d.ts +0 -11
  16. package/dist/src/location.js +0 -3
  17. package/dist/src/parser.d.ts +0 -44
  18. package/dist/src/parser.js +0 -458
  19. package/dist/src/result.d.ts +0 -24
  20. package/dist/src/result.js +0 -29
  21. package/dist/src/runtime.d.ts +0 -27
  22. package/dist/src/runtime.js +0 -343
  23. package/dist/src/scanner.d.ts +0 -23
  24. package/dist/src/scanner.js +0 -88
  25. package/dist/src/septima.d.ts +0 -32
  26. package/dist/src/septima.js +0 -91
  27. package/dist/src/should-never-happen.d.ts +0 -1
  28. package/dist/src/should-never-happen.js +0 -9
  29. package/dist/src/source-code.d.ts +0 -19
  30. package/dist/src/source-code.js +0 -90
  31. package/dist/src/stack.d.ts +0 -11
  32. package/dist/src/stack.js +0 -19
  33. package/dist/src/switch-on.d.ts +0 -1
  34. package/dist/src/switch-on.js +0 -9
  35. package/dist/src/symbol-table.d.ts +0 -6
  36. package/dist/src/symbol-table.js +0 -3
  37. package/dist/src/value.d.ts +0 -128
  38. package/dist/src/value.js +0 -634
  39. package/dist/tests/parser.spec.d.ts +0 -1
  40. package/dist/tests/parser.spec.js +0 -31
  41. package/dist/tests/septima-compute-module.spec.d.ts +0 -1
  42. package/dist/tests/septima-compute-module.spec.js +0 -30
  43. package/dist/tests/septima.spec.d.ts +0 -1
  44. package/dist/tests/septima.spec.js +0 -774
  45. package/dist/tests/value.spec.d.ts +0 -1
  46. package/dist/tests/value.spec.js +0 -355
  47. package/dist/tsconfig.tsbuildinfo +0 -1
  48. package/jest-output.json +0 -1
  49. package/src/a.js +0 -66
  50. package/src/ast-node.ts +0 -249
  51. package/src/extract-message.ts +0 -5
  52. package/src/fail-me.ts +0 -7
  53. package/src/find-array-method.ts +0 -115
  54. package/src/find-string-method.ts +0 -84
  55. package/src/index.ts +0 -1
  56. package/src/location.ts +0 -13
  57. package/src/parser.ts +0 -526
  58. package/src/result.ts +0 -45
  59. package/src/runtime.ts +0 -360
  60. package/src/scanner.ts +0 -106
  61. package/src/septima.ts +0 -114
  62. package/src/should-never-happen.ts +0 -4
  63. package/src/source-code.ts +0 -101
  64. package/src/stack.ts +0 -18
  65. package/src/switch-on.ts +0 -4
  66. package/src/symbol-table.ts +0 -7
  67. package/src/value.ts +0 -742
  68. package/tests/parser.spec.ts +0 -30
  69. package/tests/septima-compute-module.spec.ts +0 -33
  70. package/tests/septima.spec.ts +0 -839
  71. package/tests/value.spec.ts +0 -387
  72. package/tsconfig.json +0 -11
package/src/runtime.ts DELETED
@@ -1,360 +0,0 @@
1
- import { AstNode, show, span, Unit } from './ast-node'
2
- import { extractMessage } from './extract-message'
3
- import { failMe } from './fail-me'
4
- import { shouldNeverHappen } from './should-never-happen'
5
- import * as Stack from './stack'
6
- import { switchOn } from './switch-on'
7
- import { SymbolTable } from './symbol-table'
8
- import { Value } from './value'
9
-
10
- interface Placeholder {
11
- destination: undefined | Value
12
- }
13
-
14
- class SymbolFrame implements SymbolTable {
15
- constructor(readonly symbol: string, readonly placeholder: Placeholder, private readonly earlier: SymbolTable) {}
16
-
17
- lookup(sym: string): Value {
18
- if (this.symbol === sym) {
19
- const ret = this.placeholder.destination
20
- if (ret === undefined) {
21
- throw new Error(`Unresolved definition: ${this.symbol}`)
22
- }
23
- return ret
24
- }
25
-
26
- return this.earlier.lookup(sym)
27
- }
28
-
29
- export() {
30
- const ret = this.earlier.export()
31
- ret[this.symbol] = this.placeholder.destination?.export() ?? failMe(`Unbounded symbol: ${this.symbol}`)
32
- return ret
33
- }
34
-
35
- exportValue(): Record<string, Value> {
36
- const ret = this.earlier.exportValue()
37
- ret[this.symbol] = this.placeholder.destination ?? failMe(`Unbounded symbol: ${this.symbol}`)
38
- return ret
39
- }
40
- }
41
-
42
- class EmptySymbolTable implements SymbolTable {
43
- lookup(sym: string): Value {
44
- throw new Error(`Symbol ${sym} was not found`)
45
- }
46
-
47
- export() {
48
- return {}
49
- }
50
-
51
- exportValue(): Record<string, Value> {
52
- return {}
53
- }
54
- }
55
-
56
- export type Verbosity = 'quiet' | 'trace'
57
-
58
- export class Runtime {
59
- private stack: Stack.T = undefined
60
- constructor(
61
- private readonly root: AstNode,
62
- private readonly verbosity: Verbosity = 'quiet',
63
- private readonly preimports: Record<string, Value>,
64
- private readonly getAstOf: (fileName: string) => Unit,
65
- ) {}
66
-
67
- private buildInitialSymbolTable() {
68
- const empty = new EmptySymbolTable()
69
-
70
- const keys = Value.foreign(o => o.keys())
71
- const entries = Value.foreign(o => o.entries())
72
- const fromEntries = Value.foreign(o => o.fromEntries())
73
- let lib = new SymbolFrame('Object', { destination: Value.obj({ keys, entries, fromEntries }) }, empty)
74
-
75
- for (const [importName, importValue] of Object.entries(this.preimports)) {
76
- lib = new SymbolFrame(importName, { destination: importValue }, lib)
77
- }
78
- return lib
79
- }
80
-
81
- compute() {
82
- try {
83
- const value = this.evalNode(this.root, this.buildInitialSymbolTable())
84
- return { value }
85
- } catch (e) {
86
- const trace: AstNode[] = []
87
- for (let curr = this.stack; curr; curr = curr?.next) {
88
- trace.push(curr.ast)
89
- }
90
- return {
91
- expressionTrace: trace,
92
- errorMessage: extractMessage(e),
93
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
94
- stack: (e as { stack?: string[] }).stack,
95
- }
96
- }
97
- }
98
-
99
- private evalNode(ast: AstNode, table: SymbolTable): Value {
100
- this.stack = Stack.push(ast, this.stack)
101
- let ret = this.evalNodeImpl(ast, table)
102
- if (ret.isSink() && !ret.span()) {
103
- ret = ret.bindToSpan(span(ast))
104
- }
105
- switchOn(this.verbosity, {
106
- quiet: () => {},
107
- trace: () => {
108
- // eslint-disable-next-line no-console
109
- console.log(`output of <|${show(ast)}|> is ${JSON.stringify(ret)} // ${ast.tag}`)
110
- },
111
- })
112
- this.stack = Stack.pop(this.stack)
113
- return ret
114
- }
115
-
116
- private importDefinitions(pathToImportFrom: string): Value {
117
- const ast = this.getAstOf(pathToImportFrom)
118
- const exp = ast.expression
119
- const imports = ast.imports
120
- if (
121
- exp.tag === 'arrayLiteral' ||
122
- exp.tag === 'binaryOperator' ||
123
- exp.tag === 'dot' ||
124
- exp.tag === 'export*' ||
125
- exp.tag === 'functionCall' ||
126
- exp.tag === 'ident' ||
127
- exp.tag === 'if' ||
128
- exp.tag === 'indexAccess' ||
129
- exp.tag === 'lambda' ||
130
- exp.tag === 'literal' ||
131
- exp.tag === 'objectLiteral' ||
132
- exp.tag === 'unaryOperator' ||
133
- exp.tag === 'unit'
134
- ) {
135
- // TODO(imaman): throw an error on non-exporting unit?
136
- return Value.obj({})
137
- }
138
-
139
- if (exp.tag === 'topLevelExpression') {
140
- const unit: AstNode = {
141
- tag: 'unit',
142
- imports,
143
- expression: { tag: 'topLevelExpression', definitions: exp.definitions, computation: { tag: 'export*' } },
144
- }
145
- return this.evalNode(unit, this.buildInitialSymbolTable())
146
- }
147
-
148
- shouldNeverHappen(exp)
149
- }
150
-
151
- private evalNodeImpl(ast: AstNode, table: SymbolTable): Value {
152
- if (ast.tag === 'unit') {
153
- let newTable = table
154
- for (const imp of ast.imports) {
155
- const o = this.importDefinitions(imp.pathToImportFrom.text)
156
- newTable = new SymbolFrame(imp.ident.t.text, { destination: o }, newTable)
157
- }
158
- return this.evalNode(ast.expression, newTable)
159
- }
160
- if (ast.tag === 'topLevelExpression') {
161
- let newTable = table
162
- for (const def of ast.definitions) {
163
- const name = def.ident.t.text
164
- const placeholder: Placeholder = { destination: undefined }
165
- newTable = new SymbolFrame(name, placeholder, newTable)
166
- const v = this.evalNode(def.value, newTable)
167
- placeholder.destination = v
168
- }
169
-
170
- return this.evalNode(ast.computation, newTable)
171
- }
172
-
173
- if (ast.tag === 'export*') {
174
- return Value.obj(table.exportValue())
175
- }
176
-
177
- if (ast.tag === 'binaryOperator') {
178
- const lhs = this.evalNode(ast.lhs, table)
179
- if (ast.operator === '||') {
180
- return lhs.or(() => this.evalNode(ast.rhs, table))
181
- }
182
- if (ast.operator === '&&') {
183
- return lhs.and(() => this.evalNode(ast.rhs, table))
184
- }
185
-
186
- if (ast.operator === '??') {
187
- return lhs.unsink(() => this.evalNode(ast.rhs, table))
188
- }
189
-
190
- const rhs = this.evalNode(ast.rhs, table)
191
- if (ast.operator === '!=') {
192
- return lhs.equalsTo(rhs).not()
193
- }
194
- if (ast.operator === '==') {
195
- return lhs.equalsTo(rhs)
196
- }
197
-
198
- if (ast.operator === '<=') {
199
- const comp = lhs.order(rhs)
200
- return comp.isToZero('<=')
201
- }
202
- if (ast.operator === '<') {
203
- const comp = lhs.order(rhs)
204
- return comp.isToZero('<')
205
- }
206
- if (ast.operator === '>=') {
207
- const comp = lhs.order(rhs)
208
- return comp.isToZero('>=')
209
- }
210
- if (ast.operator === '>') {
211
- const comp = lhs.order(rhs)
212
- return comp.isToZero('>')
213
- }
214
- if (ast.operator === '%') {
215
- return lhs.modulo(rhs)
216
- }
217
- if (ast.operator === '*') {
218
- return lhs.times(rhs)
219
- }
220
- if (ast.operator === '**') {
221
- return lhs.power(rhs)
222
- }
223
- if (ast.operator === '+') {
224
- return lhs.plus(rhs)
225
- }
226
- if (ast.operator === '-') {
227
- return lhs.minus(rhs)
228
- }
229
- if (ast.operator === '/') {
230
- return lhs.over(rhs)
231
- }
232
-
233
- shouldNeverHappen(ast.operator)
234
- }
235
-
236
- if (ast.tag === 'unaryOperator') {
237
- const operand = this.evalNode(ast.operand, table)
238
- if (ast.operator === '!') {
239
- return operand.not()
240
- }
241
- if (ast.operator === '+') {
242
- // We intentionally do <0 + operand> instead of just <operand>. This is due to type-checking: the latter will
243
- // evaluate to the operand as-is, making expression such as `+true` dynamically valid (which is not the desired
244
- // behavior)
245
- return Value.num(0).plus(operand)
246
- }
247
- if (ast.operator === '-') {
248
- return operand.negate()
249
- }
250
-
251
- shouldNeverHappen(ast.operator)
252
- }
253
- if (ast.tag === 'ident') {
254
- return table.lookup(ast.t.text)
255
- }
256
- if (ast.tag === 'literal') {
257
- if (ast.type === 'bool') {
258
- // TODO(imaman): stricter checking of 'false'
259
- return Value.bool(ast.t.text === 'true' ? true : false)
260
- }
261
- if (ast.type === 'num') {
262
- return Value.num(Number(ast.t.text))
263
- }
264
- if (ast.type === 'sink!!') {
265
- return Value.sink(undefined, this.stack, table)
266
- }
267
- if (ast.type === 'sink!') {
268
- return Value.sink(undefined, this.stack)
269
- }
270
- if (ast.type === 'sink') {
271
- return Value.sink()
272
- }
273
- if (ast.type === 'str') {
274
- return Value.str(ast.t.text)
275
- }
276
- shouldNeverHappen(ast.type)
277
- }
278
-
279
- if (ast.tag === 'arrayLiteral') {
280
- const arr: Value[] = []
281
- for (const curr of ast.parts) {
282
- if (curr.tag === 'element') {
283
- arr.push(this.evalNode(curr.v, table))
284
- } else if (curr.tag === 'spread') {
285
- const v = this.evalNode(curr.v, table)
286
- arr.push(...v.assertArr())
287
- } else {
288
- shouldNeverHappen(curr)
289
- }
290
- }
291
-
292
- return Value.arr(arr)
293
- }
294
-
295
- if (ast.tag === 'objectLiteral') {
296
- const entries: [string, Value][] = ast.parts.flatMap(at => {
297
- if (at.tag === 'hardName') {
298
- return [[at.k.t.text, this.evalNode(at.v, table)]]
299
- }
300
- if (at.tag === 'computedName') {
301
- return [[this.evalNode(at.k, table).assertStr(), this.evalNode(at.v, table)]]
302
- }
303
- if (at.tag === 'spread') {
304
- const o = this.evalNode(at.o, table)
305
- return Object.entries(o.assertObj())
306
- }
307
-
308
- shouldNeverHappen(at)
309
- })
310
-
311
- // TODO(imaman): verify type of all keys (strings, maybe also numbers)
312
- return Value.obj(Object.fromEntries(entries))
313
- }
314
-
315
- if (ast.tag === 'lambda') {
316
- return Value.lambda(ast, table)
317
- }
318
-
319
- if (ast.tag === 'functionCall') {
320
- const argValues = ast.actualArgs.map(a => this.evalNode(a, table))
321
- const callee = this.evalNode(ast.callee, table)
322
-
323
- return this.call(callee, argValues)
324
- }
325
-
326
- if (ast.tag === 'if') {
327
- const c = this.evalNode(ast.condition, table)
328
- return c.ifElse(
329
- () => this.evalNode(ast.positive, table),
330
- () => this.evalNode(ast.negative, table),
331
- )
332
- }
333
-
334
- if (ast.tag === 'dot') {
335
- const rec = this.evalNode(ast.receiver, table)
336
- if (rec === undefined || rec === null) {
337
- throw new Error(`Cannot access attribute .${ast.ident.t.text} of ${rec}`)
338
- }
339
- return rec.access(ast.ident.t.text, (callee, args) => this.call(callee, args))
340
- }
341
-
342
- if (ast.tag === 'indexAccess') {
343
- const rec = this.evalNode(ast.receiver, table)
344
- const index = this.evalNode(ast.index, table)
345
- return rec.access(index, (callee, args) => this.call(callee, args))
346
- }
347
-
348
- shouldNeverHappen(ast)
349
- }
350
-
351
- call(callee: Value, argValues: Value[]) {
352
- return callee.call(argValues, (names, body, lambdaTable: SymbolTable) => {
353
- if (names.length > argValues.length) {
354
- throw new Error(`Arg list length mismatch: expected ${names.length} but got ${argValues.length}`)
355
- }
356
- const newTable = names.reduce((t, n, i) => new SymbolFrame(n, { destination: argValues[i] }, t), lambdaTable)
357
- return this.evalNode(body, newTable)
358
- })
359
- }
360
- }
package/src/scanner.ts DELETED
@@ -1,106 +0,0 @@
1
- import { Location } from './location'
2
- import { SourceCode } from './source-code'
3
-
4
- export interface Token {
5
- readonly text: string
6
- readonly location: Location
7
- }
8
-
9
- export class Scanner {
10
- constructor(readonly sourceCode: SourceCode, private offset = 0) {
11
- if (this.offset === 0) {
12
- this.eatWhitespace()
13
- }
14
- }
15
-
16
- get sourceRef() {
17
- return this.sourceCode.sourceRef(this.sourceCode.expandToEndOfLine({ offset: this.offset }))
18
- }
19
-
20
- private curr() {
21
- return this.sourceCode.input.substring(this.offset)
22
- }
23
-
24
- private eatWhitespace() {
25
- while (true) {
26
- if (this.consumeIf(/\s*/, false)) {
27
- continue
28
- }
29
- if (this.consumeIf('//', false)) {
30
- this.consume(/[^\n]*/, false)
31
- continue
32
- }
33
-
34
- return
35
- }
36
- }
37
-
38
- eof(): boolean {
39
- return this.offset >= this.sourceCode.input.length
40
- }
41
-
42
- synopsis() {
43
- const c = this.curr()
44
- let lookingAt = c.substring(0, 20)
45
- if (lookingAt.length !== c.length) {
46
- lookingAt = `${lookingAt}...`
47
- }
48
-
49
- return {
50
- position: this.offset,
51
- lookingAt,
52
- }
53
- }
54
-
55
- headMatches(...patterns: (RegExp | string)[]): boolean {
56
- const alt = new Scanner(this.sourceCode, this.offset)
57
- for (const p of patterns) {
58
- const t = alt.consumeIf(p, true)
59
- if (t === undefined) {
60
- return false
61
- }
62
- }
63
-
64
- return true
65
- }
66
-
67
- consume(r: RegExp | string, eatWhitespace = true): Token {
68
- const text = this.match(r)
69
- if (text === undefined) {
70
- throw new Error(`Expected ${r} ${this.sourceRef}`)
71
- }
72
-
73
- const offset = this.offset
74
- this.offset += text.length
75
-
76
- if (eatWhitespace) {
77
- this.eatWhitespace()
78
- }
79
- return { location: { offset }, text }
80
- }
81
-
82
- consumeIf(r: RegExp | string, eatWhitespace = true): Token | undefined {
83
- const ret = this.match(r)
84
- if (!ret) {
85
- return undefined
86
- }
87
-
88
- return this.consume(r, eatWhitespace)
89
- }
90
-
91
- private match(r: RegExp | string): string | undefined {
92
- if (typeof r === 'string') {
93
- if (this.curr().startsWith(r)) {
94
- return r
95
- }
96
- return undefined
97
- }
98
-
99
- const m = this.curr().match(r)
100
- if (m && m.index === 0) {
101
- return m[0]
102
- }
103
-
104
- return undefined
105
- }
106
- }
package/src/septima.ts DELETED
@@ -1,114 +0,0 @@
1
- import { Unit } from './ast-node'
2
- import { Parser } from './parser'
3
- import { Result, ResultSink, ResultSinkImpl } from './result'
4
- import { Runtime, Verbosity } from './runtime'
5
- import { Scanner } from './scanner'
6
- import { shouldNeverHappen } from './should-never-happen'
7
- import { SourceCode } from './source-code'
8
- import { Value } from './value'
9
-
10
- interface Options {
11
- /**
12
- * A callback function to be invoked when the Septima program evaluated to `sink`. Allows the caller to determine
13
- * which value will be returned in that case. For instance, passing `() => undefined` will translate a `sink` value
14
- * to `undefined`. The default behavior is to throw an error.
15
- */
16
- onSink?: (res: ResultSink) => unknown
17
- }
18
-
19
- export class Septima {
20
- /**
21
- * Runs a Septima program and returns the value it evaluates to. If it evaluates to `sink`, returns the value computed
22
- * by `options.onSink()` - if present, or throws an error - otherwise.
23
- *
24
- * This method is the simplest way to evaluate a Septima program, and it fits many common use cases. One can also use
25
- * `.compute()` to get additional details about the execution.
26
- *
27
- * @param input the source code of the Septima program
28
- * @param options
29
- * @returns the value that `input` evaluates to
30
- */
31
- static run(input: string, options?: Options): unknown {
32
- const onSink =
33
- options?.onSink ??
34
- ((r: ResultSink) => {
35
- throw new Error(r.message)
36
- })
37
- const res = new Septima().compute(input)
38
- if (res.tag === 'ok') {
39
- return res.value
40
- }
41
-
42
- if (res.tag === 'sink') {
43
- return onSink(res)
44
- }
45
-
46
- shouldNeverHappen(res)
47
- }
48
-
49
- constructor() {}
50
-
51
- computeModule(moduleName: string, moduleReader: (m: string) => string): Result {
52
- const input = moduleReader(moduleName)
53
- const sourceCode = new SourceCode(input)
54
- const value = this.computeImpl(sourceCode, 'quiet', {}, moduleReader)
55
- if (!value.isSink()) {
56
- return { value: value.export(), tag: 'ok' }
57
- }
58
- return new ResultSinkImpl(value, sourceCode)
59
- }
60
-
61
- compute(input: string, preimports: Record<string, string> = {}, verbosity: Verbosity = 'quiet'): Result {
62
- const lib: Record<string, Value> = {}
63
- for (const [importName, importCode] of Object.entries(preimports)) {
64
- const sourceCode = new SourceCode(importCode)
65
- const value = this.computeImpl(sourceCode, verbosity, {}, undefined)
66
- if (value.isSink()) {
67
- // TODO(imaman): cover!
68
- const r = new ResultSinkImpl(value, sourceCode)
69
- throw new Error(`preimport (${importName}) evaluated to sink: ${r.message}`)
70
- }
71
- lib[importName] = value
72
- }
73
-
74
- const sourceCode = new SourceCode(input)
75
- const value = this.computeImpl(sourceCode, verbosity, lib, undefined)
76
- if (!value.isSink()) {
77
- return { value: value.export(), tag: 'ok' }
78
- }
79
- return new ResultSinkImpl(value, sourceCode)
80
- }
81
-
82
- private computeImpl(
83
- sourceCode: SourceCode,
84
- verbosity: Verbosity,
85
- lib: Record<string, Value>,
86
- moduleReader: undefined | ((m: string) => string),
87
- ) {
88
- const scanner = new Scanner(sourceCode)
89
- const parser = new Parser(scanner)
90
- const ast = parse(parser)
91
-
92
- const getAstOf = (fileName: string) => {
93
- if (!moduleReader) {
94
- throw new Error(`cannot read modules`)
95
- }
96
- return parse(moduleReader(fileName))
97
- }
98
- const runtime = new Runtime(ast, verbosity, lib, getAstOf)
99
- const c = runtime.compute()
100
-
101
- if (c.value) {
102
- return c.value
103
- }
104
-
105
- const runtimeErrorMessage = `${c.errorMessage} when evaluating:\n${sourceCode.formatTrace(c.expressionTrace)}`
106
- throw new Error(runtimeErrorMessage)
107
- }
108
- }
109
-
110
- export function parse(arg: string | Parser): Unit {
111
- const parser = typeof arg === 'string' ? new Parser(new Scanner(new SourceCode(arg))) : arg
112
- const ast = parser.parse()
113
- return ast
114
- }
@@ -1,4 +0,0 @@
1
- export function shouldNeverHappen(n: never): never {
2
- // This following line never gets executed. It is here just to make the compiler happy.
3
- throw new Error(`This should never happen ${n}`)
4
- }
@@ -1,101 +0,0 @@
1
- import { AstNode, span } from './ast-node'
2
- import { Location, Location2d, Span } from './location'
3
-
4
- export class SourceCode {
5
- constructor(readonly input: string) {}
6
-
7
- formatTrace(trace: AstNode[]): string {
8
- const spacer = ' '
9
-
10
- const formatted = trace
11
- .map(ast => this.sourceRef(span(ast)))
12
- .reverse()
13
- .join(`\n${spacer}`)
14
- return `${spacer}${formatted}`
15
- }
16
-
17
- sourceRefOfLocation(loc: Location) {
18
- return this.sourceRef(this.expandToEndOfLine(loc))
19
- }
20
-
21
- sourceRef(span: Span | undefined) {
22
- if (!span) {
23
- return `at <unknown location>`
24
- }
25
- return `at ${this.formatSpan(span)} ${this.interestingPart(span)}`
26
- }
27
-
28
- formatSpan(span: Span) {
29
- const f = this.resolveLocation(span.from)
30
- const t = this.resolveLocation(span.to)
31
- if (f.line === t.line) {
32
- return `(${f.line + 1}:${f.col + 1}..${t.col + 1})`
33
- }
34
-
35
- return `(${f.line + 1}:${f.col + 1}..${t.line + 1}:${t.col + 1})`
36
- }
37
-
38
- private interestingPart(span: Span) {
39
- const f = this.resolveLocation(span.from)
40
- const t = this.resolveLocation(span.to)
41
-
42
- const strip = (s: string) => s.replace(/^[\n]*/, '').replace(/[\n]*$/, '')
43
- const limit = 80
44
-
45
- const lineAtFrom = this.lineAt(span.from)
46
- if (f.line !== t.line) {
47
- return `${strip(lineAtFrom).substring(0, limit)}...`
48
- }
49
-
50
- const stripped = strip(lineAtFrom.substring(f.col, t.col + 1))
51
- if (stripped.length <= limit) {
52
- return stripped
53
- }
54
- return `${stripped.substring(0, limit)}...`
55
- }
56
-
57
- resolveLocation(loc: Location): Location2d {
58
- const prefix = this.input.slice(0, loc.offset)
59
- let line = 0
60
- for (let i = 0; i < prefix.length; ++i) {
61
- const ch = prefix[i]
62
- if (ch === '\n') {
63
- line += 1
64
- }
65
- }
66
-
67
- let col = 0
68
- for (let i = prefix.length - 1; i >= 0; --i, ++col) {
69
- const ch = prefix[i]
70
- if (ch === '\n') {
71
- break
72
- }
73
- }
74
-
75
- return { line, col }
76
- }
77
-
78
- /**
79
- * Returns a span starting a the given input location that runs all the way to the end of the line. Specifically, the
80
- * returned span's `.to` location will point at the character (in the same line as `loc`) that is immediately before
81
- * the terminating newline character.
82
- */
83
- expandToEndOfLine(loc: Location): Span {
84
- let endOfLine = this.input.indexOf('\n', loc.offset)
85
- if (endOfLine < 0) {
86
- endOfLine = this.input.length - 1
87
- }
88
- return { from: loc, to: { offset: endOfLine } }
89
- }
90
-
91
- lineAt(loc: Location) {
92
- const precedingNewline = this.input.lastIndexOf('\n', loc.offset)
93
- // add a + 1 to skip over the '\n' character (it is not part of the line). Also works if precedingNewLine is -1 (no
94
- // preceding newline exists)
95
- const startOfLine = precedingNewline + 1
96
- const endOfLine = this.expandToEndOfLine(loc).to
97
-
98
- const ret = this.input.substring(startOfLine, endOfLine.offset + 1)
99
- return ret
100
- }
101
- }
package/src/stack.ts DELETED
@@ -1,18 +0,0 @@
1
- import { AstNode } from './ast-node'
2
- export type T = { ast: AstNode; next: T } | undefined
3
-
4
- export function push(ast: AstNode, s: T) {
5
- return { ast, next: s }
6
- }
7
-
8
- export function pop(s: T) {
9
- if (typeof s === 'undefined') {
10
- throw new Error(`Cannot pop from an empty stack`)
11
- }
12
-
13
- return s.next
14
- }
15
-
16
- export function empty(): T {
17
- return undefined
18
- }
package/src/switch-on.ts DELETED
@@ -1,4 +0,0 @@
1
- export function switchOn<G, K extends string>(selector: K, cases: Record<K, () => G>): G {
2
- const f = cases[selector]
3
- return f()
4
- }