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'
|
||||
|
||||
class Value {
|
||||
|
@ -97,11 +97,11 @@ export class Function {
|
|||
|
||||
#pop(stack) {
|
||||
const result = []
|
||||
const len = stack.length - 1
|
||||
const n = Math.min(this.length, len)
|
||||
let n = Math.min(this.length, stack.length)
|
||||
|
||||
for(let i = len; i >= len - n; i--) {
|
||||
while(n > 0) {
|
||||
result.push(stack.pop())
|
||||
n--
|
||||
}
|
||||
|
||||
result.reverse()
|
||||
|
@ -122,14 +122,14 @@ const Associativity = Object.freeze({
|
|||
export class Operator extends Function {
|
||||
constructor(name, fn, priority, associativity = Associativity.Left, length = fn.length) {
|
||||
super(name, fn, length)
|
||||
this.priority = priority
|
||||
this.precedence = priority
|
||||
this.associativity = associativity
|
||||
}
|
||||
|
||||
compare(other) {
|
||||
if (other.priority > this.priority) {
|
||||
if (other.precedence < this.precedence) {
|
||||
return 1
|
||||
} else if (other.priority === this.priority) {
|
||||
} else if (other.precedence === this.precedence) {
|
||||
if (this.associativity === Associativity.Left) {
|
||||
return 1
|
||||
} else {
|
||||
|
@ -185,9 +185,10 @@ export class Filter {
|
|||
const next = stream.next()
|
||||
if (is(Function, 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)
|
||||
}
|
||||
|
||||
operators.push(next)
|
||||
} else if (comma(next)) {
|
||||
output.push.apply(output, this.#takeUntil(leftParen, operators))
|
||||
|
@ -196,9 +197,11 @@ export class Filter {
|
|||
} else if (rightParen(next)) {
|
||||
const ops = this.#takeUntil(leftParen, operators)
|
||||
output.push.apply(output, ops)
|
||||
|
||||
if (!leftParen(operators.pop())) {
|
||||
throw new SyntaxError('mismatched parenthesis')
|
||||
}
|
||||
|
||||
if (is(Function, last(operators))) {
|
||||
output.push(operators.pop())
|
||||
}
|
||||
|
@ -226,13 +229,13 @@ export class Filter {
|
|||
const next = stream.next()
|
||||
if (is(Operator, next)) {
|
||||
const result = next.apply(stack)
|
||||
stack.unshift(result)
|
||||
stack.push(result)
|
||||
} else {
|
||||
stack.push(next)
|
||||
}
|
||||
}
|
||||
|
||||
return stack.every(identity)
|
||||
return stack[0]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,10 +49,37 @@ export class Iterator {
|
|||
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) {
|
||||
this.take(n)
|
||||
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 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it } from 'node:test'
|
||||
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'
|
||||
|
||||
describe('common parser library', () => {
|
||||
|
@ -38,7 +38,7 @@ describe('common parser library', () => {
|
|||
})
|
||||
|
||||
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.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]) => {
|
||||
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'))
|
||||
assert.deepEqual(actual, alias)
|
||||
})
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import util from 'node:util'
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from '../assert.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 { 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 identifier = n => new Identifier(n)
|
||||
const graph = n => new SelectedGraph(identifier(n))
|
||||
|
@ -68,11 +70,13 @@ describe('query', () => {
|
|||
)),
|
||||
[],
|
||||
returnVals(
|
||||
alias(path(identifier('kbity'), identifier('name')), identifier('name')),
|
||||
alias(path('kbity', 'name'), identifier('name')),
|
||||
alias(identifier('snacc'), identifier('food'))
|
||||
)
|
||||
)
|
||||
|
||||
console.log(util.inspect(actual, { depth: null }))
|
||||
console.log(util.inspect(expected, { depth: null }))
|
||||
assert.deepEqual(actual, expected)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -85,21 +85,21 @@ describe('WHERE keyword', () => {
|
|||
it('handles complex filters', () => {
|
||||
assert.parseOk(whereClause, 'WHERE h.max > 500 AND (h.current < 250 OR a.value = 10) OR a.value <= 2', ([actual]) => {
|
||||
const expected = [
|
||||
new ObjectPath(new Identifier('h'), new Property('max')),
|
||||
new ObjectPath(new Identifier('h'), new Property(new Identifier('max'))),
|
||||
new Literal(500),
|
||||
GreaterThan,
|
||||
new ObjectPath(new Identifier('h'), new Property('current')),
|
||||
new ObjectPath(new Identifier('h'), new Property(new Identifier('current'))),
|
||||
new Literal(250),
|
||||
LesserThan,
|
||||
new ObjectPath(new Identifier('a'), new Property('value')),
|
||||
Or,
|
||||
new ObjectPath(new Identifier('a'), new Property(new Identifier('value'))),
|
||||
new Literal(10),
|
||||
Equal,
|
||||
And,
|
||||
new ObjectPath(new Identifier('a'), new Property('value')),
|
||||
Or,
|
||||
And,
|
||||
new ObjectPath(new Identifier('a'), new Property(new Identifier('value'))),
|
||||
new Literal(2),
|
||||
LesserThanEqual
|
||||
LesserThanEqual,
|
||||
Or,
|
||||
]
|
||||
assert.deepEqual(actual.values, expected)
|
||||
})
|
||||
|
|
|
@ -121,12 +121,18 @@ describe('query', () => {
|
|||
)
|
||||
|
||||
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: 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