Browse Source

Replace compiler with evaluator

This class exposes two functions. `evalNode` take an AST node and evaluates it down to simplest terms. `eval` takes an array of AST nodes and calls evalNode on each of them. I don’t think we’re quite ready to be producing HTML yet so let’s not get ahead of ourselves.
master
Dylan Baker 5 years ago
parent
commit
369853af3e
10 changed files with 78 additions and 157 deletions
  1. 0
    57
      src/compiler.js
  2. 9
    9
      src/env.js
  3. 27
    0
      src/evaluator.js
  4. 7
    6
      src/index.js
  5. 1
    4
      src/parser.js
  6. 0
    46
      test/compilerTest.js
  7. 0
    21
      test/coreTest.js
  8. 21
    0
      test/evaluatorTest.js
  9. 10
    7
      test/helpers.js
  10. 3
    7
      test/parserTest.js

+ 0
- 57
src/compiler.js View File

@@ -1,57 +0,0 @@
1
-const AST = require('./ast')
2
-const Env = require('./env')
3
-
4
-module.exports = class Compiler {
5
-  constructor(env = null) {
6
-    this.result = ''
7
-
8
-    if (env) {
9
-      this.env = env
10
-    } else {
11
-      this.env = new Env()
12
-    }
13
-  }
14
-
15
-  compile(tree) {
16
-    tree.forEach(node => {
17
-      this.result += this.compileNode(node)
18
-    })
19
-
20
-    return this.result
21
-  }
22
-
23
-  compileNode(node) {
24
-    switch (node.constructor) {
25
-      case AST.Number:
26
-      case AST.String:
27
-        return node.value
28
-      case AST.Boolean:
29
-        return node.value === true ? '#t' : '#f'
30
-      case AST.Identifier:
31
-        return this.compileNode(this.env.get(node.name))
32
-      case AST.Conditional:
33
-        let condition = this.compileNode(node.condition)
34
-
35
-        if (condition) {
36
-          return this.compileNode(node.ifCase)
37
-        } else {
38
-          return this.compileNode(node.elseCase)
39
-        }
40
-      case AST.Application:
41
-        if (node.function.constructor === AST.Identifier) {
42
-          let f = this.env.get(node.function.name)
43
-          return this.compileNode(f(...node.args.map(arg => this.compileNode(arg))))
44
-        } else if (node.function.constructor === AST.Lambda) {
45
-          let env = new Env(this.env)
46
-          node.function.parameters.forEach((param, index) => {
47
-            env.set(param.name, node.args[index])
48
-          })
49
-
50
-          let compiler = new Compiler(env)
51
-          return compiler.compileNode(node.function.body)
52
-        }
53
-
54
-        return ''
55
-    }
56
-  }
57
-}

+ 9
- 9
src/env.js View File

@@ -6,15 +6,15 @@ module.exports = class Env {
6 6
       this.data = parent.data
7 7
     } else {
8 8
       this.data = {
9
-        '+': (a, b) => new AST.Number({ value: a + b }),
10
-        '-': (a, b) => new AST.Number({ value: a - b }),
11
-        '*': (a, b) => new AST.Number({ value: a * b }),
12
-        '/': (a, b) => new AST.Number({ value: a / b }),
13
-        '=': (a, b) => new AST.Boolean({ value: a === b }),
14
-        '>': (a, b) => new AST.Boolean({ value: a > b }),
15
-        '<': (a, b) => new AST.Boolean({ value: a < b }),
16
-        '>=': (a, b) => new AST.Boolean({ value: a >= b }),
17
-        '<=': (a, b) => new AST.Boolean({ value: a <= b }),
9
+        '+': (a, b) => new AST.Number({ value: a.value + b.value }),
10
+        '-': (a, b) => new AST.Number({ value: a.value - b.value }),
11
+        '*': (a, b) => new AST.Number({ value: a.value * b.value }),
12
+        '/': (a, b) => new AST.Number({ value: a.value / b.value }),
13
+        '=': (a, b) => new AST.Boolean({ value: a.value === b.value }),
14
+        '>': (a, b) => new AST.Boolean({ value: a.value > b.value }),
15
+        '<': (a, b) => new AST.Boolean({ value: a.value < b.value }),
16
+        '>=': (a, b) => new AST.Boolean({ value: a.value >= b.value }),
17
+        '<=': (a, b) => new AST.Boolean({ value: a.value <= b.value }),
18 18
       }
19 19
     }
20 20
   }

+ 27
- 0
src/evaluator.js View File

@@ -0,0 +1,27 @@
1
+const AST = require('./ast')
2
+const Env = require('./env')
3
+
4
+module.exports = class Evaluator {
5
+  eval(tree, env) {
6
+    let evaluatedTree = []
7
+
8
+    tree.forEach(node => {
9
+      evaluatedTree.push(this.evalNode(node, env))
10
+    })
11
+
12
+    return evaluatedTree
13
+  }
14
+
15
+  evalNode(node, env) {
16
+    switch (node.constructor) {
17
+      case AST.Application:
18
+        switch (node.function.constructor) {
19
+          case AST.Identifier:
20
+            node.function = env.get(node.function.name)
21
+            break
22
+        }
23
+    }
24
+
25
+    return node
26
+  }
27
+}

+ 7
- 6
src/index.js View File

@@ -1,4 +1,5 @@
1
-const Compiler = require('./compiler')
1
+const Env = require('./env')
2
+const Evaluator = require('./evaluator')
2 3
 const Lexer = require('./lexer')
3 4
 const Parser = require('./parser')
4 5
 
@@ -6,11 +7,11 @@ module.exports = function oslo(source, context) {
6 7
   const lexer = new Lexer()
7 8
   const tokens = lexer.scan(source)
8 9
 
9
-  const parser = new Parser(tokens)
10
-  const tree = parser.parse()
10
+  const parser = new Parser()
11
+  const tree = parser.parse(tokens)
11 12
 
12
-  const compiler = new Compiler()
13
-  const result = compiler.compile(tree)
13
+  const evaluator = new Evaluator()
14
+  const evaluatedTree = evaluator.eval(tree, new Env())
14 15
 
15
-  return result
16
+  return ''
16 17
 }

+ 1
- 4
src/parser.js View File

@@ -3,11 +3,8 @@ const Error = require('./Error')
3 3
 const tokenTypes = require('./tokenTypes')
4 4
 
5 5
 module.exports = class Parser {
6
-  constructor(tokenStream) {
6
+  parse(tokenStream) {
7 7
     this.tokenStream = tokenStream
8
-  }
9
-
10
-  parse() {
11 8
     let tree = []
12 9
     while (this.tokenStream.peek().type !== tokenTypes.EOF) {
13 10
       let expr = this.expr()

+ 0
- 46
test/compilerTest.js View File

@@ -1,46 +0,0 @@
1
-const test = require('tape')
2
-const helpers = require('./helpers')
3
-
4
-const AST = require('../src/ast')
5
-
6
-test('compiles numbers', t => {
7
-  t.plan(1)
8
-
9
-  const result = helpers.compile('5')
10
-
11
-  t.equal(result, '5')
12
-})
13
-
14
-test('compiles strings', t => {
15
-  t.plan(1)
16
-
17
-  const result = helpers.compile('"hello world"')
18
-
19
-  t.equal(result, 'hello world')
20
-})
21
-
22
-test('compiles core function applications', t => {
23
-  t.plan(4)
24
-
25
-  let result = helpers.compile('(+ 10 8)')
26
-  t.equal(result, '18')
27
-
28
-  result = helpers.compile('(- 7 3)')
29
-  t.equal(result, '4')
30
-
31
-  result = helpers.compile('(* 9 3)')
32
-  t.equal(result, '27')
33
-
34
-  result = helpers.compile('(/ 10 2)')
35
-  t.equal(result, '5')
36
-})
37
-
38
-test('compiles lambda expressions in the function position', t => {
39
-  t.plan(2)
40
-
41
-  let result = helpers.compile('((lambda (x) (+ x 1)) 2)')
42
-  t.equal(result, '3')
43
-
44
-  result = helpers.compile('((lambda (x y) (+ x y)) 2 3)')
45
-  t.equal(result, '5')
46
-})

+ 0
- 21
test/coreTest.js View File

@@ -1,21 +0,0 @@
1
-const test = require('tape')
2
-const helpers = require('./helpers')
3
-
4
-test('comparison functions', t => {
5
-  t.plan(5)
6
-
7
-  let result = helpers.compile('(= 1 2)')
8
-  t.equal(result, '#f')
9
-
10
-  result = helpers.compile('(> 5 4)')
11
-  t.equal(result, '#t')
12
-
13
-  result = helpers.compile('(< 58 10)')
14
-  t.equal(result, '#f')
15
-
16
-  result = helpers.compile('(>= 5 5)')
17
-  t.equal(result, '#t')
18
-
19
-  result = helpers.compile('(<= 10 10)')
20
-  t.equal(result, '#t')
21
-})

+ 21
- 0
test/evaluatorTest.js View File

@@ -0,0 +1,21 @@
1
+const test = require('tape')
2
+const helpers = require('./helpers')
3
+
4
+const AST = require('../src/ast')
5
+const Evaluator = require('../src/evaluator')
6
+
7
+test('looks up and injects the function to which a symbol refers', t => {
8
+  t.plan(2)
9
+
10
+  const tree = helpers.evaluate('(+ 1 2)')
11
+
12
+  t.deepEqual(tree[0].function.constructor, Function)
13
+  t.deepEqual(
14
+    tree[0].function.call(
15
+      this,
16
+      new AST.Number({ value: 4 }),
17
+      new AST.Number({ value: 5 })
18
+    ),
19
+    new AST.Number({ value: 9 })
20
+  )
21
+})

+ 10
- 7
test/helpers.js View File

@@ -1,4 +1,5 @@
1
-const Compiler = require('../src/compiler')
1
+const Env = require('../src/env')
2
+const Evaluator = require('../src/evaluator')
2 3
 const Lexer = require('../src/lexer')
3 4
 const Parser = require('../src/parser')
4 5
 
@@ -8,17 +9,19 @@ const scan = source => {
8 9
 }
9 10
 
10 11
 const parse = source => {
11
-  const parser = new Parser(scan(source))
12
-  return parser.parse()
12
+  const parser = new Parser()
13
+  return parser.parse(scan(source))
13 14
 }
14 15
 
15
-const compile = source => {
16
-  const compiler = new Compiler()
17
-  return compiler.compile(parse(source))
16
+const evaluate = source => {
17
+  const evaluator = new Evaluator()
18
+  return evaluator.eval(parse(source), new Env())
18 19
 }
19 20
 
20 21
 module.exports = {
21
-  compile: compile,
22
+  evaluate: evaluate,
22 23
   parse: parse,
23 24
   scan: scan,
24 25
 }
26
+
27
+console.log(evaluate('(+ 1 2)'))

+ 3
- 7
test/parserTest.js View File

@@ -82,16 +82,12 @@ test('parse conditionals', t => {
82 82
       condition: new AST.Boolean({ value: true }),
83 83
       ifCase: new AST.Application({
84 84
         function: new AST.Identifier({ name: 'do' }),
85
-        args: [
86
-          new AST.Identifier({ name: 'this' })
87
-        ]
85
+        args: [new AST.Identifier({ name: 'this' })],
88 86
       }),
89 87
       elseCase: new AST.Application({
90 88
         function: new AST.Identifier({ name: 'do' }),
91
-        args: [
92
-          new AST.Identifier({ name: 'that' })
93
-        ]
89
+        args: [new AST.Identifier({ name: 'that' })],
94 90
       }),
95
-    })
91
+    }),
96 92
   ])
97 93
 })

Loading…
Cancel
Save