Bladeren bron

My first compiler :)

Matt Coles 9 jaren geleden
commit
cbf5475de5
3 gewijzigde bestanden met toevoegingen van 264 en 0 verwijderingen
  1. 2 0
      .gitignore
  2. 237 0
      compiler.js
  3. 25 0
      stdlib.js

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
1
+*.mc
2
+output.js

+ 237 - 0
compiler.js

@@ -0,0 +1,237 @@
1
+const fs = require('fs')
2
+
3
+var tokenizer = function (input) {
4
+  let pos = 0
5
+  let tokens = []
6
+
7
+  tokens.push(input)
8
+
9
+  while (pos < input.length) {
10
+    let char = input[pos]
11
+    let parens = /[()]/
12
+    if (parens.test(char)) {
13
+      tokens.push({
14
+        type: 'paren',
15
+        value: char
16
+      })
17
+      pos++
18
+      continue
19
+    }
20
+    let whitespace = /\s/
21
+    if (whitespace.test(char)) {
22
+      pos++
23
+      continue
24
+    }
25
+    let numbers = /[0-9]/
26
+    if (numbers.test(char)) {
27
+      let numberString = ''
28
+      while (numbers.test(char)) {
29
+        numberString += char
30
+        char = input[++pos]
31
+      }
32
+      tokens.push({
33
+        type: 'number',
34
+        value: numberString
35
+      })
36
+      continue
37
+    }
38
+    let characters = /[a-zA-Z_]/
39
+    if (characters.test(char)) {
40
+      let name = ''
41
+      while (characters.test(char)) {
42
+        name += char
43
+        char = input[++pos]
44
+      }
45
+      tokens.push({
46
+        type: 'name',
47
+        value: name
48
+      })
49
+      continue
50
+    }
51
+    throw new TypeError("I'm not sure what you are telling me :( Ask my creator to teach me what a: " + char + " is.")
52
+  }
53
+  return tokens
54
+}
55
+
56
+var parser = function (input) {
57
+  let pos = 1
58
+
59
+  function walk() {
60
+    let token = input[pos]
61
+
62
+    if (token.type === 'number') {
63
+      pos++
64
+      return {
65
+        type: 'NumberLiteral',
66
+        value: token.value
67
+      }
68
+    }
69
+
70
+    if (token.type === 'name') {
71
+      pos++
72
+      return {
73
+        type: 'VariableReference',
74
+        value: token.value
75
+      }
76
+    }
77
+
78
+    if (token.type === 'paren' && token.value == '(') {
79
+      token = input[++pos]
80
+      if (token.type !== 'name') {
81
+        throw {
82
+          name: 'Compiler Error',
83
+          message: 'FunctionCall may only be type "name" not "' + token.type + '".'
84
+        }
85
+      }
86
+      let node = {
87
+        type: 'FunctionCall',
88
+        value: token.value,
89
+        params: []
90
+      }
91
+      token = input[++pos]
92
+      while ((token.type !== 'paren') || (token.type === 'paren' && token.value !== ')')) {
93
+        node.params.push(walk())
94
+        token = input[pos]
95
+      }
96
+      pos++
97
+      return node
98
+    }
99
+
100
+    throw new TypeError(token.type)
101
+  }
102
+
103
+  let ast = {
104
+    type: 'Prog',
105
+    body: []
106
+  }
107
+  while (pos < input.length) {
108
+    ast.body.push(walk())
109
+  }
110
+  return ast
111
+}
112
+
113
+var traverser = function (ast, visitor) {
114
+
115
+  function traverseArray(array, parent) {
116
+    array.forEach(function (child) {
117
+      traverseNode(child, parent)
118
+    })
119
+  }
120
+
121
+  function traverseNode(node, parent) {
122
+    const method = visitor[node.type]
123
+    
124
+    if (method) {
125
+      method(node, parent)
126
+    }
127
+
128
+    switch (node.type) {
129
+      case 'Prog':
130
+        traverseArray(node.body, node)
131
+        break
132
+      case 'FunctionCall':
133
+        traverseArray(node.params, node)
134
+        break
135
+      case 'VariableReference':
136
+        break
137
+      case 'NumberLiteral':
138
+        break
139
+      default:
140
+        throw {
141
+          name: 'Compiler Error',
142
+          message: 'Unknown leaf in AST: ' + node.type
143
+        }
144
+    }
145
+  }
146
+
147
+  traverseNode(ast, null)
148
+}
149
+
150
+var transformer = function (ast) {
151
+
152
+  let newAst = {
153
+    type: 'Prog',
154
+    body: []
155
+  }
156
+
157
+  ast._context = newAst.body
158
+
159
+  traverser(ast, {
160
+    NumberLiteral: function (node, parent) {
161
+      parent._context.push({
162
+        type: 'NumberLiteral',
163
+        value: node.value
164
+      })
165
+    },
166
+    VariableReference: function (node, parent) {
167
+      parent._context.push({
168
+        type: 'VariableReference',
169
+        value: node.value
170
+      })
171
+    },
172
+    FunctionCall: function (node, parent) {
173
+      let expression = {
174
+        type: 'FunctionCall',
175
+        callee: {
176
+          type: 'FunctionName',
177
+          name: node.value
178
+        },
179
+        args: []
180
+      }
181
+      node._context = expression.args
182
+      if (parent.type !== 'FunctionCall') {
183
+        expression = {
184
+          type: 'Statement',
185
+          expr: expression
186
+        }
187
+      }
188
+
189
+      parent._context.push(expression)
190
+    }
191
+  })
192
+
193
+  return newAst
194
+}
195
+
196
+
197
+var generator = function (node) {
198
+
199
+  switch (node.type) {
200
+    case 'Prog':
201
+      let program = node.body.map(generator)
202
+      program.unshift('var _ = require("./stdlib.js")')
203
+      return program.join('\n')
204
+      break
205
+    case 'Statement':
206
+      return (generator(node.expr) + ';')
207
+      break
208
+    case 'FunctionCall':
209
+      return (generator(node.callee) + '(' + node.args.map(generator).join(', ') + ')')
210
+      break
211
+    case 'FunctionName':
212
+      return '_.' + node.name
213
+      break
214
+    case 'VariableReference':
215
+      return '_.ref("' + node.value + '")'
216
+      break
217
+    case 'NumberLiteral':
218
+      return '{value: ' + node.value + '}'
219
+      break
220
+    default:
221
+      throw {
222
+        name: 'Compiler Error',
223
+        message: 'Unexpected leaf in transformed AST: ' + node.type
224
+      }
225
+      break
226
+  }
227
+
228
+}
229
+
230
+
231
+// const myInput = '(assign twelve 12) (assign myvar (add twelve (subtract 6 2))) (log myvar)'
232
+const myInput = fs.readFileSync(process.argv[2], { encoding: 'utf-8' })
233
+const myTokens = tokenizer(myInput)
234
+const parsedTree = parser(myTokens)
235
+const transformedTree = transformer(parsedTree)
236
+const output = generator(transformedTree)
237
+fs.writeFileSync('output.js', output)

+ 25 - 0
stdlib.js

@@ -0,0 +1,25 @@
1
+const global_obj = {}
2
+module.exports = {
3
+  assign: function (ref, value) {
4
+    global_obj[ref.name] = value.value
5
+  },
6
+  add: function (arg1, arg2) {
7
+    return {
8
+      value: (arg1.value + arg2.value)
9
+    }
10
+  },
11
+  subtract: function (arg1, arg2) {
12
+    return {
13
+      value: (arg1.value - arg2.value)
14
+    }
15
+  },
16
+  log: function (ref) {
17
+    console.log(ref.value)
18
+  },
19
+  ref: function (refname) {
20
+    return {
21
+      name: refname,
22
+      value: global_obj[refname]
23
+    }
24
+  }
25
+}