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.
Files changed (58) hide show
  1. package/dist/cjs/first-pass-parser.js +77 -0
  2. package/dist/cjs/lexer.js +322 -0
  3. package/dist/cjs/parse-in-values.js +65 -0
  4. package/dist/cjs/parse-primary.js +154 -0
  5. package/dist/cjs/parse-range-expression.js +174 -0
  6. package/dist/cjs/parser.js +85 -0
  7. package/dist/cjs/search-query-to-sql.js +346 -0
  8. package/dist/cjs/transform-to-expression.js +130 -0
  9. package/dist/cjs/validate-expression-fields.js +244 -0
  10. package/dist/cjs/validate-in-expression.js +33 -0
  11. package/dist/cjs/validate-string.js +65 -0
  12. package/dist/cjs/validate-wildcard.js +40 -0
  13. package/dist/cjs/validator.js +34 -0
  14. package/dist/esm/first-pass-parser.js +73 -0
  15. package/dist/esm/lexer.js +315 -0
  16. package/dist/esm/parse-in-values.js +61 -0
  17. package/dist/esm/parse-primary.js +147 -0
  18. package/dist/esm/parse-range-expression.js +170 -0
  19. package/dist/esm/parser.js +81 -0
  20. package/dist/esm/search-query-to-sql.js +341 -0
  21. package/dist/esm/transform-to-expression.js +126 -0
  22. package/dist/esm/validate-expression-fields.js +240 -0
  23. package/dist/esm/validate-in-expression.js +29 -0
  24. package/dist/esm/validate-string.js +61 -0
  25. package/dist/esm/validate-wildcard.js +36 -0
  26. package/dist/esm/validator.js +30 -0
  27. package/dist/types/first-pass-parser.d.ts +40 -0
  28. package/dist/types/lexer.d.ts +27 -0
  29. package/dist/types/parse-in-values.d.ts +3 -0
  30. package/dist/types/parse-primary.d.ts +6 -0
  31. package/dist/types/parse-range-expression.d.ts +2 -0
  32. package/dist/types/parser.d.ts +68 -0
  33. package/dist/types/search-query-to-sql.d.ts +18 -0
  34. package/dist/types/transform-to-expression.d.ts +3 -0
  35. package/dist/types/validate-expression-fields.d.ts +4 -0
  36. package/dist/types/validate-in-expression.d.ts +3 -0
  37. package/dist/types/validate-string.d.ts +3 -0
  38. package/dist/types/validate-wildcard.d.ts +3 -0
  39. package/dist/types/validator.d.ts +8 -0
  40. package/package.json +52 -0
  41. package/src/first-pass-parser.test.ts +441 -0
  42. package/src/first-pass-parser.ts +144 -0
  43. package/src/lexer.test.ts +439 -0
  44. package/src/lexer.ts +387 -0
  45. package/src/parse-in-values.ts +74 -0
  46. package/src/parse-primary.ts +179 -0
  47. package/src/parse-range-expression.ts +187 -0
  48. package/src/parser.test.ts +982 -0
  49. package/src/parser.ts +219 -0
  50. package/src/search-query-to-sql.test.ts +503 -0
  51. package/src/search-query-to-sql.ts +506 -0
  52. package/src/transform-to-expression.ts +153 -0
  53. package/src/validate-expression-fields.ts +296 -0
  54. package/src/validate-in-expression.ts +36 -0
  55. package/src/validate-string.ts +73 -0
  56. package/src/validate-wildcard.ts +45 -0
  57. package/src/validator.test.ts +192 -0
  58. package/src/validator.ts +53 -0
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformToExpression = void 0;
4
+ const parse_range_expression_1 = require("./parse-range-expression");
5
+ // Helper to transform FirstPassExpression into Expression
6
+ const transformToExpression = (expr, schemas) => {
7
+ switch (expr.type) {
8
+ case "NOT":
9
+ return {
10
+ type: "NOT",
11
+ expression: (0, exports.transformToExpression)(expr.expression, schemas),
12
+ position: expr.position,
13
+ length: expr.length,
14
+ };
15
+ case "WILDCARD":
16
+ // Check if this is part of a field:value pattern by looking at the prefix
17
+ const colonIndex = expr.prefix.indexOf(":");
18
+ if (colonIndex !== -1) {
19
+ const field = expr.prefix.substring(0, colonIndex).trim();
20
+ const prefix = expr.prefix.substring(colonIndex + 1).trim();
21
+ return {
22
+ type: "FIELD_VALUE",
23
+ field: {
24
+ type: "FIELD",
25
+ value: field,
26
+ position: expr.position - colonIndex - 1, // Adjust for the field part
27
+ length: colonIndex,
28
+ },
29
+ value: {
30
+ type: "VALUE",
31
+ value: prefix + "*", // Preserve the wildcard in the value
32
+ position: expr.position,
33
+ length: prefix.length + 1,
34
+ },
35
+ };
36
+ }
37
+ // If not a field:value pattern, return as a wildcard search term
38
+ return {
39
+ type: "WILDCARD",
40
+ prefix: expr.prefix,
41
+ quoted: expr.quoted,
42
+ position: expr.position,
43
+ length: expr.length,
44
+ };
45
+ case "STRING": {
46
+ // Check if the string is a field:value pattern
47
+ const colonIndex = expr.value.indexOf(":");
48
+ if (colonIndex !== -1) {
49
+ const field = expr.value.substring(0, colonIndex).trim();
50
+ let value = expr.value.substring(colonIndex + 1).trim();
51
+ // Remove quotes if present
52
+ value =
53
+ value.startsWith('"') && value.endsWith('"')
54
+ ? value.slice(1, -1)
55
+ : value;
56
+ const schema = schemas.get(field.toLowerCase());
57
+ // Check for range patterns when we have a numeric or date field
58
+ if (schema && (schema.type === "number" || schema.type === "date")) {
59
+ return (0, parse_range_expression_1.parseRangeExpression)(field, value, schema, expr.position, colonIndex);
60
+ }
61
+ return {
62
+ type: "FIELD_VALUE",
63
+ field: {
64
+ type: "FIELD",
65
+ value: field,
66
+ position: expr.position,
67
+ length: colonIndex,
68
+ },
69
+ value: {
70
+ type: "VALUE",
71
+ value,
72
+ position: expr.position + colonIndex + 1,
73
+ length: value.length,
74
+ },
75
+ };
76
+ }
77
+ return {
78
+ type: "SEARCH_TERM",
79
+ value: expr.value,
80
+ position: expr.position,
81
+ length: expr.length,
82
+ };
83
+ }
84
+ case "AND":
85
+ return {
86
+ type: "AND",
87
+ left: (0, exports.transformToExpression)(expr.left, schemas),
88
+ right: (0, exports.transformToExpression)(expr.right, schemas),
89
+ position: expr.position,
90
+ length: expr.length,
91
+ };
92
+ case "OR":
93
+ return {
94
+ type: "OR",
95
+ left: (0, exports.transformToExpression)(expr.left, schemas),
96
+ right: (0, exports.transformToExpression)(expr.right, schemas),
97
+ position: expr.position,
98
+ length: expr.length,
99
+ };
100
+ case "IN": {
101
+ const schema = schemas.get(expr.field.toLowerCase());
102
+ const transformedValues = expr.values.map((value, index) => {
103
+ let transformedValue = value;
104
+ // Handle type conversion based on schema
105
+ if ((schema === null || schema === void 0 ? void 0 : schema.type) === "number") {
106
+ transformedValue = String(Number(value));
107
+ }
108
+ return {
109
+ type: "VALUE",
110
+ value: transformedValue,
111
+ position: expr.position + expr.field.length + 3 + index * (value.length + 1), // +3 for ":IN"
112
+ length: value.length,
113
+ };
114
+ });
115
+ return {
116
+ type: "IN",
117
+ field: {
118
+ type: "FIELD",
119
+ value: expr.field,
120
+ position: expr.position,
121
+ length: expr.field.length,
122
+ },
123
+ values: transformedValues,
124
+ position: expr.position,
125
+ length: expr.length,
126
+ };
127
+ }
128
+ }
129
+ };
130
+ exports.transformToExpression = transformToExpression;
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateExpressionFields = void 0;
4
+ // Helper to validate numeric values
5
+ const validateNumber = (value, position, errors) => {
6
+ if (value === "")
7
+ return false;
8
+ if (isNaN(Number(value))) {
9
+ errors.push({
10
+ message: "Invalid numeric value",
11
+ position,
12
+ length: value.length,
13
+ });
14
+ return false;
15
+ }
16
+ return true;
17
+ };
18
+ // Helper to validate range values for numeric fields
19
+ const validateNumericRange = (start, end, basePosition, errors) => {
20
+ let isValid = true;
21
+ const startPos = basePosition;
22
+ const endPos = basePosition + start.length + 2; // +2 for the '..'
23
+ if (start && !validateNumber(start, startPos, errors)) {
24
+ isValid = false;
25
+ }
26
+ if (end && !validateNumber(end, endPos, errors)) {
27
+ isValid = false;
28
+ }
29
+ if (isValid && start && end) {
30
+ const startNum = Number(start);
31
+ const endNum = Number(end);
32
+ if (startNum > endNum) {
33
+ errors.push({
34
+ message: "Range start must be less than or equal to range end",
35
+ position: basePosition,
36
+ length: start.length + 2 + end.length,
37
+ });
38
+ isValid = false;
39
+ }
40
+ }
41
+ return isValid;
42
+ };
43
+ // Helper to validate numeric comparison operators
44
+ const validateNumericComparison = (operator, value, basePosition, errors) => {
45
+ const valuePosition = basePosition + operator.length;
46
+ return validateNumber(value, valuePosition, errors);
47
+ };
48
+ // Field validation helpers
49
+ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
50
+ switch (expr.type) {
51
+ case "IN": {
52
+ if (!allowedFields.has(expr.field.toLowerCase())) {
53
+ errors.push({
54
+ message: `Invalid field: "${expr.field}"`,
55
+ position: expr.position,
56
+ length: expr.field.length,
57
+ });
58
+ }
59
+ // Get schema for type validation
60
+ const schema = schemas.get(expr.field.toLowerCase());
61
+ if (schema) {
62
+ expr.values.forEach((value, index) => {
63
+ switch (schema.type) {
64
+ case "number":
65
+ if (isNaN(Number(value))) {
66
+ errors.push({
67
+ message: "Invalid numeric value",
68
+ position: expr.position +
69
+ expr.field.length +
70
+ 4 +
71
+ index * (value.length + 1),
72
+ length: value.length,
73
+ });
74
+ }
75
+ break;
76
+ case "date":
77
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
78
+ errors.push({
79
+ message: "Invalid date format",
80
+ position: expr.position +
81
+ expr.field.length +
82
+ 3 +
83
+ index * (value.length + 1),
84
+ length: value.length,
85
+ });
86
+ }
87
+ break;
88
+ }
89
+ });
90
+ }
91
+ break;
92
+ }
93
+ case "WILDCARD": {
94
+ // For wildcard patterns, validate against field type constraints
95
+ const schema = schemas.get(expr.prefix.toLowerCase());
96
+ if ((schema === null || schema === void 0 ? void 0 : schema.type) === "number" || (schema === null || schema === void 0 ? void 0 : schema.type) === "date") {
97
+ errors.push({
98
+ message: `Wildcards are not allowed for ${schema.type} fields`,
99
+ position: expr.position,
100
+ length: expr.length,
101
+ });
102
+ }
103
+ break;
104
+ }
105
+ case "STRING": {
106
+ const colonIndex = expr.value.indexOf(":");
107
+ if (colonIndex === -1)
108
+ return;
109
+ const fieldName = expr.value.substring(0, colonIndex).trim();
110
+ const value = expr.value.substring(colonIndex + 1).trim();
111
+ if (!allowedFields.has(fieldName.toLowerCase()) && colonIndex > 0) {
112
+ errors.push({
113
+ message: `Invalid field: "${fieldName}"`,
114
+ position: expr.position,
115
+ length: colonIndex,
116
+ });
117
+ }
118
+ if (!value) {
119
+ errors.push({
120
+ message: "Expected field value",
121
+ position: expr.position,
122
+ length: colonIndex + 1,
123
+ });
124
+ return;
125
+ }
126
+ if (value.startsWith(":")) {
127
+ errors.push({
128
+ message: "Missing field name",
129
+ position: expr.position,
130
+ length: value.length + colonIndex + 1,
131
+ });
132
+ return;
133
+ }
134
+ const schema = schemas.get(fieldName.toLowerCase());
135
+ if (!schema)
136
+ return;
137
+ const valueStartPosition = expr.position + colonIndex + 1;
138
+ if (schema.type === "number") {
139
+ if (value.includes("..")) {
140
+ if (value === ".." || value.includes("...")) {
141
+ errors.push({
142
+ message: "Invalid range format",
143
+ position: valueStartPosition,
144
+ length: value.length,
145
+ });
146
+ return;
147
+ }
148
+ const [start, end] = value.split("..");
149
+ validateNumericRange(start, end, valueStartPosition, errors);
150
+ return;
151
+ }
152
+ const comparisonMatch = value.match(/^(>=|>|<=|<)(.*)$/);
153
+ if (comparisonMatch) {
154
+ const [, operator, compValue] = comparisonMatch;
155
+ const invalidOp = /^[<>]{2,}|>=>/;
156
+ if (invalidOp.test(value)) {
157
+ errors.push({
158
+ message: "Invalid range operator",
159
+ position: valueStartPosition,
160
+ length: 3,
161
+ });
162
+ return;
163
+ }
164
+ if (!compValue) {
165
+ errors.push({
166
+ message: "Expected range value",
167
+ position: valueStartPosition + operator.length,
168
+ length: 0,
169
+ });
170
+ return;
171
+ }
172
+ validateNumericComparison(operator, compValue, valueStartPosition, errors);
173
+ return;
174
+ }
175
+ validateNumber(value, valueStartPosition, errors);
176
+ return;
177
+ }
178
+ if (schema.type === "date") {
179
+ const dateValidator = (dateStr) => {
180
+ if (!dateStr)
181
+ return true;
182
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
183
+ return false;
184
+ }
185
+ const date = new Date(dateStr);
186
+ return (!isNaN(date.getTime()) &&
187
+ dateStr === date.toISOString().split("T")[0]);
188
+ };
189
+ if (value.includes("..")) {
190
+ const [start, end] = value.split("..");
191
+ if (!dateValidator(start) || !dateValidator(end)) {
192
+ errors.push({
193
+ message: "Invalid date format",
194
+ position: valueStartPosition,
195
+ length: value.length,
196
+ });
197
+ return;
198
+ }
199
+ }
200
+ else {
201
+ const comparisonMatch = value.match(/^(>=|>|<=|<)(.*)$/);
202
+ if (comparisonMatch) {
203
+ const [, , dateStr] = comparisonMatch;
204
+ if (!dateValidator(dateStr)) {
205
+ errors.push({
206
+ message: "Invalid date format",
207
+ position: valueStartPosition,
208
+ length: value.length,
209
+ });
210
+ return;
211
+ }
212
+ }
213
+ else if (!dateValidator(value)) {
214
+ errors.push({
215
+ message: "Invalid date format",
216
+ position: valueStartPosition,
217
+ length: value.length,
218
+ });
219
+ return;
220
+ }
221
+ }
222
+ }
223
+ break;
224
+ }
225
+ }
226
+ };
227
+ const validateExpressionFields = (expr, allowedFields, errors, schemas) => {
228
+ switch (expr.type) {
229
+ case "STRING":
230
+ case "WILDCARD":
231
+ case "IN":
232
+ validateFieldValue(expr, allowedFields, errors, schemas);
233
+ break;
234
+ case "AND":
235
+ case "OR":
236
+ (0, exports.validateExpressionFields)(expr.left, allowedFields, errors, schemas);
237
+ (0, exports.validateExpressionFields)(expr.right, allowedFields, errors, schemas);
238
+ break;
239
+ case "NOT":
240
+ (0, exports.validateExpressionFields)(expr.expression, allowedFields, errors, schemas);
241
+ break;
242
+ }
243
+ };
244
+ exports.validateExpressionFields = validateExpressionFields;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateInExpression = void 0;
4
+ const validator_1 = require("./validator");
5
+ const validateInExpression = (expr, errors) => {
6
+ // Validate field name pattern
7
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(expr.field)) {
8
+ errors.push({
9
+ message: "Invalid characters in field name",
10
+ position: expr.position,
11
+ length: expr.field.length,
12
+ });
13
+ }
14
+ // Check for reserved words
15
+ if (validator_1.reservedWords.has(expr.field.toUpperCase())) {
16
+ errors.push({
17
+ message: `${expr.field} is a reserved word`,
18
+ position: expr.position,
19
+ length: expr.field.length,
20
+ });
21
+ }
22
+ // Validate value format based on field type
23
+ expr.values.forEach((value, index) => {
24
+ if (value.includes(",")) {
25
+ errors.push({
26
+ message: "Invalid character in IN value",
27
+ position: expr.position + expr.field.length + 3 + index * (value.length + 1),
28
+ length: value.length,
29
+ });
30
+ }
31
+ });
32
+ };
33
+ exports.validateInExpression = validateInExpression;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateString = void 0;
4
+ const validator_1 = require("./validator");
5
+ const validate_wildcard_1 = require("./validate-wildcard");
6
+ // Validate individual strings (field:value pairs or plain terms)
7
+ const validateString = (expr, errors) => {
8
+ // Validate wildcard usage
9
+ (0, validate_wildcard_1.validateWildcard)(expr, errors);
10
+ // For wildcard patterns, no additional validation needed
11
+ if (expr.type === "WILDCARD") {
12
+ return;
13
+ }
14
+ // Handle STRING type
15
+ // Check for empty field values
16
+ if (expr.value.endsWith(":")) {
17
+ errors.push({
18
+ message: "Expected field value",
19
+ position: expr.position,
20
+ length: expr.length,
21
+ });
22
+ return;
23
+ }
24
+ // Check for field values that start with colon
25
+ if (expr.value.startsWith(":")) {
26
+ errors.push({
27
+ message: "Missing field name",
28
+ position: expr.position,
29
+ length: expr.length,
30
+ });
31
+ return;
32
+ }
33
+ // For field:value patterns, validate the field name
34
+ if (expr.value.includes(":")) {
35
+ const [fieldName] = expr.value.split(":");
36
+ // Check for reserved words used as field names
37
+ if (validator_1.reservedWords.has(fieldName.toUpperCase())) {
38
+ errors.push({
39
+ message: `${fieldName} is a reserved word`,
40
+ position: expr.position,
41
+ length: fieldName.length,
42
+ });
43
+ return;
44
+ }
45
+ // Check for invalid characters in field names
46
+ if (!/^[a-zA-Z0-9_-]+$/.test(fieldName)) {
47
+ errors.push({
48
+ message: "Invalid characters in field name",
49
+ position: expr.position,
50
+ length: fieldName.length,
51
+ });
52
+ return;
53
+ }
54
+ }
55
+ // Handle standalone reserved words (not in field:value pattern)
56
+ if (!expr.value.includes(":") &&
57
+ validator_1.reservedWords.has(expr.value.toUpperCase())) {
58
+ errors.push({
59
+ message: `${expr.value} is a reserved word`,
60
+ position: expr.position,
61
+ length: expr.length,
62
+ });
63
+ }
64
+ };
65
+ exports.validateString = validateString;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateWildcard = void 0;
4
+ // Validates wildcard patterns
5
+ const validateWildcard = (expr, errors) => {
6
+ const value = expr.type === "STRING" ? expr.value : expr.prefix + "*";
7
+ const starCount = (value.match(/\*/g) || []).length;
8
+ const isQuoted = expr.quoted;
9
+ // For unquoted strings
10
+ if (!isQuoted) {
11
+ const firstStar = value.indexOf("*");
12
+ if (starCount > 1) {
13
+ const secondStar = value.indexOf("*", firstStar + 1);
14
+ errors.push({
15
+ message: "Only one trailing wildcard (*) is allowed",
16
+ position: expr.position + secondStar,
17
+ length: 1,
18
+ });
19
+ }
20
+ if ((firstStar !== -1 && firstStar !== value.length - 1) && !value.endsWith("**")) {
21
+ errors.push({
22
+ message: "Wildcard (*) can only appear at the end of a term",
23
+ position: expr.position + firstStar,
24
+ length: 1,
25
+ });
26
+ }
27
+ }
28
+ // For quoted strings
29
+ else {
30
+ // Handle multiple wildcards or internal wildcards in quoted strings
31
+ if (value.endsWith("**")) {
32
+ errors.push({
33
+ message: "Only one trailing wildcard (*) is allowed",
34
+ position: expr.position + value.length - 1,
35
+ length: 1,
36
+ });
37
+ }
38
+ }
39
+ };
40
+ exports.validateWildcard = validateWildcard;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateSearchQuery = exports.reservedWords = void 0;
4
+ const validate_in_expression_1 = require("./validate-in-expression");
5
+ const validate_string_1 = require("./validate-string");
6
+ exports.reservedWords = new Set(["AND", "OR"]);
7
+ const walkExpression = (expr, errors) => {
8
+ switch (expr.type) {
9
+ case "STRING":
10
+ case "WILDCARD":
11
+ (0, validate_string_1.validateString)(expr, errors);
12
+ break;
13
+ case "AND":
14
+ case "OR":
15
+ walkExpression(expr.left, errors);
16
+ walkExpression(expr.right, errors);
17
+ break;
18
+ case "NOT":
19
+ walkExpression(expr.expression, errors);
20
+ break;
21
+ case "IN":
22
+ (0, validate_in_expression_1.validateInExpression)(expr, errors);
23
+ break;
24
+ }
25
+ };
26
+ const validateSearchQuery = (expression) => {
27
+ const errors = [];
28
+ if (expression === null) {
29
+ return errors;
30
+ }
31
+ walkExpression(expression, errors);
32
+ return errors;
33
+ };
34
+ exports.validateSearchQuery = validateSearchQuery;
@@ -0,0 +1,73 @@
1
+ import { TokenType, currentToken, advanceStream } from "./lexer";
2
+ import { parsePrimary } from "./parse-primary";
3
+ const getOperatorPrecedence = (type) => type === TokenType.AND ? 2 : type === TokenType.OR ? 1 : 0;
4
+ export const parseExpression = (stream, minPrecedence = 0) => {
5
+ const token = currentToken(stream);
6
+ if (token.type === TokenType.STRING && token.value === "*") {
7
+ return {
8
+ result: {
9
+ type: "WILDCARD",
10
+ prefix: "",
11
+ quoted: false,
12
+ position: token.position,
13
+ length: token.length,
14
+ },
15
+ stream: advanceStream(stream),
16
+ };
17
+ }
18
+ let result = parsePrimary(stream);
19
+ while (true) {
20
+ const token = currentToken(result.stream);
21
+ if (token.type === TokenType.EOF)
22
+ break;
23
+ if (token.type === TokenType.AND || token.type === TokenType.OR) {
24
+ const precedence = getOperatorPrecedence(token.type);
25
+ if (precedence < minPrecedence)
26
+ break;
27
+ const operator = token.type;
28
+ const nextStream = advanceStream(result.stream);
29
+ const nextToken = currentToken(nextStream);
30
+ if (nextToken.type === TokenType.EOF) {
31
+ throw {
32
+ message: `Unexpected token: ${token.value}`,
33
+ position: token.position,
34
+ length: token.length,
35
+ };
36
+ }
37
+ const right = parseExpression(nextStream, precedence + 1);
38
+ result = {
39
+ result: {
40
+ type: operator,
41
+ left: result.result,
42
+ right: right.result,
43
+ position: token.position,
44
+ length: token.length,
45
+ },
46
+ stream: right.stream,
47
+ };
48
+ continue;
49
+ }
50
+ if (token.type === TokenType.STRING ||
51
+ token.type === TokenType.QUOTED_STRING ||
52
+ token.type === TokenType.LPAREN ||
53
+ token.type === TokenType.NOT) {
54
+ const precedence = getOperatorPrecedence(TokenType.AND);
55
+ if (precedence < minPrecedence)
56
+ break;
57
+ const right = parseExpression(result.stream, precedence + 1);
58
+ result = {
59
+ result: {
60
+ type: TokenType.AND,
61
+ left: result.result,
62
+ right: right.result,
63
+ position: token.position,
64
+ length: token.length,
65
+ },
66
+ stream: right.stream,
67
+ };
68
+ continue;
69
+ }
70
+ break;
71
+ }
72
+ return result;
73
+ };