var traverser = require('./3-traverser'); /** * ============================================================================ * ⁽(◍˃̵͈̑ᴗ˂̵͈̑)⁽ * THE TRANSFORMER!!! * ============================================================================ */ /** * 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. let 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 any `NumberLiteral` NumberLiteral: { // We'll visit them on enter. enter(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 we have `StringLiteral` StringLiteral: { enter(node, parent) { parent._context.push({ type: 'StringLiteral', value: node.value, }); }, }, // Next up, `CallExpression`. CallExpression: { enter(node, parent) { // We start creating a new node `CallExpression` with a nested // `Identifier`. let expression = { type: 'CallExpression', callee: { type: 'Identifier', name: node.name, }, 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 // `CallExpression` 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; } // Just exporting our transformer to be used in the final compiler... module.exports = transformer;