Compare commits

..

No commits in common. "8844f326b0741144b54d351337930fdc1efe72a7" and "d9ebc1e875b7d4f6369d563b626f66d6b55009cb" have entirely different histories.

6 changed files with 30 additions and 70 deletions

View file

@ -1,4 +1,4 @@
import { curry, is, last, mergeRightDeep, path } from '../fn.js' import { assocPath, curry, identity, is, last, mergeRightDeep, path } from '../fn.js'
import { Stream } from '../stream.js' import { Stream } from '../stream.js'
class Value { class Value {
@ -97,11 +97,11 @@ export class Function {
#pop(stack) { #pop(stack) {
const result = [] const result = []
let n = Math.min(this.length, stack.length) const len = stack.length - 1
const n = Math.min(this.length, len)
while(n > 0) { for(let i = len; i >= len - n; i--) {
result.push(stack.pop()) result.push(stack.pop())
n--
} }
result.reverse() result.reverse()
@ -122,14 +122,14 @@ const Associativity = Object.freeze({
export class Operator extends Function { export class Operator extends Function {
constructor(name, fn, priority, associativity = Associativity.Left, length = fn.length) { constructor(name, fn, priority, associativity = Associativity.Left, length = fn.length) {
super(name, fn, length) super(name, fn, length)
this.precedence = priority this.priority = priority
this.associativity = associativity this.associativity = associativity
} }
compare(other) { compare(other) {
if (other.precedence < this.precedence) { if (other.priority > this.priority) {
return 1 return 1
} else if (other.precedence === this.precedence) { } else if (other.priority === this.priority) {
if (this.associativity === Associativity.Left) { if (this.associativity === Associativity.Left) {
return 1 return 1
} else { } else {
@ -185,10 +185,9 @@ export class Filter {
const next = stream.next() const next = stream.next()
if (is(Function, next)) { if (is(Function, next)) {
if (is(Operator, next)) { if (is(Operator, next)) {
const ops = this.#takeWhile(op => !leftParen(op) && next.compare(op) > 0, operators) const ops = this.#takeUntil(op => leftParen(op) && next.compare(op) <= 0, operators)
output.push.apply(output, ops) output.push.apply(output, ops)
} }
operators.push(next) operators.push(next)
} else if (comma(next)) { } else if (comma(next)) {
output.push.apply(output, this.#takeUntil(leftParen, operators)) output.push.apply(output, this.#takeUntil(leftParen, operators))
@ -197,11 +196,9 @@ export class Filter {
} else if (rightParen(next)) { } else if (rightParen(next)) {
const ops = this.#takeUntil(leftParen, operators) const ops = this.#takeUntil(leftParen, operators)
output.push.apply(output, ops) output.push.apply(output, ops)
if (!leftParen(operators.pop())) { if (!leftParen(operators.pop())) {
throw new SyntaxError('mismatched parenthesis') throw new SyntaxError('mismatched parenthesis')
} }
if (is(Function, last(operators))) { if (is(Function, last(operators))) {
output.push(operators.pop()) output.push(operators.pop())
} }
@ -229,13 +226,13 @@ export class Filter {
const next = stream.next() const next = stream.next()
if (is(Operator, next)) { if (is(Operator, next)) {
const result = next.apply(stack) const result = next.apply(stack)
stack.push(result) stack.unshift(result)
} else { } else {
stack.push(next) stack.push(next)
} }
} }
return stack[0] return stack.every(identity)
}) })
} }
} }

View file

@ -49,37 +49,10 @@ export class Iterator {
return accumulator return accumulator
} }
takeWhile(fn) {
const accumulator = []
while (!this.done() || fn(this.peek())) {
accumulator.push(this.next())
}
return accumulator
}
takeUntil(fn) {
return this.takeWhile(x => !fn(x))
}
drop(n) { drop(n) {
this.take(n) this.take(n)
return this return this
} }
dropWhile(fn) {
while(!this.done() || fn(this.peek())) {
this.next()
}
return this
}
dropUntil(fn) {
this.dropWhile(x => !fn(x))
return this
}
} }
class ArrayIterator extends Iterator { class ArrayIterator extends Iterator {

View file

@ -1,6 +1,6 @@
import { describe, it } from 'node:test' import { describe, it } from 'node:test'
import assert from '../assert.js' import assert from '../assert.js'
import { Alias, Identifier, Literal, ObjectPath, Property } from '../../src/query-parser/types.js' import { Alias, Identifier, Literal, ObjectPath } from '../../src/query-parser/types.js'
import { baseValue, literal, value } from '../../src/query-parser/common.js' import { baseValue, literal, value } from '../../src/query-parser/common.js'
describe('common parser library', () => { describe('common parser library', () => {
@ -38,7 +38,7 @@ describe('common parser library', () => {
}) })
assert.parseOk(baseValue, 'ginger.snaps', ([actual]) => { assert.parseOk(baseValue, 'ginger.snaps', ([actual]) => {
assert.deepEqual(actual, new ObjectPath(new Identifier('ginger'), new Property(new Identifier('snaps')))) assert.deepEqual(actual, new ObjectPath(new Identifier('ginger'), new Identifier('snaps')))
}) })
}) })
@ -56,7 +56,7 @@ describe('common parser library', () => {
}) })
assert.parseOk(baseValue, 'monster.girl', ([actual]) => { assert.parseOk(baseValue, 'monster.girl', ([actual]) => {
assert.deepEqual(actual, new ObjectPath(new Identifier('monster'), new Property(new Identifier('girl')))) assert.deepEqual(actual, new ObjectPath(new Identifier('monster'), new Identifier('girl')))
}) })
}) })
@ -74,7 +74,7 @@ describe('common parser library', () => {
}) })
assert.parseOk(value, 'rowan.containment.isBreached AS rawrnEscaped', ([actual]) => { assert.parseOk(value, 'rowan.containment.isBreached AS rawrnEscaped', ([actual]) => {
const obj = new ObjectPath(new Identifier('rowan'), new Property(new Identifier('containment')), new Property(new Identifier('isBreached'))) const obj = new ObjectPath(new Identifier('rowan'), new Identifier('containment'), new Identifier('isBreached'))
const alias = new Alias(obj, new Identifier('rawrnEscaped')) const alias = new Alias(obj, new Identifier('rawrnEscaped'))
assert.deepEqual(actual, alias) assert.deepEqual(actual, alias)
}) })

View file

@ -1,13 +1,11 @@
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 { query } from '../../src/query-parser/index.js' import { query } from '../../src/query-parser/index.js'
import { Alias, Identifier, Match, ObjectPath, Property, Query, ReturnValues, SelectedGraph } from '../../src/query-parser/types.js' import { Alias, Identifier, Match, ObjectPath, Query, ReturnValues, SelectedGraph } from '../../src/query-parser/types.js'
import { makeNode, makeRelationship, makeRightEdge } from '../utils.js' import { makeNode, makeRelationship, makeRightEdge } from '../utils.js'
import { map } from '../../src/fn.js' import { map } from '../../src/fn.js'
const path = (...args) => new ObjectPath(identifier(args[0]), ...args.slice(1).map(prop)) const path = (...x) => new ObjectPath(...x)
const prop = x => new Property(identifier(x))
const alias = (...x) => new Alias(...x) const alias = (...x) => new Alias(...x)
const identifier = n => new Identifier(n) const identifier = n => new Identifier(n)
const graph = n => new SelectedGraph(identifier(n)) const graph = n => new SelectedGraph(identifier(n))
@ -70,13 +68,11 @@ describe('query', () => {
)), )),
[], [],
returnVals( returnVals(
alias(path('kbity', 'name'), identifier('name')), alias(path(identifier('kbity'), identifier('name')), identifier('name')),
alias(identifier('snacc'), identifier('food')) alias(identifier('snacc'), identifier('food'))
) )
) )
console.log(util.inspect(actual, { depth: null }))
console.log(util.inspect(expected, { depth: null }))
assert.deepEqual(actual, expected) assert.deepEqual(actual, expected)
}) })
}) })

View file

@ -85,21 +85,21 @@ describe('WHERE keyword', () => {
it('handles complex filters', () => { it('handles complex filters', () => {
assert.parseOk(whereClause, 'WHERE h.max > 500 AND (h.current < 250 OR a.value = 10) OR a.value <= 2', ([actual]) => { assert.parseOk(whereClause, 'WHERE h.max > 500 AND (h.current < 250 OR a.value = 10) OR a.value <= 2', ([actual]) => {
const expected = [ const expected = [
new ObjectPath(new Identifier('h'), new Property(new Identifier('max'))), new ObjectPath(new Identifier('h'), new Property('max')),
new Literal(500), new Literal(500),
GreaterThan, GreaterThan,
new ObjectPath(new Identifier('h'), new Property(new Identifier('current'))), new ObjectPath(new Identifier('h'), new Property('current')),
new Literal(250), new Literal(250),
LesserThan, LesserThan,
new ObjectPath(new Identifier('a'), new Property(new Identifier('value'))), new ObjectPath(new Identifier('a'), new Property('value')),
Or,
new Literal(10), new Literal(10),
Equal, Equal,
Or,
And, And,
new ObjectPath(new Identifier('a'), new Property(new Identifier('value'))), new ObjectPath(new Identifier('a'), new Property('value')),
new Literal(2),
LesserThanEqual,
Or, Or,
new Literal(2),
LesserThanEqual
] ]
assert.deepEqual(actual.values, expected) assert.deepEqual(actual.values, expected)
}) })

View file

@ -121,18 +121,12 @@ describe('query', () => {
) )
assert.deepEqual( assert.deepEqual(
query('MATCH (e, h:Health) WHERE h.max = 50 OR h.current > 45 RETURN e, h.max AS maxHealth', engine).unwrap(), query('MATCH (e, h:Health) WHERE h.max = 50 OR h.current < 50 RETURN e, h.max AS maxHealth', engine).unwrap(),
[ [
{ e: 12, maxHealth: 50 }, { e: 12, maxHealth: 50 },
{ e: 13, maxHealth: 50 } { e: 13, maxHealth: 50 }
] ]
) )
assert.deepEqual(
query('MATCH (e, :Player, h:Health) WHERE (h.max = 50 OR h.current > 45) AND e = 12 RETURN e, h.max AS maxHealth', engine).unwrap(),
[
{ e: 12, maxHealth: 50 }
]
)
}) })
}) })