From f30717a9fa4e38a294893213aeaf027547f47146 Mon Sep 17 00:00:00 2001 From: kitsunecafe Date: Sun, 10 Nov 2024 15:25:26 -0600 Subject: [PATCH] add aliasing and more tests :3 --- src/query/common.js | 35 +++++++++++++++++++++-------------- src/query/match.js | 19 ++++++++----------- src/query/return.js | 18 ++++++++---------- src/query/types.js | 36 ++++++++++++++++++++++++++++-------- tests/query/common.test.js | 36 ++++++++++++++++++++++++++++++++++-- tests/query/return.test.js | 4 ++-- 6 files changed, 101 insertions(+), 47 deletions(-) diff --git a/src/query/common.js b/src/query/common.js index df80b55..88ce614 100644 --- a/src/query/common.js +++ b/src/query/common.js @@ -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) + diff --git a/src/query/match.js b/src/query/match.js index bfae9f3..075b774 100644 --- a/src/query/match.js +++ b/src/query/match.js @@ -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) ) diff --git a/src/query/return.js b/src/query/return.js index 29f5708..9d70671 100644 --- a/src/query/return.js +++ b/src/query/return.js @@ -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) diff --git a/src/query/types.js b/src/query/types.js index eedf341..031868b 100644 --- a/src/query/types.js +++ b/src/query/types.js @@ -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 + } +} diff --git a/tests/query/common.test.js b/tests/query/common.test.js index 31cd17d..2d79f31 100644 --- a/tests/query/common.test.js +++ b/tests/query/common.test.js @@ -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')) + }) }) diff --git a/tests/query/return.test.js b/tests/query/return.test.js index 0d83ce9..b6e8b57 100644 --- a/tests/query/return.test.js +++ b/tests/query/return.test.js @@ -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', () => {