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 map = curry((fn, v) => v.map(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 identity = x => x
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Result } from './result.js'
|
||||
import { Iterator, Peekable, Stream } from './stream.js'
|
||||
import { curry, of } from './fn.js'
|
||||
import { Iterator, Stream } from './stream.js'
|
||||
import { curry, join, of } from './fn.js'
|
||||
|
||||
class ParseError extends Error {
|
||||
constructor(message, state, source) {
|
||||
|
@ -39,13 +39,7 @@ class ParseError extends Error {
|
|||
* @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)])
|
||||
const clone = ([a, b]) => ([a.slice(), b.clone()])
|
||||
|
||||
/**
|
||||
* 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 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 tokenizeInto = curry((fn, str) => tokenize(str).map(v => fn(v)))
|
||||
|
||||
|
@ -91,9 +90,8 @@ export const seq = (...parsers) => state => {
|
|||
}
|
||||
|
||||
export const any = (...parsers) => (state) => {
|
||||
const peekState = peekable(state)
|
||||
for (const parser of parsers) {
|
||||
const result = parser(peekState)
|
||||
const result = parser(clone(state))
|
||||
if (result.isOk()) {
|
||||
return result
|
||||
}
|
||||
|
@ -102,6 +100,32 @@ export const any = (...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) => {
|
||||
const result = parser(state)
|
||||
|
||||
|
@ -111,7 +135,8 @@ export const map = curry((fn, parser, state) => {
|
|||
const backtrack = [parsed, stream]
|
||||
|
||||
try {
|
||||
return succeed(fn(result.value, backtrack))
|
||||
const parsedValue = diff(state, result.value)
|
||||
return succeed(fn(parsedValue), backtrack)
|
||||
} catch (e) {
|
||||
return fail('map failed', state, e)
|
||||
}
|
||||
|
@ -121,7 +146,6 @@ export const map = curry((fn, parser, state) => {
|
|||
})
|
||||
|
||||
|
||||
|
||||
export const char = curry((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) =>
|
||||
seq(...tokenizeInto(char, str))(state)
|
||||
map(join(''), seq(...tokenizeInto(char, 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)
|
||||
// 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)
|
||||
return new handler(value)
|
||||
} 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')
|
||||
}
|
||||
|
||||
clone() {
|
||||
throw new NotImplementedError('Iterator::clone not implemented')
|
||||
}
|
||||
|
||||
done() {
|
||||
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 {
|
||||
static { Iterator.handledTypes.set(Array, ArrayIterator) }
|
||||
#index = -1
|
||||
#value
|
||||
|
||||
constructor(value) {
|
||||
constructor(value, index = -1) {
|
||||
super()
|
||||
this.value = value
|
||||
|
||||
if (!Array.isArray) {
|
||||
throw new Error(`${value} is not an Array`)
|
||||
}
|
||||
|
||||
this.#index = index
|
||||
this.#value = value
|
||||
}
|
||||
|
||||
next() {
|
||||
if (this.value.length > 0) {
|
||||
return this.value.shift()
|
||||
if (this.#index <= this.#value.length) {
|
||||
this.#index += 1
|
||||
return this.#value[this.#index]
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
return this.iterator.peek()
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Stream(this.iterator.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue