Compare commits

..

3 commits

4 changed files with 141 additions and 116 deletions

109
src/fn.js
View file

@ -1,8 +1,11 @@
import { Result } from './result.js'
export function curry(func) {
export const identity = x => x
// functions
export function curryN(arity, func) {
return function curried(...args) {
if (args.length >= func.length) {
if (args.length >= arity) {
return func.apply(this, args)
} else {
return function(...args2) {
@ -12,41 +15,97 @@ export function curry(func) {
}
}
export const curry = fn => curryN(fn.length, fn)
export const flip = fn => curry((a, b) => fn(b, a))
export const tap = curry((fn, val) => {
fn(val)
return val
})
// predicates
export const and = (...booleans) => booleans.every(identity)
export const or = (...booleans) => booleans.some(identity)
export const isOk = r => !is(Result, r) || r.isOk()
export const always = x => () => x
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))
export const useWith = curry((fn, tfns) =>
curryN(
tfns.length,
(...args) => pipe(enumerate, map(([i, a]) => tfns[i](a)), fn)(args)
))
export const construct = Type => args => new Type(...args)
export const converge = (fn, [head, ...rest]) => curryN(
head.length,
(...args) => pipe(...rest, fn)(head(...args))
)
export const of = value => Array.isArray(value) ? value : [value]
export const concat = curry((a, b) => b.concat(a))
export const head = value => value[0]
export const tail = value => value.slice(1)
export const prop = curry((p, v) => v[p])
export const apply = curry((v, f) => f(v))
export const reduce = curry((fn, init, v) => v.reduce(fn, init))
export const map = curry((fn, v) => v.map(fn))
export const filter = curry((fn, v) => v.filter(fn))
export const find = curry((fn, v) => v.find(fn))
export const join = curry((sep, v) => v.join(sep))
export const rev = v => v.slice().reverse()
export const pipe = (...f) => v => f.reduce(apply, v)
export const prepend = curry((value, iter) => [value, ...iter])
export const append = curry((value, iter) => [...iter, value])
export const diverge = (fn, tfns) => pipe(applyTo, flip(map)(tfns), apply(fn))
export const identity = x => x
// types
export const isArray = Array.isArray
export const isNil = v => !v
export const is = curry((type, value) => value instanceof type)
// branching
export const ifElse = curry((p, ft, ff, v) => p(v) ? ft(v) : ff(v))
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 isNil = v => !v
// predicates
export const and = (...booleans) => booleans.every(identity)
export const or = (...booleans) => booleans.some(identity)
export const isOk = value => !is(Result, value) || value.isOk()
// classes
export const construct = Type => args => new Type(...args)
// strings
export const split = curry((delim, v) => v.split(delim))
// arrays
export const length = v => v.length
export const len = length
export const of = unless(isArray, value => ([value]))
export const concat = curry((a, b) => b.concat(a))
export const take = curry((n, value) => value.slice(0, n))
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 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))
export const join = curry((sep, v) => v.join(sep))
export const rev = v => v.slice().reverse()
export const prepend = curry((value, iter) => [value, ...iter])
export const append = curry((value, iter) => [...iter, value])
export const enumerate = value => value.map((v, i) => ([i, v]))
export const flatten = curry((n, v) => v.flat(n))
export const update = curry((index, value, arr) => arr.toSpliced(index, 1, value))
export const is = curry((type, value) => value instanceof type)
// objects
const makePath = unless(isArray, split('.'))
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)
)(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 mergeLeft = curry((a, b) => ({ ...b, ...a }))
export const mergeRight = flip(mergeLeft)
export const keys = Object.keys
export const values = Object.values
export const entries = Object.entries
export const fromEntries = Object.fromEntries
// lenses
export const lens = curry((get, set) => ({ get, set }))
export const lensPath = path => lens(path(path), assocPath(path))
export const lensIndex = index => lens(nth(index), update(index))
export const view = curry(({ get }, obj) => get(obj))
export const set = curry((l, value, obj) => over(l, always(value), obj))
export const over = curry(({ get, set }, fn, obj) => set(fn(get(obj)), obj))

View file

@ -1,49 +0,0 @@
import { defineQuery, hasComponent } from 'bitecs'
/// danm this rules !!!
const re = /\((?<fromName>\w+)?:(?<fromLabel>\w+)\)(-\[(?<edgeName>\w+)?:(?<edgeLabel>\w+)\]->\((?<toName>\w+)?:(?<toLabel>\w+)\))*/
const node = (name, label) => ({ name, label })
const parse = s => {
const result = s.match(re)
const groups = result.groups
return {
from: node(groups.fromName || 'a', groups.fromLabel),
to: node(groups.toName || 'b', groups.toLabel),
edge: node(groups.edgeName, groups.edgeLabel)
}
}
export const query = (world, s) => {
const meta = parse(s)
const from = world.components[meta.from.label]
const to = world.components[meta.to.label]
const edge = world.components[meta.edge.label]
const fromQuery = defineQuery([from])
const toQuery = defineQuery([to])
const edgeQuery = defineQuery([edge])
return (world) => {
const result = []
const fq = fromQuery(world)
const tq = toQuery(world)
const eq = edgeQuery(world)
for(const feid of fq) {
const targets = []
for(const eeid of eq) {
if(edge.from[eeid] === feid && hasComponent(world, to, edge.to[eeid])) {
targets.push(edge.to[eeid])
}
}
result.push({ [feid]: targets })
}
return result
}
}

View file

@ -1,7 +1,7 @@
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 } from './fn.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 { Relationship } from './query-parser/types.js'
/*
@ -16,7 +16,7 @@ const prepareQuery = (query, component) => {
if (relationship) {
const { from: f, to: t, edge: e } = match
const [from, to, edge] = [f, t, e].map(q => prepareQuery({ match: q, returnValues }, component))
const [from, to, edge] = map(q => prepareQuery({ match: q, returnValues }, component), [f, t, e])
return { from, to, edge, relationship, schema: query }
} else {
@ -60,10 +60,11 @@ const queryRelationship = (from, edge, to, world) => {
}
const cat = flip(concat)
const assembleQuery = (query, entities) => map(entity => of({ entity, query }), entities)
const executeQuery = (query, world) => {
const executeQuery = curry((query, world) => {
if (!query.relationship) {
return map(id => ({ [id]: query }), query.query(world))
return assembleQuery(query, query.query(world))
}
const { from, to: _to, edge } = query
@ -84,53 +85,67 @@ const executeQuery = (query, world) => {
..._to,
from: {
...to,
query: () => of(Edge.to[eid])
query: always(of(Edge.to[eid]))
}
}
const results = map(cat([eid]), executeQuery(next, world))
const expanded = map(
([l, r]) => ({ [l]: query, ...r }),
results
)
return [...acc, ...expanded]
return pipe(
executeQuery(next),
map(cat([eid])),
map(([l, r]) => ([{ entity: l, query }, r])),
x => ([...acc, ...x]),
)(world)
}, [], edges)
} else {
return map(id => of({ [id]: query }), edges)
return assembleQuery(query, edges)
}
})
const returnValues = pipe(path('query.schema.returnValues.values'), map(prop('value')))
const includes = curry((val, arr) => !isNil(val) && arr.includes(val))
const maybeAssoc = curry((pred, key, value, obj) =>
when(pred, assoc(key, value), obj)
)
const resolveNode = curry((returns, name, entity, obj) =>
maybeAssoc(always(includes(name, returns)), name, entity, obj)
)
const resolveRelationship = edges => {
const { entity, query } = edges
const Edge = query.edge.type
const returns = returnValues(edges)
const queryProp = flip(prop)(query)
const [from, edge, to] = map(queryProp, ['from', 'edge', 'to'])
const [fn, en] = [from.name, edge.name]
const tn = ifElse(prop('relationship'),
path('from.name'),
prop('name'),
to
)
return pipe(
resolveNode(returns, fn, Edge.from[entity]),
resolveNode(returns, en, entity),
resolveNode(returns, tn, Edge.to[entity]),
)({})
}
const val = v => v?.value?.value
const include = returns => name => name != null && returns.includes(name)
const resolveReturns = results => {
return map(result => {
return reduce((acc, [id, query]) => {
const q = query.schema.match
const inc = include(query.schema.returnValues.values.map(x => x.value))
if (is(Relationship, q)) {
const Edge = query.edge.type
const fn = val(q.from.name)
const en = val(q.edge.name)
const tn = val(q.to.name)
return {
...acc, ...{
...(inc(fn) && { [fn]: Edge.from[id] }),
...(inc(en) && { [en]: parseInt(id, 10) }),
...(inc(tn) && { [tn]: Edge.to[id] })
}
}
} else {
const name = val(q.name)
return { ...acc, ...(inc(name) && { [name]: id }) }
}
}, {}, Object.entries(result))
}, results)
}
const resolveReturns = map(
pipe(
map(ifElse(
path('query.relationship'),
resolveRelationship,
diverge(resolveNode, [returnValues, path('query.schema.match.name.value.value'), 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)

View file

@ -1,11 +1,11 @@
import { before, describe, it } from 'node:test'
import { beforeEach, describe, it } from 'node:test'
import { addComponent, addEntity, createWorld, defineComponent, Types } from 'bitecs'
import assert from './assert.js'
import { query } from '../src/query.js'
let engine = {}
describe('query', () => {
before(() => {
beforeEach(() => {
const world = { default: createWorld() }
const component = {
Player: defineComponent(null, 10),