Add transformer/code generator/compiler inline annotations

This commit is contained in:
James Kyle 2016-03-31 02:13:54 -07:00
parent 7fa8ab691f
commit 7d5c7592bb

View File

@ -710,14 +710,6 @@ function traverser(ast, visitor) {
traverseNode(ast, null); 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) { function transformer(ast) {
// We'll create a `newAst` which like our previous AST will have a program
// node.
var newAst = { var newAst = {
type: 'Program', type: 'Program',
body: [] 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; ast._context = newAst.body;
// We'll start by calling the traverser function with our ast and a visitor.
traverser(ast, { traverser(ast, {
// The first visitor method accepts `NumberLiterals`
NumberLiteral: function(node, parent) { NumberLiteral: function(node, parent) {
// We'll create a new node also named `NumberLiteral` that we will push to
// the parent context.
parent._context.push({ parent._context.push({
type: 'NumberLiteral', type: 'NumberLiteral',
value: node.value value: node.value
}); });
}, },
// Next up, `CallExpressions`.
CallExpression: function(node, parent) { CallExpression: function(node, parent) {
// We start creating a new node `CallExpression` with a nested
// `Identifier`.
var expression = { var expression = {
type: 'CallExpression', type: 'CallExpression',
callee: { callee: {
@ -751,19 +804,32 @@ function transformer(ast) {
arguments: [] 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; 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') { 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 = { expression = {
type: 'ExpressionStatement', type: 'ExpressionStatement',
expression: expression expression: expression
}; };
} }
// Last, we push our (possibly wrapped) `CallExpression` to the `parent`'s
// `context`.
parent._context.push(expression); parent._context.push(expression);
} }
}); });
// At the end of our transformer function we'll return the new ast that we
// just created.
return newAst; 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) { function codeGenerator(node) {
// We'll break things down by the `type` of the `node`.
switch (node.type) { 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': case 'Program':
return node.body.map(codeGenerator) return node.body.map(codeGenerator)
.join('\n'); .join('\n');
// For `ExpressionStatements` we'll call the code generator on the nested
// expression and we'll add a semicolon...
case 'ExpressionStatement': case 'ExpressionStatement':
return ( return (
codeGenerator(node.expression) + 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': case 'CallExpression':
return ( return (
codeGenerator(node.callee) + codeGenerator(node.callee) +
@ -795,12 +879,15 @@ function codeGenerator(node) {
')' ')'
); );
// For `Identifiers` we'll just return the `node`'s name.
case 'Identifier': case 'Identifier':
return node.name; return node.name;
// For `NumberLiterals` we'll just return the `node`'s value.
case 'NumberLiteral': case 'NumberLiteral':
return node.value; return node.value;
// And if we haven't recognized the node, we'll throw an error.
default: default:
throw new TypeError(node.type); 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) { function compiler(input) {
var tokens = tokenizer(input); var tokens = tokenizer(input);
var ast = parser(tokens); var ast = parser(tokens);
var newAst = transformer(ast); var newAst = transformer(ast);
var output = codeGenerator(newAst); var output = codeGenerator(newAst);
// and simply return the output!
return output; return output;
} }
/** /**
* ============================================================================ * ============================================================================
* (˃̵˂̵)و * (˃̵˂̵)و