UNPKG

fastener

Version:

Functional Zipper for manipulating JSON

199 lines (166 loc) 4.47 kB
import { assocPartialU, curry, dissocPartialU, id, isArray, isDefined, isNumber, isObject, isString } from 'infestines' // function firstKey(o) { for (const k in o) return k } function lastKey(o) { let key for (const k in o) key = k return key } // function reverse(from) { let to = null while (from) { to = from.length === 3 ? [to, from[1], from[2]] : [to, from[1]] from = from[0] } return to } // const zipper = (left, focus, key, right, up) => isDefined(up) ? { left, focus, key, right, up } : { left, focus, key, right } // function intoObject(list, object) { while (list) { object[list[2]] = list[1] list = list[0] } } function fromObject(object, key, up) { let left = null let right = null let focus for (const k in object) if (isDefined(focus)) right = [right, object[k], k] else if (key === k) focus = object[k] else left = [left, object[k], k] return zipper(left, focus, key, reverse(right), up) } // function intoArray(list, array) { while (list) { array.push(list[1]) list = list[0] } } function fromArray(array, key, up) { let left = null let right = null for (let i = 0; i < key; ++i) left = [left, array[i]] for (let i = array.length - 1; key < i; --i) right = [right, array[i]] return zipper(left, array[key], key, right, up) } // export const get = z => z.focus export const keyOf = z => z.key const setU = (focus, z) => assocPartialU('focus', focus, z) export const set = curry(setU) const modifyU = (f, z) => setU(f(get(z)), z) export const modify = curry(modifyU) export function up({ left, focus, key, right, up }) { switch (typeof key) { case 'number': { const array = [] intoArray(reverse(left), array) if (isDefined(focus)) array.push(focus) intoArray(right, array) return assocPartialU('focus', array, up) } case 'string': { const object = {} intoObject(reverse(left), object) if (isDefined(focus)) object[key] = focus intoObject(right, object) return assocPartialU('focus', object, up) } } } function downToU(key, z) { const focus = z.focus if (isObject(focus) && isString(key) && key in focus) return fromObject(focus, key, dissocPartialU('focus', z)) if (isArray(focus) && isNumber(key) && 0 <= key && key < focus.length) return fromArray(focus, key, dissocPartialU('focus', z)) } export const downTo = curry(downToU) export const downPath = curry((path, z) => { for (let i = 0, n = path.length; z && i < n; ++i) z = downToU(path[i], z) return z }) const downMost = head => z => { const focus = z.focus if (isObject(focus)) return downToU(head ? firstKey(focus) : lastKey(focus), z) if (isArray(focus)) return downToU(head ? 0 : focus.length - 1, z) } export const downHead = downMost(true) export const downLast = downMost(false) export const left = ({ left, focus, key, right, up }) => left ? isNumber(key) ? zipper(left[0], left[1], key - 1, [right, focus], up) : zipper(left[0], left[1], left[2], [right, focus, key], up) : void 0 export const right = ({ left, focus, key, right, up }) => right ? isNumber(key) ? zipper([left, focus], right[1], key + 1, right[0], up) : zipper([left, focus, key], right[1], right[2], right[0], up) : void 0 export function head(z) { const u = up(z) return u && downHead(u) } export function last(z) { const u = up(z) return u && downLast(u) } export const toZipper = focus => ({ focus }) export function fromZipper(z) { const u = up(z) return u ? fromZipper(u) : get(z) } function queryMoveU(move, b, f, z) { const m = move(z) return m ? f(m) : b } export const queryMove = curry(queryMoveU) function bwd(move, z) { switch (move) { case left: return right case right: return left case up: return downTo(keyOf(z)) default: return up } } const transformMoveU = (move, f, z) => queryMoveU(move, z, x => queryMoveU(bwd(move, z), z, id, f(x)), z) export const transformMove = curry(transformMoveU) const everywhereG = f => z => transformMoveU(right, everywhereG(f), everywhereU(f, z)) const everywhereU = (f, z) => modifyU(f, transformMoveU(downHead, everywhereG(f), z)) export const everywhere = curry(everywhereU) export function pathOf(z) { const path = [] while (z && isDefined(z.key)) { path.push(z.key) z = z.up } return path.reverse() }