From 90972e95e4e84ad8fbd521c00e9d679be801abea Mon Sep 17 00:00:00 2001 From: kitsunecafe Date: Fri, 22 Nov 2024 04:46:56 -0600 Subject: [PATCH] yeah this sucks --- src/fn.js | 45 +++++++++++++++++++++++------ src/query.js | 69 ++++++++++++++++++++------------------------- tests/query.test.js | 26 +++++++++++++---- 3 files changed, 87 insertions(+), 53 deletions(-) diff --git a/src/fn.js b/src/fn.js index ff6fe3b..7e6ab47 100644 --- a/src/fn.js +++ b/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 })) diff --git a/src/query.js b/src/query.js index fa965ae..d798bc5 100644 --- a/src/query.js +++ b/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) + ) +) diff --git a/tests/query.test.js b/tests/query.test.js index be2aaea..40941c1 100644 --- a/tests/query.test.js +++ b/tests/query.test.js @@ -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 } + ] + ) }) })