mirror of
				https://github.com/flynx/walk.js.git
				synced 2025-10-31 03:50:14 +00:00 
			
		
		
		
	tweaking and cleanup...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									ba2ba57287
								
							
						
					
					
						commit
						aa2343aa93
					
				
							
								
								
									
										107
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								README.md
									
									
									
									
									
								
							| @ -15,6 +15,7 @@ This module generalizes structure traverse (*walking*). This is done via a `walk | |||||||
| 
 | 
 | ||||||
| `getter(state, node, next, down, stop) -> state`   | `getter(state, node, next, down, stop) -> state`   | ||||||
| - Recieves `state`, `node` and three control functions: `next`, `down` and `stop`, | - Recieves `state`, `node` and three control functions: `next`, `down` and `stop`, | ||||||
|  | - Called in a context (`this`), persistent within one `walk(..)` call, inherited from *walker*`.prototype` and usable to store data between `getter(..)` calls, | ||||||
| - Can process `node` and `state`, | - Can process `node` and `state`, | ||||||
| - Can queue nodes for walking via `next(...nodes)` | - Can queue nodes for walking via `next(...nodes)` | ||||||
| - Can walk nodes directly via `down(state, ...nodes) -> state` | - Can walk nodes directly via `down(state, ...nodes) -> state` | ||||||
| @ -57,15 +58,17 @@ Target use-cases: | |||||||
| 	```javascript | 	```javascript | ||||||
| 	// check if structure contains 0... | 	// check if structure contains 0... | ||||||
| 	var containsZero = walk(function(r, e, next, down, stop){ | 	var containsZero = walk(function(r, e, next, down, stop){ | ||||||
|  | 		// NOTE: we'll only count leaf nodes... | ||||||
|  | 		this.nodes_visited = (this.nodes_visited || 0) | ||||||
| 		return e === 0 ?  | 		return e === 0 ?  | ||||||
| 				// target found, abort the search... | 				// target found, abort the search and report number of nodes visited... | ||||||
| 				stop(true) | 				stop(this.nodes_visited+1) | ||||||
| 			: e instanceof Array ? | 			: e instanceof Array ? | ||||||
| 				// breadth-first walk... | 				// breadth-first walk... | ||||||
| 				!!next(...e) | 				!!next(...e) | ||||||
| 			: r }, false) | 			: (this.nodes_visited++, r) }, false) | ||||||
| 
 | 
 | ||||||
| 	containsZero( [1, [2, 0], 4, [[5], 6]] ) // -> true | 	containsZero( [1, [2, 0], 4, [[5], 6]] ) // -> 3 | ||||||
| 	containsZero( [1, [2, 5], 4, [[5], 6]] ) // -> false | 	containsZero( [1, [2, 5], 4, [[5], 6]] ) // -> false | ||||||
| 	``` | 	``` | ||||||
| 	See a more usefull search in [examples](#examples)... | 	See a more usefull search in [examples](#examples)... | ||||||
| @ -143,56 +146,56 @@ var sumd = walk(function(res, node, next, down, stop){ | |||||||
| sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5 | sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5 | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | To explicitly see the paths the above take: | ||||||
|  | ```javascript | ||||||
|  | var sum = walk(function(res, node, next){ | ||||||
|  | 	this.log(node) | ||||||
|  | 	return node instanceof Array ? | ||||||
|  | 		// compensate for that next(..) returns undefined... | ||||||
|  | 		next(...node)  | ||||||
|  | 			|| res | ||||||
|  | 		: res + node }, 0) | ||||||
|  | var sumd = walk(function(res, node, next, down, stop){ | ||||||
|  | 	this.log(node) | ||||||
|  | 	return node instanceof Array ? | ||||||
|  | 		down(res, ...node) | ||||||
|  | 		: res + node }, 0) | ||||||
|  | 
 | ||||||
|  | sum.prototype.log =  | ||||||
|  | sumd.prototype.log =  | ||||||
|  | function(node){  | ||||||
|  | 	node instanceof Array  | ||||||
|  | 		|| console.log('-->', node) } | ||||||
|  | 
 | ||||||
|  | sum([1, [2], 3, [[4, 5]]]) // -> 15 | ||||||
|  | 
 | ||||||
|  | sumd([1, [2], 3, [[4, 5]]]) // -> 15 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| FInd first zero in tree and return it's path... | FInd first zero in tree and return it's path... | ||||||
| ```javascript | ```javascript | ||||||
| // XXX res/path threading seem unnatural here... | // NOTE: the only reason this is wrapped into a function is that we need | ||||||
| var __firstZero = walk(function(res, node, next, down, stop){ | //  	to restrict the number of items (L) this is passed to 1... | ||||||
| 	var k = node[0] | var firstZero = function(L){ | ||||||
| 	var v = node[1] | 	return walk(function(res, node, next, down, stop){ | ||||||
| 	var path = res[0] | 		// setup... | ||||||
| 	return v === 0 ? | 		if(this.path == null){ | ||||||
| 			// NOTE: we .slice(1) here to remove the initial null | 			this.path = [] | ||||||
| 			//		we added in firstZero(..)... | 			node = [null, node] | ||||||
| 			stop([ path.slice(1).concat([k]) ]) | 		} | ||||||
| 		: v instanceof Object? | 		var path = this.path | ||||||
| 			down( | 		var k = node[0] | ||||||
| 				[path.concat([k]), null],  | 		var v = node[1] | ||||||
| 				...Object.entries(v)) | 		return v === 0 ? | ||||||
| 		: res }, [[], null]) | 				// NOTE: we .slice(1) here to remove the initial null | ||||||
| var firstZero = function(value){  | 				//		we added in firstZero(..)... | ||||||
| 	// NOTE: we are wrapping the input here to make it the same  | 				stop(path.slice(1).concat([k])) | ||||||
| 	//		format as that of Object.entries(..) items... | 			: v instanceof Object? | ||||||
| 	return __firstZero([null, value]).pop() } | 				(path.push(k),  | ||||||
| 
 | 				down( | ||||||
| firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y'] | 					res,  | ||||||
| ``` | 					...Object.entries(v))) | ||||||
| 
 | 			: res}, false, L) } | ||||||
| 
 |  | ||||||
| FInd first zero in tree and return it's path... |  | ||||||
| ```javascript |  | ||||||
| // same as the above but illustrates a different strategy, a bit |  | ||||||
| // cleaner but creates a walker every time it's called... |  | ||||||
| var firstZero = function(value){ |  | ||||||
| 	return walk( |  | ||||||
| 		function(res, node, next, down, stop){ |  | ||||||
| 			var k = node[0] |  | ||||||
| 			var v = node[1] |  | ||||||
| 			var path = res[0] |  | ||||||
| 			return v === 0 ? |  | ||||||
| 					// NOTE: we .slice(1) here to remove the initial null |  | ||||||
| 					//		we added in firstZero(..)... |  | ||||||
| 					stop([ path.slice(1).concat([k]) ]) |  | ||||||
| 				: v instanceof Object? |  | ||||||
| 					down( |  | ||||||
| 						[path.concat([k]), null],  |  | ||||||
| 						...Object.entries(v)) |  | ||||||
| 				: res },  |  | ||||||
| 		[[], null],  |  | ||||||
| 		// wrap the input to make it compatible with Object.entries(..)  |  | ||||||
| 		// items... |  | ||||||
| 		[null, value]) |  | ||||||
| 			// separate the result from path... |  | ||||||
| 			.pop() } |  | ||||||
| 
 | 
 | ||||||
| firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y'] | firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y'] | ||||||
| ``` | ``` | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "generic-walk", |   "name": "generic-walk", | ||||||
|   "version": "0.0.3", |   "version": "0.0.5", | ||||||
|   "description": "An extensible tree walk(..) framework...", |   "description": "An extensible tree walk(..) framework...", | ||||||
|   "main": "walk.js", |   "main": "walk.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
							
								
								
									
										332
									
								
								walk.js
									
									
									
									
									
								
							
							
						
						
									
										332
									
								
								walk.js
									
									
									
									
									
								
							| @ -1,225 +1,107 @@ | |||||||
| /********************************************************************** | /********************************************************************** | ||||||
| *  | *  | ||||||
| * | * | ||||||
| * | * | ||||||
| **********************************************************************/ | **********************************************************************/ | ||||||
| ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | ||||||
| (function(require){ var module={} // make module AMD/node compatible...
 | (function(require){ var module={} // make module AMD/node compatible...
 | ||||||
| /*********************************************************************/ | /*********************************************************************/ | ||||||
| 
 | 
 | ||||||
| // Walk...
 | // XXX might be a good idea to add a way to do something on start/end of 
 | ||||||
| //
 | // 		walk...
 | ||||||
| //	Construct a reusable walker function...
 | // 		...we can easily determine if this is the first call to getter(..)
 | ||||||
| //	walk(get(..))
 | // 		by checking and then setting an attr on the context...
 | ||||||
| //		-> func(state, ...nodes)
 | var walk =  | ||||||
| //
 | module.walk = | ||||||
| //	Construct a reusable walker function with set initial state...
 | function(getter, state, ...nodes){ | ||||||
| //	walk(get(..), state)
 | 	var func | ||||||
| //		-> func(...nodes)
 | 	// this is used to break out of the recursion...
 | ||||||
| //
 | 	// NOTE: this can leak out but we only care about it's identity thus
 | ||||||
| //	Walk the nodes...
 | 	// 		no damage can be done...
 | ||||||
| //	walk(get(..), state, ...nodes)
 | 	var WalkStopException | ||||||
| //		-> res
 | 	var err_res | ||||||
| //
 | 
 | ||||||
| // The get(..) that walk(..) expects has the following signature:
 | 	var _step = function(context, nodes, res){ | ||||||
| //		get(res, node, next(..), down(..), stop(..))
 | 		// construct a comfortable env for the user and handle the 
 | ||||||
| //			-> res
 | 		// results...
 | ||||||
| //
 | 		var _get = function(node){ | ||||||
| //		Queue next nodes to walk...
 | 			var next = [] | ||||||
| //		next(...nodes)
 | 			res = getter.call(context, | ||||||
| //			-> undefined
 | 				res,  | ||||||
| //			NOTE: this will just queue the nodes and return immediately.
 | 				node,  | ||||||
| //			NOTE: the state is passed to the nodes implicitly, the get(..)
 | 				// breadth first step...
 | ||||||
| //				handling first node from this list will get the last result
 | 				// 	next(...nodes) -> undefined
 | ||||||
| //				returned by the last get(..) call on this level as it's 
 | 				function(...nodes){ next = nodes }, | ||||||
| //				state.
 | 				// depth first step...
 | ||||||
| //
 | 				// 	down(state, ..nodes) -> res
 | ||||||
| //		Walk the next set of nodes...
 | 				function(res, ...nodes){  | ||||||
| //		down(state, ...nodes)
 | 					return _step(context, nodes, res) }, | ||||||
| //			-> res
 | 				// stop walking...
 | ||||||
| //			NOTE: this will process all the nodes and then return the 
 | 				// 	stop()
 | ||||||
| //				result.
 | 				// 	stop(value)
 | ||||||
| //
 | 				//
 | ||||||
| //		Stop the walk...
 | 				// NOTE: 'throw' is used to stop all handling, including 
 | ||||||
| //		stop()
 | 				// 		the rest of the current level...
 | ||||||
| //		stop(result)
 | 				function(r){ | ||||||
| //			-> undefined
 | 					err_res = r | ||||||
| //			NOTE: this will break current function execution in a similar 
 | 					WalkStopException = new Error('WALK_STOP_EVENT') | ||||||
| //				effect to 'return', i.e. no code in function will be 
 | 					throw WalkStopException  | ||||||
| //				executed after stop(..) call...
 | 				}) | ||||||
| //			NOTE: this is done by throwing a special error, this error 
 | 			return next | ||||||
| //				should either not be caught or must be re-thrown, as 
 | 		} | ||||||
| //				shadowing this exception may have unexpected results...
 | 
 | ||||||
| //
 | 		return nodes.length == 0 ? | ||||||
| //
 | 			// no new nodes to walk...
 | ||||||
| // Example:
 | 			res | ||||||
| //		// sum all the values of a nested array...
 | 			// do the next level...
 | ||||||
| //		var sum = walk(function(res, node, next){
 | 			// NOTE: see note below... ( ;) )
 | ||||||
| //			return node instanceof Array ?
 | 			: _step(context, nodes | ||||||
| //				// compensate for that next(..) returns undefined...
 | 				.map(_get) | ||||||
| //				next(...node) 
 | 				.reduce(function(next, e){ | ||||||
| //					|| res
 | 					return e instanceof Array ?  | ||||||
| //				: res + node }, 0)
 | 						next.concat(e)  | ||||||
| //
 | 						: next.push(e) }, []), res)  | ||||||
| //		// depth first walker...
 | 	}  | ||||||
| //		var sumd = walk(function(res, node, next, down, stop){
 | 	// _step(..) wrapper, handle WalkStopException and setup the initial
 | ||||||
| //			return node instanceof Array ?
 | 	// context...
 | ||||||
| //				down(res, ...node)
 | 	var _walk = function(nodes, res){ | ||||||
| //				: res + node }, 0)
 | 		try{ | ||||||
| //
 | 			return _step(func ? Object.create(func.prototype) : {}, nodes, res) | ||||||
| //		sum([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 3, 2, 4, 5
 | 
 | ||||||
| //		sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5
 | 		} catch(e){ | ||||||
| //
 | 			// handle the abort condition...
 | ||||||
| //
 | 			if(e === WalkStopException){ | ||||||
| // Example:
 | 				return err_res | ||||||
| // 		// XXX res/path threading seem unnatural here...
 | 			} | ||||||
| // 		var __firstZero = walk(function(res, node, next, down, stop){
 | 			// something broke...
 | ||||||
| // 			var k = node[0]
 | 			throw e | ||||||
| // 			var v = node[1]
 | 		} | ||||||
| // 			var path = res[0]
 | 	} | ||||||
| // 			return v === 0 ?
 | 
 | ||||||
| // 					// NOTE: we .slice(1) here to remove the initial null
 | 	return ( | ||||||
| // 					//		we added in firstZero(..)...
 | 		// return a reusable walker...
 | ||||||
| // 					stop([ path.slice(1).concat([k]) ])
 | 		arguments.length == 1 ? | ||||||
| // 				: v instanceof Object?
 | 			// NOTE: this wrapper is here so as to isolate and re-order res 
 | ||||||
| // 					down(
 | 			// 		construction and passing it to the next level...
 | ||||||
| // 						[path.concat([k]), null], 
 | 			// 		this is needed as when we do:
 | ||||||
| // 						...Object.entries(v))
 | 			// 			step(res, ...nodes.map(_get).reduce(...))
 | ||||||
| // 				: res }, [[], null])
 | 			// 		res (if immutable) will first get passed by value and 
 | ||||||
| // 		var firstZero = function(value){ 
 | 			// 		then will get re-assigned in _get(..), thus step(..) will 
 | ||||||
| // 			// NOTE: we are wrapping the input here to make it the same 
 | 			// 		not see the updated version...
 | ||||||
| // 			//		format as that of Object.entries(..) items...
 | 			// 		This approach simply pushes the pass-by-value of res till 
 | ||||||
| // 			return __firstZero([null, value]).pop() }
 | 			// 		after it gets updated by _get(..).
 | ||||||
| //
 | 			(func = function(state, ...nodes){ | ||||||
| //
 | 				return _walk(nodes, state) }) | ||||||
| // 		// same as the above but illustrates a different strategy, a bit
 | 		// reusable walker with a curried initial state... 
 | ||||||
| // 		// cleaner but creates a walker every time it's called...
 | 		// NOTE: better keep this immutable or clone this in get(..)
 | ||||||
| // 		var firstZero = function(value){
 | 		: arguments.length == 2 ? | ||||||
| // 			return walk(
 | 			(func = function(...nodes){ | ||||||
| // 				function(res, node, next, down, stop){
 | 				return _walk(nodes, state) }) | ||||||
| // 			        var k = node[0]
 | 		// walk...
 | ||||||
| // 					var v = node[1]
 | 		: _walk(nodes, state)) | ||||||
| // 					var path = res[0]
 | } | ||||||
| // 					return v === 0 ?
 | 
 | ||||||
| // 							// NOTE: we .slice(1) here to remove the initial null
 | 
 | ||||||
| // 							//		we added in firstZero(..)...
 | 
 | ||||||
| // 							stop([ path.slice(1).concat([k]) ])
 | /********************************************************************** | ||||||
| // 						: v instanceof Object?
 | * vim:set ts=4 sw=4 :                               */ return module }) | ||||||
| // 							down(
 |  | ||||||
| // 								[path.concat([k]), null], 
 |  | ||||||
| // 								...Object.entries(v))
 |  | ||||||
| // 						: res }, 
 |  | ||||||
| // 				[[], null], 
 |  | ||||||
| // 				// wrap the input to make it compatible with Object.entries(..) 
 |  | ||||||
| // 				// items...
 |  | ||||||
| // 				[null, value])
 |  | ||||||
| // 					// separate the result from path...
 |  | ||||||
| // 					.pop() }
 |  | ||||||
| //
 |  | ||||||
| //
 |  | ||||||
| // 		firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y']
 |  | ||||||
| //
 |  | ||||||
| //
 |  | ||||||
| //
 |  | ||||||
| // NOTE: a walker is returned iff walk(..) is passed a single argument.
 |  | ||||||
| // NOTE: the following two are equivalent:
 |  | ||||||
| // 		walk(get, start, ...nodes) 
 |  | ||||||
| // 		walk(get)(start, ...nodes)
 |  | ||||||
| // NOTE: walk goes breadth first...
 |  | ||||||
| //
 |  | ||||||
| // XXX might be a good idea to move this into it's own module...
 |  | ||||||
| // 		generic-walk might be a good name...
 |  | ||||||
| var walk =  |  | ||||||
| module.walk = |  | ||||||
| function(get, state, ...nodes){ |  | ||||||
| 	var context = {} |  | ||||||
| 	// this is used to break out of the recursion...
 |  | ||||||
| 	// NOTE: this can leak out but we only care about it's identity thus
 |  | ||||||
| 	// 		no damage can be done...
 |  | ||||||
| 	var WalkStopException |  | ||||||
| 	var err_res |  | ||||||
| 
 |  | ||||||
| 	var _step = function(nodes, res){ |  | ||||||
| 		// construct a comfortable env for the user and handle the 
 |  | ||||||
| 		// results...
 |  | ||||||
| 		var _get = function(node){ |  | ||||||
| 			var next = [] |  | ||||||
| 			res = get.call(context, |  | ||||||
| 				res,  |  | ||||||
| 				node,  |  | ||||||
| 				// breadth first step...
 |  | ||||||
| 				// 	next(...nodes) -> undefined
 |  | ||||||
| 				function(...nodes){ next = nodes }, |  | ||||||
| 				// depth first step...
 |  | ||||||
| 				// 	down(state, ..nodes) -> res
 |  | ||||||
| 				function(res, ...nodes){  |  | ||||||
| 					return _step(nodes, res) }, |  | ||||||
| 				// stop...
 |  | ||||||
| 				// 	stop()
 |  | ||||||
| 				// 	stop(value)
 |  | ||||||
| 				//
 |  | ||||||
| 				// NOTE: 'throw' is used to stop all handling, including 
 |  | ||||||
| 				// 		the rest of the current level...
 |  | ||||||
| 				function(r){ |  | ||||||
| 					err_res = r |  | ||||||
| 					WalkStopException = new Error('WALK_STOP_EVENT') |  | ||||||
| 					throw WalkStopException  |  | ||||||
| 				}) |  | ||||||
| 			return next |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return nodes.length == 0 ? |  | ||||||
| 			// no new nodes to walk...
 |  | ||||||
| 			res |  | ||||||
| 			// do the next level...
 |  | ||||||
| 			// NOTE: see note below... ( ;) )
 |  | ||||||
| 			: _step(nodes |  | ||||||
| 				.map(_get) |  | ||||||
| 				.reduce(function(next, e){ |  | ||||||
| 					return e instanceof Array ?  |  | ||||||
| 						next.concat(e)  |  | ||||||
| 						: next.push(e) }, []), res)  |  | ||||||
| 	}  |  | ||||||
| 	// _step(..) wrapper, handle WalkStopException... 
 |  | ||||||
| 	var _walk = function(nodes, res){ |  | ||||||
| 		try{ |  | ||||||
| 			return _step(nodes, res) |  | ||||||
| 
 |  | ||||||
| 		} catch(e){ |  | ||||||
| 			// handle the abort condition...
 |  | ||||||
| 			if(e === WalkStopException){ |  | ||||||
| 				return err_res |  | ||||||
| 			} |  | ||||||
| 			// something broke...
 |  | ||||||
| 			throw e |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return ( |  | ||||||
| 		// return a reusable walker...
 |  | ||||||
| 		arguments.length == 1 ? |  | ||||||
| 			// NOTE: this wrapper is here so as to isolate and re-order res 
 |  | ||||||
| 			// 		construction and passing it to the next level...
 |  | ||||||
| 			// 		this is needed as when we do:
 |  | ||||||
| 			// 			step(res, ...nodes.map(_get).reduce(...))
 |  | ||||||
| 			// 		res (if immutable) will first get passed by value and 
 |  | ||||||
| 			// 		then will get re-assigned in _get(..), thus step(..) will 
 |  | ||||||
| 			// 		not see the updated version...
 |  | ||||||
| 			// 		This approach simply pushes the pass-by-value of res till 
 |  | ||||||
| 			// 		after it gets updated by _get(..).
 |  | ||||||
| 			function(res, ...nodes){ |  | ||||||
| 				return _walk(nodes, res) } |  | ||||||
| 		// reusable walker with a curried initial state... 
 |  | ||||||
| 		// NOTE: better keep this immutable or clone this in get(..)
 |  | ||||||
| 		: arguments.length == 2 ? |  | ||||||
| 			function(...nodes){ |  | ||||||
| 				return _walk(nodes, state) } |  | ||||||
| 		// walk...
 |  | ||||||
| 		: _walk(nodes, state)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /********************************************************************** |  | ||||||
| * vim:set ts=4 sw=4 :                               */ return module }) |  | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user