@barishnamazov/gsql 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +203 -0
- package/dist/cli.js +174 -0
- package/dist/index.js +33 -0
- package/package.json +50 -0
- package/src/cli.ts +206 -0
- package/src/compiler.ts +73 -0
- package/src/generator.ts +547 -0
- package/src/index.ts +11 -0
- package/src/lexer.ts +636 -0
- package/src/parser.ts +1241 -0
- package/src/types.ts +165 -0
package/src/parser.ts
ADDED
|
@@ -0,0 +1,1241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSQL Parser
|
|
3
|
+
*
|
|
4
|
+
* Builds an Abstract Syntax Tree from GSQL source code using Chevrotain.
|
|
5
|
+
* The parser is designed to be elegant and easy to extend.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CstParser } from "chevrotain";
|
|
9
|
+
import type { CstNode } from "chevrotain";
|
|
10
|
+
import {
|
|
11
|
+
allTokens,
|
|
12
|
+
tokenize,
|
|
13
|
+
// Keywords
|
|
14
|
+
Concept,
|
|
15
|
+
Schema,
|
|
16
|
+
Mixin,
|
|
17
|
+
Enum,
|
|
18
|
+
Extension,
|
|
19
|
+
Func,
|
|
20
|
+
Trigger,
|
|
21
|
+
Index,
|
|
22
|
+
Check,
|
|
23
|
+
Before,
|
|
24
|
+
After,
|
|
25
|
+
On,
|
|
26
|
+
Each,
|
|
27
|
+
Row,
|
|
28
|
+
Statement,
|
|
29
|
+
Execute,
|
|
30
|
+
Function,
|
|
31
|
+
Unique,
|
|
32
|
+
Gin,
|
|
33
|
+
Gist,
|
|
34
|
+
Btree,
|
|
35
|
+
Hash,
|
|
36
|
+
Pkey,
|
|
37
|
+
Nonull,
|
|
38
|
+
Default,
|
|
39
|
+
Ref,
|
|
40
|
+
Ondelete,
|
|
41
|
+
Cascade,
|
|
42
|
+
Restrict,
|
|
43
|
+
SetNull,
|
|
44
|
+
SetDefault,
|
|
45
|
+
NoAction,
|
|
46
|
+
Update,
|
|
47
|
+
Insert,
|
|
48
|
+
Delete,
|
|
49
|
+
Return,
|
|
50
|
+
New,
|
|
51
|
+
Old,
|
|
52
|
+
// Data types
|
|
53
|
+
Serial,
|
|
54
|
+
BigSerial,
|
|
55
|
+
Integer,
|
|
56
|
+
Bigint,
|
|
57
|
+
SmallInt,
|
|
58
|
+
Text,
|
|
59
|
+
Varchar,
|
|
60
|
+
Char,
|
|
61
|
+
Boolean,
|
|
62
|
+
Timestamptz,
|
|
63
|
+
Timestamp,
|
|
64
|
+
Date,
|
|
65
|
+
Time,
|
|
66
|
+
Jsonb,
|
|
67
|
+
Json,
|
|
68
|
+
Uuid,
|
|
69
|
+
Inet,
|
|
70
|
+
Citext,
|
|
71
|
+
Decimal,
|
|
72
|
+
Numeric,
|
|
73
|
+
Real,
|
|
74
|
+
DoublePrecision,
|
|
75
|
+
Bytea,
|
|
76
|
+
// Literals
|
|
77
|
+
StringLiteral,
|
|
78
|
+
NumberLiteral,
|
|
79
|
+
BooleanLiteral,
|
|
80
|
+
NullLiteral,
|
|
81
|
+
// Identifiers
|
|
82
|
+
TemplateIdentifier,
|
|
83
|
+
Identifier,
|
|
84
|
+
// Punctuation
|
|
85
|
+
Arrow,
|
|
86
|
+
DoubleColon,
|
|
87
|
+
LBrace,
|
|
88
|
+
RBrace,
|
|
89
|
+
LParen,
|
|
90
|
+
RParen,
|
|
91
|
+
LBracket,
|
|
92
|
+
RBracket,
|
|
93
|
+
LAngle,
|
|
94
|
+
RAngle,
|
|
95
|
+
Semicolon,
|
|
96
|
+
Comma,
|
|
97
|
+
Dot,
|
|
98
|
+
Equals,
|
|
99
|
+
} from "./lexer.ts";
|
|
100
|
+
import type {
|
|
101
|
+
GSQLProgram,
|
|
102
|
+
TopLevelDeclaration,
|
|
103
|
+
ExtensionDecl,
|
|
104
|
+
FunctionDecl,
|
|
105
|
+
SchemaDecl,
|
|
106
|
+
ConceptDecl,
|
|
107
|
+
EnumDecl,
|
|
108
|
+
Instantiation,
|
|
109
|
+
PerInstanceIndex,
|
|
110
|
+
SchemaBodyItem,
|
|
111
|
+
ColumnDef,
|
|
112
|
+
IndexDef,
|
|
113
|
+
CheckDef,
|
|
114
|
+
TriggerDef,
|
|
115
|
+
ColumnConstraint,
|
|
116
|
+
CompileError,
|
|
117
|
+
InstantiationTarget,
|
|
118
|
+
TypeArg,
|
|
119
|
+
} from "./types.ts";
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// CST Parser Definition
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
class GSQLCstParser extends CstParser {
|
|
126
|
+
constructor() {
|
|
127
|
+
super(allTokens, {
|
|
128
|
+
recoveryEnabled: true,
|
|
129
|
+
maxLookahead: 3,
|
|
130
|
+
});
|
|
131
|
+
this.performSelfAnalysis();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Helper rule: any token that can act as an identifier
|
|
135
|
+
// This allows keywords to be used as identifiers when contextually appropriate
|
|
136
|
+
// (e.g., enum values, column names, function names)
|
|
137
|
+
private anyIdentifier = this.RULE("anyIdentifier", () => {
|
|
138
|
+
this.OR([
|
|
139
|
+
// Regular identifier
|
|
140
|
+
{ ALT: () => this.CONSUME(Identifier) },
|
|
141
|
+
// Declaration keywords
|
|
142
|
+
{ ALT: () => this.CONSUME(Concept) },
|
|
143
|
+
{ ALT: () => this.CONSUME(Schema) },
|
|
144
|
+
{ ALT: () => this.CONSUME(Mixin) },
|
|
145
|
+
{ ALT: () => this.CONSUME(Enum) },
|
|
146
|
+
{ ALT: () => this.CONSUME(Extension) },
|
|
147
|
+
{ ALT: () => this.CONSUME(Func) },
|
|
148
|
+
{ ALT: () => this.CONSUME(Trigger) },
|
|
149
|
+
// Statement keywords
|
|
150
|
+
{ ALT: () => this.CONSUME(Index) },
|
|
151
|
+
{ ALT: () => this.CONSUME(Check) },
|
|
152
|
+
{ ALT: () => this.CONSUME(Before) },
|
|
153
|
+
{ ALT: () => this.CONSUME(After) },
|
|
154
|
+
{ ALT: () => this.CONSUME(On) },
|
|
155
|
+
{ ALT: () => this.CONSUME(Each) },
|
|
156
|
+
{ ALT: () => this.CONSUME(Row) },
|
|
157
|
+
{ ALT: () => this.CONSUME(Statement) },
|
|
158
|
+
{ ALT: () => this.CONSUME(Execute) },
|
|
159
|
+
{ ALT: () => this.CONSUME(Function) },
|
|
160
|
+
{ ALT: () => this.CONSUME(Return) },
|
|
161
|
+
{ ALT: () => this.CONSUME(New) },
|
|
162
|
+
{ ALT: () => this.CONSUME(Old) },
|
|
163
|
+
// Operation keywords
|
|
164
|
+
{ ALT: () => this.CONSUME(Update) },
|
|
165
|
+
{ ALT: () => this.CONSUME(Insert) },
|
|
166
|
+
{ ALT: () => this.CONSUME(Delete) },
|
|
167
|
+
// Constraint keywords
|
|
168
|
+
{ ALT: () => this.CONSUME(Unique) },
|
|
169
|
+
{ ALT: () => this.CONSUME(Pkey) },
|
|
170
|
+
{ ALT: () => this.CONSUME(Nonull) },
|
|
171
|
+
{ ALT: () => this.CONSUME(Default) },
|
|
172
|
+
{ ALT: () => this.CONSUME(Ref) },
|
|
173
|
+
{ ALT: () => this.CONSUME(Ondelete) },
|
|
174
|
+
{ ALT: () => this.CONSUME(Cascade) },
|
|
175
|
+
{ ALT: () => this.CONSUME(Restrict) },
|
|
176
|
+
{ ALT: () => this.CONSUME(SetNull) },
|
|
177
|
+
{ ALT: () => this.CONSUME(SetDefault) },
|
|
178
|
+
{ ALT: () => this.CONSUME(NoAction) },
|
|
179
|
+
// Index types
|
|
180
|
+
{ ALT: () => this.CONSUME(Gin) },
|
|
181
|
+
{ ALT: () => this.CONSUME(Gist) },
|
|
182
|
+
{ ALT: () => this.CONSUME(Btree) },
|
|
183
|
+
{ ALT: () => this.CONSUME(Hash) },
|
|
184
|
+
// Data types
|
|
185
|
+
{ ALT: () => this.CONSUME(Serial) },
|
|
186
|
+
{ ALT: () => this.CONSUME(BigSerial) },
|
|
187
|
+
{ ALT: () => this.CONSUME(Integer) },
|
|
188
|
+
{ ALT: () => this.CONSUME(Bigint) },
|
|
189
|
+
{ ALT: () => this.CONSUME(SmallInt) },
|
|
190
|
+
{ ALT: () => this.CONSUME(Text) },
|
|
191
|
+
{ ALT: () => this.CONSUME(Varchar) },
|
|
192
|
+
{ ALT: () => this.CONSUME(Char) },
|
|
193
|
+
{ ALT: () => this.CONSUME(Boolean) },
|
|
194
|
+
{ ALT: () => this.CONSUME(Timestamptz) },
|
|
195
|
+
{ ALT: () => this.CONSUME(Timestamp) },
|
|
196
|
+
{ ALT: () => this.CONSUME(Date) },
|
|
197
|
+
{ ALT: () => this.CONSUME(Time) },
|
|
198
|
+
{ ALT: () => this.CONSUME(Jsonb) },
|
|
199
|
+
{ ALT: () => this.CONSUME(Json) },
|
|
200
|
+
{ ALT: () => this.CONSUME(Uuid) },
|
|
201
|
+
{ ALT: () => this.CONSUME(Inet) },
|
|
202
|
+
{ ALT: () => this.CONSUME(Citext) },
|
|
203
|
+
{ ALT: () => this.CONSUME(Decimal) },
|
|
204
|
+
{ ALT: () => this.CONSUME(Numeric) },
|
|
205
|
+
{ ALT: () => this.CONSUME(Real) },
|
|
206
|
+
{ ALT: () => this.CONSUME(DoublePrecision) },
|
|
207
|
+
{ ALT: () => this.CONSUME(Bytea) },
|
|
208
|
+
]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Top-level program
|
|
212
|
+
public program = this.RULE("program", () => {
|
|
213
|
+
this.MANY(() => {
|
|
214
|
+
this.SUBRULE(this.topLevelDeclaration);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Top-level declarations
|
|
219
|
+
private topLevelDeclaration = this.RULE("topLevelDeclaration", () => {
|
|
220
|
+
this.OR([
|
|
221
|
+
{ ALT: () => this.SUBRULE(this.extensionDecl) },
|
|
222
|
+
{ ALT: () => this.SUBRULE(this.functionDecl) },
|
|
223
|
+
{ ALT: () => this.SUBRULE(this.conceptDecl) },
|
|
224
|
+
{ ALT: () => this.SUBRULE(this.enumDecl) },
|
|
225
|
+
{ ALT: () => this.SUBRULE(this.schemaDecl) },
|
|
226
|
+
{ ALT: () => this.SUBRULE(this.instantiation) },
|
|
227
|
+
{ ALT: () => this.SUBRULE(this.perInstanceIndex) },
|
|
228
|
+
]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// extension citext;
|
|
232
|
+
private extensionDecl = this.RULE("extensionDecl", () => {
|
|
233
|
+
this.CONSUME(Extension);
|
|
234
|
+
this.SUBRULE(this.anyIdentifier);
|
|
235
|
+
this.CONSUME(Semicolon);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// func name() -> trigger { ... }
|
|
239
|
+
private functionDecl = this.RULE("functionDecl", () => {
|
|
240
|
+
this.CONSUME(Func);
|
|
241
|
+
this.SUBRULE(this.anyIdentifier);
|
|
242
|
+
this.CONSUME(LParen);
|
|
243
|
+
this.CONSUME(RParen);
|
|
244
|
+
this.CONSUME(Arrow);
|
|
245
|
+
this.SUBRULE2(this.anyIdentifier); // return type
|
|
246
|
+
this.SUBRULE(this.functionBody);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
private functionBody = this.RULE("functionBody", () => {
|
|
250
|
+
this.CONSUME(LBrace);
|
|
251
|
+
this.MANY(() => {
|
|
252
|
+
this.SUBRULE(this.functionStatement);
|
|
253
|
+
});
|
|
254
|
+
this.CONSUME(RBrace);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
private functionStatement = this.RULE("functionStatement", () => {
|
|
258
|
+
this.OR([
|
|
259
|
+
{ ALT: () => this.SUBRULE(this.returnStatement) },
|
|
260
|
+
{ ALT: () => this.SUBRULE(this.assignmentStatement) },
|
|
261
|
+
]);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
private returnStatement = this.RULE("returnStatement", () => {
|
|
265
|
+
this.CONSUME(Return);
|
|
266
|
+
this.OR([
|
|
267
|
+
{ ALT: () => this.CONSUME(New) },
|
|
268
|
+
{ ALT: () => this.CONSUME(Old) },
|
|
269
|
+
{ ALT: () => this.CONSUME(Identifier) },
|
|
270
|
+
]);
|
|
271
|
+
this.CONSUME(Semicolon);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
private assignmentStatement = this.RULE("assignmentStatement", () => {
|
|
275
|
+
this.OR([
|
|
276
|
+
{ ALT: () => this.CONSUME(New) },
|
|
277
|
+
{ ALT: () => this.CONSUME(Old) },
|
|
278
|
+
{ ALT: () => this.CONSUME(Identifier) },
|
|
279
|
+
]);
|
|
280
|
+
this.CONSUME(Dot);
|
|
281
|
+
this.SUBRULE(this.anyIdentifier); // field name
|
|
282
|
+
this.CONSUME(Equals);
|
|
283
|
+
this.SUBRULE(this.functionCallExpr);
|
|
284
|
+
this.CONSUME(Semicolon);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
private functionCallExpr = this.RULE("functionCallExpr", () => {
|
|
288
|
+
this.CONSUME(Identifier);
|
|
289
|
+
this.CONSUME(LParen);
|
|
290
|
+
this.CONSUME(RParen);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// concept Name<TypeParams> { ... }
|
|
294
|
+
private conceptDecl = this.RULE("conceptDecl", () => {
|
|
295
|
+
this.CONSUME(Concept);
|
|
296
|
+
this.CONSUME(Identifier);
|
|
297
|
+
this.OPTION(() => {
|
|
298
|
+
this.SUBRULE(this.typeParamList);
|
|
299
|
+
});
|
|
300
|
+
this.CONSUME(LBrace);
|
|
301
|
+
this.MANY(() => {
|
|
302
|
+
this.OR([
|
|
303
|
+
{ ALT: () => this.SUBRULE(this.enumDecl) },
|
|
304
|
+
{ ALT: () => this.SUBRULE(this.schemaDecl) },
|
|
305
|
+
]);
|
|
306
|
+
});
|
|
307
|
+
this.CONSUME(RBrace);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
private typeParamList = this.RULE("typeParamList", () => {
|
|
311
|
+
this.CONSUME(LAngle);
|
|
312
|
+
this.CONSUME(Identifier);
|
|
313
|
+
this.MANY(() => {
|
|
314
|
+
this.CONSUME(Comma);
|
|
315
|
+
this.CONSUME2(Identifier);
|
|
316
|
+
});
|
|
317
|
+
this.CONSUME(RAngle);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// enum name { value1; value2; ... }
|
|
321
|
+
private enumDecl = this.RULE("enumDecl", () => {
|
|
322
|
+
this.CONSUME(Enum);
|
|
323
|
+
this.SUBRULE(this.anyIdentifier);
|
|
324
|
+
this.CONSUME(LBrace);
|
|
325
|
+
this.MANY(() => {
|
|
326
|
+
this.SUBRULE2(this.anyIdentifier);
|
|
327
|
+
this.CONSUME(Semicolon);
|
|
328
|
+
});
|
|
329
|
+
this.CONSUME(RBrace);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// schema Name mixin M1, M2 { ... }
|
|
333
|
+
private schemaDecl = this.RULE("schemaDecl", () => {
|
|
334
|
+
this.CONSUME(Schema);
|
|
335
|
+
this.CONSUME(Identifier);
|
|
336
|
+
this.OPTION(() => {
|
|
337
|
+
this.SUBRULE(this.mixinList);
|
|
338
|
+
});
|
|
339
|
+
this.CONSUME(LBrace);
|
|
340
|
+
this.MANY(() => {
|
|
341
|
+
this.SUBRULE(this.schemaBodyItem);
|
|
342
|
+
});
|
|
343
|
+
this.CONSUME(RBrace);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
private mixinList = this.RULE("mixinList", () => {
|
|
347
|
+
this.CONSUME(Mixin);
|
|
348
|
+
this.CONSUME(Identifier);
|
|
349
|
+
this.MANY(() => {
|
|
350
|
+
this.CONSUME(Comma);
|
|
351
|
+
this.CONSUME2(Identifier);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
private schemaBodyItem = this.RULE("schemaBodyItem", () => {
|
|
356
|
+
this.OR([
|
|
357
|
+
{ ALT: () => this.SUBRULE(this.indexDef) },
|
|
358
|
+
{ ALT: () => this.SUBRULE(this.checkDef) },
|
|
359
|
+
{ ALT: () => this.SUBRULE(this.triggerDef) },
|
|
360
|
+
{ ALT: () => this.SUBRULE(this.columnDef) },
|
|
361
|
+
]);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Column definition: name type constraints;
|
|
365
|
+
private columnDef = this.RULE("columnDef", () => {
|
|
366
|
+
this.SUBRULE(this.columnName);
|
|
367
|
+
this.SUBRULE(this.dataType);
|
|
368
|
+
this.MANY(() => {
|
|
369
|
+
this.SUBRULE(this.columnConstraint);
|
|
370
|
+
});
|
|
371
|
+
this.CONSUME(Semicolon);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
private columnName = this.RULE("columnName", () => {
|
|
375
|
+
this.OR([
|
|
376
|
+
{
|
|
377
|
+
ALT: () => {
|
|
378
|
+
this.CONSUME(TemplateIdentifier);
|
|
379
|
+
this.OPTION(() => {
|
|
380
|
+
this.CONSUME(Identifier); // suffix after template, e.g., {target}_id
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
{ ALT: () => this.CONSUME2(Identifier) },
|
|
385
|
+
]);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
private dataType = this.RULE("dataType", () => {
|
|
389
|
+
this.OR([
|
|
390
|
+
{ ALT: () => this.CONSUME(Serial) },
|
|
391
|
+
{ ALT: () => this.CONSUME(BigSerial) },
|
|
392
|
+
{ ALT: () => this.CONSUME(Integer) },
|
|
393
|
+
{ ALT: () => this.CONSUME(Bigint) },
|
|
394
|
+
{ ALT: () => this.CONSUME(SmallInt) },
|
|
395
|
+
{ ALT: () => this.CONSUME(Text) },
|
|
396
|
+
{ ALT: () => this.CONSUME(Varchar) },
|
|
397
|
+
{ ALT: () => this.CONSUME(Char) },
|
|
398
|
+
{ ALT: () => this.CONSUME(Boolean) },
|
|
399
|
+
{ ALT: () => this.CONSUME(Timestamptz) },
|
|
400
|
+
{ ALT: () => this.CONSUME(Timestamp) },
|
|
401
|
+
{ ALT: () => this.CONSUME(Date) },
|
|
402
|
+
{ ALT: () => this.CONSUME(Time) },
|
|
403
|
+
{ ALT: () => this.CONSUME(Jsonb) },
|
|
404
|
+
{ ALT: () => this.CONSUME(Json) },
|
|
405
|
+
{ ALT: () => this.CONSUME(Uuid) },
|
|
406
|
+
{ ALT: () => this.CONSUME(Inet) },
|
|
407
|
+
{ ALT: () => this.CONSUME(Citext) },
|
|
408
|
+
{ ALT: () => this.CONSUME(Decimal) },
|
|
409
|
+
{ ALT: () => this.CONSUME(Numeric) },
|
|
410
|
+
{ ALT: () => this.CONSUME(Real) },
|
|
411
|
+
{ ALT: () => this.CONSUME(DoublePrecision) },
|
|
412
|
+
{ ALT: () => this.CONSUME(Bytea) },
|
|
413
|
+
{ ALT: () => this.CONSUME(Identifier) }, // custom types like enums
|
|
414
|
+
]);
|
|
415
|
+
this.OPTION(() => {
|
|
416
|
+
this.CONSUME(LParen);
|
|
417
|
+
this.CONSUME(NumberLiteral);
|
|
418
|
+
this.CONSUME(RParen);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
private columnConstraint = this.RULE("columnConstraint", () => {
|
|
423
|
+
this.OR([
|
|
424
|
+
{ ALT: () => this.CONSUME(Pkey) },
|
|
425
|
+
{ ALT: () => this.CONSUME(Nonull) },
|
|
426
|
+
{ ALT: () => this.CONSUME(Unique) },
|
|
427
|
+
{ ALT: () => this.SUBRULE(this.defaultConstraint) },
|
|
428
|
+
{ ALT: () => this.SUBRULE(this.refConstraint) },
|
|
429
|
+
{ ALT: () => this.SUBRULE(this.checkConstraint) },
|
|
430
|
+
{ ALT: () => this.SUBRULE(this.onDeleteConstraint) },
|
|
431
|
+
]);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
private defaultConstraint = this.RULE("defaultConstraint", () => {
|
|
435
|
+
this.CONSUME(Default);
|
|
436
|
+
this.CONSUME(LParen);
|
|
437
|
+
this.SUBRULE(this.defaultValue);
|
|
438
|
+
this.CONSUME(RParen);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
private defaultValue = this.RULE("defaultValue", () => {
|
|
442
|
+
this.OR([
|
|
443
|
+
{ ALT: () => this.CONSUME(StringLiteral) },
|
|
444
|
+
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
445
|
+
{ ALT: () => this.CONSUME(BooleanLiteral) },
|
|
446
|
+
{ ALT: () => this.CONSUME(NullLiteral) },
|
|
447
|
+
{ ALT: () => this.SUBRULE(this.functionCallExpr) },
|
|
448
|
+
{
|
|
449
|
+
ALT: () => {
|
|
450
|
+
// Qualified enum value: enum_type::value
|
|
451
|
+
this.SUBRULE(this.anyIdentifier);
|
|
452
|
+
this.CONSUME(DoubleColon);
|
|
453
|
+
this.SUBRULE2(this.anyIdentifier);
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
{ ALT: () => this.SUBRULE3(this.anyIdentifier) }, // enum value or other keyword
|
|
457
|
+
]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
private refConstraint = this.RULE("refConstraint", () => {
|
|
461
|
+
this.CONSUME(Ref);
|
|
462
|
+
this.CONSUME(LParen);
|
|
463
|
+
this.CONSUME(Identifier); // table
|
|
464
|
+
this.CONSUME(Dot);
|
|
465
|
+
this.CONSUME2(Identifier); // column
|
|
466
|
+
this.CONSUME(RParen);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
private checkConstraint = this.RULE("checkConstraint", () => {
|
|
470
|
+
this.CONSUME(Check);
|
|
471
|
+
this.CONSUME(LParen);
|
|
472
|
+
this.SUBRULE(this.checkExpression);
|
|
473
|
+
this.CONSUME(RParen);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
private checkExpression = this.RULE("checkExpression", () => {
|
|
477
|
+
// Consume balanced parentheses with any content
|
|
478
|
+
this.MANY(() => {
|
|
479
|
+
this.OR([
|
|
480
|
+
{
|
|
481
|
+
ALT: () => {
|
|
482
|
+
this.CONSUME(LParen);
|
|
483
|
+
this.SUBRULE(this.checkExpression);
|
|
484
|
+
this.CONSUME(RParen);
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
{ ALT: () => this.CONSUME(Identifier) },
|
|
488
|
+
{ ALT: () => this.CONSUME(TemplateIdentifier) },
|
|
489
|
+
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
490
|
+
{ ALT: () => this.CONSUME(StringLiteral) },
|
|
491
|
+
{ ALT: () => this.CONSUME(BooleanLiteral) },
|
|
492
|
+
{ ALT: () => this.CONSUME(NullLiteral) },
|
|
493
|
+
{ ALT: () => this.CONSUME(DoubleColon) },
|
|
494
|
+
{ ALT: () => this.CONSUME(Equals) },
|
|
495
|
+
{ ALT: () => this.CONSUME(Comma) },
|
|
496
|
+
{ ALT: () => this.CONSUME(Dot) },
|
|
497
|
+
{ ALT: () => this.CONSUME(LAngle) },
|
|
498
|
+
{ ALT: () => this.CONSUME(RAngle) },
|
|
499
|
+
]);
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
private onDeleteConstraint = this.RULE("onDeleteConstraint", () => {
|
|
504
|
+
this.CONSUME(Ondelete);
|
|
505
|
+
this.CONSUME(LParen);
|
|
506
|
+
this.OR([
|
|
507
|
+
{ ALT: () => this.CONSUME(Cascade) },
|
|
508
|
+
{ ALT: () => this.CONSUME(Restrict) },
|
|
509
|
+
{ ALT: () => this.CONSUME(SetNull) },
|
|
510
|
+
{ ALT: () => this.CONSUME(SetDefault) },
|
|
511
|
+
{ ALT: () => this.CONSUME(NoAction) },
|
|
512
|
+
]);
|
|
513
|
+
this.CONSUME(RParen);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// index(col1, col2) unique gin;
|
|
517
|
+
private indexDef = this.RULE("indexDef", () => {
|
|
518
|
+
this.CONSUME(Index);
|
|
519
|
+
this.CONSUME(LParen);
|
|
520
|
+
this.SUBRULE(this.indexColumnList);
|
|
521
|
+
this.CONSUME(RParen);
|
|
522
|
+
this.MANY(() => {
|
|
523
|
+
this.OR([
|
|
524
|
+
{ ALT: () => this.CONSUME(Unique) },
|
|
525
|
+
{ ALT: () => this.CONSUME(Gin) },
|
|
526
|
+
{ ALT: () => this.CONSUME(Gist) },
|
|
527
|
+
{ ALT: () => this.CONSUME(Btree) },
|
|
528
|
+
{ ALT: () => this.CONSUME(Hash) },
|
|
529
|
+
]);
|
|
530
|
+
});
|
|
531
|
+
this.CONSUME(Semicolon);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
private indexColumnList = this.RULE("indexColumnList", () => {
|
|
535
|
+
this.SUBRULE(this.indexColumn);
|
|
536
|
+
this.MANY(() => {
|
|
537
|
+
this.CONSUME(Comma);
|
|
538
|
+
this.SUBRULE2(this.indexColumn);
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
private indexColumn = this.RULE("indexColumn", () => {
|
|
543
|
+
this.OR([
|
|
544
|
+
{ ALT: () => this.CONSUME(TemplateIdentifier) },
|
|
545
|
+
{ ALT: () => this.CONSUME(Identifier) },
|
|
546
|
+
]);
|
|
547
|
+
this.OPTION(() => {
|
|
548
|
+
this.CONSUME2(Identifier); // suffix after template
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// check(expression);
|
|
553
|
+
private checkDef = this.RULE("checkDef", () => {
|
|
554
|
+
this.CONSUME(Check);
|
|
555
|
+
this.CONSUME(LParen);
|
|
556
|
+
this.SUBRULE(this.checkExpression);
|
|
557
|
+
this.CONSUME(RParen);
|
|
558
|
+
this.CONSUME(Semicolon);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// trigger name before update on each row execute function fn();
|
|
562
|
+
private triggerDef = this.RULE("triggerDef", () => {
|
|
563
|
+
this.CONSUME(Trigger);
|
|
564
|
+
this.CONSUME(Identifier); // name
|
|
565
|
+
this.OR([{ ALT: () => this.CONSUME(Before) }, { ALT: () => this.CONSUME(After) }]);
|
|
566
|
+
this.OR2([
|
|
567
|
+
{ ALT: () => this.CONSUME(Update) },
|
|
568
|
+
{ ALT: () => this.CONSUME(Insert) },
|
|
569
|
+
{ ALT: () => this.CONSUME(Delete) },
|
|
570
|
+
]);
|
|
571
|
+
this.CONSUME(On);
|
|
572
|
+
this.CONSUME(Each);
|
|
573
|
+
this.OR3([{ ALT: () => this.CONSUME(Row) }, { ALT: () => this.CONSUME(Statement) }]);
|
|
574
|
+
this.CONSUME(Execute);
|
|
575
|
+
this.CONSUME(Function);
|
|
576
|
+
this.CONSUME2(Identifier); // function name
|
|
577
|
+
this.CONSUME(LParen);
|
|
578
|
+
this.CONSUME(RParen);
|
|
579
|
+
this.CONSUME(Semicolon);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// table_name = ConceptName<Type1, Type2>;
|
|
583
|
+
// Or: table1, table2 = ConceptName<Type>;
|
|
584
|
+
// Or: table1[alias], table2 = ConceptName<Type[alias]>;
|
|
585
|
+
private instantiation = this.RULE("instantiation", () => {
|
|
586
|
+
this.SUBRULE(this.instantiationTargetList);
|
|
587
|
+
this.CONSUME(Equals);
|
|
588
|
+
this.SUBRULE(this.conceptReference);
|
|
589
|
+
this.CONSUME(Semicolon);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
private instantiationTargetList = this.RULE("instantiationTargetList", () => {
|
|
593
|
+
this.SUBRULE(this.instantiationTarget);
|
|
594
|
+
this.MANY(() => {
|
|
595
|
+
this.CONSUME(Comma);
|
|
596
|
+
this.SUBRULE2(this.instantiationTarget);
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
private instantiationTarget = this.RULE("instantiationTarget", () => {
|
|
601
|
+
this.CONSUME(Identifier);
|
|
602
|
+
this.OPTION(() => {
|
|
603
|
+
this.CONSUME(LBracket);
|
|
604
|
+
this.CONSUME2(Identifier);
|
|
605
|
+
this.CONSUME(RBracket);
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
private conceptReference = this.RULE("conceptReference", () => {
|
|
610
|
+
this.CONSUME(Identifier); // concept name
|
|
611
|
+
this.OPTION(() => {
|
|
612
|
+
this.CONSUME(LAngle);
|
|
613
|
+
this.SUBRULE(this.typeArgList);
|
|
614
|
+
this.CONSUME(RAngle);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
private typeArgList = this.RULE("typeArgList", () => {
|
|
619
|
+
this.SUBRULE(this.typeArg);
|
|
620
|
+
this.MANY(() => {
|
|
621
|
+
this.CONSUME(Comma);
|
|
622
|
+
this.SUBRULE2(this.typeArg);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
private typeArg = this.RULE("typeArg", () => {
|
|
627
|
+
this.CONSUME(Identifier);
|
|
628
|
+
this.OPTION(() => {
|
|
629
|
+
this.CONSUME(LBracket);
|
|
630
|
+
this.CONSUME2(Identifier);
|
|
631
|
+
this.CONSUME(RBracket);
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// index(table, column);
|
|
636
|
+
private perInstanceIndex = this.RULE("perInstanceIndex", () => {
|
|
637
|
+
this.CONSUME(Index);
|
|
638
|
+
this.CONSUME(LParen);
|
|
639
|
+
this.CONSUME(Identifier); // table
|
|
640
|
+
this.CONSUME(Comma);
|
|
641
|
+
this.CONSUME2(Identifier); // column
|
|
642
|
+
this.MANY(() => {
|
|
643
|
+
this.CONSUME2(Comma);
|
|
644
|
+
this.CONSUME3(Identifier);
|
|
645
|
+
});
|
|
646
|
+
this.CONSUME(RParen);
|
|
647
|
+
this.MANY2(() => {
|
|
648
|
+
this.OR([
|
|
649
|
+
{ ALT: () => this.CONSUME(Unique) },
|
|
650
|
+
{ ALT: () => this.CONSUME(Gin) },
|
|
651
|
+
{ ALT: () => this.CONSUME(Gist) },
|
|
652
|
+
{ ALT: () => this.CONSUME(Btree) },
|
|
653
|
+
{ ALT: () => this.CONSUME(Hash) },
|
|
654
|
+
]);
|
|
655
|
+
});
|
|
656
|
+
this.CONSUME(Semicolon);
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// ============================================================================
|
|
661
|
+
// CST to AST Visitor
|
|
662
|
+
// ============================================================================
|
|
663
|
+
|
|
664
|
+
const parserInstance = new GSQLCstParser();
|
|
665
|
+
|
|
666
|
+
function extractImage(node: CstNode, tokenType: string): string {
|
|
667
|
+
const tokens = node.children[tokenType];
|
|
668
|
+
if (tokens && tokens.length > 0) {
|
|
669
|
+
const token = tokens[0];
|
|
670
|
+
if (token && "image" in token) {
|
|
671
|
+
return token.image;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return "";
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function extractAllImages(node: CstNode, tokenType: string): string[] {
|
|
678
|
+
const tokens = node.children[tokenType];
|
|
679
|
+
if (!tokens) return [];
|
|
680
|
+
return tokens.map((t) => ("image" in t ? t.image : "")).filter((s) => s !== "");
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function extractChild(node: CstNode, childName: string): CstNode | undefined {
|
|
684
|
+
const children = node.children[childName];
|
|
685
|
+
const firstChild = children?.[0];
|
|
686
|
+
if (firstChild && "children" in firstChild) {
|
|
687
|
+
return firstChild;
|
|
688
|
+
}
|
|
689
|
+
return undefined;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function extractChildren(node: CstNode, childName: string): CstNode[] {
|
|
693
|
+
const children = node.children[childName];
|
|
694
|
+
if (!children) return [];
|
|
695
|
+
return children.filter((c): c is CstNode => "children" in c);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Extract image from anyIdentifier rule (which can match many token types)
|
|
699
|
+
function extractAnyIdentifier(node: CstNode): string {
|
|
700
|
+
const anyIdNodes = extractChildren(node, "anyIdentifier");
|
|
701
|
+
const anyIdNode = anyIdNodes[0];
|
|
702
|
+
if (anyIdNode) {
|
|
703
|
+
// Look through all children of anyIdentifier for a token with an image
|
|
704
|
+
for (const key of Object.keys(anyIdNode.children)) {
|
|
705
|
+
const tokens = anyIdNode.children[key];
|
|
706
|
+
const firstToken = tokens?.[0];
|
|
707
|
+
if (firstToken && "image" in firstToken) {
|
|
708
|
+
return firstToken.image;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return "";
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function extractAllAnyIdentifiers(node: CstNode): string[] {
|
|
716
|
+
const anyIdNodes = extractChildren(node, "anyIdentifier");
|
|
717
|
+
const result: string[] = [];
|
|
718
|
+
for (const anyIdNode of anyIdNodes) {
|
|
719
|
+
for (const key of Object.keys(anyIdNode.children)) {
|
|
720
|
+
const tokens = anyIdNode.children[key];
|
|
721
|
+
const firstToken = tokens?.[0];
|
|
722
|
+
if (firstToken && "image" in firstToken) {
|
|
723
|
+
result.push(firstToken.image);
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return result;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
class CstToAstVisitor {
|
|
732
|
+
visit(cst: CstNode): GSQLProgram {
|
|
733
|
+
const declarations: TopLevelDeclaration[] = [];
|
|
734
|
+
const topLevelDecls = extractChildren(cst, "topLevelDeclaration");
|
|
735
|
+
|
|
736
|
+
for (const decl of topLevelDecls) {
|
|
737
|
+
const parsed = this.visitTopLevelDeclaration(decl);
|
|
738
|
+
if (parsed) {
|
|
739
|
+
declarations.push(parsed);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return {
|
|
744
|
+
type: "Program",
|
|
745
|
+
declarations,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private visitTopLevelDeclaration(node: CstNode): TopLevelDeclaration | null {
|
|
750
|
+
const extension = extractChild(node, "extensionDecl");
|
|
751
|
+
if (extension) return this.visitExtensionDecl(extension);
|
|
752
|
+
|
|
753
|
+
const func = extractChild(node, "functionDecl");
|
|
754
|
+
if (func) return this.visitFunctionDecl(func);
|
|
755
|
+
|
|
756
|
+
const concept = extractChild(node, "conceptDecl");
|
|
757
|
+
if (concept) return this.visitConceptDecl(concept);
|
|
758
|
+
|
|
759
|
+
const enumDecl = extractChild(node, "enumDecl");
|
|
760
|
+
if (enumDecl) return this.visitEnumDecl(enumDecl);
|
|
761
|
+
|
|
762
|
+
const schema = extractChild(node, "schemaDecl");
|
|
763
|
+
if (schema) return this.visitSchemaDecl(schema);
|
|
764
|
+
|
|
765
|
+
const instantiation = extractChild(node, "instantiation");
|
|
766
|
+
if (instantiation) return this.visitInstantiation(instantiation);
|
|
767
|
+
|
|
768
|
+
const perInstanceIndex = extractChild(node, "perInstanceIndex");
|
|
769
|
+
if (perInstanceIndex) return this.visitPerInstanceIndex(perInstanceIndex);
|
|
770
|
+
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
private visitExtensionDecl(node: CstNode): ExtensionDecl {
|
|
775
|
+
return {
|
|
776
|
+
type: "ExtensionDecl",
|
|
777
|
+
name: extractAnyIdentifier(node),
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
private visitFunctionDecl(node: CstNode): FunctionDecl {
|
|
782
|
+
const identifiers = extractAllAnyIdentifiers(node);
|
|
783
|
+
const name = identifiers[0] ?? "";
|
|
784
|
+
const returnType = identifiers[1] ?? "";
|
|
785
|
+
|
|
786
|
+
const bodyNode = extractChild(node, "functionBody");
|
|
787
|
+
let body = "";
|
|
788
|
+
|
|
789
|
+
if (bodyNode) {
|
|
790
|
+
const statements = extractChildren(bodyNode, "functionStatement");
|
|
791
|
+
const parts: string[] = [];
|
|
792
|
+
|
|
793
|
+
for (const stmt of statements) {
|
|
794
|
+
const returnStmt = extractChild(stmt, "returnStatement");
|
|
795
|
+
if (returnStmt) {
|
|
796
|
+
// Could be New, Old, or Identifier token
|
|
797
|
+
let id = extractImage(returnStmt, "Identifier");
|
|
798
|
+
if (!id) id = extractImage(returnStmt, "New");
|
|
799
|
+
if (!id) id = extractImage(returnStmt, "Old");
|
|
800
|
+
parts.push(`RETURN ${id};`);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const assignStmt = extractChild(stmt, "assignmentStatement");
|
|
804
|
+
if (assignStmt) {
|
|
805
|
+
// Object could be New, Old, or Identifier
|
|
806
|
+
let obj = extractImage(assignStmt, "Identifier");
|
|
807
|
+
if (!obj) obj = extractImage(assignStmt, "New");
|
|
808
|
+
if (!obj) obj = extractImage(assignStmt, "Old");
|
|
809
|
+
// Field is from anyIdentifier subrule
|
|
810
|
+
const field = extractAnyIdentifier(assignStmt);
|
|
811
|
+
const funcCallNode = extractChild(assignStmt, "functionCallExpr");
|
|
812
|
+
const funcName = funcCallNode ? extractImage(funcCallNode, "Identifier") : "";
|
|
813
|
+
parts.push(`${obj}.${field} := ${funcName}();`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
body = parts.join("\n ");
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
type: "FunctionDecl",
|
|
821
|
+
name,
|
|
822
|
+
returnType,
|
|
823
|
+
body,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
private visitConceptDecl(node: CstNode): ConceptDecl {
|
|
828
|
+
const name = extractImage(node, "Identifier");
|
|
829
|
+
const typeParams: string[] = [];
|
|
830
|
+
|
|
831
|
+
const typeParamListNode = extractChild(node, "typeParamList");
|
|
832
|
+
if (typeParamListNode) {
|
|
833
|
+
typeParams.push(...extractAllImages(typeParamListNode, "Identifier"));
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const members: (SchemaDecl | EnumDecl)[] = [];
|
|
837
|
+
|
|
838
|
+
const schemas = extractChildren(node, "schemaDecl");
|
|
839
|
+
for (const schema of schemas) {
|
|
840
|
+
const s = this.visitSchemaDecl(schema);
|
|
841
|
+
s.conceptScope = name;
|
|
842
|
+
members.push(s);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const enums = extractChildren(node, "enumDecl");
|
|
846
|
+
for (const e of enums) {
|
|
847
|
+
const en = this.visitEnumDecl(e);
|
|
848
|
+
en.conceptScope = name;
|
|
849
|
+
members.push(en);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return {
|
|
853
|
+
type: "ConceptDecl",
|
|
854
|
+
name,
|
|
855
|
+
typeParams,
|
|
856
|
+
members,
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
private visitEnumDecl(node: CstNode): EnumDecl {
|
|
861
|
+
const identifiers = extractAllAnyIdentifiers(node);
|
|
862
|
+
const name = identifiers[0] ?? "";
|
|
863
|
+
const values = identifiers.slice(1);
|
|
864
|
+
|
|
865
|
+
return {
|
|
866
|
+
type: "EnumDecl",
|
|
867
|
+
name,
|
|
868
|
+
values,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
private visitSchemaDecl(node: CstNode): SchemaDecl {
|
|
873
|
+
const name = extractImage(node, "Identifier");
|
|
874
|
+
const mixins: string[] = [];
|
|
875
|
+
|
|
876
|
+
const mixinListNode = extractChild(node, "mixinList");
|
|
877
|
+
if (mixinListNode) {
|
|
878
|
+
mixins.push(...extractAllImages(mixinListNode, "Identifier"));
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const members: SchemaBodyItem[] = [];
|
|
882
|
+
const bodyItems = extractChildren(node, "schemaBodyItem");
|
|
883
|
+
|
|
884
|
+
for (const item of bodyItems) {
|
|
885
|
+
const parsed = this.visitSchemaBodyItem(item);
|
|
886
|
+
if (parsed) {
|
|
887
|
+
members.push(parsed);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return {
|
|
892
|
+
type: "SchemaDecl",
|
|
893
|
+
name,
|
|
894
|
+
mixins,
|
|
895
|
+
members,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
private visitSchemaBodyItem(node: CstNode): SchemaBodyItem | null {
|
|
900
|
+
const column = extractChild(node, "columnDef");
|
|
901
|
+
if (column) return this.visitColumnDef(column);
|
|
902
|
+
|
|
903
|
+
const index = extractChild(node, "indexDef");
|
|
904
|
+
if (index) return this.visitIndexDef(index);
|
|
905
|
+
|
|
906
|
+
const check = extractChild(node, "checkDef");
|
|
907
|
+
if (check) return this.visitCheckDef(check);
|
|
908
|
+
|
|
909
|
+
const trigger = extractChild(node, "triggerDef");
|
|
910
|
+
if (trigger) return this.visitTriggerDef(trigger);
|
|
911
|
+
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
private visitColumnDef(node: CstNode): ColumnDef {
|
|
916
|
+
const colNameNode = extractChild(node, "columnName");
|
|
917
|
+
let name = "";
|
|
918
|
+
|
|
919
|
+
if (colNameNode) {
|
|
920
|
+
const template = extractImage(colNameNode, "TemplateIdentifier");
|
|
921
|
+
const id = extractImage(colNameNode, "Identifier");
|
|
922
|
+
name = template ? template + id : id;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const dataTypeNode = extractChild(node, "dataType");
|
|
926
|
+
let dataType = "";
|
|
927
|
+
|
|
928
|
+
if (dataTypeNode) {
|
|
929
|
+
// Get the first token in dataType
|
|
930
|
+
for (const key of Object.keys(dataTypeNode.children)) {
|
|
931
|
+
const tokens = dataTypeNode.children[key];
|
|
932
|
+
const firstToken = tokens?.[0];
|
|
933
|
+
if (firstToken && "image" in firstToken) {
|
|
934
|
+
dataType = firstToken.image;
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// Check for size parameter
|
|
940
|
+
const sizeTokens = dataTypeNode.children["NumberLiteral"];
|
|
941
|
+
const firstSizeToken = sizeTokens?.[0];
|
|
942
|
+
if (firstSizeToken && "image" in firstSizeToken) {
|
|
943
|
+
dataType += `(${firstSizeToken.image})`;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const constraints: ColumnConstraint[] = [];
|
|
948
|
+
const constraintNodes = extractChildren(node, "columnConstraint");
|
|
949
|
+
|
|
950
|
+
for (const c of constraintNodes) {
|
|
951
|
+
const constraint = this.visitColumnConstraint(c);
|
|
952
|
+
if (constraint) {
|
|
953
|
+
constraints.push(constraint);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return {
|
|
958
|
+
type: "ColumnDef",
|
|
959
|
+
name,
|
|
960
|
+
dataType,
|
|
961
|
+
constraints,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
private visitColumnConstraint(node: CstNode): ColumnConstraint | null {
|
|
966
|
+
if (node.children["Pkey"]) return { type: "PrimaryKey" };
|
|
967
|
+
if (node.children["Nonull"]) return { type: "NotNull" };
|
|
968
|
+
if (node.children["Unique"]) return { type: "Unique" };
|
|
969
|
+
|
|
970
|
+
const defaultNode = extractChild(node, "defaultConstraint");
|
|
971
|
+
if (defaultNode) {
|
|
972
|
+
const valueNode = extractChild(defaultNode, "defaultValue");
|
|
973
|
+
let value = "";
|
|
974
|
+
|
|
975
|
+
if (valueNode) {
|
|
976
|
+
const strLit = extractImage(valueNode, "StringLiteral");
|
|
977
|
+
const numLit = extractImage(valueNode, "NumberLiteral");
|
|
978
|
+
const boolLit = extractImage(valueNode, "BooleanLiteral");
|
|
979
|
+
const nullLit = extractImage(valueNode, "NullLiteral");
|
|
980
|
+
const funcCall = extractChild(valueNode, "functionCallExpr");
|
|
981
|
+
const doubleColon = extractImage(valueNode, "DoubleColon");
|
|
982
|
+
const anyIds = extractAllAnyIdentifiers(valueNode);
|
|
983
|
+
|
|
984
|
+
if (strLit) value = strLit;
|
|
985
|
+
else if (numLit) value = numLit;
|
|
986
|
+
else if (boolLit) value = boolLit;
|
|
987
|
+
else if (nullLit) value = nullLit;
|
|
988
|
+
else if (funcCall) value = extractImage(funcCall, "Identifier") + "()";
|
|
989
|
+
else if (doubleColon && anyIds.length === 2 && anyIds[0] && anyIds[1]) {
|
|
990
|
+
// Qualified enum value: type::value
|
|
991
|
+
value = `${anyIds[0]}::${anyIds[1]}`;
|
|
992
|
+
} else if (anyIds.length > 0 && anyIds[0]) value = anyIds[0];
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return { type: "Default", value };
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const refNode = extractChild(node, "refConstraint");
|
|
999
|
+
if (refNode) {
|
|
1000
|
+
const ids = extractAllImages(refNode, "Identifier");
|
|
1001
|
+
return { type: "Reference", table: ids[0] ?? "", column: ids[1] ?? "" };
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const checkNode = extractChild(node, "checkConstraint");
|
|
1005
|
+
if (checkNode) {
|
|
1006
|
+
const exprNode = extractChild(checkNode, "checkExpression");
|
|
1007
|
+
const value = exprNode ? this.reconstructCheckExpression(exprNode) : "";
|
|
1008
|
+
return { type: "Check", value };
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const onDeleteNode = extractChild(node, "onDeleteConstraint");
|
|
1012
|
+
if (onDeleteNode) {
|
|
1013
|
+
const actionMap: Record<string, string> = {
|
|
1014
|
+
Cascade: "CASCADE",
|
|
1015
|
+
Restrict: "RESTRICT",
|
|
1016
|
+
SetNull: "SET NULL",
|
|
1017
|
+
SetDefault: "SET DEFAULT",
|
|
1018
|
+
NoAction: "NO ACTION",
|
|
1019
|
+
};
|
|
1020
|
+
for (const [key, action] of Object.entries(actionMap)) {
|
|
1021
|
+
if (onDeleteNode.children[key]) {
|
|
1022
|
+
return { type: "OnDelete", action };
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
private reconstructCheckExpression(node: CstNode): string {
|
|
1031
|
+
const parts: string[] = [];
|
|
1032
|
+
|
|
1033
|
+
for (const key of Object.keys(node.children)) {
|
|
1034
|
+
const children = node.children[key];
|
|
1035
|
+
if (!children) continue;
|
|
1036
|
+
|
|
1037
|
+
for (const child of children) {
|
|
1038
|
+
if ("image" in child) {
|
|
1039
|
+
parts.push(child.image);
|
|
1040
|
+
} else if ("children" in child && key === "checkExpression") {
|
|
1041
|
+
const inner = this.reconstructCheckExpression(child);
|
|
1042
|
+
if (inner) parts.push(`(${inner})`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return parts.join(" ");
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
private visitIndexDef(node: CstNode): IndexDef {
|
|
1051
|
+
const columns: string[] = [];
|
|
1052
|
+
const columnListNode = extractChild(node, "indexColumnList");
|
|
1053
|
+
|
|
1054
|
+
if (columnListNode) {
|
|
1055
|
+
const colNodes = extractChildren(columnListNode, "indexColumn");
|
|
1056
|
+
for (const col of colNodes) {
|
|
1057
|
+
const template = extractImage(col, "TemplateIdentifier");
|
|
1058
|
+
const id = extractImage(col, "Identifier");
|
|
1059
|
+
columns.push(template ? template + id : id);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const unique = node.children["Unique"] !== undefined;
|
|
1064
|
+
let using: string | undefined;
|
|
1065
|
+
|
|
1066
|
+
if (node.children["Gin"]) using = "gin";
|
|
1067
|
+
if (node.children["Gist"]) using = "gist";
|
|
1068
|
+
if (node.children["Btree"]) using = "btree";
|
|
1069
|
+
if (node.children["Hash"]) using = "hash";
|
|
1070
|
+
|
|
1071
|
+
return {
|
|
1072
|
+
type: "IndexDef",
|
|
1073
|
+
columns,
|
|
1074
|
+
unique,
|
|
1075
|
+
using,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
private visitCheckDef(node: CstNode): CheckDef {
|
|
1080
|
+
const exprNode = extractChild(node, "checkExpression");
|
|
1081
|
+
const expression = exprNode ? this.reconstructCheckExpression(exprNode) : "";
|
|
1082
|
+
|
|
1083
|
+
return {
|
|
1084
|
+
type: "CheckDef",
|
|
1085
|
+
expression,
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
private visitTriggerDef(node: CstNode): TriggerDef {
|
|
1090
|
+
const identifiers = extractAllImages(node, "Identifier");
|
|
1091
|
+
const name = identifiers[0] ?? "";
|
|
1092
|
+
const executeFunction = identifiers[1] ?? "";
|
|
1093
|
+
|
|
1094
|
+
const timing = node.children["Before"] ? "before" : "after";
|
|
1095
|
+
|
|
1096
|
+
let event = "update";
|
|
1097
|
+
if (node.children["Insert"]) event = "insert";
|
|
1098
|
+
if (node.children["Delete"]) event = "delete";
|
|
1099
|
+
|
|
1100
|
+
const forEach = node.children["Row"] ? "row" : "statement";
|
|
1101
|
+
|
|
1102
|
+
return {
|
|
1103
|
+
type: "TriggerDef",
|
|
1104
|
+
name,
|
|
1105
|
+
timing,
|
|
1106
|
+
event,
|
|
1107
|
+
forEach,
|
|
1108
|
+
executeFunction,
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
private visitInstantiation(node: CstNode): Instantiation {
|
|
1113
|
+
const targets: InstantiationTarget[] = [];
|
|
1114
|
+
const targetListNode = extractChild(node, "instantiationTargetList");
|
|
1115
|
+
|
|
1116
|
+
if (targetListNode) {
|
|
1117
|
+
const targetNodes = extractChildren(targetListNode, "instantiationTarget");
|
|
1118
|
+
for (const t of targetNodes) {
|
|
1119
|
+
const ids = extractAllImages(t, "Identifier");
|
|
1120
|
+
targets.push({
|
|
1121
|
+
tableName: ids[0] ?? "",
|
|
1122
|
+
alias: ids[1],
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const conceptRefNode = extractChild(node, "conceptReference");
|
|
1128
|
+
let conceptName = "";
|
|
1129
|
+
const typeArgs: TypeArg[] = [];
|
|
1130
|
+
|
|
1131
|
+
if (conceptRefNode) {
|
|
1132
|
+
conceptName = extractImage(conceptRefNode, "Identifier");
|
|
1133
|
+
|
|
1134
|
+
const typeArgListNode = extractChild(conceptRefNode, "typeArgList");
|
|
1135
|
+
if (typeArgListNode) {
|
|
1136
|
+
const argNodes = extractChildren(typeArgListNode, "typeArg");
|
|
1137
|
+
for (const arg of argNodes) {
|
|
1138
|
+
const ids = extractAllImages(arg, "Identifier");
|
|
1139
|
+
typeArgs.push({
|
|
1140
|
+
tableName: ids[0] ?? "",
|
|
1141
|
+
alias: ids[1],
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
return {
|
|
1148
|
+
type: "Instantiation",
|
|
1149
|
+
targets,
|
|
1150
|
+
conceptName,
|
|
1151
|
+
typeArgs,
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
private visitPerInstanceIndex(node: CstNode): PerInstanceIndex {
|
|
1156
|
+
const identifiers = extractAllImages(node, "Identifier");
|
|
1157
|
+
const tableName = identifiers[0] ?? "";
|
|
1158
|
+
const columns = identifiers.slice(1);
|
|
1159
|
+
|
|
1160
|
+
const unique = node.children["Unique"] !== undefined;
|
|
1161
|
+
let using: string | undefined;
|
|
1162
|
+
|
|
1163
|
+
if (node.children["Gin"]) using = "gin";
|
|
1164
|
+
if (node.children["Gist"]) using = "gist";
|
|
1165
|
+
if (node.children["Btree"]) using = "btree";
|
|
1166
|
+
if (node.children["Hash"]) using = "hash";
|
|
1167
|
+
|
|
1168
|
+
return {
|
|
1169
|
+
type: "PerInstanceIndex",
|
|
1170
|
+
tableName,
|
|
1171
|
+
columns,
|
|
1172
|
+
unique,
|
|
1173
|
+
using,
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// ============================================================================
|
|
1179
|
+
// Main Parse Function
|
|
1180
|
+
// ============================================================================
|
|
1181
|
+
|
|
1182
|
+
export interface ParseResult {
|
|
1183
|
+
ast: GSQLProgram | null;
|
|
1184
|
+
errors: CompileError[];
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
export function parse(source: string): ParseResult {
|
|
1188
|
+
const lexResult = tokenize(source);
|
|
1189
|
+
const errors: CompileError[] = [];
|
|
1190
|
+
|
|
1191
|
+
// Check for lexer errors
|
|
1192
|
+
for (const error of lexResult.errors) {
|
|
1193
|
+
errors.push({
|
|
1194
|
+
message: error.message,
|
|
1195
|
+
location: {
|
|
1196
|
+
start: { line: error.line ?? 0, column: error.column ?? 0, offset: error.offset },
|
|
1197
|
+
end: { line: error.line ?? 0, column: error.column ?? 0, offset: error.offset },
|
|
1198
|
+
},
|
|
1199
|
+
severity: "error",
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (errors.length > 0) {
|
|
1204
|
+
return { ast: null, errors };
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Parse tokens
|
|
1208
|
+
parserInstance.input = lexResult.tokens;
|
|
1209
|
+
const cst = parserInstance.program();
|
|
1210
|
+
|
|
1211
|
+
// Check for parser errors
|
|
1212
|
+
for (const error of parserInstance.errors) {
|
|
1213
|
+
const token = error.token;
|
|
1214
|
+
errors.push({
|
|
1215
|
+
message: error.message,
|
|
1216
|
+
location: {
|
|
1217
|
+
start: {
|
|
1218
|
+
line: token.startLine ?? 0,
|
|
1219
|
+
column: token.startColumn ?? 0,
|
|
1220
|
+
offset: token.startOffset,
|
|
1221
|
+
},
|
|
1222
|
+
end: {
|
|
1223
|
+
line: token.endLine ?? 0,
|
|
1224
|
+
column: token.endColumn ?? 0,
|
|
1225
|
+
offset: token.endOffset ?? token.startOffset,
|
|
1226
|
+
},
|
|
1227
|
+
},
|
|
1228
|
+
severity: "error",
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
if (errors.length > 0) {
|
|
1233
|
+
return { ast: null, errors };
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Convert CST to AST
|
|
1237
|
+
const visitor = new CstToAstVisitor();
|
|
1238
|
+
const ast = visitor.visit(cst);
|
|
1239
|
+
|
|
1240
|
+
return { ast, errors };
|
|
1241
|
+
}
|