Compare commits
3 commits
d9ebc1e875
...
8844f326b0
Author | SHA1 | Date | |
---|---|---|---|
8844f326b0 | |||
b2de794ef1 | |||
3a776ca1e1 |
6 changed files with 70 additions and 30 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { assocPath, curry, identity, is, last, mergeRightDeep, path } from '../fn.js'
|
import { curry, 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 = []
|
||||||
const len = stack.length - 1
|
let n = Math.min(this.length, stack.length)
|
||||||
const n = Math.min(this.length, len)
|
|
||||||
|
|
||||||
for(let i = len; i >= len - n; i--) {
|
while(n > 0) {
|
||||||
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.priority = priority
|
this.precedence = priority
|
||||||
this.associativity = associativity
|
this.associativity = associativity
|
||||||
}
|
}
|
||||||
|
|
||||||
compare(other) {
|
compare(other) {
|
||||||
if (other.priority > this.priority) {
|
if (other.precedence < this.precedence) {
|
||||||
return 1
|
return 1
|
||||||
} else if (other.priority === this.priority) {
|
} else if (other.precedence === this.precedence) {
|
||||||
if (this.associativity === Associativity.Left) {
|
if (this.associativity === Associativity.Left) {
|
||||||
return 1
|
return 1
|
||||||
} else {
|
} else {
|
||||||
|
@ -185,9 +185,10 @@ 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.#takeUntil(op => leftParen(op) && next.compare(op) <= 0, operators)
|
const ops = this.#takeWhile(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))
|
||||||
|
@ -196,9 +197,11 @@ 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())
|
||||||
}
|
}
|
||||||
|
@ -226,13 +229,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.unshift(result)
|
stack.push(result)
|
||||||
} else {
|
} else {
|
||||||
stack.push(next)
|
stack.push(next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stack.every(identity)
|
return stack[0]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,37 @@ 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 {
|
||||||
|
|
|
@ -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 } from '../../src/query-parser/types.js'
|
import { Alias, Identifier, Literal, ObjectPath, Property } 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 Identifier('snaps')))
|
assert.deepEqual(actual, new ObjectPath(new Identifier('ginger'), new Property(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 Identifier('girl')))
|
assert.deepEqual(actual, new ObjectPath(new Identifier('monster'), new Property(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 Identifier('containment'), new Identifier('isBreached'))
|
const obj = new ObjectPath(new Identifier('rowan'), new Property(new Identifier('containment')), new Property(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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
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, Query, ReturnValues, SelectedGraph } from '../../src/query-parser/types.js'
|
import { Alias, Identifier, Match, ObjectPath, Property, 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 = (...x) => new ObjectPath(...x)
|
const path = (...args) => new ObjectPath(identifier(args[0]), ...args.slice(1).map(prop))
|
||||||
|
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))
|
||||||
|
@ -68,11 +70,13 @@ describe('query', () => {
|
||||||
)),
|
)),
|
||||||
[],
|
[],
|
||||||
returnVals(
|
returnVals(
|
||||||
alias(path(identifier('kbity'), identifier('name')), identifier('name')),
|
alias(path('kbity', '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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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('max')),
|
new ObjectPath(new Identifier('h'), new Property(new Identifier('max'))),
|
||||||
new Literal(500),
|
new Literal(500),
|
||||||
GreaterThan,
|
GreaterThan,
|
||||||
new ObjectPath(new Identifier('h'), new Property('current')),
|
new ObjectPath(new Identifier('h'), new Property(new Identifier('current'))),
|
||||||
new Literal(250),
|
new Literal(250),
|
||||||
LesserThan,
|
LesserThan,
|
||||||
new ObjectPath(new Identifier('a'), new Property('value')),
|
new ObjectPath(new Identifier('a'), new Property(new Identifier('value'))),
|
||||||
Or,
|
|
||||||
new Literal(10),
|
new Literal(10),
|
||||||
Equal,
|
Equal,
|
||||||
And,
|
|
||||||
new ObjectPath(new Identifier('a'), new Property('value')),
|
|
||||||
Or,
|
Or,
|
||||||
|
And,
|
||||||
|
new ObjectPath(new Identifier('a'), new Property(new Identifier('value'))),
|
||||||
new Literal(2),
|
new Literal(2),
|
||||||
LesserThanEqual
|
LesserThanEqual,
|
||||||
|
Or,
|
||||||
]
|
]
|
||||||
assert.deepEqual(actual.values, expected)
|
assert.deepEqual(actual.values, expected)
|
||||||
})
|
})
|
||||||
|
|
|
@ -121,12 +121,18 @@ describe('query', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
query('MATCH (e, h:Health) WHERE h.max = 50 OR h.current < 50 RETURN e, h.max AS maxHealth', engine).unwrap(),
|
query('MATCH (e, h:Health) WHERE h.max = 50 OR h.current > 45 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 }
|
||||||
|
]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue