cleanup and query parsing
This commit is contained in:
parent
f39a926743
commit
34d09432c1
4 changed files with 134 additions and 36 deletions
|
@ -25,6 +25,7 @@ export const apply = curry((v, f) => f(v))
|
||||||
export const reduce = curry((fn, init, v) => v.reduce(fn, init))
|
export const reduce = curry((fn, init, v) => v.reduce(fn, init))
|
||||||
export const map = curry((fn, v) => v.map(fn))
|
export const map = curry((fn, v) => v.map(fn))
|
||||||
export const filter = curry((fn, v) => v.filter(fn))
|
export const filter = curry((fn, v) => v.filter(fn))
|
||||||
|
export const join = curry((sep, v) => v.join(sep))
|
||||||
export const pipe = (...f) => v => f.reduce(apply, v)
|
export const pipe = (...f) => v => f.reduce(apply, v)
|
||||||
|
|
||||||
export const identity = x => x
|
export const identity = x => x
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Result } from './result.js'
|
import { Result } from './result.js'
|
||||||
import { Iterator, Peekable, Stream } from './stream.js'
|
import { Iterator, Stream } from './stream.js'
|
||||||
import { curry, of } from './fn.js'
|
import { curry, join, of } from './fn.js'
|
||||||
|
|
||||||
class ParseError extends Error {
|
class ParseError extends Error {
|
||||||
constructor(message, state, source) {
|
constructor(message, state, source) {
|
||||||
|
@ -39,13 +39,7 @@ class ParseError extends Error {
|
||||||
* @return {ParserState} - A new ParserState
|
* @return {ParserState} - A new ParserState
|
||||||
*/
|
*/
|
||||||
const ParserState = value => ([[], new Stream(value)])
|
const ParserState = value => ([[], new Stream(value)])
|
||||||
|
const clone = ([a, b]) => ([a.slice(), b.clone()])
|
||||||
/**
|
|
||||||
* Create a Peekable from an existing ParserState
|
|
||||||
* @param {ParserState}
|
|
||||||
* @return {Peekable}
|
|
||||||
*/
|
|
||||||
const peekable = ([a, b]) => ([a, new Peekable(b)])
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update ParserState with parsed tokens
|
* Update ParserState with parsed tokens
|
||||||
|
@ -69,6 +63,11 @@ const succeed = curry((v, [p, rest]) => Result.Ok([p.concat(of(v)), rest]))
|
||||||
const fail = curry((msg, state, err = undefined) => Result.Err(new ParseError(msg, state, err)))
|
const fail = curry((msg, state, err = undefined) => Result.Err(new ParseError(msg, state, err)))
|
||||||
|
|
||||||
const next = ([, rest]) => rest.next()
|
const next = ([, rest]) => rest.next()
|
||||||
|
// b.len - a.len
|
||||||
|
// b.slice(-diff)
|
||||||
|
//const difflen = result.value[0].length - parsed.length
|
||||||
|
//console.log(difflen, result.value[0], result.value[0].slice(-difflen))
|
||||||
|
const diff = ([a], [b]) => b.slice(-Math.max(0, b.length - a.length))
|
||||||
const tokenize = str => str.split('')
|
const tokenize = str => str.split('')
|
||||||
const tokenizeInto = curry((fn, str) => tokenize(str).map(v => fn(v)))
|
const tokenizeInto = curry((fn, str) => tokenize(str).map(v => fn(v)))
|
||||||
|
|
||||||
|
@ -91,9 +90,8 @@ export const seq = (...parsers) => state => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const any = (...parsers) => (state) => {
|
export const any = (...parsers) => (state) => {
|
||||||
const peekState = peekable(state)
|
|
||||||
for (const parser of parsers) {
|
for (const parser of parsers) {
|
||||||
const result = parser(peekState)
|
const result = parser(clone(state))
|
||||||
if (result.isOk()) {
|
if (result.isOk()) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -102,6 +100,32 @@ export const any = (...parsers) => (state) => {
|
||||||
return fail('no matching parsers', state)
|
return fail('no matching parsers', state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const many = curry((parser, state) => {
|
||||||
|
let result = Result.Ok(state)
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const res = parser(clone(result.value))
|
||||||
|
if (res.isOk()) {
|
||||||
|
result = res
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
export const skip = curry((parser, state) => {
|
||||||
|
const [parsed] = state
|
||||||
|
const result = parser(state)
|
||||||
|
|
||||||
|
if (result.isOk()) {
|
||||||
|
return Result.Ok([parsed, result.value[1]])
|
||||||
|
} else {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export const map = curry((fn, parser, state) => {
|
export const map = curry((fn, parser, state) => {
|
||||||
const result = parser(state)
|
const result = parser(state)
|
||||||
|
|
||||||
|
@ -111,7 +135,8 @@ export const map = curry((fn, parser, state) => {
|
||||||
const backtrack = [parsed, stream]
|
const backtrack = [parsed, stream]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return succeed(fn(result.value, backtrack))
|
const parsedValue = diff(state, result.value)
|
||||||
|
return succeed(fn(parsedValue), backtrack)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return fail('map failed', state, e)
|
return fail('map failed', state, e)
|
||||||
}
|
}
|
||||||
|
@ -121,7 +146,6 @@ export const map = curry((fn, parser, state) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const char = curry((ch, state) => {
|
export const char = curry((ch, state) => {
|
||||||
return next(state) === ch ? succeed(ch, state) : fail(`could not parse ${ch}`, state)
|
return next(state) === ch ? succeed(ch, state) : fail(`could not parse ${ch}`, state)
|
||||||
})
|
})
|
||||||
|
@ -131,7 +155,7 @@ export const noCaseChar = curry((ch, state) => (
|
||||||
))
|
))
|
||||||
|
|
||||||
export const string = curry((str, state) =>
|
export const string = curry((str, state) =>
|
||||||
seq(...tokenizeInto(char, str))(state)
|
map(join(''), seq(...tokenizeInto(char, str)))(state)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const anyChar = curry((str, state) =>
|
export const anyChar = curry((str, state) =>
|
||||||
|
|
69
src/query.js
69
src/query.js
|
@ -1,6 +1,73 @@
|
||||||
import { } from './parser.js'
|
import { join } from './fn.js'
|
||||||
|
import { alpha, alphanumeric, any, char, many, map, noCaseString, parse, seq, skip } from './parser.js'
|
||||||
|
|
||||||
// MATCH (a:Label)-[e:Label]->(b:Label)
|
// MATCH (a:Label)-[e:Label]->(b:Label)
|
||||||
// RETURN a, b
|
// RETURN a, b
|
||||||
|
|
||||||
|
const Identifier = (name, label) => ({ name, label })
|
||||||
|
|
||||||
|
const ObjectType = Object.freeze({
|
||||||
|
Node: 0,
|
||||||
|
Edge: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const GraphObject = ({ name, label }, type, properties = []) => Object.freeze({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
type,
|
||||||
|
properties
|
||||||
|
})
|
||||||
|
|
||||||
|
const whitespace = char(' ')
|
||||||
|
const matchKeyword = noCaseString('match')
|
||||||
|
const returnKeyword = noCaseString('return')
|
||||||
|
|
||||||
|
const name = map(
|
||||||
|
join(''),
|
||||||
|
seq(alpha, many(alphanumeric))
|
||||||
|
)
|
||||||
|
const colon = char(':')
|
||||||
|
const label = seq(skip(colon), name)
|
||||||
|
|
||||||
|
const id = any(
|
||||||
|
map(
|
||||||
|
([name, label]) => Identifier(name, label),
|
||||||
|
seq(name, label)
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
([name]) => Identifier(name),
|
||||||
|
name
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
([label]) => Identifier(undefined, label),
|
||||||
|
label
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const lparen = char('(')
|
||||||
|
const rparen = char(')')
|
||||||
|
const node = map(
|
||||||
|
([id]) => GraphObject(id, ObjectType.Node),
|
||||||
|
seq(skip(lparen), id, skip(rparen))
|
||||||
|
)
|
||||||
|
|
||||||
|
const lsquare = char('[')
|
||||||
|
const rsquare = char(']')
|
||||||
|
const edge = map(
|
||||||
|
([id]) => GraphObject(id, ObjectType.Edge),
|
||||||
|
seq(skip(lsquare), id, skip(rsquare))
|
||||||
|
)
|
||||||
|
|
||||||
|
const hyphen = char('-')
|
||||||
|
const rchevron = char('>')
|
||||||
|
const arrow = seq(hyphen, rchevron)
|
||||||
|
const relationship = seq(skip(hyphen), edge, skip(arrow), node)
|
||||||
|
|
||||||
|
const matchParameters = seq(node, many(relationship))
|
||||||
|
const matchStmt = seq(skip(matchKeyword), skip(many(whitespace)), matchParameters)
|
||||||
|
|
||||||
|
const query = 'MATCH (:Label)-[e:Label]->(b:Label)'
|
||||||
|
const result = parse(matchStmt, query)
|
||||||
|
console.log(result.value[0])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class Iterator {
|
||||||
const handler = types.get(ctr)
|
const handler = types.get(ctr)
|
||||||
return new handler(value)
|
return new handler(value)
|
||||||
} else {
|
} else {
|
||||||
throw new NotImplementedError(`${value} is not an iterator`)
|
throw new NotImplementedError(`${value} is not an Iterator`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,10 @@ export class Iterator {
|
||||||
throw new NotImplementedError('Iterator::peek not implemented')
|
throw new NotImplementedError('Iterator::peek not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
throw new NotImplementedError('Iterator::clone not implemented')
|
||||||
|
}
|
||||||
|
|
||||||
done() {
|
done() {
|
||||||
return !!this.peek()
|
return !!this.peek()
|
||||||
}
|
}
|
||||||
|
@ -48,37 +52,35 @@ export class Iterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
class ArrayIterator extends Iterator {
|
||||||
static { Iterator.handledTypes.set(Array, ArrayIterator) }
|
static { Iterator.handledTypes.set(Array, ArrayIterator) }
|
||||||
|
#index = -1
|
||||||
|
#value
|
||||||
|
|
||||||
constructor(value) {
|
constructor(value, index = -1) {
|
||||||
super()
|
super()
|
||||||
this.value = value
|
|
||||||
|
if (!Array.isArray) {
|
||||||
|
throw new Error(`${value} is not an Array`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#index = index
|
||||||
|
this.#value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
next() {
|
next() {
|
||||||
if (this.value.length > 0) {
|
if (this.#index <= this.#value.length) {
|
||||||
return this.value.shift()
|
this.#index += 1
|
||||||
|
return this.#value[this.#index]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
peek() {
|
peek() {
|
||||||
return this.value.length > 0 ? this.value[0] : undefined
|
return this.#value[this.#index]
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new ArrayIterator(this.#value, this.#index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,5 +113,9 @@ export class Stream extends Iterator {
|
||||||
peek() {
|
peek() {
|
||||||
return this.iterator.peek()
|
return this.iterator.peek()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new Stream(this.iterator.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue