added a walk done handler + updated docs...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-09-30 01:48:59 +03:00
parent 7df2a6b4d9
commit 1f6b2a0f69
3 changed files with 95 additions and 63 deletions

View File

@ -3,22 +3,36 @@
An extensible tree walk(..) framework...
- [walk.js](#walkjs)
- [Theory and operation](#theory-and-operation)
- [Constructing the walker and walking ( walk(..) )](#constructing-the-walker-and-walking--walk)
- [Getting and processing nodes ( getter(..) )](#getting-and-processing-nodes--getter)
- [Putting it all together](#putting-it-all-together)
- [Installation and loading](#installation-and-loading)
- [API](#api)
- [`getter(..)`](#getter)
- [`done(..)` (optional)](#done-optional)
- [Examples](#examples)
## Theory and operation
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
### Constructing the walker and walking ( walk(..) )
`walk(getter)(state, ...nodes) -> state`
`walk(getter, state)(...nodes) -> state`
`walk(getter, state, ...nodes) -> state`
- Recieves a `getter` function a `state` and a list of `nodes`,
- Iterates through `nodes` calling the `getter(..)` per node, threading the `state` through each call,
`walk(getter[, done])(state, ...nodes) -> state`
`walk(getter[, done], state)(...nodes) -> state`
`walk(getter[, done], state, ...nodes) -> state`
- Recieves a `getter` function, an optional `done` function, a `state` and a list of `nodes`,
- Iterates through `nodes`, calling the `getter(..)` per node and threading the `state` through each call,
- If `done(..)` is given, it is called passing the `state` after walking is done, the return value is set as the new `state` value,
- Returns the `state` when there are no more `nodes`.
### Getting and processing nodes
### Getting and processing nodes ( getter(..) )
`getter(state, node, next, stop) -> state`
- Recieves `state`, `node` and two control functions: `next` and `stop`,
@ -84,6 +98,7 @@ And for *flat* lists `.reduce(..)` and friends are simpler and more logical. `wa
See a more usefull search in [examples](#examples)...
## Installation and loading
```shell
@ -97,21 +112,27 @@ var walk = require('generic-walk').walk
*Note: This module supports both AMD and node's `require(..)`**
## API
`walk(getter(..)) -> walker(state, ...nodes)`
`walk(getter(..), done(..)) -> walker(state, ...nodes)`
Construct a reusable walker.
`walk(getter(..), state) -> walker(...nodes)`
`walk(getter(..), done(..), state) -> walker(...nodes)`
Construct a reusable walker with fixed initial state.
`walk(getter(..), state, ...nodes) -> result`
`walk(getter(..), done(..), state, ...nodes) -> result`
Walk the nodes.
*Note that `state` can not be a function.*
### The getter
### `getter(..)`
`getter(state, node, next(..), stop(..)) -> state`
User provided function, called to process a node.
@ -132,6 +153,13 @@ Stop walking and return `state`. The passed `state` is directly returned from th
*Note that `stop(..)` behaves in a similar manner to `return`, i.e. execution is aborted immidiately.*
### `done(..)` (optional)
`done(state) -> state`
User provided function, if given, is called by the *walker* after walking is done (no more nodes to handle). `state` is passed as argument and the return value is returned from the *walker*. This is run in the same context (`this`) as `getter(..)`.
## Examples
Sum all the values of a nested array (breadth-first)...
@ -158,31 +186,31 @@ sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5
To explicitly see the paths the `sum`/`sumd` take we need to modify them a little:
```javascript
var makeSummer = function(mode){
return walk(function(res, node, next){
var walker = walk(
function(res, node, next){
this.log(node)
return node instanceof Array ?
next(mode == 'breadth-first' ? 'queue' : 'do', res, ...node)
: res + node }, 0) }
next(mode == 'breadth-first' ? 'queue' : 'do',
res,
...node)
: res + node },
// print the path when done...
function(state){
console.log('-->', this.path)
return state
},
1)
// log the nodes...
walker.prototype.log = function(node){
this.path = node instanceof Array ?
this.path
: (this.path || []).concat([node]) }
return walker
}
var sum = makeSummer('breadth-first')
var sumd = makeSummer('depth-first')
// define the path logger...
sum.prototype.log =
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
sumd([1, [2], 3, [[4, 5]]]) // -> 15

View File

@ -1,6 +1,6 @@
{
"name": "generic-walk",
"version": "1.1.0",
"version": "1.3.0",
"description": "An extensible tree walk(..) framework...",
"main": "walk.js",
"scripts": {

70
walk.js
View File

@ -7,38 +7,43 @@
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
// XXX might be a good idea to add a way to do something on start/end of
// walk...
// ...we can easily determine if this is the first call to getter(..)
// by checking and then setting an attr on the context...
// Walk a set of nodes...
//
// Construct a walker...
// walk(getter(..))
// walk(getter(..), done(..))
// -> walker(state, ...nodes)
// -> state
//
// Construct a walker with a curried start state...
// walk(getter(..), state)
// walk(getter(..), done(..), state)
// -> walker(...nodes)
// -> state
//
// Walk the set of nodes...
// walk(getter(..), state, ...nodes)
// walk(getter(..), done(..), state, ...nodes)
// -> state
//
// NOTE: state can not be a function...
//
//
// 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) -- REJECTED
// - non-uniform...
// - a second handler passed to walk(..)
// - will mess up the signature that is already complex...
// XXX EXPERIMENTAL: done(..) handler is not yet final...
var walk =
module.walk =
function(getter, state, ...nodes){
// XXX EXPERIMENTAL...
var done
// we've got a done handler passed...
if(state instanceof Function){
done = state
state = nodes.shift()
}
// holds the constructed walker function, used mainly to link its
// .prototype to the context of the getter(..)...
var func
@ -141,11 +146,10 @@ function(getter, state, ...nodes){
}
}
// onWalkEnd handler...
// XXX need a more natural way to do this while providing access
// to the context...
res = context.onWalkEnd ?
context.onWalkEnd(res)
// call the done handler...
// XXX EXPERIMENTAL...
res = done ?
done.call(context, res)
: res
return res
@ -153,7 +157,7 @@ function(getter, state, ...nodes){
return (
// reusable walker...
arguments.length == 1 ?
arguments.length == (done ? 2 : 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:
@ -167,7 +171,7 @@ function(getter, state, ...nodes){
return _walk(nodes, state) })
// reusable walker with a curried initial state...
// NOTE: better keep this immutable or clone this in get(..)
: arguments.length == 2 ?
: arguments.length == (done ? 3 : 2) ?
(func = function(...nodes){
return _walk(nodes, state) })
// walk...