diff --git a/README.md b/README.md index 56ad3b7..19b5bf4 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,13 @@ An extensible tree walk(..) framework... + ## Theory and operation -This module generalizes structure traverse (*walking*). This is done via a `walk(..)` function that recieves a user-defined `getter(..)` and returns a *walker*. +This module generalizes structure traverse (*walking*). This is done via a `walk(..)` function that recieves a user-defined `getter(..)` function and returns a *walker*. + + +### Constructing the walker and walking `walk(getter)(state, ...nodes) -> state` `walk(getter, state)(...nodes) -> state` @@ -13,6 +17,9 @@ This module generalizes structure traverse (*walking*). This is done via a `walk - Iterates through `nodes` calling the `getter(..)` per node, threading the `state` through each call, - Returns the `state` when there are no more `nodes`. + +### The getter + `getter(state, node, next, down, stop) -> state` - 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, @@ -22,6 +29,9 @@ This module generalizes structure traverse (*walking*). This is done via a `walk - Can abbort *walking* and return a state via `stop()` or `stop(state)` - Returns `state`, + +### Putting it all together + A trivial *flat* example... ```javascript walk(function(r, n){ return r+n }, 0, ...[1,2,3]) // -> 6 @@ -32,11 +42,11 @@ The above is essentially equivalent to... [1,2,3].reduce(function(r, n){ return r+n }, 0) // -> 6 ``` -And for trivial or *flat* lists `.reduce(..)` and friends are simpler and more logical. +And for *flat* lists `.reduce(..)` and friends are simpler and more logical. `walk(..)` is designed to simplify more complex cases: -Target use-cases: - The input is not *flat*: ```javascript + // sum the items in a *deep* array (depth-first)... var sum = walk(function(r, n){ return n instanceof Array ? down(r, ...n) @@ -54,6 +64,7 @@ Target use-cases: sumr( [1, [2, 3], 4, [[5], 6]] ) // -> 21 ``` + - Need to abort the recursion prematurelly: ```javascript // check if structure contains 0... @@ -61,7 +72,8 @@ Target use-cases: // NOTE: we'll only count leaf nodes... this.nodes_visited = (this.nodes_visited || 0) return e === 0 ? - // target found, abort the search and report number of nodes visited... + // target found... + //...abort search, report number of nodes visited... stop(this.nodes_visited+1) : e instanceof Array ? // breadth-first walk... @@ -146,7 +158,7 @@ var sumd = walk(function(res, node, next, down, stop){ sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5 ``` -To explicitly see the paths the above take: +To explicitly see the paths the `sum`/`sumd` take we need to modify them a little: ```javascript var sum = walk(function(res, node, next){ this.log(node) @@ -161,11 +173,21 @@ var sumd = walk(function(res, node, next, down, stop){ down(res, ...node) : res + node }, 0) +// define the path logger... sum.prototype.log = -sumd.prototype.log = -function(node){ - node instanceof Array - || console.log('-->', node) } +sumd.prototype.log = +function(node){ + this.path = node instanceof Array ? + this.path + : (this.path || []).concat([node]) +} +// XXX need a more natural way to catch the end of the walk... +sum.prototype.onWalkEnd = +sumd.prototype.onWalkEnd = +function(res){ + console.log('-->', this.path) + return res +} sum([1, [2], 3, [[4, 5]]]) // -> 15 diff --git a/package.json b/package.json index 102e05a..949bf31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "generic-walk", - "version": "0.0.5", + "version": "1.0.0", "description": "An extensible tree walk(..) framework...", "main": "walk.js", "scripts": { diff --git a/walk.js b/walk.js index 5416325..4788fa4 100644 --- a/walk.js +++ b/walk.js @@ -11,16 +11,45 @@ // walk... // ...we can easily determine if this is the first call to getter(..) // by checking and then setting an attr on the context... +// XXX this is essentially a version of .reduce(..), I wonder if it is +// feasible to do a version of .map(..), i.e. a mechanism to modify/clone +// the input... +// XXX we need a way to handle the walk end event... +// ways this can be done: +// - a type argument to getter... +// getter(action, ...) +// getter('start', state) +// getter('node', state, node, ...) +// getter('stop', state) +// + uniform and adds no new clutter... +// - this will make the general case getter more complex +// Q: can we implement this or a similar approach in way +// that would abstract the user from the actions unless +// they want to handle it??? +// ...currently, they would need to handle the actions +// to ignore the non-node actions... +// ...one way to go is to simply mark the first/last +// calls to getter in some way... +// the problem here is that there is no way to tell +// if a specific getter is last before the call is made... +// - event handlers (see .onWalkEnd) +// - non-uniform... +// - a second handler passed to walk(..) var walk = module.walk = function(getter, state, ...nodes){ + // holds the constructed walker function, used mainly to link its + // .prototype to the context of the getter(..)... var func // 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... + // no damage is likely to be done... var WalkStopException - var err_res + // This is used to hold the result when stop(..) is called, until we + // catch WalkStopException and return it from the walker... + var stop_res + // handle the getter(..) i/o for the user per call... var _step = function(context, nodes, res){ // construct a comfortable env for the user and handle the // results... @@ -43,7 +72,7 @@ function(getter, state, ...nodes){ // NOTE: 'throw' is used to stop all handling, including // the rest of the current level... function(r){ - err_res = r + stop_res = r WalkStopException = new Error('WALK_STOP_EVENT') throw WalkStopException }) @@ -65,21 +94,36 @@ function(getter, state, ...nodes){ // _step(..) wrapper, handle WalkStopException and setup the initial // context... var _walk = function(nodes, res){ + var context = func ? + Object.create(func.prototype) + : {} + try{ - return _step(func ? Object.create(func.prototype) : {}, nodes, res) + var res = _step(context, nodes, res) } catch(e){ // handle the abort condition... if(e === WalkStopException){ - return err_res - } + var res = stop_res + // something broke... - throw e + } else { + throw e + } } + + // onWalkEnd handler... + // XXX need a more natural way to do this while providing access + // to the context... + res = context.onWalkEnd ? + context.onWalkEnd(res) + : res + + return res } return ( - // return a reusable walker... + // 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...