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 or = (...booleans) => booleans.some(identity)
export const isOk = r => !is(Result, r) || r.isOk() 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 of = value => Array.isArray(value) ? value : [value]
export const head = value => value[0] export const head = value => value[0]
export const tail = value => value.slice(1) 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 join = curry((sep, v) => v.join(sep))
export const rev = v => v.slice().reverse() export const rev = v => v.slice().reverse()
export const pipe = (...f) => v => f.reduce(apply, v) 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 export const identity = x => x

View file

@ -1,3 +1,4 @@
import { inspect } from 'node:util'
import { Result } from './result.js' import { Result } from './result.js'
import { Iterator, Stream } from './stream.js' import { Iterator, Stream } from './stream.js'
import { curry, join, of } from './fn.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 quoted = collect(seq(skip(Symbol.Quote), until(Symbol.Quote), skip(Symbol.Quote)))
export const number = seq(digit, many(digit)) export const number = seq(digit, many(digit))
export const signed = seq(maybe(any(Symbol.Plus, Symbol.Hyphen)), number) 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)))) 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))) 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 { map, maybe, seq } from '../parser.js'
import { word } from './common.js'
import { matchClause } from './match.js' import { matchClause } from './match.js'
import { returnClause } from './return.js' import { returnClause } from './return.js'
import { Query, SelectedGraph } from './types.js' import { Query, SelectedGraph } from './types.js'
import { useClause } from './use.js' import { useClause } from './use.js'
const collect = args => { const hasUseClause = pipe(
return args head,
if (is(SelectedGraph, head(args))) { is(SelectedGraph)
return new Query(...args) )
} else {
return new Query(undefined, ...args) const constructQuery = construct(Query)
}
} const collect = ifElse(
hasUseClause,
constructQuery,
pipe(prepend(undefined), constructQuery)
)
export const query = map( export const query = map(
collect, collect,
seq( seq(
maybe(useClause), maybe(useClause),
matchClause, word(matchClause),
returnClause word(returnClause)
) )
) )

View file

@ -1,12 +1,12 @@
import { identifier, literal, Symbol, ws } from './common.js' import { identifier, literal, Symbol, ws } from './common.js'
import { Node, Edge, KeyValuePair, Label, Name, Direction, DirectedEdge, Relationship } from './types.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' import { many, maybe, map, seq, skip, between, noCaseString, separated, list, any } from '../parser.js'
const { Bracket, Colon, Comma, Hyphen } = Symbol const { Bracket, Colon, Comma, Hyphen } = Symbol
const name = map( const name = map(
([x]) => new Name(x), construct(Name),
identifier identifier
) )
@ -21,7 +21,7 @@ const bracketed = curry((value, { Left, Right }, state) => (
)) ))
const kvp = map( const kvp = map(
([k, v]) => new KeyValuePair(k, v), construct(KeyValuePair),
separated(name, trim(Colon), literal) separated(name, trim(Colon), literal)
) )
export const kvps = list(trim(Comma), kvp) 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 { describe, it } from 'node:test'
import assert from '../assert.js' import assert from '../assert.js'
import { node, edge, matchClause } from '../../src/query/match.js' import { node, edge, matchClause } from '../../src/query/match.js'

View file

@ -1,40 +1,73 @@
import { describe, it } from 'node:test' import { describe, it } from 'node:test'
import assert from '../assert.js' import assert from '../assert.js'
import { query } from '../../src/query/index.js' import { query } from '../../src/query/index.js'
import { Identifier, Query, ReturnValues } from '../../src/query/types.js' import { Alias, Identifier, ObjectPath, Query, ReturnValues, SelectedGraph } from '../../src/query/types.js'
import { makeEdge, makeNode } from '../utils.js' import { makeNode, makeRelationship, makeRightEdge } from '../utils.js'
const i = n => new Identifier(n) const path = (...v) => new ObjectPath(...v)
const rv = (...v) => new ReturnValues(...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) const q = (u, m, rv) => new Query(u, m, rv)
describe('query', () => { describe('query', () => {
//it('should match a node', () => { it('should match a node', () => {
// assert.parseOk(query, 'MATCH (node:Label) RETURN node', ([actual]) => { assert.parseOk(query, 'MATCH (node:Label) RETURN node', ([actual]) => {
// const expected = q( const expected = q(
// undefined, // no use clause undefined, // no use clause
// makeNode('node', 'Label'), makeNode('node', 'Label'),
// rv(i('node')) returnVals(identifier('node'))
// ) )
// assert.deepEqual(actual, expected) assert.deepEqual(actual, expected)
// }) })
//}) })
it('should match a relationship', () => { it('should match a relationship', () => {
assert.parseOk(query, 'MATCH (rown:Creature)-[:PETPATS]->(kbity:NetCat) RETURN rown, kbity', (actual) => { assert.parseOk(query, 'MATCH (rown:Creature)-[:Petpats]->(kbity:NetCat) RETURN rown, kbity', ([actual]) => {
console.log(actual) const expected = q(
//const expected = q( undefined, // no use clause
// undefined, // no use clause makeRelationship(
// [ makeNode('rown', 'Creature'),
// makeNode('rown', 'Creature'), makeRightEdge(undefined, 'Petpats'),
// makeEdge(undefined, 'PETPATS'), makeNode('kbity', 'NetCat'),
// makeNode('kbity', 'NetCat'), ),
// ], returnVals(identifier('rown'), identifier('kbity'))
// rv(i('rown'), i('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 makeNode = graphObject
export const makeEdge = (name, label, props = []) => graphObject(name, label, props, Edge) 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 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) export const makeRelationship = (from, edge, to) => new Relationship(from, edge, to)