UNPKG

zippa

Version:
221 lines (191 loc) 6.81 kB
'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);