From 61b57bca2a8284cce7b878db45473179b4d30c5c Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Fri, 28 Sep 2018 04:25:44 +0300 Subject: [PATCH] added stop(..) to walk(..) + examples... Signed-off-by: Alex A. Naanou --- diff.js | 205 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 146 insertions(+), 59 deletions(-) diff --git a/diff.js b/diff.js index e913f7b..7567724 100644 --- a/diff.js +++ b/diff.js @@ -263,17 +263,23 @@ var proxy = function(path, func){ } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // // Construct a reusable walker function... // walk(get(..)) -// -> func(start, ...nodes) +// -> func(state, ...nodes) +// +// Construct a reusable walker function with set initial state... +// walk(get(..), state) +// -> func(...nodes) // // Walk the nodes... -// walk(get(..), start, ...nodes) +// walk(get(..), state, ...nodes) // -> res // // The get(..) that walk(..) expects has the following signature: -// get(res, node, next(..), down(..)) +// get(res, node, next(..), down(..), stop(..)) // -> res // // Queue next nodes to walk... @@ -286,41 +292,90 @@ var proxy = function(path, func){ // state. // // Walk the next set of nodes... -// down(start, ...nodes) +// down(state, ...nodes) // -> res // NOTE: this will process all the nodes and then return the // result. // +// Stop the walk... +// stop() +// stop(result) +// -> undefined +// NOTE: this will break current function execution in a similar +// effect to 'return', i.e. no code in function will be +// executed after stop(..) call... +// NOTE: this is done by throwing a special error, this error +// should either not be caught or must be re-thrown, as +// shadowing this exception may have unexpected results... +// // // Example: // // sum all the values of a nested array... // var sum = walk(function(res, node, next){ -// // go into arrays... -// if(node instanceof Array){ -// next(...node) -// -// // sum the values... -// } else { -// console.log(node) -// res += node -// } -// return res -// }) +// return node instanceof Array ? +// // compensate for that next(..) returns undefined... +// next(...node) +// || res +// : res + node }, 0) // // // depth first walker... -// var sumd = walk(function(res, node, next, down){ -// if(node instanceof Array){ -// res = down(res, ...node) +// var sumd = walk(function(res, node, next, down, stop){ +// return node instanceof Array ? +// down(res, ...node) +// : res + node }, 0) // -// } else { -// console.log(node) -// res += node -// } -// return res -// }) +// sum([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 3, 2, 4, 5 +// sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5 +// +// +// Example: +// // XXX res/path threading seem unnatural here... +// var __firstZero = walk(function(res, node, next, down, stop){ +// var k = node[0] +// var v = node[1] +// var path = res[0] +// return v === 0 ? +// // NOTE: we .slice(1) here to remove the initial null +// // we added in firstZero(..)... +// stop([ path.slice(1).concat([k]) ]) +// : v instanceof Object? +// down( +// [path.concat([k]), null], +// ...Object.entries(v)) +// : res }, [[], null]) +// var firstZero = function(value){ +// // NOTE: we are wrapping the input here to make it the same +// // format as that of Object.entries(..) items... +// return __firstZero([null, value]).pop() } +// +// +// // same as the above but illustrates a different strategy, a bit +// // cleaner but creates a walker every time it's called... +// var firstZero = function(value){ +// return walk( +// function(res, node, next, down, stop){ +// var k = node[0] +// var v = node[1] +// var path = res[0] +// return v === 0 ? +// // NOTE: we .slice(1) here to remove the initial null +// // we added in firstZero(..)... +// stop([ path.slice(1).concat([k]) ]) +// : v instanceof Object? +// down( +// [path.concat([k]), null], +// ...Object.entries(v)) +// : res }, +// [[], null], +// // wrap the input to make it compatible with Object.entries(..) +// // items... +// [null, value]) +// // separate the result from path... +// .pop() } +// +// +// firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y'] // -// sum([1, [2], 3, [[4, 5]]]) // -> 15 and log 1, 3, 2, 4, 5 -// sumd([1, [2], 3, [[4, 5]]]) // -> 15 and log 1, 2, 3, 4, 5 // // // NOTE: a walker is returned iff walk(..) is passed a single argument. @@ -329,60 +384,92 @@ var proxy = function(path, func){ // walk(get)(start, ...nodes) // NOTE: walk goes breadth first... // -var walk = function(get, state, ...args){ - var _step = function(args, res){ +// XXX might be a good idea to move this into it's own module... +// generic-walk might be a good name... +var walk = function(get, state, ...nodes){ + // 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... + var WalkStopException + var err_res + + var _step = function(nodes, res){ // construct a comfortable env for the user and handle the // results... var _get = function(node){ var next = [] - res = get(res, node, + res = get( + res, + node, // breadth first step... - // next(...nodes) - // -> undefined - // NOTE: this is different to down(..) in that this will - // return immediately and no result is known at time - // of call thus undefined is returned... - // NOTE: this is different to down(..) in that the user - // indirectly gives this the state by returning it - // from the last get(..) called per level. + // next(...nodes) -> undefined function(...nodes){ next = nodes }, // depth first step... - // down(state, ..nodes) - // -> res - // NOTE: this is different to next as the user has control - // over what value to start with and will get the - // result they can handle... + // down(state, ..nodes) -> res function(res, ...nodes){ - return _step(nodes, res) }) + return _step(nodes, res) }, + // stop... + // stop() + // stop(value) + // + // NOTE: 'throw' is used to stop all handling, including + // the rest of the current level... + function(r){ + err_res = r + WalkStopException = new Error('WALK_STOP_EVENT') + throw WalkStopException + }) return next } - return args.length == 0 ? + + return nodes.length == 0 ? // no new nodes to walk... res // do the next level... // NOTE: see note below... ( ;) ) - : _step(args + : _step(nodes .map(_get) .reduce(function(next, e){ return e instanceof Array ? next.concat(e) - : next.push(e) }, []), res) } + : next.push(e) }, []), res) + } + // call _step(..) and handle WalkStopException... + var _walk = function(nodes, res){ + try{ + return _step(nodes, res) - return arguments.length == 1 ? + } catch(e){ + // handle the abort condition... + if(e === WalkStopException){ + return err_res + } + // something broke... + throw e + } + } + + return ( // return a reusable walker... - // 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: - // step(res, ...args.map(_get).reduce(...)) - // res (if immutable) will first get passed by value and - // then will get re-assigned in _get(..), thus step(..) will - // not see the updated version... - // This approach simply pushes the pass-by-value of res till - // after it gets updated by _get(..). - function(res, ...args){ - return _step(args, res) } + arguments.length == 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: + // step(res, ...nodes.map(_get).reduce(...)) + // res (if immutable) will first get passed by value and + // then will get re-assigned in _get(..), thus step(..) will + // not see the updated version... + // This approach simply pushes the pass-by-value of res till + // after it gets updated by _get(..). + function(res, ...nodes){ + return _walk(nodes, res) } + // reusable walker with a curried initial state... + // NOTE: better keep this immutable or clone this in get(..) + : arguments.length == 2 ? + function(...nodes){ + return _walk(nodes, state) } // walk... - : _step(args, res) + : _walk(nodes, state)) }