| var hasOwnProperty = Object.prototype.hasOwnProperty; | 
| var noop = function() {}; | 
|   | 
| function ensureFunction(value) { | 
|     return typeof value === 'function' ? value : noop; | 
| } | 
|   | 
| function invokeForType(fn, type) { | 
|     return function(node, item, list) { | 
|         if (node.type === type) { | 
|             fn.call(this, node, item, list); | 
|         } | 
|     }; | 
| } | 
|   | 
| function getWalkersFromStructure(name, nodeType) { | 
|     var structure = nodeType.structure; | 
|     var walkers = []; | 
|   | 
|     for (var key in structure) { | 
|         if (hasOwnProperty.call(structure, key) === false) { | 
|             continue; | 
|         } | 
|   | 
|         var fieldTypes = structure[key]; | 
|         var walker = { | 
|             name: key, | 
|             type: false, | 
|             nullable: false | 
|         }; | 
|   | 
|         if (!Array.isArray(structure[key])) { | 
|             fieldTypes = [structure[key]]; | 
|         } | 
|   | 
|         for (var i = 0; i < fieldTypes.length; i++) { | 
|             var fieldType = fieldTypes[i]; | 
|             if (fieldType === null) { | 
|                 walker.nullable = true; | 
|             } else if (typeof fieldType === 'string') { | 
|                 walker.type = 'node'; | 
|             } else if (Array.isArray(fieldType)) { | 
|                 walker.type = 'list'; | 
|             } | 
|         } | 
|   | 
|         if (walker.type) { | 
|             walkers.push(walker); | 
|         } | 
|     } | 
|   | 
|     if (walkers.length) { | 
|         return { | 
|             context: nodeType.walkContext, | 
|             fields: walkers | 
|         }; | 
|     } | 
|   | 
|     return null; | 
| } | 
|   | 
| function getTypesFromConfig(config) { | 
|     var types = {}; | 
|   | 
|     for (var name in config.node) { | 
|         if (hasOwnProperty.call(config.node, name)) { | 
|             var nodeType = config.node[name]; | 
|   | 
|             if (!nodeType.structure) { | 
|                 throw new Error('Missed `structure` field in `' + name + '` node type definition'); | 
|             } | 
|   | 
|             types[name] = getWalkersFromStructure(name, nodeType); | 
|         } | 
|     } | 
|   | 
|     return types; | 
| } | 
|   | 
| function createTypeIterator(config, reverse) { | 
|     var fields = config.fields.slice(); | 
|     var contextName = config.context; | 
|     var useContext = typeof contextName === 'string'; | 
|   | 
|     if (reverse) { | 
|         fields.reverse(); | 
|     } | 
|   | 
|     return function(node, context, walk) { | 
|         var prevContextValue; | 
|   | 
|         if (useContext) { | 
|             prevContextValue = context[contextName]; | 
|             context[contextName] = node; | 
|         } | 
|   | 
|         for (var i = 0; i < fields.length; i++) { | 
|             var field = fields[i]; | 
|             var ref = node[field.name]; | 
|   | 
|             if (!field.nullable || ref) { | 
|                 if (field.type === 'list') { | 
|                     if (reverse) { | 
|                         ref.forEachRight(walk); | 
|                     } else { | 
|                         ref.forEach(walk); | 
|                     } | 
|                 } else { | 
|                     walk(ref); | 
|                 } | 
|             } | 
|         } | 
|   | 
|         if (useContext) { | 
|             context[contextName] = prevContextValue; | 
|         } | 
|     }; | 
| } | 
|   | 
| function createFastTraveralMap(iterators) { | 
|     return { | 
|         Atrule: { | 
|             StyleSheet: iterators.StyleSheet, | 
|             Atrule: iterators.Atrule, | 
|             Rule: iterators.Rule, | 
|             Block: iterators.Block | 
|         }, | 
|         Rule: { | 
|             StyleSheet: iterators.StyleSheet, | 
|             Atrule: iterators.Atrule, | 
|             Rule: iterators.Rule, | 
|             Block: iterators.Block | 
|         }, | 
|         Declaration: { | 
|             StyleSheet: iterators.StyleSheet, | 
|             Atrule: iterators.Atrule, | 
|             Rule: iterators.Rule, | 
|             Block: iterators.Block | 
|         } | 
|     }; | 
| } | 
|   | 
| module.exports = function createWalker(config) { | 
|     var types = getTypesFromConfig(config); | 
|     var iteratorsNatural = {}; | 
|     var iteratorsReverse = {}; | 
|   | 
|     for (var name in types) { | 
|         if (hasOwnProperty.call(types, name) && types[name] !== null) { | 
|             iteratorsNatural[name] = createTypeIterator(types[name], false); | 
|             iteratorsReverse[name] = createTypeIterator(types[name], true); | 
|         } | 
|     } | 
|   | 
|     var fastTraversalIteratorsNatural = createFastTraveralMap(iteratorsNatural); | 
|     var fastTraversalIteratorsReverse = createFastTraveralMap(iteratorsReverse); | 
|   | 
|     var walk = function(root, options) { | 
|         function walkNode(node, item, list) { | 
|             enter.call(context, node, item, list); | 
|   | 
|             if (iterators.hasOwnProperty(node.type)) { | 
|                 iterators[node.type](node, context, walkNode); | 
|             } | 
|   | 
|             leave.call(context, node, item, list); | 
|         } | 
|   | 
|         var enter = noop; | 
|         var leave = noop; | 
|         var iterators = iteratorsNatural; | 
|         var context = { | 
|             root: root, | 
|             stylesheet: null, | 
|             atrule: null, | 
|             atrulePrelude: null, | 
|             rule: null, | 
|             selector: null, | 
|             block: null, | 
|             declaration: null, | 
|             function: null | 
|         }; | 
|   | 
|         if (typeof options === 'function') { | 
|             enter = options; | 
|         } else if (options) { | 
|             enter = ensureFunction(options.enter); | 
|             leave = ensureFunction(options.leave); | 
|   | 
|             if (options.reverse) { | 
|                 iterators = iteratorsReverse; | 
|             } | 
|   | 
|             if (options.visit) { | 
|                 if (fastTraversalIteratorsNatural.hasOwnProperty(options.visit)) { | 
|                     iterators = options.reverse | 
|                         ? fastTraversalIteratorsReverse[options.visit] | 
|                         : fastTraversalIteratorsNatural[options.visit]; | 
|                 } else if (!types.hasOwnProperty(options.visit)) { | 
|                     throw new Error('Bad value `' + options.visit + '` for `visit` option (should be: ' + Object.keys(types).join(', ') + ')'); | 
|                 } | 
|   | 
|                 enter = invokeForType(enter, options.visit); | 
|                 leave = invokeForType(leave, options.visit); | 
|             } | 
|         } | 
|   | 
|         if (enter === noop && leave === noop) { | 
|             throw new Error('Neither `enter` nor `leave` walker handler is set or both aren\'t a function'); | 
|         } | 
|   | 
|         // swap handlers in reverse mode to invert visit order | 
|         if (options.reverse) { | 
|             var tmp = enter; | 
|             enter = leave; | 
|             leave = tmp; | 
|         } | 
|   | 
|         walkNode(root); | 
|     }; | 
|   | 
|     walk.find = function(ast, fn) { | 
|         var found = null; | 
|   | 
|         walk(ast, function(node, item, list) { | 
|             if (found === null && fn.call(this, node, item, list)) { | 
|                 found = node; | 
|             } | 
|         }); | 
|   | 
|         return found; | 
|     }; | 
|   | 
|     walk.findLast = function(ast, fn) { | 
|         var found = null; | 
|   | 
|         walk(ast, { | 
|             reverse: true, | 
|             enter: function(node, item, list) { | 
|                 if (found === null && fn.call(this, node, item, list)) { | 
|                     found = node; | 
|                 } | 
|             } | 
|         }); | 
|   | 
|         return found; | 
|     }; | 
|   | 
|     walk.findAll = function(ast, fn) { | 
|         var found = []; | 
|   | 
|         walk(ast, function(node, item, list) { | 
|             if (fn.call(this, node, item, list)) { | 
|                 found.push(node); | 
|             } | 
|         }); | 
|   | 
|         return found; | 
|     }; | 
|   | 
|     return walk; | 
| }; |