sorry pigments
This commit is contained in:
parent
bd0065c635
commit
8ec0d942c9
5 changed files with 370 additions and 46 deletions
42
src/fn.js
Normal file
42
src/fn.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { Result } from './result.js'
|
||||
|
||||
export function curry(func) {
|
||||
return function curried(...args) {
|
||||
if (args.length >= func.length) {
|
||||
return func.apply(this, args)
|
||||
} else {
|
||||
return function(...args2) {
|
||||
return curried.apply(this, args.concat(args2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 of = value => Array.isArray(value) ? value : [value]
|
||||
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 pipe = (...f) => v => f.reduce(apply, v)
|
||||
|
||||
export const identity = x => x
|
||||
|
||||
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
|
||||
|
||||
export const take = curry((n, value) => value.slice(0, n))
|
||||
export const drop = curry((n, value) => value.slice(n))
|
||||
|
||||
export const is = curry((type, value) => value instanceof type)
|
||||
|
161
src/parser.js
Normal file
161
src/parser.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
import { Result } from './result.js'
|
||||
import { Iterator, Peekable, Stream } from './stream.js'
|
||||
import { curry, is, of, tryPipe } from './fn.js'
|
||||
|
||||
class ParseError extends Error {
|
||||
constructor(message, state, source) {
|
||||
super(message)
|
||||
this.state = state
|
||||
this.source = source
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef Iterator
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {[T[], Stream]} ParserState
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Peekable
|
||||
* @property {Iterator} iterator
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T, E
|
||||
* @typedef {Object} Result
|
||||
* @property {T} value
|
||||
* @property {E} error
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a ParserState from input
|
||||
*
|
||||
* @template T
|
||||
* @param {T[]} value - Any iterator or value which can become an iterator
|
||||
* @return {ParserState} - A new ParserState
|
||||
*/
|
||||
const ParserState = value => ([[], new Stream(value)])
|
||||
|
||||
/**
|
||||
* Create a Peekable from an existing ParserState
|
||||
* @param {ParserState}
|
||||
* @return {Peekable}
|
||||
*/
|
||||
const peekable = ([a, b]) => ([a, new Peekable(b)])
|
||||
|
||||
/**
|
||||
* Update ParserState with parsed tokens
|
||||
*
|
||||
* @template T
|
||||
* @param {T[]} - parsed tokens
|
||||
* @param {ParserState}
|
||||
* @return {Result} - Result.Ok with updated ParserState
|
||||
*/
|
||||
const succeed = curry((v, [p, rest]) => Result.Ok([p.concat(of(v)), rest]))
|
||||
|
||||
/**
|
||||
* Indicate parser failure
|
||||
*
|
||||
* @template E
|
||||
* @param {string} - error message
|
||||
* @param {ParserState}
|
||||
* @param {E=} error
|
||||
* @return {Result} - Error with ParserState information and error source
|
||||
*/
|
||||
const fail = curry((msg, state, err = undefined) => Result.Err(new ParseError(msg, state, err)))
|
||||
|
||||
const next = ([, rest]) => rest.next()
|
||||
const tokenize = str => str.split('')
|
||||
const tokenizeInto = curry((fn, str) => tokenize(str).map(v => fn(v)))
|
||||
|
||||
const LowerAlpha = 'abcdefghijklmnopqrstuvwxyz'
|
||||
const UpperAlpha = LowerAlpha.toUpperCase()
|
||||
const Alpha = LowerAlpha + UpperAlpha
|
||||
const Digits = '1234567890'
|
||||
const Alphanumeric = Alpha + Digits
|
||||
|
||||
export const seq = (...parsers) => state => {
|
||||
let acc = Result.Ok(state)
|
||||
for (const parser of parsers) {
|
||||
if (acc.isOk()) {
|
||||
acc = parser(acc.value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
export const any = (...parsers) => (state) => {
|
||||
const peekState = peekable(state)
|
||||
for (const parser of parsers) {
|
||||
const result = parser(peekState)
|
||||
if (result.isOk()) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return fail('no matching parsers', state)
|
||||
}
|
||||
|
||||
export const map = curry((fn, parser, state) => {
|
||||
const result = parser(state)
|
||||
|
||||
if (result.isOk()) {
|
||||
const [parsed] = state
|
||||
const [, stream] = result.value
|
||||
const backtrack = [parsed, stream]
|
||||
|
||||
try {
|
||||
return succeed(fn(result.value, backtrack))
|
||||
} catch (e) {
|
||||
return fail('map failed', state, e)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
|
||||
|
||||
export const char = curry((ch, state) => {
|
||||
return next(state) === ch ? succeed(ch, state) : fail(`could not parse ${ch}`, state)
|
||||
})
|
||||
|
||||
export const noCaseChar = curry((ch, state) => (
|
||||
any(char(ch.toLowerCase()), char(ch.toUpperCase()))(state)
|
||||
))
|
||||
|
||||
export const string = curry((str, state) =>
|
||||
seq(...tokenizeInto(char, str))(state)
|
||||
)
|
||||
|
||||
export const anyChar = curry((str, state) =>
|
||||
any(...tokenizeInto(char, str))(state)
|
||||
)
|
||||
|
||||
export const digit = anyChar(Digits)
|
||||
export const lowerAlpha = anyChar(LowerAlpha)
|
||||
export const upperAlpha = anyChar(UpperAlpha)
|
||||
export const alpha = anyChar(Alpha)
|
||||
export const alphanumeric = anyChar(Alphanumeric)
|
||||
|
||||
export const noCaseString = curry((str, state) => (
|
||||
seq(...tokenizeInto(noCaseChar, str))(state)
|
||||
))
|
||||
|
||||
export const maybe = curry((parser, state) => {
|
||||
const result = parser(state)
|
||||
return result.isOk() ? result : succeed([], state)
|
||||
})
|
||||
|
||||
export const eof = (state) => {
|
||||
return rest[1].done() ? succeed([], state) : fail('eof did not match', state)
|
||||
}
|
||||
|
||||
export const parse = curry((parser, input) => parser(ParserState(input)))
|
||||
|
49
src/query-old.js
Normal file
49
src/query-old.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
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
|
||||
}
|
||||
}
|
49
src/query.js
49
src/query.js
|
@ -1,49 +1,6 @@
|
|||
import { defineQuery, hasComponent } from 'bitecs'
|
||||
/// danm this rules !!!
|
||||
const re = /\((?<fromName>\w+)?:(?<fromLabel>\w+)\)(-\[(?<edgeName>\w+)?:(?<edgeLabel>\w+)\]->\((?<toName>\w+)?:(?<toLabel>\w+)\))*/
|
||||
import { } from './parser.js'
|
||||
|
||||
const node = (name, label) => ({ name, label })
|
||||
// MATCH (a:Label)-[e:Label]->(b:Label)
|
||||
// RETURN a, b
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
115
src/stream.js
Normal file
115
src/stream.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
export class NotImplementedError extends Error {
|
||||
constructor(msg) {
|
||||
super(msg)
|
||||
}
|
||||
}
|
||||
|
||||
export class Iterator {
|
||||
static handledTypes = new Map()
|
||||
|
||||
static from(value) {
|
||||
if (!value) { return }
|
||||
const types = Iterator.handledTypes
|
||||
const ctr = value.constructor || value.constructor
|
||||
|
||||
if (types.has(ctr)) {
|
||||
const handler = types.get(ctr)
|
||||
return new handler(value)
|
||||
} else {
|
||||
throw new NotImplementedError(`${value} is not an iterator`)
|
||||
}
|
||||
}
|
||||
|
||||
next() {
|
||||
throw new NotImplementedError('Iterator::next not implemented')
|
||||
}
|
||||
|
||||
peek() {
|
||||
throw new NotImplementedError('Iterator::peek not implemented')
|
||||
}
|
||||
|
||||
done() {
|
||||
return !!this.peek()
|
||||
}
|
||||
|
||||
take(n) {
|
||||
let accumulator = []
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
accumulator.push(this.next())
|
||||
}
|
||||
|
||||
return accumulator
|
||||
}
|
||||
|
||||
drop(n) {
|
||||
this.take(n)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export class Peekable extends Iterator {
|
||||
constructor(iterator) {
|
||||
super()
|
||||
this.iterator = iterator
|
||||
}
|
||||
|
||||
next() {
|
||||
return this.iterator.peek()
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this.iterator.peek()
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIterator extends Iterator {
|
||||
static { Iterator.handledTypes.set(Array, ArrayIterator) }
|
||||
|
||||
constructor(value) {
|
||||
super()
|
||||
this.value = value
|
||||
}
|
||||
|
||||
next() {
|
||||
if (this.value.length > 0) {
|
||||
return this.value.shift()
|
||||
}
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this.value.length > 0 ? this.value[0] : undefined
|
||||
}
|
||||
}
|
||||
|
||||
class StringIterator extends ArrayIterator {
|
||||
static { Iterator.handledTypes.set(String, StringIterator) }
|
||||
|
||||
constructor(value) {
|
||||
super(value.split(''))
|
||||
}
|
||||
}
|
||||
|
||||
export class Stream extends Iterator {
|
||||
constructor(value) {
|
||||
super()
|
||||
if (!value.next || !typeof value.next === 'function') {
|
||||
this.iterator = Iterator.from(value)
|
||||
|
||||
if (!value) {
|
||||
throw new Error(`${value} is not an iterator`)
|
||||
}
|
||||
} else {
|
||||
this.iterator = value
|
||||
}
|
||||
}
|
||||
|
||||
next() {
|
||||
return this.iterator.next()
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this.iterator.peek()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue