add aliasing and more tests :3
This commit is contained in:
parent
f154a5fc9c
commit
f30717a9fa
6 changed files with 101 additions and 47 deletions
|
@ -1,5 +1,6 @@
|
|||
import { join } from '../fn.js'
|
||||
import { alpha, alphanumeric, any, char, digit, list, many, map, maybe, noCaseString, separated, seq, skip, string, until } from '../parser.js'
|
||||
import { Alias, Literal, ObjectPath } from './types.js'
|
||||
|
||||
export const Symbol = Object.freeze({
|
||||
Bracket: Object.freeze({
|
||||
|
@ -30,22 +31,28 @@ export const Symbol = Object.freeze({
|
|||
Quote: char('"')
|
||||
})
|
||||
|
||||
|
||||
|
||||
export const word = value => seq(skip(ws), value, skip(ws))
|
||||
export const collect = parser => map(join(''), parser)
|
||||
export const quoted = collect(seq(skip(Symbol.Quote), until(Symbol.Quote), skip(Symbol.Quote)))
|
||||
export const number = seq(digit, many(digit))
|
||||
export const signed = seq(maybe(any(Symbol.Plus, Symbol.Hyphen)), number)
|
||||
export const toBoolean = v => v === 'false' ? false : true
|
||||
|
||||
export const Literal = Object.freeze({
|
||||
Float: map(([n]) => parseFloat(n, 10), collect(seq(signed, Symbol.Period, number))),
|
||||
Integer: map(([n]) => parseInt(n, 10), collect(signed)),
|
||||
String: quoted,
|
||||
Boolean: map(([v]) => toBoolean(v), any(string('true'), string('false')))
|
||||
})
|
||||
|
||||
export const literal = any(...Object.values(Literal))
|
||||
export const ws = skip(many(Symbol.Space))
|
||||
export const identifier = map(join(''), seq(alpha, many(alphanumeric)))
|
||||
export const accessor = seq(identifier, list(Symbol.Period, identifier))
|
||||
export const identifier = collect(seq(alpha, many(alphanumeric)))
|
||||
|
||||
const float = map(([n]) => parseFloat(n, 10), collect(seq(signed, Symbol.Period, number)))
|
||||
const integer = map(([n]) => parseInt(n, 10), collect(signed))
|
||||
const str = quoted
|
||||
|
||||
const toBoolean = v => v === 'false' ? false : true
|
||||
const boolean = map(([v]) => toBoolean(v), any(string('true'), string('false')))
|
||||
|
||||
const property = seq(skip(Symbol.Period), identifier)
|
||||
const accessor = map(x => new ObjectPath(...x), seq(identifier, property, many(property)))
|
||||
|
||||
export const literal = map(([x]) => new Literal(x), any(float, integer, str, boolean))
|
||||
export const baseValue = any(literal, accessor, identifier)
|
||||
|
||||
const as = noCaseString('as')
|
||||
export const alias = map(([value, name]) => new Alias(value, name), seq(baseValue, word(skip(as)), identifier))
|
||||
export const value = any(alias, baseValue)
|
||||
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import { Symbol, ws } from './common.js'
|
||||
import { Identifier, Node, Edge, KeyValuePair } from './types.js'
|
||||
import { collect, Symbol, ws } from './common.js'
|
||||
import { Node, Edge, KeyValuePair, Label, Name } from './types.js'
|
||||
import { curry, join } from '../fn.js'
|
||||
import { alpha, alphanumeric, many, maybe, map, parse, seq, skip, between, noCaseString, separated, until, list } from '../parser.js'
|
||||
|
||||
const { Bracket, Colon, Comma, Hyphen, Quote } = Symbol
|
||||
|
||||
const name = map(
|
||||
join(''),
|
||||
seq(alpha, many(alphanumeric))
|
||||
([x]) => new Name(x),
|
||||
collect(seq(alpha, many(alphanumeric)))
|
||||
)
|
||||
const label = seq(skip(Colon), name)
|
||||
|
||||
const label = map(([x]) => new Label(x), seq(skip(Colon), name))
|
||||
|
||||
const trim = curry((parser, state) => (
|
||||
between(ws, parser, ws, state)
|
||||
|
@ -27,14 +28,10 @@ const kvp = map(
|
|||
const kvps = list(trim(Comma), kvp)
|
||||
export const properties = bracketed(kvps, Bracket.Curly)
|
||||
|
||||
const id = map(
|
||||
([name, label]) => new Identifier(name, label),
|
||||
seq(maybe(name), label)
|
||||
)
|
||||
|
||||
const id = seq(maybe(name), label)
|
||||
|
||||
export const node = map(
|
||||
([id, ...properties]) => new Node(id, properties),
|
||||
([name, label, ...properties]) => new Node(name, label, properties),
|
||||
bracketed(seq(id, ws, maybe(properties)), Bracket.Round)
|
||||
)
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import { ReturnValues } from './types.js'
|
||||
import { list, map, maybe, noCaseString, parse, seq, skip } from '../parser.js'
|
||||
import { accessor, identifier, Symbol, ws } from './common.js'
|
||||
|
||||
const as = noCaseString('as')
|
||||
const alias = seq(as, identifier)
|
||||
const aliasId = seq(accessor, maybe(alias))
|
||||
import { list, map, noCaseString, seq, skip } from '../parser.js'
|
||||
import { value, Symbol, ws } from './common.js'
|
||||
|
||||
const keyword = noCaseString('return')
|
||||
|
||||
const params = map(
|
||||
values => new ReturnValues(values),
|
||||
seq(list(seq(Symbol.Comma, ws), aliasId))
|
||||
)
|
||||
//const params = map(
|
||||
// values => new ReturnValues(values),
|
||||
// seq(list(seq(Symbol.Comma, ws), alias))
|
||||
//)
|
||||
|
||||
const params = seq(list(seq(ws, Symbol.Comma, ws), value))
|
||||
|
||||
export const statement = seq(skip(keyword), ws, params)
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
export class Identifier {
|
||||
constructor(name, label) {
|
||||
if (label == null) {
|
||||
this.label = name
|
||||
} else {
|
||||
this.name = name
|
||||
this.label = label
|
||||
}
|
||||
export class Name {
|
||||
constructor(value) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
export class Label {
|
||||
constructor(value) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,9 +49,28 @@ export class KeyValuePair {
|
|||
}
|
||||
}
|
||||
|
||||
export class Literal {
|
||||
constructor(value) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
export class Identifier {
|
||||
constructor(value) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
export class Alias {
|
||||
constructor(value, alias) {
|
||||
this.value = value
|
||||
this.alias = alias
|
||||
}
|
||||
}
|
||||
|
||||
export class ObjectPath {
|
||||
constructor(value, ...path) {
|
||||
this.value = value
|
||||
this.path = path
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
import { digit, many, parse, seq } from '../../src/parser.js'
|
||||
import { parse } from '../../src/parser.js'
|
||||
import * as Common from '../../src/query/common.js'
|
||||
import { Alias, ObjectPath } from '../../src/query/types.js'
|
||||
|
||||
describe('common parser library', () => {
|
||||
it('literals should match literal types', () => {
|
||||
|
@ -15,7 +16,7 @@ describe('common parser library', () => {
|
|||
const negFloat = parse(Common.literal, '-12.01')
|
||||
const string = parse(Common.literal, '"akjsdfuaio"')
|
||||
|
||||
const v = r => r.value[0][0]
|
||||
const v = r => r.value[0][0].value
|
||||
assert.strictEqual(v(tBool), true)
|
||||
assert.strictEqual(v(fBool), false)
|
||||
assert.strictEqual(v(uint), 5)
|
||||
|
@ -26,5 +27,36 @@ describe('common parser library', () => {
|
|||
assert.strictEqual(v(negFloat), -12.01)
|
||||
assert.strictEqual(v(string), 'akjsdfuaio')
|
||||
})
|
||||
|
||||
it('value should match literals', () => {
|
||||
const bool = parse(Common.baseValue, 'false')
|
||||
const uint = parse(Common.baseValue, '11')
|
||||
const float = parse(Common.baseValue, '0.15')
|
||||
const str = parse(Common.baseValue, '"abc"')
|
||||
|
||||
const v = r => r.value[0][0].value
|
||||
assert.strictEqual(v(bool), false)
|
||||
assert.strictEqual(v(uint), 11)
|
||||
assert.strictEqual(v(float), 0.15)
|
||||
assert.strictEqual(v(str), 'abc')
|
||||
})
|
||||
|
||||
it('value should match variables', () => {
|
||||
const identifier = parse(Common.baseValue, 'test')
|
||||
const accessor = parse(Common.baseValue, 'test.value')
|
||||
const v = r => r.value[0][0]
|
||||
assert.strictEqual(v(identifier), 'test')
|
||||
assert.deepEqual(v(accessor), new ObjectPath('test', 'value'))
|
||||
})
|
||||
|
||||
it('aliases should work i hope', () => {
|
||||
const noAlias = parse(Common.value, 'test')
|
||||
const aliased1 = parse(Common.value, 'crybaby AS cb')
|
||||
const aliased2 = parse(Common.value, 'property.name AS name')
|
||||
const v = r => r.value[0][0]
|
||||
assert.strictEqual(v(noAlias), 'test')
|
||||
assert.deepEqual(v(aliased1), new Alias('crybaby', 'cb'))
|
||||
assert.deepEqual(v(aliased2), new Alias(new ObjectPath('property', 'name'), 'name'))
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -2,14 +2,14 @@ import { describe, it } from 'node:test'
|
|||
import assert from 'node:assert'
|
||||
import { parse } from '../../src/parser.js'
|
||||
import { statement } from '../../src/query/return.js'
|
||||
import { Alias } from '../../src/query/types.js'
|
||||
|
||||
describe('return parser', () => {
|
||||
it('should collect a single value for a query to return', () => {
|
||||
const result = parse(statement, 'RETURN folklore AS f')
|
||||
console.log(result.error)
|
||||
assert(result.isOk())
|
||||
const [[selected]] = result.value
|
||||
assert.deepStrictEqual(selected, ['folklore'])
|
||||
assert.deepStrictEqual(selected, new Alias('folklore', 'f'))
|
||||
})
|
||||
|
||||
//it('should collect multiple values for a query to return', () => {
|
||||
|
|
Loading…
Reference in a new issue