yeah this sucks

This commit is contained in:
Rowan 2024-11-22 04:46:56 -06:00
parent ceab51b145
commit 90972e95e4
3 changed files with 87 additions and 53 deletions

View file

@ -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 }))

View file

@ -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)
)
)

View file

@ -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 }
]) ]
)
}) })
}) })