flowquery 1.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/.github/workflows/npm-publish.yml +30 -0
- package/.github/workflows/release.yml +84 -0
- package/CODE_OF_CONDUCT.md +10 -0
- package/FlowQueryLogoIcon.png +0 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/SECURITY.md +14 -0
- package/SUPPORT.md +13 -0
- package/docs/flowquery.min.js +1 -0
- package/docs/index.html +105 -0
- package/flowquery-vscode/.vscode-test.mjs +5 -0
- package/flowquery-vscode/.vscodeignore +13 -0
- package/flowquery-vscode/LICENSE +21 -0
- package/flowquery-vscode/README.md +11 -0
- package/flowquery-vscode/demo/FlowQueryVSCodeDemo.gif +0 -0
- package/flowquery-vscode/eslint.config.mjs +25 -0
- package/flowquery-vscode/extension.js +508 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -0
- package/flowquery-vscode/flowquery-worker.js +66 -0
- package/flowquery-vscode/images/FlowQueryLogoIcon.png +0 -0
- package/flowquery-vscode/jsconfig.json +13 -0
- package/flowquery-vscode/libs/page.css +53 -0
- package/flowquery-vscode/libs/table.css +13 -0
- package/flowquery-vscode/libs/tabs.css +66 -0
- package/flowquery-vscode/package-lock.json +2917 -0
- package/flowquery-vscode/package.json +51 -0
- package/flowquery-vscode/test/extension.test.js +196 -0
- package/flowquery-vscode/test/worker.test.js +25 -0
- package/flowquery-vscode/vsc-extension-quickstart.md +42 -0
- package/jest.config.js +11 -0
- package/package.json +28 -0
- package/queries/analyze_catfacts.cql +75 -0
- package/queries/azure_openai_completions.cql +13 -0
- package/queries/azure_openai_models.cql +9 -0
- package/queries/mock_pipeline.cql +84 -0
- package/queries/openai_completions.cql +15 -0
- package/queries/openai_models.cql +13 -0
- package/queries/test.cql +6 -0
- package/queries/tool_inference.cql +24 -0
- package/queries/wisdom.cql +6 -0
- package/queries/wisdom_letter_histogram.cql +8 -0
- package/src/compute/runner.ts +65 -0
- package/src/index.browser.ts +11 -0
- package/src/index.ts +12 -0
- package/src/io/command_line.ts +74 -0
- package/src/parsing/alias.ts +23 -0
- package/src/parsing/alias_option.ts +5 -0
- package/src/parsing/ast_node.ts +153 -0
- package/src/parsing/base_parser.ts +92 -0
- package/src/parsing/components/csv.ts +9 -0
- package/src/parsing/components/from.ts +12 -0
- package/src/parsing/components/headers.ts +12 -0
- package/src/parsing/components/json.ts +9 -0
- package/src/parsing/components/null.ts +9 -0
- package/src/parsing/components/post.ts +9 -0
- package/src/parsing/components/text.ts +9 -0
- package/src/parsing/context.ts +48 -0
- package/src/parsing/data_structures/associative_array.ts +43 -0
- package/src/parsing/data_structures/json_array.ts +31 -0
- package/src/parsing/data_structures/key_value_pair.ts +37 -0
- package/src/parsing/data_structures/lookup.ts +40 -0
- package/src/parsing/data_structures/range_lookup.ts +36 -0
- package/src/parsing/expressions/expression.ts +142 -0
- package/src/parsing/expressions/f_string.ts +26 -0
- package/src/parsing/expressions/identifier.ts +22 -0
- package/src/parsing/expressions/number.ts +40 -0
- package/src/parsing/expressions/operator.ts +179 -0
- package/src/parsing/expressions/reference.ts +42 -0
- package/src/parsing/expressions/string.ts +34 -0
- package/src/parsing/functions/aggregate_function.ts +58 -0
- package/src/parsing/functions/avg.ts +37 -0
- package/src/parsing/functions/collect.ts +44 -0
- package/src/parsing/functions/function.ts +60 -0
- package/src/parsing/functions/function_factory.ts +66 -0
- package/src/parsing/functions/join.ts +26 -0
- package/src/parsing/functions/predicate_function.ts +44 -0
- package/src/parsing/functions/predicate_function_factory.ts +15 -0
- package/src/parsing/functions/predicate_sum.ts +29 -0
- package/src/parsing/functions/rand.ts +13 -0
- package/src/parsing/functions/range.ts +18 -0
- package/src/parsing/functions/reducer_element.ts +10 -0
- package/src/parsing/functions/replace.ts +19 -0
- package/src/parsing/functions/round.ts +17 -0
- package/src/parsing/functions/size.ts +17 -0
- package/src/parsing/functions/split.ts +26 -0
- package/src/parsing/functions/stringify.ts +26 -0
- package/src/parsing/functions/sum.ts +31 -0
- package/src/parsing/functions/to_json.ts +17 -0
- package/src/parsing/functions/value_holder.ts +13 -0
- package/src/parsing/logic/case.ts +26 -0
- package/src/parsing/logic/else.ts +12 -0
- package/src/parsing/logic/end.ts +9 -0
- package/src/parsing/logic/then.ts +12 -0
- package/src/parsing/logic/when.ts +12 -0
- package/src/parsing/operations/aggregated_return.ts +18 -0
- package/src/parsing/operations/aggregated_with.ts +18 -0
- package/src/parsing/operations/group_by.ts +124 -0
- package/src/parsing/operations/limit.ts +22 -0
- package/src/parsing/operations/load.ts +92 -0
- package/src/parsing/operations/operation.ts +65 -0
- package/src/parsing/operations/projection.ts +18 -0
- package/src/parsing/operations/return.ts +43 -0
- package/src/parsing/operations/unwind.ts +32 -0
- package/src/parsing/operations/where.ts +38 -0
- package/src/parsing/operations/with.ts +20 -0
- package/src/parsing/parser.ts +762 -0
- package/src/parsing/token_to_node.ts +91 -0
- package/src/tokenization/keyword.ts +43 -0
- package/src/tokenization/operator.ts +25 -0
- package/src/tokenization/string_walker.ts +194 -0
- package/src/tokenization/symbol.ts +15 -0
- package/src/tokenization/token.ts +633 -0
- package/src/tokenization/token_mapper.ts +53 -0
- package/src/tokenization/token_type.ts +15 -0
- package/src/tokenization/tokenizer.ts +229 -0
- package/src/tokenization/trie.ts +117 -0
- package/src/utils/object_utils.ts +17 -0
- package/src/utils/string_utils.ts +114 -0
- package/tests/compute/runner.test.ts +498 -0
- package/tests/parsing/context.test.ts +27 -0
- package/tests/parsing/expression.test.ts +40 -0
- package/tests/parsing/parser.test.ts +434 -0
- package/tests/tokenization/token_mapper.test.ts +47 -0
- package/tests/tokenization/tokenizer.test.ts +67 -0
- package/tests/tokenization/trie.test.ts +20 -0
- package/tsconfig.json +15 -0
- package/typedoc.json +16 -0
- package/webpack.config.js +26 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
import Return from "./operations/return";
|
|
2
|
+
import Expression from "./expressions/expression";
|
|
3
|
+
import ASTNode from "./ast_node";
|
|
4
|
+
import BaseParser from "./base_parser";
|
|
5
|
+
import FunctionFactory from "./functions/function_factory";
|
|
6
|
+
import Function from "./functions/function";
|
|
7
|
+
import AssociativeArray from "./data_structures/associative_array";
|
|
8
|
+
import JSONArray from "./data_structures/json_array";
|
|
9
|
+
import KeyValuePair from "./data_structures/key_value_pair";
|
|
10
|
+
import With from "./operations/with";
|
|
11
|
+
import Unwind from "./operations/unwind";
|
|
12
|
+
import Where from "./operations/where";
|
|
13
|
+
import { AliasOption } from "./alias_option";
|
|
14
|
+
import Load from "./operations/load";
|
|
15
|
+
import From from "./components/from";
|
|
16
|
+
import Alias from "./alias";
|
|
17
|
+
import Headers from "./components/headers";
|
|
18
|
+
import Case from "./logic/case";
|
|
19
|
+
import When from "./logic/when";
|
|
20
|
+
import Else from "./logic/else";
|
|
21
|
+
import Then from "./logic/then";
|
|
22
|
+
import Lookup from "./data_structures/lookup";
|
|
23
|
+
import { Not } from "./expressions/operator";
|
|
24
|
+
import Reference from "./expressions/reference";
|
|
25
|
+
import Operation from "./operations/operation";
|
|
26
|
+
import Post from './components/post';
|
|
27
|
+
import AggregatedReturn from "./operations/aggregated_return";
|
|
28
|
+
import Context from "./context";
|
|
29
|
+
import AggregateFunction from "./functions/aggregate_function";
|
|
30
|
+
import ObjectUtils from "../utils/object_utils";
|
|
31
|
+
import PredicateFunction from "./functions/predicate_function";
|
|
32
|
+
import Token from "../tokenization/token";
|
|
33
|
+
import PredicateFunctionFactory from "./functions/predicate_function_factory";
|
|
34
|
+
import FString from "./expressions/f_string";
|
|
35
|
+
import String from "./expressions/string";
|
|
36
|
+
import AggregatedWith from "./operations/aggregated_with";
|
|
37
|
+
import Identifier from "./expressions/identifier";
|
|
38
|
+
import Limit from "./operations/limit";
|
|
39
|
+
import RangeLookup from "./data_structures/range_lookup";
|
|
40
|
+
import Null from "./components/null";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Main parser for FlowQuery statements.
|
|
44
|
+
*
|
|
45
|
+
* Parses FlowQuery declarative query language statements into an Abstract Syntax Tree (AST).
|
|
46
|
+
* Supports operations like WITH, UNWIND, RETURN, LOAD, WHERE, and LIMIT, along with
|
|
47
|
+
* expressions, functions, data structures, and logical constructs.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const parser = new Parser();
|
|
52
|
+
* const ast = parser.parse("unwind [1, 2, 3, 4, 5] as num return num");
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
class Parser extends BaseParser {
|
|
56
|
+
private variables: Map<string, ASTNode> = new Map();
|
|
57
|
+
private context: Context = new Context();
|
|
58
|
+
private _returns: number = 0;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Parses a FlowQuery statement into an Abstract Syntax Tree.
|
|
62
|
+
*
|
|
63
|
+
* @param statement - The FlowQuery statement to parse
|
|
64
|
+
* @returns The root AST node containing the parsed structure
|
|
65
|
+
* @throws {Error} If the statement is malformed or contains syntax errors
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const ast = parser.parse("LOAD JSON FROM 'https://api.adviceslip.com/advice' AS data RETURN data");
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
public parse(statement: string): ASTNode {
|
|
73
|
+
this.tokenize(statement);
|
|
74
|
+
const root: ASTNode = new ASTNode();
|
|
75
|
+
let previous: Operation | null = null;
|
|
76
|
+
let operation: Operation | null = null;
|
|
77
|
+
while(!this.token.isEOF()) {
|
|
78
|
+
if(root.childCount() > 0) {
|
|
79
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
80
|
+
} else {
|
|
81
|
+
this.skipWhitespaceAndComments();
|
|
82
|
+
}
|
|
83
|
+
operation = this.parseOperation();
|
|
84
|
+
if(operation === null) {
|
|
85
|
+
throw new Error('Expected one of WITH, UNWIND, RETURN, or LOAD');
|
|
86
|
+
}
|
|
87
|
+
if(this._returns > 1) {
|
|
88
|
+
throw new Error('Only one RETURN statement is allowed');
|
|
89
|
+
}
|
|
90
|
+
if(previous !== null) {
|
|
91
|
+
previous.addSibling(operation);
|
|
92
|
+
} else {
|
|
93
|
+
root.addChild(operation);
|
|
94
|
+
}
|
|
95
|
+
const where = this.parseWhere();
|
|
96
|
+
if(where !== null) {
|
|
97
|
+
if(operation instanceof Return) {
|
|
98
|
+
(operation as Return).where = where;
|
|
99
|
+
} else {
|
|
100
|
+
operation.addSibling(where);
|
|
101
|
+
operation = where;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const limit = this.parseLimit();
|
|
105
|
+
if(limit !== null) {
|
|
106
|
+
operation.addSibling(limit);
|
|
107
|
+
operation = limit;
|
|
108
|
+
}
|
|
109
|
+
previous = operation;
|
|
110
|
+
}
|
|
111
|
+
if(!(operation instanceof Return)) {
|
|
112
|
+
throw new Error('Last statement must be a RETURN or a WHERE statement');
|
|
113
|
+
}
|
|
114
|
+
return root;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private parseOperation(): Operation | null {
|
|
118
|
+
return (
|
|
119
|
+
this.parseWith() ||
|
|
120
|
+
this.parseUnwind() ||
|
|
121
|
+
this.parseReturn() ||
|
|
122
|
+
this.parseLoad()
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private parseWith(): With | null {
|
|
127
|
+
if(!this.token.isWith()) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
this.setNextToken();
|
|
131
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
132
|
+
const expressions = Array.from(this.parseExpressions(AliasOption.REQUIRED));
|
|
133
|
+
if(expressions.length === 0) {
|
|
134
|
+
throw new Error('Expected expression');
|
|
135
|
+
}
|
|
136
|
+
if(expressions.some((expression: Expression) => expression.has_reducers())) {
|
|
137
|
+
return new AggregatedWith(expressions);
|
|
138
|
+
}
|
|
139
|
+
return new With(expressions);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private parseUnwind(): Unwind | null {
|
|
143
|
+
if(!this.token.isUnwind()) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
this.setNextToken();
|
|
147
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
148
|
+
const expression: Expression | null = this.parseExpression();
|
|
149
|
+
if(expression === null) {
|
|
150
|
+
throw new Error('Expected expression');
|
|
151
|
+
}
|
|
152
|
+
if(!ObjectUtils.isInstanceOfAny(expression.firstChild(), [JSONArray, Function, Reference, Lookup, RangeLookup])) {
|
|
153
|
+
throw new Error('Expected array, function, reference, or lookup.');
|
|
154
|
+
}
|
|
155
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
156
|
+
const alias = this.parseAlias();
|
|
157
|
+
if(alias !== null) {
|
|
158
|
+
expression.setAlias(alias.getAlias());
|
|
159
|
+
} else {
|
|
160
|
+
throw new Error('Expected alias');
|
|
161
|
+
}
|
|
162
|
+
const unwind = new Unwind(expression);
|
|
163
|
+
this.variables.set(alias.getAlias(), unwind);
|
|
164
|
+
return unwind;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private parseReturn(): Return | null {
|
|
168
|
+
if(!this.token.isReturn()) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
this.setNextToken();
|
|
172
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
173
|
+
const expressions = Array.from(this.parseExpressions(AliasOption.OPTIONAL));
|
|
174
|
+
if(expressions.length === 0) {
|
|
175
|
+
throw new Error('Expected expression');
|
|
176
|
+
}
|
|
177
|
+
if(expressions.some((expression: Expression) => expression.has_reducers())) {
|
|
178
|
+
return new AggregatedReturn(expressions);
|
|
179
|
+
}
|
|
180
|
+
this._returns++;
|
|
181
|
+
return new Return(expressions);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private parseWhere(): Where | null {
|
|
185
|
+
if(!this.token.isWhere()) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
this.expectPreviousTokenToBeWhitespaceOrComment();
|
|
189
|
+
this.setNextToken();
|
|
190
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
191
|
+
const expression = this.parseExpression();
|
|
192
|
+
if(expression === null) {
|
|
193
|
+
throw new Error('Expected expression');
|
|
194
|
+
}
|
|
195
|
+
if(ObjectUtils.isInstanceOfAny(expression.firstChild(), [JSONArray, AssociativeArray])) {
|
|
196
|
+
throw new Error('Expected an expression which can be evaluated to a boolean');
|
|
197
|
+
}
|
|
198
|
+
return new Where(expression);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private parseLoad(): Load | null {
|
|
202
|
+
if(!this.token.isLoad()) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const load = new Load();
|
|
206
|
+
this.setNextToken();
|
|
207
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
208
|
+
if(!(this.token.isJSON() || this.token.isCSV() || this.token.isText())) {
|
|
209
|
+
throw new Error('Expected JSON, CSV, or TEXT');
|
|
210
|
+
}
|
|
211
|
+
load.addChild(this.token.node);
|
|
212
|
+
this.setNextToken();
|
|
213
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
214
|
+
if(!this.token.isFrom()) {
|
|
215
|
+
throw new Error('Expected FROM');
|
|
216
|
+
}
|
|
217
|
+
this.setNextToken();
|
|
218
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
219
|
+
const from = new From();
|
|
220
|
+
load.addChild(from);
|
|
221
|
+
const expression = this.parseExpression();
|
|
222
|
+
if(expression === null) {
|
|
223
|
+
throw new Error('Expected expression');
|
|
224
|
+
}
|
|
225
|
+
from.addChild(expression);
|
|
226
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
227
|
+
if(this.token.isHeaders()) {
|
|
228
|
+
const headers = new Headers();
|
|
229
|
+
this.setNextToken();
|
|
230
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
231
|
+
const header = this.parseExpression();
|
|
232
|
+
if(header === null) {
|
|
233
|
+
throw new Error('Expected expression');
|
|
234
|
+
}
|
|
235
|
+
headers.addChild(header);
|
|
236
|
+
load.addChild(headers);
|
|
237
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
238
|
+
}
|
|
239
|
+
if(this.token.isPost()) {
|
|
240
|
+
const post = new Post();
|
|
241
|
+
this.setNextToken();
|
|
242
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
243
|
+
const payload = this.parseExpression();
|
|
244
|
+
if(payload === null) {
|
|
245
|
+
throw new Error('Expected expression');
|
|
246
|
+
}
|
|
247
|
+
post.addChild(payload);
|
|
248
|
+
load.addChild(post);
|
|
249
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
250
|
+
}
|
|
251
|
+
const alias = this.parseAlias();
|
|
252
|
+
if(alias !== null) {
|
|
253
|
+
load.addChild(alias);
|
|
254
|
+
this.variables.set(alias.getAlias(), load);
|
|
255
|
+
} else {
|
|
256
|
+
throw new Error('Expected alias');
|
|
257
|
+
}
|
|
258
|
+
return load;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private parseLimit(): Limit | null {
|
|
262
|
+
this.skipWhitespaceAndComments();
|
|
263
|
+
if(!this.token.isLimit()) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
this.expectPreviousTokenToBeWhitespaceOrComment();
|
|
267
|
+
this.setNextToken();
|
|
268
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
269
|
+
if(!this.token.isNumber()) {
|
|
270
|
+
throw new Error('Expected number');
|
|
271
|
+
}
|
|
272
|
+
const limit = new Limit(parseInt(this.token.value || '0'));
|
|
273
|
+
this.setNextToken();
|
|
274
|
+
return limit;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private *parseExpressions(alias_option: AliasOption = AliasOption.NOT_ALLOWED): IterableIterator<Expression> {
|
|
278
|
+
while(true) {
|
|
279
|
+
const expression: Expression | null = this.parseExpression();
|
|
280
|
+
if(expression !== null) {
|
|
281
|
+
const alias = this.parseAlias();
|
|
282
|
+
if(expression.firstChild() instanceof Reference && alias === null) {
|
|
283
|
+
const reference: Reference = expression.firstChild() as Reference;
|
|
284
|
+
expression.setAlias(reference.identifier);
|
|
285
|
+
this.variables.set(reference.identifier, expression);
|
|
286
|
+
} else if(alias_option === AliasOption.REQUIRED && alias === null && !(expression.firstChild() instanceof Reference)) {
|
|
287
|
+
throw new Error('Alias required');
|
|
288
|
+
} else if(alias_option === AliasOption.NOT_ALLOWED && alias !== null) {
|
|
289
|
+
throw new Error('Alias not allowed');
|
|
290
|
+
} else if([AliasOption.OPTIONAL, AliasOption.REQUIRED].includes(alias_option) && alias !== null) {
|
|
291
|
+
expression.setAlias(alias.getAlias());
|
|
292
|
+
this.variables.set(alias.getAlias(), expression);
|
|
293
|
+
}
|
|
294
|
+
yield expression;
|
|
295
|
+
} else {
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
this.skipWhitespaceAndComments();
|
|
299
|
+
if(!this.token.isComma()) {
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
this.setNextToken();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private parseExpression(): Expression | null {
|
|
307
|
+
const expression = new Expression();
|
|
308
|
+
while(true) {
|
|
309
|
+
this.skipWhitespaceAndComments();
|
|
310
|
+
if(this.token.isIdentifier() && !this.peek()?.isLeftParenthesis()) {
|
|
311
|
+
const identifier: string = this.token.value || '';
|
|
312
|
+
const reference = new Reference(identifier, this.variables.get(identifier));
|
|
313
|
+
this.setNextToken();
|
|
314
|
+
const lookup = this.parseLookup(reference);
|
|
315
|
+
expression.addNode(lookup);
|
|
316
|
+
} else if(this.token.isIdentifier() && this.peek()?.isLeftParenthesis()) {
|
|
317
|
+
const func = this.parsePredicateFunction() || this.parseFunction();
|
|
318
|
+
if(func !== null) {
|
|
319
|
+
const lookup = this.parseLookup(func);
|
|
320
|
+
expression.addNode(lookup);
|
|
321
|
+
}
|
|
322
|
+
} else if(this.token.isOperand()) {
|
|
323
|
+
expression.addNode(this.token.node);
|
|
324
|
+
this.setNextToken();
|
|
325
|
+
} else if(this.token.isFString()) {
|
|
326
|
+
const f_string = this.parseFString();
|
|
327
|
+
if(f_string === null) {
|
|
328
|
+
throw new Error('Expected f-string');
|
|
329
|
+
}
|
|
330
|
+
expression.addNode(f_string);
|
|
331
|
+
} else if(this.token.isLeftParenthesis()) {
|
|
332
|
+
this.setNextToken();
|
|
333
|
+
const sub = this.parseExpression();
|
|
334
|
+
if(sub === null) {
|
|
335
|
+
throw new Error('Expected expression');
|
|
336
|
+
}
|
|
337
|
+
if(!this.token.isRightParenthesis()) {
|
|
338
|
+
throw new Error('Expected right parenthesis');
|
|
339
|
+
}
|
|
340
|
+
this.setNextToken();
|
|
341
|
+
const lookup = this.parseLookup(sub);
|
|
342
|
+
expression.addNode(lookup);
|
|
343
|
+
} else if(this.token.isOpeningBrace() || this.token.isOpeningBracket()) {
|
|
344
|
+
const json = this.parseJSON();
|
|
345
|
+
if(json === null) {
|
|
346
|
+
throw new Error('Expected JSON object');
|
|
347
|
+
}
|
|
348
|
+
const lookup = this.parseLookup(json);
|
|
349
|
+
expression.addNode(lookup);
|
|
350
|
+
} else if(this.token.isCase()) {
|
|
351
|
+
const _case = this.parseCase();
|
|
352
|
+
if(_case === null) {
|
|
353
|
+
throw new Error('Expected CASE statement');
|
|
354
|
+
}
|
|
355
|
+
expression.addNode(_case);
|
|
356
|
+
} else if(this.token.isNot()) {
|
|
357
|
+
const not = new Not();
|
|
358
|
+
this.setNextToken();
|
|
359
|
+
const sub = this.parseExpression();
|
|
360
|
+
if(sub === null) {
|
|
361
|
+
throw new Error('Expected expression');
|
|
362
|
+
}
|
|
363
|
+
not.addChild(sub);
|
|
364
|
+
expression.addNode(not);
|
|
365
|
+
} else {
|
|
366
|
+
if(expression.nodesAdded()) {
|
|
367
|
+
throw new Error('Expected operand or left parenthesis');
|
|
368
|
+
} else {
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
this.skipWhitespaceAndComments();
|
|
373
|
+
if(this.token.isOperator()) {
|
|
374
|
+
expression.addNode(this.token.node);
|
|
375
|
+
} else {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
this.setNextToken();
|
|
379
|
+
}
|
|
380
|
+
if(expression.nodesAdded()) {
|
|
381
|
+
expression.finish();
|
|
382
|
+
return expression;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private parseLookup(node: ASTNode): ASTNode {
|
|
388
|
+
let variable: ASTNode = node;
|
|
389
|
+
let lookup: Lookup | RangeLookup | null = null;
|
|
390
|
+
while(true) {
|
|
391
|
+
if(this.token.isDot()) {
|
|
392
|
+
this.setNextToken();
|
|
393
|
+
if(!this.token.isIdentifier() && !this.token.isKeyword()) {
|
|
394
|
+
throw new Error('Expected identifier');
|
|
395
|
+
}
|
|
396
|
+
lookup = new Lookup();
|
|
397
|
+
lookup.index = new Identifier(this.token.value || '');
|
|
398
|
+
lookup.variable = variable;
|
|
399
|
+
this.setNextToken();
|
|
400
|
+
} else if(this.token.isOpeningBracket()) {
|
|
401
|
+
this.setNextToken();
|
|
402
|
+
this.skipWhitespaceAndComments();
|
|
403
|
+
const index = this.parseExpression();
|
|
404
|
+
let to: Expression | null = null;
|
|
405
|
+
this.skipWhitespaceAndComments();
|
|
406
|
+
if(this.token.isColon()) {
|
|
407
|
+
this.setNextToken();
|
|
408
|
+
this.skipWhitespaceAndComments();
|
|
409
|
+
lookup = new RangeLookup();
|
|
410
|
+
to = this.parseExpression();
|
|
411
|
+
} else {
|
|
412
|
+
if(index === null) {
|
|
413
|
+
throw new Error('Expected expression');
|
|
414
|
+
}
|
|
415
|
+
lookup = new Lookup();
|
|
416
|
+
}
|
|
417
|
+
this.skipWhitespaceAndComments();
|
|
418
|
+
if(!this.token.isClosingBracket()) {
|
|
419
|
+
throw new Error('Expected closing bracket');
|
|
420
|
+
}
|
|
421
|
+
this.setNextToken();
|
|
422
|
+
if(lookup instanceof RangeLookup) {
|
|
423
|
+
lookup.from = index || new Null();
|
|
424
|
+
lookup.to = to || new Null();
|
|
425
|
+
} else if(lookup instanceof Lookup && index !== null) {
|
|
426
|
+
lookup.index = index;
|
|
427
|
+
}
|
|
428
|
+
lookup.variable = variable;
|
|
429
|
+
} else {
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
variable = lookup || variable;
|
|
433
|
+
}
|
|
434
|
+
return variable;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private parseCase(): Case | null {
|
|
438
|
+
if(!this.token.isCase()) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
this.setNextToken();
|
|
442
|
+
const _case = new Case();
|
|
443
|
+
let parts: number = 0;
|
|
444
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
445
|
+
while(true) {
|
|
446
|
+
const when = this.parseWhen();
|
|
447
|
+
if(when === null && parts === 0) {
|
|
448
|
+
throw new Error('Expected WHEN');
|
|
449
|
+
} else if(when === null && parts > 0) {
|
|
450
|
+
break;
|
|
451
|
+
} else if (when !== null) {
|
|
452
|
+
_case.addChild(when);
|
|
453
|
+
}
|
|
454
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
455
|
+
const then = this.parseThen();
|
|
456
|
+
if(then === null) {
|
|
457
|
+
throw new Error('Expected THEN');
|
|
458
|
+
} else {
|
|
459
|
+
_case.addChild(then);
|
|
460
|
+
}
|
|
461
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
462
|
+
parts++;
|
|
463
|
+
}
|
|
464
|
+
const _else = this.parseElse();
|
|
465
|
+
if(_else === null) {
|
|
466
|
+
throw new Error('Expected ELSE');
|
|
467
|
+
} else {
|
|
468
|
+
_case.addChild(_else);
|
|
469
|
+
}
|
|
470
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
471
|
+
if(!this.token.isEnd()) {
|
|
472
|
+
throw new Error('Expected END');
|
|
473
|
+
}
|
|
474
|
+
this.setNextToken();
|
|
475
|
+
return _case;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private parseWhen(): When | null {
|
|
479
|
+
if(!this.token.isWhen()) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
this.setNextToken();
|
|
483
|
+
const when = new When();
|
|
484
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
485
|
+
const expression = this.parseExpression();
|
|
486
|
+
if(expression === null) {
|
|
487
|
+
throw new Error('Expected expression');
|
|
488
|
+
}
|
|
489
|
+
when.addChild(expression);
|
|
490
|
+
return when;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private parseThen(): Then | null {
|
|
494
|
+
if(!this.token.isThen()) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
this.setNextToken();
|
|
498
|
+
const then = new Then();
|
|
499
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
500
|
+
const expression = this.parseExpression();
|
|
501
|
+
if(expression === null) {
|
|
502
|
+
throw new Error('Expected expression');
|
|
503
|
+
}
|
|
504
|
+
then.addChild(expression);
|
|
505
|
+
return then;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private parseElse(): Else | null {
|
|
509
|
+
if(!this.token.isElse()) {
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
this.setNextToken();
|
|
513
|
+
const _else = new Else();
|
|
514
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
515
|
+
const expression = this.parseExpression();
|
|
516
|
+
if(expression === null) {
|
|
517
|
+
throw new Error('Expected expression');
|
|
518
|
+
}
|
|
519
|
+
_else.addChild(expression);
|
|
520
|
+
return _else;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private parseAlias(): Alias | null {
|
|
524
|
+
this.skipWhitespaceAndComments();
|
|
525
|
+
if(!this.token.isAs()) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
this.expectPreviousTokenToBeWhitespaceOrComment();
|
|
529
|
+
this.setNextToken();
|
|
530
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
531
|
+
if((!this.token.isIdentifier() && !this.token.isKeyword()) || this.token.value === null) {
|
|
532
|
+
throw new Error('Expected identifier');
|
|
533
|
+
}
|
|
534
|
+
const alias = new Alias(this.token.value || '');
|
|
535
|
+
this.setNextToken();
|
|
536
|
+
return alias;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private parseFunction(): Function | null {
|
|
540
|
+
if(!this.token.isIdentifier()) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
if(this.token.value === null) {
|
|
544
|
+
throw new Error('Expected identifier');
|
|
545
|
+
}
|
|
546
|
+
if(!this.peek()?.isLeftParenthesis()) {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
const func = FunctionFactory.create(this.token.value);
|
|
550
|
+
if(func instanceof AggregateFunction && this.context.containsType(AggregateFunction)) {
|
|
551
|
+
throw new Error('Aggregate functions cannot be nested');
|
|
552
|
+
}
|
|
553
|
+
this.context.push(func);
|
|
554
|
+
this.setNextToken();
|
|
555
|
+
this.setNextToken();
|
|
556
|
+
this.skipWhitespaceAndComments();
|
|
557
|
+
if(this.token.isDistinct()) {
|
|
558
|
+
func.distinct = true;
|
|
559
|
+
this.setNextToken();
|
|
560
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
561
|
+
}
|
|
562
|
+
func.parameters = Array.from(this.parseExpressions(AliasOption.NOT_ALLOWED));
|
|
563
|
+
this.skipWhitespaceAndComments();
|
|
564
|
+
if(!this.token.isRightParenthesis()) {
|
|
565
|
+
throw new Error('Expected right parenthesis');
|
|
566
|
+
}
|
|
567
|
+
this.setNextToken();
|
|
568
|
+
this.context.pop();
|
|
569
|
+
return func;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private parsePredicateFunction(): PredicateFunction | null {
|
|
573
|
+
if(!this.ahead([Token.IDENTIFIER(""), Token.LEFT_PARENTHESIS, Token.IDENTIFIER(""), Token.IN])) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
if(this.token.value === null) {
|
|
577
|
+
throw new Error('Expected identifier');
|
|
578
|
+
}
|
|
579
|
+
const func = PredicateFunctionFactory.create(this.token.value);
|
|
580
|
+
this.setNextToken();
|
|
581
|
+
if(!this.token.isLeftParenthesis()) {
|
|
582
|
+
throw new Error('Expected left parenthesis');
|
|
583
|
+
}
|
|
584
|
+
this.setNextToken();
|
|
585
|
+
this.skipWhitespaceAndComments();
|
|
586
|
+
if(!this.token.isIdentifier()) {
|
|
587
|
+
throw new Error('Expected identifier');
|
|
588
|
+
}
|
|
589
|
+
const reference = new Reference(this.token.value);
|
|
590
|
+
this.variables.set(reference.identifier, reference);
|
|
591
|
+
func.addChild(reference);
|
|
592
|
+
this.setNextToken();
|
|
593
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
594
|
+
if(!this.token.isIn()) {
|
|
595
|
+
throw new Error('Expected IN');
|
|
596
|
+
}
|
|
597
|
+
this.setNextToken();
|
|
598
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
599
|
+
const expression = this.parseExpression();
|
|
600
|
+
if(expression === null) {
|
|
601
|
+
throw new Error('Expected expression');
|
|
602
|
+
}
|
|
603
|
+
if(!ObjectUtils.isInstanceOfAny(expression.firstChild(), [JSONArray, Reference, Lookup, Function])) {
|
|
604
|
+
throw new Error('Expected array or reference');
|
|
605
|
+
}
|
|
606
|
+
func.addChild(expression);
|
|
607
|
+
this.skipWhitespaceAndComments();
|
|
608
|
+
if(!this.token.isPipe()) {
|
|
609
|
+
throw new Error('Expected pipe');
|
|
610
|
+
}
|
|
611
|
+
this.setNextToken();
|
|
612
|
+
const _return = this.parseExpression();
|
|
613
|
+
if(_return === null) {
|
|
614
|
+
throw new Error('Expected expression');
|
|
615
|
+
}
|
|
616
|
+
func.addChild(_return);
|
|
617
|
+
const where = this.parseWhere();
|
|
618
|
+
if(where !== null) {
|
|
619
|
+
func.addChild(where);
|
|
620
|
+
}
|
|
621
|
+
this.skipWhitespaceAndComments();
|
|
622
|
+
if(!this.token.isRightParenthesis()) {
|
|
623
|
+
throw new Error('Expected right parenthesis');
|
|
624
|
+
}
|
|
625
|
+
this.setNextToken();
|
|
626
|
+
this.variables.delete(reference.identifier);
|
|
627
|
+
return func;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private parseFString(): FString | null {
|
|
631
|
+
if(!this.token.isFString()) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
const f_string = new FString();
|
|
635
|
+
while(this.token.isFString()) {
|
|
636
|
+
if(this.token.value !== null) {
|
|
637
|
+
f_string.addChild(new String(this.token.value));
|
|
638
|
+
}
|
|
639
|
+
this.setNextToken();
|
|
640
|
+
if(this.token.isOpeningBrace()) {
|
|
641
|
+
this.setNextToken();
|
|
642
|
+
const expression = this.parseExpression();
|
|
643
|
+
if(expression === null) {
|
|
644
|
+
throw new Error('Expected expression');
|
|
645
|
+
}
|
|
646
|
+
f_string.addChild(expression);
|
|
647
|
+
if(!this.token.isClosingBrace()) {
|
|
648
|
+
throw new Error('Expected closing brace');
|
|
649
|
+
}
|
|
650
|
+
this.setNextToken();
|
|
651
|
+
} else {
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return f_string;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
private parseJSON(): AssociativeArray | JSONArray {
|
|
659
|
+
if(this.token.isOpeningBrace()) {
|
|
660
|
+
const array = this.parseAssociativeArray();
|
|
661
|
+
if(array === null) {
|
|
662
|
+
throw new Error('Expected associative array');
|
|
663
|
+
}
|
|
664
|
+
return array;
|
|
665
|
+
} else if(this.token.isOpeningBracket()) {
|
|
666
|
+
const array = this.parseJSONArray();
|
|
667
|
+
if(array === null) {
|
|
668
|
+
throw new Error('Expected JSON array');
|
|
669
|
+
}
|
|
670
|
+
return array;
|
|
671
|
+
}
|
|
672
|
+
throw new Error('Expected opening brace or bracket');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
private parseAssociativeArray(): AssociativeArray | null {
|
|
676
|
+
if(!this.token.isOpeningBrace()) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
const array = new AssociativeArray();
|
|
680
|
+
this.setNextToken();
|
|
681
|
+
while(true) {
|
|
682
|
+
this.skipWhitespaceAndComments();
|
|
683
|
+
if(this.token.isClosingBrace()) {
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
if(!this.token.isIdentifier() && !this.token.isKeyword()) {
|
|
687
|
+
throw new Error('Expected identifier');
|
|
688
|
+
}
|
|
689
|
+
const key = this.token.value;
|
|
690
|
+
if(key === null) {
|
|
691
|
+
throw new Error('Expected string');
|
|
692
|
+
}
|
|
693
|
+
this.setNextToken();
|
|
694
|
+
this.skipWhitespaceAndComments();
|
|
695
|
+
if(!this.token.isColon()) {
|
|
696
|
+
throw new Error('Expected colon');
|
|
697
|
+
}
|
|
698
|
+
this.setNextToken();
|
|
699
|
+
this.skipWhitespaceAndComments();
|
|
700
|
+
const value = this.parseExpression();
|
|
701
|
+
if(value === null) {
|
|
702
|
+
throw new Error('Expected expression');
|
|
703
|
+
}
|
|
704
|
+
array.addKeyValue(new KeyValuePair(key, value));
|
|
705
|
+
this.skipWhitespaceAndComments();
|
|
706
|
+
if(this.token.isComma()) {
|
|
707
|
+
this.setNextToken();
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
this.setNextToken();
|
|
711
|
+
return array;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
private parseJSONArray(): JSONArray | null {
|
|
715
|
+
if(!this.token.isOpeningBracket()) {
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
const array = new JSONArray();
|
|
719
|
+
this.setNextToken();
|
|
720
|
+
while(true) {
|
|
721
|
+
this.skipWhitespaceAndComments();
|
|
722
|
+
if(this.token.isClosingBracket()) {
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
const value = this.parseExpression();
|
|
726
|
+
if(value === null) {
|
|
727
|
+
throw new Error('Expected expression');
|
|
728
|
+
}
|
|
729
|
+
array.addValue(value);
|
|
730
|
+
this.skipWhitespaceAndComments();
|
|
731
|
+
if(this.token.isComma()) {
|
|
732
|
+
this.setNextToken();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
this.setNextToken();
|
|
736
|
+
return array;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
private expectAndSkipWhitespaceAndComments(): void {
|
|
740
|
+
const skipped = this.skipWhitespaceAndComments();
|
|
741
|
+
if(!skipped) {
|
|
742
|
+
throw new Error('Expected whitespace or comment');
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private skipWhitespaceAndComments(): boolean {
|
|
747
|
+
let skipped: boolean = this.previousToken.isWhitespaceOrComment();
|
|
748
|
+
while(this.token.isWhitespace() || this.token.isComment()) {
|
|
749
|
+
this.setNextToken();
|
|
750
|
+
skipped = true;
|
|
751
|
+
}
|
|
752
|
+
return skipped;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
private expectPreviousTokenToBeWhitespaceOrComment(): void {
|
|
756
|
+
if(!this.previousToken.isWhitespaceOrComment()) {
|
|
757
|
+
throw new Error('Expected whitespace or comment');
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export default Parser;
|