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/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 ast = parse(this.parser)
59
- const runtime = new Runtime(ast, verbosity, this.parser)
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
- if (!c.value.isSink()) {
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${this.sourceCode.formatTrace(c.expressionTrace)}`
83
+ const runtimeErrorMessage = `${c.errorMessage} when evaluating:\n${sourceCode.formatTrace(c.expressionTrace)}`
70
84
  throw new Error(runtimeErrorMessage)
71
85
  }
72
86
  }
@@ -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
  })