cleanup and query parsing

This commit is contained in:
Rowan 2024-11-09 06:17:57 -06:00
parent f39a926743
commit 34d09432c1
4 changed files with 134 additions and 36 deletions

View file

@ -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

View file

@ -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) =>

View file

@ -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])

View file

@ -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())
}
} }