add aliasing and more tests :3

This commit is contained in:
Rowan 2024-11-10 15:25:26 -06:00
parent f154a5fc9c
commit f30717a9fa
6 changed files with 101 additions and 47 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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', () => {