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