patram 0.10.0 → 0.12.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/bin/patram.js +4 -4
- package/lib/cli/arguments.types.d.ts +1 -0
- package/lib/cli/commands/check.js +27 -15
- package/lib/cli/commands/fields.js +0 -4
- package/lib/cli/commands/queries.js +179 -1
- package/lib/cli/commands/query.js +1 -8
- package/lib/cli/commands/refs.js +3 -10
- package/lib/cli/commands/show.js +1 -8
- package/lib/cli/help-metadata.js +106 -111
- package/lib/cli/main.js +10 -10
- package/lib/cli/parse-arguments-helpers.js +416 -66
- package/lib/cli/parse-arguments.js +4 -4
- package/lib/cli/render-help.js +10 -4
- package/lib/config/defaults.js +33 -25
- package/lib/config/load-patram-config.d.ts +19 -33
- package/lib/config/load-patram-config.js +18 -121
- package/lib/config/load-patram-config.types.d.ts +3 -40
- package/lib/config/manage-stored-queries-helpers.d.ts +69 -0
- package/lib/config/manage-stored-queries-helpers.js +320 -0
- package/lib/config/manage-stored-queries-jsonc.d.ts +31 -0
- package/lib/config/manage-stored-queries-jsonc.js +95 -0
- package/lib/config/manage-stored-queries.d.ts +77 -0
- package/lib/config/manage-stored-queries.js +300 -0
- package/lib/config/patram-config.d.ts +34 -34
- package/lib/config/patram-config.js +3 -3
- package/lib/config/patram-config.types.d.ts +5 -11
- package/lib/config/resolve-patram-graph-config.d.ts +5 -1
- package/lib/config/resolve-patram-graph-config.js +3 -119
- package/lib/config/schema.d.ts +158 -269
- package/lib/config/schema.js +72 -210
- package/lib/config/validate-patram-config-value.d.ts +13 -0
- package/lib/config/validate-patram-config-value.js +94 -0
- package/lib/config/validation.d.ts +2 -12
- package/lib/config/validation.js +125 -483
- package/lib/find-close-match.d.ts +4 -1
- package/lib/graph/build-graph-identity.d.ts +1 -32
- package/lib/graph/build-graph-identity.js +5 -269
- package/lib/graph/build-graph.d.ts +13 -4
- package/lib/graph/build-graph.js +347 -488
- package/lib/graph/build-graph.types.d.ts +8 -9
- package/lib/graph/check-directive-metadata-helpers.d.ts +30 -0
- package/lib/graph/check-directive-metadata-helpers.js +126 -0
- package/lib/graph/check-directive-metadata.d.ts +8 -9
- package/lib/graph/check-directive-metadata.js +70 -561
- package/lib/graph/check-directive-path-target.d.ts +6 -13
- package/lib/graph/check-directive-path-target.js +26 -57
- package/lib/graph/check-directive-value.d.ts +1 -5
- package/lib/graph/check-directive-value.js +40 -180
- package/lib/graph/check-graph.d.ts +5 -5
- package/lib/graph/check-graph.js +8 -6
- package/lib/graph/document-node-identity.d.ts +23 -7
- package/lib/graph/document-node-identity.js +417 -160
- package/lib/graph/graph-node.d.ts +42 -0
- package/lib/graph/graph-node.js +83 -0
- package/lib/graph/inspect-reverse-references.js +16 -11
- package/lib/graph/load-project-graph.d.ts +7 -7
- package/lib/graph/load-project-graph.js +7 -7
- package/lib/graph/parse-where-clause.types.d.ts +3 -2
- package/lib/graph/query/cypher-reader.d.ts +59 -0
- package/lib/graph/query/cypher-reader.js +151 -0
- package/lib/graph/query/cypher-support.d.ts +79 -0
- package/lib/graph/query/cypher-support.js +213 -0
- package/lib/graph/query/cypher-tokenize.d.ts +13 -0
- package/lib/graph/query/cypher-tokenize.js +225 -0
- package/lib/graph/query/cypher.types.d.ts +43 -0
- package/lib/graph/query/execute.d.ts +7 -7
- package/lib/graph/query/execute.js +71 -33
- package/lib/graph/query/inspect.js +58 -24
- package/lib/graph/query/parse-cypher-patterns.d.ts +27 -0
- package/lib/graph/query/parse-cypher-patterns.js +382 -0
- package/lib/graph/query/parse-cypher.d.ts +7 -0
- package/lib/graph/query/parse-cypher.js +580 -0
- package/lib/graph/query/parse-query.d.ts +13 -0
- package/lib/graph/query/parse-query.js +97 -0
- package/lib/graph/query/resolve.d.ts +6 -0
- package/lib/graph/query/resolve.js +81 -24
- package/lib/output/command-output.js +12 -5
- package/lib/output/compact-layout.js +221 -0
- package/lib/output/format-output-item-block.js +31 -1
- package/lib/output/format-output-metadata.js +16 -29
- package/lib/output/format-stored-query-block.js +95 -0
- package/lib/output/layout-incoming-references.js +101 -19
- package/lib/output/layout-stored-queries.js +23 -330
- package/lib/output/list-queries.js +1 -1
- package/lib/output/render-field-discovery.js +11 -2
- package/lib/output/render-output-view.js +9 -5
- package/lib/output/renderers/json.js +5 -26
- package/lib/output/renderers/plain.js +155 -35
- package/lib/output/renderers/rich.js +250 -36
- package/lib/output/resolve-check-target.js +120 -11
- package/lib/output/resolved-link-layout.js +43 -0
- package/lib/output/rich-source/render.js +193 -35
- package/lib/output/show-document.js +25 -18
- package/lib/output/view-model/index.js +124 -103
- package/lib/parse/jsdoc/parse-jsdoc-blocks.js +1 -1
- package/lib/parse/jsdoc/parse-jsdoc-claims.js +12 -6
- package/lib/parse/markdown/parse-markdown-claims.js +99 -62
- package/lib/parse/markdown/parse-markdown-directives.d.ts +10 -6
- package/lib/parse/markdown/parse-markdown-directives.js +104 -18
- package/lib/parse/markdown/parse-markdown-prose.d.ts +27 -0
- package/lib/parse/markdown/parse-markdown-prose.js +243 -0
- package/lib/parse/parse-claims.d.ts +2 -6
- package/lib/parse/parse-claims.js +11 -53
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.d.ts +4 -4
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.js +4 -4
- package/lib/parse/yaml/parse-yaml-claims.js +4 -4
- package/lib/patram.d.ts +9 -3
- package/lib/patram.js +1 -1
- package/lib/scan/discover-fields.js +194 -55
- package/lib/scan/list-source-files.d.ts +4 -4
- package/lib/scan/list-source-files.js +4 -4
- package/package.json +2 -1
- package/lib/directive-validation-test-helpers.js +0 -87
- package/lib/graph/query/parse.d.ts +0 -75
- package/lib/graph/query/parse.js +0 -1064
- package/lib/output/derived-summary.js +0 -280
- package/lib/output/format-derived-summary-row.js +0 -9
package/lib/graph/query/parse.js
DELETED
|
@@ -1,1064 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-lines */
|
|
2
|
-
/**
|
|
3
|
-
* @import { PatramDiagnostic } from '../../config/load-patram-config.types.ts';
|
|
4
|
-
* @import {
|
|
5
|
-
* ParseWhereClauseResult,
|
|
6
|
-
* ParsedAggregateComparison,
|
|
7
|
-
* ParsedAggregateName,
|
|
8
|
-
* ParsedAggregateTerm,
|
|
9
|
-
* ParsedExpression,
|
|
10
|
-
* ParsedFieldName,
|
|
11
|
-
* ParsedTerm,
|
|
12
|
-
* ParsedTraversalTerm,
|
|
13
|
-
* } from '../parse-where-clause.types.ts';
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {{ bindings: Record<string, string>, index: number, where_clause: string }} ParserState
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @typedef {{
|
|
22
|
-
* diagnostic: PatramDiagnostic,
|
|
23
|
-
* success: false,
|
|
24
|
-
* }} ParseFailureResult
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @typedef {{
|
|
29
|
-
* expression: ParsedExpression,
|
|
30
|
-
* success: true,
|
|
31
|
-
* } | ParseFailureResult} ParseExpressionResult
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @typedef {{
|
|
36
|
-
* success: true,
|
|
37
|
-
* term: ParsedTerm,
|
|
38
|
-
* } | ParseFailureResult} ParseTermResult
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @typedef {{
|
|
43
|
-
* success: true,
|
|
44
|
-
* value: string,
|
|
45
|
-
* } | ParseFailureResult} ParseValueResult
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Parse one where clause into a structured boolean expression.
|
|
50
|
-
*
|
|
51
|
-
* @param {string} where_clause
|
|
52
|
-
* @param {{ bindings?: Record<string, string> }=} options
|
|
53
|
-
* @returns {ParseWhereClauseResult}
|
|
54
|
-
*/
|
|
55
|
-
export function parseWhereClause(where_clause, options = {}) {
|
|
56
|
-
/** @type {ParserState} */
|
|
57
|
-
const parser_state = {
|
|
58
|
-
bindings: options.bindings ?? {},
|
|
59
|
-
index: 0,
|
|
60
|
-
where_clause,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
skipWhitespace(parser_state);
|
|
64
|
-
|
|
65
|
-
if (isAtEnd(parser_state)) {
|
|
66
|
-
return fail(1, 'Query must not be empty.');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const expression_result = parseExpression(parser_state, null);
|
|
70
|
-
|
|
71
|
-
if (!expression_result.success) {
|
|
72
|
-
return expression_result;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
skipWhitespace(parser_state);
|
|
76
|
-
|
|
77
|
-
if (!isAtEnd(parser_state)) {
|
|
78
|
-
return failToken(parser_state);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
expression: expression_result.expression,
|
|
83
|
-
success: true,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @param {ParserState} parser_state
|
|
89
|
-
* @param {')' | null} stop_character
|
|
90
|
-
* @returns {ParseExpressionResult}
|
|
91
|
-
*/
|
|
92
|
-
function parseExpression(parser_state, stop_character) {
|
|
93
|
-
return parseOrExpression(parser_state, stop_character);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @param {ParserState} parser_state
|
|
98
|
-
* @param {')' | null} stop_character
|
|
99
|
-
* @returns {ParseExpressionResult}
|
|
100
|
-
*/
|
|
101
|
-
function parseOrExpression(parser_state, stop_character) {
|
|
102
|
-
const first_expression_result = parseAndExpression(
|
|
103
|
-
parser_state,
|
|
104
|
-
stop_character,
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
if (!first_expression_result.success) {
|
|
108
|
-
return first_expression_result;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** @type {ParsedExpression[]} */
|
|
112
|
-
const expressions = [first_expression_result.expression];
|
|
113
|
-
|
|
114
|
-
while (true) {
|
|
115
|
-
skipWhitespace(parser_state);
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
currentCharacter(parser_state) === stop_character ||
|
|
119
|
-
isAtEnd(parser_state)
|
|
120
|
-
) {
|
|
121
|
-
return collapseBooleanExpression('or', expressions);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!consumeKeyword(parser_state, 'or')) {
|
|
125
|
-
return collapseBooleanExpression('or', expressions);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
skipWhitespace(parser_state);
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
currentCharacter(parser_state) === stop_character ||
|
|
132
|
-
isAtEnd(parser_state)
|
|
133
|
-
) {
|
|
134
|
-
return failExpectedTerm(parser_state);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const next_expression_result = parseAndExpression(
|
|
138
|
-
parser_state,
|
|
139
|
-
stop_character,
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
if (!next_expression_result.success) {
|
|
143
|
-
return next_expression_result;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
expressions.push(next_expression_result.expression);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* @param {ParserState} parser_state
|
|
152
|
-
* @param {')' | null} stop_character
|
|
153
|
-
* @returns {ParseExpressionResult}
|
|
154
|
-
*/
|
|
155
|
-
function parseAndExpression(parser_state, stop_character) {
|
|
156
|
-
const first_expression_result = parseUnaryExpression(
|
|
157
|
-
parser_state,
|
|
158
|
-
stop_character,
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
if (!first_expression_result.success) {
|
|
162
|
-
return first_expression_result;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** @type {ParsedExpression[]} */
|
|
166
|
-
const expressions = [first_expression_result.expression];
|
|
167
|
-
|
|
168
|
-
while (true) {
|
|
169
|
-
skipWhitespace(parser_state);
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
currentCharacter(parser_state) === stop_character ||
|
|
173
|
-
isAtEnd(parser_state)
|
|
174
|
-
) {
|
|
175
|
-
return collapseBooleanExpression('and', expressions);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (!consumeKeyword(parser_state, 'and')) {
|
|
179
|
-
return collapseBooleanExpression('and', expressions);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
skipWhitespace(parser_state);
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
currentCharacter(parser_state) === stop_character ||
|
|
186
|
-
isAtEnd(parser_state)
|
|
187
|
-
) {
|
|
188
|
-
return failExpectedTerm(parser_state);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const next_expression_result = parseUnaryExpression(
|
|
192
|
-
parser_state,
|
|
193
|
-
stop_character,
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
if (!next_expression_result.success) {
|
|
197
|
-
return next_expression_result;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
expressions.push(next_expression_result.expression);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* @param {ParserState} parser_state
|
|
206
|
-
* @param {')' | null} stop_character
|
|
207
|
-
* @returns {ParseExpressionResult}
|
|
208
|
-
*/
|
|
209
|
-
function parseUnaryExpression(parser_state, stop_character) {
|
|
210
|
-
skipWhitespace(parser_state);
|
|
211
|
-
const start_index = parser_state.index;
|
|
212
|
-
|
|
213
|
-
if (!consumeKeyword(parser_state, 'not')) {
|
|
214
|
-
return parsePrimaryExpression(parser_state, stop_character);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const whitespace_length = skipWhitespace(parser_state);
|
|
218
|
-
|
|
219
|
-
if (whitespace_length === 0 && currentCharacter(parser_state) !== '(') {
|
|
220
|
-
return fail(
|
|
221
|
-
start_index + 1,
|
|
222
|
-
`Unsupported query token "${readToken(parser_state, start_index)}".`,
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
currentCharacter(parser_state) === stop_character ||
|
|
228
|
-
isAtEnd(parser_state)
|
|
229
|
-
) {
|
|
230
|
-
return failExpectedTerm(parser_state);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const expression_result = parseUnaryExpression(parser_state, stop_character);
|
|
234
|
-
|
|
235
|
-
if (!expression_result.success) {
|
|
236
|
-
return expression_result;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return successExpression({
|
|
240
|
-
expression: expression_result.expression,
|
|
241
|
-
kind: 'not',
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* @param {ParserState} parser_state
|
|
247
|
-
* @param {')' | null} stop_character
|
|
248
|
-
* @returns {ParseExpressionResult}
|
|
249
|
-
*/
|
|
250
|
-
function parsePrimaryExpression(parser_state, stop_character) {
|
|
251
|
-
skipWhitespace(parser_state);
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
currentCharacter(parser_state) === stop_character ||
|
|
255
|
-
isAtEnd(parser_state)
|
|
256
|
-
) {
|
|
257
|
-
return failExpectedTerm(parser_state);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (consumeOperator(parser_state, '(')) {
|
|
261
|
-
const expression_result = parseExpression(parser_state, ')');
|
|
262
|
-
|
|
263
|
-
if (!expression_result.success) {
|
|
264
|
-
return expression_result;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (!consumeOperator(parser_state, ')')) {
|
|
268
|
-
return failToken(parser_state);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return expression_result;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const term_result = parseTerm(parser_state);
|
|
275
|
-
|
|
276
|
-
if (!term_result.success) {
|
|
277
|
-
return term_result;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return successExpression({
|
|
281
|
-
kind: 'term',
|
|
282
|
-
term: term_result.term,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* @param {'and' | 'or'} kind
|
|
288
|
-
* @param {ParsedExpression[]} expressions
|
|
289
|
-
* @returns {ParseExpressionResult}
|
|
290
|
-
*/
|
|
291
|
-
function collapseBooleanExpression(kind, expressions) {
|
|
292
|
-
if (expressions.length === 1) {
|
|
293
|
-
return successExpression(expressions[0]);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return successExpression({
|
|
297
|
-
expressions,
|
|
298
|
-
kind,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* @param {ParserState} parser_state
|
|
304
|
-
* @returns {ParseTermResult}
|
|
305
|
-
*/
|
|
306
|
-
function parseTerm(parser_state) {
|
|
307
|
-
return parseAggregate(parser_state) ?? parseAtomicTerm(parser_state);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* @param {ParserState} parser_state
|
|
312
|
-
* @returns {ParseTermResult | null}
|
|
313
|
-
*/
|
|
314
|
-
function parseAggregate(parser_state) {
|
|
315
|
-
const start_index = parser_state.index;
|
|
316
|
-
const aggregate_name = parseIdentifier(parser_state);
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
aggregate_name !== 'any' &&
|
|
320
|
-
aggregate_name !== 'count' &&
|
|
321
|
-
aggregate_name !== 'none'
|
|
322
|
-
) {
|
|
323
|
-
parser_state.index = start_index;
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
skipWhitespace(parser_state);
|
|
328
|
-
|
|
329
|
-
if (currentCharacter(parser_state) !== '(') {
|
|
330
|
-
parser_state.index = start_index;
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
parser_state.index += 1;
|
|
335
|
-
skipWhitespace(parser_state);
|
|
336
|
-
|
|
337
|
-
const traversal_result = parseTraversal(parser_state);
|
|
338
|
-
|
|
339
|
-
if (!traversal_result.success) {
|
|
340
|
-
return traversal_result;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (!consumeOperator(parser_state, ',')) {
|
|
344
|
-
return failToken(parser_state);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
skipWhitespace(parser_state);
|
|
348
|
-
const expression_result = parseExpression(parser_state, ')');
|
|
349
|
-
|
|
350
|
-
if (!expression_result.success) {
|
|
351
|
-
return expression_result;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (!consumeOperator(parser_state, ')')) {
|
|
355
|
-
return failToken(parser_state);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return createAggregateTerm(
|
|
359
|
-
parser_state,
|
|
360
|
-
aggregate_name,
|
|
361
|
-
traversal_result.traversal,
|
|
362
|
-
expression_result.expression,
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* @param {ParserState} parser_state
|
|
368
|
-
* @returns {ParseTermResult}
|
|
369
|
-
*/
|
|
370
|
-
function parseAtomicTerm(parser_state) {
|
|
371
|
-
const start_index = parser_state.index;
|
|
372
|
-
const field_or_relation_name = parseIdentifier(parser_state);
|
|
373
|
-
|
|
374
|
-
if (!field_or_relation_name) {
|
|
375
|
-
return failToken(parser_state);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return parseOperatorTerm(parser_state, start_index, field_or_relation_name);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* @param {ParserState} parser_state
|
|
383
|
-
* @param {ParsedAggregateName} aggregate_name
|
|
384
|
-
* @param {ParsedTraversalTerm} traversal
|
|
385
|
-
* @param {ParsedExpression} expression
|
|
386
|
-
* @returns {ParseTermResult}
|
|
387
|
-
*/
|
|
388
|
-
function createAggregateTerm(
|
|
389
|
-
parser_state,
|
|
390
|
-
aggregate_name,
|
|
391
|
-
traversal,
|
|
392
|
-
expression,
|
|
393
|
-
) {
|
|
394
|
-
/** @type {ParsedAggregateTerm} */
|
|
395
|
-
const aggregate_term = {
|
|
396
|
-
aggregate_name,
|
|
397
|
-
expression,
|
|
398
|
-
kind: 'aggregate',
|
|
399
|
-
traversal,
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
if (aggregate_name !== 'count') {
|
|
403
|
-
return successTerm(aggregate_term);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const count_tail = parseCountTail(parser_state);
|
|
407
|
-
|
|
408
|
-
if (!count_tail) {
|
|
409
|
-
return failToken(parser_state);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return successTerm({
|
|
413
|
-
...aggregate_term,
|
|
414
|
-
comparison: count_tail.comparison,
|
|
415
|
-
value: count_tail.value,
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* @param {ParserState} parser_state
|
|
421
|
-
* @param {number} start_index
|
|
422
|
-
* @param {ParsedFieldName | string} field_name
|
|
423
|
-
* @returns {ParseTermResult | null}
|
|
424
|
-
*/
|
|
425
|
-
function parseFieldSet(parser_state, start_index, field_name) {
|
|
426
|
-
const operator_start_index = parser_state.index;
|
|
427
|
-
|
|
428
|
-
if (!consumeRequiredWhitespace(parser_state)) {
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const operator = parseSetOperator(parser_state);
|
|
433
|
-
|
|
434
|
-
if (!operator) {
|
|
435
|
-
parser_state.index = operator_start_index;
|
|
436
|
-
return null;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (!consumeRequiredWhitespace(parser_state)) {
|
|
440
|
-
return failToken(parser_state);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const values = parseList(parser_state);
|
|
444
|
-
|
|
445
|
-
if (!values) {
|
|
446
|
-
return failToken(parser_state);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (!Array.isArray(values)) {
|
|
450
|
-
return values;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
return successTerm({
|
|
454
|
-
column: start_index + 1,
|
|
455
|
-
field_name,
|
|
456
|
-
kind: 'field_set',
|
|
457
|
-
operator,
|
|
458
|
-
values,
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* @param {ParserState} parser_state
|
|
464
|
-
* @param {number} start_index
|
|
465
|
-
* @param {string} field_or_relation_name
|
|
466
|
-
* @returns {ParseTermResult}
|
|
467
|
-
*/
|
|
468
|
-
function parseOperatorTerm(parser_state, start_index, field_or_relation_name) {
|
|
469
|
-
const field_set = parseFieldSet(
|
|
470
|
-
parser_state,
|
|
471
|
-
start_index,
|
|
472
|
-
field_or_relation_name,
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
if (field_set) {
|
|
476
|
-
return field_set;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const prefix_term = parsePrefixTerm(
|
|
480
|
-
parser_state,
|
|
481
|
-
start_index,
|
|
482
|
-
field_or_relation_name,
|
|
483
|
-
);
|
|
484
|
-
|
|
485
|
-
if (prefix_term) {
|
|
486
|
-
return prefix_term;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
const contains_term = parseContainsTerm(
|
|
490
|
-
parser_state,
|
|
491
|
-
start_index,
|
|
492
|
-
field_or_relation_name,
|
|
493
|
-
);
|
|
494
|
-
|
|
495
|
-
if (contains_term) {
|
|
496
|
-
return contains_term;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const comparison_term = parseFieldComparisonTerm(
|
|
500
|
-
parser_state,
|
|
501
|
-
start_index,
|
|
502
|
-
field_or_relation_name,
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
if (comparison_term) {
|
|
506
|
-
return comparison_term;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
const equality_term = parseEqualityTerm(
|
|
510
|
-
parser_state,
|
|
511
|
-
start_index,
|
|
512
|
-
field_or_relation_name,
|
|
513
|
-
);
|
|
514
|
-
|
|
515
|
-
if (equality_term) {
|
|
516
|
-
return equality_term;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (consumeOperator(parser_state, ':*')) {
|
|
520
|
-
return successTerm({
|
|
521
|
-
column: start_index + 1,
|
|
522
|
-
kind: 'relation',
|
|
523
|
-
relation_name: field_or_relation_name,
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
parser_state.index = start_index;
|
|
528
|
-
return failToken(parser_state);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* @param {ParserState} parser_state
|
|
533
|
-
* @returns {{ comparison: ParsedAggregateComparison, value: number } | null}
|
|
534
|
-
*/
|
|
535
|
-
function parseCountTail(parser_state) {
|
|
536
|
-
if (!consumeRequiredWhitespace(parser_state)) {
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const comparison = parseComparison(parser_state);
|
|
541
|
-
|
|
542
|
-
if (!comparison || !consumeRequiredWhitespace(parser_state)) {
|
|
543
|
-
return null;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const value = parseInteger(parser_state);
|
|
547
|
-
|
|
548
|
-
return value === null ? null : { comparison, value };
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* @param {ParserState} parser_state
|
|
553
|
-
* @returns {{ success: true, traversal: ParsedTraversalTerm } | { diagnostic: PatramDiagnostic, success: false }}
|
|
554
|
-
*/
|
|
555
|
-
function parseTraversal(parser_state) {
|
|
556
|
-
const column = parser_state.index + 1;
|
|
557
|
-
const direction = parseIdentifier(parser_state);
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
(direction !== 'in' && direction !== 'out') ||
|
|
561
|
-
!consumeOperator(parser_state, ':')
|
|
562
|
-
) {
|
|
563
|
-
return failToken(parser_state);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const relation_name = parseIdentifier(parser_state);
|
|
567
|
-
|
|
568
|
-
return relation_name
|
|
569
|
-
? { success: true, traversal: { column, direction, relation_name } }
|
|
570
|
-
: failToken(parser_state);
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* @param {ParserState} parser_state
|
|
575
|
-
* @returns {'in' | 'not in' | null}
|
|
576
|
-
*/
|
|
577
|
-
function parseSetOperator(parser_state) {
|
|
578
|
-
if (consumeKeyword(parser_state, 'in')) {
|
|
579
|
-
return 'in';
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (
|
|
583
|
-
!consumeKeyword(parser_state, 'not') ||
|
|
584
|
-
!consumeRequiredWhitespace(parser_state)
|
|
585
|
-
) {
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
return consumeKeyword(parser_state, 'in') ? 'not in' : null;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* @param {ParserState} parser_state
|
|
594
|
-
* @param {number} start_index
|
|
595
|
-
* @param {string} field_name
|
|
596
|
-
* @returns {ParseTermResult | null}
|
|
597
|
-
*/
|
|
598
|
-
function parsePrefixTerm(parser_state, start_index, field_name) {
|
|
599
|
-
if (!consumeOperator(parser_state, '^=')) {
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
skipWhitespace(parser_state);
|
|
604
|
-
const value_result = parseResolvedBareValue(parser_state);
|
|
605
|
-
|
|
606
|
-
if (!value_result) {
|
|
607
|
-
return failToken(parser_state);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if (!value_result.success) {
|
|
611
|
-
return value_result;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
return value_result.value
|
|
615
|
-
? successTerm({
|
|
616
|
-
column: start_index + 1,
|
|
617
|
-
field_name,
|
|
618
|
-
kind: 'field',
|
|
619
|
-
operator: '^=',
|
|
620
|
-
value: value_result.value,
|
|
621
|
-
})
|
|
622
|
-
: failToken(parser_state);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/**
|
|
626
|
-
* @param {ParserState} parser_state
|
|
627
|
-
* @param {number} start_index
|
|
628
|
-
* @param {string} field_name
|
|
629
|
-
* @returns {ParseTermResult | null}
|
|
630
|
-
*/
|
|
631
|
-
function parseContainsTerm(parser_state, start_index, field_name) {
|
|
632
|
-
if (!consumeOperator(parser_state, '~')) {
|
|
633
|
-
return null;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
skipWhitespace(parser_state);
|
|
637
|
-
const value_result = parseResolvedBareValue(parser_state);
|
|
638
|
-
|
|
639
|
-
if (!value_result) {
|
|
640
|
-
return failToken(parser_state);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
if (!value_result.success) {
|
|
644
|
-
return value_result;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
return value_result.value
|
|
648
|
-
? successTerm({
|
|
649
|
-
column: start_index + 1,
|
|
650
|
-
field_name,
|
|
651
|
-
kind: 'field',
|
|
652
|
-
operator: '~',
|
|
653
|
-
value: value_result.value,
|
|
654
|
-
})
|
|
655
|
-
: failToken(parser_state);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* @param {ParserState} parser_state
|
|
660
|
-
* @param {number} start_index
|
|
661
|
-
* @param {string} field_or_relation_name
|
|
662
|
-
* @returns {ParseTermResult | null}
|
|
663
|
-
*/
|
|
664
|
-
function parseEqualityTerm(parser_state, start_index, field_or_relation_name) {
|
|
665
|
-
if (!consumeOperator(parser_state, '=')) {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
skipWhitespace(parser_state);
|
|
670
|
-
const value_result = parseResolvedBareValue(parser_state);
|
|
671
|
-
|
|
672
|
-
if (!value_result) {
|
|
673
|
-
return failToken(parser_state);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (!value_result.success) {
|
|
677
|
-
return value_result;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const value = value_result.value;
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
field_or_relation_name.startsWith('$') ||
|
|
684
|
-
field_or_relation_name === 'title' ||
|
|
685
|
-
!value.includes(':')
|
|
686
|
-
) {
|
|
687
|
-
return successTerm({
|
|
688
|
-
column: start_index + 1,
|
|
689
|
-
field_name: field_or_relation_name,
|
|
690
|
-
kind: 'field',
|
|
691
|
-
operator: '=',
|
|
692
|
-
value,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return successTerm({
|
|
697
|
-
column: start_index + 1,
|
|
698
|
-
kind: 'relation_target',
|
|
699
|
-
relation_name: field_or_relation_name,
|
|
700
|
-
target_id: value,
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* @param {ParserState} parser_state
|
|
706
|
-
* @param {number} start_index
|
|
707
|
-
* @param {string} field_name
|
|
708
|
-
* @returns {ParseTermResult | null}
|
|
709
|
-
*/
|
|
710
|
-
function parseFieldComparisonTerm(parser_state, start_index, field_name) {
|
|
711
|
-
const operator = parseFieldComparisonOperator(parser_state);
|
|
712
|
-
|
|
713
|
-
if (!operator) {
|
|
714
|
-
return null;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
skipWhitespace(parser_state);
|
|
718
|
-
const value_result = parseResolvedBareValue(parser_state);
|
|
719
|
-
|
|
720
|
-
if (!value_result) {
|
|
721
|
-
return failToken(parser_state);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
if (!value_result.success) {
|
|
725
|
-
return value_result;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
return successTerm({
|
|
729
|
-
column: start_index + 1,
|
|
730
|
-
field_name,
|
|
731
|
-
kind: 'field',
|
|
732
|
-
operator,
|
|
733
|
-
value: value_result.value,
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/**
|
|
738
|
-
* @param {ParserState} parser_state
|
|
739
|
-
* @returns {'!=' | '<=' | '>=' | '<' | '>' | null}
|
|
740
|
-
*/
|
|
741
|
-
function parseFieldComparisonOperator(parser_state) {
|
|
742
|
-
/** @type {Array<'!=' | '<=' | '>=' | '<' | '>'>} */
|
|
743
|
-
const comparisons = ['!=', '<=', '>=', '<', '>'];
|
|
744
|
-
|
|
745
|
-
return (
|
|
746
|
-
comparisons.find((value) => consumeOperator(parser_state, value)) ?? null
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* @param {ParserState} parser_state
|
|
752
|
-
* @returns {string[] | ParseFailureResult | null}
|
|
753
|
-
*/
|
|
754
|
-
function parseList(parser_state) {
|
|
755
|
-
if (!consumeOperator(parser_state, '[')) {
|
|
756
|
-
return null;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/** @type {string[]} */
|
|
760
|
-
const values = [];
|
|
761
|
-
|
|
762
|
-
while (true) {
|
|
763
|
-
skipWhitespace(parser_state);
|
|
764
|
-
|
|
765
|
-
if (consumeOperator(parser_state, ']')) {
|
|
766
|
-
return values.length > 0 ? values : null;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const list_value_result = parseResolvedListValue(parser_state);
|
|
770
|
-
|
|
771
|
-
if (!list_value_result) {
|
|
772
|
-
return null;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
if (!list_value_result.success) {
|
|
776
|
-
return list_value_result;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
values.push(list_value_result.value);
|
|
780
|
-
skipWhitespace(parser_state);
|
|
781
|
-
|
|
782
|
-
if (consumeOperator(parser_state, ']')) {
|
|
783
|
-
return values;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
if (!consumeOperator(parser_state, ',')) {
|
|
787
|
-
return null;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* @param {ParserState} parser_state
|
|
794
|
-
* @returns {ParsedAggregateComparison | null}
|
|
795
|
-
*/
|
|
796
|
-
function parseComparison(parser_state) {
|
|
797
|
-
/** @type {ParsedAggregateComparison[]} */
|
|
798
|
-
const comparisons = ['>=', '<=', '!=', '=', '>', '<'];
|
|
799
|
-
|
|
800
|
-
return (
|
|
801
|
-
comparisons.find((value) => consumeOperator(parser_state, value)) ?? null
|
|
802
|
-
);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
/**
|
|
806
|
-
* @param {ParserState} parser_state
|
|
807
|
-
* @returns {string | null}
|
|
808
|
-
*/
|
|
809
|
-
function parseIdentifier(parser_state) {
|
|
810
|
-
return readMatch(parser_state, /^\$?[a-z_][a-z0-9_]*/u);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* @param {ParserState} parser_state
|
|
815
|
-
* @returns {number | null}
|
|
816
|
-
*/
|
|
817
|
-
function parseInteger(parser_state) {
|
|
818
|
-
const numeric_text = readMatch(parser_state, /^\d+/u);
|
|
819
|
-
return numeric_text ? Number.parseInt(numeric_text, 10) : null;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* @param {ParserState} parser_state
|
|
824
|
-
* @returns {string | null}
|
|
825
|
-
*/
|
|
826
|
-
function parseBareValue(parser_state) {
|
|
827
|
-
return readMatch(parser_state, /^[^\s\],)]+/u);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
/**
|
|
831
|
-
* @param {ParserState} parser_state
|
|
832
|
-
* @returns {string | null}
|
|
833
|
-
*/
|
|
834
|
-
function parseListValue(parser_state) {
|
|
835
|
-
const list_value = readMatch(parser_state, /^[^\s\],][^\],)]*/u);
|
|
836
|
-
return list_value?.trim() || null;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* @param {ParserState} parser_state
|
|
841
|
-
* @returns {ParseValueResult | null}
|
|
842
|
-
*/
|
|
843
|
-
function parseResolvedBareValue(parser_state) {
|
|
844
|
-
const start_index = parser_state.index;
|
|
845
|
-
const raw_value = parseBareValue(parser_state);
|
|
846
|
-
|
|
847
|
-
if (!raw_value) {
|
|
848
|
-
return null;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
return resolveBindingValue(parser_state, raw_value, start_index);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
/**
|
|
855
|
-
* @param {ParserState} parser_state
|
|
856
|
-
* @returns {ParseValueResult | null}
|
|
857
|
-
*/
|
|
858
|
-
function parseResolvedListValue(parser_state) {
|
|
859
|
-
const start_index = parser_state.index;
|
|
860
|
-
const raw_value = parseListValue(parser_state);
|
|
861
|
-
|
|
862
|
-
if (!raw_value) {
|
|
863
|
-
return null;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
return resolveBindingValue(parser_state, raw_value, start_index);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
* @param {ParserState} parser_state
|
|
871
|
-
* @param {string} raw_value
|
|
872
|
-
* @param {number} start_index
|
|
873
|
-
* @returns {ParseValueResult}
|
|
874
|
-
*/
|
|
875
|
-
function resolveBindingValue(parser_state, raw_value, start_index) {
|
|
876
|
-
if (!raw_value.startsWith('@')) {
|
|
877
|
-
return {
|
|
878
|
-
success: true,
|
|
879
|
-
value: raw_value,
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
const binding_name = raw_value.slice(1);
|
|
884
|
-
|
|
885
|
-
if (!/^[a-z_][a-z0-9_]*$/u.test(binding_name)) {
|
|
886
|
-
return fail(
|
|
887
|
-
start_index + 1,
|
|
888
|
-
`Unsupported query binding "${raw_value}".`,
|
|
889
|
-
'query.invalid',
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
const binding_value = parser_state.bindings[binding_name];
|
|
894
|
-
|
|
895
|
-
if (binding_value === undefined) {
|
|
896
|
-
return fail(
|
|
897
|
-
start_index + 1,
|
|
898
|
-
`Missing query binding "${binding_name}".`,
|
|
899
|
-
'query.binding_missing',
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
return {
|
|
904
|
-
success: true,
|
|
905
|
-
value: binding_value,
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
/**
|
|
910
|
-
* @param {ParserState} parser_state
|
|
911
|
-
* @param {RegExp} pattern
|
|
912
|
-
* @returns {string | null}
|
|
913
|
-
*/
|
|
914
|
-
function readMatch(parser_state, pattern) {
|
|
915
|
-
const match = parser_state.where_clause
|
|
916
|
-
.slice(parser_state.index)
|
|
917
|
-
.match(pattern);
|
|
918
|
-
|
|
919
|
-
if (!match) {
|
|
920
|
-
return null;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
parser_state.index += match[0].length;
|
|
924
|
-
return match[0];
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
/**
|
|
928
|
-
* @param {ParserState} parser_state
|
|
929
|
-
* @param {string} operator
|
|
930
|
-
* @returns {boolean}
|
|
931
|
-
*/
|
|
932
|
-
function consumeOperator(parser_state, operator) {
|
|
933
|
-
skipWhitespace(parser_state);
|
|
934
|
-
|
|
935
|
-
if (!parser_state.where_clause.startsWith(operator, parser_state.index)) {
|
|
936
|
-
return false;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
parser_state.index += operator.length;
|
|
940
|
-
return true;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
/**
|
|
944
|
-
* @param {ParserState} parser_state
|
|
945
|
-
* @param {string} keyword
|
|
946
|
-
* @returns {boolean}
|
|
947
|
-
*/
|
|
948
|
-
function consumeKeyword(parser_state, keyword) {
|
|
949
|
-
if (!parser_state.where_clause.startsWith(keyword, parser_state.index)) {
|
|
950
|
-
return false;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
const next_character =
|
|
954
|
-
parser_state.where_clause[parser_state.index + keyword.length];
|
|
955
|
-
|
|
956
|
-
if (next_character && /[a-z_]/u.test(next_character)) {
|
|
957
|
-
return false;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
parser_state.index += keyword.length;
|
|
961
|
-
return true;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
/**
|
|
965
|
-
* @param {ParserState} parser_state
|
|
966
|
-
* @returns {boolean}
|
|
967
|
-
*/
|
|
968
|
-
function consumeRequiredWhitespace(parser_state) {
|
|
969
|
-
return skipWhitespace(parser_state) > 0;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
/**
|
|
973
|
-
* @param {ParserState} parser_state
|
|
974
|
-
* @returns {number}
|
|
975
|
-
*/
|
|
976
|
-
function skipWhitespace(parser_state) {
|
|
977
|
-
const whitespace = readMatch(parser_state, /^\s+/u);
|
|
978
|
-
return whitespace?.length ?? 0;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
/**
|
|
982
|
-
* @param {ParserState} parser_state
|
|
983
|
-
* @returns {string | undefined}
|
|
984
|
-
*/
|
|
985
|
-
function currentCharacter(parser_state) {
|
|
986
|
-
return parser_state.where_clause[parser_state.index];
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
/**
|
|
990
|
-
* @param {ParserState} parser_state
|
|
991
|
-
* @returns {boolean}
|
|
992
|
-
*/
|
|
993
|
-
function isAtEnd(parser_state) {
|
|
994
|
-
return parser_state.index >= parser_state.where_clause.length;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
/**
|
|
998
|
-
* @param {ParsedExpression} expression
|
|
999
|
-
* @returns {ParseExpressionResult}
|
|
1000
|
-
*/
|
|
1001
|
-
function successExpression(expression) {
|
|
1002
|
-
return { expression, success: true };
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
/**
|
|
1006
|
-
* @param {ParsedTerm} term
|
|
1007
|
-
* @returns {ParseTermResult}
|
|
1008
|
-
*/
|
|
1009
|
-
function successTerm(term) {
|
|
1010
|
-
return { success: true, term };
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
/**
|
|
1014
|
-
* @param {ParserState} parser_state
|
|
1015
|
-
* @returns {{ diagnostic: PatramDiagnostic, success: false }}
|
|
1016
|
-
*/
|
|
1017
|
-
function failToken(parser_state) {
|
|
1018
|
-
return fail(
|
|
1019
|
-
parser_state.index + 1,
|
|
1020
|
-
`Unsupported query token "${readToken(parser_state, parser_state.index)}".`,
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
/**
|
|
1025
|
-
* @param {ParserState} parser_state
|
|
1026
|
-
* @returns {{ diagnostic: PatramDiagnostic, success: false }}
|
|
1027
|
-
*/
|
|
1028
|
-
function failExpectedTerm(parser_state) {
|
|
1029
|
-
return fail(
|
|
1030
|
-
isAtEnd(parser_state)
|
|
1031
|
-
? parser_state.where_clause.length + 1
|
|
1032
|
-
: parser_state.index + 1,
|
|
1033
|
-
'Expected a query term.',
|
|
1034
|
-
);
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
/**
|
|
1038
|
-
* @param {number} column
|
|
1039
|
-
* @param {string} message
|
|
1040
|
-
* @param {string} code
|
|
1041
|
-
* @returns {{ diagnostic: PatramDiagnostic, success: false }}
|
|
1042
|
-
*/
|
|
1043
|
-
function fail(column, message, code = 'query.invalid') {
|
|
1044
|
-
return {
|
|
1045
|
-
diagnostic: {
|
|
1046
|
-
code,
|
|
1047
|
-
column,
|
|
1048
|
-
level: 'error',
|
|
1049
|
-
line: 1,
|
|
1050
|
-
message,
|
|
1051
|
-
path: '<query>',
|
|
1052
|
-
},
|
|
1053
|
-
success: false,
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
/**
|
|
1058
|
-
* @param {ParserState} parser_state
|
|
1059
|
-
* @param {number} start_index
|
|
1060
|
-
* @returns {string}
|
|
1061
|
-
*/
|
|
1062
|
-
function readToken(parser_state, start_index) {
|
|
1063
|
-
return parser_state.where_clause.slice(start_index).match(/^\S+/u)?.[0] ?? '';
|
|
1064
|
-
}
|