mirror of
https://github.com/jamiebuilds/the-super-tiny-compiler.git
synced 2024-10-27 20:34:08 +00:00
Add transformer/code generator/compiler inline annotations
This commit is contained in:
parent
7fa8ab691f
commit
7d5c7592bb
@ -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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ============================================================================
|
||||
* (๑˃̵ᴗ˂̵)و
|
||||
|
Loading…
Reference in New Issue
Block a user