yeah this sucks
This commit is contained in:
parent
ceab51b145
commit
90972e95e4
3 changed files with 87 additions and 53 deletions
45
src/fn.js
45
src/fn.js
|
@ -23,6 +23,8 @@ export const tap = curry((fn, val) => {
|
|||
})
|
||||
|
||||
export const always = x => () => x
|
||||
export const orDefault = curry((value, defaultValue) => value || defaultValue)
|
||||
export const orElse = curry((value, fn) => orDefault(value, fn()))
|
||||
export const pipe = (...f) => v => reduce(applyTo, v, f)
|
||||
export const apply = fn => args => fn.apply(null, args)
|
||||
export const applyTo = curry((v, fn) => fn(v))
|
||||
|
@ -32,9 +34,9 @@ export const useWith = curry((fn, tfns) =>
|
|||
(...args) => pipe(enumerate, map(([i, a]) => tfns[i](a)), fn)(args)
|
||||
))
|
||||
|
||||
export const converge = (fn, [head, ...rest]) => curryN(
|
||||
head.length,
|
||||
(...args) => pipe(...rest, fn)(head(...args))
|
||||
export const converge = (fn, tfns) => curryN(
|
||||
max(map(length, tfns)),
|
||||
(...args) => fn(...map(tfn => tfn(...args), tfns))
|
||||
)
|
||||
|
||||
export const diverge = (fn, tfns) => pipe(applyTo, flip(map)(tfns), apply(fn))
|
||||
|
@ -50,8 +52,10 @@ export const when = curry((p, f, v) => ifElse(p, f, identity, v))
|
|||
export const unless = curry((p, f, v) => ifElse(p, identity, f, v))
|
||||
|
||||
// predicates
|
||||
export const and = (...booleans) => booleans.every(identity)
|
||||
export const or = (...booleans) => booleans.some(identity)
|
||||
export const every = curry((predicate, v) => v.every(predicate))
|
||||
export const some = curry((predicate, v) => v.some(predicate))
|
||||
export const all = curry((predicates, v) => every(applyTo(v), predicates))
|
||||
export const any = curry((predicates, v) => some(applyTo(v), predicates))
|
||||
export const isOk = value => !is(Result, value) || value.isOk()
|
||||
|
||||
// classes
|
||||
|
@ -60,7 +64,14 @@ export const construct = Type => args => new Type(...args)
|
|||
// strings
|
||||
export const split = curry((delim, v) => v.split(delim))
|
||||
|
||||
// math
|
||||
export const max = v => Math.max(...v)
|
||||
export const min = v => Math.min(...v)
|
||||
export const inc = v => v + 1
|
||||
export const dec = v => v + 1
|
||||
|
||||
// arrays
|
||||
export const map = curry((fn, v) => v.map(fn))
|
||||
export const length = v => v.length
|
||||
export const len = length
|
||||
export const of = unless(isArray, value => ([value]))
|
||||
|
@ -70,7 +81,7 @@ export const drop = curry((n, value) => value.slice(n))
|
|||
export const nth = curry((index, value) => value[index])
|
||||
export const head = nth(0)
|
||||
export const tail = drop(1)
|
||||
export const map = curry((fn, v) => v.map(fn))
|
||||
export const last = v => v && v[v.length - 1]
|
||||
export const filter = curry((fn, v) => v.filter(fn))
|
||||
export const reduce = curry((fn, init, v) => v.reduce(fn, init))
|
||||
export const find = curry((fn, v) => v.find(fn))
|
||||
|
@ -88,10 +99,28 @@ export const assoc = curry((key, value, obj) => ({ ...obj, [key]: value }))
|
|||
export const assocPath = curry((key, value, obj) => pipe(
|
||||
makePath,
|
||||
rev,
|
||||
reduce((acc, val) => fromEntries([[val, acc]]), value),
|
||||
mergeLeft(obj)
|
||||
reduce((acc, val) => ({ [val]: acc }), value),
|
||||
mergeRightDeep(obj),
|
||||
)(key))
|
||||
|
||||
export const mergeLeftDeep = curry((a, b) => {
|
||||
return reduce((acc, key) => {
|
||||
const av = acc[key]
|
||||
const bv = b[key]
|
||||
|
||||
return pipe(
|
||||
ifElse(
|
||||
every(is(Object)),
|
||||
apply(mergeLeftDeep),
|
||||
last
|
||||
),
|
||||
v => assoc(key, v, acc)
|
||||
)([av, bv])
|
||||
}, a, keys(b))
|
||||
})
|
||||
|
||||
export const mergeRightDeep = flip(mergeLeftDeep)
|
||||
|
||||
export const prop = curry((key, obj) => obj && key && obj[key])
|
||||
export const path = curry((key, obj) => reduce(flip(prop), obj, makePath(key)))
|
||||
export const mergeLeft = curry((a, b) => ({ ...b, ...a }))
|
||||
|
|
69
src/query.js
69
src/query.js
|
@ -1,15 +1,9 @@
|
|||
import { defineQuery, hasComponent } from 'bitecs'
|
||||
import { query as q } from './query-parser/index.js'
|
||||
import { parseAll } from './parser.js'
|
||||
import { curry, of, is, reduce, flip, concat, map, when, assoc, mergeLeft, isNil, path, prop, ifElse, diverge, pipe, curryN, always, tap } from './fn.js'
|
||||
import { curry, of, is, reduce, flip, concat, map, when, assoc, mergeLeft, isNil, path, prop, ifElse, diverge, pipe, always, assocPath, prepend, orDefault, useWith, applyTo } from './fn.js'
|
||||
import { Relationship } from './query-parser/types.js'
|
||||
|
||||
/*
|
||||
* necessary engine structure
|
||||
* worlds: World[]
|
||||
* Component: Component[]
|
||||
*/
|
||||
|
||||
const prepareQuery = (query, component) => {
|
||||
const { match, returnValues } = query
|
||||
const relationship = is(Relationship, match)
|
||||
|
@ -20,16 +14,14 @@ const prepareQuery = (query, component) => {
|
|||
|
||||
return { from, to, edge, relationship, schema: query }
|
||||
} else {
|
||||
const label = match.label?.value?.value
|
||||
const name = match.name?.value?.value
|
||||
const label = path('label.value.value', match)
|
||||
const name = path('name.value.value', match)
|
||||
const type = component[label]
|
||||
|
||||
return { name, label, type, relationship, query: defineQuery([type]), schema: query }
|
||||
}
|
||||
}
|
||||
|
||||
const getEndNode = end => end.relationship ? end.from : end
|
||||
|
||||
const matches = (world, type, query, set = new Set()) => entity => {
|
||||
if (set.has(entity)) {
|
||||
return true
|
||||
|
@ -59,7 +51,6 @@ const queryRelationship = (from, edge, to, world) => {
|
|||
return result
|
||||
}
|
||||
|
||||
const cat = flip(concat)
|
||||
const assembleQuery = (query, entities) => map(entity => of({ entity, query }), entities)
|
||||
|
||||
const executeQuery = curry((query, world) => {
|
||||
|
@ -68,32 +59,27 @@ const executeQuery = curry((query, world) => {
|
|||
}
|
||||
|
||||
const { from, to: _to, edge } = query
|
||||
const to = getEndNode(_to)
|
||||
|
||||
const Edge = edge.type
|
||||
|
||||
const to = when(prop('relationship'), prop('from'), _to)
|
||||
const edges = queryRelationship(
|
||||
{ type: from.type, results: from.query(world) },
|
||||
{ type: Edge, results: edge.query(world) },
|
||||
{ type: edge.type, results: edge.query(world) },
|
||||
{ type: to.type, results: to.query(world) },
|
||||
world
|
||||
)
|
||||
|
||||
if (_to.relationship) {
|
||||
return reduce((acc, eid) => {
|
||||
const next = {
|
||||
..._to,
|
||||
from: {
|
||||
...to,
|
||||
query: always(of(Edge.to[eid]))
|
||||
}
|
||||
}
|
||||
const next = assocPath(
|
||||
'from.query',
|
||||
always(of(edge.type.to[eid])),
|
||||
_to
|
||||
)
|
||||
|
||||
return pipe(
|
||||
executeQuery(next),
|
||||
map(cat([eid])),
|
||||
map(prepend(eid)),
|
||||
map(([l, r]) => ([{ entity: l, query }, r])),
|
||||
x => ([...acc, ...x]),
|
||||
concat(acc)
|
||||
)(world)
|
||||
}, [], edges)
|
||||
} else {
|
||||
|
@ -102,6 +88,7 @@ const executeQuery = curry((query, world) => {
|
|||
})
|
||||
|
||||
const returnValues = pipe(path('query.schema.returnValues.values'), map(prop('value')))
|
||||
const nodeName = path('query.schema.match.name.value.value')
|
||||
const includes = curry((val, arr) => !isNil(val) && arr.includes(val))
|
||||
|
||||
const maybeAssoc = curry((pred, key, value, obj) =>
|
||||
|
@ -126,10 +113,11 @@ const resolveRelationship = edges => {
|
|||
to
|
||||
)
|
||||
|
||||
const resolve = resolveNode(returns)
|
||||
return pipe(
|
||||
resolveNode(returns, fn, Edge.from[entity]),
|
||||
resolveNode(returns, en, entity),
|
||||
resolveNode(returns, tn, Edge.to[entity]),
|
||||
resolve(fn, Edge.from[entity]),
|
||||
resolve(en, entity),
|
||||
resolve(tn, Edge.to[entity]),
|
||||
)({})
|
||||
}
|
||||
|
||||
|
@ -138,19 +126,22 @@ const resolveReturns = map(
|
|||
map(ifElse(
|
||||
path('query.relationship'),
|
||||
resolveRelationship,
|
||||
diverge(resolveNode, [returnValues, path('query.schema.match.name.value.value'), prop('entity'), always({})])
|
||||
diverge(resolveNode, [returnValues, nodeName, prop('entity'), always({})])
|
||||
)),
|
||||
reduce(mergeLeft, {})
|
||||
)
|
||||
)
|
||||
|
||||
export const query = curry((input, engine) => {
|
||||
return parseAll(q, input).map(({ use, ...rest }) => {
|
||||
const worldName = use && use.value || 'default'
|
||||
const world = engine.world[worldName]
|
||||
const prepared = prepareQuery(rest, engine.component)
|
||||
const edges = executeQuery(prepared, world)
|
||||
return resolveReturns(edges)
|
||||
})
|
||||
})
|
||||
export const query = curry((input, { world, component }) =>
|
||||
map(({ use, ...rest }) =>
|
||||
pipe(
|
||||
prop('value'),
|
||||
orDefault('default'),
|
||||
flip(prop)(world),
|
||||
executeQuery(prepareQuery(rest, component)),
|
||||
resolveReturns
|
||||
)(use),
|
||||
parseAll(q, input)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -53,12 +53,26 @@ describe('query', () => {
|
|||
|
||||
knows(c, player) // 10
|
||||
|
||||
const q = 'MATCH (player:Player)-[e1:Knows]->(a:NPC)-[e2:Knows]->(b:NPC) RETURN player, e1, a, e2, b'
|
||||
const result = query(q, engine)
|
||||
assert.deepEqual(result.unwrap(), [
|
||||
{ player: 0, e1: 4, a: 1, e2: 7, b: 2 },
|
||||
{ player: 0, e1: 4, a: 1, e2: 8, b: 3 }
|
||||
])
|
||||
assert.deepEqual(
|
||||
query('MATCH (player:Player) RETURN player', engine).unwrap(),
|
||||
[{ player: 0 }]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (player:Player)-[e1:Knows]->(a:NPC) RETURN player, e1, a', engine).unwrap(),
|
||||
[
|
||||
{ player: 0, e1: 4, a: 1 },
|
||||
{ player: 0, e1: 5, a: 3 }
|
||||
]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (player:Player)-[e1:Knows]->(a:NPC)-[e2:Knows]->(b:NPC) RETURN player, e1, a, e2, b', engine).unwrap(),
|
||||
[
|
||||
{ player: 0, e1: 4, a: 1, e2: 7, b: 2 },
|
||||
{ player: 0, e1: 4, a: 1, e2: 8, b: 3 }
|
||||
]
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue