392 lines
7.9 KiB
JavaScript
392 lines
7.9 KiB
JavaScript
import { curry, is, last, mergeRightDeep, path } from '../fn.js'
|
|
import { Stream } from '../stream.js'
|
|
|
|
class Value {
|
|
value
|
|
|
|
constructor(value) {
|
|
this.value = value
|
|
}
|
|
|
|
toString() {
|
|
return this.value.toString()
|
|
}
|
|
|
|
valueOf() {
|
|
return this.value?.valueOf() ?? this.value
|
|
}
|
|
|
|
equals(other) {
|
|
if (other == null) {
|
|
return false
|
|
}
|
|
|
|
const value = this.valueOf()
|
|
return value === other
|
|
|| value === other.valueOf()
|
|
}
|
|
}
|
|
|
|
export class Identifier extends Value {
|
|
constructor(value) {
|
|
super(value)
|
|
}
|
|
|
|
from(node) {
|
|
return node[this.valueOf()]
|
|
}
|
|
|
|
pluck(node) {
|
|
const key = this.valueOf()
|
|
return { [key]: node[key] }
|
|
}
|
|
}
|
|
|
|
export class Property extends Identifier {
|
|
constructor(value) {
|
|
super(value)
|
|
}
|
|
|
|
pluck(node) {
|
|
return node[this.valueOf()]
|
|
}
|
|
}
|
|
|
|
export class Name extends Identifier {
|
|
constructor(value) {
|
|
super(value)
|
|
}
|
|
}
|
|
|
|
export class Label extends Identifier {
|
|
constructor(value) {
|
|
super(value)
|
|
}
|
|
}
|
|
|
|
export class SelectedGraph extends Identifier {
|
|
constructor(value) {
|
|
super(value)
|
|
}
|
|
}
|
|
|
|
export class Literal {
|
|
constructor(value) {
|
|
this.value = value
|
|
}
|
|
|
|
from(node) {
|
|
return this.valueOf()
|
|
}
|
|
|
|
valueOf() {
|
|
return this.value.valueOf()
|
|
}
|
|
}
|
|
|
|
export const None = new Literal(null)
|
|
|
|
export class Function {
|
|
#fn
|
|
|
|
constructor(name, fn, length = fn.length) {
|
|
this.name = name
|
|
this.#fn = fn
|
|
this.length = length
|
|
}
|
|
|
|
#pop(stack) {
|
|
const result = []
|
|
let n = Math.min(this.length, stack.length)
|
|
|
|
while(n > 0) {
|
|
result.push(stack.pop())
|
|
n--
|
|
}
|
|
|
|
result.reverse()
|
|
return result
|
|
}
|
|
|
|
apply(stack) {
|
|
const args = this.#pop(stack)
|
|
return this.#fn.apply(undefined, args)
|
|
}
|
|
}
|
|
|
|
const Associativity = Object.freeze({
|
|
Left: 0,
|
|
Right: 1
|
|
})
|
|
|
|
export class Operator extends Function {
|
|
constructor(name, fn, priority, associativity = Associativity.Left, length = fn.length) {
|
|
super(name, fn, length)
|
|
this.precedence = priority
|
|
this.associativity = associativity
|
|
}
|
|
|
|
compare(other) {
|
|
if (other.precedence < this.precedence) {
|
|
return 1
|
|
} else if (other.precedence === this.precedence) {
|
|
if (this.associativity === Associativity.Left) {
|
|
return 1
|
|
} else {
|
|
return 0
|
|
}
|
|
} else {
|
|
return -1
|
|
}
|
|
}
|
|
}
|
|
|
|
export const Is = new Operator('IS', (x, y) => typeof x === y, 2, Associativity.Right)
|
|
export const Not = new Operator('NOT', x => !x, 2, Associativity.Right)
|
|
export const And = new Operator('AND', (x, y) => x && y, 11)
|
|
export const Or = new Operator('OR', (x, y) => x || y, 12)
|
|
export const Xor = new Operator('XOR', (x, y) => x != y, 14)
|
|
export const GreaterThan = new Operator('GT', (x, y) => x > y, 6)
|
|
export const GreaterThanEqual = new Operator('GTE', (x, y) => x >= y, 6)
|
|
export const LesserThan = new Operator('LT', (x, y) => x < y, 6)
|
|
export const LesserThanEqual = new Operator('LTE', (x, y) => x <= y, 6)
|
|
export const Equal = new Operator('EQ', (x, y) => x === y, 7)
|
|
|
|
export class Filter {
|
|
constructor(...args) {
|
|
this.values = this.#shuntingYard(args)
|
|
}
|
|
|
|
#takeWhile(fn, stack) {
|
|
const result = []
|
|
let val = last(stack)
|
|
|
|
while (stack.length > 0 && fn(val)) {
|
|
result.push(stack.pop())
|
|
val = last(stack)
|
|
}
|
|
return result
|
|
}
|
|
|
|
#takeUntil(fn, stack) {
|
|
return this.#takeWhile(x => !fn(x), stack)
|
|
}
|
|
|
|
#shuntingYard(args) {
|
|
const stream = new Stream(args)
|
|
const output = []
|
|
const operators = []
|
|
const valueOf = curry((x, y) => y.valueOf() === x)
|
|
const leftParen = valueOf('(')
|
|
const comma = valueOf(',')
|
|
const rightParen = valueOf(')')
|
|
|
|
while (!stream.done()) {
|
|
const next = stream.next()
|
|
if (is(Function, next)) {
|
|
if (is(Operator, next)) {
|
|
const ops = this.#takeWhile(op => !leftParen(op) && next.compare(op) > 0, operators)
|
|
output.push.apply(output, ops)
|
|
}
|
|
|
|
operators.push(next)
|
|
} else if (comma(next)) {
|
|
output.push.apply(output, this.#takeUntil(leftParen, operators))
|
|
} else if (leftParen(next)) {
|
|
operators.push(next)
|
|
} else if (rightParen(next)) {
|
|
const ops = this.#takeUntil(leftParen, operators)
|
|
output.push.apply(output, ops)
|
|
|
|
if (!leftParen(operators.pop())) {
|
|
throw new SyntaxError('mismatched parenthesis')
|
|
}
|
|
|
|
if (is(Function, last(operators))) {
|
|
output.push(operators.pop())
|
|
}
|
|
} else {
|
|
output.push(next)
|
|
}
|
|
}
|
|
|
|
const rest = this.#takeUntil(leftParen, operators)
|
|
output.push.apply(output, rest)
|
|
|
|
if (operators.length > 0) {
|
|
throw new SyntaxError('mismatched parenthesis')
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
passes(nodes) {
|
|
return nodes.every(node => {
|
|
const values = this.values.map(v => v.from ? v.from(node.values) : v)
|
|
const stream = new Stream(values)
|
|
const stack = []
|
|
while (!stream.done()) {
|
|
const next = stream.next()
|
|
if (is(Operator, next)) {
|
|
const result = next.apply(stack)
|
|
stack.push(result)
|
|
} else {
|
|
stack.push(next)
|
|
}
|
|
}
|
|
|
|
return stack[0]
|
|
})
|
|
}
|
|
}
|
|
|
|
export class Alias extends Identifier {
|
|
constructor(value, alias) {
|
|
super(value)
|
|
this.alias = alias
|
|
}
|
|
|
|
pluck(node) {
|
|
return { [this.alias.valueOf()]: this.value.from(node) }
|
|
}
|
|
}
|
|
|
|
export class ObjectPath extends Identifier {
|
|
constructor(value, ...path) {
|
|
super(value)
|
|
this.path = path
|
|
}
|
|
|
|
toString() {
|
|
return [this.valueOf()].concat(this.path).join('.')
|
|
}
|
|
|
|
from(node) {
|
|
return path(this.toString(), node)
|
|
}
|
|
|
|
pluck(node) {
|
|
const values = this.path.reduce((acc, v) => ({ [v.valueOf()]: v.pluck(acc) }), node[this.valueOf()])
|
|
return { [this.valueOf()]: values }
|
|
}
|
|
|
|
equals(value) {
|
|
if (is(ObjectPath, value)) {
|
|
return value != null
|
|
&& this.path.length === value.path.length
|
|
&& super.equals(value)
|
|
&& this.path.every((x, i) => x === value.path[i])
|
|
} else {
|
|
return super.equals(value)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class GraphObject {
|
|
constructor(name, label, properties = []) {
|
|
this.name = name
|
|
this.label = label
|
|
this.properties = properties
|
|
}
|
|
}
|
|
|
|
export class Component extends GraphObject {
|
|
constructor(name, label, properties) {
|
|
super(name, label, properties)
|
|
}
|
|
}
|
|
|
|
export class Node {
|
|
constructor(components) {
|
|
this.components = components
|
|
}
|
|
}
|
|
|
|
|
|
export const Direction = Object.freeze({
|
|
Left: 0,
|
|
Right: 1
|
|
})
|
|
|
|
export class Edge {
|
|
constructor(components) {
|
|
this.components = components
|
|
}
|
|
}
|
|
|
|
export class DirectedEdge extends Edge {
|
|
constructor(components, direction) {
|
|
super(components)
|
|
this.direction = direction
|
|
}
|
|
|
|
static fromEdge(edge, direction) {
|
|
return new DirectedEdge(edge.components, direction)
|
|
}
|
|
}
|
|
|
|
export class Relationship {
|
|
constructor(left, edge, right) {
|
|
const [from, to] = edge.direction === Direction.Right ? [left, right] : [right, left]
|
|
this.from = from
|
|
this.to = to
|
|
this.edge = edge
|
|
}
|
|
}
|
|
|
|
export class ReturnValues {
|
|
constructor(...args) {
|
|
this.values = args
|
|
}
|
|
|
|
from(values) {
|
|
// TODO: pass with entries
|
|
const returns = Object.keys(values).map(key => this.#pluck(values, key))
|
|
return Object.assign({}, ...returns)
|
|
}
|
|
|
|
#pluck(values, name) {
|
|
return this.findName(name)
|
|
.map(value => value.pluck(values))
|
|
.reduce(mergeRightDeep, {})
|
|
}
|
|
|
|
find(fn) {
|
|
return this.values.find(fn)
|
|
}
|
|
|
|
filter(fn) {
|
|
return this.values.filter(fn)
|
|
}
|
|
|
|
findName(value) {
|
|
return this.filter(x => x.equals(value))
|
|
}
|
|
}
|
|
|
|
export class KeyValuePair {
|
|
constructor(key, value) {
|
|
this.key = key
|
|
this.value = value
|
|
}
|
|
}
|
|
|
|
export class Match {
|
|
constructor(value) {
|
|
this.value = value
|
|
}
|
|
|
|
valueOf() {
|
|
return this.value
|
|
}
|
|
}
|
|
|
|
export class Query {
|
|
constructor(use, matches, filters, returnValues) {
|
|
this.use = use
|
|
this.matches = matches
|
|
this.filters = filters
|
|
this.returnValues = returnValues
|
|
}
|
|
}
|