806 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			806 lines
		
	
	
		
			26 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,
 | 
						|
        VisitorOption,
 | 
						|
        VisitorKeys,
 | 
						|
        BREAK,
 | 
						|
        SKIP,
 | 
						|
        REMOVE;
 | 
						|
 | 
						|
    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;
 | 
						|
    }
 | 
						|
 | 
						|
    // 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;
 | 
						|
    }
 | 
						|
 | 
						|
    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',
 | 
						|
        ChainExpression: 'ChainExpression',
 | 
						|
        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',
 | 
						|
        ImportExpression: 'ImportExpression',
 | 
						|
        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',
 | 
						|
        PrivateIdentifier: 'PrivateIdentifier',
 | 
						|
        Program: 'Program',
 | 
						|
        Property: 'Property',
 | 
						|
        PropertyDefinition: 'PropertyDefinition',
 | 
						|
        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'],
 | 
						|
        ChainExpression: ['expression'],
 | 
						|
        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'],
 | 
						|
        ImportExpression: ['source'],
 | 
						|
        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'],
 | 
						|
        PrivateIdentifier: [],
 | 
						|
        Program: ['body'],
 | 
						|
        Property: ['key', 'value'],
 | 
						|
        PropertyDefinition: ['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 (Array.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 (Array.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 = Object.keys;
 | 
						|
        } else if (typeof visitor.fallback === 'function') {
 | 
						|
            this.__fallback = visitor.fallback;
 | 
						|
        }
 | 
						|
 | 
						|
        this.__keys = VisitorKeys;
 | 
						|
        if (visitor.keys) {
 | 
						|
            this.__keys = Object.assign(Object.create(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;
 | 
						|
    }
 | 
						|
  
 | 
						|
    function candidateExistsInLeaveList(leavelist, candidate) {
 | 
						|
        for (var i = leavelist.length - 1; i >= 0; --i) {
 | 
						|
            if (leavelist[i].node === candidate) {
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    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 (Array.isArray(candidate)) {
 | 
						|
                        current2 = candidate.length;
 | 
						|
                        while ((current2 -= 1) >= 0) {
 | 
						|
                            if (!candidate[current2]) {
 | 
						|
                                continue;
 | 
						|
                            }
 | 
						|
 | 
						|
                            if (candidateExistsInLeaveList(leavelist, 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)) {
 | 
						|
                        if (candidateExistsInLeaveList(leavelist, candidate)) {
 | 
						|
                          continue;
 | 
						|
                        }
 | 
						|
 | 
						|
                        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 (Array.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.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 : */
 |