@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/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
+ }