@claudetools/tools 0.8.11 → 0.9.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.
Files changed (75) hide show
  1. package/dist/codedna/generators/astro.d.ts +18 -0
  2. package/dist/codedna/generators/astro.js +91 -0
  3. package/dist/codedna/generators/authjs.d.ts +18 -0
  4. package/dist/codedna/generators/authjs.js +68 -0
  5. package/dist/codedna/generators/better-auth.d.ts +18 -0
  6. package/dist/codedna/generators/better-auth.js +62 -0
  7. package/dist/codedna/generators/drizzle-orm.d.ts +18 -0
  8. package/dist/codedna/generators/drizzle-orm.js +65 -0
  9. package/dist/codedna/generators/elysia-api.d.ts +12 -0
  10. package/dist/codedna/generators/elysia-api.js +64 -0
  11. package/dist/codedna/generators/hono-api.d.ts +12 -0
  12. package/dist/codedna/generators/hono-api.js +64 -0
  13. package/dist/codedna/generators/lucia-auth.d.ts +18 -0
  14. package/dist/codedna/generators/lucia-auth.js +69 -0
  15. package/dist/codedna/generators/prisma.d.ts +18 -0
  16. package/dist/codedna/generators/prisma.js +64 -0
  17. package/dist/codedna/generators/react-router-v7.d.ts +18 -0
  18. package/dist/codedna/generators/react-router-v7.js +77 -0
  19. package/dist/codedna/generators/react19-shadcn.d.ts +21 -0
  20. package/dist/codedna/generators/react19-shadcn.js +367 -0
  21. package/dist/codedna/generators/sveltekit.d.ts +18 -0
  22. package/dist/codedna/generators/sveltekit.js +73 -0
  23. package/dist/codedna/generators/tanstack-start-drizzle.d.ts +92 -0
  24. package/dist/codedna/generators/tanstack-start-drizzle.js +824 -0
  25. package/dist/codedna/generators/trpc-api.d.ts +12 -0
  26. package/dist/codedna/generators/trpc-api.js +64 -0
  27. package/dist/codedna/index.d.ts +31 -0
  28. package/dist/codedna/index.js +39 -0
  29. package/dist/codedna/kappa-api-generator.d.ts +89 -0
  30. package/dist/codedna/kappa-api-generator.js +493 -0
  31. package/dist/codedna/kappa-ast.d.ts +552 -0
  32. package/dist/codedna/kappa-ast.js +141 -0
  33. package/dist/codedna/kappa-cli.d.ts +2 -0
  34. package/dist/codedna/kappa-cli.js +302 -0
  35. package/dist/codedna/kappa-component-generator.d.ts +47 -0
  36. package/dist/codedna/kappa-component-generator.js +295 -0
  37. package/dist/codedna/kappa-design-generator.d.ts +52 -0
  38. package/dist/codedna/kappa-design-generator.js +365 -0
  39. package/dist/codedna/kappa-drizzle-generator.d.ts +45 -0
  40. package/dist/codedna/kappa-drizzle-generator.js +355 -0
  41. package/dist/codedna/kappa-form-generator.d.ts +51 -0
  42. package/dist/codedna/kappa-form-generator.js +319 -0
  43. package/dist/codedna/kappa-lexer.d.ts +268 -0
  44. package/dist/codedna/kappa-lexer.js +757 -0
  45. package/dist/codedna/kappa-page-generator.d.ts +57 -0
  46. package/dist/codedna/kappa-page-generator.js +338 -0
  47. package/dist/codedna/kappa-parser.d.ts +261 -0
  48. package/dist/codedna/kappa-parser.js +2547 -0
  49. package/dist/codedna/kappa-provenance.d.ts +101 -0
  50. package/dist/codedna/kappa-provenance.js +199 -0
  51. package/dist/codedna/kappa-types-generator.d.ts +37 -0
  52. package/dist/codedna/kappa-types-generator.js +159 -0
  53. package/dist/codedna/kappa-validator.d.ts +86 -0
  54. package/dist/codedna/kappa-validator.js +638 -0
  55. package/dist/codedna/kappa-zod-generator.d.ts +32 -0
  56. package/dist/codedna/kappa-zod-generator.js +216 -0
  57. package/dist/handlers/kappa-handlers.d.ts +116 -0
  58. package/dist/handlers/kappa-handlers.js +465 -0
  59. package/dist/handlers/tool-handlers.js +121 -0
  60. package/dist/templates/claude-md.d.ts +1 -1
  61. package/dist/templates/claude-md.js +166 -9
  62. package/dist/tools.js +199 -0
  63. package/docs/research/2026-01-02-codedna-il-specification.md +639 -0
  64. package/docs/research/2026-01-02-codedna-v2-research.md +943 -0
  65. package/docs/research/2026-01-02-computation-foundations.md +564 -0
  66. package/docs/research/2026-01-02-hardware-description.md +814 -0
  67. package/docs/research/2026-01-02-kappa-specification.md +697 -0
  68. package/docs/research/2026-01-02-kappa-tanstack-example.md +527 -0
  69. package/docs/research/2026-01-02-kappa-v2-synthesis.md +406 -0
  70. package/docs/research/2026-01-02-kappa-v2.5-specification.md +1218 -0
  71. package/docs/research/2026-01-02-kappa-v3-specification.md +1864 -0
  72. package/docs/research/2026-01-02-kappa-whitepaper.md +662 -0
  73. package/docs/research/2026-01-02-logic-constraint.md +731 -0
  74. package/docs/research/2026-01-02-quantum-computation.md +635 -0
  75. package/package.json +4 -2
@@ -0,0 +1,2547 @@
1
+ // =============================================================================
2
+ // Kappa v2.5 Parser (Chevrotain)
3
+ // =============================================================================
4
+ //
5
+ // Parses Kappa specification language into AST nodes.
6
+ // Uses Chevrotain's Embedded Actions pattern for direct AST construction.
7
+ //
8
+ // Reference: docs/research/2026-01-02-kappa-v2.5-specification.md
9
+ //
10
+ import { CstParser } from 'chevrotain';
11
+ import * as tokens from './kappa-lexer.js';
12
+ // =============================================================================
13
+ // Parser Definition
14
+ // =============================================================================
15
+ class KappaParser extends CstParser {
16
+ constructor() {
17
+ super(tokens.allTokens, {
18
+ recoveryEnabled: true,
19
+ });
20
+ this.performSelfAnalysis();
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // Root Rule
24
+ // ---------------------------------------------------------------------------
25
+ kappaSpec = this.RULE('kappaSpec', () => {
26
+ this.MANY(() => {
27
+ this.OR([
28
+ { ALT: () => this.SUBRULE(this.projectBlock) },
29
+ { ALT: () => this.SUBRULE(this.authBlock) },
30
+ { ALT: () => this.SUBRULE(this.entityBlock) },
31
+ { ALT: () => this.SUBRULE(this.apiBlock) },
32
+ { ALT: () => this.SUBRULE(this.journeyBlock) },
33
+ { ALT: () => this.SUBRULE(this.pageBlock) },
34
+ { ALT: () => this.SUBRULE(this.componentBlock) },
35
+ { ALT: () => this.SUBRULE(this.formBlock) },
36
+ { ALT: () => this.SUBRULE(this.designBlock) },
37
+ ]);
38
+ });
39
+ });
40
+ // ---------------------------------------------------------------------------
41
+ // Project Block
42
+ // ---------------------------------------------------------------------------
43
+ projectBlock = this.RULE('projectBlock', () => {
44
+ this.CONSUME(tokens.Project);
45
+ this.CONSUME(tokens.PascalIdentifier);
46
+ this.CONSUME(tokens.LBrace);
47
+ this.MANY(() => {
48
+ this.SUBRULE(this.projectProperty);
49
+ });
50
+ this.CONSUME(tokens.RBrace);
51
+ });
52
+ projectProperty = this.RULE('projectProperty', () => {
53
+ this.OR([
54
+ {
55
+ ALT: () => {
56
+ this.CONSUME(tokens.Version);
57
+ this.CONSUME(tokens.Colon);
58
+ this.CONSUME(tokens.StringLiteral);
59
+ },
60
+ },
61
+ {
62
+ ALT: () => {
63
+ this.CONSUME(tokens.Stack);
64
+ this.CONSUME2(tokens.Colon);
65
+ this.SUBRULE(this.stackValue);
66
+ },
67
+ },
68
+ {
69
+ ALT: () => {
70
+ this.CONSUME(tokens.Database);
71
+ this.CONSUME3(tokens.Colon);
72
+ this.SUBRULE(this.databaseValue);
73
+ },
74
+ },
75
+ {
76
+ ALT: () => {
77
+ this.CONSUME(tokens.Auth);
78
+ this.CONSUME4(tokens.Colon);
79
+ this.SUBRULE(this.authTypeValue);
80
+ },
81
+ },
82
+ {
83
+ ALT: () => {
84
+ this.CONSUME(tokens.Ui);
85
+ this.CONSUME5(tokens.Colon);
86
+ this.SUBRULE(this.uiValue);
87
+ },
88
+ },
89
+ ]);
90
+ });
91
+ stackValue = this.RULE('stackValue', () => {
92
+ this.OR([
93
+ { ALT: () => this.CONSUME(tokens.TanstackStart) },
94
+ { ALT: () => this.CONSUME(tokens.Nextjs) },
95
+ { ALT: () => this.CONSUME(tokens.Remix) },
96
+ { ALT: () => this.CONSUME(tokens.Sveltekit) },
97
+ { ALT: () => this.CONSUME(tokens.Astro) },
98
+ { ALT: () => this.CONSUME(tokens.Express) },
99
+ { ALT: () => this.CONSUME(tokens.Hono) },
100
+ { ALT: () => this.CONSUME(tokens.Elysia) },
101
+ ]);
102
+ });
103
+ databaseValue = this.RULE('databaseValue', () => {
104
+ this.OR([
105
+ { ALT: () => this.CONSUME(tokens.Postgres) },
106
+ { ALT: () => this.CONSUME(tokens.Mysql) },
107
+ { ALT: () => this.CONSUME(tokens.Sqlite) },
108
+ { ALT: () => this.CONSUME(tokens.Turso) },
109
+ { ALT: () => this.CONSUME(tokens.Mongodb) },
110
+ ]);
111
+ });
112
+ authTypeValue = this.RULE('authTypeValue', () => {
113
+ this.OR([
114
+ { ALT: () => this.CONSUME(tokens.Jwt) },
115
+ { ALT: () => this.CONSUME(tokens.Session) },
116
+ { ALT: () => this.CONSUME(tokens.Oauth) },
117
+ ]);
118
+ });
119
+ uiValue = this.RULE('uiValue', () => {
120
+ this.OR([
121
+ { ALT: () => this.CONSUME(tokens.Shadcn) },
122
+ { ALT: () => this.CONSUME(tokens.Mui) },
123
+ { ALT: () => this.CONSUME(tokens.Chakra) },
124
+ { ALT: () => this.CONSUME(tokens.TailwindOnly) },
125
+ ]);
126
+ });
127
+ // ---------------------------------------------------------------------------
128
+ // Auth Block
129
+ // ---------------------------------------------------------------------------
130
+ authBlock = this.RULE('authBlock', () => {
131
+ this.CONSUME(tokens.Auth);
132
+ this.OR([
133
+ { ALT: () => this.CONSUME(tokens.Jwt) },
134
+ { ALT: () => this.CONSUME(tokens.Session) },
135
+ ]);
136
+ this.CONSUME(tokens.LBrace);
137
+ this.MANY(() => {
138
+ this.SUBRULE(this.authProperty);
139
+ });
140
+ this.CONSUME(tokens.RBrace);
141
+ });
142
+ authProperty = this.RULE('authProperty', () => {
143
+ this.OR([
144
+ {
145
+ ALT: () => {
146
+ this.CONSUME(tokens.Secret);
147
+ this.CONSUME(tokens.Colon);
148
+ this.SUBRULE(this.envCall);
149
+ },
150
+ },
151
+ {
152
+ ALT: () => {
153
+ this.CONSUME(tokens.Issuer);
154
+ this.CONSUME2(tokens.Colon);
155
+ this.CONSUME(tokens.StringLiteral);
156
+ },
157
+ },
158
+ {
159
+ ALT: () => {
160
+ this.CONSUME(tokens.Audience);
161
+ this.CONSUME3(tokens.Colon);
162
+ this.CONSUME2(tokens.StringLiteral);
163
+ },
164
+ },
165
+ {
166
+ ALT: () => {
167
+ this.CONSUME(tokens.Expires);
168
+ this.CONSUME4(tokens.Colon);
169
+ this.CONSUME(tokens.DurationLiteral);
170
+ },
171
+ },
172
+ {
173
+ ALT: () => {
174
+ this.CONSUME(tokens.Refresh);
175
+ this.CONSUME5(tokens.Colon);
176
+ this.SUBRULE(this.refreshValue);
177
+ },
178
+ },
179
+ {
180
+ ALT: () => {
181
+ this.CONSUME(tokens.Hash);
182
+ this.CONSUME6(tokens.Colon);
183
+ this.SUBRULE(this.hashConfig);
184
+ },
185
+ },
186
+ {
187
+ ALT: () => {
188
+ this.CONSUME(tokens.Duration);
189
+ this.CONSUME7(tokens.Colon);
190
+ this.CONSUME2(tokens.DurationLiteral);
191
+ },
192
+ },
193
+ ]);
194
+ });
195
+ envCall = this.RULE('envCall', () => {
196
+ this.CONSUME(tokens.Env);
197
+ this.CONSUME(tokens.LParen);
198
+ // Env var names can be SCREAMING_SNAKE_CASE (ConstantIdentifier), PascalCase, or camelCase
199
+ this.OR([
200
+ { ALT: () => this.CONSUME(tokens.ConstantIdentifier) },
201
+ { ALT: () => this.CONSUME(tokens.PascalIdentifier) },
202
+ { ALT: () => this.CONSUME(tokens.Identifier) },
203
+ ]);
204
+ this.CONSUME(tokens.RParen);
205
+ });
206
+ refreshValue = this.RULE('refreshValue', () => {
207
+ this.OR([
208
+ { ALT: () => this.CONSUME(tokens.Sliding) },
209
+ { ALT: () => this.CONSUME(tokens.Fixed) },
210
+ { ALT: () => this.CONSUME(tokens.None) },
211
+ ]);
212
+ });
213
+ hashConfig = this.RULE('hashConfig', () => {
214
+ this.OR([
215
+ {
216
+ ALT: () => {
217
+ this.CONSUME(tokens.Argon2id);
218
+ this.CONSUME(tokens.LParen);
219
+ this.SUBRULE(this.hashParams);
220
+ this.CONSUME(tokens.RParen);
221
+ },
222
+ },
223
+ {
224
+ ALT: () => {
225
+ this.CONSUME(tokens.Bcrypt);
226
+ this.OPTION(() => {
227
+ this.CONSUME2(tokens.LParen);
228
+ this.CONSUME(tokens.Rounds);
229
+ this.CONSUME(tokens.Colon);
230
+ this.CONSUME(tokens.IntegerLiteral);
231
+ this.CONSUME2(tokens.RParen);
232
+ });
233
+ },
234
+ },
235
+ ]);
236
+ });
237
+ hashParams = this.RULE('hashParams', () => {
238
+ this.MANY_SEP({
239
+ SEP: tokens.Comma,
240
+ DEF: () => {
241
+ this.OR([
242
+ {
243
+ ALT: () => {
244
+ this.CONSUME(tokens.Memory);
245
+ this.CONSUME(tokens.Colon);
246
+ this.CONSUME(tokens.SizeLiteral);
247
+ },
248
+ },
249
+ {
250
+ ALT: () => {
251
+ this.CONSUME(tokens.Iterations);
252
+ this.CONSUME2(tokens.Colon);
253
+ this.CONSUME(tokens.IntegerLiteral);
254
+ },
255
+ },
256
+ {
257
+ ALT: () => {
258
+ this.CONSUME(tokens.Parallelism);
259
+ this.CONSUME3(tokens.Colon);
260
+ this.CONSUME2(tokens.IntegerLiteral);
261
+ },
262
+ },
263
+ ]);
264
+ },
265
+ });
266
+ });
267
+ // ---------------------------------------------------------------------------
268
+ // Entity Block
269
+ // ---------------------------------------------------------------------------
270
+ entityBlock = this.RULE('entityBlock', () => {
271
+ this.CONSUME(tokens.Entity);
272
+ this.CONSUME(tokens.PascalIdentifier);
273
+ this.CONSUME(tokens.LBrace);
274
+ this.MANY(() => {
275
+ this.OR([
276
+ { ALT: () => this.SUBRULE(this.entityField) },
277
+ { ALT: () => this.SUBRULE(this.entityRelationship) },
278
+ { ALT: () => this.SUBRULE(this.entityCapability) },
279
+ { ALT: () => this.SUBRULE(this.entityLifecycleHook) },
280
+ ]);
281
+ });
282
+ this.CONSUME(tokens.RBrace);
283
+ });
284
+ // Field name can be an identifier OR a keyword that happens to match a type name
285
+ // (e.g., "email: email" where first email is field name, second is type)
286
+ // Also used for color names like "primary", "background"
287
+ fieldName = this.RULE('fieldName', () => {
288
+ this.OR([
289
+ { ALT: () => this.CONSUME(tokens.Identifier) },
290
+ // Keywords that could also be field names or color names
291
+ { ALT: () => this.CONSUME(tokens.Email) },
292
+ { ALT: () => this.CONSUME(tokens.Password) },
293
+ { ALT: () => this.CONSUME(tokens.String) },
294
+ { ALT: () => this.CONSUME(tokens.Int) },
295
+ { ALT: () => this.CONSUME(tokens.Float) },
296
+ { ALT: () => this.CONSUME(tokens.Bool) },
297
+ { ALT: () => this.CONSUME(tokens.Url) },
298
+ { ALT: () => this.CONSUME(tokens.Uuid) },
299
+ { ALT: () => this.CONSUME(tokens.Phone) },
300
+ { ALT: () => this.CONSUME(tokens.Slug) },
301
+ { ALT: () => this.CONSUME(tokens.Markdown) },
302
+ { ALT: () => this.CONSUME(tokens.Json) },
303
+ { ALT: () => this.CONSUME(tokens.Timestamp) },
304
+ { ALT: () => this.CONSUME(tokens.Date) },
305
+ { ALT: () => this.CONSUME(tokens.Time) },
306
+ { ALT: () => this.CONSUME(tokens.Text) },
307
+ { ALT: () => this.CONSUME(tokens.Primary) },
308
+ // Additional keywords commonly used as field names
309
+ { ALT: () => this.CONSUME(tokens.Title) },
310
+ { ALT: () => this.CONSUME(tokens.Label) },
311
+ { ALT: () => this.CONSUME(tokens.State) },
312
+ { ALT: () => this.CONSUME(tokens.Version) },
313
+ { ALT: () => this.CONSUME(tokens.Color) },
314
+ ]);
315
+ });
316
+ entityField = this.RULE('entityField', () => {
317
+ this.SUBRULE(this.fieldName);
318
+ this.CONSUME(tokens.Colon);
319
+ this.SUBRULE(this.fieldType);
320
+ this.MANY(() => {
321
+ this.CONSUME(tokens.Comma);
322
+ this.SUBRULE(this.fieldModifier);
323
+ });
324
+ this.OPTION(() => {
325
+ this.CONSUME(tokens.Equals);
326
+ this.SUBRULE(this.defaultValue);
327
+ });
328
+ });
329
+ fieldType = this.RULE('fieldType', () => {
330
+ this.OR([
331
+ // Array type: [Type]
332
+ {
333
+ ALT: () => {
334
+ this.CONSUME(tokens.LBracket);
335
+ this.SUBRULE(this.innerFieldType);
336
+ this.CONSUME(tokens.RBracket);
337
+ },
338
+ },
339
+ // String with optional length constraint (handles both "string" and "string(1..100)")
340
+ {
341
+ ALT: () => {
342
+ this.CONSUME(tokens.String);
343
+ this.OPTION(() => {
344
+ this.CONSUME(tokens.LParen);
345
+ this.SUBRULE(this.rangeConstraint);
346
+ this.CONSUME(tokens.RParen);
347
+ });
348
+ },
349
+ },
350
+ // Other primitive types
351
+ { ALT: () => this.CONSUME(tokens.Int) },
352
+ { ALT: () => this.CONSUME(tokens.Float) },
353
+ { ALT: () => this.CONSUME(tokens.Bool) },
354
+ { ALT: () => this.CONSUME(tokens.Email) },
355
+ { ALT: () => this.CONSUME(tokens.Url) },
356
+ { ALT: () => this.CONSUME(tokens.Uuid) },
357
+ { ALT: () => this.CONSUME(tokens.Phone) },
358
+ { ALT: () => this.CONSUME(tokens.Slug) },
359
+ { ALT: () => this.CONSUME(tokens.Markdown) },
360
+ { ALT: () => this.CONSUME(tokens.Json) },
361
+ { ALT: () => this.CONSUME(tokens.Timestamp) },
362
+ { ALT: () => this.CONSUME(tokens.Date) },
363
+ { ALT: () => this.CONSUME(tokens.Time) },
364
+ // Enum type (inline)
365
+ {
366
+ ALT: () => {
367
+ this.CONSUME(tokens.Identifier);
368
+ this.AT_LEAST_ONE(() => {
369
+ this.CONSUME(tokens.Pipe);
370
+ this.CONSUME2(tokens.Identifier);
371
+ });
372
+ },
373
+ },
374
+ // Reference type
375
+ { ALT: () => this.CONSUME(tokens.PascalIdentifier) },
376
+ ]);
377
+ });
378
+ // Inner field type for arrays (all types except array to avoid recursion issues)
379
+ innerFieldType = this.RULE('innerFieldType', () => {
380
+ this.OR([
381
+ // String with optional length constraint
382
+ {
383
+ ALT: () => {
384
+ this.CONSUME(tokens.String);
385
+ this.OPTION(() => {
386
+ this.CONSUME(tokens.LParen);
387
+ this.SUBRULE(this.rangeConstraint);
388
+ this.CONSUME(tokens.RParen);
389
+ });
390
+ },
391
+ },
392
+ // Other primitive types
393
+ { ALT: () => this.CONSUME(tokens.Int) },
394
+ { ALT: () => this.CONSUME(tokens.Float) },
395
+ { ALT: () => this.CONSUME(tokens.Bool) },
396
+ { ALT: () => this.CONSUME(tokens.Email) },
397
+ { ALT: () => this.CONSUME(tokens.Url) },
398
+ { ALT: () => this.CONSUME(tokens.Uuid) },
399
+ { ALT: () => this.CONSUME(tokens.Phone) },
400
+ { ALT: () => this.CONSUME(tokens.Slug) },
401
+ { ALT: () => this.CONSUME(tokens.Markdown) },
402
+ { ALT: () => this.CONSUME(tokens.Json) },
403
+ { ALT: () => this.CONSUME(tokens.Timestamp) },
404
+ { ALT: () => this.CONSUME(tokens.Date) },
405
+ { ALT: () => this.CONSUME(tokens.Time) },
406
+ // Enum type (inline)
407
+ {
408
+ ALT: () => {
409
+ this.CONSUME(tokens.Identifier);
410
+ this.AT_LEAST_ONE(() => {
411
+ this.CONSUME(tokens.Pipe);
412
+ this.CONSUME2(tokens.Identifier);
413
+ });
414
+ },
415
+ },
416
+ // Reference type
417
+ { ALT: () => this.CONSUME(tokens.PascalIdentifier) },
418
+ ]);
419
+ });
420
+ rangeConstraint = this.RULE('rangeConstraint', () => {
421
+ this.OPTION(() => this.CONSUME(tokens.IntegerLiteral));
422
+ this.CONSUME(tokens.DotDot);
423
+ this.OPTION2(() => this.CONSUME2(tokens.IntegerLiteral));
424
+ });
425
+ fieldModifier = this.RULE('fieldModifier', () => {
426
+ this.OR([
427
+ { ALT: () => this.CONSUME(tokens.Primary) },
428
+ { ALT: () => this.CONSUME(tokens.Unique) },
429
+ { ALT: () => this.CONSUME(tokens.Auto) },
430
+ { ALT: () => this.CONSUME(tokens.Immutable) },
431
+ { ALT: () => this.CONSUME(tokens.Internal) },
432
+ { ALT: () => this.CONSUME(tokens.Hashed) },
433
+ { ALT: () => this.CONSUME(tokens.Optional) },
434
+ ]);
435
+ });
436
+ defaultValue = this.RULE('defaultValue', () => {
437
+ this.OR([
438
+ { ALT: () => this.CONSUME(tokens.StringLiteral) },
439
+ { ALT: () => this.CONSUME(tokens.IntegerLiteral) },
440
+ { ALT: () => this.CONSUME(tokens.Identifier) },
441
+ { ALT: () => this.CONSUME(tokens.True) },
442
+ { ALT: () => this.CONSUME(tokens.False) },
443
+ ]);
444
+ });
445
+ entityRelationship = this.RULE('entityRelationship', () => {
446
+ this.OR([
447
+ { ALT: () => this.CONSUME(tokens.HasMany) },
448
+ { ALT: () => this.CONSUME(tokens.HasOne) },
449
+ { ALT: () => this.CONSUME(tokens.BelongsTo) },
450
+ { ALT: () => this.CONSUME(tokens.ManyToMany) },
451
+ ]);
452
+ this.CONSUME(tokens.Colon);
453
+ this.CONSUME(tokens.PascalIdentifier);
454
+ // Optional "through JoinEntity" for many-to-many
455
+ this.OPTION(() => {
456
+ this.CONSUME(tokens.Through);
457
+ this.CONSUME2(tokens.PascalIdentifier);
458
+ });
459
+ // Optional "(as aliasName)" for relationship alias
460
+ this.OPTION2(() => {
461
+ this.CONSUME(tokens.LParen);
462
+ this.CONSUME(tokens.As);
463
+ this.CONSUME(tokens.Identifier);
464
+ this.CONSUME(tokens.RParen);
465
+ });
466
+ });
467
+ entityCapability = this.RULE('entityCapability', () => {
468
+ this.OR([
469
+ { ALT: () => this.CONSUME(tokens.Can) },
470
+ { ALT: () => this.CONSUME(tokens.Cannot) },
471
+ ]);
472
+ this.CONSUME(tokens.Colon);
473
+ this.OR2([
474
+ { ALT: () => this.SUBRULE(this.capabilityList) },
475
+ { ALT: () => this.SUBRULE(this.singleCapability) },
476
+ ]);
477
+ });
478
+ capabilityList = this.RULE('capabilityList', () => {
479
+ this.CONSUME(tokens.LBracket);
480
+ this.MANY_SEP({
481
+ SEP: tokens.Comma,
482
+ DEF: () => this.SUBRULE(this.singleCapability),
483
+ });
484
+ this.CONSUME(tokens.RBracket);
485
+ });
486
+ singleCapability = this.RULE('singleCapability', () => {
487
+ this.SUBRULE(this.capabilityAction);
488
+ this.CONSUME(tokens.PascalIdentifier);
489
+ this.OPTION(() => {
490
+ this.CONSUME(tokens.LParen);
491
+ this.SUBRULE(this.capabilityCondition);
492
+ this.CONSUME(tokens.RParen);
493
+ });
494
+ });
495
+ capabilityAction = this.RULE('capabilityAction', () => {
496
+ this.OR([
497
+ { ALT: () => this.CONSUME(tokens.Create) },
498
+ { ALT: () => this.CONSUME(tokens.Read) },
499
+ { ALT: () => this.CONSUME(tokens.Update) },
500
+ { ALT: () => this.CONSUME(tokens.Delete) },
501
+ ]);
502
+ });
503
+ capabilityCondition = this.RULE('capabilityCondition', () => {
504
+ this.OR([
505
+ {
506
+ ALT: () => {
507
+ this.CONSUME(tokens.Where);
508
+ this.CONSUME(tokens.Colon);
509
+ this.CONSUME(tokens.Identifier);
510
+ },
511
+ },
512
+ {
513
+ ALT: () => {
514
+ this.CONSUME(tokens.Unless);
515
+ this.CONSUME2(tokens.Colon);
516
+ this.CONSUME2(tokens.Identifier);
517
+ },
518
+ },
519
+ ]);
520
+ });
521
+ entityLifecycleHook = this.RULE('entityLifecycleHook', () => {
522
+ this.OR([
523
+ { ALT: () => this.CONSUME(tokens.OnCreate) },
524
+ { ALT: () => this.CONSUME(tokens.OnUpdate) },
525
+ { ALT: () => this.CONSUME(tokens.OnDelete) },
526
+ ]);
527
+ this.CONSUME(tokens.Colon);
528
+ this.SUBRULE(this.hookAction);
529
+ });
530
+ hookAction = this.RULE('hookAction', () => {
531
+ this.OR([
532
+ { ALT: () => this.CONSUME(tokens.SendEmail) },
533
+ { ALT: () => this.CONSUME(tokens.Anonymize) },
534
+ { ALT: () => this.CONSUME(tokens.Notify) },
535
+ { ALT: () => this.CONSUME(tokens.Webhook) },
536
+ { ALT: () => this.CONSUME(tokens.Identifier) },
537
+ ]);
538
+ this.CONSUME(tokens.LParen);
539
+ this.MANY_SEP({
540
+ SEP: tokens.Comma,
541
+ DEF: () => this.SUBRULE(this.hookActionParam),
542
+ });
543
+ this.CONSUME(tokens.RParen);
544
+ });
545
+ hookActionParam = this.RULE('hookActionParam', () => {
546
+ this.CONSUME(tokens.Identifier);
547
+ this.CONSUME(tokens.Colon);
548
+ this.OR([
549
+ { ALT: () => this.CONSUME(tokens.StringLiteral) },
550
+ { ALT: () => this.CONSUME2(tokens.Identifier) },
551
+ { ALT: () => this.SUBRULE(this.arrayLiteral) },
552
+ ]);
553
+ });
554
+ arrayLiteral = this.RULE('arrayLiteral', () => {
555
+ this.CONSUME(tokens.LBracket);
556
+ this.MANY_SEP({
557
+ SEP: tokens.Comma,
558
+ DEF: () => this.CONSUME(tokens.Identifier),
559
+ });
560
+ this.CONSUME(tokens.RBracket);
561
+ });
562
+ // ---------------------------------------------------------------------------
563
+ // API Block
564
+ // ---------------------------------------------------------------------------
565
+ apiBlock = this.RULE('apiBlock', () => {
566
+ this.CONSUME(tokens.Api);
567
+ this.CONSUME(tokens.PascalIdentifier);
568
+ this.CONSUME(tokens.LBrace);
569
+ this.MANY(() => {
570
+ this.OR([
571
+ { ALT: () => this.SUBRULE(this.crudShorthand) },
572
+ { ALT: () => this.SUBRULE(this.apiEndpoint) },
573
+ ]);
574
+ });
575
+ this.CONSUME(tokens.RBrace);
576
+ });
577
+ // CRUD shorthand: crud: User [create, read, update, delete] -[DB, Auth]->
578
+ crudShorthand = this.RULE('crudShorthand', () => {
579
+ this.CONSUME(tokens.Crud);
580
+ this.CONSUME(tokens.Colon);
581
+ this.CONSUME(tokens.PascalIdentifier); // Entity name
582
+ this.CONSUME(tokens.LBracket);
583
+ this.SUBRULE(this.crudActionList);
584
+ this.CONSUME(tokens.RBracket);
585
+ this.OPTION(() => this.SUBRULE(this.effectAnnotation));
586
+ });
587
+ crudActionList = this.RULE('crudActionList', () => {
588
+ this.SUBRULE(this.crudAction);
589
+ this.MANY(() => {
590
+ this.CONSUME(tokens.Comma);
591
+ this.SUBRULE2(this.crudAction);
592
+ });
593
+ });
594
+ crudAction = this.RULE('crudAction', () => {
595
+ this.OR([
596
+ { ALT: () => this.CONSUME(tokens.Create) },
597
+ { ALT: () => this.CONSUME(tokens.Read) },
598
+ { ALT: () => this.CONSUME(tokens.Update) },
599
+ { ALT: () => this.CONSUME(tokens.Delete) },
600
+ { ALT: () => this.CONSUME(tokens.List) },
601
+ ]);
602
+ });
603
+ apiEndpoint = this.RULE('apiEndpoint', () => {
604
+ this.CONSUME(tokens.Identifier);
605
+ this.CONSUME(tokens.Colon);
606
+ this.CONSUME(tokens.LParen);
607
+ this.OPTION(() => this.SUBRULE(this.parameterList));
608
+ this.CONSUME(tokens.RParen);
609
+ this.SUBRULE(this.effectAnnotation);
610
+ this.SUBRULE(this.returnType);
611
+ });
612
+ parameterList = this.RULE('parameterList', () => {
613
+ this.SUBRULE(this.parameter);
614
+ this.MANY(() => {
615
+ this.CONSUME(tokens.Comma);
616
+ this.SUBRULE2(this.parameter);
617
+ });
618
+ });
619
+ parameter = this.RULE('parameter', () => {
620
+ // Parameter name can be identifier or keyword (e.g., "email: email")
621
+ this.SUBRULE(this.fieldName);
622
+ this.CONSUME(tokens.Colon);
623
+ this.SUBRULE(this.parameterType);
624
+ });
625
+ parameterType = this.RULE('parameterType', () => {
626
+ this.OR([
627
+ { ALT: () => this.CONSUME(tokens.String) },
628
+ { ALT: () => this.CONSUME(tokens.Int) },
629
+ { ALT: () => this.CONSUME(tokens.Float) },
630
+ { ALT: () => this.CONSUME(tokens.Bool) },
631
+ { ALT: () => this.CONSUME(tokens.Email) },
632
+ { ALT: () => this.CONSUME(tokens.Uuid) },
633
+ { ALT: () => this.CONSUME(tokens.PascalIdentifier) },
634
+ ]);
635
+ });
636
+ effectAnnotation = this.RULE('effectAnnotation', () => {
637
+ this.CONSUME(tokens.EffectArrowStart);
638
+ this.SUBRULE(this.effectList);
639
+ this.CONSUME(tokens.EffectArrowEnd);
640
+ });
641
+ effectList = this.RULE('effectList', () => {
642
+ this.SUBRULE(this.effect);
643
+ this.MANY(() => {
644
+ this.CONSUME(tokens.Comma);
645
+ this.SUBRULE2(this.effect);
646
+ });
647
+ });
648
+ effect = this.RULE('effect', () => {
649
+ this.OR([
650
+ { ALT: () => this.CONSUME(tokens.DB) },
651
+ { ALT: () => this.CONSUME(tokens.AuthEffect) },
652
+ { ALT: () => this.CONSUME(tokens.EmailEffect) },
653
+ { ALT: () => this.CONSUME(tokens.ErrorEffect) },
654
+ { ALT: () => this.CONSUME(tokens.Cache) },
655
+ { ALT: () => this.CONSUME(tokens.Queue) },
656
+ { ALT: () => this.CONSUME(tokens.External) },
657
+ { ALT: () => this.CONSUME(tokens.Identifier) },
658
+ ]);
659
+ });
660
+ returnType = this.RULE('returnType', () => {
661
+ this.OR([
662
+ // Void
663
+ { ALT: () => this.CONSUME(tokens.Void) },
664
+ // Array type
665
+ {
666
+ ALT: () => {
667
+ this.CONSUME(tokens.LBracket);
668
+ this.SUBRULE(this.returnTypeInner);
669
+ this.CONSUME(tokens.RBracket);
670
+ },
671
+ },
672
+ // Object type
673
+ {
674
+ ALT: () => {
675
+ this.CONSUME(tokens.LBrace);
676
+ this.SUBRULE(this.objectTypeFields);
677
+ this.CONSUME(tokens.RBrace);
678
+ },
679
+ },
680
+ // Simple type
681
+ { ALT: () => this.SUBRULE2(this.returnTypeInner) },
682
+ ]);
683
+ });
684
+ returnTypeInner = this.RULE('returnTypeInner', () => {
685
+ this.OR([
686
+ { ALT: () => this.CONSUME(tokens.PascalIdentifier) },
687
+ { ALT: () => this.CONSUME(tokens.String) },
688
+ { ALT: () => this.CONSUME(tokens.Int) },
689
+ { ALT: () => this.CONSUME(tokens.Bool) },
690
+ ]);
691
+ // Optional join with another type
692
+ this.OPTION(() => {
693
+ this.CONSUME(tokens.Ampersand);
694
+ this.CONSUME(tokens.LBrace);
695
+ this.SUBRULE(this.objectTypeFields);
696
+ this.CONSUME(tokens.RBrace);
697
+ });
698
+ });
699
+ objectTypeFields = this.RULE('objectTypeFields', () => {
700
+ this.SUBRULE(this.objectTypeField);
701
+ this.MANY(() => {
702
+ this.CONSUME(tokens.Comma);
703
+ this.SUBRULE2(this.objectTypeField);
704
+ });
705
+ });
706
+ objectTypeField = this.RULE('objectTypeField', () => {
707
+ this.CONSUME(tokens.Identifier);
708
+ this.CONSUME(tokens.Colon);
709
+ this.SUBRULE(this.returnTypeInner);
710
+ });
711
+ // ---------------------------------------------------------------------------
712
+ // Journey Block
713
+ // ---------------------------------------------------------------------------
714
+ journeyBlock = this.RULE('journeyBlock', () => {
715
+ this.CONSUME(tokens.Journey);
716
+ this.CONSUME(tokens.PascalIdentifier);
717
+ this.CONSUME(tokens.LBrace);
718
+ this.MANY(() => {
719
+ this.SUBRULE(this.journeyProperty);
720
+ });
721
+ this.CONSUME(tokens.RBrace);
722
+ });
723
+ journeyProperty = this.RULE('journeyProperty', () => {
724
+ this.OR([
725
+ {
726
+ ALT: () => {
727
+ this.CONSUME(tokens.Actor);
728
+ this.CONSUME(tokens.Colon);
729
+ this.CONSUME(tokens.Identifier);
730
+ },
731
+ },
732
+ {
733
+ ALT: () => {
734
+ this.CONSUME(tokens.Steps);
735
+ this.CONSUME(tokens.LBrace);
736
+ this.MANY(() => {
737
+ this.SUBRULE(this.journeyStep);
738
+ });
739
+ this.CONSUME(tokens.RBrace);
740
+ },
741
+ },
742
+ ]);
743
+ });
744
+ journeyStep = this.RULE('journeyStep', () => {
745
+ this.CONSUME(tokens.Identifier);
746
+ this.CONSUME(tokens.LBrace);
747
+ this.MANY_SEP({
748
+ SEP: tokens.Comma,
749
+ DEF: () => this.SUBRULE(this.journeyStepProperty),
750
+ });
751
+ this.CONSUME(tokens.RBrace);
752
+ });
753
+ journeyStepProperty = this.RULE('journeyStepProperty', () => {
754
+ this.OR([
755
+ {
756
+ ALT: () => {
757
+ this.CONSUME(tokens.Page);
758
+ this.CONSUME(tokens.Colon);
759
+ this.CONSUME(tokens.PascalIdentifier);
760
+ },
761
+ },
762
+ {
763
+ ALT: () => {
764
+ this.CONSUME(tokens.Next);
765
+ this.CONSUME2(tokens.Colon);
766
+ this.CONSUME(tokens.Identifier);
767
+ },
768
+ },
769
+ {
770
+ ALT: () => {
771
+ this.CONSUME(tokens.WaitFor);
772
+ this.CONSUME3(tokens.Colon);
773
+ this.CONSUME2(tokens.Identifier);
774
+ },
775
+ },
776
+ {
777
+ ALT: () => {
778
+ this.CONSUME(tokens.Action);
779
+ this.CONSUME4(tokens.Colon);
780
+ this.CONSUME3(tokens.Identifier);
781
+ },
782
+ },
783
+ {
784
+ ALT: () => {
785
+ this.CONSUME(tokens.Condition);
786
+ this.CONSUME5(tokens.Colon);
787
+ this.CONSUME4(tokens.Identifier);
788
+ },
789
+ },
790
+ ]);
791
+ });
792
+ // ---------------------------------------------------------------------------
793
+ // Page Block
794
+ // ---------------------------------------------------------------------------
795
+ pageBlock = this.RULE('pageBlock', () => {
796
+ this.CONSUME(tokens.Page);
797
+ this.CONSUME(tokens.PascalIdentifier);
798
+ this.CONSUME(tokens.LBrace);
799
+ this.MANY(() => {
800
+ this.SUBRULE(this.pageProperty);
801
+ });
802
+ this.CONSUME(tokens.RBrace);
803
+ });
804
+ pageProperty = this.RULE('pageProperty', () => {
805
+ this.OR([
806
+ {
807
+ ALT: () => {
808
+ this.CONSUME(tokens.Route);
809
+ this.CONSUME(tokens.Colon);
810
+ this.CONSUME(tokens.StringLiteral);
811
+ },
812
+ },
813
+ {
814
+ ALT: () => {
815
+ this.CONSUME(tokens.Layout);
816
+ this.CONSUME2(tokens.Colon);
817
+ this.CONSUME(tokens.PascalIdentifier);
818
+ },
819
+ },
820
+ {
821
+ ALT: () => {
822
+ this.CONSUME(tokens.Loader);
823
+ this.CONSUME3(tokens.Colon);
824
+ this.CONSUME(tokens.Identifier);
825
+ },
826
+ },
827
+ {
828
+ ALT: () => {
829
+ this.CONSUME(tokens.Guard);
830
+ this.CONSUME4(tokens.Colon);
831
+ this.CONSUME2(tokens.Identifier);
832
+ },
833
+ },
834
+ {
835
+ ALT: () => {
836
+ this.CONSUME(tokens.Meta);
837
+ this.CONSUME(tokens.LBrace);
838
+ this.MANY(() => {
839
+ this.SUBRULE(this.metaProperty);
840
+ });
841
+ this.CONSUME(tokens.RBrace);
842
+ },
843
+ },
844
+ ]);
845
+ });
846
+ metaProperty = this.RULE('metaProperty', () => {
847
+ this.CONSUME(tokens.Identifier);
848
+ this.CONSUME(tokens.Colon);
849
+ this.CONSUME(tokens.StringLiteral);
850
+ });
851
+ // ---------------------------------------------------------------------------
852
+ // Component Block
853
+ // ---------------------------------------------------------------------------
854
+ componentBlock = this.RULE('componentBlock', () => {
855
+ this.CONSUME(tokens.Component);
856
+ this.CONSUME(tokens.PascalIdentifier);
857
+ this.CONSUME(tokens.LBrace);
858
+ this.MANY(() => {
859
+ this.SUBRULE(this.componentProperty);
860
+ });
861
+ this.CONSUME(tokens.RBrace);
862
+ });
863
+ componentProperty = this.RULE('componentProperty', () => {
864
+ this.OR([
865
+ {
866
+ ALT: () => {
867
+ this.CONSUME(tokens.Props);
868
+ this.CONSUME(tokens.LBrace);
869
+ this.MANY(() => {
870
+ this.SUBRULE(this.propDefinition);
871
+ });
872
+ this.CONSUME(tokens.RBrace);
873
+ },
874
+ },
875
+ {
876
+ ALT: () => {
877
+ this.CONSUME(tokens.State);
878
+ this.CONSUME2(tokens.LBrace);
879
+ this.MANY2(() => {
880
+ this.SUBRULE(this.stateDefinition);
881
+ });
882
+ this.CONSUME2(tokens.RBrace);
883
+ },
884
+ },
885
+ {
886
+ ALT: () => {
887
+ this.CONSUME(tokens.Handlers);
888
+ this.CONSUME3(tokens.LBrace);
889
+ this.MANY3(() => {
890
+ this.SUBRULE(this.handlerDefinition);
891
+ });
892
+ this.CONSUME3(tokens.RBrace);
893
+ },
894
+ },
895
+ ]);
896
+ });
897
+ propDefinition = this.RULE('propDefinition', () => {
898
+ this.CONSUME(tokens.Identifier);
899
+ this.CONSUME(tokens.Colon);
900
+ this.SUBRULE(this.parameterType);
901
+ this.OPTION(() => {
902
+ this.CONSUME(tokens.Question);
903
+ });
904
+ });
905
+ stateDefinition = this.RULE('stateDefinition', () => {
906
+ this.CONSUME(tokens.Identifier);
907
+ this.CONSUME(tokens.Colon);
908
+ this.SUBRULE(this.parameterType);
909
+ this.OPTION(() => {
910
+ this.CONSUME(tokens.Equals);
911
+ this.SUBRULE(this.defaultValue);
912
+ });
913
+ });
914
+ handlerDefinition = this.RULE('handlerDefinition', () => {
915
+ this.CONSUME(tokens.Identifier);
916
+ this.CONSUME(tokens.Colon);
917
+ this.SUBRULE(this.effectAnnotation);
918
+ this.CONSUME(tokens.Void);
919
+ });
920
+ // ---------------------------------------------------------------------------
921
+ // Form Block
922
+ // ---------------------------------------------------------------------------
923
+ formBlock = this.RULE('formBlock', () => {
924
+ this.CONSUME(tokens.Form);
925
+ this.CONSUME(tokens.PascalIdentifier);
926
+ this.CONSUME(tokens.LBrace);
927
+ this.MANY(() => {
928
+ this.SUBRULE(this.formProperty);
929
+ });
930
+ this.CONSUME(tokens.RBrace);
931
+ });
932
+ formProperty = this.RULE('formProperty', () => {
933
+ this.OR([
934
+ {
935
+ ALT: () => {
936
+ this.CONSUME(tokens.Fields);
937
+ this.CONSUME(tokens.LBrace);
938
+ this.MANY(() => {
939
+ this.SUBRULE(this.formFieldDefinition);
940
+ });
941
+ this.CONSUME(tokens.RBrace);
942
+ },
943
+ },
944
+ {
945
+ ALT: () => {
946
+ this.CONSUME(tokens.Submit);
947
+ this.CONSUME2(tokens.LBrace);
948
+ this.MANY2(() => {
949
+ this.SUBRULE(this.submitProperty);
950
+ });
951
+ this.CONSUME2(tokens.RBrace);
952
+ },
953
+ },
954
+ {
955
+ ALT: () => {
956
+ this.CONSUME(tokens.Validation);
957
+ this.CONSUME3(tokens.LBrace);
958
+ this.MANY3(() => {
959
+ this.SUBRULE(this.validationRule);
960
+ });
961
+ this.CONSUME3(tokens.RBrace);
962
+ },
963
+ },
964
+ ]);
965
+ });
966
+ formFieldDefinition = this.RULE('formFieldDefinition', () => {
967
+ this.CONSUME(tokens.Identifier);
968
+ this.CONSUME(tokens.Colon);
969
+ this.SUBRULE(this.formFieldType);
970
+ this.OPTION(() => {
971
+ this.CONSUME(tokens.LBrace);
972
+ this.MANY(() => {
973
+ this.SUBRULE(this.formFieldProperty);
974
+ });
975
+ this.CONSUME(tokens.RBrace);
976
+ });
977
+ });
978
+ formFieldType = this.RULE('formFieldType', () => {
979
+ this.OR([
980
+ { ALT: () => this.CONSUME(tokens.Text) },
981
+ { ALT: () => this.CONSUME(tokens.Textarea) },
982
+ { ALT: () => this.CONSUME(tokens.Email) },
983
+ { ALT: () => this.CONSUME(tokens.Password) },
984
+ { ALT: () => this.CONSUME(tokens.Number) },
985
+ { ALT: () => this.CONSUME(tokens.Checkbox) },
986
+ { ALT: () => this.CONSUME(tokens.Radio) },
987
+ { ALT: () => this.CONSUME(tokens.Select) },
988
+ { ALT: () => this.CONSUME(tokens.Date) },
989
+ { ALT: () => this.CONSUME(tokens.File) },
990
+ ]);
991
+ });
992
+ formFieldProperty = this.RULE('formFieldProperty', () => {
993
+ this.OR([
994
+ {
995
+ ALT: () => {
996
+ this.CONSUME(tokens.Label);
997
+ this.CONSUME(tokens.Colon);
998
+ this.CONSUME(tokens.StringLiteral);
999
+ },
1000
+ },
1001
+ {
1002
+ ALT: () => {
1003
+ this.CONSUME(tokens.Placeholder);
1004
+ this.CONSUME2(tokens.Colon);
1005
+ this.CONSUME2(tokens.StringLiteral);
1006
+ },
1007
+ },
1008
+ {
1009
+ ALT: () => {
1010
+ this.CONSUME(tokens.Required);
1011
+ this.CONSUME3(tokens.Colon);
1012
+ this.OR2([
1013
+ { ALT: () => this.CONSUME(tokens.True) },
1014
+ { ALT: () => this.CONSUME(tokens.False) },
1015
+ ]);
1016
+ },
1017
+ },
1018
+ {
1019
+ ALT: () => {
1020
+ this.CONSUME(tokens.Rows);
1021
+ this.CONSUME4(tokens.Colon);
1022
+ this.CONSUME(tokens.IntegerLiteral);
1023
+ },
1024
+ },
1025
+ {
1026
+ ALT: () => {
1027
+ this.CONSUME(tokens.Options);
1028
+ this.CONSUME(tokens.LBracket);
1029
+ this.MANY_SEP({
1030
+ SEP: tokens.Comma,
1031
+ DEF: () => this.CONSUME3(tokens.StringLiteral),
1032
+ });
1033
+ this.CONSUME(tokens.RBracket);
1034
+ },
1035
+ },
1036
+ ]);
1037
+ });
1038
+ submitProperty = this.RULE('submitProperty', () => {
1039
+ this.OR([
1040
+ {
1041
+ ALT: () => {
1042
+ this.CONSUME(tokens.Action);
1043
+ this.CONSUME(tokens.Colon);
1044
+ this.CONSUME(tokens.Identifier);
1045
+ this.CONSUME(tokens.Dot);
1046
+ this.CONSUME2(tokens.Identifier);
1047
+ },
1048
+ },
1049
+ {
1050
+ ALT: () => {
1051
+ this.CONSUME(tokens.Button);
1052
+ this.CONSUME2(tokens.Colon);
1053
+ this.CONSUME(tokens.StringLiteral);
1054
+ },
1055
+ },
1056
+ {
1057
+ ALT: () => {
1058
+ this.CONSUME(tokens.OnSuccess);
1059
+ this.CONSUME3(tokens.Colon);
1060
+ this.CONSUME3(tokens.Identifier);
1061
+ },
1062
+ },
1063
+ {
1064
+ ALT: () => {
1065
+ this.CONSUME(tokens.OnError);
1066
+ this.CONSUME4(tokens.Colon);
1067
+ this.CONSUME4(tokens.Identifier);
1068
+ },
1069
+ },
1070
+ ]);
1071
+ });
1072
+ validationRule = this.RULE('validationRule', () => {
1073
+ this.CONSUME(tokens.Identifier);
1074
+ this.CONSUME(tokens.Colon);
1075
+ this.CONSUME2(tokens.Identifier);
1076
+ });
1077
+ // ---------------------------------------------------------------------------
1078
+ // Design Block
1079
+ // ---------------------------------------------------------------------------
1080
+ designBlock = this.RULE('designBlock', () => {
1081
+ this.CONSUME(tokens.Design);
1082
+ this.CONSUME(tokens.LBrace);
1083
+ this.MANY(() => {
1084
+ this.SUBRULE(this.designSection);
1085
+ });
1086
+ this.CONSUME(tokens.RBrace);
1087
+ });
1088
+ designSection = this.RULE('designSection', () => {
1089
+ this.OR([
1090
+ {
1091
+ ALT: () => {
1092
+ this.CONSUME(tokens.Colors);
1093
+ this.CONSUME(tokens.LBrace);
1094
+ this.MANY(() => {
1095
+ this.SUBRULE(this.colorDefinition);
1096
+ });
1097
+ this.CONSUME(tokens.RBrace);
1098
+ },
1099
+ },
1100
+ {
1101
+ ALT: () => {
1102
+ this.CONSUME(tokens.Typography);
1103
+ this.CONSUME2(tokens.LBrace);
1104
+ this.MANY2(() => {
1105
+ this.SUBRULE(this.typographyDefinition);
1106
+ });
1107
+ this.CONSUME2(tokens.RBrace);
1108
+ },
1109
+ },
1110
+ {
1111
+ ALT: () => {
1112
+ this.CONSUME(tokens.Spacing);
1113
+ this.CONSUME3(tokens.LBrace);
1114
+ this.MANY3(() => {
1115
+ this.SUBRULE(this.spacingDefinition);
1116
+ });
1117
+ this.CONSUME3(tokens.RBrace);
1118
+ },
1119
+ },
1120
+ {
1121
+ ALT: () => {
1122
+ this.CONSUME(tokens.Animation);
1123
+ this.CONSUME4(tokens.LBrace);
1124
+ this.MANY4(() => {
1125
+ this.SUBRULE(this.animationDefinition);
1126
+ });
1127
+ this.CONSUME4(tokens.RBrace);
1128
+ },
1129
+ },
1130
+ {
1131
+ ALT: () => {
1132
+ this.CONSUME(tokens.Dark);
1133
+ this.CONSUME5(tokens.LBrace);
1134
+ this.MANY5(() => {
1135
+ this.SUBRULE2(this.colorDefinition);
1136
+ });
1137
+ this.CONSUME5(tokens.RBrace);
1138
+ },
1139
+ },
1140
+ ]);
1141
+ });
1142
+ colorDefinition = this.RULE('colorDefinition', () => {
1143
+ this.SUBRULE(this.fieldName);
1144
+ this.CONSUME(tokens.Colon);
1145
+ this.CONSUME(tokens.HexColor);
1146
+ });
1147
+ typographyDefinition = this.RULE('typographyDefinition', () => {
1148
+ this.CONSUME(tokens.Identifier);
1149
+ this.CONSUME(tokens.Colon);
1150
+ this.OR([
1151
+ { ALT: () => this.CONSUME(tokens.StringLiteral) },
1152
+ { ALT: () => this.CONSUME(tokens.SizeLiteral) },
1153
+ { ALT: () => this.CONSUME(tokens.IntegerLiteral) },
1154
+ ]);
1155
+ });
1156
+ spacingDefinition = this.RULE('spacingDefinition', () => {
1157
+ this.CONSUME(tokens.Identifier);
1158
+ this.CONSUME(tokens.Colon);
1159
+ this.CONSUME(tokens.SizeLiteral);
1160
+ });
1161
+ animationDefinition = this.RULE('animationDefinition', () => {
1162
+ this.CONSUME(tokens.Identifier);
1163
+ this.CONSUME(tokens.Colon);
1164
+ this.OR([
1165
+ { ALT: () => this.CONSUME(tokens.DurationLiteral) },
1166
+ { ALT: () => this.CONSUME(tokens.CSSFunctionLiteral) },
1167
+ { ALT: () => this.CONSUME(tokens.StringLiteral) },
1168
+ ]);
1169
+ });
1170
+ }
1171
+ // =============================================================================
1172
+ // Parser Instance
1173
+ // =============================================================================
1174
+ const parser = new KappaParser();
1175
+ // =============================================================================
1176
+ // CST Visitor for AST Construction
1177
+ // =============================================================================
1178
+ const BaseCstVisitor = parser.getBaseCstVisitorConstructor();
1179
+ class KappaAstBuilder extends BaseCstVisitor {
1180
+ constructor() {
1181
+ super();
1182
+ this.validateVisitor();
1183
+ }
1184
+ // Helper: Extract source location from tokens
1185
+ getLocation(ctx) {
1186
+ const firstToken = this.findFirstToken(ctx);
1187
+ const lastToken = this.findLastToken(ctx);
1188
+ if (!firstToken || !lastToken) {
1189
+ return {
1190
+ startLine: 0,
1191
+ startColumn: 0,
1192
+ endLine: 0,
1193
+ endColumn: 0,
1194
+ startOffset: 0,
1195
+ endOffset: 0,
1196
+ };
1197
+ }
1198
+ return {
1199
+ startLine: firstToken.startLine ?? 0,
1200
+ startColumn: firstToken.startColumn ?? 0,
1201
+ endLine: lastToken.endLine ?? 0,
1202
+ endColumn: lastToken.endColumn ?? 0,
1203
+ startOffset: firstToken.startOffset,
1204
+ endOffset: lastToken.endOffset ?? firstToken.startOffset,
1205
+ };
1206
+ }
1207
+ findFirstToken(ctx) {
1208
+ let first = null;
1209
+ for (const key of Object.keys(ctx)) {
1210
+ const val = ctx[key];
1211
+ if (Array.isArray(val) && val.length > 0) {
1212
+ const token = val[0];
1213
+ if (token && typeof token === 'object' && 'startOffset' in token) {
1214
+ if (!first || token.startOffset < first.startOffset) {
1215
+ first = token;
1216
+ }
1217
+ }
1218
+ }
1219
+ }
1220
+ return first;
1221
+ }
1222
+ findLastToken(ctx) {
1223
+ let last = null;
1224
+ for (const key of Object.keys(ctx)) {
1225
+ const val = ctx[key];
1226
+ if (Array.isArray(val) && val.length > 0) {
1227
+ const token = val[val.length - 1];
1228
+ if (token && typeof token === 'object' && 'endOffset' in token) {
1229
+ const endOffset = token.endOffset ?? token.startOffset;
1230
+ if (!last || endOffset > (last.endOffset ?? last.startOffset)) {
1231
+ last = token;
1232
+ }
1233
+ }
1234
+ }
1235
+ }
1236
+ return last;
1237
+ }
1238
+ // Root visitor
1239
+ kappaSpec(ctx) {
1240
+ const project = ctx.projectBlock ? this.visit(ctx.projectBlock) : undefined;
1241
+ const auth = ctx.authBlock ? this.visit(ctx.authBlock) : undefined;
1242
+ const entities = ctx.entityBlock
1243
+ ? ctx.entityBlock.map((e) => this.visit(e))
1244
+ : [];
1245
+ const apis = ctx.apiBlock ? ctx.apiBlock.map((a) => this.visit(a)) : [];
1246
+ const journeys = ctx.journeyBlock
1247
+ ? ctx.journeyBlock.map((j) => this.visit(j))
1248
+ : [];
1249
+ const pages = ctx.pageBlock ? ctx.pageBlock.map((p) => this.visit(p)) : [];
1250
+ const components = ctx.componentBlock
1251
+ ? ctx.componentBlock.map((c) => this.visit(c))
1252
+ : [];
1253
+ const forms = ctx.formBlock ? ctx.formBlock.map((f) => this.visit(f)) : [];
1254
+ const design = ctx.designBlock ? this.visit(ctx.designBlock) : undefined;
1255
+ return {
1256
+ kind: 'KappaSpec',
1257
+ loc: this.getLocation(ctx),
1258
+ project,
1259
+ auth,
1260
+ entities,
1261
+ apis,
1262
+ journeys,
1263
+ pages,
1264
+ components,
1265
+ forms,
1266
+ design,
1267
+ };
1268
+ }
1269
+ // Project block visitor
1270
+ projectBlock(ctx) {
1271
+ const name = (ctx.PascalIdentifier?.[0])?.image ?? '';
1272
+ const properties = ctx.projectProperty
1273
+ ? ctx.projectProperty.map((p) => this.visit(p))
1274
+ : [];
1275
+ const block = {
1276
+ kind: 'ProjectBlock',
1277
+ loc: this.getLocation(ctx),
1278
+ name,
1279
+ stack: 'tanstack-start', // Default
1280
+ };
1281
+ for (const prop of properties) {
1282
+ if (prop.type === 'version')
1283
+ block.version = prop.value;
1284
+ if (prop.type === 'stack')
1285
+ block.stack = prop.value;
1286
+ if (prop.type === 'database')
1287
+ block.database = prop.value;
1288
+ if (prop.type === 'auth')
1289
+ block.auth = prop.value;
1290
+ if (prop.type === 'ui')
1291
+ block.ui = prop.value;
1292
+ }
1293
+ return block;
1294
+ }
1295
+ projectProperty(ctx) {
1296
+ if (ctx.Version) {
1297
+ const value = (ctx.StringLiteral?.[0])?.image ?? '';
1298
+ return { type: 'version', value: value.slice(1, -1) }; // Remove quotes
1299
+ }
1300
+ if (ctx.Stack) {
1301
+ return { type: 'stack', value: this.visit(ctx.stackValue) };
1302
+ }
1303
+ if (ctx.Database) {
1304
+ return { type: 'database', value: this.visit(ctx.databaseValue) };
1305
+ }
1306
+ if (ctx.Auth) {
1307
+ return { type: 'auth', value: this.visit(ctx.authTypeValue) };
1308
+ }
1309
+ if (ctx.Ui) {
1310
+ return { type: 'ui', value: this.visit(ctx.uiValue) };
1311
+ }
1312
+ return { type: 'unknown', value: '' };
1313
+ }
1314
+ stackValue(ctx) {
1315
+ if (ctx.TanstackStart)
1316
+ return 'tanstack-start';
1317
+ if (ctx.Nextjs)
1318
+ return 'next';
1319
+ if (ctx.Remix)
1320
+ return 'remix';
1321
+ if (ctx.Sveltekit)
1322
+ return 'sveltekit';
1323
+ if (ctx.Astro)
1324
+ return 'astro';
1325
+ if (ctx.Express)
1326
+ return 'express';
1327
+ if (ctx.Hono)
1328
+ return 'hono';
1329
+ if (ctx.Elysia)
1330
+ return 'elysia';
1331
+ return 'tanstack-start';
1332
+ }
1333
+ databaseValue(ctx) {
1334
+ if (ctx.Postgres)
1335
+ return 'postgres';
1336
+ if (ctx.Mysql)
1337
+ return 'mysql';
1338
+ if (ctx.Sqlite)
1339
+ return 'sqlite';
1340
+ if (ctx.Turso)
1341
+ return 'turso';
1342
+ if (ctx.Mongodb)
1343
+ return 'mongodb';
1344
+ return 'postgres';
1345
+ }
1346
+ authTypeValue(ctx) {
1347
+ if (ctx.Jwt)
1348
+ return 'jwt';
1349
+ if (ctx.Session)
1350
+ return 'session';
1351
+ if (ctx.Oauth)
1352
+ return 'oauth';
1353
+ return 'jwt';
1354
+ }
1355
+ uiValue(ctx) {
1356
+ if (ctx.Shadcn)
1357
+ return 'shadcn';
1358
+ if (ctx.Mui)
1359
+ return 'mui';
1360
+ if (ctx.Chakra)
1361
+ return 'chakra';
1362
+ if (ctx.TailwindOnly)
1363
+ return 'tailwind-only';
1364
+ return 'shadcn';
1365
+ }
1366
+ // Auth block visitor
1367
+ authBlock(ctx) {
1368
+ const type = ctx.Jwt ? 'jwt' : 'session';
1369
+ const properties = ctx.authProperty
1370
+ ? ctx.authProperty.map((p) => this.visit(p))
1371
+ : [];
1372
+ const block = {
1373
+ kind: 'AuthBlock',
1374
+ loc: this.getLocation(ctx),
1375
+ type,
1376
+ };
1377
+ if (type === 'jwt') {
1378
+ const jwt = {
1379
+ kind: 'JWTConfig',
1380
+ loc: this.getLocation(ctx),
1381
+ secret: '',
1382
+ expires: '7d',
1383
+ hash: { kind: 'HashConfig', loc: this.getLocation(ctx), algorithm: 'argon2id' },
1384
+ };
1385
+ for (const prop of properties) {
1386
+ if (prop.type === 'secret')
1387
+ jwt.secret = prop.value;
1388
+ if (prop.type === 'issuer')
1389
+ jwt.issuer = prop.value;
1390
+ if (prop.type === 'audience')
1391
+ jwt.audience = prop.value;
1392
+ if (prop.type === 'expires')
1393
+ jwt.expires = prop.value;
1394
+ if (prop.type === 'refresh')
1395
+ jwt.refresh = prop.value;
1396
+ if (prop.type === 'hash')
1397
+ jwt.hash = prop.value;
1398
+ }
1399
+ block.jwt = jwt;
1400
+ }
1401
+ else {
1402
+ const session = {
1403
+ kind: 'SessionConfig',
1404
+ loc: this.getLocation(ctx),
1405
+ duration: '24h',
1406
+ hash: { kind: 'HashConfig', loc: this.getLocation(ctx), algorithm: 'argon2id' },
1407
+ };
1408
+ for (const prop of properties) {
1409
+ if (prop.type === 'duration')
1410
+ session.duration = prop.value;
1411
+ if (prop.type === 'hash')
1412
+ session.hash = prop.value;
1413
+ }
1414
+ block.session = session;
1415
+ }
1416
+ return block;
1417
+ }
1418
+ authProperty(ctx) {
1419
+ if (ctx.Secret) {
1420
+ return { type: 'secret', value: this.visit(ctx.envCall) };
1421
+ }
1422
+ if (ctx.Issuer) {
1423
+ const value = (ctx.StringLiteral?.[0])?.image ?? '';
1424
+ return { type: 'issuer', value: value.slice(1, -1) };
1425
+ }
1426
+ if (ctx.Audience) {
1427
+ const value = (ctx.StringLiteral?.[0])?.image ?? '';
1428
+ return { type: 'audience', value: value.slice(1, -1) };
1429
+ }
1430
+ if (ctx.Expires) {
1431
+ return { type: 'expires', value: (ctx.DurationLiteral?.[0])?.image ?? '7d' };
1432
+ }
1433
+ if (ctx.Refresh) {
1434
+ return { type: 'refresh', value: this.visit(ctx.refreshValue) };
1435
+ }
1436
+ if (ctx.Hash) {
1437
+ return { type: 'hash', value: this.visit(ctx.hashConfig) };
1438
+ }
1439
+ if (ctx.Duration) {
1440
+ return { type: 'duration', value: (ctx.DurationLiteral?.[0])?.image ?? '24h' };
1441
+ }
1442
+ return { type: 'unknown', value: '' };
1443
+ }
1444
+ envCall(ctx) {
1445
+ return ctx.ConstantIdentifier?.[0]?.image ?? ctx.PascalIdentifier?.[0]?.image ?? ctx.Identifier?.[0]?.image ?? '';
1446
+ }
1447
+ refreshValue(ctx) {
1448
+ if (ctx.Sliding)
1449
+ return 'sliding';
1450
+ if (ctx.Fixed)
1451
+ return 'fixed';
1452
+ if (ctx.None)
1453
+ return 'none';
1454
+ return 'sliding';
1455
+ }
1456
+ hashConfig(ctx) {
1457
+ const algorithm = ctx.Argon2id ? 'argon2id' : 'bcrypt';
1458
+ const config = {
1459
+ kind: 'HashConfig',
1460
+ loc: this.getLocation(ctx),
1461
+ algorithm,
1462
+ };
1463
+ if (ctx.hashParams) {
1464
+ const params = this.visit(ctx.hashParams);
1465
+ Object.assign(config, params);
1466
+ }
1467
+ if (ctx.IntegerLiteral && algorithm === 'bcrypt') {
1468
+ config.rounds = parseInt((ctx.IntegerLiteral?.[0])?.image ?? '10', 10);
1469
+ }
1470
+ return config;
1471
+ }
1472
+ hashParams(ctx) {
1473
+ const params = {};
1474
+ if (ctx.Memory) {
1475
+ params.memory = (ctx.SizeLiteral?.[0])?.image;
1476
+ }
1477
+ if (ctx.Iterations) {
1478
+ params.iterations = parseInt((ctx.IntegerLiteral?.[0])?.image ?? '3', 10);
1479
+ }
1480
+ if (ctx.Parallelism) {
1481
+ params.parallelism = parseInt((ctx.IntegerLiteral?.[1] ?? ctx.IntegerLiteral?.[0])?.image ?? '1', 10);
1482
+ }
1483
+ return params;
1484
+ }
1485
+ // Entity block visitor (placeholder - will be extended)
1486
+ entityBlock(ctx) {
1487
+ const name = (ctx.PascalIdentifier?.[0])?.image ?? '';
1488
+ const fields = ctx.entityField
1489
+ ? ctx.entityField.map((f) => this.visit(f))
1490
+ : [];
1491
+ const relationships = ctx.entityRelationship
1492
+ ? ctx.entityRelationship.map((r) => this.visit(r))
1493
+ : [];
1494
+ const capabilities = ctx.entityCapability
1495
+ ? ctx.entityCapability.map((c) => this.visit(c))
1496
+ : [];
1497
+ const lifecycleHooks = ctx.entityLifecycleHook
1498
+ ? ctx.entityLifecycleHook.map((h) => this.visit(h))
1499
+ : [];
1500
+ return {
1501
+ kind: 'EntityBlock',
1502
+ loc: this.getLocation(ctx),
1503
+ name,
1504
+ fields,
1505
+ relationships,
1506
+ capabilities,
1507
+ lifecycleHooks,
1508
+ indexes: [],
1509
+ };
1510
+ }
1511
+ entityField(ctx) {
1512
+ const name = ctx.fieldName ? this.visit(ctx.fieldName) : '';
1513
+ const type = this.visit(ctx.fieldType);
1514
+ const modifiers = ctx.fieldModifier
1515
+ ? ctx.fieldModifier.map((m) => this.visit(m))
1516
+ : [];
1517
+ const defaultValue = ctx.defaultValue ? this.visit(ctx.defaultValue) : undefined;
1518
+ return {
1519
+ kind: 'EntityField',
1520
+ loc: this.getLocation(ctx),
1521
+ name,
1522
+ type,
1523
+ modifiers,
1524
+ defaultValue,
1525
+ };
1526
+ }
1527
+ fieldName(ctx) {
1528
+ // Extract the image from whichever token was matched
1529
+ const tokenTypes = [
1530
+ 'Identifier',
1531
+ 'Email',
1532
+ 'Password',
1533
+ 'String',
1534
+ 'Int',
1535
+ 'Float',
1536
+ 'Bool',
1537
+ 'Url',
1538
+ 'Uuid',
1539
+ 'Phone',
1540
+ 'Slug',
1541
+ 'Markdown',
1542
+ 'Json',
1543
+ 'Timestamp',
1544
+ 'Date',
1545
+ 'Time',
1546
+ 'Text',
1547
+ 'Primary',
1548
+ ];
1549
+ for (const tokenType of tokenTypes) {
1550
+ const token = ctx[tokenType]?.[0];
1551
+ if (token) {
1552
+ return token.image;
1553
+ }
1554
+ }
1555
+ return '';
1556
+ }
1557
+ fieldType(ctx) {
1558
+ // Check for array type first
1559
+ if (ctx.innerFieldType) {
1560
+ const itemType = this.visit(ctx.innerFieldType);
1561
+ return {
1562
+ kind: 'array',
1563
+ itemType,
1564
+ };
1565
+ }
1566
+ // Check for enum type (multiple identifiers with pipes)
1567
+ if (ctx.Pipe) {
1568
+ const identifiers = ctx.Identifier;
1569
+ return {
1570
+ kind: 'enum',
1571
+ values: identifiers.map((t) => t.image),
1572
+ };
1573
+ }
1574
+ // Check for string with range constraint
1575
+ if (ctx.rangeConstraint) {
1576
+ const range = this.visit(ctx.rangeConstraint);
1577
+ return {
1578
+ kind: 'primitive',
1579
+ type: 'string',
1580
+ range,
1581
+ };
1582
+ }
1583
+ // Check for reference type
1584
+ if (ctx.PascalIdentifier) {
1585
+ return {
1586
+ kind: 'reference',
1587
+ entity: (ctx.PascalIdentifier?.[0])?.image ?? '',
1588
+ optional: false,
1589
+ };
1590
+ }
1591
+ // Primitive types
1592
+ const primitiveMap = {
1593
+ String: 'string',
1594
+ Int: 'int',
1595
+ Float: 'float',
1596
+ Bool: 'bool',
1597
+ Email: 'email',
1598
+ Url: 'url',
1599
+ Uuid: 'uuid',
1600
+ Phone: 'phone',
1601
+ Slug: 'slug',
1602
+ Markdown: 'markdown',
1603
+ Json: 'json',
1604
+ Timestamp: 'timestamp',
1605
+ Date: 'date',
1606
+ Time: 'time',
1607
+ };
1608
+ for (const [key, value] of Object.entries(primitiveMap)) {
1609
+ if (ctx[key]) {
1610
+ return { kind: 'primitive', type: value };
1611
+ }
1612
+ }
1613
+ return { kind: 'primitive', type: 'string' };
1614
+ }
1615
+ // Inner field type visitor (for array item types - same logic without array recursion)
1616
+ innerFieldType(ctx) {
1617
+ // Check for enum type (multiple identifiers with pipes)
1618
+ if (ctx.Pipe) {
1619
+ const identifiers = ctx.Identifier;
1620
+ return {
1621
+ kind: 'enum',
1622
+ values: identifiers.map((t) => t.image),
1623
+ };
1624
+ }
1625
+ // Check for string with range constraint
1626
+ if (ctx.rangeConstraint) {
1627
+ const range = this.visit(ctx.rangeConstraint);
1628
+ return {
1629
+ kind: 'primitive',
1630
+ type: 'string',
1631
+ range,
1632
+ };
1633
+ }
1634
+ // Check for reference type
1635
+ if (ctx.PascalIdentifier) {
1636
+ return {
1637
+ kind: 'reference',
1638
+ entity: (ctx.PascalIdentifier?.[0])?.image ?? '',
1639
+ optional: false,
1640
+ };
1641
+ }
1642
+ // Primitive types
1643
+ const primitiveMap = {
1644
+ String: 'string',
1645
+ Int: 'int',
1646
+ Float: 'float',
1647
+ Bool: 'bool',
1648
+ Email: 'email',
1649
+ Url: 'url',
1650
+ Uuid: 'uuid',
1651
+ Phone: 'phone',
1652
+ Slug: 'slug',
1653
+ Markdown: 'markdown',
1654
+ Json: 'json',
1655
+ Timestamp: 'timestamp',
1656
+ Date: 'date',
1657
+ Time: 'time',
1658
+ };
1659
+ for (const [key, value] of Object.entries(primitiveMap)) {
1660
+ if (ctx[key]) {
1661
+ return { kind: 'primitive', type: value };
1662
+ }
1663
+ }
1664
+ return { kind: 'primitive', type: 'string' };
1665
+ }
1666
+ rangeConstraint(ctx) {
1667
+ const integers = ctx.IntegerLiteral ?? [];
1668
+ if (integers.length === 2) {
1669
+ return {
1670
+ min: parseInt(integers[0].image, 10),
1671
+ max: parseInt(integers[1].image, 10),
1672
+ };
1673
+ }
1674
+ if (integers.length === 1) {
1675
+ // Could be min or max depending on position
1676
+ return { max: parseInt(integers[0].image, 10) };
1677
+ }
1678
+ return {};
1679
+ }
1680
+ fieldModifier(ctx) {
1681
+ if (ctx.Primary)
1682
+ return 'primary';
1683
+ if (ctx.Unique)
1684
+ return 'unique';
1685
+ if (ctx.Auto)
1686
+ return 'auto';
1687
+ if (ctx.Immutable)
1688
+ return 'immutable';
1689
+ if (ctx.Internal)
1690
+ return 'internal';
1691
+ if (ctx.Hashed)
1692
+ return 'hashed';
1693
+ if (ctx.Optional)
1694
+ return 'optional';
1695
+ return 'optional';
1696
+ }
1697
+ defaultValue(ctx) {
1698
+ if (ctx.StringLiteral)
1699
+ return ctx.StringLiteral[0].image.slice(1, -1);
1700
+ if (ctx.IntegerLiteral)
1701
+ return ctx.IntegerLiteral[0].image;
1702
+ if (ctx.Identifier)
1703
+ return ctx.Identifier[0].image;
1704
+ if (ctx.True)
1705
+ return 'true';
1706
+ if (ctx.False)
1707
+ return 'false';
1708
+ return '';
1709
+ }
1710
+ entityRelationship(ctx) {
1711
+ let type;
1712
+ if (ctx.HasMany)
1713
+ type = 'has_many';
1714
+ else if (ctx.HasOne)
1715
+ type = 'has_one';
1716
+ else if (ctx.ManyToMany)
1717
+ type = 'many_to_many';
1718
+ else
1719
+ type = 'belongs_to';
1720
+ const pascalIds = ctx.PascalIdentifier;
1721
+ const entity = pascalIds?.[0]?.image ?? '';
1722
+ // Second PascalIdentifier is the "through" entity (only present with ManyToMany)
1723
+ const through = ctx.Through ? pascalIds?.[1]?.image : undefined;
1724
+ const as = ctx.As ? (ctx.Identifier?.[0])?.image : undefined;
1725
+ return {
1726
+ kind: 'EntityRelationship',
1727
+ loc: this.getLocation(ctx),
1728
+ type,
1729
+ entity,
1730
+ as,
1731
+ through,
1732
+ };
1733
+ }
1734
+ entityCapability(ctx) {
1735
+ const allowed = !!ctx.Can;
1736
+ const capData = ctx.singleCapability
1737
+ ? this.visit(ctx.singleCapability)
1738
+ : { action: 'read', entity: '' };
1739
+ return {
1740
+ kind: 'Capability',
1741
+ loc: this.getLocation(ctx),
1742
+ allowed,
1743
+ action: capData.action,
1744
+ target: capData.entity,
1745
+ condition: capData.condition?.type === 'where' ? capData.condition.value : undefined,
1746
+ unless: capData.condition?.type === 'unless' ? capData.condition.value : undefined,
1747
+ };
1748
+ }
1749
+ capabilityList(ctx) {
1750
+ return ctx.singleCapability
1751
+ ? ctx.singleCapability.map((c) => this.visit(c))
1752
+ : [];
1753
+ }
1754
+ singleCapability(ctx) {
1755
+ const action = this.visit(ctx.capabilityAction);
1756
+ const entity = (ctx.PascalIdentifier?.[0])?.image ?? '';
1757
+ const condition = ctx.capabilityCondition
1758
+ ? this.visit(ctx.capabilityCondition)
1759
+ : undefined;
1760
+ return { action, entity, condition };
1761
+ }
1762
+ capabilityAction(ctx) {
1763
+ if (ctx.Create)
1764
+ return 'create';
1765
+ if (ctx.Read)
1766
+ return 'read';
1767
+ if (ctx.Update)
1768
+ return 'update';
1769
+ if (ctx.Delete)
1770
+ return 'delete';
1771
+ return 'read';
1772
+ }
1773
+ capabilityCondition(ctx) {
1774
+ if (ctx.Where) {
1775
+ return { type: 'where', value: ctx.Identifier?.[0]?.image ?? '' };
1776
+ }
1777
+ return { type: 'unless', value: ctx.Identifier?.[0]?.image ?? '' };
1778
+ }
1779
+ entityLifecycleHook(ctx) {
1780
+ let event;
1781
+ if (ctx.OnCreate)
1782
+ event = 'on_create';
1783
+ else if (ctx.OnUpdate)
1784
+ event = 'on_update';
1785
+ else
1786
+ event = 'on_delete';
1787
+ const actionData = this.visit(ctx.hookAction);
1788
+ return {
1789
+ kind: 'LifecycleHook',
1790
+ loc: this.getLocation(ctx),
1791
+ event,
1792
+ actions: [{
1793
+ kind: 'LifecycleAction',
1794
+ loc: this.getLocation(ctx),
1795
+ type: actionData.name,
1796
+ params: actionData.params,
1797
+ }],
1798
+ };
1799
+ }
1800
+ hookAction(ctx) {
1801
+ let name;
1802
+ if (ctx.SendEmail)
1803
+ name = 'send_email';
1804
+ else if (ctx.Anonymize)
1805
+ name = 'anonymize';
1806
+ else if (ctx.Notify)
1807
+ name = 'notify';
1808
+ else if (ctx.Webhook)
1809
+ name = 'webhook';
1810
+ else
1811
+ name = (ctx.Identifier?.[0])?.image ?? '';
1812
+ const params = {};
1813
+ if (ctx.hookActionParam) {
1814
+ for (const p of ctx.hookActionParam) {
1815
+ const param = this.visit(p);
1816
+ params[param.name] = param.value;
1817
+ }
1818
+ }
1819
+ return { name, params };
1820
+ }
1821
+ hookActionParam(ctx) {
1822
+ const name = (ctx.Identifier?.[0])?.image ?? '';
1823
+ let value;
1824
+ if (ctx.StringLiteral) {
1825
+ value = (ctx.StringLiteral?.[0])?.image.slice(1, -1);
1826
+ }
1827
+ else if (ctx.arrayLiteral) {
1828
+ value = this.visit(ctx.arrayLiteral);
1829
+ }
1830
+ else {
1831
+ value = (ctx.Identifier?.[1])?.image ?? '';
1832
+ }
1833
+ return { name, value };
1834
+ }
1835
+ arrayLiteral(ctx) {
1836
+ return (ctx.Identifier ?? []).map((t) => t.image);
1837
+ }
1838
+ // API block visitor
1839
+ apiBlock(ctx) {
1840
+ const name = (ctx.PascalIdentifier?.[0])?.image ?? '';
1841
+ const operations = ctx.apiEndpoint
1842
+ ? ctx.apiEndpoint.map((e) => this.visit(e))
1843
+ : [];
1844
+ const crud = ctx.crudShorthand
1845
+ ? ctx.crudShorthand.map((c) => this.visit(c))
1846
+ : [];
1847
+ return {
1848
+ kind: 'APIBlock',
1849
+ loc: this.getLocation(ctx),
1850
+ name,
1851
+ operations,
1852
+ crud,
1853
+ };
1854
+ }
1855
+ crudShorthand(ctx) {
1856
+ const entity = (ctx.PascalIdentifier?.[0])?.image ?? '';
1857
+ const actionList = ctx.crudActionList
1858
+ ? this.visit(ctx.crudActionList)
1859
+ : [];
1860
+ const effects = ctx.effectAnnotation
1861
+ ? this.visit(ctx.effectAnnotation)
1862
+ : ['DB']; // Default to DB effect
1863
+ const actions = actionList.map((action) => ({
1864
+ action,
1865
+ effects,
1866
+ }));
1867
+ return {
1868
+ kind: 'CRUDShorthand',
1869
+ loc: this.getLocation(ctx),
1870
+ entity,
1871
+ actions,
1872
+ };
1873
+ }
1874
+ crudActionList(ctx) {
1875
+ return ctx.crudAction
1876
+ ? ctx.crudAction.map((a) => this.visit(a))
1877
+ : [];
1878
+ }
1879
+ crudAction(ctx) {
1880
+ if (ctx.Create)
1881
+ return 'create';
1882
+ if (ctx.Read)
1883
+ return 'read';
1884
+ if (ctx.Update)
1885
+ return 'update';
1886
+ if (ctx.Delete)
1887
+ return 'delete';
1888
+ if (ctx.List)
1889
+ return 'list';
1890
+ return 'read';
1891
+ }
1892
+ apiEndpoint(ctx) {
1893
+ const name = (ctx.Identifier?.[0])?.image ?? '';
1894
+ const parameters = ctx.parameterList ? this.visit(ctx.parameterList) : [];
1895
+ const effects = this.visit(ctx.effectAnnotation);
1896
+ const returnType = this.visit(ctx.returnType);
1897
+ return {
1898
+ kind: 'APIOperation',
1899
+ loc: this.getLocation(ctx),
1900
+ name,
1901
+ parameters,
1902
+ effects,
1903
+ returnType,
1904
+ };
1905
+ }
1906
+ parameterList(ctx) {
1907
+ return ctx.parameter ? ctx.parameter.map((p) => this.visit(p)) : [];
1908
+ }
1909
+ parameter(ctx) {
1910
+ const name = ctx.fieldName ? this.visit(ctx.fieldName) : '';
1911
+ const type = this.visit(ctx.parameterType);
1912
+ return {
1913
+ kind: 'APIParameter',
1914
+ loc: this.getLocation(ctx),
1915
+ name,
1916
+ type,
1917
+ optional: false,
1918
+ };
1919
+ }
1920
+ parameterType(ctx) {
1921
+ if (ctx.String)
1922
+ return 'string';
1923
+ if (ctx.Int)
1924
+ return 'int';
1925
+ if (ctx.Float)
1926
+ return 'float';
1927
+ if (ctx.Bool)
1928
+ return 'bool';
1929
+ if (ctx.Email)
1930
+ return 'email';
1931
+ if (ctx.Uuid)
1932
+ return 'uuid';
1933
+ if (ctx.PascalIdentifier)
1934
+ return ctx.PascalIdentifier[0].image;
1935
+ return 'string';
1936
+ }
1937
+ effectAnnotation(ctx) {
1938
+ return this.visit(ctx.effectList);
1939
+ }
1940
+ effectList(ctx) {
1941
+ return ctx.effect ? ctx.effect.map((e) => this.visit(e)) : [];
1942
+ }
1943
+ effect(ctx) {
1944
+ if (ctx.DB)
1945
+ return 'DB';
1946
+ if (ctx.AuthEffect)
1947
+ return 'Auth';
1948
+ if (ctx.EmailEffect)
1949
+ return 'Email';
1950
+ if (ctx.Audit)
1951
+ return 'Audit';
1952
+ if (ctx.Cache)
1953
+ return 'Cache';
1954
+ if (ctx.Queue)
1955
+ return 'Queue';
1956
+ if (ctx.External)
1957
+ return 'External';
1958
+ return ctx.Identifier?.[0]?.image ?? 'DB';
1959
+ }
1960
+ returnType(ctx) {
1961
+ const loc = this.getLocation(ctx);
1962
+ if (ctx.Void) {
1963
+ return { kind: 'APIReturnType', loc, type: 'void', isArray: false };
1964
+ }
1965
+ if (ctx.objectTypeFields) {
1966
+ const fields = this.visit(ctx.objectTypeFields);
1967
+ const fieldStr = fields.map(f => `${f.name}: ${f.type}`).join(', ');
1968
+ return { kind: 'APIReturnType', loc, type: `{ ${fieldStr} }`, isArray: false };
1969
+ }
1970
+ if (ctx.LBracket) {
1971
+ const inner = this.returnTypeInner(ctx.returnTypeInner);
1972
+ return {
1973
+ kind: 'APIReturnType',
1974
+ loc,
1975
+ type: inner.type,
1976
+ isArray: true,
1977
+ joined: inner.joined
1978
+ };
1979
+ }
1980
+ const inner = this.returnTypeInner(ctx.returnTypeInner);
1981
+ return { kind: 'APIReturnType', loc, type: inner.type, isArray: false, joined: inner.joined };
1982
+ }
1983
+ returnTypeInner(ctx) {
1984
+ // Handle case where ctx is an array of CstNodes (from array return type)
1985
+ if (Array.isArray(ctx)) {
1986
+ if (ctx.length > 0) {
1987
+ return this.visit(ctx[0]);
1988
+ }
1989
+ return { type: 'unknown' };
1990
+ }
1991
+ const baseType = this.parameterType(ctx);
1992
+ if (ctx.objectTypeFields) {
1993
+ const fields = this.visit(ctx.objectTypeFields);
1994
+ const joined = fields.map(f => ({ field: f.name, entity: f.type }));
1995
+ return { type: baseType, joined };
1996
+ }
1997
+ return { type: baseType };
1998
+ }
1999
+ objectTypeFields(ctx) {
2000
+ return ctx.objectTypeField
2001
+ ? ctx.objectTypeField.map((f) => this.visit(f))
2002
+ : [];
2003
+ }
2004
+ objectTypeField(ctx) {
2005
+ const name = (ctx.Identifier?.[0])?.image ?? '';
2006
+ const inner = this.returnTypeInner(ctx);
2007
+ return { name, type: inner.type };
2008
+ }
2009
+ // Journey block visitor
2010
+ journeyBlock(ctx) {
2011
+ const name = (ctx.PascalIdentifier?.[0])?.image ?? '';
2012
+ let actor = '';
2013
+ const steps = [];
2014
+ if (ctx.journeyProperty) {
2015
+ for (const prop of ctx.journeyProperty) {
2016
+ const result = this.visit(prop);
2017
+ if (result.type === 'actor')
2018
+ actor = result.value;
2019
+ if (result.type === 'steps')
2020
+ steps.push(...result.value);
2021
+ }
2022
+ }
2023
+ return {
2024
+ kind: 'JourneyBlock',
2025
+ loc: this.getLocation(ctx),
2026
+ name,
2027
+ actor,
2028
+ steps,
2029
+ recovery: [],
2030
+ };
2031
+ }
2032
+ journeyProperty(ctx) {
2033
+ if (ctx.Actor) {
2034
+ return { type: 'actor', value: (ctx.Identifier?.[0])?.image ?? '' };
2035
+ }
2036
+ if (ctx.Steps) {
2037
+ const steps = ctx.journeyStep
2038
+ ? ctx.journeyStep.map((s) => this.visit(s))
2039
+ : [];
2040
+ return { type: 'steps', value: steps };
2041
+ }
2042
+ return { type: 'unknown', value: '' };
2043
+ }
2044
+ journeyStep(ctx) {
2045
+ const name = (ctx.Identifier?.[0])?.image ?? '';
2046
+ const properties = {};
2047
+ if (ctx.journeyStepProperty) {
2048
+ for (const prop of ctx.journeyStepProperty) {
2049
+ const result = this.visit(prop);
2050
+ properties[result.type] = result.value;
2051
+ }
2052
+ }
2053
+ return {
2054
+ kind: 'JourneyStep',
2055
+ loc: this.getLocation(ctx),
2056
+ name,
2057
+ page: properties.page,
2058
+ next: properties.next,
2059
+ waitFor: properties.waitFor,
2060
+ action: properties.action,
2061
+ skipIf: properties.condition,
2062
+ };
2063
+ }
2064
+ journeyStepProperty(ctx) {
2065
+ if (ctx.Page) {
2066
+ return { type: 'page', value: (ctx.PascalIdentifier?.[0])?.image ?? '' };
2067
+ }
2068
+ if (ctx.Next) {
2069
+ return { type: 'next', value: (ctx.Identifier?.[0])?.image ?? '' };
2070
+ }
2071
+ if (ctx.WaitFor) {
2072
+ return { type: 'waitFor', value: (ctx.Identifier?.[0])?.image ?? '' };
2073
+ }
2074
+ if (ctx.Action) {
2075
+ return { type: 'action', value: (ctx.Identifier?.[0])?.image ?? '' };
2076
+ }
2077
+ if (ctx.Condition) {
2078
+ return { type: 'condition', value: (ctx.Identifier?.[0])?.image ?? '' };
2079
+ }
2080
+ return { type: 'unknown', value: '' };
2081
+ }
2082
+ // Page block visitor
2083
+ pageBlock(ctx) {
2084
+ const name = (ctx.PascalIdentifier?.[0])?.image ?? '';
2085
+ const properties = {};
2086
+ if (ctx.pageProperty) {
2087
+ for (const prop of ctx.pageProperty) {
2088
+ const result = this.visit(prop);
2089
+ properties[result.type] = result.value;
2090
+ }
2091
+ }
2092
+ return {
2093
+ kind: 'PageBlock',
2094
+ loc: this.getLocation(ctx),
2095
+ name,
2096
+ route: properties.route ?? '',
2097
+ loaders: [],
2098
+ actions: [],
2099
+ guard: properties.guard,
2100
+ meta: properties.meta,
2101
+ };
2102
+ }
2103
+ pageProperty(ctx) {
2104
+ if (ctx.Route) {
2105
+ const value = (ctx.StringLiteral?.[0])?.image ?? '';
2106
+ return { type: 'route', value: value.slice(1, -1) };
2107
+ }
2108
+ if (ctx.Layout) {
2109
+ return { type: 'layout', value: (ctx.PascalIdentifier?.[0])?.image ?? '' };
2110
+ }
2111
+ if (ctx.Loader) {
2112
+ return { type: 'loader', value: (ctx.Identifier?.[0])?.image ?? '' };
2113
+ }
2114
+ if (ctx.Guard) {
2115
+ return { type: 'guard', value: (ctx.Identifier?.[0])?.image ?? '' };
2116
+ }
2117
+ if (ctx.Meta) {
2118
+ const meta = {};
2119
+ if (ctx.metaProperty) {
2120
+ for (const prop of ctx.metaProperty) {
2121
+ const result = this.visit(prop);
2122
+ meta[result.name] = result.value;
2123
+ }
2124
+ }
2125
+ return { type: 'meta', value: meta };
2126
+ }
2127
+ return { type: 'unknown', value: '' };
2128
+ }
2129
+ metaProperty(ctx) {
2130
+ const name = ctx.Identifier?.[0]?.image ?? '';
2131
+ const value = ctx.StringLiteral?.[0]?.image.slice(1, -1) ?? '';
2132
+ return { name, value };
2133
+ }
2134
+ // Component block visitor
2135
+ componentBlock(ctx) {
2136
+ const name = (ctx.PascalIdentifier?.[0])?.image ?? '';
2137
+ const props = [];
2138
+ const state = [];
2139
+ const handlers = [];
2140
+ if (ctx.componentProperty) {
2141
+ for (const prop of ctx.componentProperty) {
2142
+ const result = this.visit(prop);
2143
+ if (result.type === 'props')
2144
+ props.push(...result.value);
2145
+ if (result.type === 'state')
2146
+ state.push(...result.value);
2147
+ if (result.type === 'handlers')
2148
+ handlers.push(...result.value);
2149
+ }
2150
+ }
2151
+ return {
2152
+ kind: 'ComponentBlock',
2153
+ loc: this.getLocation(ctx),
2154
+ name,
2155
+ props: props.map((p) => ({
2156
+ kind: 'ComponentProp',
2157
+ loc: this.getLocation(ctx),
2158
+ name: p.name,
2159
+ type: p.type,
2160
+ optional: p.optional ?? false,
2161
+ })),
2162
+ variants: [],
2163
+ };
2164
+ }
2165
+ componentProperty(ctx) {
2166
+ if (ctx.Props) {
2167
+ const props = ctx.propDefinition
2168
+ ? ctx.propDefinition.map((p) => this.visit(p))
2169
+ : [];
2170
+ return { type: 'props', value: props };
2171
+ }
2172
+ if (ctx.State) {
2173
+ const state = ctx.stateDefinition
2174
+ ? ctx.stateDefinition.map((s) => this.visit(s))
2175
+ : [];
2176
+ return { type: 'state', value: state };
2177
+ }
2178
+ if (ctx.Handlers) {
2179
+ const handlers = ctx.handlerDefinition
2180
+ ? ctx.handlerDefinition.map((h) => this.visit(h))
2181
+ : [];
2182
+ return { type: 'handlers', value: handlers };
2183
+ }
2184
+ return { type: 'unknown', value: [] };
2185
+ }
2186
+ propDefinition(ctx) {
2187
+ const name = (ctx.Identifier?.[0])?.image ?? '';
2188
+ const type = this.visit(ctx.parameterType);
2189
+ const optional = !!ctx.Question;
2190
+ return { name, type, optional };
2191
+ }
2192
+ stateDefinition(ctx) {
2193
+ const name = (ctx.Identifier?.[0])?.image ?? '';
2194
+ const type = this.visit(ctx.parameterType);
2195
+ const defaultValue = ctx.defaultValue ? this.visit(ctx.defaultValue) : undefined;
2196
+ return { name, type, defaultValue };
2197
+ }
2198
+ handlerDefinition(ctx) {
2199
+ const name = (ctx.Identifier?.[0])?.image ?? '';
2200
+ const effects = this.visit(ctx.effectAnnotation);
2201
+ return { name, effects };
2202
+ }
2203
+ // Form block visitor
2204
+ formBlock(ctx) {
2205
+ const name = (ctx.PascalIdentifier?.[0])?.image ?? '';
2206
+ const fields = [];
2207
+ let submit = {};
2208
+ let validation = {};
2209
+ if (ctx.formProperty) {
2210
+ for (const prop of ctx.formProperty) {
2211
+ const result = this.visit(prop);
2212
+ if (result.type === 'fields')
2213
+ fields.push(...result.value);
2214
+ if (result.type === 'submit')
2215
+ submit = result.value;
2216
+ if (result.type === 'validation')
2217
+ validation = result.value;
2218
+ }
2219
+ }
2220
+ const submitData = submit;
2221
+ return {
2222
+ kind: 'FormBlock',
2223
+ loc: this.getLocation(ctx),
2224
+ name,
2225
+ fields,
2226
+ submit: {
2227
+ kind: 'FormSubmit',
2228
+ loc: this.getLocation(ctx),
2229
+ action: submitData.action ?? '',
2230
+ button: submitData.button ?? 'Submit',
2231
+ onSuccess: submitData.onSuccess ?? 'redirect',
2232
+ onError: submitData.onError ?? 'show_error',
2233
+ },
2234
+ layout: [],
2235
+ };
2236
+ }
2237
+ formProperty(ctx) {
2238
+ if (ctx.Fields) {
2239
+ const fields = ctx.formFieldDefinition
2240
+ ? ctx.formFieldDefinition.map((f) => this.visit(f))
2241
+ : [];
2242
+ return { type: 'fields', value: fields };
2243
+ }
2244
+ if (ctx.Submit) {
2245
+ const submit = {};
2246
+ if (ctx.submitProperty) {
2247
+ for (const prop of ctx.submitProperty) {
2248
+ const result = this.visit(prop);
2249
+ submit[result.type] = result.value;
2250
+ }
2251
+ }
2252
+ return { type: 'submit', value: submit };
2253
+ }
2254
+ if (ctx.Validation) {
2255
+ const validation = {};
2256
+ if (ctx.validationRule) {
2257
+ for (const rule of ctx.validationRule) {
2258
+ const result = this.visit(rule);
2259
+ validation[result.name] = result.value;
2260
+ }
2261
+ }
2262
+ return { type: 'validation', value: validation };
2263
+ }
2264
+ return { type: 'unknown', value: {} };
2265
+ }
2266
+ formFieldDefinition(ctx) {
2267
+ const name = (ctx.Identifier?.[0])?.image ?? '';
2268
+ const fieldType = this.visit(ctx.formFieldType);
2269
+ const properties = {};
2270
+ if (ctx.formFieldProperty) {
2271
+ for (const prop of ctx.formFieldProperty) {
2272
+ const result = this.visit(prop);
2273
+ properties[result.type] = result.value;
2274
+ }
2275
+ }
2276
+ return {
2277
+ kind: 'FormField',
2278
+ loc: this.getLocation(ctx),
2279
+ name,
2280
+ type: fieldType,
2281
+ label: properties.label ?? name,
2282
+ placeholder: properties.placeholder,
2283
+ required: properties.required ?? false,
2284
+ config: properties.rows ? { rows: properties.rows } : undefined,
2285
+ options: properties.options
2286
+ ? properties.options.map((opt) => ({
2287
+ kind: 'FormFieldOption',
2288
+ loc: this.getLocation(ctx),
2289
+ value: opt,
2290
+ label: opt,
2291
+ }))
2292
+ : undefined,
2293
+ };
2294
+ }
2295
+ formFieldType(ctx) {
2296
+ if (ctx.Text)
2297
+ return 'text';
2298
+ if (ctx.Textarea)
2299
+ return 'textarea';
2300
+ if (ctx.Email)
2301
+ return 'email';
2302
+ if (ctx.Password)
2303
+ return 'password';
2304
+ if (ctx.Number)
2305
+ return 'number';
2306
+ if (ctx.Checkbox)
2307
+ return 'checkbox';
2308
+ if (ctx.Radio)
2309
+ return 'radio';
2310
+ if (ctx.Select)
2311
+ return 'select';
2312
+ if (ctx.Date)
2313
+ return 'date';
2314
+ if (ctx.File)
2315
+ return 'file';
2316
+ return 'text';
2317
+ }
2318
+ formFieldProperty(ctx) {
2319
+ if (ctx.Label) {
2320
+ const value = (ctx.StringLiteral?.[0])?.image ?? '';
2321
+ return { type: 'label', value: value.slice(1, -1) };
2322
+ }
2323
+ if (ctx.Placeholder) {
2324
+ const value = (ctx.StringLiteral?.[0])?.image ?? '';
2325
+ return { type: 'placeholder', value: value.slice(1, -1) };
2326
+ }
2327
+ if (ctx.Required) {
2328
+ return { type: 'required', value: !!ctx.True };
2329
+ }
2330
+ if (ctx.Rows) {
2331
+ return { type: 'rows', value: parseInt((ctx.IntegerLiteral?.[0])?.image ?? '4', 10) };
2332
+ }
2333
+ if (ctx.Options) {
2334
+ const options = (ctx.StringLiteral ?? []).map((t) => t.image.slice(1, -1));
2335
+ return { type: 'options', value: options };
2336
+ }
2337
+ return { type: 'unknown', value: '' };
2338
+ }
2339
+ submitProperty(ctx) {
2340
+ if (ctx.Action) {
2341
+ const parts = (ctx.Identifier ?? []).map((t) => t.image);
2342
+ return { type: 'action', value: parts.join('.') };
2343
+ }
2344
+ if (ctx.Button) {
2345
+ return { type: 'button', value: ctx.StringLiteral?.[0]?.image.slice(1, -1) ?? '' };
2346
+ }
2347
+ if (ctx.OnSuccess) {
2348
+ return { type: 'onSuccess', value: ctx.Identifier?.[0]?.image ?? '' };
2349
+ }
2350
+ if (ctx.OnError) {
2351
+ return { type: 'onError', value: ctx.Identifier?.[0]?.image ?? '' };
2352
+ }
2353
+ return { type: 'unknown', value: '' };
2354
+ }
2355
+ validationRule(ctx) {
2356
+ const identifiers = ctx.Identifier ?? [];
2357
+ return {
2358
+ name: identifiers[0]?.image ?? '',
2359
+ value: identifiers[1]?.image ?? '',
2360
+ };
2361
+ }
2362
+ // Design block visitor
2363
+ designBlock(ctx) {
2364
+ const colorsMap = {};
2365
+ let darkColorsMap;
2366
+ let typography;
2367
+ let spacing;
2368
+ let motion;
2369
+ if (ctx.designSection) {
2370
+ for (const section of ctx.designSection) {
2371
+ const result = this.visit(section);
2372
+ if (result.type === 'colors') {
2373
+ for (const [key, value] of Object.entries(result.value)) {
2374
+ colorsMap[key] = { value, isFunction: false };
2375
+ }
2376
+ }
2377
+ if (result.type === 'dark') {
2378
+ darkColorsMap = {};
2379
+ for (const [key, value] of Object.entries(result.value)) {
2380
+ darkColorsMap[key] = { value, isFunction: false };
2381
+ }
2382
+ }
2383
+ if (result.type === 'typography')
2384
+ typography = result.value;
2385
+ if (result.type === 'spacing')
2386
+ spacing = result.value;
2387
+ if (result.type === 'animation')
2388
+ motion = result.value;
2389
+ }
2390
+ }
2391
+ return {
2392
+ kind: 'DesignBlock',
2393
+ loc: this.getLocation(ctx),
2394
+ colors: {
2395
+ kind: 'ColorPalette',
2396
+ loc: this.getLocation(ctx),
2397
+ colors: colorsMap,
2398
+ },
2399
+ darkColors: darkColorsMap
2400
+ ? {
2401
+ kind: 'ColorPalette',
2402
+ loc: this.getLocation(ctx),
2403
+ colors: darkColorsMap,
2404
+ }
2405
+ : undefined,
2406
+ typography,
2407
+ spacing,
2408
+ motion,
2409
+ };
2410
+ }
2411
+ designSection(ctx) {
2412
+ if (ctx.Colors) {
2413
+ const colors = {};
2414
+ if (ctx.colorDefinition) {
2415
+ for (const def of ctx.colorDefinition) {
2416
+ const result = this.visit(def);
2417
+ colors[result.name] = result.value;
2418
+ }
2419
+ }
2420
+ return { type: 'colors', value: colors };
2421
+ }
2422
+ if (ctx.Dark) {
2423
+ const colors = {};
2424
+ if (ctx.colorDefinition) {
2425
+ for (const def of ctx.colorDefinition) {
2426
+ const result = this.visit(def);
2427
+ colors[result.name] = result.value;
2428
+ }
2429
+ }
2430
+ return { type: 'dark', value: colors };
2431
+ }
2432
+ if (ctx.Typography) {
2433
+ const typography = {};
2434
+ if (ctx.typographyDefinition) {
2435
+ for (const def of ctx.typographyDefinition) {
2436
+ const result = this.visit(def);
2437
+ typography[result.name] = result.value;
2438
+ }
2439
+ }
2440
+ return { type: 'typography', value: typography };
2441
+ }
2442
+ if (ctx.Spacing) {
2443
+ const spacing = {};
2444
+ if (ctx.spacingDefinition) {
2445
+ for (const def of ctx.spacingDefinition) {
2446
+ const result = this.visit(def);
2447
+ spacing[result.name] = result.value;
2448
+ }
2449
+ }
2450
+ return { type: 'spacing', value: spacing };
2451
+ }
2452
+ if (ctx.Animation) {
2453
+ const animation = {};
2454
+ if (ctx.animationDefinition) {
2455
+ for (const def of ctx.animationDefinition) {
2456
+ const result = this.visit(def);
2457
+ animation[result.name] = result.value;
2458
+ }
2459
+ }
2460
+ return { type: 'animation', value: animation };
2461
+ }
2462
+ return { type: 'unknown', value: {} };
2463
+ }
2464
+ colorDefinition(ctx) {
2465
+ const fieldNameNode = ctx.fieldName?.[0];
2466
+ const hexColorToken = ctx.HexColor?.[0];
2467
+ return {
2468
+ name: fieldNameNode ? this.visit(fieldNameNode) : '',
2469
+ value: hexColorToken?.image ?? '',
2470
+ };
2471
+ }
2472
+ typographyDefinition(ctx) {
2473
+ const name = ctx.Identifier?.[0]?.image ?? '';
2474
+ let value = '';
2475
+ if (ctx.StringLiteral)
2476
+ value = ctx.StringLiteral[0].image.slice(1, -1);
2477
+ else if (ctx.SizeLiteral)
2478
+ value = ctx.SizeLiteral[0].image;
2479
+ else if (ctx.IntegerLiteral)
2480
+ value = ctx.IntegerLiteral[0].image;
2481
+ return { name, value };
2482
+ }
2483
+ spacingDefinition(ctx) {
2484
+ return {
2485
+ name: ctx.Identifier?.[0]?.image ?? '',
2486
+ value: ctx.SizeLiteral?.[0]?.image ?? '',
2487
+ };
2488
+ }
2489
+ animationDefinition(ctx) {
2490
+ const name = ctx.Identifier?.[0]?.image ?? '';
2491
+ let value = '';
2492
+ if (ctx.DurationLiteral)
2493
+ value = ctx.DurationLiteral[0].image;
2494
+ else if (ctx.CSSFunctionLiteral)
2495
+ value = ctx.CSSFunctionLiteral[0].image;
2496
+ else if (ctx.StringLiteral)
2497
+ value = ctx.StringLiteral[0].image.slice(1, -1);
2498
+ return { name, value };
2499
+ }
2500
+ }
2501
+ // =============================================================================
2502
+ // Parse Function
2503
+ // =============================================================================
2504
+ const astBuilder = new KappaAstBuilder();
2505
+ export function parse(source) {
2506
+ // Tokenize
2507
+ const lexResult = tokens.tokenize(source);
2508
+ if (!lexResult.success) {
2509
+ return {
2510
+ success: false,
2511
+ errors: lexResult.errors.map((e) => ({
2512
+ message: e.message,
2513
+ line: e.line,
2514
+ column: e.column,
2515
+ })),
2516
+ };
2517
+ }
2518
+ // Parse
2519
+ parser.input = lexResult.tokens;
2520
+ const cst = parser.kappaSpec();
2521
+ if (parser.errors.length > 0) {
2522
+ return {
2523
+ success: false,
2524
+ errors: parser.errors.map((e) => ({
2525
+ message: e.message,
2526
+ line: e.token.startLine,
2527
+ column: e.token.startColumn,
2528
+ })),
2529
+ };
2530
+ }
2531
+ // Build AST
2532
+ const ast = astBuilder.visit(cst);
2533
+ return {
2534
+ success: true,
2535
+ ast,
2536
+ errors: [],
2537
+ };
2538
+ }
2539
+ /**
2540
+ * Convenience function that parses source and returns the AST directly.
2541
+ * Returns null if parsing fails.
2542
+ */
2543
+ export function parseToAST(source) {
2544
+ const result = parse(source);
2545
+ return result.success ? result.ast ?? null : null;
2546
+ }
2547
+ export { parser, KappaParser, KappaAstBuilder };