sorry pigments

This commit is contained in:
Rowan 2024-11-09 02:22:37 -06:00
parent bd0065c635
commit 8ec0d942c9
5 changed files with 370 additions and 46 deletions

42
src/fn.js Normal file
View 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
View 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
View 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
}
}

View file

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