|
1 |
| -import { term } from './lib/structs' |
| 1 | +import { Term, Type, Operator } from './lib/structs' |
2 | 2 |
|
3 |
| -export function parseQuery(queryString: string): term[] { |
| 3 | +export function parseQuery(queryString: string): Term[] { |
4 | 4 | const queryRgx: RegExp = /[+?-]?(["'(]).+?(\1|\))|[^"'()\s]+/g
|
5 | 5 |
|
6 | 6 | let matches = queryString.match(queryRgx)
|
7 | 7 | if (!matches) return []
|
8 | 8 |
|
9 | 9 | return matches.map((match) => {
|
10 | 10 | /* strip op */
|
11 |
| - let op = '?' |
| 11 | + let op = Operator.OR |
12 | 12 | if (/[+?-]/.test(match[0])) {
|
13 |
| - op = match[0] |
| 13 | + op = Operator[match[0]] |
14 | 14 | match = match.substring(1)
|
15 | 15 | }
|
16 | 16 |
|
17 | 17 | if (match[0] == '"' || match[0] == "'") {
|
18 | 18 | return {
|
19 |
| - type: 'phr', |
| 19 | + type: Type.phrase, |
20 | 20 | val: match,
|
21 | 21 | op,
|
22 | 22 | }
|
23 | 23 | } else {
|
24 | 24 | return {
|
25 |
| - type: 'tok', |
| 25 | + type: Type.token, |
26 | 26 | val: match,
|
27 | 27 | op,
|
28 | 28 | }
|
29 | 29 | }
|
30 | 30 | })
|
31 | 31 | }
|
| 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