850 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			850 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /* | ||
|  |   Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com> | ||
|  |   Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> | ||
|  | 
 | ||
|  |   Redistribution and use in source and binary forms, with or without | ||
|  |   modification, are permitted provided that the following conditions are met: | ||
|  | 
 | ||
|  |     * Redistributions of source code must retain the above copyright | ||
|  |       notice, this list of conditions and the following disclaimer. | ||
|  |     * Redistributions in binary form must reproduce the above copyright | ||
|  |       notice, this list of conditions and the following disclaimer in the | ||
|  |       documentation and/or other materials provided with the distribution. | ||
|  | 
 | ||
|  |   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
|  |   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
|  |   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
|  |   ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | ||
|  |   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
|  |   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
|  |   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
|  |   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
|  |   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
|  |   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
|  | */ | ||
|  | /*jslint vars:false, bitwise:true*/ | ||
|  | /*jshint indent:4*/ | ||
|  | /*global exports:true*/ | ||
|  | (function clone(exports) { | ||
|  |     'use strict'; | ||
|  | 
 | ||
|  |     var Syntax, | ||
|  |         isArray, | ||
|  |         VisitorOption, | ||
|  |         VisitorKeys, | ||
|  |         objectCreate, | ||
|  |         objectKeys, | ||
|  |         BREAK, | ||
|  |         SKIP, | ||
|  |         REMOVE; | ||
|  | 
 | ||
|  |     function ignoreJSHintError() { } | ||
|  | 
 | ||
|  |     isArray = Array.isArray; | ||
|  |     if (!isArray) { | ||
|  |         isArray = function isArray(array) { | ||
|  |             return Object.prototype.toString.call(array) === '[object Array]'; | ||
|  |         }; | ||
|  |     } | ||
|  | 
 | ||
|  |     function deepCopy(obj) { | ||
|  |         var ret = {}, key, val; | ||
|  |         for (key in obj) { | ||
|  |             if (obj.hasOwnProperty(key)) { | ||
|  |                 val = obj[key]; | ||
|  |                 if (typeof val === 'object' && val !== null) { | ||
|  |                     ret[key] = deepCopy(val); | ||
|  |                 } else { | ||
|  |                     ret[key] = val; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         return ret; | ||
|  |     } | ||
|  | 
 | ||
|  |     function shallowCopy(obj) { | ||
|  |         var ret = {}, key; | ||
|  |         for (key in obj) { | ||
|  |             if (obj.hasOwnProperty(key)) { | ||
|  |                 ret[key] = obj[key]; | ||
|  |             } | ||
|  |         } | ||
|  |         return ret; | ||
|  |     } | ||
|  |     ignoreJSHintError(shallowCopy); | ||
|  | 
 | ||
|  |     // based on LLVM libc++ upper_bound / lower_bound
 | ||
|  |     // MIT License
 | ||
|  | 
 | ||
|  |     function upperBound(array, func) { | ||
|  |         var diff, len, i, current; | ||
|  | 
 | ||
|  |         len = array.length; | ||
|  |         i = 0; | ||
|  | 
 | ||
|  |         while (len) { | ||
|  |             diff = len >>> 1; | ||
|  |             current = i + diff; | ||
|  |             if (func(array[current])) { | ||
|  |                 len = diff; | ||
|  |             } else { | ||
|  |                 i = current + 1; | ||
|  |                 len -= diff + 1; | ||
|  |             } | ||
|  |         } | ||
|  |         return i; | ||
|  |     } | ||
|  | 
 | ||
|  |     function lowerBound(array, func) { | ||
|  |         var diff, len, i, current; | ||
|  | 
 | ||
|  |         len = array.length; | ||
|  |         i = 0; | ||
|  | 
 | ||
|  |         while (len) { | ||
|  |             diff = len >>> 1; | ||
|  |             current = i + diff; | ||
|  |             if (func(array[current])) { | ||
|  |                 i = current + 1; | ||
|  |                 len -= diff + 1; | ||
|  |             } else { | ||
|  |                 len = diff; | ||
|  |             } | ||
|  |         } | ||
|  |         return i; | ||
|  |     } | ||
|  |     ignoreJSHintError(lowerBound); | ||
|  | 
 | ||
|  |     objectCreate = Object.create || (function () { | ||
|  |         function F() { } | ||
|  | 
 | ||
|  |         return function (o) { | ||
|  |             F.prototype = o; | ||
|  |             return new F(); | ||
|  |         }; | ||
|  |     })(); | ||
|  | 
 | ||
|  |     objectKeys = Object.keys || function (o) { | ||
|  |         var keys = [], key; | ||
|  |         for (key in o) { | ||
|  |             keys.push(key); | ||
|  |         } | ||
|  |         return keys; | ||
|  |     }; | ||
|  | 
 | ||
|  |     function extend(to, from) { | ||
|  |         var keys = objectKeys(from), key, i, len; | ||
|  |         for (i = 0, len = keys.length; i < len; i += 1) { | ||
|  |             key = keys[i]; | ||
|  |             to[key] = from[key]; | ||
|  |         } | ||
|  |         return to; | ||
|  |     } | ||
|  | 
 | ||
|  |     Syntax = { | ||
|  |         AssignmentExpression: 'AssignmentExpression', | ||
|  |         AssignmentPattern: 'AssignmentPattern', | ||
|  |         ArrayExpression: 'ArrayExpression', | ||
|  |         ArrayPattern: 'ArrayPattern', | ||
|  |         ArrowFunctionExpression: 'ArrowFunctionExpression', | ||
|  |         AwaitExpression: 'AwaitExpression', // CAUTION: It's deferred to ES7.
 | ||
|  |         BlockStatement: 'BlockStatement', | ||
|  |         BinaryExpression: 'BinaryExpression', | ||
|  |         BreakStatement: 'BreakStatement', | ||
|  |         CallExpression: 'CallExpression', | ||
|  |         CatchClause: 'CatchClause', | ||
|  |         ClassBody: 'ClassBody', | ||
|  |         ClassDeclaration: 'ClassDeclaration', | ||
|  |         ClassExpression: 'ClassExpression', | ||
|  |         ComprehensionBlock: 'ComprehensionBlock',  // CAUTION: It's deferred to ES7.
 | ||
|  |         ComprehensionExpression: 'ComprehensionExpression',  // CAUTION: It's deferred to ES7.
 | ||
|  |         ConditionalExpression: 'ConditionalExpression', | ||
|  |         ContinueStatement: 'ContinueStatement', | ||
|  |         DebuggerStatement: 'DebuggerStatement', | ||
|  |         DirectiveStatement: 'DirectiveStatement', | ||
|  |         DoWhileStatement: 'DoWhileStatement', | ||
|  |         EmptyStatement: 'EmptyStatement', | ||
|  |         ExportAllDeclaration: 'ExportAllDeclaration', | ||
|  |         ExportDefaultDeclaration: 'ExportDefaultDeclaration', | ||
|  |         ExportNamedDeclaration: 'ExportNamedDeclaration', | ||
|  |         ExportSpecifier: 'ExportSpecifier', | ||
|  |         ExpressionStatement: 'ExpressionStatement', | ||
|  |         ForStatement: 'ForStatement', | ||
|  |         ForInStatement: 'ForInStatement', | ||
|  |         ForOfStatement: 'ForOfStatement', | ||
|  |         FunctionDeclaration: 'FunctionDeclaration', | ||
|  |         FunctionExpression: 'FunctionExpression', | ||
|  |         GeneratorExpression: 'GeneratorExpression',  // CAUTION: It's deferred to ES7.
 | ||
|  |         Identifier: 'Identifier', | ||
|  |         IfStatement: 'IfStatement', | ||
|  |         ImportDeclaration: 'ImportDeclaration', | ||
|  |         ImportDefaultSpecifier: 'ImportDefaultSpecifier', | ||
|  |         ImportNamespaceSpecifier: 'ImportNamespaceSpecifier', | ||
|  |         ImportSpecifier: 'ImportSpecifier', | ||
|  |         Literal: 'Literal', | ||
|  |         LabeledStatement: 'LabeledStatement', | ||
|  |         LogicalExpression: 'LogicalExpression', | ||
|  |         MemberExpression: 'MemberExpression', | ||
|  |         MetaProperty: 'MetaProperty', | ||
|  |         MethodDefinition: 'MethodDefinition', | ||
|  |         ModuleSpecifier: 'ModuleSpecifier', | ||
|  |         NewExpression: 'NewExpression', | ||
|  |         ObjectExpression: 'ObjectExpression', | ||
|  |         ObjectPattern: 'ObjectPattern', | ||
|  |         Program: 'Program', | ||
|  |         Property: 'Property', | ||
|  |         RestElement: 'RestElement', | ||
|  |         ReturnStatement: 'ReturnStatement', | ||
|  |         SequenceExpression: 'SequenceExpression', | ||
|  |         SpreadElement: 'SpreadElement', | ||
|  |         Super: 'Super', | ||
|  |         SwitchStatement: 'SwitchStatement', | ||
|  |         SwitchCase: 'SwitchCase', | ||
|  |         TaggedTemplateExpression: 'TaggedTemplateExpression', | ||
|  |         TemplateElement: 'TemplateElement', | ||
|  |         TemplateLiteral: 'TemplateLiteral', | ||
|  |         ThisExpression: 'ThisExpression', | ||
|  |         ThrowStatement: 'ThrowStatement', | ||
|  |         TryStatement: 'TryStatement', | ||
|  |         UnaryExpression: 'UnaryExpression', | ||
|  |         UpdateExpression: 'UpdateExpression', | ||
|  |         VariableDeclaration: 'VariableDeclaration', | ||
|  |         VariableDeclarator: 'VariableDeclarator', | ||
|  |         WhileStatement: 'WhileStatement', | ||
|  |         WithStatement: 'WithStatement', | ||
|  |         YieldExpression: 'YieldExpression' | ||
|  |     }; | ||
|  | 
 | ||
|  |     VisitorKeys = { | ||
|  |         AssignmentExpression: ['left', 'right'], | ||
|  |         AssignmentPattern: ['left', 'right'], | ||
|  |         ArrayExpression: ['elements'], | ||
|  |         ArrayPattern: ['elements'], | ||
|  |         ArrowFunctionExpression: ['params', 'body'], | ||
|  |         AwaitExpression: ['argument'], // CAUTION: It's deferred to ES7.
 | ||
|  |         BlockStatement: ['body'], | ||
|  |         BinaryExpression: ['left', 'right'], | ||
|  |         BreakStatement: ['label'], | ||
|  |         CallExpression: ['callee', 'arguments'], | ||
|  |         CatchClause: ['param', 'body'], | ||
|  |         ClassBody: ['body'], | ||
|  |         ClassDeclaration: ['id', 'superClass', 'body'], | ||
|  |         ClassExpression: ['id', 'superClass', 'body'], | ||
|  |         ComprehensionBlock: ['left', 'right'],  // CAUTION: It's deferred to ES7.
 | ||
|  |         ComprehensionExpression: ['blocks', 'filter', 'body'],  // CAUTION: It's deferred to ES7.
 | ||
|  |         ConditionalExpression: ['test', 'consequent', 'alternate'], | ||
|  |         ContinueStatement: ['label'], | ||
|  |         DebuggerStatement: [], | ||
|  |         DirectiveStatement: [], | ||
|  |         DoWhileStatement: ['body', 'test'], | ||
|  |         EmptyStatement: [], | ||
|  |         ExportAllDeclaration: ['source'], | ||
|  |         ExportDefaultDeclaration: ['declaration'], | ||
|  |         ExportNamedDeclaration: ['declaration', 'specifiers', 'source'], | ||
|  |         ExportSpecifier: ['exported', 'local'], | ||
|  |         ExpressionStatement: ['expression'], | ||
|  |         ForStatement: ['init', 'test', 'update', 'body'], | ||
|  |         ForInStatement: ['left', 'right', 'body'], | ||
|  |         ForOfStatement: ['left', 'right', 'body'], | ||
|  |         FunctionDeclaration: ['id', 'params', 'body'], | ||
|  |         FunctionExpression: ['id', 'params', 'body'], | ||
|  |         GeneratorExpression: ['blocks', 'filter', 'body'],  // CAUTION: It's deferred to ES7.
 | ||
|  |         Identifier: [], | ||
|  |         IfStatement: ['test', 'consequent', 'alternate'], | ||
|  |         ImportDeclaration: ['specifiers', 'source'], | ||
|  |         ImportDefaultSpecifier: ['local'], | ||
|  |         ImportNamespaceSpecifier: ['local'], | ||
|  |         ImportSpecifier: ['imported', 'local'], | ||
|  |         Literal: [], | ||
|  |         LabeledStatement: ['label', 'body'], | ||
|  |         LogicalExpression: ['left', 'right'], | ||
|  |         MemberExpression: ['object', 'property'], | ||
|  |         MetaProperty: ['meta', 'property'], | ||
|  |         MethodDefinition: ['key', 'value'], | ||
|  |         ModuleSpecifier: [], | ||
|  |         NewExpression: ['callee', 'arguments'], | ||
|  |         ObjectExpression: ['properties'], | ||
|  |         ObjectPattern: ['properties'], | ||
|  |         Program: ['body'], | ||
|  |         Property: ['key', 'value'], | ||
|  |         RestElement: [ 'argument' ], | ||
|  |         ReturnStatement: ['argument'], | ||
|  |         SequenceExpression: ['expressions'], | ||
|  |         SpreadElement: ['argument'], | ||
|  |         Super: [], | ||
|  |         SwitchStatement: ['discriminant', 'cases'], | ||
|  |         SwitchCase: ['test', 'consequent'], | ||
|  |         TaggedTemplateExpression: ['tag', 'quasi'], | ||
|  |         TemplateElement: [], | ||
|  |         TemplateLiteral: ['quasis', 'expressions'], | ||
|  |         ThisExpression: [], | ||
|  |         ThrowStatement: ['argument'], | ||
|  |         TryStatement: ['block', 'handler', 'finalizer'], | ||
|  |         UnaryExpression: ['argument'], | ||
|  |         UpdateExpression: ['argument'], | ||
|  |         VariableDeclaration: ['declarations'], | ||
|  |         VariableDeclarator: ['id', 'init'], | ||
|  |         WhileStatement: ['test', 'body'], | ||
|  |         WithStatement: ['object', 'body'], | ||
|  |         YieldExpression: ['argument'] | ||
|  |     }; | ||
|  | 
 | ||
|  |     // unique id
 | ||
|  |     BREAK = {}; | ||
|  |     SKIP = {}; | ||
|  |     REMOVE = {}; | ||
|  | 
 | ||
|  |     VisitorOption = { | ||
|  |         Break: BREAK, | ||
|  |         Skip: SKIP, | ||
|  |         Remove: REMOVE | ||
|  |     }; | ||
|  | 
 | ||
|  |     function Reference(parent, key) { | ||
|  |         this.parent = parent; | ||
|  |         this.key = key; | ||
|  |     } | ||
|  | 
 | ||
|  |     Reference.prototype.replace = function replace(node) { | ||
|  |         this.parent[this.key] = node; | ||
|  |     }; | ||
|  | 
 | ||
|  |     Reference.prototype.remove = function remove() { | ||
|  |         if (isArray(this.parent)) { | ||
|  |             this.parent.splice(this.key, 1); | ||
|  |             return true; | ||
|  |         } else { | ||
|  |             this.replace(null); | ||
|  |             return false; | ||
|  |         } | ||
|  |     }; | ||
|  | 
 | ||
|  |     function Element(node, path, wrap, ref) { | ||
|  |         this.node = node; | ||
|  |         this.path = path; | ||
|  |         this.wrap = wrap; | ||
|  |         this.ref = ref; | ||
|  |     } | ||
|  | 
 | ||
|  |     function Controller() { } | ||
|  | 
 | ||
|  |     // API:
 | ||
|  |     // return property path array from root to current node
 | ||
|  |     Controller.prototype.path = function path() { | ||
|  |         var i, iz, j, jz, result, element; | ||
|  | 
 | ||
|  |         function addToPath(result, path) { | ||
|  |             if (isArray(path)) { | ||
|  |                 for (j = 0, jz = path.length; j < jz; ++j) { | ||
|  |                     result.push(path[j]); | ||
|  |                 } | ||
|  |             } else { | ||
|  |                 result.push(path); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         // root node
 | ||
|  |         if (!this.__current.path) { | ||
|  |             return null; | ||
|  |         } | ||
|  | 
 | ||
|  |         // first node is sentinel, second node is root element
 | ||
|  |         result = []; | ||
|  |         for (i = 2, iz = this.__leavelist.length; i < iz; ++i) { | ||
|  |             element = this.__leavelist[i]; | ||
|  |             addToPath(result, element.path); | ||
|  |         } | ||
|  |         addToPath(result, this.__current.path); | ||
|  |         return result; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // API:
 | ||
|  |     // return type of current node
 | ||
|  |     Controller.prototype.type = function () { | ||
|  |         var node = this.current(); | ||
|  |         return node.type || this.__current.wrap; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // API:
 | ||
|  |     // return array of parent elements
 | ||
|  |     Controller.prototype.parents = function parents() { | ||
|  |         var i, iz, result; | ||
|  | 
 | ||
|  |         // first node is sentinel
 | ||
|  |         result = []; | ||
|  |         for (i = 1, iz = this.__leavelist.length; i < iz; ++i) { | ||
|  |             result.push(this.__leavelist[i].node); | ||
|  |         } | ||
|  | 
 | ||
|  |         return result; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // API:
 | ||
|  |     // return current node
 | ||
|  |     Controller.prototype.current = function current() { | ||
|  |         return this.__current.node; | ||
|  |     }; | ||
|  | 
 | ||
|  |     Controller.prototype.__execute = function __execute(callback, element) { | ||
|  |         var previous, result; | ||
|  | 
 | ||
|  |         result = undefined; | ||
|  | 
 | ||
|  |         previous  = this.__current; | ||
|  |         this.__current = element; | ||
|  |         this.__state = null; | ||
|  |         if (callback) { | ||
|  |             result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node); | ||
|  |         } | ||
|  |         this.__current = previous; | ||
|  | 
 | ||
|  |         return result; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // API:
 | ||
|  |     // notify control skip / break
 | ||
|  |     Controller.prototype.notify = function notify(flag) { | ||
|  |         this.__state = flag; | ||
|  |     }; | ||
|  | 
 | ||
|  |     // API:
 | ||
|  |     // skip child nodes of current node
 | ||
|  |     Controller.prototype.skip = function () { | ||
|  |         this.notify(SKIP); | ||
|  |     }; | ||
|  | 
 | ||
|  |     // API:
 | ||
|  |     // break traversals
 | ||
|  |     Controller.prototype['break'] = function () { | ||
|  |         this.notify(BREAK); | ||
|  |     }; | ||
|  | 
 | ||
|  |     // API:
 | ||
|  |     // remove node
 | ||
|  |     Controller.prototype.remove = function () { | ||
|  |         this.notify(REMOVE); | ||
|  |     }; | ||
|  | 
 | ||
|  |     Controller.prototype.__initialize = function(root, visitor) { | ||
|  |         this.visitor = visitor; | ||
|  |         this.root = root; | ||
|  |         this.__worklist = []; | ||
|  |         this.__leavelist = []; | ||
|  |         this.__current = null; | ||
|  |         this.__state = null; | ||
|  |         this.__fallback = null; | ||
|  |         if (visitor.fallback === 'iteration') { | ||
|  |             this.__fallback = objectKeys; | ||
|  |         } else if (typeof visitor.fallback === 'function') { | ||
|  |             this.__fallback = visitor.fallback; | ||
|  |         } | ||
|  | 
 | ||
|  |         this.__keys = VisitorKeys; | ||
|  |         if (visitor.keys) { | ||
|  |             this.__keys = extend(objectCreate(this.__keys), visitor.keys); | ||
|  |         } | ||
|  |     }; | ||
|  | 
 | ||
|  |     function isNode(node) { | ||
|  |         if (node == null) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         return typeof node === 'object' && typeof node.type === 'string'; | ||
|  |     } | ||
|  | 
 | ||
|  |     function isProperty(nodeType, key) { | ||
|  |         return (nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === key; | ||
|  |     } | ||
|  | 
 | ||
|  |     Controller.prototype.traverse = function traverse(root, visitor) { | ||
|  |         var worklist, | ||
|  |             leavelist, | ||
|  |             element, | ||
|  |             node, | ||
|  |             nodeType, | ||
|  |             ret, | ||
|  |             key, | ||
|  |             current, | ||
|  |             current2, | ||
|  |             candidates, | ||
|  |             candidate, | ||
|  |             sentinel; | ||
|  | 
 | ||
|  |         this.__initialize(root, visitor); | ||
|  | 
 | ||
|  |         sentinel = {}; | ||
|  | 
 | ||
|  |         // reference
 | ||
|  |         worklist = this.__worklist; | ||
|  |         leavelist = this.__leavelist; | ||
|  | 
 | ||
|  |         // initialize
 | ||
|  |         worklist.push(new Element(root, null, null, null)); | ||
|  |         leavelist.push(new Element(null, null, null, null)); | ||
|  | 
 | ||
|  |         while (worklist.length) { | ||
|  |             element = worklist.pop(); | ||
|  | 
 | ||
|  |             if (element === sentinel) { | ||
|  |                 element = leavelist.pop(); | ||
|  | 
 | ||
|  |                 ret = this.__execute(visitor.leave, element); | ||
|  | 
 | ||
|  |                 if (this.__state === BREAK || ret === BREAK) { | ||
|  |                     return; | ||
|  |                 } | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (element.node) { | ||
|  | 
 | ||
|  |                 ret = this.__execute(visitor.enter, element); | ||
|  | 
 | ||
|  |                 if (this.__state === BREAK || ret === BREAK) { | ||
|  |                     return; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 worklist.push(sentinel); | ||
|  |                 leavelist.push(element); | ||
|  | 
 | ||
|  |                 if (this.__state === SKIP || ret === SKIP) { | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 node = element.node; | ||
|  |                 nodeType = node.type || element.wrap; | ||
|  |                 candidates = this.__keys[nodeType]; | ||
|  |                 if (!candidates) { | ||
|  |                     if (this.__fallback) { | ||
|  |                         candidates = this.__fallback(node); | ||
|  |                     } else { | ||
|  |                         throw new Error('Unknown node type ' + nodeType + '.'); | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  |                 current = candidates.length; | ||
|  |                 while ((current -= 1) >= 0) { | ||
|  |                     key = candidates[current]; | ||
|  |                     candidate = node[key]; | ||
|  |                     if (!candidate) { | ||
|  |                         continue; | ||
|  |                     } | ||
|  | 
 | ||
|  |                     if (isArray(candidate)) { | ||
|  |                         current2 = candidate.length; | ||
|  |                         while ((current2 -= 1) >= 0) { | ||
|  |                             if (!candidate[current2]) { | ||
|  |                                 continue; | ||
|  |                             } | ||
|  |                             if (isProperty(nodeType, candidates[current])) { | ||
|  |                                 element = new Element(candidate[current2], [key, current2], 'Property', null); | ||
|  |                             } else if (isNode(candidate[current2])) { | ||
|  |                                 element = new Element(candidate[current2], [key, current2], null, null); | ||
|  |                             } else { | ||
|  |                                 continue; | ||
|  |                             } | ||
|  |                             worklist.push(element); | ||
|  |                         } | ||
|  |                     } else if (isNode(candidate)) { | ||
|  |                         worklist.push(new Element(candidate, key, null, null)); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     }; | ||
|  | 
 | ||
|  |     Controller.prototype.replace = function replace(root, visitor) { | ||
|  |         var worklist, | ||
|  |             leavelist, | ||
|  |             node, | ||
|  |             nodeType, | ||
|  |             target, | ||
|  |             element, | ||
|  |             current, | ||
|  |             current2, | ||
|  |             candidates, | ||
|  |             candidate, | ||
|  |             sentinel, | ||
|  |             outer, | ||
|  |             key; | ||
|  | 
 | ||
|  |         function removeElem(element) { | ||
|  |             var i, | ||
|  |                 key, | ||
|  |                 nextElem, | ||
|  |                 parent; | ||
|  | 
 | ||
|  |             if (element.ref.remove()) { | ||
|  |                 // When the reference is an element of an array.
 | ||
|  |                 key = element.ref.key; | ||
|  |                 parent = element.ref.parent; | ||
|  | 
 | ||
|  |                 // If removed from array, then decrease following items' keys.
 | ||
|  |                 i = worklist.length; | ||
|  |                 while (i--) { | ||
|  |                     nextElem = worklist[i]; | ||
|  |                     if (nextElem.ref && nextElem.ref.parent === parent) { | ||
|  |                         if  (nextElem.ref.key < key) { | ||
|  |                             break; | ||
|  |                         } | ||
|  |                         --nextElem.ref.key; | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         this.__initialize(root, visitor); | ||
|  | 
 | ||
|  |         sentinel = {}; | ||
|  | 
 | ||
|  |         // reference
 | ||
|  |         worklist = this.__worklist; | ||
|  |         leavelist = this.__leavelist; | ||
|  | 
 | ||
|  |         // initialize
 | ||
|  |         outer = { | ||
|  |             root: root | ||
|  |         }; | ||
|  |         element = new Element(root, null, null, new Reference(outer, 'root')); | ||
|  |         worklist.push(element); | ||
|  |         leavelist.push(element); | ||
|  | 
 | ||
|  |         while (worklist.length) { | ||
|  |             element = worklist.pop(); | ||
|  | 
 | ||
|  |             if (element === sentinel) { | ||
|  |                 element = leavelist.pop(); | ||
|  | 
 | ||
|  |                 target = this.__execute(visitor.leave, element); | ||
|  | 
 | ||
|  |                 // node may be replaced with null,
 | ||
|  |                 // so distinguish between undefined and null in this place
 | ||
|  |                 if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) { | ||
|  |                     // replace
 | ||
|  |                     element.ref.replace(target); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (this.__state === REMOVE || target === REMOVE) { | ||
|  |                     removeElem(element); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (this.__state === BREAK || target === BREAK) { | ||
|  |                     return outer.root; | ||
|  |                 } | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             target = this.__execute(visitor.enter, element); | ||
|  | 
 | ||
|  |             // node may be replaced with null,
 | ||
|  |             // so distinguish between undefined and null in this place
 | ||
|  |             if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) { | ||
|  |                 // replace
 | ||
|  |                 element.ref.replace(target); | ||
|  |                 element.node = target; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (this.__state === REMOVE || target === REMOVE) { | ||
|  |                 removeElem(element); | ||
|  |                 element.node = null; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (this.__state === BREAK || target === BREAK) { | ||
|  |                 return outer.root; | ||
|  |             } | ||
|  | 
 | ||
|  |             // node may be null
 | ||
|  |             node = element.node; | ||
|  |             if (!node) { | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             worklist.push(sentinel); | ||
|  |             leavelist.push(element); | ||
|  | 
 | ||
|  |             if (this.__state === SKIP || target === SKIP) { | ||
|  |                 continue; | ||
|  |             } | ||
|  | 
 | ||
|  |             nodeType = node.type || element.wrap; | ||
|  |             candidates = this.__keys[nodeType]; | ||
|  |             if (!candidates) { | ||
|  |                 if (this.__fallback) { | ||
|  |                     candidates = this.__fallback(node); | ||
|  |                 } else { | ||
|  |                     throw new Error('Unknown node type ' + nodeType + '.'); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             current = candidates.length; | ||
|  |             while ((current -= 1) >= 0) { | ||
|  |                 key = candidates[current]; | ||
|  |                 candidate = node[key]; | ||
|  |                 if (!candidate) { | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (isArray(candidate)) { | ||
|  |                     current2 = candidate.length; | ||
|  |                     while ((current2 -= 1) >= 0) { | ||
|  |                         if (!candidate[current2]) { | ||
|  |                             continue; | ||
|  |                         } | ||
|  |                         if (isProperty(nodeType, candidates[current])) { | ||
|  |                             element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2)); | ||
|  |                         } else if (isNode(candidate[current2])) { | ||
|  |                             element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2)); | ||
|  |                         } else { | ||
|  |                             continue; | ||
|  |                         } | ||
|  |                         worklist.push(element); | ||
|  |                     } | ||
|  |                 } else if (isNode(candidate)) { | ||
|  |                     worklist.push(new Element(candidate, key, null, new Reference(node, key))); | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return outer.root; | ||
|  |     }; | ||
|  | 
 | ||
|  |     function traverse(root, visitor) { | ||
|  |         var controller = new Controller(); | ||
|  |         return controller.traverse(root, visitor); | ||
|  |     } | ||
|  | 
 | ||
|  |     function replace(root, visitor) { | ||
|  |         var controller = new Controller(); | ||
|  |         return controller.replace(root, visitor); | ||
|  |     } | ||
|  | 
 | ||
|  |     function extendCommentRange(comment, tokens) { | ||
|  |         var target; | ||
|  | 
 | ||
|  |         target = upperBound(tokens, function search(token) { | ||
|  |             return token.range[0] > comment.range[0]; | ||
|  |         }); | ||
|  | 
 | ||
|  |         comment.extendedRange = [comment.range[0], comment.range[1]]; | ||
|  | 
 | ||
|  |         if (target !== tokens.length) { | ||
|  |             comment.extendedRange[1] = tokens[target].range[0]; | ||
|  |         } | ||
|  | 
 | ||
|  |         target -= 1; | ||
|  |         if (target >= 0) { | ||
|  |             comment.extendedRange[0] = tokens[target].range[1]; | ||
|  |         } | ||
|  | 
 | ||
|  |         return comment; | ||
|  |     } | ||
|  | 
 | ||
|  |     function attachComments(tree, providedComments, tokens) { | ||
|  |         // At first, we should calculate extended comment ranges.
 | ||
|  |         var comments = [], comment, len, i, cursor; | ||
|  | 
 | ||
|  |         if (!tree.range) { | ||
|  |             throw new Error('attachComments needs range information'); | ||
|  |         } | ||
|  | 
 | ||
|  |         // tokens array is empty, we attach comments to tree as 'leadingComments'
 | ||
|  |         if (!tokens.length) { | ||
|  |             if (providedComments.length) { | ||
|  |                 for (i = 0, len = providedComments.length; i < len; i += 1) { | ||
|  |                     comment = deepCopy(providedComments[i]); | ||
|  |                     comment.extendedRange = [0, tree.range[0]]; | ||
|  |                     comments.push(comment); | ||
|  |                 } | ||
|  |                 tree.leadingComments = comments; | ||
|  |             } | ||
|  |             return tree; | ||
|  |         } | ||
|  | 
 | ||
|  |         for (i = 0, len = providedComments.length; i < len; i += 1) { | ||
|  |             comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens)); | ||
|  |         } | ||
|  | 
 | ||
|  |         // This is based on John Freeman's implementation.
 | ||
|  |         cursor = 0; | ||
|  |         traverse(tree, { | ||
|  |             enter: function (node) { | ||
|  |                 var comment; | ||
|  | 
 | ||
|  |                 while (cursor < comments.length) { | ||
|  |                     comment = comments[cursor]; | ||
|  |                     if (comment.extendedRange[1] > node.range[0]) { | ||
|  |                         break; | ||
|  |                     } | ||
|  | 
 | ||
|  |                     if (comment.extendedRange[1] === node.range[0]) { | ||
|  |                         if (!node.leadingComments) { | ||
|  |                             node.leadingComments = []; | ||
|  |                         } | ||
|  |                         node.leadingComments.push(comment); | ||
|  |                         comments.splice(cursor, 1); | ||
|  |                     } else { | ||
|  |                         cursor += 1; | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // already out of owned node
 | ||
|  |                 if (cursor === comments.length) { | ||
|  |                     return VisitorOption.Break; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (comments[cursor].extendedRange[0] > node.range[1]) { | ||
|  |                     return VisitorOption.Skip; | ||
|  |                 } | ||
|  |             } | ||
|  |         }); | ||
|  | 
 | ||
|  |         cursor = 0; | ||
|  |         traverse(tree, { | ||
|  |             leave: function (node) { | ||
|  |                 var comment; | ||
|  | 
 | ||
|  |                 while (cursor < comments.length) { | ||
|  |                     comment = comments[cursor]; | ||
|  |                     if (node.range[1] < comment.extendedRange[0]) { | ||
|  |                         break; | ||
|  |                     } | ||
|  | 
 | ||
|  |                     if (node.range[1] === comment.extendedRange[0]) { | ||
|  |                         if (!node.trailingComments) { | ||
|  |                             node.trailingComments = []; | ||
|  |                         } | ||
|  |                         node.trailingComments.push(comment); | ||
|  |                         comments.splice(cursor, 1); | ||
|  |                     } else { | ||
|  |                         cursor += 1; | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // already out of owned node
 | ||
|  |                 if (cursor === comments.length) { | ||
|  |                     return VisitorOption.Break; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (comments[cursor].extendedRange[0] > node.range[1]) { | ||
|  |                     return VisitorOption.Skip; | ||
|  |                 } | ||
|  |             } | ||
|  |         }); | ||
|  | 
 | ||
|  |         return tree; | ||
|  |     } | ||
|  | 
 | ||
|  |     exports.version = require('./package.json').version; | ||
|  |     exports.Syntax = Syntax; | ||
|  |     exports.traverse = traverse; | ||
|  |     exports.replace = replace; | ||
|  |     exports.attachComments = attachComments; | ||
|  |     exports.VisitorKeys = VisitorKeys; | ||
|  |     exports.VisitorOption = VisitorOption; | ||
|  |     exports.Controller = Controller; | ||
|  |     exports.cloneEnvironment = function () { return clone({}); }; | ||
|  | 
 | ||
|  |     return exports; | ||
|  | }(exports)); | ||
|  | /* vim: set sw=4 ts=4 et tw=80 : */ |