Skip to content

Commit 61b45dc

Browse files
committed
edit: works for simple cases
TODO: tests better error handling
1 parent 1b1e895 commit 61b45dc

File tree

2 files changed

+128
-12
lines changed

2 files changed

+128
-12
lines changed

src/lib/structs.ts

+20-6
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ export interface query {
1515
/**
1616
* either an array of @param term interfaces or a string to be parsed by `parseQuery()`
1717
* */
18-
terms: term[] | string
18+
terms: Term[] | string
1919
/**
2020
* the name(s) of the document key to search. @default "text"
2121
* */
2222
key?: string[] | string
2323
/**
2424
* a list of @filter interfaces. All filters are implicitly AND'ed together.
2525
* */
26-
filters?: filter[]
26+
filters?: Filter[]
2727
}
2828

2929
/**
@@ -67,12 +67,12 @@ export interface collection {
6767
* | LEVENS | TODO | TODO| TODO |
6868
* ```
6969
* */
70-
export interface term {
70+
export interface Term {
7171
/**
7272
* must be one of [ 'phr', 'tok' ], corresponding to `PHRASE` and
7373
* `TOKENS` respectively.
7474
**/
75-
type: string
75+
type: Type
7676
/**
7777
* the search string
7878
* */
@@ -81,13 +81,27 @@ export interface term {
8181
* must be one of [ '+', '?', '-' ] corresponding to `ANDS`, `ORS`, and
8282
* `NOTS`, respectively.
8383
* */
84-
op: string
84+
op: Operator
85+
}
86+
87+
export enum Type {
88+
phrase = 'phrase',
89+
token = 'token',
90+
}
91+
92+
export enum Operator {
93+
AND = '+',
94+
'+' = AND,
95+
OR = '?',
96+
'?' = OR,
97+
NOT = '-',
98+
'-' = NOT,
8599
}
86100

87101
/**
88102
* passed to AQL `FILTER`
89103
* */
90-
export interface filter {
104+
export interface Filter {
91105
/** the arango document field name to filter on */
92106
field: string
93107
/** the high-level operator to filter by */

src/parse.ts

+108-6
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,133 @@
1-
import { term } from './lib/structs'
1+
import { Term, Type, Operator } from './lib/structs'
22

3-
export function parseQuery(queryString: string): term[] {
3+
export function parseQuery(queryString: string): Term[] {
44
const queryRgx: RegExp = /[+?-]?(["'(]).+?(\1|\))|[^"'()\s]+/g
55

66
let matches = queryString.match(queryRgx)
77
if (!matches) return []
88

99
return matches.map((match) => {
1010
/* strip op */
11-
let op = '?'
11+
let op = Operator.OR
1212
if (/[+?-]/.test(match[0])) {
13-
op = match[0]
13+
op = Operator[match[0]]
1414
match = match.substring(1)
1515
}
1616

1717
if (match[0] == '"' || match[0] == "'") {
1818
return {
19-
type: 'phr',
19+
type: Type.phrase,
2020
val: match,
2121
op,
2222
}
2323
} else {
2424
return {
25-
type: 'tok',
25+
type: Type.token,
2626
val: match,
2727
op,
2828
}
2929
}
3030
})
3131
}
32+
33+
export function parseQueryEXP(queryString: string): Term[] {
34+
return lex(queryString)
35+
}
36+
37+
class Char {
38+
char: string
39+
constructor(ch: string) {
40+
this.char = ch
41+
}
42+
43+
isSpace() {
44+
return /\s/.test(this.char)
45+
}
46+
isOperator() {
47+
return /[+-?]/.test(this.char)
48+
}
49+
isQuote() {
50+
return /"|'/.test(this.char)
51+
}
52+
}
53+
54+
function lex(queryString: string) {
55+
const tokens = []
56+
let curToken = ''
57+
let curTokenType: Type = Type.token
58+
let curOperator: Operator = Operator.OR
59+
let isToken = false
60+
61+
for (let i = 0; i < queryString.length; i++) {
62+
const ch = new Char(queryString[i])
63+
if (ch.isSpace()) {
64+
if (curToken.length) {
65+
if (curTokenType === Type.token) {
66+
isToken = true
67+
} else {
68+
curToken += ch.char
69+
}
70+
}
71+
} else if (ch.isOperator()) {
72+
if (curToken.length) {
73+
throw new ParseError(
74+
ch.char,
75+
i,
76+
`Logical operator must precede query term.`,
77+
)
78+
}
79+
curOperator = Operator[ch.char]
80+
} else if (ch.isQuote()) {
81+
if (curToken.length && curTokenType === Type.phrase) {
82+
isToken = true
83+
} else {
84+
curTokenType = Type.phrase
85+
}
86+
} else {
87+
curToken += ch.char
88+
}
89+
90+
if (isToken) {
91+
resetCur()
92+
}
93+
}
94+
if (curToken.length) {
95+
resetCur()
96+
}
97+
98+
function resetCur() {
99+
tokens.push({
100+
type: curTokenType,
101+
val: curToken,
102+
op: curOperator,
103+
})
104+
curToken = ''
105+
curTokenType = Type.token
106+
curOperator = Operator.OR
107+
isToken = false
108+
}
109+
110+
return tokens
111+
}
112+
113+
export class ParseError extends Error {
114+
char: string
115+
pos: number
116+
constructor(char: string, pos: number, message: string) {
117+
super(message)
118+
this.char = char
119+
this.pos = pos
120+
}
121+
122+
print() {
123+
return `err: “ ${this.char} ” in position ${this.pos} ${this.message}`
124+
}
125+
// U+201C “
126+
// U+201D ”
127+
}
128+
129+
try {
130+
console.log(parseQueryEXP('"+hello -there" -apple'))
131+
} catch (e) {
132+
console.error(e)
133+
}

0 commit comments

Comments
 (0)