var assert = require("assert"); var types = require("./types"); var NodePath = require("./node-path"); var Node = types.namedTypes.Node; var isArray = types.builtInTypes.array; var isObject = types.builtInTypes.object; var isFunction = types.builtInTypes.function; var hasOwn = Object.prototype.hasOwnProperty; var undefined; function PathVisitor() { assert.ok(this instanceof PathVisitor); this._reusableContextStack = []; this._methodNameTable = computeMethodNameTable(this); this.Context = makeContextConstructor(this); } function computeMethodNameTable(visitor) { var typeNames = Object.create(null); for (var methodName in visitor) { if (/^visit[A-Z]/.test(methodName)) { typeNames[methodName.slice("visit".length)] = true; } } var supertypeTable = types.computeSupertypeLookupTable(typeNames); var methodNameTable = Object.create(null); for (var typeName in supertypeTable) { if (hasOwn.call(supertypeTable, typeName)) { methodName = "visit" + supertypeTable[typeName]; if (isFunction.check(visitor[methodName])) { methodNameTable[typeName] = methodName; } } } return methodNameTable; } PathVisitor.fromMethodsObject = function fromMethodsObject(methods) { if (methods instanceof PathVisitor) { return methods; } if (!isObject.check(methods)) { // An empty visitor? return new PathVisitor; } function Visitor() { assert.ok(this instanceof Visitor); PathVisitor.call(this); } var Vp = Visitor.prototype = Object.create(PVp); Vp.constructor = Visitor; extend(Vp, methods); extend(Visitor, PathVisitor); isFunction.assert(Visitor.fromMethodsObject); isFunction.assert(Visitor.visit); return new Visitor; }; function extend(target, source) { for (var property in source) { if (hasOwn.call(source, property)) { target[property] = source[property]; } } return target; } PathVisitor.visit = function visit(node, methods) { var visitor = PathVisitor.fromMethodsObject(methods); if (node instanceof NodePath) { visitor.visit(node); return node.value; } var rootPath = new NodePath({ root: node }); visitor.visit(rootPath.get("root")); return rootPath.value.root; }; var PVp = PathVisitor.prototype; PVp.visit = function(path) { if (this instanceof this.Context) { // If we somehow end up calling context.visit, then we need to // re-invoke the .visit method against context.visitor. return this.visitor.visit(path); } assert.ok(path instanceof NodePath); var value = path.value; var methodName = Node.check(value) && this._methodNameTable[value.type]; if (methodName) { var context = this.acquireContext(path); try { context.invokeVisitorMethod(methodName); } finally { this.releaseContext(context); } } else { // If there was no visitor method to call, visit the children of // this node generically. visitChildren(path, this); } }; function visitChildren(path, visitor) { assert.ok(path instanceof NodePath); assert.ok(visitor instanceof PathVisitor); var value = path.value; if (isArray.check(value)) { path.each(visitor.visit, visitor); } else if (!isObject.check(value)) { // No children to visit. } else { var childNames = types.getFieldNames(value); var childCount = childNames.length; var childPaths = []; for (var i = 0; i < childCount; ++i) { var childName = childNames[i]; if (!hasOwn.call(value, childName)) { value[childName] = types.getFieldValue(value, childName); } childPaths.push(path.get(childName)); } for (var i = 0; i < childCount; ++i) { visitor.visit(childPaths[i]); } } } PVp.acquireContext = function(path) { if (this._reusableContextStack.length === 0) { return new this.Context(path); } return this._reusableContextStack.pop().reset(path); }; PVp.releaseContext = function(context) { assert.ok(context instanceof this.Context); this._reusableContextStack.push(context); context.currentPath = null; }; function makeContextConstructor(visitor) { function Context(path) { assert.ok(this instanceof Context); assert.ok(this instanceof PathVisitor); assert.ok(path instanceof NodePath); Object.defineProperty(this, "visitor", { value: visitor, writable: false, enumerable: true, configurable: false }); this.currentPath = path; this.needToCallTraverse = true; Object.seal(this); } assert.ok(visitor instanceof PathVisitor); // Note that the visitor object is the prototype of Context.prototype, // so all visitor methods are inherited by context objects. var Cp = Context.prototype = Object.create(visitor); Cp.constructor = Context; extend(Cp, sharedContextProtoMethods); return Context; } // Every PathVisitor has a different this.Context constructor and // this.Context.prototype object, but those prototypes can all use the // same reset, invokeVisitorMethod, and traverse function objects. var sharedContextProtoMethods = Object.create(null); sharedContextProtoMethods.reset = function reset(path) { assert.ok(this instanceof this.Context); assert.ok(path instanceof NodePath); this.currentPath = path; this.needToCallTraverse = true; return this; }; sharedContextProtoMethods.invokeVisitorMethod = function invokeVisitorMethod(methodName) { assert.ok(this instanceof this.Context); assert.ok(this.currentPath instanceof NodePath); var result = this.visitor[methodName].call(this, this.currentPath); if (result === false) { // Visitor methods return false to indicate that they have handled // their own traversal needs, and we should not complain if // this.needToCallTraverse is still true. this.needToCallTraverse = false; } else if (result !== undefined) { // Any other non-undefined value returned from the visitor method // is interpreted as a replacement value. this.currentPath = this.currentPath.replace(result)[0]; if (this.needToCallTraverse) { // If this.traverse still hasn't been called, visit the // children of the replacement node. this.traverse(this.currentPath); } } assert.strictEqual( this.needToCallTraverse, false, "Must either call this.traverse or return false in " + methodName ); }; sharedContextProtoMethods.traverse = function traverse(path, newVisitor) { assert.ok(this instanceof this.Context); assert.ok(path instanceof NodePath); assert.ok(this.currentPath instanceof NodePath); this.needToCallTraverse = false; visitChildren(path, PathVisitor.fromMethodsObject( newVisitor || this.visitor )); }; module.exports = PathVisitor;