finalize dsl parser for now

This commit is contained in:
Rowan 2024-11-18 19:10:29 -06:00
parent bab47f8a95
commit 84e5bb4c02
9 changed files with 86 additions and 45 deletions

View file

@ -17,6 +17,8 @@ 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 construct = Type => args => new Type(...args)
export const of = value => Array.isArray(value) ? value : [value]
export const head = value => value[0]
export const tail = value => value.slice(1)
@ -29,6 +31,8 @@ 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 identity = x => x

View file

@ -1,3 +1,4 @@
import { inspect } from 'node:util'
import { Result } from './result.js'
import { Iterator, Stream } from './stream.js'
import { curry, join, of } from './fn.js'

View file

@ -1,3 +0,0 @@
// MATCH (a:Label)-[e:Label]->(b:Label)
// RETURN a, b
//

View file

@ -36,7 +36,7 @@ export const collect = parser => map(join(''), parser)
export const quoted = collect(seq(skip(Symbol.Quote), until(Symbol.Quote), skip(Symbol.Quote)))
export const number = seq(digit, many(digit))
export const signed = seq(maybe(any(Symbol.Plus, Symbol.Hyphen)), number)
export const ws = skip(many(Symbol.Space))
export const ws = skip(many(any(Symbol.Newline, Symbol.Space)))
export const identifier = map(([x]) => new Identifier(x), collect(seq(alpha, many(alphanumeric))))
const float = map(([n]) => parseFloat(n, 10), collect(seq(signed, Symbol.Period, number)))

View file

@ -1,25 +1,30 @@
import { head, is } from '../fn.js'
import { construct, head, ifElse, is, pipe, prepend } from '../fn.js'
import { map, maybe, seq } from '../parser.js'
import { word } from './common.js'
import { matchClause } from './match.js'
import { returnClause } from './return.js'
import { Query, SelectedGraph } from './types.js'
import { useClause } from './use.js'
const collect = args => {
return args
if (is(SelectedGraph, head(args))) {
return new Query(...args)
} else {
return new Query(undefined, ...args)
}
}
const hasUseClause = pipe(
head,
is(SelectedGraph)
)
const constructQuery = construct(Query)
const collect = ifElse(
hasUseClause,
constructQuery,
pipe(prepend(undefined), constructQuery)
)
export const query = map(
collect,
seq(
maybe(useClause),
matchClause,
returnClause
word(matchClause),
word(returnClause)
)
)

View file

@ -1,12 +1,12 @@
import { identifier, literal, Symbol, ws } from './common.js'
import { Node, Edge, KeyValuePair, Label, Name, Direction, DirectedEdge, Relationship } from './types.js'
import { curry, filter, is } from '../fn.js'
import { construct, curry, filter, is } from '../fn.js'
import { many, maybe, map, seq, skip, between, noCaseString, separated, list, any } from '../parser.js'
const { Bracket, Colon, Comma, Hyphen } = Symbol
const name = map(
([x]) => new Name(x),
construct(Name),
identifier
)
@ -21,7 +21,7 @@ const bracketed = curry((value, { Left, Right }, state) => (
))
const kvp = map(
([k, v]) => new KeyValuePair(k, v),
construct(KeyValuePair),
separated(name, trim(Colon), literal)
)
export const kvps = list(trim(Comma), kvp)

View file

@ -1,4 +1,3 @@
import util from 'node:util'
import { describe, it } from 'node:test'
import assert from '../assert.js'
import { node, edge, matchClause } from '../../src/query/match.js'

View file

@ -1,40 +1,73 @@
import { describe, it } from 'node:test'
import assert from '../assert.js'
import { query } from '../../src/query/index.js'
import { Identifier, Query, ReturnValues } from '../../src/query/types.js'
import { makeEdge, makeNode } from '../utils.js'
import { Alias, Identifier, ObjectPath, Query, ReturnValues, SelectedGraph } from '../../src/query/types.js'
import { makeNode, makeRelationship, makeRightEdge } from '../utils.js'
const i = n => new Identifier(n)
const rv = (...v) => new ReturnValues(...v)
const path = (...v) => new ObjectPath(...v)
const alias = (...v) => new Alias(...v)
const identifier = n => new Identifier(n)
const graph = n => new SelectedGraph(identifier(n))
const returnVals = (...v) => new ReturnValues(...v)
const q = (u, m, rv) => new Query(u, m, rv)
describe('query', () => {
//it('should match a node', () => {
// assert.parseOk(query, 'MATCH (node:Label) RETURN node', ([actual]) => {
// const expected = q(
// undefined, // no use clause
// makeNode('node', 'Label'),
// rv(i('node'))
// )
it('should match a node', () => {
assert.parseOk(query, 'MATCH (node:Label) RETURN node', ([actual]) => {
const expected = q(
undefined, // no use clause
makeNode('node', 'Label'),
returnVals(identifier('node'))
)
// assert.deepEqual(actual, expected)
// })
//})
assert.deepEqual(actual, expected)
})
})
it('should match a relationship', () => {
assert.parseOk(query, 'MATCH (rown:Creature)-[:PETPATS]->(kbity:NetCat) RETURN rown, kbity', (actual) => {
console.log(actual)
//const expected = q(
// undefined, // no use clause
// [
// makeNode('rown', 'Creature'),
// makeEdge(undefined, 'PETPATS'),
// makeNode('kbity', 'NetCat'),
// ],
// rv(i('rown'), i('kbity'))
//)
assert.parseOk(query, 'MATCH (rown:Creature)-[:Petpats]->(kbity:NetCat) RETURN rown, kbity', ([actual]) => {
const expected = q(
undefined, // no use clause
makeRelationship(
makeNode('rown', 'Creature'),
makeRightEdge(undefined, 'Petpats'),
makeNode('kbity', 'NetCat'),
),
returnVals(identifier('rown'), identifier('kbity'))
)
//assert.deepEqual(actual, expected)
assert.deepEqual(actual, expected)
})
})
it('should support the USE clause', () => {
assert.parseOk(query, 'USE aminals MATCH (kbity:Cat) RETURN kbity', ([actual]) => {
const expected = q(
graph('aminals'),
makeNode('kbity', 'Cat'),
returnVals(identifier('kbity'))
)
assert.deepEqual(actual, expected)
})
})
it('should support complex queries', () => {
assert.parseOk(query, 'USE aminals MATCH (kbity:Cat { type: "cute" })-[:Eats { schedule: "daily" }]->(snacc:Feesh { type: "vegan", weight: 0.7 }) RETURN kbity.name AS name, snacc AS food', ([actual]) => {
const expected = q(
graph('aminals'),
makeRelationship(
makeNode('kbity', 'Cat', [['type', 'cute']]),
makeRightEdge(undefined, 'Eats', [['schedule', 'daily']]),
makeNode('snacc', 'Feesh', [['type', 'vegan'], ['weight', 0.7]])
),
returnVals(
alias(path(identifier('kbity'), identifier('name')), identifier('name')),
alias(identifier('snacc'), identifier('food'))
)
)
assert.deepEqual(actual, expected)
})
})
})

View file

@ -11,5 +11,7 @@ export const graphObject = (name, label, props = [], Type = Node) => new Type(
export const makeNode = graphObject
export const makeEdge = (name, label, props = []) => graphObject(name, label, props, Edge)
export const makeDirectedEdge = (name, label, direction, props = []) => DirectedEdge.fromEdge(makeEdge(name, label, props), direction)
export const makeLeftEdge = (name, label, props = []) => DirectedEdge.fromEdge(makeEdge(name, label, props), 0)
export const makeRightEdge = (name, label, props = []) => DirectedEdge.fromEdge(makeEdge(name, label, props), 1)
export const makeRelationship = (from, edge, to) => new Relationship(from, edge, to)