added .onWalkEnd event as an experiment... (this will change)

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-09-29 23:44:44 +03:00
parent aa2343aa93
commit a2c8bb5d32
3 changed files with 84 additions and 18 deletions

View File

@ -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) }
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

View File

@ -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": {

58
walk.js
View File

@ -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...
} 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...