wizard-ql 2.1.1 → 4.0.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/LICENSE +1 -1
- package/README.md +154 -53
- package/dist/dominput.d.ts +6 -6
- package/dist/errors.d.ts +4 -4
- package/dist/index.d.ts +2 -5
- package/dist/index.js +992 -840
- package/dist/parser.d.ts +402 -0
- package/dist/regex.d.ts +23 -4
- package/dist/spec.d.ts +86 -158
- package/package.json +14 -9
- package/dist/execute.d.ts +0 -9
- package/dist/parse.d.ts +0 -55
- package/dist/stringify.d.ts +0 -42
- package/dist/summarize.d.ts +0 -15
package/dist/index.js
CHANGED
|
@@ -1,87 +1,5 @@
|
|
|
1
1
|
// src/spec.ts
|
|
2
|
-
var BRACKETS = [
|
|
3
|
-
["[", "]"],
|
|
4
|
-
["{", "}"]
|
|
5
|
-
];
|
|
6
|
-
var PARENS = [
|
|
7
|
-
["(", ")"]
|
|
8
|
-
];
|
|
9
|
-
var NEGATORS = ["!"];
|
|
10
|
-
var ARRAY_DELIMITERS = [","];
|
|
11
|
-
var OPERATION_ALIAS_DICTIONARY = {
|
|
12
|
-
AND: "AND",
|
|
13
|
-
"&&": "AND",
|
|
14
|
-
"&": "AND",
|
|
15
|
-
"^": "AND",
|
|
16
|
-
OR: "OR",
|
|
17
|
-
"||": "OR",
|
|
18
|
-
"|": "OR",
|
|
19
|
-
V: "OR",
|
|
20
|
-
GEQ: "GEQ",
|
|
21
|
-
">=": "GEQ",
|
|
22
|
-
"=>": "GEQ",
|
|
23
|
-
LEQ: "LEQ",
|
|
24
|
-
"<=": "LEQ",
|
|
25
|
-
"=<": "LEQ",
|
|
26
|
-
NOTEQUALS: "NOTEQUAL",
|
|
27
|
-
NOTEQUAL: "NOTEQUAL",
|
|
28
|
-
NEQ: "NOTEQUAL",
|
|
29
|
-
ISNT: "NOTEQUAL",
|
|
30
|
-
"!==": "NOTEQUAL",
|
|
31
|
-
"!=": "NOTEQUAL",
|
|
32
|
-
EQUALS: "EQUAL",
|
|
33
|
-
EQUAL: "EQUAL",
|
|
34
|
-
EQ: "EQUAL",
|
|
35
|
-
IS: "EQUAL",
|
|
36
|
-
"==": "EQUAL",
|
|
37
|
-
"=": "EQUAL",
|
|
38
|
-
LESS: "LESS",
|
|
39
|
-
"<": "LESS",
|
|
40
|
-
GREATER: "GREATER",
|
|
41
|
-
">": "GREATER",
|
|
42
|
-
MORE: "GREATER",
|
|
43
|
-
NOTIN: "NOTIN",
|
|
44
|
-
"!:": "NOTIN",
|
|
45
|
-
IN: "IN",
|
|
46
|
-
":": "IN",
|
|
47
|
-
NOTMATCHES: "NOTMATCH",
|
|
48
|
-
NOTMATCH: "NOTMATCH",
|
|
49
|
-
"!~": "NOTMATCH",
|
|
50
|
-
MATCHES: "MATCH",
|
|
51
|
-
MATCH: "MATCH",
|
|
52
|
-
"~": "MATCH"
|
|
53
|
-
};
|
|
54
|
-
var ALIASES = Object.keys(OPERATION_ALIAS_DICTIONARY);
|
|
55
|
-
var OPERATION_PURPOSE_DICTIONARY = {
|
|
56
|
-
AND: "junction",
|
|
57
|
-
OR: "junction",
|
|
58
|
-
EQUAL: "comparison",
|
|
59
|
-
NOTEQUAL: "comparison",
|
|
60
|
-
LESS: "comparison",
|
|
61
|
-
GREATER: "comparison",
|
|
62
|
-
GEQ: "comparison",
|
|
63
|
-
LEQ: "comparison",
|
|
64
|
-
IN: "comparison",
|
|
65
|
-
NOTIN: "comparison",
|
|
66
|
-
MATCH: "comparison",
|
|
67
|
-
NOTMATCH: "comparison"
|
|
68
|
-
};
|
|
69
|
-
var COMPARISON_TYPE_DICTIONARY = {
|
|
70
|
-
EQUAL: "primitive",
|
|
71
|
-
NOTEQUAL: "primitive",
|
|
72
|
-
GEQ: "numeric",
|
|
73
|
-
GREATER: "numeric",
|
|
74
|
-
LEQ: "numeric",
|
|
75
|
-
LESS: "numeric",
|
|
76
|
-
IN: "array",
|
|
77
|
-
NOTIN: "array",
|
|
78
|
-
MATCH: "string",
|
|
79
|
-
NOTMATCH: "string"
|
|
80
|
-
};
|
|
81
2
|
var TYPE_PRIORITY = ["boolean", "date", "number", "string"];
|
|
82
|
-
// src/regex.ts
|
|
83
|
-
var ESCAPE_REGEX = "(?<=(?<!\\\\)(?:\\\\\\\\)*)";
|
|
84
|
-
|
|
85
3
|
// src/errors.ts
|
|
86
4
|
function constructHeader(startToken, startIndex, endToken, endIndex) {
|
|
87
5
|
let header = "Token #";
|
|
@@ -146,800 +64,1027 @@ class ConstraintError extends Error {
|
|
|
146
64
|
}
|
|
147
65
|
}
|
|
148
66
|
|
|
149
|
-
// src/
|
|
150
|
-
var
|
|
151
|
-
var
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
trimmed = trimmed.trimStart();
|
|
157
|
-
const lengthDiff = trimmed.length - pretrimLength;
|
|
158
|
-
if (trimmed) {
|
|
159
|
-
const token = { content: trimmed, index: index - lengthDiff };
|
|
160
|
-
array.push(token);
|
|
161
|
-
return token;
|
|
162
|
-
}
|
|
67
|
+
// src/regex.ts
|
|
68
|
+
var ESCAPE_REGEX = "(?<=(?<!\\\\)(?:\\\\\\\\)*)";
|
|
69
|
+
var NO_ESCAPE_ERROR = new Error("Your JS runtime version does not include RegExp.escape(). Please update or use a polyfill");
|
|
70
|
+
function createQuoteRegexString(quotes) {
|
|
71
|
+
if (!RegExp.escape)
|
|
72
|
+
throw NO_ESCAPE_ERROR;
|
|
73
|
+
return `${ESCAPE_REGEX}(?<quote>${quotes.map((q) => RegExp.escape(q)).join("|")})(?<quotecontent>.*?)${ESCAPE_REGEX}\\k<quote>`;
|
|
163
74
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let subopenings = 0;
|
|
177
|
-
let endToken;
|
|
178
|
-
for (const submatch of matches) {
|
|
179
|
-
if (match[0] === submatch[0] || match[0] === submatch[0])
|
|
180
|
-
++subopenings;
|
|
181
|
-
if (BRACKETS.some(([o, c]) => match[0] === o && submatch[0] === c)) {
|
|
182
|
-
if (subopenings)
|
|
183
|
-
--subopenings;
|
|
184
|
-
else {
|
|
185
|
-
endToken = {
|
|
186
|
-
content: submatch[0],
|
|
187
|
-
index: submatch.index
|
|
188
|
-
};
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const subtokens = endToken ? _tokenize(expression.slice(startToken.index + startToken.content.length, endToken.index), new RegExp(`${"(?<=(?<!\\\\)(?:\\\\\\\\)*)(?<quote>\\x27|\\x22|\\x60)(?<quotecontent>.*?)(?<=(?<!\\\\)(?:\\\\\\\\)*)\\k<quote>"}|${ESCAPE_REGEX},`, "g")) : _tokenize(expression.slice(startToken.index + startToken.content.length), TOKEN_REGEX);
|
|
194
|
-
for (const subtoken of subtokens)
|
|
195
|
-
subtoken.index += match.index + 1;
|
|
196
|
-
tokens.push(...subtokens);
|
|
197
|
-
if (endToken) {
|
|
198
|
-
tokens.push(endToken);
|
|
199
|
-
lastMatchEnd = endToken.index + endToken.content.length;
|
|
200
|
-
} else {
|
|
201
|
-
lastMatchEnd = expression.length;
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
continue;
|
|
205
|
-
} else {
|
|
206
|
-
pushSanitized(tokens, match.groups?.quotecontent !== undefined ? expression.slice(match.index, match.index + match[0].length) : match[0], match.index);
|
|
207
|
-
}
|
|
208
|
-
lastMatchEnd = match.index + match[0].length;
|
|
75
|
+
function createArrayDelimitRegexString(quotes, delimiters) {
|
|
76
|
+
if (!RegExp.escape)
|
|
77
|
+
throw NO_ESCAPE_ERROR;
|
|
78
|
+
return `${createQuoteRegexString(quotes)}|${ESCAPE_REGEX}(?:${delimiters.map((d) => RegExp.escape(d)).join("|")})`;
|
|
79
|
+
}
|
|
80
|
+
function isAlpha(text) {
|
|
81
|
+
for (let c = 0;c < text.length; ++c) {
|
|
82
|
+
const char = text.charCodeAt(c);
|
|
83
|
+
const isUpper = char >= 65 && char <= 90;
|
|
84
|
+
const isLower = char >= 97 && char <= 122;
|
|
85
|
+
if (!isUpper && !isLower)
|
|
86
|
+
return false;
|
|
209
87
|
}
|
|
210
|
-
|
|
211
|
-
return tokens;
|
|
88
|
+
return true;
|
|
212
89
|
}
|
|
213
|
-
function
|
|
214
|
-
|
|
90
|
+
function createTokenRegexString(keywords, quotes) {
|
|
91
|
+
if (!RegExp.escape)
|
|
92
|
+
throw NO_ESCAPE_ERROR;
|
|
93
|
+
const QUOTE_REGEX_STR = createQuoteRegexString(quotes);
|
|
94
|
+
const tokens = keywords.sort((a, b) => b.length - a.length).map((alias) => {
|
|
95
|
+
const escaped = RegExp.escape(alias);
|
|
96
|
+
return isAlpha(alias) ? `(?<=${ESCAPE_REGEX}\\s|^)${escaped}(?=${ESCAPE_REGEX}\\s|$)` : `${ESCAPE_REGEX}${escaped}`;
|
|
97
|
+
});
|
|
98
|
+
const full = `${QUOTE_REGEX_STR}|${tokens.join("|")}`;
|
|
99
|
+
return full;
|
|
215
100
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
101
|
+
|
|
102
|
+
// src/parser.ts
|
|
103
|
+
class WizardParser {
|
|
104
|
+
static DEFAULT_OPERATORS = {
|
|
105
|
+
AND: {
|
|
106
|
+
negationName: "OR",
|
|
107
|
+
type: "productjunction",
|
|
108
|
+
aliases: ["&&", "&", "^", "∧"],
|
|
109
|
+
negationAliases: ["||", "|", "V", "∨"]
|
|
110
|
+
},
|
|
111
|
+
EQUAL: {
|
|
112
|
+
negationName: "NOTEQUAL",
|
|
113
|
+
type: "primitive",
|
|
114
|
+
aliases: ["EQUALS", "EQ", "IS", "=", "=="],
|
|
115
|
+
negationAliases: ["NOTEQUALS", "NEQ", "ISNT", "!=", "!==", "≠"]
|
|
116
|
+
},
|
|
117
|
+
LESS: {
|
|
118
|
+
negationName: "GEQ",
|
|
119
|
+
type: "numeric",
|
|
120
|
+
aliases: ["<"],
|
|
121
|
+
negationAliases: [">=", "=>", "≥"],
|
|
122
|
+
exclusionary: true
|
|
123
|
+
},
|
|
124
|
+
GREATER: {
|
|
125
|
+
negationName: "LEQ",
|
|
126
|
+
type: "numeric",
|
|
127
|
+
aliases: [">", "MORE", "MORETHAN"],
|
|
128
|
+
negationAliases: ["<=", "=<", "≤"],
|
|
129
|
+
exclusionary: true
|
|
130
|
+
},
|
|
131
|
+
IN: {
|
|
132
|
+
negationName: "NOTIN",
|
|
133
|
+
type: "array",
|
|
134
|
+
aliases: [":", "∈"],
|
|
135
|
+
negationAliases: ["!:", "∉"]
|
|
136
|
+
},
|
|
137
|
+
MATCH: {
|
|
138
|
+
negationName: "NOTMATCH",
|
|
139
|
+
type: "string",
|
|
140
|
+
aliases: ["MATCHES", "~", "≅"],
|
|
141
|
+
negationAliases: ["NOTMATCHES", "!~", "≇"]
|
|
142
|
+
}
|
|
225
143
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
144
|
+
static DEFAULT_DIALECTS = {
|
|
145
|
+
programmatic: {
|
|
146
|
+
AND: "&",
|
|
147
|
+
OR: "|",
|
|
148
|
+
EQUAL: "=",
|
|
149
|
+
NOTEQUAL: "!=",
|
|
150
|
+
GEQ: ">=",
|
|
151
|
+
GREATER: ">",
|
|
152
|
+
LEQ: "<=",
|
|
153
|
+
LESS: "<",
|
|
154
|
+
IN: ":",
|
|
155
|
+
NOTIN: "!:",
|
|
156
|
+
MATCH: "~",
|
|
157
|
+
NOTMATCH: "!~"
|
|
158
|
+
},
|
|
159
|
+
linguistic: {
|
|
160
|
+
AND: "AND",
|
|
161
|
+
OR: "OR",
|
|
162
|
+
EQUAL: "EQUALS",
|
|
163
|
+
NOTEQUAL: "NOTEQUALS",
|
|
164
|
+
GEQ: "GEQ",
|
|
165
|
+
GREATER: "GREATER",
|
|
166
|
+
LEQ: "LEQ",
|
|
167
|
+
LESS: "LESS",
|
|
168
|
+
IN: "IN",
|
|
169
|
+
NOTIN: "NOTIN",
|
|
170
|
+
MATCH: "MATCHES",
|
|
171
|
+
NOTMATCH: "NOTMATCHES"
|
|
172
|
+
},
|
|
173
|
+
formal: {
|
|
174
|
+
AND: "∧",
|
|
175
|
+
OR: "∨",
|
|
176
|
+
EQUAL: "=",
|
|
177
|
+
NOTEQUAL: "≠",
|
|
178
|
+
GEQ: "≥",
|
|
179
|
+
GREATER: ">",
|
|
180
|
+
LEQ: "≤",
|
|
181
|
+
LESS: "<",
|
|
182
|
+
IN: "∈",
|
|
183
|
+
NOTIN: "∉",
|
|
184
|
+
MATCH: "≅",
|
|
185
|
+
NOTMATCH: "≇"
|
|
237
186
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
break;
|
|
267
|
-
case "LESS":
|
|
268
|
-
expression.operation = "GEQ";
|
|
269
|
-
break;
|
|
270
|
-
case "LEQ":
|
|
271
|
-
expression.operation = "GREATER";
|
|
272
|
-
break;
|
|
273
|
-
case "GREATER":
|
|
274
|
-
expression.operation = "LEQ";
|
|
275
|
-
break;
|
|
276
|
-
case "IN":
|
|
277
|
-
expression.operation = "NOTIN";
|
|
278
|
-
break;
|
|
279
|
-
case "NOTIN":
|
|
280
|
-
expression.operation = "IN";
|
|
281
|
-
break;
|
|
282
|
-
case "MATCH":
|
|
283
|
-
expression.operation = "NOTMATCH";
|
|
284
|
-
break;
|
|
285
|
-
case "NOTMATCH":
|
|
286
|
-
expression.operation = "MATCH";
|
|
287
|
-
break;
|
|
187
|
+
};
|
|
188
|
+
static DEFAULT_QUOTES = ['"', "'", "`"];
|
|
189
|
+
static DEFAULT_NEGATORS = ["!"];
|
|
190
|
+
static DEFAULT_ARRAY_DELIMITERS = [","];
|
|
191
|
+
static DEFAULT_ARRAY_BRACKETS = [
|
|
192
|
+
["[", "]"],
|
|
193
|
+
["{", "}"]
|
|
194
|
+
];
|
|
195
|
+
static DEFAULT_GROUP_BRACKETS = [
|
|
196
|
+
["(", ")"]
|
|
197
|
+
];
|
|
198
|
+
OPERATOR_DICTIONARY;
|
|
199
|
+
CONFIG;
|
|
200
|
+
TOKEN_REGEX;
|
|
201
|
+
QUOTE_REGEX;
|
|
202
|
+
QUOTE_EDGE_REGEX;
|
|
203
|
+
ARRAY_DELIMITER_REGEX;
|
|
204
|
+
FIELD_LIST;
|
|
205
|
+
constructor(config = {}) {
|
|
206
|
+
this.CONFIG = config;
|
|
207
|
+
if (!config.operators) {
|
|
208
|
+
config.operators = WizardParser.DEFAULT_OPERATORS;
|
|
209
|
+
if (config.implicitCondition === undefined) {
|
|
210
|
+
config.implicitCondition = {
|
|
211
|
+
operator: "EQUAL",
|
|
212
|
+
value: "true",
|
|
213
|
+
asType: "boolean"
|
|
214
|
+
};
|
|
288
215
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
216
|
+
if (!config.dialects)
|
|
217
|
+
config.dialects = WizardParser.DEFAULT_DIALECTS;
|
|
218
|
+
}
|
|
219
|
+
const symbolPool = new Set;
|
|
220
|
+
config.symbols ??= {};
|
|
221
|
+
config.symbols.quotes ??= WizardParser.DEFAULT_QUOTES;
|
|
222
|
+
config.symbols.negators ??= WizardParser.DEFAULT_NEGATORS;
|
|
223
|
+
config.symbols.arrayDelimiters ??= WizardParser.DEFAULT_ARRAY_DELIMITERS;
|
|
224
|
+
config.symbols.arrayBrackets ??= WizardParser.DEFAULT_ARRAY_BRACKETS;
|
|
225
|
+
config.symbols.groupBrackets ??= WizardParser.DEFAULT_GROUP_BRACKETS;
|
|
226
|
+
for (const [opening, closing] of config.symbols.arrayBrackets) {
|
|
227
|
+
if (opening === closing)
|
|
228
|
+
throw Error(`Opening array bracket cannot be the same as closing array bracket ("${opening}")`);
|
|
229
|
+
if (symbolPool.has(opening))
|
|
230
|
+
throw Error(`Opening array bracket "${opening}" uses a symbol already defined elsewhere in the parser`);
|
|
231
|
+
if (symbolPool.has(closing))
|
|
232
|
+
throw Error(`Closing array bracket "${closing}" uses a symbol already defined elsewhere in the parser`);
|
|
233
|
+
symbolPool.add(opening);
|
|
234
|
+
symbolPool.add(closing);
|
|
235
|
+
}
|
|
236
|
+
for (const [opening, closing] of config.symbols.groupBrackets) {
|
|
237
|
+
if (opening === closing)
|
|
238
|
+
throw Error(`Opening group bracket cannot be the same as closing group bracket ("${opening}")`);
|
|
239
|
+
if (symbolPool.has(opening))
|
|
240
|
+
throw Error(`Opening group bracket "${opening}" uses a symbol already defined elsewhere in the parser`);
|
|
241
|
+
if (symbolPool.has(closing))
|
|
242
|
+
throw Error(`Closing group bracket "${closing}" uses a symbol already defined elsewhere in the parser`);
|
|
243
|
+
symbolPool.add(opening);
|
|
244
|
+
symbolPool.add(closing);
|
|
245
|
+
}
|
|
246
|
+
for (const quote of config.symbols.quotes) {
|
|
247
|
+
if (symbolPool.has(quote))
|
|
248
|
+
throw Error(`Quote "${quote}" uses a symbol already defined elsewhere in the parser`);
|
|
249
|
+
symbolPool.add(quote);
|
|
250
|
+
}
|
|
251
|
+
this.FIELD_LIST = new Map;
|
|
252
|
+
if (config.caseInsensitive) {
|
|
253
|
+
if (config.types) {
|
|
254
|
+
for (const key in config.types)
|
|
255
|
+
this.FIELD_LIST.set(key.toLowerCase(), key);
|
|
317
256
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const num = Number(raw);
|
|
322
|
-
const date = dateInterpreter(isNaN(num) ? unescaped : num);
|
|
323
|
-
if (isNaN(+date))
|
|
324
|
-
break;
|
|
325
|
-
return date;
|
|
257
|
+
if (config.restricted) {
|
|
258
|
+
for (const key in config.restricted)
|
|
259
|
+
this.FIELD_LIST.set(key.toLowerCase(), key);
|
|
326
260
|
}
|
|
327
261
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
function validateCondition(condition, constraints, valueIsImplicit, ctx) {
|
|
340
|
-
let validated = false;
|
|
341
|
-
condition.field = processToken(condition.field).unescaped;
|
|
342
|
-
const field = constraints?.caseInsensitive ? [...Object.keys(constraints.types ?? {}), ...Object.keys(constraints.restricted ?? {})].find((k) => k.toLowerCase() === condition.field.toLowerCase()) ?? condition.field : condition.field;
|
|
343
|
-
const restriction = constraints?.restricted?.[field];
|
|
344
|
-
const type = constraints?.types?.[field];
|
|
345
|
-
if (constraints?.disallowUnvalidated && restriction === undefined && type === undefined)
|
|
346
|
-
throw new ConstraintError(`Unknown field "${condition.field}"`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
347
|
-
const operationType = COMPARISON_TYPE_DICTIONARY[condition.operation];
|
|
348
|
-
const types = type && (Array.isArray(type) ? type : [type]);
|
|
349
|
-
const processedValues = (Array.isArray(condition.value) ? condition.value : [condition.value]).map((v) => processToken(v));
|
|
350
|
-
if (restriction === true)
|
|
351
|
-
throw new ConstraintError(`Field "${condition.field}" is restricted`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
352
|
-
else if (Array.isArray(restriction)) {
|
|
353
|
-
const [philosophy, checks] = restriction;
|
|
354
|
-
if (philosophy === "deny") {
|
|
355
|
-
for (const check of checks) {
|
|
356
|
-
if (check instanceof RegExp) {
|
|
357
|
-
if (processedValues.some(({ unescaped }) => check.test(unescaped))) {
|
|
358
|
-
throw new ConstraintError(`Value for field "${condition.field}" violates prohibitive pattern constraint "${check.toString()}". Prohibited values/patterns: Allowed values/patterns: ${checks.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
262
|
+
this.OPERATOR_DICTIONARY = {};
|
|
263
|
+
for (const operationName in config.operators) {
|
|
264
|
+
const operation = config.operators[operationName];
|
|
265
|
+
for (const symbol of symbolPool) {
|
|
266
|
+
if (operationName.includes(symbol))
|
|
267
|
+
throw Error(`Operator "${operationName}" contains symbol "${symbol}"`);
|
|
268
|
+
if (operation.aliases) {
|
|
269
|
+
for (const alias of operation.aliases) {
|
|
270
|
+
if (alias.includes(symbol))
|
|
271
|
+
throw Error(`Operator "${operationName}" alias "${alias}" contains symbol "${symbol}"`);
|
|
359
272
|
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
273
|
+
}
|
|
274
|
+
if (operation.negationName.includes(symbol))
|
|
275
|
+
throw Error(`Operator "${operation.negationName}" contains symbol "${symbol}"`);
|
|
276
|
+
if (operation.negationAliases) {
|
|
277
|
+
for (const alias of operation.negationAliases) {
|
|
278
|
+
if (alias.includes(symbol))
|
|
279
|
+
throw Error(`Operator "${operation.negationName}" alias "${alias}" contains symbol "${symbol}"`);
|
|
363
280
|
}
|
|
364
281
|
}
|
|
365
282
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
283
|
+
const opDef = {
|
|
284
|
+
name: operationName,
|
|
285
|
+
type: operation.type,
|
|
286
|
+
negation: operation.negationName,
|
|
287
|
+
exclusionary: operation.exclusionary ?? false
|
|
288
|
+
};
|
|
289
|
+
const negOpDef = {
|
|
290
|
+
name: operation.negationName,
|
|
291
|
+
type: operation.type === "sumjunction" ? "productjunction" : operation.type === "productjunction" ? "sumjunction" : operation.type,
|
|
292
|
+
negation: operationName,
|
|
293
|
+
exclusionary: !operation.exclusionary
|
|
294
|
+
};
|
|
295
|
+
if (operationName in this.OPERATOR_DICTIONARY)
|
|
296
|
+
throw Error(`Two operator definitions have been supplied with the same name "${operationName}"`);
|
|
297
|
+
this.OPERATOR_DICTIONARY[opDef.name] = opDef;
|
|
298
|
+
if (operation.negationName in this.OPERATOR_DICTIONARY)
|
|
299
|
+
throw Error(`An operator definition shares a negation name with another operator's definition "${operation.negationName}"`);
|
|
300
|
+
this.OPERATOR_DICTIONARY[negOpDef.name] = negOpDef;
|
|
301
|
+
if (operation.aliases) {
|
|
302
|
+
for (const alias of operation.aliases) {
|
|
303
|
+
if (alias in this.OPERATOR_DICTIONARY)
|
|
304
|
+
throw Error(`An operator definition has an alias that refers to another operator "${alias}"`);
|
|
305
|
+
this.OPERATOR_DICTIONARY[alias] = opDef;
|
|
370
306
|
}
|
|
371
307
|
}
|
|
308
|
+
if (operation.negationAliases) {
|
|
309
|
+
for (const alias of operation.negationAliases) {
|
|
310
|
+
if (alias in this.OPERATOR_DICTIONARY)
|
|
311
|
+
throw Error(`An operator definition has a negation alias that refers to another operator "${alias}"`);
|
|
312
|
+
this.OPERATOR_DICTIONARY[alias] = negOpDef;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
for (const negator of config.symbols.negators) {
|
|
317
|
+
if (symbolPool.has(negator))
|
|
318
|
+
throw Error(`Negator "${negator}" uses a symbol already defined elsewhere in the parser`);
|
|
319
|
+
symbolPool.add(negator);
|
|
372
320
|
}
|
|
373
|
-
|
|
321
|
+
this.TOKEN_REGEX = new RegExp(createTokenRegexString(Object.keys(this.OPERATOR_DICTIONARY).concat(config.symbols.groupBrackets.flat()).concat(config.symbols.arrayBrackets.flat()).concat(config.symbols.negators).concat(config.symbols.arrayDelimiters), config.symbols.quotes), "g");
|
|
322
|
+
const quoteRegexStr = createQuoteRegexString(config.symbols.quotes);
|
|
323
|
+
this.QUOTE_REGEX = new RegExp(quoteRegexStr);
|
|
324
|
+
this.QUOTE_EDGE_REGEX = new RegExp(`^${quoteRegexStr}$`);
|
|
325
|
+
this.ARRAY_DELIMITER_REGEX = new RegExp(createArrayDelimitRegexString(config.symbols.quotes, config.symbols.arrayDelimiters), "g");
|
|
326
|
+
Object.freeze(config);
|
|
374
327
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
opTypes = ["boolean"];
|
|
385
|
-
else {
|
|
386
|
-
switch (operationType) {
|
|
387
|
-
case "primitive":
|
|
388
|
-
case "array":
|
|
389
|
-
opTypes = ["boolean", "number", "string"];
|
|
390
|
-
if (types?.includes("date"))
|
|
391
|
-
opTypes.push("date");
|
|
392
|
-
break;
|
|
393
|
-
case "numeric":
|
|
394
|
-
opTypes = ["number"];
|
|
395
|
-
if (types?.includes("date"))
|
|
396
|
-
opTypes.push("date");
|
|
397
|
-
break;
|
|
398
|
-
case "string":
|
|
399
|
-
opTypes = ["string"];
|
|
400
|
-
break;
|
|
401
|
-
}
|
|
328
|
+
static pushSanitized(array, item, index) {
|
|
329
|
+
let trimmed = item.trimEnd();
|
|
330
|
+
const pretrimLength = trimmed.length;
|
|
331
|
+
trimmed = trimmed.trimStart();
|
|
332
|
+
const lengthDiff = trimmed.length - pretrimLength;
|
|
333
|
+
if (trimmed) {
|
|
334
|
+
const token = { content: trimmed, index: index - lengthDiff };
|
|
335
|
+
array.push(token);
|
|
336
|
+
return token;
|
|
402
337
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
338
|
+
}
|
|
339
|
+
interpretDate(v) {
|
|
340
|
+
if (this.CONFIG.dateInterpreter)
|
|
341
|
+
return this.CONFIG.dateInterpreter(v);
|
|
342
|
+
else
|
|
343
|
+
return new Date(v);
|
|
344
|
+
}
|
|
345
|
+
_tokenize(expression, pattern) {
|
|
346
|
+
const tokens = [];
|
|
347
|
+
const matches = expression.toUpperCase().matchAll(pattern);
|
|
348
|
+
let lastMatchEnd = null;
|
|
349
|
+
for (const match of matches) {
|
|
350
|
+
WizardParser.pushSanitized(tokens, expression.slice(lastMatchEnd ?? 0, match.index), lastMatchEnd === null ? 0 : lastMatchEnd);
|
|
351
|
+
if (this.CONFIG.symbols.arrayBrackets.some(([o]) => match[0] === o)) {
|
|
352
|
+
const startToken = {
|
|
353
|
+
content: match[0],
|
|
354
|
+
index: match.index
|
|
355
|
+
};
|
|
356
|
+
tokens.push(startToken);
|
|
357
|
+
let subopenings = 0;
|
|
358
|
+
let endToken;
|
|
359
|
+
for (const submatch of matches) {
|
|
360
|
+
if (match[0] === submatch[0] || match[0] === submatch[0])
|
|
361
|
+
++subopenings;
|
|
362
|
+
if (this.CONFIG.symbols.arrayBrackets.some(([o, c]) => match[0] === o && submatch[0] === c)) {
|
|
363
|
+
if (subopenings)
|
|
364
|
+
--subopenings;
|
|
365
|
+
else {
|
|
366
|
+
endToken = {
|
|
367
|
+
content: submatch[0],
|
|
368
|
+
index: submatch.index
|
|
369
|
+
};
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const subtokens = endToken ? this._tokenize(expression.slice(startToken.index + startToken.content.length, endToken.index), this.ARRAY_DELIMITER_REGEX) : this._tokenize(expression.slice(startToken.index + startToken.content.length), pattern);
|
|
375
|
+
for (const subtoken of subtokens)
|
|
376
|
+
subtoken.index += match.index + 1;
|
|
377
|
+
tokens.push(...subtokens);
|
|
378
|
+
if (endToken) {
|
|
379
|
+
tokens.push(endToken);
|
|
380
|
+
lastMatchEnd = endToken.index + endToken.content.length;
|
|
381
|
+
} else {
|
|
382
|
+
lastMatchEnd = expression.length;
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
continue;
|
|
386
|
+
} else {
|
|
387
|
+
WizardParser.pushSanitized(tokens, match.groups?.quotecontent !== undefined ? expression.slice(match.index, match.index + match[0].length) : match[0], match.index);
|
|
415
388
|
}
|
|
389
|
+
lastMatchEnd = match.index + match[0].length;
|
|
416
390
|
}
|
|
391
|
+
WizardParser.pushSanitized(tokens, expression.slice(lastMatchEnd ?? 0), lastMatchEnd ?? 0);
|
|
392
|
+
return tokens;
|
|
417
393
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (inConjunction) {
|
|
435
|
-
const prior = expressions.at(-1);
|
|
436
|
-
if (!prior)
|
|
437
|
-
throw new ParseError("Unexpected: Expression list empty when parser is meant to append to an AND group", ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
438
|
-
if (prior.type !== "group" || prior.operation !== "AND")
|
|
439
|
-
throw new ParseError("Unexpected: Last expression is not an AND group yet parser thinks it's appending to one", ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
440
|
-
return prior.constituents;
|
|
394
|
+
tokenize(expression) {
|
|
395
|
+
return this._tokenize(expression, this.TOKEN_REGEX);
|
|
396
|
+
}
|
|
397
|
+
static getClosingIndex(tokens, start, opening, closing) {
|
|
398
|
+
let openingCount = 1;
|
|
399
|
+
for (let index = start + 1;index < tokens.length; ++index) {
|
|
400
|
+
switch (tokens[index].content) {
|
|
401
|
+
case opening:
|
|
402
|
+
++openingCount;
|
|
403
|
+
break;
|
|
404
|
+
case closing:
|
|
405
|
+
--openingCount;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
if (openingCount === 0)
|
|
409
|
+
return index;
|
|
441
410
|
}
|
|
442
|
-
return
|
|
411
|
+
return -1;
|
|
443
412
|
}
|
|
444
|
-
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
413
|
+
processToken(token) {
|
|
414
|
+
const unquoted = token.match(this.QUOTE_EDGE_REGEX)?.groups?.quotecontent;
|
|
415
|
+
const escaped = unquoted ?? token;
|
|
416
|
+
const unescaped = escaped.replaceAll(new RegExp(`(?<!${ESCAPE_REGEX}\\\\)\\\\`, "g"), "");
|
|
417
|
+
return {
|
|
418
|
+
raw: token,
|
|
419
|
+
unquoted,
|
|
420
|
+
escaped,
|
|
421
|
+
unescaped
|
|
451
422
|
};
|
|
452
|
-
if (!ctx)
|
|
453
|
-
ctx = baseCtx;
|
|
454
|
-
const group = getExpressionGroup(ctx);
|
|
455
|
-
if (field && comparisonOperation && value) {
|
|
456
|
-
if (!expectingExpression)
|
|
457
|
-
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
458
|
-
group.push(validateCondition({
|
|
459
|
-
type: "condition",
|
|
460
|
-
field: field.content,
|
|
461
|
-
operation: comparisonOperation.content,
|
|
462
|
-
value: value.content
|
|
463
|
-
}, constraints, value.implicit, baseCtx));
|
|
464
|
-
inConjunction = false;
|
|
465
|
-
expectingExpression = false;
|
|
466
|
-
} else if (field && !comparisonOperation && !value) {
|
|
467
|
-
if (!expectingExpression)
|
|
468
|
-
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
469
|
-
group.push(validateCondition({
|
|
470
|
-
type: "condition",
|
|
471
|
-
field: field.content,
|
|
472
|
-
operation: "EQUAL",
|
|
473
|
-
value: "true"
|
|
474
|
-
}, constraints, true, baseCtx));
|
|
475
|
-
inConjunction = false;
|
|
476
|
-
expectingExpression = false;
|
|
477
|
-
} else if (field || comparisonOperation || value !== undefined)
|
|
478
|
-
throw new ParseError("Failed to resolve condition; missing operand or operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
479
|
-
field = undefined;
|
|
480
|
-
comparisonOperation = undefined;
|
|
481
|
-
value = undefined;
|
|
482
423
|
}
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
if (
|
|
486
|
-
throw new
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
if (
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
424
|
+
complementExpression(expression) {
|
|
425
|
+
const inverse = this.OPERATOR_DICTIONARY[expression.operation].negation;
|
|
426
|
+
if (!inverse)
|
|
427
|
+
throw new Error(`Could not find inverse operation given operation name "${expression.operation}"`);
|
|
428
|
+
expression.operation = inverse;
|
|
429
|
+
if (expression.type === "group")
|
|
430
|
+
expression.constituents.forEach((e) => this.complementExpression(e));
|
|
431
|
+
}
|
|
432
|
+
coerceType(processedToken, fieldTypes, operatorTypes) {
|
|
433
|
+
const { raw, unescaped } = processedToken;
|
|
434
|
+
let operatorHits = 0;
|
|
435
|
+
let fieldHits = 0;
|
|
436
|
+
for (const type of TYPE_PRIORITY) {
|
|
437
|
+
const operatorHit = operatorTypes.includes(type);
|
|
438
|
+
const fieldHit = fieldTypes.includes(type);
|
|
439
|
+
if (operatorHit)
|
|
440
|
+
++operatorHits;
|
|
441
|
+
if (fieldHit)
|
|
442
|
+
++fieldHits;
|
|
443
|
+
if (!operatorHit || !fieldHit)
|
|
444
|
+
continue;
|
|
445
|
+
switch (type) {
|
|
446
|
+
case "boolean":
|
|
447
|
+
if (raw === "true")
|
|
448
|
+
return true;
|
|
449
|
+
else if (raw === "false")
|
|
450
|
+
return false;
|
|
451
|
+
break;
|
|
452
|
+
case "number": {
|
|
453
|
+
const num = Number(raw);
|
|
454
|
+
if (isNaN(num))
|
|
455
|
+
break;
|
|
456
|
+
return num;
|
|
457
|
+
}
|
|
458
|
+
case "string":
|
|
459
|
+
return unescaped;
|
|
460
|
+
case "date": {
|
|
461
|
+
const num = Number(raw);
|
|
462
|
+
const date = this.interpretDate(isNaN(num) ? unescaped : num);
|
|
463
|
+
if (isNaN(+date))
|
|
464
|
+
break;
|
|
465
|
+
return date;
|
|
505
466
|
}
|
|
506
467
|
}
|
|
507
|
-
|
|
508
|
-
|
|
468
|
+
if (operatorTypes.includes(type))
|
|
469
|
+
--operatorHits;
|
|
470
|
+
if (fieldTypes.includes(type))
|
|
471
|
+
--fieldHits;
|
|
509
472
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
473
|
+
if (!operatorHits)
|
|
474
|
+
throw new Error("operator");
|
|
475
|
+
if (!fieldHits)
|
|
476
|
+
throw new Error("field");
|
|
477
|
+
throw new Error("disagreement");
|
|
478
|
+
}
|
|
479
|
+
validateCondition(condition, valueIsImplicit, ctx) {
|
|
480
|
+
let validated = false;
|
|
481
|
+
condition.field = this.processToken(condition.field).unescaped;
|
|
482
|
+
const field = this.CONFIG.caseInsensitive ? this.FIELD_LIST.get(condition.field.toLowerCase()) ?? condition.field : condition.field;
|
|
483
|
+
const restriction = this.CONFIG.restricted?.[field];
|
|
484
|
+
const fieldType = this.CONFIG.types?.[field];
|
|
485
|
+
if (this.CONFIG.disallowUnvalidated && restriction === undefined && fieldType === undefined)
|
|
486
|
+
throw new ConstraintError(`Unknown field "${condition.field}"`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
487
|
+
const fieldTypes = fieldType && (Array.isArray(fieldType) ? fieldType : [fieldType]);
|
|
488
|
+
const operationType = this.OPERATOR_DICTIONARY[condition.operation].type;
|
|
489
|
+
const processedValues = (Array.isArray(condition.value) ? condition.value : [condition.value]).map((v) => this.processToken(v));
|
|
490
|
+
if (restriction === true)
|
|
491
|
+
throw new ConstraintError(`Field "${condition.field}" is restricted`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
492
|
+
else if (Array.isArray(restriction)) {
|
|
493
|
+
const [philosophy, checks] = restriction;
|
|
494
|
+
if (philosophy === "deny") {
|
|
495
|
+
for (const check of checks) {
|
|
496
|
+
if (check instanceof RegExp) {
|
|
497
|
+
if (processedValues.some(({ unescaped }) => check.test(unescaped))) {
|
|
498
|
+
throw new ConstraintError(`Value for field "${condition.field}" violates prohibitive pattern constraint "${check.toString()}". Prohibited values/patterns: Allowed values/patterns: ${checks.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
if (processedValues.some(({ unescaped }) => unescaped === check)) {
|
|
502
|
+
throw new ConstraintError(`Forbidden value "${check}" for field "${condition.field}". Prohibited values/patterns: ${checks.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
for (const { unescaped } of processedValues) {
|
|
508
|
+
if (!checks.some((c) => c instanceof RegExp ? c.test(unescaped) : c === unescaped)) {
|
|
509
|
+
throw new ConstraintError(`Value for field "${condition.field}" does not meet any allowed value/pattern. Allowed values/patterns: ${checks.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
543
510
|
}
|
|
544
|
-
case "OR":
|
|
545
|
-
inConjunction = true;
|
|
546
|
-
if (prior.type === "group" && prior.operation === "AND")
|
|
547
|
-
continue;
|
|
548
|
-
expressions.splice(-1, 1);
|
|
549
|
-
expressions.push({
|
|
550
|
-
type: "group",
|
|
551
|
-
operation: "AND",
|
|
552
|
-
constituents: [
|
|
553
|
-
prior
|
|
554
|
-
]
|
|
555
|
-
});
|
|
556
|
-
continue;
|
|
557
511
|
}
|
|
558
512
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
513
|
+
validated = true;
|
|
514
|
+
}
|
|
515
|
+
if (operationType === "array" && !Array.isArray(condition.value))
|
|
516
|
+
throw new ConstraintError(`Value "${condition.value}" is not permitted for "${condition.operation}" which expects an array`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
517
|
+
else if (operationType !== "array" && Array.isArray(condition.value))
|
|
518
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" is not permitted for "${condition.operation}" which expects a non-array value`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
519
|
+
for (let i = 0;i < processedValues.length; ++i) {
|
|
520
|
+
const v = processedValues[i];
|
|
521
|
+
let opTypes;
|
|
522
|
+
if (valueIsImplicit) {
|
|
523
|
+
const implicitTypes = this.CONFIG.implicitCondition.asType;
|
|
524
|
+
opTypes = Array.isArray(implicitTypes) ? implicitTypes : [implicitTypes];
|
|
525
|
+
} else {
|
|
526
|
+
switch (operationType) {
|
|
527
|
+
case "primitive":
|
|
528
|
+
case "array":
|
|
529
|
+
opTypes = ["boolean", "number", "string"];
|
|
530
|
+
if (fieldTypes?.includes("date"))
|
|
531
|
+
opTypes.push("date");
|
|
532
|
+
break;
|
|
533
|
+
case "number":
|
|
534
|
+
opTypes = ["number"];
|
|
535
|
+
break;
|
|
536
|
+
case "date":
|
|
537
|
+
opTypes = ["date"];
|
|
538
|
+
break;
|
|
539
|
+
case "boolean":
|
|
540
|
+
opTypes = ["boolean"];
|
|
541
|
+
break;
|
|
542
|
+
case "numeric":
|
|
543
|
+
opTypes = ["number"];
|
|
544
|
+
if (fieldTypes?.includes("date"))
|
|
545
|
+
opTypes.push("date");
|
|
546
|
+
break;
|
|
547
|
+
case "string":
|
|
548
|
+
opTypes = ["string"];
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
const value = this.coerceType(v, fieldTypes ?? opTypes, opTypes);
|
|
554
|
+
if (Array.isArray(condition.value))
|
|
555
|
+
condition.value[i] = value;
|
|
556
|
+
else
|
|
557
|
+
condition.value = value;
|
|
558
|
+
} catch (err) {
|
|
559
|
+
switch (err.message) {
|
|
560
|
+
case "operator":
|
|
561
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" not allowed for operation "${condition.operation}" which only allows for "${operationType}" type`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
562
|
+
case "field":
|
|
563
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" only has types not permitted for field "${condition.field}". Allowed types: ${(fieldTypes ?? opTypes).join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
564
|
+
case "disagreement":
|
|
565
|
+
throw new ConstraintError(`Value "${condition.value.toString()}" using operation "${condition.operation}" cannot be coerced into a valid type for "${condition.field}". Allowed field types: ${(fieldTypes ?? ["ALL"]).join(", ")}; Allowed operator types: ${opTypes.join(", ")}`, ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
566
|
+
}
|
|
564
567
|
}
|
|
565
|
-
continue;
|
|
566
568
|
}
|
|
567
|
-
if (
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
569
|
+
if (fieldTypes)
|
|
570
|
+
validated = true;
|
|
571
|
+
const edit = condition;
|
|
572
|
+
edit.field = field;
|
|
573
|
+
edit.validated = validated;
|
|
574
|
+
return edit;
|
|
575
|
+
}
|
|
576
|
+
_parse(tokens, _offset) {
|
|
577
|
+
let field;
|
|
578
|
+
let comparisonOperation;
|
|
579
|
+
let value;
|
|
580
|
+
let inConjunction = false;
|
|
581
|
+
let activeGroupOperation;
|
|
582
|
+
let expectingExpression = true;
|
|
583
|
+
const expressions = [];
|
|
584
|
+
const getExpressionGroup = (ctx) => {
|
|
585
|
+
if (inConjunction) {
|
|
586
|
+
const prior = expressions.at(-1);
|
|
587
|
+
if (!prior)
|
|
588
|
+
throw new ParseError("Unexpected: Expression list empty when parser is meant to append to an AND group", ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
589
|
+
if (prior.type !== "group" || prior.operation !== "AND")
|
|
590
|
+
throw new ParseError("Unexpected: Last expression is not an AND group yet parser thinks it's appending to one", ctx?.tokens, ctx?.startToken, ctx?.startIndex, ctx?.endToken, ctx?.endIndex);
|
|
591
|
+
return prior.constituents;
|
|
592
|
+
}
|
|
593
|
+
return expressions;
|
|
594
|
+
};
|
|
595
|
+
const resolveCondition = (ctx) => {
|
|
596
|
+
const baseCtx = {
|
|
597
|
+
tokens,
|
|
598
|
+
startToken: field?.token ?? comparisonOperation?.token ?? value?.token,
|
|
599
|
+
startIndex: field?.index ?? comparisonOperation?.index ?? value?.index,
|
|
600
|
+
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
601
|
+
endIndex: value?.index ?? comparisonOperation?.index ?? field?.index
|
|
602
|
+
};
|
|
603
|
+
if (!ctx)
|
|
604
|
+
ctx = baseCtx;
|
|
605
|
+
const group = getExpressionGroup(ctx);
|
|
606
|
+
if (field && comparisonOperation && value) {
|
|
607
|
+
if (!expectingExpression)
|
|
608
|
+
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
609
|
+
group.push(this.validateCondition({
|
|
610
|
+
type: "condition",
|
|
611
|
+
field: field.content,
|
|
612
|
+
operation: comparisonOperation.content,
|
|
613
|
+
value: value.content
|
|
614
|
+
}, value.implicit, baseCtx));
|
|
615
|
+
inConjunction = false;
|
|
616
|
+
expectingExpression = false;
|
|
617
|
+
} else if (field && !comparisonOperation && !value) {
|
|
618
|
+
if (!expectingExpression)
|
|
619
|
+
throw new ParseError("Unexpected expression resolution before junctive operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
620
|
+
if (this.CONFIG.implicitCondition) {
|
|
621
|
+
group.push(this.validateCondition({
|
|
622
|
+
type: "condition",
|
|
623
|
+
field: field.content,
|
|
624
|
+
operation: this.CONFIG.implicitCondition.operator,
|
|
625
|
+
value: this.CONFIG.implicitCondition.value
|
|
626
|
+
}, true, baseCtx));
|
|
627
|
+
inConjunction = false;
|
|
628
|
+
expectingExpression = false;
|
|
629
|
+
} else
|
|
630
|
+
throw new ParseError("Failed to resolve condition; missing operand or operator (No implicit condition is configured)", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
631
|
+
} else if (field || comparisonOperation || value !== undefined)
|
|
632
|
+
throw new ParseError("Failed to resolve condition; missing operand or operator", ctx.tokens, ctx.startToken, ctx.startIndex, ctx.endToken, ctx.endIndex);
|
|
633
|
+
field = undefined;
|
|
634
|
+
comparisonOperation = undefined;
|
|
635
|
+
value = undefined;
|
|
636
|
+
};
|
|
637
|
+
for (let t = 0;t < tokens.length; ++t) {
|
|
638
|
+
const token = tokens[t];
|
|
639
|
+
if (this.CONFIG.symbols.groupBrackets.some(([, c]) => token.content === c))
|
|
640
|
+
throw new ParseError("Unexpected closing parenthesis", tokens, token, _offset + t);
|
|
641
|
+
if (this.CONFIG.symbols.arrayBrackets.some(([, c]) => token.content === c))
|
|
642
|
+
throw new ParseError("Unexpected closing bracket/brace", tokens, token, _offset + t);
|
|
643
|
+
const paren = this.CONFIG.symbols.groupBrackets.find(([o]) => token.content === o);
|
|
644
|
+
if (paren) {
|
|
645
|
+
if (field || comparisonOperation || value)
|
|
646
|
+
throw new ParseError("Tried to open a group during an operation", tokens, token, _offset + t);
|
|
647
|
+
const closingIndex = WizardParser.getClosingIndex(tokens, t, paren[0], paren[1]);
|
|
580
648
|
if (closingIndex === -1)
|
|
581
649
|
throw new ParseError("Missing closing parenthesis for group", tokens, token, _offset + t);
|
|
582
650
|
++t;
|
|
583
|
-
const
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
expressions.push(...futureSubExpression.constituents);
|
|
651
|
+
const subExpression = this._parse(tokens.slice(t, closingIndex), _offset + t);
|
|
652
|
+
if (subExpression) {
|
|
653
|
+
if (subExpression.type === "group" && subExpression.operation === activeGroupOperation)
|
|
654
|
+
expressions.push(...subExpression.constituents);
|
|
588
655
|
else {
|
|
589
|
-
const group = getExpressionGroup({
|
|
590
|
-
|
|
591
|
-
startToken: token,
|
|
592
|
-
startIndex: _offset + t,
|
|
593
|
-
endToken: tokens[closingIndex],
|
|
594
|
-
endIndex: closingIndex
|
|
595
|
-
});
|
|
596
|
-
group.push(futureSubExpression);
|
|
656
|
+
const group = getExpressionGroup({ tokens, startToken: token, startIndex: _offset + t });
|
|
657
|
+
group.push(subExpression);
|
|
597
658
|
inConjunction = false;
|
|
598
659
|
}
|
|
599
660
|
}
|
|
600
661
|
t = closingIndex;
|
|
601
662
|
continue;
|
|
602
663
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (op && OPERATION_PURPOSE_DICTIONARY[op] === "comparison") {
|
|
606
|
-
if (comparisonOperation || !field)
|
|
607
|
-
throw new ParseError("Unexpected comparison operator", tokens, field?.token ?? token, field?.index ?? _offset + t, token, _offset + t);
|
|
608
|
-
comparisonOperation = {
|
|
609
|
-
content: op,
|
|
610
|
-
token,
|
|
611
|
-
index: _offset + t
|
|
612
|
-
};
|
|
613
|
-
continue;
|
|
614
|
-
} else if (field)
|
|
615
|
-
throw new ParseError("Expected a comparison operator", tokens, field.token, field.index, token, _offset + t);
|
|
616
|
-
}
|
|
617
|
-
if (!field) {
|
|
618
|
-
if (NEGATORS.includes(token.content)) {
|
|
619
|
-
const nextToken = tokens[t + 1];
|
|
620
|
-
if (!nextToken)
|
|
621
|
-
throw new ParseError('Unexpected "!"', tokens, token, _offset + t);
|
|
664
|
+
const op = this.OPERATOR_DICTIONARY[token.content];
|
|
665
|
+
if (op?.type === "sumjunction" || op?.type === "productjunction") {
|
|
622
666
|
resolveCondition({
|
|
623
667
|
tokens,
|
|
624
|
-
startToken: token,
|
|
625
|
-
startIndex: _offset + t,
|
|
626
|
-
endToken:
|
|
627
|
-
endIndex: _offset + t
|
|
668
|
+
startToken: field?.token ?? token,
|
|
669
|
+
startIndex: field?.index ?? _offset + t,
|
|
670
|
+
endToken: token,
|
|
671
|
+
endIndex: _offset + t
|
|
628
672
|
});
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
673
|
+
const prior = expressions.at(-1);
|
|
674
|
+
if (!prior)
|
|
675
|
+
throw new ParseError("Unexpected junction operator with no preceding expression", tokens, token, _offset + t);
|
|
676
|
+
expectingExpression = true;
|
|
677
|
+
if (activeGroupOperation && activeGroupOperation !== op.name) {
|
|
678
|
+
if (expressions.length < 2)
|
|
679
|
+
throw new ParseError("Unexpected junction operator with no preceding expression", tokens, token, _offset + t);
|
|
680
|
+
const activeGroupOperationType = this.OPERATOR_DICTIONARY[activeGroupOperation].type;
|
|
681
|
+
if (activeGroupOperationType === op.type)
|
|
682
|
+
throw new ParseError("Mixed two junction operators of the same precedence level. Unclear how to separate without grouping", tokens, token, _offset + t);
|
|
683
|
+
switch (op.type) {
|
|
684
|
+
case "productjunction": {
|
|
685
|
+
inConjunction = true;
|
|
686
|
+
if (prior.type === "group" && this.OPERATOR_DICTIONARY[prior.operation].type === "productjunction")
|
|
687
|
+
continue;
|
|
688
|
+
expressions.splice(-1, 1);
|
|
689
|
+
expressions.push({
|
|
690
|
+
type: "group",
|
|
691
|
+
operation: op.name,
|
|
692
|
+
constituents: [
|
|
693
|
+
prior
|
|
694
|
+
]
|
|
695
|
+
});
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
case "sumjunction": {
|
|
699
|
+
const futureSubgroup = this._parse(tokens.slice(t + 1), _offset + t);
|
|
700
|
+
if (futureSubgroup === null)
|
|
701
|
+
throw new ParseError("Dangling junction operator", tokens, token, _offset + t);
|
|
702
|
+
return {
|
|
703
|
+
type: "group",
|
|
704
|
+
operation: op.name,
|
|
705
|
+
constituents: [
|
|
706
|
+
{
|
|
707
|
+
type: "group",
|
|
708
|
+
operation: activeGroupOperation,
|
|
709
|
+
constituents: expressions
|
|
710
|
+
},
|
|
711
|
+
futureSubgroup
|
|
712
|
+
]
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
activeGroupOperation = op.name;
|
|
718
|
+
if (expressions.length === 1 && expressions[0]?.type === "group" && expressions[0].operation === activeGroupOperation) {
|
|
719
|
+
const exp = expressions[0];
|
|
720
|
+
expressions.splice(0, 1);
|
|
721
|
+
expressions.push(...exp.constituents);
|
|
722
|
+
}
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (this.CONFIG.symbols.negators.includes(token.content)) {
|
|
726
|
+
const nextToken = tokens[t + 1];
|
|
727
|
+
const nextParen = this.CONFIG.symbols.groupBrackets.find(([o]) => nextToken?.content === o);
|
|
728
|
+
if (nextParen) {
|
|
729
|
+
resolveCondition({
|
|
730
|
+
tokens,
|
|
731
|
+
startToken: field?.token ?? token,
|
|
732
|
+
startIndex: field?.index ?? _offset + t,
|
|
733
|
+
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
734
|
+
endIndex: value?.index ?? comparisonOperation?.index ?? field?.index
|
|
735
|
+
});
|
|
736
|
+
++t;
|
|
737
|
+
const closingIndex = WizardParser.getClosingIndex(tokens, t, nextParen[0], nextParen[1]);
|
|
738
|
+
if (closingIndex === -1)
|
|
739
|
+
throw new ParseError("Missing closing parenthesis for group", tokens, token, _offset + t);
|
|
740
|
+
++t;
|
|
741
|
+
const futureSubExpression = this._parse(tokens.slice(t, closingIndex), _offset + t);
|
|
742
|
+
if (futureSubExpression) {
|
|
743
|
+
this.complementExpression(futureSubExpression);
|
|
744
|
+
if (futureSubExpression.type === "group" && futureSubExpression.operation === activeGroupOperation)
|
|
745
|
+
expressions.push(...futureSubExpression.constituents);
|
|
746
|
+
else {
|
|
747
|
+
const group = getExpressionGroup({
|
|
748
|
+
tokens,
|
|
749
|
+
startToken: token,
|
|
750
|
+
startIndex: _offset + t,
|
|
751
|
+
endToken: tokens[closingIndex],
|
|
752
|
+
endIndex: closingIndex
|
|
753
|
+
});
|
|
754
|
+
group.push(futureSubExpression);
|
|
755
|
+
inConjunction = false;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
t = closingIndex;
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (!comparisonOperation || op) {
|
|
763
|
+
if (op) {
|
|
764
|
+
if (comparisonOperation || !field)
|
|
765
|
+
throw new ParseError("Unexpected comparison operator", tokens, field?.token ?? token, field?.index ?? _offset + t, token, _offset + t);
|
|
766
|
+
comparisonOperation = {
|
|
767
|
+
content: op.name,
|
|
768
|
+
token,
|
|
769
|
+
index: _offset + t
|
|
770
|
+
};
|
|
771
|
+
continue;
|
|
772
|
+
} else if (field)
|
|
773
|
+
throw new ParseError("Expected a comparison operator", tokens, field.token, field.index, token, _offset + t);
|
|
774
|
+
}
|
|
775
|
+
if (!field) {
|
|
776
|
+
if (this.CONFIG.symbols.negators.includes(token.content)) {
|
|
777
|
+
if (!this.CONFIG.implicitCondition)
|
|
778
|
+
throw new ParseError("Could not attempt a negative implicit condition as one is not defined in the config", tokens, token, _offset + t);
|
|
779
|
+
const nextToken = tokens[t + 1];
|
|
780
|
+
if (!nextToken)
|
|
781
|
+
throw new ParseError('Unexpected "!"', tokens, token, _offset + t);
|
|
782
|
+
resolveCondition({
|
|
783
|
+
tokens,
|
|
784
|
+
startToken: token,
|
|
785
|
+
startIndex: _offset + t,
|
|
786
|
+
endToken: nextToken,
|
|
787
|
+
endIndex: _offset + t + 1
|
|
788
|
+
});
|
|
789
|
+
++t;
|
|
790
|
+
field = {
|
|
791
|
+
content: nextToken.content,
|
|
792
|
+
token,
|
|
793
|
+
index: _offset + t
|
|
794
|
+
};
|
|
795
|
+
comparisonOperation = {
|
|
796
|
+
content: this.OPERATOR_DICTIONARY[this.CONFIG.implicitCondition.operator].negation
|
|
797
|
+
};
|
|
798
|
+
value = {
|
|
799
|
+
content: this.CONFIG.implicitCondition.value,
|
|
800
|
+
implicit: true
|
|
801
|
+
};
|
|
802
|
+
resolveCondition({
|
|
803
|
+
tokens,
|
|
804
|
+
startToken: field.token,
|
|
805
|
+
startIndex: field.index,
|
|
806
|
+
endToken: nextToken,
|
|
807
|
+
endIndex: _offset + t
|
|
808
|
+
});
|
|
809
|
+
} else {
|
|
810
|
+
field = {
|
|
811
|
+
content: token.content,
|
|
812
|
+
token,
|
|
813
|
+
index: _offset + t
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
if (!value) {
|
|
819
|
+
const bracket = this.CONFIG.symbols.arrayBrackets.find(([o]) => token.content === o);
|
|
820
|
+
if (bracket) {
|
|
821
|
+
const closingIndex = WizardParser.getClosingIndex(tokens, t, bracket[0], bracket[1]);
|
|
822
|
+
if (closingIndex === -1)
|
|
823
|
+
throw new ParseError("Missing closing bracket/brace for array value", tokens, token, _offset + t);
|
|
824
|
+
++t;
|
|
825
|
+
const arr = [];
|
|
826
|
+
value = {
|
|
827
|
+
content: arr,
|
|
828
|
+
token: tokens[closingIndex],
|
|
829
|
+
index: closingIndex
|
|
830
|
+
};
|
|
831
|
+
const arrayContents = tokens.slice(t, closingIndex);
|
|
832
|
+
let workingEntry = "";
|
|
833
|
+
let firstEntryToken;
|
|
834
|
+
let firstEntryTokenIndex;
|
|
835
|
+
let lastEntryToken;
|
|
836
|
+
let lastEntryTokenIndex;
|
|
837
|
+
const resolveEntry = (subtoken, subindex) => {
|
|
838
|
+
if (workingEntry) {
|
|
839
|
+
const subquotes = workingEntry.match(this.QUOTE_REGEX);
|
|
840
|
+
if (subquotes && (subquotes.index !== 0 || subquotes[0].length !== workingEntry.length))
|
|
841
|
+
throw new ParseError("Quotes must surround entire values in arrays", tokens, firstEntryToken ?? subtoken, firstEntryTokenIndex ?? subindex, lastEntryToken ?? subtoken, lastEntryTokenIndex ?? subindex);
|
|
842
|
+
arr.push(workingEntry);
|
|
843
|
+
workingEntry = "";
|
|
844
|
+
firstEntryToken = undefined;
|
|
845
|
+
firstEntryTokenIndex = undefined;
|
|
846
|
+
lastEntryToken = undefined;
|
|
847
|
+
lastEntryTokenIndex = undefined;
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
for (let ct = 0;ct < arrayContents.length; ++ct) {
|
|
851
|
+
const contentToken = arrayContents[ct];
|
|
852
|
+
if (this.CONFIG.symbols.arrayDelimiters.includes(contentToken.content)) {
|
|
853
|
+
if (!workingEntry)
|
|
854
|
+
throw new ParseError("Unexpected blank entry in array", tokens, contentToken, _offset + t + ct);
|
|
855
|
+
resolveEntry(contentToken, _offset + t + ct);
|
|
856
|
+
} else {
|
|
857
|
+
lastEntryToken = contentToken;
|
|
858
|
+
lastEntryTokenIndex = _offset + t + ct;
|
|
859
|
+
if (!firstEntryToken)
|
|
860
|
+
firstEntryToken = lastEntryToken;
|
|
861
|
+
if (!firstEntryTokenIndex)
|
|
862
|
+
firstEntryTokenIndex = lastEntryTokenIndex;
|
|
863
|
+
workingEntry += contentToken.content;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
resolveEntry(arrayContents.at(-1), _offset + t + arrayContents.length - 1);
|
|
867
|
+
if (!arr.length) {
|
|
868
|
+
throw new ParseError("Empty array provided as value", tokens, token, _offset + t - 1, tokens[closingIndex], _offset + closingIndex);
|
|
869
|
+
}
|
|
870
|
+
t = closingIndex;
|
|
871
|
+
} else {
|
|
872
|
+
value = {
|
|
873
|
+
content: token.content,
|
|
874
|
+
token,
|
|
875
|
+
index: _offset + t
|
|
876
|
+
};
|
|
877
|
+
}
|
|
642
878
|
resolveCondition({
|
|
643
879
|
tokens,
|
|
644
880
|
startToken: field.token,
|
|
645
881
|
startIndex: field.index,
|
|
646
|
-
endToken:
|
|
647
|
-
endIndex:
|
|
882
|
+
endToken: value.token ?? comparisonOperation?.token ?? field.token,
|
|
883
|
+
endIndex: value.index ?? comparisonOperation?.index ?? field.index
|
|
648
884
|
});
|
|
649
|
-
} else {
|
|
650
|
-
field = {
|
|
651
|
-
content: token.content,
|
|
652
|
-
token,
|
|
653
|
-
index: _offset + t
|
|
654
|
-
};
|
|
655
885
|
}
|
|
656
|
-
continue;
|
|
657
886
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
887
|
+
resolveCondition({
|
|
888
|
+
tokens,
|
|
889
|
+
startToken: field?.token,
|
|
890
|
+
startIndex: field?.index,
|
|
891
|
+
endToken: value?.token ?? comparisonOperation?.token ?? field?.token,
|
|
892
|
+
endIndex: value?.index ?? comparisonOperation?.index ?? field?.index
|
|
893
|
+
});
|
|
894
|
+
if (inConjunction)
|
|
895
|
+
throw new ParseError("Dangling junction operator", tokens, tokens.at(-1), _offset + tokens.length - 1);
|
|
896
|
+
if (activeGroupOperation) {
|
|
897
|
+
if (expressions.length === 1)
|
|
898
|
+
throw new ParseError("Dangling junction operator", tokens, tokens.at(-1), _offset + tokens.length - 1);
|
|
899
|
+
return {
|
|
900
|
+
type: "group",
|
|
901
|
+
operation: activeGroupOperation,
|
|
902
|
+
constituents: expressions
|
|
903
|
+
};
|
|
904
|
+
} else if (expressions.length > 1)
|
|
905
|
+
throw new ParseError("Group possesses multiple conditions without disjunctive operators", tokens, tokens[0], _offset);
|
|
906
|
+
else
|
|
907
|
+
return expressions[0] ?? null;
|
|
908
|
+
}
|
|
909
|
+
parse(expression) {
|
|
910
|
+
let tokens;
|
|
911
|
+
if (Array.isArray(expression)) {
|
|
912
|
+
tokens = [];
|
|
913
|
+
let type;
|
|
914
|
+
for (let t = 0;t < expression.length; ++t) {
|
|
915
|
+
const token = expression[t];
|
|
916
|
+
if (!type)
|
|
917
|
+
type = typeof token;
|
|
918
|
+
if (typeof token !== type)
|
|
919
|
+
console.warn("WizardQL: parse was called with a mixed array of string tokens and token objects");
|
|
920
|
+
tokens.push(typeof token === "string" ? { content: token, index: type === "string" ? t : -1 } : token);
|
|
921
|
+
}
|
|
922
|
+
} else
|
|
923
|
+
tokens = this.tokenize(expression);
|
|
924
|
+
return this._parse(tokens, 0);
|
|
925
|
+
}
|
|
926
|
+
addQuotesIfNecessary(value) {
|
|
927
|
+
if (value instanceof Date) {
|
|
928
|
+
return this.CONFIG.dateSerializer ? this.CONFIG.dateSerializer(value) : value.toISOString();
|
|
929
|
+
}
|
|
930
|
+
if (typeof value !== "string")
|
|
931
|
+
return value.toString();
|
|
932
|
+
const QUOTE = this.CONFIG.symbols.quotes[0];
|
|
933
|
+
if (value === "true")
|
|
934
|
+
return `${QUOTE}true${QUOTE}`;
|
|
935
|
+
if (value === "false")
|
|
936
|
+
return `${QUOTE}false${QUOTE}`;
|
|
937
|
+
if (!isNaN(Number(value)))
|
|
938
|
+
return `${QUOTE}${value}${QUOTE}`;
|
|
939
|
+
const escaped = value.replaceAll("\\", "\\\\");
|
|
940
|
+
if (new RegExp(this.TOKEN_REGEX, "gi").test(escaped))
|
|
941
|
+
return `${QUOTE}${escaped.replaceAll(QUOTE, `\\${QUOTE}`)}${QUOTE}`;
|
|
942
|
+
return escaped;
|
|
943
|
+
}
|
|
944
|
+
stringify(expression, opts) {
|
|
945
|
+
const {
|
|
946
|
+
dialect,
|
|
947
|
+
alwaysParenthesize = false,
|
|
948
|
+
compact = false,
|
|
949
|
+
condenseImplicit = false
|
|
950
|
+
} = typeof opts === "string" ? { dialect: opts } : opts;
|
|
951
|
+
if (!this.CONFIG.dialects)
|
|
952
|
+
throw new Error("No dialect dictionaries are defined in the config");
|
|
953
|
+
if (!(dialect in this.CONFIG.dialects))
|
|
954
|
+
throw new Error(`Dialect '${dialect}' is not defined in the config`);
|
|
955
|
+
let string = "";
|
|
956
|
+
switch (expression.type) {
|
|
957
|
+
case "group":
|
|
958
|
+
for (const constituent of expression.constituents) {
|
|
959
|
+
if (string.length) {
|
|
960
|
+
const transformed = this.CONFIG.dialects[dialect][expression.operation];
|
|
961
|
+
const alpha = isAlpha(transformed);
|
|
962
|
+
if (!compact || alpha)
|
|
963
|
+
string += " ";
|
|
964
|
+
string += transformed;
|
|
965
|
+
if (!compact || alpha)
|
|
966
|
+
string += " ";
|
|
704
967
|
}
|
|
968
|
+
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
969
|
+
string += this.CONFIG.symbols.groupBrackets[0][0];
|
|
970
|
+
string += this.stringify(constituent, opts);
|
|
971
|
+
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
972
|
+
string += this.CONFIG.symbols.groupBrackets[0][1];
|
|
705
973
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
974
|
+
break;
|
|
975
|
+
case "condition": {
|
|
976
|
+
const resolveNonImplicit = () => {
|
|
977
|
+
const transformed = this.CONFIG.dialects[dialect][expression.operation];
|
|
978
|
+
const alpha = isAlpha(transformed);
|
|
979
|
+
string += expression.field.toString();
|
|
980
|
+
if (!compact || alpha)
|
|
981
|
+
string += " ";
|
|
982
|
+
string += transformed;
|
|
983
|
+
if (!compact || alpha)
|
|
984
|
+
string += " ";
|
|
985
|
+
if (Array.isArray(expression.value)) {
|
|
986
|
+
const join = expression.value.map((v) => this.addQuotesIfNecessary(v)).join(compact ? this.CONFIG.symbols.arrayDelimiters[0] : this.CONFIG.symbols.arrayDelimiters[0] + " ");
|
|
987
|
+
string += `${this.CONFIG.symbols.arrayBrackets[0][0]}${join}${this.CONFIG.symbols.arrayBrackets[0][1]}`;
|
|
988
|
+
} else
|
|
989
|
+
string += this.addQuotesIfNecessary(expression.value);
|
|
716
990
|
};
|
|
991
|
+
if (condenseImplicit && this.CONFIG.implicitCondition) {
|
|
992
|
+
const implicitOperator = this.OPERATOR_DICTIONARY[this.CONFIG.implicitCondition.operator];
|
|
993
|
+
let isNegation = expression.operation === implicitOperator.negation;
|
|
994
|
+
if (expression.operation === implicitOperator.name || isNegation) {
|
|
995
|
+
const fieldType = this.CONFIG.types?.[expression.field];
|
|
996
|
+
const fieldTypes = fieldType && (Array.isArray(fieldType) ? fieldType : [fieldType]);
|
|
997
|
+
const opTypes = Array.isArray(this.CONFIG.implicitCondition.asType) ? this.CONFIG.implicitCondition.asType : [this.CONFIG.implicitCondition.asType];
|
|
998
|
+
const processed = this.processToken(this.CONFIG.implicitCondition.value);
|
|
999
|
+
let coerced;
|
|
1000
|
+
try {
|
|
1001
|
+
coerced = this.coerceType(processed, fieldTypes ?? opTypes, opTypes);
|
|
1002
|
+
} catch {
|
|
1003
|
+
resolveNonImplicit();
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
const bothBoolean = typeof coerced === "boolean" && typeof expression.value === "boolean";
|
|
1007
|
+
if (coerced.valueOf() === expression.value || bothBoolean) {
|
|
1008
|
+
if (bothBoolean && coerced !== expression.value)
|
|
1009
|
+
isNegation = !isNegation;
|
|
1010
|
+
string += `${isNegation ? this.CONFIG.symbols.negators[0] : ""}${expression.field.toString()}`;
|
|
1011
|
+
} else
|
|
1012
|
+
resolveNonImplicit();
|
|
1013
|
+
} else
|
|
1014
|
+
resolveNonImplicit();
|
|
1015
|
+
} else
|
|
1016
|
+
resolveNonImplicit();
|
|
1017
|
+
break;
|
|
717
1018
|
}
|
|
718
|
-
resolveCondition({
|
|
719
|
-
tokens,
|
|
720
|
-
startToken: field.token,
|
|
721
|
-
startIndex: field.index,
|
|
722
|
-
endToken: value.token ?? comparisonOperation?.token ?? field.token,
|
|
723
|
-
endIndex: value.index ?? comparisonOperation?.index ?? field.index
|
|
724
|
-
});
|
|
725
1019
|
}
|
|
1020
|
+
return string;
|
|
726
1021
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
throw new ParseError("Dangling junction operator", tokens, tokens.at(-1), _offset + tokens.length - 1);
|
|
736
|
-
if (groupOperation) {
|
|
737
|
-
if (expressions.length === 1)
|
|
738
|
-
throw new ParseError("Dangling junction operator", tokens, tokens.at(-1), _offset + tokens.length - 1);
|
|
739
|
-
return {
|
|
740
|
-
type: "group",
|
|
741
|
-
operation: groupOperation,
|
|
742
|
-
constituents: expressions
|
|
743
|
-
};
|
|
744
|
-
} else if (expressions.length > 1)
|
|
745
|
-
throw new ParseError("Group possesses multiple conditions without disjunctive operators", tokens, tokens[0], _offset);
|
|
746
|
-
else
|
|
747
|
-
return expressions[0] ?? null;
|
|
748
|
-
}
|
|
749
|
-
function parse(expression, constraints) {
|
|
750
|
-
let tokens;
|
|
751
|
-
if (Array.isArray(expression)) {
|
|
752
|
-
tokens = [];
|
|
753
|
-
let type;
|
|
754
|
-
for (let t = 0;t < expression.length; ++t) {
|
|
755
|
-
const token = expression[t];
|
|
756
|
-
if (!type)
|
|
757
|
-
type = typeof token;
|
|
758
|
-
if (typeof token !== type)
|
|
759
|
-
console.warn("WizardQL: parse was called with a mixed array of string tokens and token objects");
|
|
760
|
-
tokens.push(typeof token === "string" ? { content: token, index: type === "string" ? t : -1 } : token);
|
|
1022
|
+
static getOrPutArrayInMap(map, key) {
|
|
1023
|
+
const existingEntry = map.get(key);
|
|
1024
|
+
if (existingEntry)
|
|
1025
|
+
return existingEntry;
|
|
1026
|
+
else {
|
|
1027
|
+
const arr = [];
|
|
1028
|
+
map.set(key, arr);
|
|
1029
|
+
return arr;
|
|
761
1030
|
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1031
|
+
}
|
|
1032
|
+
summarize(expressions) {
|
|
1033
|
+
const array = Array.isArray(expressions) ? expressions : [expressions];
|
|
1034
|
+
const summary = new Map;
|
|
1035
|
+
for (const expression of array) {
|
|
1036
|
+
if (expression.type === "group") {
|
|
1037
|
+
const constituents = this.summarize(expression.constituents);
|
|
1038
|
+
for (const [field, values] of constituents.entries()) {
|
|
1039
|
+
const collection = WizardParser.getOrPutArrayInMap(summary, field);
|
|
1040
|
+
collection.push(...values);
|
|
1041
|
+
}
|
|
1042
|
+
} else {
|
|
1043
|
+
const collection = WizardParser.getOrPutArrayInMap(summary, expression.field);
|
|
1044
|
+
collection.push({
|
|
1045
|
+
operation: expression.operation,
|
|
1046
|
+
value: expression.value,
|
|
1047
|
+
exclusionary: this.OPERATOR_DICTIONARY[expression.operation].exclusionary
|
|
1048
|
+
});
|
|
774
1049
|
}
|
|
775
|
-
break;
|
|
776
1050
|
}
|
|
777
|
-
|
|
778
|
-
switch (expression.operation) {
|
|
779
|
-
case "EQUAL":
|
|
780
|
-
query.where(expression.field, "=", expression.value);
|
|
781
|
-
break;
|
|
782
|
-
case "NOTEQUAL":
|
|
783
|
-
query.where(expression.field, "!=", expression.value);
|
|
784
|
-
break;
|
|
785
|
-
case "GEQ":
|
|
786
|
-
query.where(expression.field, ">=", expression.value);
|
|
787
|
-
break;
|
|
788
|
-
case "LEQ":
|
|
789
|
-
query.where(expression.field, "<=", expression.value);
|
|
790
|
-
break;
|
|
791
|
-
case "GREATER":
|
|
792
|
-
query.where(expression.field, ">", expression.value);
|
|
793
|
-
break;
|
|
794
|
-
case "LESS":
|
|
795
|
-
query.where(expression.field, "<", expression.value);
|
|
796
|
-
break;
|
|
797
|
-
case "IN":
|
|
798
|
-
query.whereIn(expression.field, expression.value);
|
|
799
|
-
break;
|
|
800
|
-
case "NOTIN":
|
|
801
|
-
query.whereNotIn(expression.field, expression.value);
|
|
802
|
-
break;
|
|
803
|
-
case "MATCH":
|
|
804
|
-
query.whereRaw("?? ~* ?", [expression.field, expression.value]);
|
|
805
|
-
break;
|
|
806
|
-
case "NOTMATCH":
|
|
807
|
-
query.whereRaw("?? !~* ?", [expression.field, expression.value]);
|
|
808
|
-
break;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
// src/summarize.ts
|
|
813
|
-
function getOrPutArrayInMap(map, key) {
|
|
814
|
-
const existingEntry = map.get(key);
|
|
815
|
-
if (existingEntry)
|
|
816
|
-
return existingEntry;
|
|
817
|
-
else {
|
|
818
|
-
const arr = [];
|
|
819
|
-
map.set(key, arr);
|
|
820
|
-
return arr;
|
|
1051
|
+
return summary;
|
|
821
1052
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1053
|
+
getPartType(segment, activeArrayOpeningBracket) {
|
|
1054
|
+
if (segment.match(this.QUOTE_EDGE_REGEX))
|
|
1055
|
+
return "quoted";
|
|
1056
|
+
if (!isNaN(Number(segment)))
|
|
1057
|
+
return "number";
|
|
1058
|
+
for (const [opening, closing] of this.CONFIG.symbols.arrayBrackets) {
|
|
1059
|
+
if (!activeArrayOpeningBracket && segment === opening)
|
|
1060
|
+
return "openingarraybracket";
|
|
1061
|
+
else if (activeArrayOpeningBracket === opening && segment === closing)
|
|
1062
|
+
return "closingarraybracket";
|
|
1063
|
+
}
|
|
1064
|
+
if (!activeArrayOpeningBracket) {
|
|
1065
|
+
for (const [opening, closing] of this.CONFIG.symbols.groupBrackets) {
|
|
1066
|
+
if (segment === opening)
|
|
1067
|
+
return "openinggroupbracket";
|
|
1068
|
+
else if (segment === closing)
|
|
1069
|
+
return "closinggroupbracket";
|
|
832
1070
|
}
|
|
833
|
-
} else {
|
|
834
|
-
const collection = getOrPutArrayInMap(summary, expression.field);
|
|
835
|
-
collection.push({
|
|
836
|
-
operation: expression.operation,
|
|
837
|
-
value: expression.value,
|
|
838
|
-
exclusionary: ["NOTEQUAL", "LESS", "GREATER", "NOTIN"].includes(expression.operation)
|
|
839
|
-
});
|
|
840
1071
|
}
|
|
1072
|
+
if (activeArrayOpeningBracket && this.CONFIG.symbols.arrayDelimiters.includes(segment))
|
|
1073
|
+
return "arraydelimiter";
|
|
1074
|
+
if (!activeArrayOpeningBracket && this.CONFIG.symbols.negators.includes(segment))
|
|
1075
|
+
return "negator";
|
|
1076
|
+
if (!activeArrayOpeningBracket && segment in this.OPERATOR_DICTIONARY) {
|
|
1077
|
+
const type = this.OPERATOR_DICTIONARY[segment].type;
|
|
1078
|
+
if (type === "sumjunction" || type === "productjunction")
|
|
1079
|
+
return "junctionoperator";
|
|
1080
|
+
else
|
|
1081
|
+
return "conditionoperator";
|
|
1082
|
+
}
|
|
1083
|
+
return "literal";
|
|
841
1084
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
// src/stringify.ts
|
|
845
|
-
var DEFAULT_BRACKET = BRACKETS[0];
|
|
846
|
-
var DEFAULT_PAREN = PARENS[0];
|
|
847
|
-
var DEFAULT_NEGATOR = NEGATORS[0];
|
|
848
|
-
var DEFAULT_DELIMITER = ARRAY_DELIMITERS[0];
|
|
849
|
-
var formats = {
|
|
850
|
-
programmatic: {
|
|
851
|
-
AND: "&",
|
|
852
|
-
OR: "|",
|
|
853
|
-
EQUAL: "=",
|
|
854
|
-
NOTEQUAL: "!=",
|
|
855
|
-
GEQ: ">=",
|
|
856
|
-
GREATER: ">",
|
|
857
|
-
LEQ: "<=",
|
|
858
|
-
LESS: "<",
|
|
859
|
-
IN: ":",
|
|
860
|
-
NOTIN: "!:",
|
|
861
|
-
MATCH: "~",
|
|
862
|
-
NOTMATCH: "!~"
|
|
863
|
-
},
|
|
864
|
-
linguistic: {
|
|
865
|
-
AND: "AND",
|
|
866
|
-
OR: "OR",
|
|
867
|
-
EQUAL: "EQUALS",
|
|
868
|
-
NOTEQUAL: "NOTEQUALS",
|
|
869
|
-
GEQ: "GEQ",
|
|
870
|
-
GREATER: "GREATER",
|
|
871
|
-
LEQ: "LEQ",
|
|
872
|
-
LESS: "LESS",
|
|
873
|
-
IN: "IN",
|
|
874
|
-
NOTIN: "NOTIN",
|
|
875
|
-
MATCH: "MATCHES",
|
|
876
|
-
NOTMATCH: "NOTMATCHES"
|
|
877
|
-
},
|
|
878
|
-
formal: {
|
|
879
|
-
AND: "^",
|
|
880
|
-
OR: "V"
|
|
1085
|
+
resolveOperatorAlias(alias) {
|
|
1086
|
+
return this.OPERATOR_DICTIONARY[alias]?.name;
|
|
881
1087
|
}
|
|
882
|
-
};
|
|
883
|
-
function addQuotesIfNecessary(value) {
|
|
884
|
-
if (typeof value !== "string")
|
|
885
|
-
return value.toString();
|
|
886
|
-
if (value === "true")
|
|
887
|
-
return '"true"';
|
|
888
|
-
if (value === "false")
|
|
889
|
-
return '"false"';
|
|
890
|
-
if (!isNaN(Number(value)))
|
|
891
|
-
return `"${value}"`;
|
|
892
|
-
const escaped = value.replaceAll("\\", "\\\\");
|
|
893
|
-
if (new RegExp(TOKEN_REGEX, "gi").test(escaped))
|
|
894
|
-
return `"${escaped.replaceAll('"', "\\\"")}"`;
|
|
895
|
-
return escaped;
|
|
896
|
-
}
|
|
897
|
-
function stringify(expression, opts = {}) {
|
|
898
|
-
const {
|
|
899
|
-
junctionNotation = "programmatic",
|
|
900
|
-
comparisonNotation = "programmatic",
|
|
901
|
-
alwaysParenthesize = false,
|
|
902
|
-
compact = false,
|
|
903
|
-
condenseBooleans = false
|
|
904
|
-
} = opts;
|
|
905
|
-
let string = "";
|
|
906
|
-
switch (expression.type) {
|
|
907
|
-
case "group":
|
|
908
|
-
for (const constituent of expression.constituents) {
|
|
909
|
-
if (string.length) {
|
|
910
|
-
if (!compact || junctionNotation === "linguistic")
|
|
911
|
-
string += " ";
|
|
912
|
-
string += formats[junctionNotation][expression.operation];
|
|
913
|
-
if (!compact || junctionNotation === "linguistic")
|
|
914
|
-
string += " ";
|
|
915
|
-
}
|
|
916
|
-
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
917
|
-
string += DEFAULT_PAREN[0];
|
|
918
|
-
string += stringify(constituent, opts);
|
|
919
|
-
if (alwaysParenthesize && constituent.type === "group" || expression.operation === "AND" && constituent.operation === "OR")
|
|
920
|
-
string += DEFAULT_PAREN[1];
|
|
921
|
-
}
|
|
922
|
-
break;
|
|
923
|
-
case "condition":
|
|
924
|
-
if (condenseBooleans && ["EQUAL", "NOTEQUAL"].includes(expression.operation) && typeof expression.value === "boolean") {
|
|
925
|
-
const negative = expression.operation === "EQUAL" && !expression.value || expression.operation === "NOTEQUAL" && expression.value;
|
|
926
|
-
string += `${negative ? DEFAULT_NEGATOR : ""}${expression.field}`;
|
|
927
|
-
} else {
|
|
928
|
-
string += expression.field;
|
|
929
|
-
if (!compact || comparisonNotation === "linguistic")
|
|
930
|
-
string += " ";
|
|
931
|
-
string += formats[comparisonNotation][expression.operation];
|
|
932
|
-
if (!compact || comparisonNotation === "linguistic")
|
|
933
|
-
string += " ";
|
|
934
|
-
if (Array.isArray(expression.value)) {
|
|
935
|
-
const join = expression.value.map(addQuotesIfNecessary).join(compact ? DEFAULT_DELIMITER : DEFAULT_DELIMITER + " ");
|
|
936
|
-
string += `${DEFAULT_BRACKET[0]}${join}${DEFAULT_BRACKET[1]}`;
|
|
937
|
-
} else
|
|
938
|
-
string += addQuotesIfNecessary(expression.value);
|
|
939
|
-
}
|
|
940
|
-
break;
|
|
941
|
-
}
|
|
942
|
-
return string;
|
|
943
1088
|
}
|
|
944
1089
|
// src/dominput.ts
|
|
945
1090
|
function getCursorIndex(element) {
|
|
@@ -973,7 +1118,8 @@ function setCursor(element, index) {
|
|
|
973
1118
|
accumulated += length;
|
|
974
1119
|
}
|
|
975
1120
|
}
|
|
976
|
-
function createDOMInput({ input,
|
|
1121
|
+
function createDOMInput({ input, config, onUpdate, parseOnInitialize }) {
|
|
1122
|
+
const parser = new WizardParser(config);
|
|
977
1123
|
const history = [];
|
|
978
1124
|
let historyIndex = -1;
|
|
979
1125
|
let savedCursor;
|
|
@@ -982,13 +1128,13 @@ function createDOMInput({ input, constraints, onUpdate, parseOnInitialize }) {
|
|
|
982
1128
|
const text = input.textContent.replaceAll(`
|
|
983
1129
|
`, "");
|
|
984
1130
|
const endPadding = input.textContent?.match(/\s*$/)?.[0].length ?? 0;
|
|
985
|
-
const newTokens = tokenize(text);
|
|
1131
|
+
const newTokens = parser.tokenize(text);
|
|
986
1132
|
const lastToken = newTokens.at(-1);
|
|
987
|
-
if (lastToken && lastToken.content
|
|
1133
|
+
if (lastToken && parser.resolveOperatorAlias(lastToken.content) && focused && (!endPadding && lastToken.content.match(/^[A-Za-z]+?$/)))
|
|
988
1134
|
return;
|
|
989
1135
|
const absoluteIndex = focused ? getCursorIndex(input) : 0;
|
|
990
1136
|
observer.disconnect();
|
|
991
|
-
let
|
|
1137
|
+
let activeArrayOpeningBracket;
|
|
992
1138
|
let offset = 0;
|
|
993
1139
|
for (let t = 0;t < newTokens.length; ++t) {
|
|
994
1140
|
const token = newTokens[t];
|
|
@@ -1009,24 +1155,37 @@ function createDOMInput({ input, constraints, onUpdate, parseOnInitialize }) {
|
|
|
1009
1155
|
const element = document.createElement("span");
|
|
1010
1156
|
element.textContent = token.content;
|
|
1011
1157
|
element.toggleAttribute("data-node", true);
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1158
|
+
const partType = parser.getPartType(token.content, activeArrayOpeningBracket);
|
|
1159
|
+
token.partType = partType;
|
|
1160
|
+
switch (partType) {
|
|
1161
|
+
case "quoted":
|
|
1162
|
+
element.toggleAttribute("data-quoted", true);
|
|
1163
|
+
break;
|
|
1164
|
+
case "number":
|
|
1165
|
+
element.toggleAttribute("data-number", true);
|
|
1166
|
+
break;
|
|
1167
|
+
case "openingarraybracket":
|
|
1168
|
+
activeArrayOpeningBracket = token.content;
|
|
1169
|
+
case "openinggroupbracket":
|
|
1020
1170
|
element.setAttribute("data-bracket", token.content);
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1171
|
+
break;
|
|
1172
|
+
case "closingarraybracket":
|
|
1173
|
+
activeArrayOpeningBracket = undefined;
|
|
1174
|
+
case "closinggroupbracket":
|
|
1175
|
+
element.setAttribute("data-bracket", token.content);
|
|
1176
|
+
break;
|
|
1177
|
+
case "arraydelimiter":
|
|
1178
|
+
element.toggleAttribute("data-delimiter", true);
|
|
1179
|
+
break;
|
|
1180
|
+
case "negator":
|
|
1181
|
+
element.toggleAttribute("data-negator", true);
|
|
1182
|
+
break;
|
|
1183
|
+
case "junctionoperator":
|
|
1184
|
+
element.setAttribute("data-operator", "junction");
|
|
1185
|
+
break;
|
|
1186
|
+
case "conditionoperator":
|
|
1187
|
+
element.setAttribute("data-operator", "condition");
|
|
1188
|
+
break;
|
|
1030
1189
|
}
|
|
1031
1190
|
const existing = input.childNodes.item(t + offset);
|
|
1032
1191
|
if (existing)
|
|
@@ -1059,7 +1218,7 @@ function createDOMInput({ input, constraints, onUpdate, parseOnInitialize }) {
|
|
|
1059
1218
|
}
|
|
1060
1219
|
let result;
|
|
1061
1220
|
try {
|
|
1062
|
-
result = parse(newTokens
|
|
1221
|
+
result = parser.parse(newTokens);
|
|
1063
1222
|
input.removeAttribute("data-error-message");
|
|
1064
1223
|
input.removeAttribute("data-error-start");
|
|
1065
1224
|
input.removeAttribute("data-error-end");
|
|
@@ -1118,16 +1277,9 @@ function createDOMInput({ input, constraints, onUpdate, parseOnInitialize }) {
|
|
|
1118
1277
|
};
|
|
1119
1278
|
}
|
|
1120
1279
|
export {
|
|
1121
|
-
tokenize,
|
|
1122
|
-
summarize,
|
|
1123
|
-
stringify,
|
|
1124
|
-
parse,
|
|
1125
|
-
executeAsKnex,
|
|
1126
1280
|
createDOMInput,
|
|
1127
|
-
|
|
1281
|
+
WizardParser,
|
|
1282
|
+
TYPE_PRIORITY,
|
|
1128
1283
|
ParseError,
|
|
1129
|
-
|
|
1130
|
-
OPERATION_ALIAS_DICTIONARY,
|
|
1131
|
-
ConstraintError,
|
|
1132
|
-
COMPARISON_TYPE_DICTIONARY
|
|
1284
|
+
ConstraintError
|
|
1133
1285
|
};
|