From 1f6b2a0f69a88f18704778bc89a882b763c648ad Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Sun, 30 Sep 2018 01:48:59 +0300 Subject: [PATCH] added a walk done handler + updated docs... Signed-off-by: Alex A. Naanou --- README.md | 86 ++++++++++++++++++++++++++++++++++------------------ package.json | 2 +- walk.js | 70 ++++++++++++++++++++++-------------------- 3 files changed, 95 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index a1badb8..10a0f44 100644 --- a/README.md +++ b/README.md @@ -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){ - this.log(node) - return node instanceof Array ? - next(mode == 'breadth-first' ? 'queue' : 'do', res, ...node) - : res + node }, 0) } + var walker = walk( + function(res, node, next){ + this.log(node) + return node instanceof Array ? + 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 diff --git a/package.json b/package.json index b52ff09..227616a 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/walk.js b/walk.js index 52b6e82..6d31612 100644 --- a/walk.js +++ b/walk.js @@ -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...