mirror of
https://github.com/flynx/walk.js.git
synced 2025-10-29 11:00:13 +00:00
added .onWalkEnd event as an experiment... (this will change)
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
aa2343aa93
commit
a2c8bb5d32
40
README.md
40
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
|
||||
|
||||
|
||||
@ -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": {
|
||||
|
||||
60
walk.js
60
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...
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user