septima-lang 0.0.2 → 0.0.3
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/parser.d.ts +2 -0
- package/dist/src/parser.js +61 -5
- package/dist/src/runtime.d.ts +2 -3
- package/dist/src/runtime.js +7 -4
- package/dist/src/scanner.d.ts +2 -1
- package/dist/src/scanner.js +16 -4
- package/dist/src/septima.d.ts +3 -4
- package/dist/src/septima.js +28 -12
- package/dist/tests/septima.spec.js +68 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jest-output.json +1 -1
- package/package.json +1 -1
- package/src/a.js +66 -0
- package/src/parser.ts +69 -5
- package/src/runtime.ts +7 -3
- package/src/scanner.ts +16 -4
- package/src/septima.ts +30 -16
- package/tests/septima.spec.ts +67 -0
package/src/septima.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Runtime, Verbosity } from './runtime'
|
|
|
4
4
|
import { Scanner } from './scanner'
|
|
5
5
|
import { shouldNeverHappen } from './should-never-happen'
|
|
6
6
|
import { SourceCode } from './source-code'
|
|
7
|
+
import { Value } from './value'
|
|
7
8
|
|
|
8
9
|
interface Options {
|
|
9
10
|
/**
|
|
@@ -15,10 +16,6 @@ interface Options {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export class Septima {
|
|
18
|
-
private readonly scanner
|
|
19
|
-
private readonly sourceCode
|
|
20
|
-
private readonly parser
|
|
21
|
-
|
|
22
19
|
/**
|
|
23
20
|
* Runs a Septima program and returns the value it evaluates to. If it evaluates to `sink`, returns the value computed
|
|
24
21
|
* by `options.onSink()` - if present, or throws an error - otherwise.
|
|
@@ -48,25 +45,42 @@ export class Septima {
|
|
|
48
45
|
shouldNeverHappen(res)
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
constructor(readonly input: string) {
|
|
52
|
-
this.sourceCode = new SourceCode(this.input)
|
|
53
|
-
this.scanner = new Scanner(this.sourceCode)
|
|
54
|
-
this.parser = new Parser(this.scanner)
|
|
55
|
-
}
|
|
48
|
+
constructor(readonly input: string, private readonly preimports: Record<string, string> = {}) {}
|
|
56
49
|
|
|
57
50
|
compute(verbosity: Verbosity = 'quiet'): Result {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
51
|
+
const lib: Record<string, Value> = {}
|
|
52
|
+
for (const [importName, importCode] of Object.entries(this.preimports)) {
|
|
53
|
+
const sourceCode = new SourceCode(importCode)
|
|
54
|
+
const value = this.computeImpl(sourceCode, verbosity, {})
|
|
55
|
+
if (value.isSink()) {
|
|
56
|
+
// TODO(imaman): cover!
|
|
57
|
+
const r = new ResultSinkImpl(value, sourceCode)
|
|
58
|
+
throw new Error(`preimport (${importName}) evaluated to sink: ${r.message}`)
|
|
59
|
+
}
|
|
60
|
+
lib[importName] = value
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const sourceCode = new SourceCode(this.input)
|
|
64
|
+
const value = this.computeImpl(sourceCode, verbosity, lib)
|
|
65
|
+
if (!value.isSink()) {
|
|
66
|
+
return { value: value.export(), tag: 'ok' }
|
|
67
|
+
}
|
|
68
|
+
return new ResultSinkImpl(value, sourceCode)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private computeImpl(sourceCode: SourceCode, verbosity: Verbosity, lib: Record<string, Value>) {
|
|
72
|
+
const scanner = new Scanner(sourceCode)
|
|
73
|
+
const parser = new Parser(scanner)
|
|
74
|
+
|
|
75
|
+
const ast = parse(parser)
|
|
76
|
+
const runtime = new Runtime(ast, verbosity, lib)
|
|
60
77
|
const c = runtime.compute()
|
|
61
78
|
|
|
62
79
|
if (c.value) {
|
|
63
|
-
|
|
64
|
-
return { value: c.value.export(), tag: 'ok' }
|
|
65
|
-
}
|
|
66
|
-
return new ResultSinkImpl(c.value, this.sourceCode)
|
|
80
|
+
return c.value
|
|
67
81
|
}
|
|
68
82
|
|
|
69
|
-
const runtimeErrorMessage = `${c.errorMessage} when evaluating:\n${
|
|
83
|
+
const runtimeErrorMessage = `${c.errorMessage} when evaluating:\n${sourceCode.formatTrace(c.expressionTrace)}`
|
|
70
84
|
throw new Error(runtimeErrorMessage)
|
|
71
85
|
}
|
|
72
86
|
}
|
package/tests/septima.spec.ts
CHANGED
|
@@ -30,6 +30,10 @@ describe('septima', () => {
|
|
|
30
30
|
expect(() => run(`6 789`)).toThrowError(`Loitering input at (1:3..5) 789`)
|
|
31
31
|
expect(run(`3.14`)).toEqual(3.14)
|
|
32
32
|
})
|
|
33
|
+
test('an optional return keyword can be placed before the result', () => {
|
|
34
|
+
expect(run(`return 5`)).toEqual(5)
|
|
35
|
+
expect(run(`return 3.14`)).toEqual(3.14)
|
|
36
|
+
})
|
|
33
37
|
|
|
34
38
|
test('booleans', () => {
|
|
35
39
|
expect(run(`true`)).toEqual(true)
|
|
@@ -200,6 +204,10 @@ describe('septima', () => {
|
|
|
200
204
|
expect(run(`let x = 5; x+3`)).toEqual(8)
|
|
201
205
|
expect(run(`let x = 5; let y = 20; x*y+4`)).toEqual(104)
|
|
202
206
|
})
|
|
207
|
+
test('do not need the trailing semicolon', () => {
|
|
208
|
+
expect(run(`let x = 5 x+3`)).toEqual(8)
|
|
209
|
+
expect(run(`let x = 5 let y = 20 x*y+4`)).toEqual(104)
|
|
210
|
+
})
|
|
203
211
|
test('fails if the variable was not defined', () => {
|
|
204
212
|
expect(() => run(`let x = 5; x+y`)).toThrowError('Symbol y was not found')
|
|
205
213
|
})
|
|
@@ -282,6 +290,14 @@ describe('septima', () => {
|
|
|
282
290
|
expect(run(`{a: 1, b: 2}`)).toEqual({ a: 1, b: 2 })
|
|
283
291
|
expect(run(`{a: "A", b: "B", c: "CCC"}`)).toEqual({ a: 'A', b: 'B', c: 'CCC' })
|
|
284
292
|
})
|
|
293
|
+
test('allow a dangling comma', () => {
|
|
294
|
+
expect(run(`{a: 1,}`)).toEqual({ a: 1 })
|
|
295
|
+
expect(run(`{a: 1, b: 2,}`)).toEqual({ a: 1, b: 2 })
|
|
296
|
+
expect(run(`{a: "A", b: "B", c: "CCC",}`)).toEqual({ a: 'A', b: 'B', c: 'CCC' })
|
|
297
|
+
})
|
|
298
|
+
test('a dangling comma in an empty object is not allowed', () => {
|
|
299
|
+
expect(() => run(`{,}`)).toThrowError('Expected an identifier at (1:2..3) ,}')
|
|
300
|
+
})
|
|
285
301
|
test('supports computed attributes names via the [<expression>]: <value> notation', () => {
|
|
286
302
|
expect(run(`{["a" + 'b']: 'a-and-b'}`)).toEqual({ ab: 'a-and-b' })
|
|
287
303
|
})
|
|
@@ -408,6 +424,33 @@ describe('septima', () => {
|
|
|
408
424
|
expect(run(`let triple = (fun(a) 3*a); triple(100) - triple(90)`)).toEqual(30)
|
|
409
425
|
expect(run(`let triple = fun(a) 3*a; triple(100) - triple(90)`)).toEqual(30)
|
|
410
426
|
})
|
|
427
|
+
describe('arrow function notation', () => {
|
|
428
|
+
test('a single formal argument does not need to be surrounded with parenthesis', () => {
|
|
429
|
+
expect(run(`let triple = a => 3*a; triple(100)`)).toEqual(300)
|
|
430
|
+
})
|
|
431
|
+
test('(a) => <expression>', () => {
|
|
432
|
+
expect(run(`let triple = (a) => 3*a; triple(100)`)).toEqual(300)
|
|
433
|
+
})
|
|
434
|
+
test('() => <expression>', () => {
|
|
435
|
+
expect(run(`let five = () => 5; five()`)).toEqual(5)
|
|
436
|
+
})
|
|
437
|
+
test('(a,b) => <expression>', () => {
|
|
438
|
+
expect(run(`let conc = (a,b) => a+b; conc('al', 'pha')`)).toEqual('alpha')
|
|
439
|
+
expect(run(`let conc = (a,b,c,d,e,f) => a+b+c+d+e+f; conc('M', 'o', 'n', 'd', 'a', 'y')`)).toEqual('Monday')
|
|
440
|
+
})
|
|
441
|
+
test('body of an arrow function can be { return <expression>}', () => {
|
|
442
|
+
expect(run(`let triple = a => { return 3*a }; triple(100)`)).toEqual(300)
|
|
443
|
+
expect(run(`let triple = (a) => { return 3*a }; triple(100)`)).toEqual(300)
|
|
444
|
+
expect(run(`let five = () => { return 5 }; five()`)).toEqual(5)
|
|
445
|
+
expect(run(`let concat = (a,b) => { return a+b }; concat('al', 'pha')`)).toEqual('alpha')
|
|
446
|
+
})
|
|
447
|
+
test('body of an arrow function can include let definitions', () => {
|
|
448
|
+
expect(run(`let triple = a => { let factor = 3; return factor*a }; triple(100)`)).toEqual(300)
|
|
449
|
+
expect(run(`let triple = (a) => { let factor = 3; return 3*a }; triple(100)`)).toEqual(300)
|
|
450
|
+
expect(run(`let five = () => { let two = 2; let three = 3; return three+two }; five()`)).toEqual(5)
|
|
451
|
+
expect(run(`let concat = (a,b) => { let u = '_'; return u+a+b+u }; concat('a', 'b')`)).toEqual('_ab_')
|
|
452
|
+
})
|
|
453
|
+
})
|
|
411
454
|
test('can have no args', () => {
|
|
412
455
|
expect(run(`let pi = fun() 3.14; 2*pi()`)).toEqual(6.28)
|
|
413
456
|
expect(run(`(fun() 3.14)()*2`)).toEqual(6.28)
|
|
@@ -743,6 +786,28 @@ describe('septima', () => {
|
|
|
743
786
|
expect(run(`let count = fun (n) if (n <= 0) 0 else 1 + count(n-1); count(330)`)).toEqual(330)
|
|
744
787
|
})
|
|
745
788
|
})
|
|
789
|
+
describe('preimport', () => {
|
|
790
|
+
test('definitions from a preimported file can be used', () => {
|
|
791
|
+
const septima = new Septima(`libA.plus10(4) + libA.plus20(2)`, {
|
|
792
|
+
libA: `{ plus10: fun (n) n+10, plus20: fun (n) n+20}`,
|
|
793
|
+
})
|
|
794
|
+
expect(septima.compute()).toMatchObject({ value: 36 })
|
|
795
|
+
})
|
|
796
|
+
test('supports multiple preimports', () => {
|
|
797
|
+
const septima = new Septima(`a.calc(4) + b.calc(1)`, {
|
|
798
|
+
a: `{ calc: fun (n) n+10 }`,
|
|
799
|
+
b: `{ calc: fun (n) n+20 }`,
|
|
800
|
+
})
|
|
801
|
+
expect(septima.compute()).toMatchObject({ value: 35 })
|
|
802
|
+
})
|
|
803
|
+
})
|
|
804
|
+
test.todo('support file names in locations')
|
|
805
|
+
test.todo('string interpolation via `foo` strings')
|
|
806
|
+
test.todo('imports')
|
|
807
|
+
test.todo('arrow functions')
|
|
808
|
+
test.todo('optional parameters')
|
|
809
|
+
test.todo('optional type annotations?')
|
|
810
|
+
test.todo('allow redundant commas')
|
|
746
811
|
test.todo('left associativity of +/-')
|
|
747
812
|
test.todo('comparison of arrays')
|
|
748
813
|
test.todo('comparison of lambdas?')
|
|
@@ -752,4 +817,6 @@ describe('septima', () => {
|
|
|
752
817
|
test.todo('quoting of a ticks inside a string')
|
|
753
818
|
test.todo('number in scientific notation')
|
|
754
819
|
test.todo('number methods')
|
|
820
|
+
test.todo('drop the fun () notation and use just arrow functions')
|
|
821
|
+
test.todo('proper internal representation of arrow function, in particular: show(), span()')
|
|
755
822
|
})
|