mirror of
https://github.com/jamiebuilds/the-super-tiny-compiler.git
synced 2024-10-27 20:34:08 +00:00
97 lines
3.1 KiB
JavaScript
97 lines
3.1 KiB
JavaScript
/**
|
|
* ============================================================================
|
|
* ⌒(❀>◞౪◟<❀)⌒
|
|
* THE TRAVERSER!!!
|
|
* ============================================================================
|
|
*/
|
|
|
|
/**
|
|
* So now we have our AST, and we want to be able to visit different nodes with
|
|
* a visitor. We need to be able to call the methods on the visitor whenever we
|
|
* encounter a node with a matching type.
|
|
*
|
|
* traverse(ast, {
|
|
* Program(node, parent) {
|
|
* // ...
|
|
* },
|
|
*
|
|
* CallExpression(node, parent) {
|
|
* // ...
|
|
* },
|
|
*
|
|
* NumberLiteral(node, parent) {
|
|
* // ...
|
|
* },
|
|
* });
|
|
*/
|
|
|
|
// So we define a traverser function which accepts an AST and a
|
|
// visitor. Inside we're going to define two functions...
|
|
function traverser(ast, visitor) {
|
|
|
|
// A `traverseArray` function that will allow us to iterate over an array and
|
|
// call the next function that we will define: `traverseNode`.
|
|
function traverseArray(array, parent) {
|
|
array.forEach(child => {
|
|
traverseNode(child, parent);
|
|
});
|
|
}
|
|
|
|
// `traverseNode` will accept a `node` and its `parent` node. So that it can
|
|
// pass both to our visitor methods.
|
|
function traverseNode(node, parent) {
|
|
|
|
// We start by testing for the existence of a method on the visitor with a
|
|
// matching `type`.
|
|
let methods = visitor[node.type];
|
|
|
|
// If there is an `enter` method for this node type we'll call it with the
|
|
// `node` and its `parent`.
|
|
if (methods && methods.enter) {
|
|
methods.enter(node, parent);
|
|
}
|
|
|
|
// Next we are going to split things up by the current node type.
|
|
switch (node.type) {
|
|
|
|
// We'll start with our top level `Program`. Since Program nodes have a
|
|
// property named body that has an array of nodes, we will call
|
|
// `traverseArray` to traverse down into them.
|
|
//
|
|
// (Remember that `traverseArray` will in turn call `traverseNode` so we
|
|
// are causing the tree to be traversed recursively)
|
|
case 'Program':
|
|
traverseArray(node.body, node);
|
|
break;
|
|
|
|
// Next we do the same with `CallExpression` and traverse their `params`.
|
|
case 'CallExpression':
|
|
traverseArray(node.params, node);
|
|
break;
|
|
|
|
// In the cases of `NumberLiteral` and `StringLiteral` we don't have any
|
|
// child nodes to visit, so we'll just break.
|
|
case 'NumberLiteral':
|
|
case 'StringLiteral':
|
|
break;
|
|
|
|
// And again, if we haven't recognized the node type then we'll throw an
|
|
// error.
|
|
default:
|
|
throw new SyntaxError(`Unrecognized node ${node.type}: ${JSON.stringify(node)}}`);
|
|
}
|
|
|
|
// If there is an `exit` method for this node type we'll call it with the
|
|
// `node` and its `parent`.
|
|
if (methods && methods.exit) {
|
|
methods.exit(node, parent);
|
|
}
|
|
}
|
|
|
|
// Finally we kickstart the traverser by calling `traverseNode` with our ast
|
|
// with no `parent` because the top level of the AST doesn't have a parent.
|
|
traverseNode(ast, null);
|
|
}
|
|
|
|
// Just exporting our traverser to be used in the final compiler...
|
|
module.exports = traverser; |