A basic compiler based off of thejameskyle's super-tiny-compiler

compiler.js 5.1KB

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