zippa
Version:
A Generic Zipper Library
221 lines (191 loc) • 6.81 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.onPost = exports.onPre = exports.visit = exports.POST = exports.PRE = undefined;
var _curry = require('ramda/src/curry');
var _curry2 = _interopRequireDefault(_curry);
var _has = require('ramda/src/has');
var _has2 = _interopRequireDefault(_has);
var _zipper = require('./zipper');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Pre-event identifier.
* @type {String}
* @default
* @constant
*/
var PRE = exports.PRE = 'PRE';
/**
* Post-event identifier.
* @type {String}
* @default
* @constant
*/
var POST = exports.POST = 'POST';
function visitItem(event, initialItem, initialState, visitors) {
var _item = initialItem;
var _state = initialState;
var _stop = false;
var _cut = false;
var i = 0;
for (; i < visitors.length; i++) {
var visitor = visitors[i];
var res = visitor(event, _item, _state) || {};
var item = res.item;
var state = res.state;
var stop = res.stop;
var cut = res.cut;
if ((0, _has2.default)('item', res)) _item = item;
if ((0, _has2.default)('state', res)) _state = state;
if (stop || cut) {
_stop = stop;
_cut = cut;
break;
}
}
return { item: _item, state: _state, stop: _stop, cut: _cut };
}
function visitLocation(event, zipper, _state, visitors) {
var res = visitItem(event, (0, _zipper.value)(zipper), _state, visitors) || {};
var item = res.item;
var state = res.state;
var stop = res.stop;
var cut = res.cut;
return {
// Will not do anything if ctx.item === value(item)
loc: res.hasOwnProperty('item') ? (0, _zipper.replace)(item, zipper) : zipper,
state: res.hasOwnProperty('state') ? state : _state,
stop: stop,
cut: cut
};
}
var DOWN = 'DOWN';
var RIGHT = 'RIGHT';
function finishVisit(loc, state) {
return { item: (0, _zipper.value)((0, _zipper.root)(loc)), state: state };
}
/**
* Visits the data structure in depth-first order, calling
* one or more visitors on each node, on pre and post events.
*
* The visitor functions are called with three arguments:
* - `event` - {@link PRE} or {@link POST}
* - `item` - the item currently being visited
* - `state` - the state of the visit
*
* If you're visiting for side-effects, you don't have to return
* anything from your visitor function. To edit items, visit state,
* or stop the visit, you must return an object which can have
* zero or more of the following keys:
*
* - `item`: if supplied, will replace the item at the current location
* with the supplied value. The data structure won't change if the value
* has the same reference as the current value.
* - `state`: if supplied, will update the state of the visit with the
* value. State is shared with all visitors.
* - `stop`: if truthy, will stop the visit
* - `cut`: if truthy, will skip the subtree of the current node.
*
* Returns an object with the following keys:
*
* - `item`: the item value in the zipper after visiting the whole data structure.
* - `zipper`: the zipper value after visiting the whole data structure.
* - `state`: the state at the end of the visit.
*
* @param {Function[]} visitors - Array of visitor functions
* @param {*} [initialState] - Initial state for the visit
* @param {Zipper} initialZipper - A Zipper value to visit
* @return {Object}
*/
var visit = exports.visit = (0, _curry2.default)(function visit(visitors, initialState, initialZipper) {
var direction = DOWN;
var z = initialZipper;
var state = initialState;
var isFirst = true;
while (isFirst || (0, _zipper.isNotTop)(z)) {
isFirst = false;
if (direction === DOWN) {
// Going down, which means we start visiting this subtree.
// Apply pre-visitor to the current location.
var _res = visitLocation(PRE, z, state, visitors);
state = _res.state;
z = _res.loc;
if (_res.stop) return finishVisit(z, state);
if (!_res.cut && (0, _zipper.canGoDown)(z)) {
z = (0, _zipper.down)(z);
} else {
// Can't go down, so on next iteration
// we try to go right.
direction = RIGHT;
}
} else if (direction === RIGHT) {
// Going right, which means we've visited the whole
// subtree of the current item. Applying the post-visitor.
var _res2 = visitLocation(POST, z, state, visitors);
state = _res2.state;
z = _res2.loc;
if (_res2.stop) return finishVisit(z, state);
if ((0, _zipper.canGoRight)(z)) {
z = (0, _zipper.right)(z);
direction = DOWN;
} else {
// Can't go right, therefore we go up.
// For the next loop iteration, we want to try
// going right as well to stay in depth-first search
// order.
z = (0, _zipper.up)(z);
direction = RIGHT;
}
}
}
// We are back at the root, but that still needs the post-visitor.
var res = visitLocation(POST, z, state, visitors);
state = res.state;
z = res.loc;
return {
item: (0, _zipper.value)(z),
zipper: z,
state: state
};
});
var onEvent = (0, _curry2.default)(function (matchEvent, fn) {
return function (event, item, state) {
return event === matchEvent ? fn(item, state) : undefined;
};
});
/**
* Takes a visitor function that takes an `item` and `state` argument,
* and returns a visitor function that is only invoked on the pre-event.
*
* Equal to:
*
* ```javascript
* const myVisitor = (item, state) => console.log('visited item', item);
* function visitor(event, item, state) {
* if (event === PRE) return myVisitor(item, state);
* }
* ```
*
* @param {Function} fn - visitor function that takes `item` and `state` arguments
* @returns {Function} visitor function
*/
var onPre = exports.onPre = onEvent(PRE);
/**
* Takes a visitor function that takes an `item` and `state` argument,
* and returns a visitor function that is only invoked on the post-event.
*
* Equal to:
*
* ```javascript
*
* const myVisitor = (item, state) => console.log('visited item', item);
* function visitor(event, item, state) {
* if (event === POST) return myVisitor(item, state);
* }
* ```
*
* @param {Function} fn - visitor function that takes `item` and `state` arguments
* @returns {Function} visitor function
*/
var onPost = exports.onPost = onEvent(POST);