130 lines
3.4 KiB
JavaScript
130 lines
3.4 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
NODE_TYPE_IDS_MAP,
|
|
NODE_TYPES_COUNT,
|
|
LEAF_NODE_TYPES_COUNT,
|
|
} = require('../generated/lazy/types.js');
|
|
|
|
// Getter for private `#visitorsArr` property of `Visitor` class. Initialized in class body below.
|
|
let getVisitorsArr;
|
|
|
|
/**
|
|
* Visitor class, used to visit an AST.
|
|
*/
|
|
class Visitor {
|
|
#visitorsArr;
|
|
|
|
/**
|
|
* Create `Visitor`.
|
|
*
|
|
* Provide an object where keys are names of AST nodes you want to visit,
|
|
* and values are visitor functions which receive AST node objects of that type.
|
|
*
|
|
* Keys can also be postfixed with `:exit` to visit when exiting the node, rather than entering.
|
|
*
|
|
* ```js
|
|
* const visitor = new Visitor({
|
|
* BinaryExpression(binExpr) {
|
|
* // Do stuff when entering a `BinaryExpression`
|
|
* },
|
|
* 'BinaryExpression:exit'(binExpr) {
|
|
* // Do stuff when exiting a `BinaryExpression`
|
|
* },
|
|
* });
|
|
* ```
|
|
*
|
|
* @class
|
|
* @param {Object} visitor - Object defining visit functions for AST nodes
|
|
* @returns {Visitor}
|
|
*/
|
|
constructor(visitor) {
|
|
this.#visitorsArr = createVisitorsArr(visitor);
|
|
}
|
|
|
|
static {
|
|
getVisitorsArr = visitor => visitor.#visitorsArr;
|
|
}
|
|
}
|
|
|
|
module.exports = { Visitor, getVisitorsArr };
|
|
|
|
/**
|
|
* Create array of visitors, keyed by node type ID.
|
|
*
|
|
* Each element of array is one of:
|
|
*
|
|
* * No visitor for this type = `null`.
|
|
* * Visitor for leaf node = visit function.
|
|
* * Visitor for non-leaf node = object of form `{ enter, exit }`,
|
|
* where each property is either a visitor function or `null`.
|
|
*
|
|
* @param {Object} visitor - Visitors object from user
|
|
* @returns {Array<Object|Function|null>} - Array of visitors
|
|
*/
|
|
function createVisitorsArr(visitor) {
|
|
if (visitor === null || typeof visitor !== 'object') {
|
|
throw new Error('`visitor` must be an object');
|
|
}
|
|
|
|
// Create empty visitors array
|
|
const visitorsArr = [];
|
|
for (let i = NODE_TYPES_COUNT; i !== 0; i--) {
|
|
visitorsArr.push(null);
|
|
}
|
|
|
|
// Populate visitors array from provided object
|
|
for (let name of Object.keys(visitor)) {
|
|
const visitFn = visitor[name];
|
|
if (typeof visitFn !== 'function') {
|
|
throw new Error(`'${name}' property of \`visitor\` object is not a function`);
|
|
}
|
|
|
|
const isExit = name.endsWith(':exit');
|
|
if (isExit) name = name.slice(0, -5);
|
|
|
|
const typeId = NODE_TYPE_IDS_MAP.get(name);
|
|
if (typeId === void 0) throw new Error(`Unknown node type '${name}' in \`visitor\` object`);
|
|
|
|
if (typeId < LEAF_NODE_TYPES_COUNT) {
|
|
// Leaf node. Store just 1 function.
|
|
const existingVisitFn = visitorsArr[typeId];
|
|
if (existingVisitFn === null) {
|
|
visitorsArr[typeId] = visitFn;
|
|
} else if (isExit) {
|
|
visitorsArr[typeId] = combineVisitFunctions(existingVisitFn, visitFn);
|
|
} else {
|
|
visitorsArr[typeId] = combineVisitFunctions(visitFn, existingVisitFn);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
let enterExit = visitorsArr[typeId];
|
|
if (enterExit === null) {
|
|
enterExit = visitorsArr[typeId] = { enter: null, exit: null };
|
|
}
|
|
|
|
if (isExit) {
|
|
enterExit.exit = visitFn;
|
|
} else {
|
|
enterExit.enter = visitFn;
|
|
}
|
|
}
|
|
|
|
return visitorsArr;
|
|
}
|
|
|
|
/**
|
|
* Combine 2 visitor functions into 1.
|
|
*
|
|
* @param {function} visit1 - 1st visitor function
|
|
* @param {function} visit2 - 2nd visitor function
|
|
* @returns {function} - Combined visitor function
|
|
*/
|
|
function combineVisitFunctions(visit1, visit2) {
|
|
return function(node) {
|
|
visit1(node);
|
|
visit2(node);
|
|
};
|
|
}
|