-
Notifications
You must be signed in to change notification settings - Fork 36
Approximating JSPath with Partial Lenses
JSPath is a DSL for querying JSON.
This is the beginnings of a translation of JSPath
queries to Partial Lenses
expressions. The translation is based on the description of the semantics in
the JSPath documentation and some
experiments in the REPL with JSPath. The rules are not (yet) complete and might
contain errors.
JSPath(path, data) ~ collectM(P"path", data)
A JSPath(path, data)
expression can be translated by translating the path
to
a monadic traversal, P"path"
and using a monadic collectM
function to
collect the results.
const CollectM = {
map: (xy, xM) => [xy(xM[0]), xM[1]],
ap: (xyM, xM) => [xyM[0](xM[0]), [xyM[1], xM[1]]],
of: v => [v, []],
chain: (xyM, xM) => {
const yM = xyM(xM[0])
return [yM[0], [xM[1], yM[1]]]
}
}
const collectM =
R.pipe(
L.traverse(CollectM, v => [v, {v}]),
L.collect([L.flatten, 'v'])
)
The reason for needing a monadic traversal is that the ..
operator of JSPath
targets both the focus itself and the children of the focus.
P"." ~ immediate
P".." ~ nested
P"name" ~ prop(name)
P"{predicate}" ~ L.when(F"predicate")
P"path1 ... pathN" ~ [P"path1", ..., P"pathN"]
P"path1 | ... | pathN" ~ L.seq(P"path1", ..., P"pathN")
For this reason the definition of nested
uses L.seq
, which requires a monad.
const immediate = L.ifElse(R.is(Array), L.elems, L.identity)
const nested = L.lazy(rec => L.cond(
[R.is(Array), [L.elems, rec]],
[R.is(Object), L.seq(L.identity, [L.values, rec])]
))
const prop = name => [L.when(R.has(name)), name]
Predicates are translated to functions that take the data at the focus as their parameter.
F"path" ~ collectM(P"path")
F"constant" ~ R.always(constant)
F"!predicate" ~ negate(F"predicate")
F"lhs && rhs" ~ andOp(F"lhs", F"rhs")
F"lhs || rhs" ~ orOp(F"lhs", F"rhs")
F"lhs cmpOp rhs" ~ cmpOp(F"lhs", C"cmpOp", F"rhs")
F"lhs arithOp rhs" ~ binOp(F"lhs", O"arithOp", F"rhs")
Comparison operators are promoted to operate on sets (arrays) of elements.
const promote x => R.is(Array, x) ? x : [x]
const cmpOp = (lhs, op, rhs) => data => {
const lhss = promote(lhs(data))
const rhss = promote(rhs(data))
return R.any(lhs => R.any(rhs => op(lhs, rhs), rhss), lhss)
}
Other binary operators aren't.
const binOp = (lhs, op, rhs) => data =>
op(lhs(data), rhs(data))
Logic operations are lazy.
const orOp = (lhs, rhs) => data =>
lhs(data) || rhs(data)
const andOp = (lhs, rhs) => data =>
lhs(data) && rhs(data)