Compare commits

..

3 commits

Author SHA1 Message Date
8844f326b0 fix objectpath references in tests 2024-11-28 01:25:49 -06:00
b2de794ef1 fix shunting yard 2024-11-28 00:01:39 -06:00
3a776ca1e1 fix shunting yard 2024-11-27 22:51:24 -06:00
6 changed files with 70 additions and 30 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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