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...
|
An extensible tree walk(..) framework...
|
||||||
|
|
||||||
|
|
||||||
## Theory and operation
|
## 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`
|
||||||
`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,
|
- Iterates through `nodes` calling the `getter(..)` per node, threading the `state` through each call,
|
||||||
- Returns the `state` when there are no more `nodes`.
|
- Returns the `state` when there are no more `nodes`.
|
||||||
|
|
||||||
|
|
||||||
|
### The getter
|
||||||
|
|
||||||
`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,
|
- 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)`
|
- Can abbort *walking* and return a state via `stop()` or `stop(state)`
|
||||||
- Returns `state`,
|
- Returns `state`,
|
||||||
|
|
||||||
|
|
||||||
|
### Putting it all together
|
||||||
|
|
||||||
A trivial *flat* example...
|
A trivial *flat* example...
|
||||||
```javascript
|
```javascript
|
||||||
walk(function(r, n){ return r+n }, 0, ...[1,2,3]) // -> 6
|
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
|
[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*:
|
- The input is not *flat*:
|
||||||
```javascript
|
```javascript
|
||||||
|
// sum the items in a *deep* array (depth-first)...
|
||||||
var sum = walk(function(r, n){
|
var sum = walk(function(r, n){
|
||||||
return n instanceof Array ?
|
return n instanceof Array ?
|
||||||
down(r, ...n)
|
down(r, ...n)
|
||||||
@ -54,6 +64,7 @@ Target use-cases:
|
|||||||
|
|
||||||
sumr( [1, [2, 3], 4, [[5], 6]] ) // -> 21
|
sumr( [1, [2, 3], 4, [[5], 6]] ) // -> 21
|
||||||
```
|
```
|
||||||
|
|
||||||
- Need to abort the recursion prematurelly:
|
- Need to abort the recursion prematurelly:
|
||||||
```javascript
|
```javascript
|
||||||
// check if structure contains 0...
|
// check if structure contains 0...
|
||||||
@ -61,7 +72,8 @@ Target use-cases:
|
|||||||
// NOTE: we'll only count leaf nodes...
|
// NOTE: we'll only count leaf nodes...
|
||||||
this.nodes_visited = (this.nodes_visited || 0)
|
this.nodes_visited = (this.nodes_visited || 0)
|
||||||
return e === 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)
|
stop(this.nodes_visited+1)
|
||||||
: e instanceof Array ?
|
: e instanceof Array ?
|
||||||
// breadth-first walk...
|
// 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
|
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
|
```javascript
|
||||||
var sum = walk(function(res, node, next){
|
var sum = walk(function(res, node, next){
|
||||||
this.log(node)
|
this.log(node)
|
||||||
@ -161,11 +173,21 @@ var sumd = walk(function(res, node, next, down, stop){
|
|||||||
down(res, ...node)
|
down(res, ...node)
|
||||||
: res + node }, 0)
|
: res + node }, 0)
|
||||||
|
|
||||||
|
// define the path logger...
|
||||||
sum.prototype.log =
|
sum.prototype.log =
|
||||||
sumd.prototype.log =
|
sumd.prototype.log =
|
||||||
function(node){
|
function(node){
|
||||||
node instanceof Array
|
this.path = node instanceof Array ?
|
||||||
|| console.log('-->', node) }
|
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
|
sum([1, [2], 3, [[4, 5]]]) // -> 15
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "generic-walk",
|
"name": "generic-walk",
|
||||||
"version": "0.0.5",
|
"version": "1.0.0",
|
||||||
"description": "An extensible tree walk(..) framework...",
|
"description": "An extensible tree walk(..) framework...",
|
||||||
"main": "walk.js",
|
"main": "walk.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
60
walk.js
60
walk.js
@ -11,16 +11,45 @@
|
|||||||
// walk...
|
// walk...
|
||||||
// ...we can easily determine if this is the first call to getter(..)
|
// ...we can easily determine if this is the first call to getter(..)
|
||||||
// by checking and then setting an attr on the context...
|
// 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 =
|
var walk =
|
||||||
module.walk =
|
module.walk =
|
||||||
function(getter, state, ...nodes){
|
function(getter, state, ...nodes){
|
||||||
|
// holds the constructed walker function, used mainly to link its
|
||||||
|
// .prototype to the context of the getter(..)...
|
||||||
var func
|
var func
|
||||||
// this is used to break out of the recursion...
|
// this is used to break out of the recursion...
|
||||||
// NOTE: this can leak out but we only care about it's identity thus
|
// 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 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){
|
var _step = function(context, nodes, res){
|
||||||
// construct a comfortable env for the user and handle the
|
// construct a comfortable env for the user and handle the
|
||||||
// results...
|
// results...
|
||||||
@ -43,7 +72,7 @@ function(getter, state, ...nodes){
|
|||||||
// NOTE: 'throw' is used to stop all handling, including
|
// NOTE: 'throw' is used to stop all handling, including
|
||||||
// the rest of the current level...
|
// the rest of the current level...
|
||||||
function(r){
|
function(r){
|
||||||
err_res = r
|
stop_res = r
|
||||||
WalkStopException = new Error('WALK_STOP_EVENT')
|
WalkStopException = new Error('WALK_STOP_EVENT')
|
||||||
throw WalkStopException
|
throw WalkStopException
|
||||||
})
|
})
|
||||||
@ -65,21 +94,36 @@ function(getter, state, ...nodes){
|
|||||||
// _step(..) wrapper, handle WalkStopException and setup the initial
|
// _step(..) wrapper, handle WalkStopException and setup the initial
|
||||||
// context...
|
// context...
|
||||||
var _walk = function(nodes, res){
|
var _walk = function(nodes, res){
|
||||||
|
var context = func ?
|
||||||
|
Object.create(func.prototype)
|
||||||
|
: {}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
return _step(func ? Object.create(func.prototype) : {}, nodes, res)
|
var res = _step(context, nodes, res)
|
||||||
|
|
||||||
} catch(e){
|
} catch(e){
|
||||||
// handle the abort condition...
|
// handle the abort condition...
|
||||||
if(e === WalkStopException){
|
if(e === WalkStopException){
|
||||||
return err_res
|
var res = stop_res
|
||||||
}
|
|
||||||
// something broke...
|
// 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 (
|
||||||
// return a reusable walker...
|
// reusable walker...
|
||||||
arguments.length == 1 ?
|
arguments.length == 1 ?
|
||||||
// NOTE: this wrapper is here so as to isolate and re-order res
|
// NOTE: this wrapper is here so as to isolate and re-order res
|
||||||
// construction and passing it to the next level...
|
// construction and passing it to the next level...
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user