From 7d5c7592bb3242e685b8ee3f20ba443621396a78 Mon Sep 17 00:00:00 2001 From: James Kyle Date: Thu, 31 Mar 2016 02:13:54 -0700 Subject: [PATCH] Add transformer/code generator/compiler inline annotations --- super-tiny-compiler.js | 117 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/super-tiny-compiler.js b/super-tiny-compiler.js index c414c2c..95b7fd6 100644 --- a/super-tiny-compiler.js +++ b/super-tiny-compiler.js @@ -710,14 +710,6 @@ function traverser(ast, visitor) { traverseNode(ast, null); } -/** - * ---------------------------------------------------------------------------- - * *Note:* This is all I've written so far, so the code below isn't annnotated - * yet. You can still read it all and it totally works, but I plan on improving - * this in the near future - * ---------------------------------------------------------------------------- - */ - /** * ============================================================================ * ⁽(◍˃̵͈̑ᴗ˂̵͈̑)⁽ @@ -725,23 +717,84 @@ function traverser(ast, visitor) { * ============================================================================ */ +/** + * Next up, the transformer. Our transformer is going to take the AST that we + * have built and pass it to our traverser function with a visitor and will + * create a new ast. + * + * ---------------------------------------------------------------------------- + * Original AST | Transformed AST + * ---------------------------------------------------------------------------- + * { | { + * type: 'Program', | type: 'Program', + * body: [{ | body: [{ + * type: 'CallExpression', | type: 'ExpressionStatement', + * name: 'add', | expression: { + * params: [{ | type: 'CallExpression', + * type: 'NumberLiteral', | callee: { + * value: '2' | type: 'Identifier', + * }, { | name: 'add' + * type: 'CallExpression', | }, + * name: 'subtract', | arguments: [{ + * params: [{ | type: 'NumberLiteral', + * type: 'NumberLiteral', | value: '2' + * value: '4' | }, { + * }, { | type: 'CallExpression', + * type: 'NumberLiteral', | callee: { + * value: '2' | type: 'Identifier', + * }] | name: 'subtract' + * }] | }, + * }] | arguments: [{ + * } | type: 'NumberLiteral', + * | value: '4' + * ---------------------------------- | }, { + * | type: 'NumberLiteral', + * | value: '2' + * | }] + * (sorry the other one is longer.) | } + * | } + * | }] + * | } + * ---------------------------------------------------------------------------- + */ + +// So we have our transformer function which will accept the lisp ast. function transformer(ast) { + + // We'll create a `newAst` which like our previous AST will have a program + // node. var newAst = { type: 'Program', body: [] }; + // Next I'm going to cheat a little and create a bit of a hack. We're going to + // use a property named `context` on our parent nodes that we're going to push + // nodes to their parent's `context`. Normally you would have a better + // abstraction than this, but for our purposes this keeps things simple. + // + // Just take note that the context is a reference *from* the old ast *to* the + // new ast. ast._context = newAst.body; + // We'll start by calling the traverser function with our ast and a visitor. traverser(ast, { + + // The first visitor method accepts `NumberLiterals` NumberLiteral: function(node, parent) { + // We'll create a new node also named `NumberLiteral` that we will push to + // the parent context. parent._context.push({ type: 'NumberLiteral', value: node.value }); }, + // Next up, `CallExpressions`. CallExpression: function(node, parent) { + + // We start creating a new node `CallExpression` with a nested + // `Identifier`. var expression = { type: 'CallExpression', callee: { @@ -751,19 +804,32 @@ function transformer(ast) { arguments: [] }; + // Next we're going to define a new context on the original + // `CallExpression` node that will reference the `expression`'s arguments + // so that we can push arguments. node._context = expression.arguments; + // Then we're going to check if the parent node is a `CallExpression`. + // If it is not... if (parent.type !== 'CallExpression') { + + // We're going to wrap our `CallExpression` node with an + // `ExpressionStatement`. We do this because the top level + // `CallExpressions` in JavaScript are actually statements. expression = { type: 'ExpressionStatement', expression: expression }; } + // Last, we push our (possibly wrapped) `CallExpression` to the `parent`'s + // `context`. parent._context.push(expression); } }); + // At the end of our transformer function we'll return the new ast that we + // just created. return newAst; } @@ -774,18 +840,36 @@ function transformer(ast) { * ============================================================================ */ +/** + * Now let's move onto our last phase: The Code Generator. + * + * Our code generator is going to recursively call itself to print each node in + * the tree into one giant string. + */ + function codeGenerator(node) { + + // We'll break things down by the `type` of the `node`. switch (node.type) { + + // If we have a `Program` node. We will map through each node in the `body` + // and run them through the code generator and join them with a newline. case 'Program': return node.body.map(codeGenerator) .join('\n'); + // For `ExpressionStatements` we'll call the code generator on the nested + // expression and we'll add a semicolon... case 'ExpressionStatement': return ( codeGenerator(node.expression) + - ';' + ';' // << (...because we like to code the *correct* way) ); + // For `CallExpressions` we will print the `callee`, add an open + // parenthesis, we'll map through each node in the `arguments` array and run + // them through the code generator, joining them with a comma, and then + // we'll add a closing parenthesis. case 'CallExpression': return ( codeGenerator(node.callee) + @@ -795,12 +879,15 @@ function codeGenerator(node) { ')' ); + // For `Identifiers` we'll just return the `node`'s name. case 'Identifier': return node.name; + // For `NumberLiterals` we'll just return the `node`'s value. case 'NumberLiteral': return node.value; + // And if we haven't recognized the node, we'll throw an error. default: throw new TypeError(node.type); } @@ -813,16 +900,26 @@ function codeGenerator(node) { * ============================================================================ */ +/** + * FINALLY! We'll create our `compiler` function. Here we will link together + * every part of the pipeline. + * + * 1. input => tokenizer => tokens + * 2. tokens => parser => ast + * 3. ast => transformer => newAst + * 4. newAst => generator => output + */ + function compiler(input) { var tokens = tokenizer(input); var ast = parser(tokens); var newAst = transformer(ast); var output = codeGenerator(newAst); + // and simply return the output! return output; } - /** * ============================================================================ * (๑˃̵ᴗ˂̵)و