search-input-query-parser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/first-pass-parser.js +77 -0
- package/dist/cjs/lexer.js +322 -0
- package/dist/cjs/parse-in-values.js +65 -0
- package/dist/cjs/parse-primary.js +154 -0
- package/dist/cjs/parse-range-expression.js +174 -0
- package/dist/cjs/parser.js +85 -0
- package/dist/cjs/search-query-to-sql.js +346 -0
- package/dist/cjs/transform-to-expression.js +130 -0
- package/dist/cjs/validate-expression-fields.js +244 -0
- package/dist/cjs/validate-in-expression.js +33 -0
- package/dist/cjs/validate-string.js +65 -0
- package/dist/cjs/validate-wildcard.js +40 -0
- package/dist/cjs/validator.js +34 -0
- package/dist/esm/first-pass-parser.js +73 -0
- package/dist/esm/lexer.js +315 -0
- package/dist/esm/parse-in-values.js +61 -0
- package/dist/esm/parse-primary.js +147 -0
- package/dist/esm/parse-range-expression.js +170 -0
- package/dist/esm/parser.js +81 -0
- package/dist/esm/search-query-to-sql.js +341 -0
- package/dist/esm/transform-to-expression.js +126 -0
- package/dist/esm/validate-expression-fields.js +240 -0
- package/dist/esm/validate-in-expression.js +29 -0
- package/dist/esm/validate-string.js +61 -0
- package/dist/esm/validate-wildcard.js +36 -0
- package/dist/esm/validator.js +30 -0
- package/dist/types/first-pass-parser.d.ts +40 -0
- package/dist/types/lexer.d.ts +27 -0
- package/dist/types/parse-in-values.d.ts +3 -0
- package/dist/types/parse-primary.d.ts +6 -0
- package/dist/types/parse-range-expression.d.ts +2 -0
- package/dist/types/parser.d.ts +68 -0
- package/dist/types/search-query-to-sql.d.ts +18 -0
- package/dist/types/transform-to-expression.d.ts +3 -0
- package/dist/types/validate-expression-fields.d.ts +4 -0
- package/dist/types/validate-in-expression.d.ts +3 -0
- package/dist/types/validate-string.d.ts +3 -0
- package/dist/types/validate-wildcard.d.ts +3 -0
- package/dist/types/validator.d.ts +8 -0
- package/package.json +52 -0
- package/src/first-pass-parser.test.ts +441 -0
- package/src/first-pass-parser.ts +144 -0
- package/src/lexer.test.ts +439 -0
- package/src/lexer.ts +387 -0
- package/src/parse-in-values.ts +74 -0
- package/src/parse-primary.ts +179 -0
- package/src/parse-range-expression.ts +187 -0
- package/src/parser.test.ts +982 -0
- package/src/parser.ts +219 -0
- package/src/search-query-to-sql.test.ts +503 -0
- package/src/search-query-to-sql.ts +506 -0
- package/src/transform-to-expression.ts +153 -0
- package/src/validate-expression-fields.ts +296 -0
- package/src/validate-in-expression.ts +36 -0
- package/src/validate-string.ts +73 -0
- package/src/validate-wildcard.ts +45 -0
- package/src/validator.test.ts +192 -0
- package/src/validator.ts +53 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { tokenize, createStream } from "./lexer";
|
|
3
|
+
import { parseExpression, FirstPassExpression } from "./first-pass-parser";
|
|
4
|
+
|
|
5
|
+
describe("First Pass Parser", () => {
|
|
6
|
+
const parseQuery = (input: string): FirstPassExpression => {
|
|
7
|
+
const tokens = tokenize(input);
|
|
8
|
+
const stream = createStream(tokens);
|
|
9
|
+
return parseExpression(stream).result;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe("Primary Expression Parsing", () => {
|
|
13
|
+
test("parses simple string literals", () => {
|
|
14
|
+
const result = parseQuery("boots");
|
|
15
|
+
expect(result).toEqual({
|
|
16
|
+
type: "STRING",
|
|
17
|
+
value: "boots",
|
|
18
|
+
quoted: false,
|
|
19
|
+
position: 0,
|
|
20
|
+
length: 5,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("parses quoted string literals", () => {
|
|
25
|
+
const result = parseQuery('"red shoes"');
|
|
26
|
+
expect(result).toEqual({
|
|
27
|
+
type: "STRING",
|
|
28
|
+
value: '"red shoes"',
|
|
29
|
+
position: 0,
|
|
30
|
+
quoted: true,
|
|
31
|
+
length: 11,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("parses field:value pairs", () => {
|
|
36
|
+
const result = parseQuery("color:red");
|
|
37
|
+
expect(result).toEqual({
|
|
38
|
+
type: "STRING",
|
|
39
|
+
value: "color:red",
|
|
40
|
+
quoted: false,
|
|
41
|
+
position: 0,
|
|
42
|
+
length: 9,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("parses quoted field values", () => {
|
|
47
|
+
const result = parseQuery('status:"in progress"');
|
|
48
|
+
expect(result).toEqual({
|
|
49
|
+
type: "STRING",
|
|
50
|
+
value: 'status:"in progress"',
|
|
51
|
+
quoted: true,
|
|
52
|
+
position: 0,
|
|
53
|
+
length: 20,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("Operator Precedence", () => {
|
|
59
|
+
test("handles implicit AND with higher precedence than OR", () => {
|
|
60
|
+
const result = parseQuery("a b OR c");
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
type: "OR",
|
|
63
|
+
left: {
|
|
64
|
+
type: "AND",
|
|
65
|
+
left: {
|
|
66
|
+
type: "STRING",
|
|
67
|
+
value: "a",
|
|
68
|
+
quoted: false,
|
|
69
|
+
position: 0,
|
|
70
|
+
length: 1,
|
|
71
|
+
},
|
|
72
|
+
right: {
|
|
73
|
+
type: "STRING",
|
|
74
|
+
value: "b",
|
|
75
|
+
quoted: false,
|
|
76
|
+
position: 2,
|
|
77
|
+
length: 1,
|
|
78
|
+
},
|
|
79
|
+
position: 2,
|
|
80
|
+
length: 1,
|
|
81
|
+
},
|
|
82
|
+
right: {
|
|
83
|
+
type: "STRING",
|
|
84
|
+
value: "c",
|
|
85
|
+
quoted: false,
|
|
86
|
+
position: 7,
|
|
87
|
+
length: 1,
|
|
88
|
+
},
|
|
89
|
+
position: 4,
|
|
90
|
+
length: 2,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("handles explicit AND with higher precedence than OR", () => {
|
|
95
|
+
const result = parseQuery("a AND b OR c");
|
|
96
|
+
expect(result).toEqual({
|
|
97
|
+
type: "OR",
|
|
98
|
+
left: {
|
|
99
|
+
type: "AND",
|
|
100
|
+
left: {
|
|
101
|
+
type: "STRING",
|
|
102
|
+
value: "a",
|
|
103
|
+
quoted: false,
|
|
104
|
+
position: 0,
|
|
105
|
+
length: 1,
|
|
106
|
+
},
|
|
107
|
+
right: {
|
|
108
|
+
type: "STRING",
|
|
109
|
+
value: "b",
|
|
110
|
+
quoted: false,
|
|
111
|
+
position: 6,
|
|
112
|
+
length: 1,
|
|
113
|
+
},
|
|
114
|
+
position: 2,
|
|
115
|
+
length: 3,
|
|
116
|
+
},
|
|
117
|
+
right: {
|
|
118
|
+
type: "STRING",
|
|
119
|
+
value: "c",
|
|
120
|
+
quoted: false,
|
|
121
|
+
position: 11,
|
|
122
|
+
length: 1,
|
|
123
|
+
},
|
|
124
|
+
position: 8,
|
|
125
|
+
length: 2,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("handles complex precedence with parentheses", () => {
|
|
130
|
+
const result = parseQuery("(a OR b) AND c");
|
|
131
|
+
expect(result).toEqual({
|
|
132
|
+
type: "AND",
|
|
133
|
+
left: {
|
|
134
|
+
type: "OR",
|
|
135
|
+
left: {
|
|
136
|
+
type: "STRING",
|
|
137
|
+
value: "a",
|
|
138
|
+
quoted: false,
|
|
139
|
+
position: 1,
|
|
140
|
+
length: 1,
|
|
141
|
+
},
|
|
142
|
+
right: {
|
|
143
|
+
type: "STRING",
|
|
144
|
+
value: "b",
|
|
145
|
+
quoted: false,
|
|
146
|
+
position: 6,
|
|
147
|
+
length: 1,
|
|
148
|
+
},
|
|
149
|
+
position: 3,
|
|
150
|
+
length: 2,
|
|
151
|
+
},
|
|
152
|
+
right: {
|
|
153
|
+
type: "STRING",
|
|
154
|
+
value: "c",
|
|
155
|
+
quoted: false,
|
|
156
|
+
position: 13,
|
|
157
|
+
length: 1,
|
|
158
|
+
},
|
|
159
|
+
position: 9,
|
|
160
|
+
length: 3,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("NOT Expression Parsing", () => {
|
|
166
|
+
test("parses simple NOT expressions", () => {
|
|
167
|
+
const result = parseQuery("NOT test");
|
|
168
|
+
expect(result).toEqual({
|
|
169
|
+
type: "NOT",
|
|
170
|
+
expression: {
|
|
171
|
+
type: "STRING",
|
|
172
|
+
value: "test",
|
|
173
|
+
quoted: false,
|
|
174
|
+
position: 4,
|
|
175
|
+
length: 4,
|
|
176
|
+
},
|
|
177
|
+
position: 0,
|
|
178
|
+
length: 3,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("parses NOT with parentheses", () => {
|
|
183
|
+
const result = parseQuery("NOT (test)");
|
|
184
|
+
expect(result).toEqual({
|
|
185
|
+
type: "NOT",
|
|
186
|
+
expression: {
|
|
187
|
+
type: "STRING",
|
|
188
|
+
value: "test",
|
|
189
|
+
quoted: false,
|
|
190
|
+
position: 5,
|
|
191
|
+
length: 4,
|
|
192
|
+
},
|
|
193
|
+
position: 0,
|
|
194
|
+
length: 3,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("parses NOT with field:value pairs", () => {
|
|
199
|
+
const result = parseQuery("NOT status:active");
|
|
200
|
+
expect(result).toEqual({
|
|
201
|
+
type: "NOT",
|
|
202
|
+
expression: {
|
|
203
|
+
type: "STRING",
|
|
204
|
+
value: "status:active",
|
|
205
|
+
quoted: false,
|
|
206
|
+
position: 4,
|
|
207
|
+
length: 13,
|
|
208
|
+
},
|
|
209
|
+
position: 0,
|
|
210
|
+
length: 3,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("parses complex expressions with NOT", () => {
|
|
215
|
+
const result = parseQuery("boots AND NOT leather");
|
|
216
|
+
expect(result).toEqual({
|
|
217
|
+
type: "AND",
|
|
218
|
+
left: {
|
|
219
|
+
type: "STRING",
|
|
220
|
+
value: "boots",
|
|
221
|
+
quoted: false,
|
|
222
|
+
position: 0,
|
|
223
|
+
length: 5,
|
|
224
|
+
},
|
|
225
|
+
right: {
|
|
226
|
+
type: "NOT",
|
|
227
|
+
expression: {
|
|
228
|
+
type: "STRING",
|
|
229
|
+
value: "leather",
|
|
230
|
+
quoted: false,
|
|
231
|
+
position: 14,
|
|
232
|
+
length: 7,
|
|
233
|
+
},
|
|
234
|
+
position: 10,
|
|
235
|
+
length: 3,
|
|
236
|
+
},
|
|
237
|
+
position: 6,
|
|
238
|
+
length: 3,
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("Parentheses Handling", () => {
|
|
244
|
+
test("parses simple parenthesized expressions", () => {
|
|
245
|
+
const result = parseQuery("(boots)");
|
|
246
|
+
expect(result).toEqual({
|
|
247
|
+
type: "STRING",
|
|
248
|
+
value: "boots",
|
|
249
|
+
quoted: false,
|
|
250
|
+
position: 1,
|
|
251
|
+
length: 5,
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("parses nested parentheses", () => {
|
|
256
|
+
const result = parseQuery("((a OR b))");
|
|
257
|
+
expect(result).toEqual({
|
|
258
|
+
type: "OR",
|
|
259
|
+
left: {
|
|
260
|
+
type: "STRING",
|
|
261
|
+
value: "a",
|
|
262
|
+
quoted: false,
|
|
263
|
+
position: 2,
|
|
264
|
+
length: 1,
|
|
265
|
+
},
|
|
266
|
+
right: {
|
|
267
|
+
type: "STRING",
|
|
268
|
+
value: "b",
|
|
269
|
+
quoted: false,
|
|
270
|
+
position: 7,
|
|
271
|
+
length: 1,
|
|
272
|
+
},
|
|
273
|
+
position: 4,
|
|
274
|
+
length: 2,
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("handles multiple nested groups", () => {
|
|
279
|
+
const result = parseQuery("(a AND (b OR c))");
|
|
280
|
+
expect(result).toEqual({
|
|
281
|
+
type: "AND",
|
|
282
|
+
left: {
|
|
283
|
+
type: "STRING",
|
|
284
|
+
value: "a",
|
|
285
|
+
quoted: false,
|
|
286
|
+
position: 1,
|
|
287
|
+
length: 1,
|
|
288
|
+
},
|
|
289
|
+
right: {
|
|
290
|
+
type: "OR",
|
|
291
|
+
left: {
|
|
292
|
+
type: "STRING",
|
|
293
|
+
value: "b",
|
|
294
|
+
quoted: false,
|
|
295
|
+
position: 8,
|
|
296
|
+
length: 1,
|
|
297
|
+
},
|
|
298
|
+
right: {
|
|
299
|
+
type: "STRING",
|
|
300
|
+
value: "c",
|
|
301
|
+
quoted: false,
|
|
302
|
+
position: 13,
|
|
303
|
+
length: 1,
|
|
304
|
+
},
|
|
305
|
+
position: 10,
|
|
306
|
+
length: 2,
|
|
307
|
+
},
|
|
308
|
+
position: 3,
|
|
309
|
+
length: 3,
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe("Error Handling", () => {
|
|
315
|
+
// TODO: Uncomment these tests after implementing error handling
|
|
316
|
+
// test("throws error for unmatched right parenthesis", () => {
|
|
317
|
+
// expect(() => parseQuery("a)")).toThrow("Expected RPAREN");
|
|
318
|
+
// });
|
|
319
|
+
|
|
320
|
+
test("throws error for empty parentheses", () => {
|
|
321
|
+
expect(() => parseQuery("()")).toThrow('Unexpected ")"');
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("throws error for missing right parenthesis", () => {
|
|
325
|
+
expect(() => parseQuery("(a")).toThrow("Expected ')'");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("throws error for standalone operators", () => {
|
|
329
|
+
expect(() => parseQuery("AND")).toThrow("AND is a reserved word");
|
|
330
|
+
expect(() => parseQuery("OR")).toThrow("OR is a reserved word");
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("throws error for missing right operand", () => {
|
|
334
|
+
expect(() => parseQuery("a AND")).toThrow("Unexpected token");
|
|
335
|
+
expect(() => parseQuery("a OR")).toThrow("Unexpected token");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("throws error for consecutive operators", () => {
|
|
339
|
+
expect(() => parseQuery("a AND AND b")).toThrow("AND is a reserved word");
|
|
340
|
+
expect(() => parseQuery("a OR OR b")).toThrow("OR is a reserved word");
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe("Complex Expressions", () => {
|
|
345
|
+
test("parses complex nested expressions with mixed operators", () => {
|
|
346
|
+
const result = parseQuery("(a AND b) OR (c AND (d OR e))");
|
|
347
|
+
expect(result).toEqual({
|
|
348
|
+
type: "OR",
|
|
349
|
+
left: {
|
|
350
|
+
type: "AND",
|
|
351
|
+
left: {
|
|
352
|
+
type: "STRING",
|
|
353
|
+
value: "a",
|
|
354
|
+
quoted: false,
|
|
355
|
+
position: 1,
|
|
356
|
+
length: 1,
|
|
357
|
+
},
|
|
358
|
+
right: {
|
|
359
|
+
type: "STRING",
|
|
360
|
+
value: "b",
|
|
361
|
+
quoted: false,
|
|
362
|
+
position: 7,
|
|
363
|
+
length: 1,
|
|
364
|
+
},
|
|
365
|
+
position: 3,
|
|
366
|
+
length: 3,
|
|
367
|
+
},
|
|
368
|
+
right: {
|
|
369
|
+
type: "AND",
|
|
370
|
+
left: {
|
|
371
|
+
type: "STRING",
|
|
372
|
+
value: "c",
|
|
373
|
+
quoted: false,
|
|
374
|
+
position: 14,
|
|
375
|
+
length: 1,
|
|
376
|
+
},
|
|
377
|
+
right: {
|
|
378
|
+
type: "OR",
|
|
379
|
+
left: {
|
|
380
|
+
type: "STRING",
|
|
381
|
+
value: "d",
|
|
382
|
+
quoted: false,
|
|
383
|
+
position: 21,
|
|
384
|
+
length: 1,
|
|
385
|
+
},
|
|
386
|
+
right: {
|
|
387
|
+
type: "STRING",
|
|
388
|
+
value: "e",
|
|
389
|
+
quoted: false,
|
|
390
|
+
position: 26,
|
|
391
|
+
length: 1,
|
|
392
|
+
},
|
|
393
|
+
position: 23,
|
|
394
|
+
length: 2,
|
|
395
|
+
},
|
|
396
|
+
position: 16,
|
|
397
|
+
length: 3,
|
|
398
|
+
},
|
|
399
|
+
position: 10,
|
|
400
|
+
length: 2,
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("parses complex field-value expressions", () => {
|
|
405
|
+
const result = parseQuery(
|
|
406
|
+
'category:"winter boots" AND (color:black OR color:brown)'
|
|
407
|
+
);
|
|
408
|
+
expect(result).toEqual({
|
|
409
|
+
type: "AND",
|
|
410
|
+
left: {
|
|
411
|
+
type: "STRING",
|
|
412
|
+
value: 'category:"winter boots"',
|
|
413
|
+
quoted: true,
|
|
414
|
+
position: 0,
|
|
415
|
+
length: 23,
|
|
416
|
+
},
|
|
417
|
+
right: {
|
|
418
|
+
type: "OR",
|
|
419
|
+
left: {
|
|
420
|
+
type: "STRING",
|
|
421
|
+
value: "color:black",
|
|
422
|
+
quoted: false,
|
|
423
|
+
position: 29,
|
|
424
|
+
length: 11,
|
|
425
|
+
},
|
|
426
|
+
right: {
|
|
427
|
+
type: "STRING",
|
|
428
|
+
value: "color:brown",
|
|
429
|
+
quoted: false,
|
|
430
|
+
position: 44,
|
|
431
|
+
length: 11,
|
|
432
|
+
},
|
|
433
|
+
position: 41,
|
|
434
|
+
length: 2,
|
|
435
|
+
},
|
|
436
|
+
position: 24,
|
|
437
|
+
length: 3,
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { TokenType, TokenStream, currentToken, advanceStream } from "./lexer";
|
|
2
|
+
import { parsePrimary } from "./parse-primary";
|
|
3
|
+
|
|
4
|
+
// First Pass AST types (from tokenizer/parser)
|
|
5
|
+
export type PositionLength = {
|
|
6
|
+
position: number;
|
|
7
|
+
length: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type StringLiteral = {
|
|
11
|
+
readonly type: "STRING";
|
|
12
|
+
readonly value: string;
|
|
13
|
+
readonly quoted: boolean;
|
|
14
|
+
} & PositionLength;
|
|
15
|
+
|
|
16
|
+
export type WildcardPattern = {
|
|
17
|
+
readonly type: "WILDCARD";
|
|
18
|
+
readonly prefix: string;
|
|
19
|
+
readonly quoted: boolean;
|
|
20
|
+
} & PositionLength;
|
|
21
|
+
|
|
22
|
+
export type AndExpression = {
|
|
23
|
+
readonly type: "AND";
|
|
24
|
+
readonly left: FirstPassExpression;
|
|
25
|
+
readonly right: FirstPassExpression;
|
|
26
|
+
} & PositionLength;
|
|
27
|
+
|
|
28
|
+
export type OrExpression = {
|
|
29
|
+
readonly type: "OR";
|
|
30
|
+
readonly left: FirstPassExpression;
|
|
31
|
+
readonly right: FirstPassExpression;
|
|
32
|
+
} & PositionLength;
|
|
33
|
+
|
|
34
|
+
export type NotExpression = {
|
|
35
|
+
readonly type: "NOT";
|
|
36
|
+
readonly expression: FirstPassExpression;
|
|
37
|
+
} & PositionLength;
|
|
38
|
+
|
|
39
|
+
export type InExpression = {
|
|
40
|
+
readonly type: "IN";
|
|
41
|
+
readonly field: string;
|
|
42
|
+
readonly values: string[];
|
|
43
|
+
} & PositionLength;
|
|
44
|
+
|
|
45
|
+
export type FirstPassExpression =
|
|
46
|
+
| StringLiteral
|
|
47
|
+
| WildcardPattern
|
|
48
|
+
| AndExpression
|
|
49
|
+
| OrExpression
|
|
50
|
+
| NotExpression
|
|
51
|
+
| InExpression;
|
|
52
|
+
|
|
53
|
+
// Parser functions
|
|
54
|
+
export interface ParseResult<T> {
|
|
55
|
+
readonly result: T;
|
|
56
|
+
readonly stream: TokenStream;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getOperatorPrecedence = (type: TokenType): number =>
|
|
60
|
+
type === TokenType.AND ? 2 : type === TokenType.OR ? 1 : 0;
|
|
61
|
+
|
|
62
|
+
export const parseExpression = (
|
|
63
|
+
stream: TokenStream,
|
|
64
|
+
minPrecedence: number = 0
|
|
65
|
+
): ParseResult<FirstPassExpression> => {
|
|
66
|
+
const token = currentToken(stream);
|
|
67
|
+
if (token.type === TokenType.STRING && token.value === "*") {
|
|
68
|
+
return {
|
|
69
|
+
result: {
|
|
70
|
+
type: "WILDCARD",
|
|
71
|
+
prefix: "",
|
|
72
|
+
quoted: false,
|
|
73
|
+
position: token.position,
|
|
74
|
+
length: token.length,
|
|
75
|
+
},
|
|
76
|
+
stream: advanceStream(stream),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let result = parsePrimary(stream);
|
|
81
|
+
|
|
82
|
+
while (true) {
|
|
83
|
+
const token = currentToken(result.stream);
|
|
84
|
+
if (token.type === TokenType.EOF) break;
|
|
85
|
+
|
|
86
|
+
if (token.type === TokenType.AND || token.type === TokenType.OR) {
|
|
87
|
+
const precedence = getOperatorPrecedence(token.type);
|
|
88
|
+
if (precedence < minPrecedence) break;
|
|
89
|
+
|
|
90
|
+
const operator = token.type;
|
|
91
|
+
const nextStream = advanceStream(result.stream);
|
|
92
|
+
|
|
93
|
+
const nextToken = currentToken(nextStream);
|
|
94
|
+
if (nextToken.type === TokenType.EOF) {
|
|
95
|
+
throw {
|
|
96
|
+
message: `Unexpected token: ${token.value}`,
|
|
97
|
+
position: token.position,
|
|
98
|
+
length: token.length,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const right = parseExpression(nextStream, precedence + 1);
|
|
103
|
+
|
|
104
|
+
result = {
|
|
105
|
+
result: {
|
|
106
|
+
type: operator,
|
|
107
|
+
left: result.result,
|
|
108
|
+
right: right.result,
|
|
109
|
+
position: token.position,
|
|
110
|
+
length: token.length,
|
|
111
|
+
},
|
|
112
|
+
stream: right.stream,
|
|
113
|
+
};
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
token.type === TokenType.STRING ||
|
|
119
|
+
token.type === TokenType.QUOTED_STRING ||
|
|
120
|
+
token.type === TokenType.LPAREN ||
|
|
121
|
+
token.type === TokenType.NOT
|
|
122
|
+
) {
|
|
123
|
+
const precedence = getOperatorPrecedence(TokenType.AND);
|
|
124
|
+
if (precedence < minPrecedence) break;
|
|
125
|
+
|
|
126
|
+
const right = parseExpression(result.stream, precedence + 1);
|
|
127
|
+
result = {
|
|
128
|
+
result: {
|
|
129
|
+
type: TokenType.AND,
|
|
130
|
+
left: result.result,
|
|
131
|
+
right: right.result,
|
|
132
|
+
position: token.position,
|
|
133
|
+
length: token.length,
|
|
134
|
+
},
|
|
135
|
+
stream: right.stream,
|
|
136
|
+
};
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
};
|