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,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseExpression = void 0;
|
|
4
|
+
const lexer_1 = require("./lexer");
|
|
5
|
+
const parse_primary_1 = require("./parse-primary");
|
|
6
|
+
const getOperatorPrecedence = (type) => type === lexer_1.TokenType.AND ? 2 : type === lexer_1.TokenType.OR ? 1 : 0;
|
|
7
|
+
const parseExpression = (stream, minPrecedence = 0) => {
|
|
8
|
+
const token = (0, lexer_1.currentToken)(stream);
|
|
9
|
+
if (token.type === lexer_1.TokenType.STRING && token.value === "*") {
|
|
10
|
+
return {
|
|
11
|
+
result: {
|
|
12
|
+
type: "WILDCARD",
|
|
13
|
+
prefix: "",
|
|
14
|
+
quoted: false,
|
|
15
|
+
position: token.position,
|
|
16
|
+
length: token.length,
|
|
17
|
+
},
|
|
18
|
+
stream: (0, lexer_1.advanceStream)(stream),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
let result = (0, parse_primary_1.parsePrimary)(stream);
|
|
22
|
+
while (true) {
|
|
23
|
+
const token = (0, lexer_1.currentToken)(result.stream);
|
|
24
|
+
if (token.type === lexer_1.TokenType.EOF)
|
|
25
|
+
break;
|
|
26
|
+
if (token.type === lexer_1.TokenType.AND || token.type === lexer_1.TokenType.OR) {
|
|
27
|
+
const precedence = getOperatorPrecedence(token.type);
|
|
28
|
+
if (precedence < minPrecedence)
|
|
29
|
+
break;
|
|
30
|
+
const operator = token.type;
|
|
31
|
+
const nextStream = (0, lexer_1.advanceStream)(result.stream);
|
|
32
|
+
const nextToken = (0, lexer_1.currentToken)(nextStream);
|
|
33
|
+
if (nextToken.type === lexer_1.TokenType.EOF) {
|
|
34
|
+
throw {
|
|
35
|
+
message: `Unexpected token: ${token.value}`,
|
|
36
|
+
position: token.position,
|
|
37
|
+
length: token.length,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const right = (0, exports.parseExpression)(nextStream, precedence + 1);
|
|
41
|
+
result = {
|
|
42
|
+
result: {
|
|
43
|
+
type: operator,
|
|
44
|
+
left: result.result,
|
|
45
|
+
right: right.result,
|
|
46
|
+
position: token.position,
|
|
47
|
+
length: token.length,
|
|
48
|
+
},
|
|
49
|
+
stream: right.stream,
|
|
50
|
+
};
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (token.type === lexer_1.TokenType.STRING ||
|
|
54
|
+
token.type === lexer_1.TokenType.QUOTED_STRING ||
|
|
55
|
+
token.type === lexer_1.TokenType.LPAREN ||
|
|
56
|
+
token.type === lexer_1.TokenType.NOT) {
|
|
57
|
+
const precedence = getOperatorPrecedence(lexer_1.TokenType.AND);
|
|
58
|
+
if (precedence < minPrecedence)
|
|
59
|
+
break;
|
|
60
|
+
const right = (0, exports.parseExpression)(result.stream, precedence + 1);
|
|
61
|
+
result = {
|
|
62
|
+
result: {
|
|
63
|
+
type: lexer_1.TokenType.AND,
|
|
64
|
+
left: result.result,
|
|
65
|
+
right: right.result,
|
|
66
|
+
position: token.position,
|
|
67
|
+
length: token.length,
|
|
68
|
+
},
|
|
69
|
+
stream: right.stream,
|
|
70
|
+
};
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
};
|
|
77
|
+
exports.parseExpression = parseExpression;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tokenize = exports.advanceStream = exports.currentToken = exports.createStream = exports.TokenType = void 0;
|
|
4
|
+
// Token types and data structures
|
|
5
|
+
var TokenType;
|
|
6
|
+
(function (TokenType) {
|
|
7
|
+
TokenType["STRING"] = "STRING";
|
|
8
|
+
TokenType["QUOTED_STRING"] = "QUOTED_STRING";
|
|
9
|
+
TokenType["LPAREN"] = "LPAREN";
|
|
10
|
+
TokenType["RPAREN"] = "RPAREN";
|
|
11
|
+
TokenType["AND"] = "AND";
|
|
12
|
+
TokenType["OR"] = "OR";
|
|
13
|
+
TokenType["NOT"] = "NOT";
|
|
14
|
+
TokenType["EOF"] = "EOF";
|
|
15
|
+
TokenType["IN"] = "IN";
|
|
16
|
+
TokenType["COMMA"] = "COMMA";
|
|
17
|
+
TokenType["NUMBER"] = "NUMBER";
|
|
18
|
+
})(TokenType || (exports.TokenType = TokenType = {}));
|
|
19
|
+
// Tokenizer functions
|
|
20
|
+
const createStream = (tokens) => ({
|
|
21
|
+
tokens,
|
|
22
|
+
position: 0,
|
|
23
|
+
});
|
|
24
|
+
exports.createStream = createStream;
|
|
25
|
+
const currentToken = (stream) => stream.position < stream.tokens.length
|
|
26
|
+
? stream.tokens[stream.position]
|
|
27
|
+
: { type: TokenType.EOF, value: "", position: stream.position, length: 0 };
|
|
28
|
+
exports.currentToken = currentToken;
|
|
29
|
+
const advanceStream = (stream) => ({
|
|
30
|
+
...stream,
|
|
31
|
+
position: stream.position + 1,
|
|
32
|
+
});
|
|
33
|
+
exports.advanceStream = advanceStream;
|
|
34
|
+
const isSpecialChar = (char) => /[\s"():(),]/.test(char);
|
|
35
|
+
const isEscapeChar = (char) => char === "\\";
|
|
36
|
+
const isQuoteChar = (char) => char === '"';
|
|
37
|
+
const isWhitespace = (char) => /\s/.test(char);
|
|
38
|
+
const isWildcard = (char) => char === "*";
|
|
39
|
+
const readUntil = (input, start, predicate) => {
|
|
40
|
+
let result = "";
|
|
41
|
+
let pos = start;
|
|
42
|
+
let foundWildcard = false;
|
|
43
|
+
while (pos < input.length) {
|
|
44
|
+
const char = input[pos];
|
|
45
|
+
// Once we find a wildcard, include everything up to the next whitespace or special char
|
|
46
|
+
if (isWildcard(char)) {
|
|
47
|
+
foundWildcard = true;
|
|
48
|
+
}
|
|
49
|
+
if (isWhitespace(char) || (!foundWildcard && !predicate(char))) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
result += char;
|
|
53
|
+
pos++;
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
const tokenizeQuotedString = (input, position) => {
|
|
58
|
+
let value = '"'; // Start with opening quote
|
|
59
|
+
let pos = position + 1; // Skip opening quote in input processing
|
|
60
|
+
let length = 2; // Start with 2 for the quotes
|
|
61
|
+
while (pos < input.length) {
|
|
62
|
+
const char = input[pos];
|
|
63
|
+
if (isQuoteChar(char)) {
|
|
64
|
+
// Add closing quote
|
|
65
|
+
value += '"';
|
|
66
|
+
// Move past closing quote
|
|
67
|
+
pos++;
|
|
68
|
+
// Read any wildcards after the closing quote
|
|
69
|
+
let wildcards = "";
|
|
70
|
+
while (pos < input.length && isWildcard(input[pos])) {
|
|
71
|
+
wildcards += "*";
|
|
72
|
+
pos++;
|
|
73
|
+
length++;
|
|
74
|
+
}
|
|
75
|
+
if (wildcards) {
|
|
76
|
+
value += wildcards;
|
|
77
|
+
}
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
type: TokenType.QUOTED_STRING,
|
|
81
|
+
value,
|
|
82
|
+
position,
|
|
83
|
+
length,
|
|
84
|
+
},
|
|
85
|
+
pos,
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
if (isEscapeChar(char) && pos + 1 < input.length) {
|
|
89
|
+
value += input[pos] + input[pos + 1]; // Include escape char and escaped char
|
|
90
|
+
length += 2;
|
|
91
|
+
pos += 2;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
value += char;
|
|
95
|
+
length++;
|
|
96
|
+
pos++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw { message: "Unterminated quoted string", position, length };
|
|
100
|
+
};
|
|
101
|
+
const tokenizeString = (input, position) => {
|
|
102
|
+
let pos = position;
|
|
103
|
+
if (/^-?\d+(\.\d+)?/.test(input.slice(pos))) {
|
|
104
|
+
const match = input.slice(pos).match(/^-?\d+(\.\d+)?/);
|
|
105
|
+
if (match) {
|
|
106
|
+
const numValue = match[0];
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
type: TokenType.NUMBER,
|
|
110
|
+
value: numValue,
|
|
111
|
+
position: pos,
|
|
112
|
+
length: numValue.length,
|
|
113
|
+
},
|
|
114
|
+
pos + numValue.length,
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Read until we hit a special character, whitespace, or colon
|
|
119
|
+
const fieldPart = readUntil(input, pos, (char) => !isWhitespace(char) && char !== ":" && !isSpecialChar(char));
|
|
120
|
+
pos += fieldPart.length;
|
|
121
|
+
// Check if this is a field:value pattern
|
|
122
|
+
if (pos < input.length && input[pos] === ":") {
|
|
123
|
+
// Skip colon
|
|
124
|
+
pos++;
|
|
125
|
+
// Handle quoted values
|
|
126
|
+
if (pos < input.length && input[pos] === '"') {
|
|
127
|
+
const [quotedToken, newPos] = tokenizeQuotedString(input, pos);
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
type: TokenType.QUOTED_STRING,
|
|
131
|
+
value: `${fieldPart}:${quotedToken.value}`,
|
|
132
|
+
position: position,
|
|
133
|
+
length: newPos - position,
|
|
134
|
+
},
|
|
135
|
+
newPos,
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
// Handle unquoted values
|
|
139
|
+
const valuePart = readUntil(input, pos, (char) => !isWhitespace(char) && !isSpecialChar(char));
|
|
140
|
+
pos += valuePart.length;
|
|
141
|
+
// Check for wildcard after the value
|
|
142
|
+
if (pos < input.length && isWildcard(input[pos])) {
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
type: TokenType.STRING,
|
|
146
|
+
value: `${fieldPart}:${valuePart}*`,
|
|
147
|
+
position,
|
|
148
|
+
length: pos + 1 - position,
|
|
149
|
+
},
|
|
150
|
+
pos + 1,
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
return [
|
|
154
|
+
{
|
|
155
|
+
type: TokenType.STRING,
|
|
156
|
+
value: `${fieldPart}:${valuePart}`,
|
|
157
|
+
position,
|
|
158
|
+
length: pos - position,
|
|
159
|
+
},
|
|
160
|
+
pos,
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
// Handle logical operators (case-insensitive)
|
|
164
|
+
const upperFieldPart = fieldPart.toUpperCase();
|
|
165
|
+
if (upperFieldPart === "AND" ||
|
|
166
|
+
upperFieldPart === "OR" ||
|
|
167
|
+
upperFieldPart === "NOT") {
|
|
168
|
+
return [
|
|
169
|
+
{
|
|
170
|
+
type: upperFieldPart === "AND"
|
|
171
|
+
? TokenType.AND
|
|
172
|
+
: upperFieldPart === "OR"
|
|
173
|
+
? TokenType.OR
|
|
174
|
+
: TokenType.NOT,
|
|
175
|
+
value: upperFieldPart,
|
|
176
|
+
position,
|
|
177
|
+
length: fieldPart.length,
|
|
178
|
+
},
|
|
179
|
+
pos,
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
// Handle IN operator (case-insensitive)
|
|
183
|
+
if (upperFieldPart === "IN") {
|
|
184
|
+
return [
|
|
185
|
+
{
|
|
186
|
+
type: TokenType.IN,
|
|
187
|
+
value: "IN",
|
|
188
|
+
position,
|
|
189
|
+
length: fieldPart.length,
|
|
190
|
+
},
|
|
191
|
+
pos,
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
// Read any wildcards after the string
|
|
195
|
+
let wildcards = "";
|
|
196
|
+
while (pos < input.length && isWildcard(input[pos])) {
|
|
197
|
+
wildcards += "*";
|
|
198
|
+
pos++;
|
|
199
|
+
}
|
|
200
|
+
if (wildcards) {
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
type: TokenType.STRING,
|
|
204
|
+
value: fieldPart + wildcards,
|
|
205
|
+
position,
|
|
206
|
+
length: pos - position,
|
|
207
|
+
},
|
|
208
|
+
pos,
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
// Handle plain strings
|
|
212
|
+
return [
|
|
213
|
+
{
|
|
214
|
+
type: TokenType.STRING,
|
|
215
|
+
value: fieldPart,
|
|
216
|
+
position,
|
|
217
|
+
length: fieldPart.length,
|
|
218
|
+
},
|
|
219
|
+
pos,
|
|
220
|
+
];
|
|
221
|
+
};
|
|
222
|
+
const tokenize = (input) => {
|
|
223
|
+
const tokens = [];
|
|
224
|
+
let position = 0;
|
|
225
|
+
while (position < input.length) {
|
|
226
|
+
const char = input[position];
|
|
227
|
+
if (isWhitespace(char)) {
|
|
228
|
+
position++;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
switch (char) {
|
|
232
|
+
case "-": {
|
|
233
|
+
// Check if this is the start of a term/expression
|
|
234
|
+
if (position === 0 || isWhitespace(input[position - 1])) {
|
|
235
|
+
tokens.push({
|
|
236
|
+
type: TokenType.NOT,
|
|
237
|
+
value: "NOT",
|
|
238
|
+
position,
|
|
239
|
+
length: 1,
|
|
240
|
+
});
|
|
241
|
+
position++;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// If minus is not at start of term, treat it as part of the term
|
|
245
|
+
const [token, newPos] = tokenizeString(input, position);
|
|
246
|
+
tokens.push(token);
|
|
247
|
+
position = newPos;
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case '"': {
|
|
252
|
+
// Before tokenizing a quoted string, check if it's adjacent to a previous quoted string
|
|
253
|
+
if (tokens.length > 0) {
|
|
254
|
+
const prevToken = tokens[tokens.length - 1];
|
|
255
|
+
const prevEnd = prevToken.position + prevToken.length;
|
|
256
|
+
// If there's no whitespace between this quote and the previous token's end
|
|
257
|
+
if (position === prevEnd &&
|
|
258
|
+
prevToken.type !== TokenType.COMMA &&
|
|
259
|
+
(prevToken.type === TokenType.QUOTED_STRING ||
|
|
260
|
+
prevToken.type === TokenType.STRING)) {
|
|
261
|
+
throw {
|
|
262
|
+
message: "Invalid syntax: Missing operator or whitespace between terms",
|
|
263
|
+
position: position,
|
|
264
|
+
length: 1,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const [token, newPos] = tokenizeQuotedString(input, position);
|
|
269
|
+
// After tokenizing, check if the next character is not a whitespace or special character
|
|
270
|
+
if (newPos < input.length &&
|
|
271
|
+
!isWhitespace(input[newPos]) &&
|
|
272
|
+
!isSpecialChar(input[newPos])) {
|
|
273
|
+
throw {
|
|
274
|
+
message: "Invalid syntax: Missing operator or whitespace between terms",
|
|
275
|
+
position: newPos,
|
|
276
|
+
length: 1,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
tokens.push(token);
|
|
280
|
+
position = newPos;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "(": {
|
|
284
|
+
tokens.push({
|
|
285
|
+
type: TokenType.LPAREN,
|
|
286
|
+
value: "(",
|
|
287
|
+
position,
|
|
288
|
+
length: 1,
|
|
289
|
+
});
|
|
290
|
+
position++;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case ")": {
|
|
294
|
+
tokens.push({
|
|
295
|
+
type: TokenType.RPAREN,
|
|
296
|
+
value: ")",
|
|
297
|
+
position,
|
|
298
|
+
length: 1,
|
|
299
|
+
});
|
|
300
|
+
position++;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case ",": {
|
|
304
|
+
tokens.push({
|
|
305
|
+
type: TokenType.COMMA,
|
|
306
|
+
value: ",",
|
|
307
|
+
position,
|
|
308
|
+
length: 1,
|
|
309
|
+
});
|
|
310
|
+
position++;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
default: {
|
|
314
|
+
const [token, newPos] = tokenizeString(input, position);
|
|
315
|
+
tokens.push(token);
|
|
316
|
+
position = newPos;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return tokens;
|
|
321
|
+
};
|
|
322
|
+
exports.tokenize = tokenize;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseInValues = void 0;
|
|
4
|
+
const lexer_1 = require("./lexer");
|
|
5
|
+
const parseInValues = (stream, inValuePosition) => {
|
|
6
|
+
const values = [];
|
|
7
|
+
let currentStream = stream;
|
|
8
|
+
// Expect opening parenthesis
|
|
9
|
+
if ((0, lexer_1.currentToken)(currentStream).type !== lexer_1.TokenType.LPAREN) {
|
|
10
|
+
throw {
|
|
11
|
+
message: "Expected '(' after IN",
|
|
12
|
+
position: inValuePosition, // Use the position passed from the caller
|
|
13
|
+
length: 1,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
currentStream = (0, lexer_1.advanceStream)(currentStream);
|
|
17
|
+
while (true) {
|
|
18
|
+
const token = (0, lexer_1.currentToken)(currentStream);
|
|
19
|
+
if (token.type === lexer_1.TokenType.RPAREN) {
|
|
20
|
+
if (values.length === 0) {
|
|
21
|
+
throw {
|
|
22
|
+
message: "IN operator requires at least one value",
|
|
23
|
+
position: token.position,
|
|
24
|
+
length: 1,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
result: values,
|
|
29
|
+
stream: (0, lexer_1.advanceStream)(currentStream),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (token.type === lexer_1.TokenType.EOF ||
|
|
33
|
+
(token.type !== lexer_1.TokenType.STRING &&
|
|
34
|
+
token.type !== lexer_1.TokenType.QUOTED_STRING &&
|
|
35
|
+
token.type !== lexer_1.TokenType.NUMBER &&
|
|
36
|
+
token.type !== lexer_1.TokenType.COMMA)) {
|
|
37
|
+
throw {
|
|
38
|
+
message: "Expected ',' or ')' after IN value",
|
|
39
|
+
position: token.position,
|
|
40
|
+
length: 1,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (token.type === lexer_1.TokenType.STRING ||
|
|
44
|
+
token.type === lexer_1.TokenType.QUOTED_STRING ||
|
|
45
|
+
token.type === lexer_1.TokenType.NUMBER) {
|
|
46
|
+
values.push(token.value);
|
|
47
|
+
currentStream = (0, lexer_1.advanceStream)(currentStream);
|
|
48
|
+
const nextToken = (0, lexer_1.currentToken)(currentStream);
|
|
49
|
+
if (nextToken.type === lexer_1.TokenType.COMMA) {
|
|
50
|
+
currentStream = (0, lexer_1.advanceStream)(currentStream);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (nextToken.type === lexer_1.TokenType.RPAREN) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
throw {
|
|
57
|
+
message: "Expected ',' or ')' after IN value",
|
|
58
|
+
position: nextToken.position,
|
|
59
|
+
length: 1,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
currentStream = (0, lexer_1.advanceStream)(currentStream);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
exports.parseInValues = parseInValues;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePrimary = exports.extractFieldValue = exports.isFieldValuePattern = exports.expectToken = void 0;
|
|
4
|
+
const first_pass_parser_1 = require("./first-pass-parser");
|
|
5
|
+
const parse_in_values_1 = require("./parse-in-values");
|
|
6
|
+
const lexer_1 = require("./lexer");
|
|
7
|
+
const expectToken = (stream, type, message) => {
|
|
8
|
+
const token = (0, lexer_1.currentToken)(stream);
|
|
9
|
+
if (token.type !== type) {
|
|
10
|
+
throw {
|
|
11
|
+
message: message ? message : `Expected ${type}`,
|
|
12
|
+
position: token.position,
|
|
13
|
+
length: token.length,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return (0, lexer_1.advanceStream)(stream);
|
|
17
|
+
};
|
|
18
|
+
exports.expectToken = expectToken;
|
|
19
|
+
// Helper to check if a string value represents a field:value pattern
|
|
20
|
+
const isFieldValuePattern = (value) => {
|
|
21
|
+
return value.includes(":");
|
|
22
|
+
};
|
|
23
|
+
exports.isFieldValuePattern = isFieldValuePattern;
|
|
24
|
+
// Helper to extract field and value from a field:value pattern
|
|
25
|
+
const extractFieldValue = (value) => {
|
|
26
|
+
const [field, ...valueParts] = value.split(":");
|
|
27
|
+
return [field, valueParts.join(":")];
|
|
28
|
+
};
|
|
29
|
+
exports.extractFieldValue = extractFieldValue;
|
|
30
|
+
const parsePrimary = (stream) => {
|
|
31
|
+
const token = (0, lexer_1.currentToken)(stream);
|
|
32
|
+
switch (token.type) {
|
|
33
|
+
case lexer_1.TokenType.NOT: {
|
|
34
|
+
const nextStream = (0, lexer_1.advanceStream)(stream);
|
|
35
|
+
const nextToken = (0, lexer_1.currentToken)(nextStream);
|
|
36
|
+
if (nextToken.type === lexer_1.TokenType.LPAREN) {
|
|
37
|
+
const afterLParen = (0, lexer_1.advanceStream)(nextStream);
|
|
38
|
+
const exprResult = (0, first_pass_parser_1.parseExpression)(afterLParen);
|
|
39
|
+
const finalStream = (0, exports.expectToken)(exprResult.stream, lexer_1.TokenType.RPAREN, "Expected ')'");
|
|
40
|
+
return {
|
|
41
|
+
result: {
|
|
42
|
+
type: "NOT",
|
|
43
|
+
expression: exprResult.result,
|
|
44
|
+
position: token.position,
|
|
45
|
+
length: token.length,
|
|
46
|
+
},
|
|
47
|
+
stream: finalStream,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const exprResult = (0, exports.parsePrimary)(nextStream);
|
|
51
|
+
return {
|
|
52
|
+
result: {
|
|
53
|
+
type: "NOT",
|
|
54
|
+
expression: exprResult.result,
|
|
55
|
+
position: token.position,
|
|
56
|
+
length: token.length,
|
|
57
|
+
},
|
|
58
|
+
stream: exprResult.stream,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
case lexer_1.TokenType.LPAREN: {
|
|
62
|
+
const innerStream = (0, lexer_1.advanceStream)(stream);
|
|
63
|
+
const exprResult = (0, first_pass_parser_1.parseExpression)(innerStream);
|
|
64
|
+
const finalStream = (0, exports.expectToken)(exprResult.stream, lexer_1.TokenType.RPAREN, "Expected ')'");
|
|
65
|
+
return { result: exprResult.result, stream: finalStream };
|
|
66
|
+
}
|
|
67
|
+
case lexer_1.TokenType.STRING:
|
|
68
|
+
case lexer_1.TokenType.QUOTED_STRING: {
|
|
69
|
+
const { value } = token;
|
|
70
|
+
const isQuoted = token.type === lexer_1.TokenType.QUOTED_STRING;
|
|
71
|
+
// Check for field:IN pattern
|
|
72
|
+
if (value.includes(":")) {
|
|
73
|
+
const [field, remainder] = value.split(":");
|
|
74
|
+
if (remainder.toUpperCase() === "IN") {
|
|
75
|
+
const nextStream = (0, lexer_1.advanceStream)(stream);
|
|
76
|
+
const colonIndex = value.indexOf(":");
|
|
77
|
+
const inValuePosition = token.position + colonIndex + 2; // After field:IN
|
|
78
|
+
const inValuesResult = (0, parse_in_values_1.parseInValues)(nextStream, inValuePosition);
|
|
79
|
+
return {
|
|
80
|
+
result: {
|
|
81
|
+
type: "IN",
|
|
82
|
+
field,
|
|
83
|
+
values: inValuesResult.result,
|
|
84
|
+
position: token.position,
|
|
85
|
+
length: token.length + inValuesResult.stream.position - nextStream.position,
|
|
86
|
+
},
|
|
87
|
+
stream: inValuesResult.stream,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Handle field:value patterns
|
|
92
|
+
if ((0, exports.isFieldValuePattern)(value)) {
|
|
93
|
+
const [field, rawValue] = (0, exports.extractFieldValue)(value);
|
|
94
|
+
// If it has a trailing wildcard
|
|
95
|
+
if (rawValue.endsWith("*")) {
|
|
96
|
+
return {
|
|
97
|
+
result: {
|
|
98
|
+
type: "WILDCARD",
|
|
99
|
+
prefix: `${field}:${rawValue.slice(0, -1)}`,
|
|
100
|
+
quoted: isQuoted,
|
|
101
|
+
position: token.position,
|
|
102
|
+
length: token.length,
|
|
103
|
+
},
|
|
104
|
+
stream: (0, lexer_1.advanceStream)(stream),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Handle regular terms with wildcards
|
|
109
|
+
if (value.endsWith("*")) {
|
|
110
|
+
return {
|
|
111
|
+
result: {
|
|
112
|
+
type: "WILDCARD",
|
|
113
|
+
prefix: value.slice(0, -1),
|
|
114
|
+
quoted: isQuoted,
|
|
115
|
+
position: token.position,
|
|
116
|
+
length: token.length,
|
|
117
|
+
},
|
|
118
|
+
stream: (0, lexer_1.advanceStream)(stream),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Regular string without wildcards
|
|
122
|
+
return {
|
|
123
|
+
result: {
|
|
124
|
+
type: "STRING",
|
|
125
|
+
value,
|
|
126
|
+
quoted: token.type === lexer_1.TokenType.QUOTED_STRING,
|
|
127
|
+
position: token.position,
|
|
128
|
+
length: token.length,
|
|
129
|
+
},
|
|
130
|
+
stream: (0, lexer_1.advanceStream)(stream),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
case lexer_1.TokenType.AND:
|
|
134
|
+
case lexer_1.TokenType.OR:
|
|
135
|
+
throw {
|
|
136
|
+
message: `${token.value} is a reserved word`,
|
|
137
|
+
position: token.position,
|
|
138
|
+
length: token.length,
|
|
139
|
+
};
|
|
140
|
+
case lexer_1.TokenType.RPAREN:
|
|
141
|
+
throw {
|
|
142
|
+
message: 'Unexpected ")"',
|
|
143
|
+
position: token.position,
|
|
144
|
+
length: token.length,
|
|
145
|
+
};
|
|
146
|
+
default:
|
|
147
|
+
throw {
|
|
148
|
+
message: "Unexpected token",
|
|
149
|
+
position: token.position,
|
|
150
|
+
length: token.length,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
exports.parsePrimary = parsePrimary;
|