@sshadows/tree-sitter-al 2.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/grammar.js ADDED
@@ -0,0 +1,3338 @@
1
+ /**
2
+ * @file AL for Business Central (V2)
3
+ * @author Torben Leth <sshadows@sshadows.dk>
4
+ * @license MIT
5
+ */
6
+
7
+ /// <reference types="tree-sitter-cli/dsl" />
8
+ // @ts-check
9
+
10
+ // Case-insensitive keyword via regex
11
+ function kw(word, precedence = null) {
12
+ const regex = new RustRegex(`(?i)${word}`);
13
+ return precedence !== null ? token(prec(precedence, regex)) : token(regex);
14
+ }
15
+
16
+ // Object declaration helper — with object ID
17
+ function _object_with_id(keyword_name) {
18
+ return $ => seq(
19
+ $[keyword_name + '_keyword'],
20
+ field('object_id', $.integer),
21
+ field('object_name', $._identifier_or_quoted),
22
+ '{',
23
+ repeat($._body_element),
24
+ '}'
25
+ );
26
+ }
27
+
28
+ // Object declaration helper — without object ID
29
+ function _object_without_id(keyword_name) {
30
+ return $ => seq(
31
+ $[keyword_name + '_keyword'],
32
+ field('object_name', $._identifier_or_quoted),
33
+ '{',
34
+ repeat($._body_element),
35
+ '}'
36
+ );
37
+ }
38
+
39
+ // Extension declaration helper — with object ID
40
+ function _extension_with_id(keyword_name) {
41
+ return $ => seq(
42
+ $[keyword_name + '_keyword'],
43
+ field('object_id', $.integer),
44
+ field('object_name', $._identifier_or_quoted),
45
+ $.extends_keyword,
46
+ field('base_object', $._identifier_or_quoted),
47
+ '{',
48
+ repeat($._body_element),
49
+ '}'
50
+ );
51
+ }
52
+
53
+ // Extension declaration helper — without object ID
54
+ function _extension_without_id(keyword_name) {
55
+ return $ => seq(
56
+ $[keyword_name + '_keyword'],
57
+ field('object_name', $._identifier_or_quoted),
58
+ $.extends_keyword,
59
+ field('base_object', $._identifier_or_quoted),
60
+ '{',
61
+ repeat($._body_element),
62
+ '}'
63
+ );
64
+ }
65
+
66
+ // Section template: keyword(name) { content* }
67
+ function _named_section(keyword_rule, content_rule) {
68
+ return $ => seq(
69
+ keyword_rule,
70
+ '(',
71
+ field('name', $._identifier_or_quoted),
72
+ ')',
73
+ '{',
74
+ repeat(content_rule($)),
75
+ '}'
76
+ );
77
+ }
78
+
79
+ module.exports = grammar({
80
+ name: "al",
81
+
82
+ word: $ => $.identifier,
83
+
84
+ extras: $ => [
85
+ /\s/,
86
+ $.comment,
87
+ $.multiline_comment,
88
+ $.pragma,
89
+ $.preproc_region,
90
+ $.preproc_endregion,
91
+ /\uFEFF/, // BOM
92
+ ],
93
+
94
+ externals: $ => [
95
+ $.property_name, // identifier followed by = (not :=)
96
+ $.continue_as_identifier, // 'continue' followed by ':=' (used as variable)
97
+ ],
98
+
99
+ conflicts: $ => [
100
+ [$._property_value, $.option_member],
101
+ [$._property_value, $.option_member, $._identifier_or_quoted],
102
+ [$.caption_value, $.option_member],
103
+ [$.assignment_statement, $.assignment_expression],
104
+ [$.preproc_conditional, $.preproc_conditional_layout],
105
+ [$.preproc_conditional, $.preproc_conditional_actions],
106
+ [$._expression, $._identifier_or_quoted],
107
+ [$._body_element, $.preproc_conditional_var],
108
+ [$.filter_value, $._literal_value],
109
+ [$.filter_value, $._expression],
110
+ [$._body_element, $._action_element],
111
+ [$._body_element, $._layout_element],
112
+ [$.modify_modification, $.modify_action_modification],
113
+ [$._signed_integer, $.option_member],
114
+ [$.option_member, $._literal_value],
115
+ [$._body_element, $._procedure_header],
116
+ [$._body_element, $._action_element, $._procedure_header],
117
+ [$._field_source, $._field_header],
118
+ [$._property_value, $.option_member, $._namespaced_or_simple_ref],
119
+ [$.addafter_modification, $.addafter_views_modification],
120
+ [$.addbefore_modification, $.addbefore_views_modification],
121
+ [$._report_body_element, $.preproc_conditional],
122
+ [$._query_body_element, $.preproc_conditional],
123
+ [$.preproc_conditional_report, $.preproc_conditional],
124
+ [$.preproc_conditional_query, $.preproc_conditional],
125
+ [$._xmlport_body_element, $.preproc_conditional],
126
+ [$.preproc_conditional_xmlport, $.preproc_conditional],
127
+ [$._body_element, $._procedure_header, $.preproc_conditional_var],
128
+ [$._body_element],
129
+ [$.calc_field_reference, $._expression],
130
+ [$.option_member, $._identifier_or_quoted],
131
+ [$._single_pattern, $._expression],
132
+ [$.preproc_conditional_link_values, $.preproc_conditional_permissions, $.preproc_conditional_impl_values, $.preproc_conditional_table_relation],
133
+ [$.preproc_conditional_permissions, $.preproc_conditional_table_relation],
134
+ [$.preproc_conditional_link_values, $.preproc_conditional_permissions, $.preproc_conditional_impl_values],
135
+ [$.preproc_conditional_link_values, $.preproc_conditional_impl_values],
136
+ [$.preproc_conditional_controladdin, $.preproc_conditional],
137
+ [$.procedure, $.interface_procedure_suffix],
138
+ [$.procedure, $._procedure_header, $.interface_procedure_suffix],
139
+ [$._procedure_header, $.interface_procedure_suffix],
140
+ [$.preproc_conditional_case, $.preproc_split_case_branch, $.preproc_conditional_case_patterns],
141
+ [$.case_branch, $.preproc_split_case_branch, $.preproc_conditional_case_patterns],
142
+ [$._preproc_guard_block, $._statement],
143
+ ],
144
+
145
+ rules: {
146
+ source_file: $ => seq(
147
+ optional($.namespace_declaration),
148
+ repeat(choice(
149
+ $.using_statement,
150
+ $._object,
151
+ $.preproc_conditional_object,
152
+ )),
153
+ ),
154
+
155
+ // Preprocessor conditional wrapping entire objects or using statements
156
+ preproc_conditional_object: $ => seq(
157
+ $.preproc_if,
158
+ optional($.namespace_declaration),
159
+ repeat(choice($.using_statement, $._object, $.preproc_conditional_object)),
160
+ repeat(seq(
161
+ $.preproc_elif,
162
+ optional($.namespace_declaration),
163
+ repeat(choice($.using_statement, $._object, $.preproc_conditional_object)),
164
+ )),
165
+ optional(seq(
166
+ $.preproc_else,
167
+ optional($.namespace_declaration),
168
+ repeat(choice($.using_statement, $._object, $.preproc_conditional_object)),
169
+ )),
170
+ $.preproc_endif,
171
+ ),
172
+
173
+ // --- Namespace / Using ---
174
+
175
+ namespace_declaration: $ => seq(
176
+ $.namespace_keyword,
177
+ field('name', $.namespace_name),
178
+ ';'
179
+ ),
180
+
181
+ using_statement: $ => seq(
182
+ $.using_keyword,
183
+ field('namespace', $.namespace_name),
184
+ ';'
185
+ ),
186
+
187
+ namespace_name: $ => prec.right(seq(
188
+ $.identifier,
189
+ repeat(seq('.', $.identifier))
190
+ )),
191
+
192
+ // --- Object types ---
193
+
194
+ _object: $ => choice(
195
+ $.table_declaration,
196
+ $.tableextension_declaration,
197
+ $.page_declaration,
198
+ $.pageextension_declaration,
199
+ $.pagecustomization_declaration,
200
+ $.codeunit_declaration,
201
+ $.report_declaration,
202
+ $.reportextension_declaration,
203
+ $.query_declaration,
204
+ $.xmlport_declaration,
205
+ $.enum_declaration,
206
+ $.enumextension_declaration,
207
+ $.interface_declaration,
208
+ $.controladdin_declaration,
209
+ $.dotnet_declaration,
210
+ $.profile_declaration,
211
+ $.profileextension_declaration,
212
+ $.permissionset_declaration,
213
+ $.permissionsetextension_declaration,
214
+ $.entitlement_declaration,
215
+ $.preproc_split_declaration,
216
+ ),
217
+
218
+ // Preprocessor-split object declaration:
219
+ // #if ... codeunit 1 "Name" implements A #else codeunit 1 "Name" implements B #endif { body }
220
+ // Also handles nested: #if A header1 #else #if B header2 #else header3 #endif #endif { body }
221
+ preproc_split_declaration: $ => prec(25, seq(
222
+ $.preproc_if,
223
+ $._object_header,
224
+ repeat(seq($.preproc_elif, $._object_header)),
225
+ optional(seq($.preproc_else, choice(
226
+ $._object_header,
227
+ // Nested: #else #if ... #endif
228
+ seq(
229
+ $.preproc_if,
230
+ $._object_header,
231
+ repeat(seq($.preproc_elif, $._object_header)),
232
+ optional(seq($.preproc_else, $._object_header)),
233
+ $.preproc_endif,
234
+ ),
235
+ ))),
236
+ $.preproc_endif,
237
+ '{',
238
+ repeat($._body_element),
239
+ '}'
240
+ )),
241
+
242
+ // Object header without body (used in preproc split declarations)
243
+ _object_header: $ => choice(
244
+ seq($.codeunit_keyword, field('object_id', $.integer), field('object_name', $._identifier_or_quoted), optional($.implements_clause)),
245
+ seq($.enum_keyword, field('object_id', $.integer), field('object_name', $._identifier_or_quoted), optional($.implements_clause)),
246
+ seq(choice($.table_keyword, $.page_keyword, $.report_keyword, $.query_keyword, $.xmlport_keyword, $.permissionset_keyword),
247
+ field('object_id', $.integer), field('object_name', $._identifier_or_quoted)),
248
+ seq(choice($.tableextension_keyword, $.pageextension_keyword, $.reportextension_keyword, $.enumextension_keyword, $.permissionsetextension_keyword),
249
+ field('object_id', $.integer), field('object_name', $._identifier_or_quoted), $.extends_keyword, field('base_object', $._identifier_or_quoted)),
250
+ seq($.interface_keyword, field('object_name', $._identifier_or_quoted)),
251
+ ),
252
+
253
+ // --- With ID, no extends ---
254
+
255
+ table_declaration: _object_with_id('table'),
256
+ page_declaration: _object_with_id('page'),
257
+ report_declaration: _object_with_id('report'),
258
+ query_declaration: _object_with_id('query'),
259
+ xmlport_declaration: _object_with_id('xmlport'),
260
+ permissionset_declaration: _object_with_id('permissionset'),
261
+
262
+ // --- With ID + optional implements ---
263
+
264
+ codeunit_declaration: $ => seq(
265
+ $.codeunit_keyword,
266
+ field('object_id', $.integer),
267
+ field('object_name', $._identifier_or_quoted),
268
+ optional($.implements_clause),
269
+ '{',
270
+ repeat($._body_element),
271
+ '}'
272
+ ),
273
+
274
+ enum_declaration: $ => seq(
275
+ $.enum_keyword,
276
+ field('object_id', $.integer),
277
+ field('object_name', $._identifier_or_quoted),
278
+ optional($.implements_clause),
279
+ '{',
280
+ repeat($._body_element),
281
+ '}'
282
+ ),
283
+
284
+ // --- With ID + extends ---
285
+
286
+ tableextension_declaration: _extension_with_id('tableextension'),
287
+ pageextension_declaration: _extension_with_id('pageextension'),
288
+ reportextension_declaration: _extension_with_id('reportextension'),
289
+ enumextension_declaration: _extension_with_id('enumextension'),
290
+ permissionsetextension_declaration: _extension_with_id('permissionsetextension'),
291
+
292
+ // --- Without ID ---
293
+
294
+ controladdin_declaration: $ => seq(
295
+ $.controladdin_keyword,
296
+ field('object_name', $._identifier_or_quoted),
297
+ '{',
298
+ repeat(choice($._body_element, $.interface_procedure, $.preproc_conditional_controladdin)),
299
+ '}'
300
+ ),
301
+
302
+ // Preprocessor conditional inside controladdin bodies (needs interface_procedure support)
303
+ preproc_conditional_controladdin: $ => seq(
304
+ $.preproc_if,
305
+ repeat(choice($._body_element, $.interface_procedure, $.preproc_conditional_controladdin)),
306
+ repeat(seq(
307
+ $.preproc_elif,
308
+ repeat(choice($._body_element, $.interface_procedure, $.preproc_conditional_controladdin)),
309
+ )),
310
+ optional(seq(
311
+ $.preproc_else,
312
+ repeat(choice($._body_element, $.interface_procedure, $.preproc_conditional_controladdin)),
313
+ )),
314
+ $.preproc_endif,
315
+ ),
316
+ profile_declaration: _object_without_id('profile'),
317
+ entitlement_declaration: _object_without_id('entitlement'),
318
+
319
+ // --- Without ID + extends ---
320
+
321
+ profileextension_declaration: _extension_without_id('profileextension'),
322
+
323
+ // --- Interface (without ID, optional extends) ---
324
+
325
+ interface_declaration: $ => seq(
326
+ $.interface_keyword,
327
+ field('object_name', $._identifier_or_quoted),
328
+ optional(seq(
329
+ $.extends_keyword,
330
+ field('extends_interface', $._identifier_or_quoted)
331
+ )),
332
+ optional(seq(
333
+ kw('access'),
334
+ '=',
335
+ field('access_value', choice(
336
+ $.internal_keyword,
337
+ kw('public'),
338
+ $.identifier
339
+ ))
340
+ )),
341
+ '{',
342
+ repeat(choice($._body_element, $.interface_procedure)),
343
+ '}'
344
+ ),
345
+
346
+ // --- Pagecustomization (without ID, uses customizes) ---
347
+
348
+ pagecustomization_declaration: $ => seq(
349
+ $.pagecustomization_keyword,
350
+ field('object_name', $._identifier_or_quoted),
351
+ $.customizes_keyword,
352
+ field('target_page', $._identifier_or_quoted),
353
+ '{',
354
+ repeat($._body_element),
355
+ '}'
356
+ ),
357
+
358
+ // --- Dotnet (no ID, no name) ---
359
+
360
+ dotnet_declaration: $ => seq(
361
+ $.dotnet_keyword,
362
+ '{',
363
+ repeat($.assembly_declaration),
364
+ '}'
365
+ ),
366
+
367
+ // --- Implements clause ---
368
+
369
+ implements_clause: $ => seq(
370
+ $.implements_keyword,
371
+ field('interface', $._identifier_or_quoted),
372
+ repeat(seq(',', field('interface', $._identifier_or_quoted)))
373
+ ),
374
+
375
+ // =====================================================================
376
+ // Body elements — all sections/declarations that can appear in object bodies
377
+ // =====================================================================
378
+
379
+ _body_element: $ => choice(
380
+ $.property,
381
+ $.empty_statement,
382
+ // Table internals
383
+ $.fields_section,
384
+ $.keys_section,
385
+ $.fieldgroups_section,
386
+ // Enum values
387
+ $.enum_value_declaration,
388
+ // Labels
389
+ $.labels_section,
390
+ // Page layout & actions
391
+ $.layout_section,
392
+ $.actions_section,
393
+ $.views_section,
394
+ // Report sections
395
+ $.dataset_section,
396
+ $.requestpage_section,
397
+ $.rendering_section,
398
+ // Query sections
399
+ $.elements_section,
400
+ // XMLport sections
401
+ $.schema_section,
402
+ // DotNet internals
403
+ $.assembly_declaration,
404
+ // Code structure
405
+ $.procedure,
406
+ $.trigger_declaration,
407
+ $.var_section,
408
+ $.attribute_item,
409
+ // ControlAddIn event declarations
410
+ $.event_declaration,
411
+ // Preprocessor-split procedure: header variants in #if/#else, shared body
412
+ $.preproc_split_procedure,
413
+ // Preprocessor conditionals
414
+ $.preproc_conditional,
415
+ // Extension modifications (table/page extensions)
416
+ $.modify_modification,
417
+ ),
418
+
419
+ empty_statement: $ => ';',
420
+
421
+ // =====================================================================
422
+ // Properties
423
+ // =====================================================================
424
+
425
+ // --- Generic property: Name = Value ; ---
426
+ // The scanner's PROPERTY_NAME token disambiguates from variables (Name : Type)
427
+ // All properties share this structure; complex value types are part of _property_value.
428
+ property: $ => seq(
429
+ field('name', $.property_name), // Scanner token: identifier followed by =
430
+ '=',
431
+ optional(field('value', $._property_value)),
432
+ ';'
433
+ ),
434
+
435
+ _property_value: $ => choice(
436
+ // Simple values
437
+ $.boolean,
438
+ $.integer,
439
+ $.decimal,
440
+ $.string_literal,
441
+ $.identifier,
442
+ $.quoted_identifier,
443
+ // Complex value types
444
+ $.caption_value, // 'Text', Locked = true, Comment = '...'
445
+ $.ml_value_list, // ENU='English', DEU='German'
446
+ $._calc_formula_expression, // sum("Table".Field where(...))
447
+ $.tabledata_permission_list, // tabledata X = R, tabledata Y = RIMD
448
+ $.order_by_list, // ascending("No.", Name)
449
+ $.implementation_value_list, // "IFace" = "Impl", ...
450
+ $.option_member_list, // Option1, Option2, "Option 3"
451
+ $.table_relation_value, // Customer where(...) or if(...) Item else Resource
452
+ $.sorting_value, // sorting("Starting Date")
453
+ $.link_value_list, // "Field" = field(Other), ...
454
+ $.property_expression, // Expressions used as property values
455
+ $.keyword_identifier, // Keywords used as simple property values (TestIsolation = Codeunit)
456
+ $.where_clause, // SourceTableView/SubPageView = where(...)
457
+ $.object_reference_value, // RunObject = Codeunit "BOM-Explode BOM"
458
+ $.decimal_range_value, // DecimalPlaces = 0 : 5
459
+ $.signed_integer_list, // OptionOrdinalValues = -1, 0, 1
460
+ $.time_literal, // InitValue = 000000T
461
+ $.date_literal, // InitValue = 0D
462
+ $.datetime_literal, // InitValue = 0DT
463
+ ),
464
+
465
+ // Decimal range: N : M (used by DecimalPlaces property)
466
+ decimal_range_value: $ => prec(5, seq(
467
+ field('min', $.integer),
468
+ ':',
469
+ optional(field('max', $.integer))
470
+ )),
471
+
472
+ // Signed integer list: -1, 0, 1, 2 (used by OptionOrdinalValues)
473
+ signed_integer_list: $ => prec.left(5, seq(
474
+ $._signed_integer,
475
+ repeat1(seq(',', $._signed_integer))
476
+ )),
477
+
478
+ _signed_integer: $ => choice(
479
+ $.integer,
480
+ seq('-', $.integer),
481
+ ),
482
+
483
+ // Object reference as property value: Codeunit "BOM-Explode BOM"
484
+ // Also supports namespace: Page Microsoft.Sales."Sales Order"
485
+ object_reference_value: $ => prec(3, seq(
486
+ choice(
487
+ $.codeunit_keyword,
488
+ $.page_keyword,
489
+ $.report_keyword,
490
+ $.query_keyword,
491
+ $.xmlport_keyword,
492
+ $.table_keyword,
493
+ ),
494
+ $._namespaced_or_simple_ref,
495
+ )),
496
+
497
+ // Expressions that can appear as property values (member access, function calls, etc.)
498
+ property_expression: $ => prec(-2, choice(
499
+ $.call_expression,
500
+ $.member_expression,
501
+ $.qualified_enum_value,
502
+ $.database_reference,
503
+ $.unary_expression,
504
+ $.additive_expression,
505
+ $.multiplicative_expression,
506
+ $.comparison_expression,
507
+ $.logical_expression,
508
+ $.parenthesized_expression,
509
+ $.subscript_expression,
510
+ )),
511
+
512
+ boolean: $ => choice(kw('true'), kw('false')),
513
+
514
+ decimal: $ => token(seq(/\d+/, '.', /\d+/)),
515
+
516
+ // --- Caption value with sub-fields (Locked, Comment, MaxLength) ---
517
+ // 'My Caption', Locked = true, Comment = 'text', MaxLength = 100
518
+ // Also handles trailing comma: Caption = 'text',;
519
+ // Uses property_name (scanner token) since the sub-fields follow Name = Value pattern
520
+ caption_value: $ => prec.right(seq(
521
+ $.string_literal,
522
+ choice(
523
+ seq(
524
+ repeat1(seq(
525
+ ',',
526
+ $.property_name,
527
+ '=',
528
+ choice($.boolean, $.string_literal, $.integer),
529
+ )),
530
+ optional(','), // Trailing comma after sub-fields
531
+ ),
532
+ ',', // Trailing comma only (no sub-fields): Caption = 'text',;
533
+ ),
534
+ )),
535
+
536
+ // --- ML (Multilingual) value list ---
537
+ // ENU='English', DEU='German'
538
+ ml_value_list: $ => prec.right(seq(
539
+ $.ml_value_pair,
540
+ repeat(seq(',', $.ml_value_pair)),
541
+ optional(seq(',', kw('locked'), '=', $.boolean))
542
+ )),
543
+
544
+ ml_value_pair: $ => seq(
545
+ field('language', $._identifier_or_quoted),
546
+ '=',
547
+ field('value', $.string_literal)
548
+ ),
549
+
550
+ // --- CalcFormula values ---
551
+ // sum("Ledger Entry".Amount where("No." = field("No.")))
552
+ _calc_formula_expression: $ => choice(
553
+ $.lookup_formula,
554
+ $.aggregate_formula,
555
+ seq('-', $.aggregate_formula), // Negated formulas
556
+ ),
557
+
558
+ lookup_formula: $ => seq(
559
+ kw('lookup'),
560
+ '(',
561
+ field('target', $.calc_field_reference),
562
+ optional($.where_clause),
563
+ ')'
564
+ ),
565
+
566
+ aggregate_formula: $ => prec(15, seq(
567
+ field('function', alias($.identifier, $.aggregate_function)),
568
+ '(',
569
+ field('target', $.calc_field_reference),
570
+ optional($.where_clause),
571
+ ')'
572
+ )),
573
+
574
+ // Table.Field or "Table"."Field" or Namespace.Table."Field" reference used in CalcFormula
575
+ calc_field_reference: $ => prec.left(seq(
576
+ choice($._identifier_or_quoted, $.keyword_identifier),
577
+ repeat(seq('.', choice($._identifier_or_quoted, $.keyword_identifier)))
578
+ )),
579
+
580
+ // WHERE(conditions) clause — shared by CalcFormula, TableRelation, etc.
581
+ where_clause: $ => seq(
582
+ kw('where', 15),
583
+ '(',
584
+ $.where_conditions,
585
+ ')'
586
+ ),
587
+
588
+ where_conditions: $ => repeat1(choice(
589
+ seq($.where_condition, optional(',')),
590
+ $.preproc_conditional_where,
591
+ )),
592
+
593
+ preproc_conditional_where: $ => seq(
594
+ $.preproc_if,
595
+ repeat(seq($.where_condition, optional(','))),
596
+ repeat(seq(
597
+ $.preproc_elif,
598
+ repeat(seq($.where_condition, optional(','))),
599
+ )),
600
+ optional(seq(
601
+ $.preproc_else,
602
+ repeat(seq($.where_condition, optional(','))),
603
+ )),
604
+ $.preproc_endif,
605
+ ),
606
+
607
+ where_condition: $ => seq(
608
+ field('field', $._identifier_or_quoted),
609
+ '=',
610
+ choice(
611
+ // field("No.") or field(upperlimit("Date Filter")) or field(filter(Totaling))
612
+ seq(
613
+ choice(kw('field'), kw('upperlimit')),
614
+ '(',
615
+ choice(
616
+ field('value', $._identifier_or_quoted),
617
+ // field(filter(...)) — field with filter applied
618
+ seq($.filter_keyword, '(', field('value', $._identifier_or_quoted), ')'),
619
+ // field(upperlimit(...)) — field with upperlimit
620
+ seq(kw('upperlimit'), '(', choice(
621
+ field('value', $._identifier_or_quoted),
622
+ // upperlimit(filter(...)) — upperlimit with filter
623
+ seq($.filter_keyword, '(', field('value', $._identifier_or_quoted), ')'),
624
+ ), ')'),
625
+ ),
626
+ ')'
627
+ ),
628
+ // const(value) — also accepts keyword identifiers like Report, Page, Codeunit, Action
629
+ seq(kw('const'), '(', optional(field('value', choice(
630
+ $.string_literal, $.identifier, $.quoted_identifier, $.integer, $.boolean,
631
+ $.database_reference, $.qualified_enum_value, $.keyword_identifier,
632
+ ))), ')'),
633
+ // filter(expression)
634
+ seq($.filter_keyword, '(', field('value', $.filter_value), ')'),
635
+ )
636
+ ),
637
+
638
+ // Filter values — simplified, allows comparison operators, ranges, etc.
639
+ filter_value: $ => repeat1(choice(
640
+ $.string_literal,
641
+ $.identifier,
642
+ $.quoted_identifier,
643
+ $.integer,
644
+ $.decimal,
645
+ $.boolean, // true/false in filters
646
+ $.date_literal, // 0D, 20200101D
647
+ $.time_literal, // 0T, 120000T
648
+ $.datetime_literal, // 0DT
649
+ $.biginteger_literal, // 1000L
650
+ $.qualified_enum_value, // Enum::Value references
651
+ $.database_reference, // Report::"Name", Page::"Name", etc.
652
+ $.keyword_identifier, // Keywords used as filter values (Page, Codeunit, etc.)
653
+ '..', // Range operator
654
+ seq('-', $.integer), // Negative integer: -1, -100
655
+ '-', // Negation sign (standalone)
656
+ token(prec(-1, /[<>=|&@*%]+/)), // Filter operators like <>, |, =, >, etc.
657
+ )),
658
+
659
+ // --- Sorting/SourceTableView value ---
660
+ // sorting("Starting Date") order(ascending) where("Status" = const(Active))
661
+ sorting_value: $ => prec(5, seq(
662
+ kw('sorting'),
663
+ '(',
664
+ $._identifier_or_quoted,
665
+ repeat(seq(',', $._identifier_or_quoted)),
666
+ ')',
667
+ optional(seq(
668
+ kw('order'),
669
+ '(',
670
+ choice(kw('ascending'), kw('descending')),
671
+ ')',
672
+ )),
673
+ optional($.where_clause),
674
+ )),
675
+
676
+ // --- Link value list ---
677
+ // "Field" = field(OtherField), "Field2" = const(Value)
678
+ // Used by SubPageLink, SubPageView, DataItemLink, etc.
679
+ link_value_list: $ => prec.left(6, repeat1(choice(
680
+ seq($.link_value, optional(',')),
681
+ $.preproc_conditional_link_values,
682
+ ))),
683
+
684
+ preproc_conditional_link_values: $ => seq(
685
+ $.preproc_if,
686
+ repeat(seq($.link_value, optional(','))),
687
+ repeat(seq(
688
+ $.preproc_elif,
689
+ repeat(seq($.link_value, optional(','))),
690
+ )),
691
+ optional(seq(
692
+ $.preproc_else,
693
+ repeat(seq($.link_value, optional(','))),
694
+ )),
695
+ $.preproc_endif,
696
+ ),
697
+
698
+ link_value: $ => seq(
699
+ field('field', $._identifier_or_quoted),
700
+ '=',
701
+ choice(
702
+ seq(
703
+ choice(kw('field'), kw('upperlimit')),
704
+ '(',
705
+ choice(
706
+ field('value', $._identifier_or_quoted),
707
+ seq($.filter_keyword, '(', field('value', $._identifier_or_quoted), ')'),
708
+ seq(kw('upperlimit'), '(', choice(
709
+ field('value', $._identifier_or_quoted),
710
+ seq($.filter_keyword, '(', field('value', $._identifier_or_quoted), ')'),
711
+ ), ')'),
712
+ ),
713
+ ')'
714
+ ),
715
+ seq(kw('const'), '(', optional(field('value', choice(
716
+ $.string_literal, $.identifier, $.quoted_identifier, $.integer, $.boolean,
717
+ $.database_reference, $.qualified_enum_value, $.keyword_identifier,
718
+ ))), ')'),
719
+ seq($.filter_keyword, '(', field('value', $.filter_value), ')'),
720
+ // Direct reference: DataItem.FieldName (used in query DataItemLink)
721
+ field('value', prec(3, seq(
722
+ $._identifier_or_quoted,
723
+ '.', $._identifier_or_quoted
724
+ ))),
725
+ )
726
+ ),
727
+
728
+ // --- TableRelation value ---
729
+ // Customer where("No." = field("Customer No."))
730
+ // if("Type" = const(Item)) Item else Resource
731
+ table_relation_value: $ => prec.right(5, choice(
732
+ $.table_relation_expression,
733
+ $.preproc_conditional_table_relation,
734
+ )),
735
+
736
+ // Preprocessor conditionals inside TableRelation value
737
+ preproc_conditional_table_relation: $ => seq(
738
+ $.preproc_if,
739
+ optional(seq($.table_relation_expression, optional(';'))),
740
+ repeat(seq(
741
+ $.preproc_elif,
742
+ optional(seq($.table_relation_expression, optional(';'))),
743
+ )),
744
+ optional(seq(
745
+ $.preproc_else,
746
+ optional(seq($.table_relation_expression, optional(';'))),
747
+ )),
748
+ $.preproc_endif,
749
+ ),
750
+
751
+ table_relation_expression: $ => choice(
752
+ $.simple_table_relation,
753
+ $.if_table_relation,
754
+ $.preproc_conditional_table_relation,
755
+ ),
756
+
757
+ if_table_relation: $ => prec.right(15, seq(
758
+ kw('if'),
759
+ '(',
760
+ $.where_conditions,
761
+ ')',
762
+ field('then_relation', $.simple_table_relation),
763
+ optional(seq(
764
+ kw('else'),
765
+ field('else_relation', $.table_relation_expression)
766
+ ))
767
+ )),
768
+
769
+ simple_table_relation: $ => prec.right(20, seq(
770
+ field('table', choice($._namespaced_or_simple_ref, $.member_expression)),
771
+ optional(prec(25, $.where_clause))
772
+ )),
773
+
774
+ // --- Permissions value list ---
775
+ // tabledata Customer = R, tabledata "Sales Header" = RIMD
776
+ // Permission list that allows preprocessor conditionals between items
777
+ // Items separated by commas, with preproc blocks interleaved
778
+ tabledata_permission_list: $ => prec.left(repeat1(choice(
779
+ seq($.tabledata_permission, optional(',')),
780
+ $.preproc_conditional_permissions,
781
+ ))),
782
+
783
+ // Preprocessor conditionals inside permission lists
784
+ preproc_conditional_permissions: $ => seq(
785
+ $.preproc_if,
786
+ repeat(choice(
787
+ seq($.tabledata_permission, optional(',')),
788
+ $.preproc_conditional_permissions,
789
+ )),
790
+ repeat(seq(
791
+ $.preproc_elif,
792
+ repeat(choice(
793
+ seq($.tabledata_permission, optional(',')),
794
+ $.preproc_conditional_permissions,
795
+ )),
796
+ )),
797
+ optional(seq(
798
+ $.preproc_else,
799
+ repeat(choice(
800
+ seq($.tabledata_permission, optional(',')),
801
+ $.preproc_conditional_permissions,
802
+ )),
803
+ )),
804
+ $.preproc_endif,
805
+ ),
806
+
807
+ tabledata_permission: $ => seq(
808
+ choice(
809
+ kw('tabledata'),
810
+ $.table_keyword,
811
+ $.codeunit_keyword,
812
+ $.page_keyword,
813
+ $.report_keyword,
814
+ $.query_keyword,
815
+ $.xmlport_keyword,
816
+ kw('system'),
817
+ ),
818
+ field('table_name', choice($._namespaced_or_simple_ref, '*')),
819
+ '=',
820
+ field('permission', $.permission_type)
821
+ ),
822
+
823
+ permission_type: $ => token(prec(-1, new RustRegex('[rRiImMdDxX]+'))),
824
+
825
+ // --- OrderBy value list ---
826
+ // ascending("No.", Name)
827
+ order_by_list: $ => prec.left(5, seq(
828
+ $.order_by_item,
829
+ repeat(seq(',', $.order_by_item))
830
+ )),
831
+
832
+ order_by_item: $ => seq(
833
+ choice(kw('ascending', 5), kw('descending', 5)),
834
+ '(',
835
+ $._identifier_or_quoted,
836
+ repeat(seq(',', $._identifier_or_quoted)),
837
+ ')'
838
+ ),
839
+
840
+ // --- Implementation value list ---
841
+ // "My Interface" = "My Codeunit"
842
+ implementation_value_list: $ => prec.left(repeat1(choice(
843
+ seq($.implementation_value, optional(',')),
844
+ $.preproc_conditional_impl_values,
845
+ ))),
846
+
847
+ preproc_conditional_impl_values: $ => seq(
848
+ $.preproc_if,
849
+ repeat(seq($.implementation_value, optional(','))),
850
+ repeat(seq(
851
+ $.preproc_elif,
852
+ repeat(seq($.implementation_value, optional(','))),
853
+ )),
854
+ optional(seq(
855
+ $.preproc_else,
856
+ repeat(seq($.implementation_value, optional(','))),
857
+ )),
858
+ $.preproc_endif,
859
+ ),
860
+
861
+ implementation_value: $ => seq(
862
+ field('interface', $._identifier_or_quoted),
863
+ '=',
864
+ field('implementation', $._identifier_or_quoted)
865
+ ),
866
+
867
+ // --- OptionMembers value list ---
868
+ // Option1, Option2, "Option 3"
869
+ // Also supports leading/trailing commas for blank entries: ,Sale,"Total Sale"
870
+ option_member_list: $ => prec.right(choice(
871
+ // Standard: Member, Member, ... (also supports empty slots: Member,,Member)
872
+ seq(
873
+ $.option_member,
874
+ repeat(seq(',', optional($.option_member)))
875
+ ),
876
+ // With leading commas (blank entries): ,,,Member,Member
877
+ // Used by OptionMembers property and Option type: ,Sale,"Total Sale"
878
+ seq(
879
+ repeat1(','),
880
+ optional(seq(
881
+ $.option_member,
882
+ repeat(seq(',', optional($.option_member)))
883
+ ))
884
+ ),
885
+ )),
886
+
887
+ option_member: $ => choice(
888
+ $.identifier,
889
+ $.quoted_identifier,
890
+ $.string_literal,
891
+ $.integer, // Numeric option members (ValuesAllowed = 0, None, Partial)
892
+ seq('-', $.integer), // Negative integer option members (ValuesAllowed = -1)
893
+ $.keyword_identifier, // System, Action, etc.
894
+ alias($.keyword_as_identifier, $.identifier), // Type, Field, etc.
895
+ $.local_keyword, // 'Local' as option member
896
+ $.internal_keyword, // 'Internal' as option member
897
+ $.protected_keyword, // 'Protected' as option member
898
+ $.boolean, // true/false as option member
899
+ ),
900
+
901
+ // =====================================================================
902
+ // Table internals
903
+ // =====================================================================
904
+
905
+ // --- Fields section ---
906
+ // fields { field(1; "No."; Code[20]) { } }
907
+ fields_section: $ => seq(
908
+ $.fields_keyword,
909
+ '{',
910
+ repeat(choice(
911
+ $.field_declaration,
912
+ $.attribute_item,
913
+ $.preproc_conditional_fields,
914
+ // Extension modifications inside fields section
915
+ $.modify_modification,
916
+ )),
917
+ '}'
918
+ ),
919
+
920
+ preproc_conditional_fields: $ => seq(
921
+ $.preproc_if,
922
+ repeat(choice($.field_declaration, $.attribute_item, $.modify_modification)),
923
+ repeat(seq(
924
+ $.preproc_elif,
925
+ repeat(choice($.field_declaration, $.attribute_item, $.modify_modification)),
926
+ )),
927
+ optional(seq(
928
+ $.preproc_else,
929
+ repeat(choice($.field_declaration, $.attribute_item, $.modify_modification)),
930
+ )),
931
+ $.preproc_endif,
932
+ ),
933
+
934
+ field_declaration: $ => seq(
935
+ kw('field'),
936
+ '(',
937
+ field('id', $.integer),
938
+ ';',
939
+ field('name', $._identifier_or_quoted),
940
+ ';',
941
+ field('type', $.type_specification),
942
+ ')',
943
+ optional(seq(
944
+ '{',
945
+ repeat($._body_element),
946
+ '}'
947
+ ))
948
+ ),
949
+
950
+ // --- Keys section ---
951
+ // keys { key(PK; "No.") { Clustered = true; } }
952
+ keys_section: $ => seq(
953
+ $.keys_keyword,
954
+ '{',
955
+ repeat(choice(
956
+ $.key_declaration,
957
+ $.attribute_item,
958
+ $.preproc_conditional_keys,
959
+ )),
960
+ '}'
961
+ ),
962
+
963
+ preproc_conditional_keys: $ => seq(
964
+ $.preproc_if,
965
+ repeat(choice($.key_declaration, $.attribute_item)),
966
+ repeat(seq($.preproc_elif, repeat(choice($.key_declaration, $.attribute_item)))),
967
+ optional(seq($.preproc_else, repeat(choice($.key_declaration, $.attribute_item)))),
968
+ $.preproc_endif,
969
+ ),
970
+
971
+ key_declaration: $ => seq(
972
+ $.key_keyword,
973
+ '(',
974
+ field('name', $._identifier_or_quoted),
975
+ ';',
976
+ field('fields', $.field_list),
977
+ ')',
978
+ optional(seq(
979
+ '{',
980
+ repeat($._body_element),
981
+ '}'
982
+ ))
983
+ ),
984
+
985
+ // Comma-separated list of field names
986
+ field_list: $ => seq(
987
+ $._identifier_or_quoted,
988
+ repeat(seq(',', $._identifier_or_quoted))
989
+ ),
990
+
991
+ // --- Fieldgroups section ---
992
+ // fieldgroups { fieldgroup(DropDown; "No.", Name) { } }
993
+ fieldgroups_section: $ => seq(
994
+ $.fieldgroups_keyword,
995
+ '{',
996
+ repeat(choice(
997
+ $.fieldgroup_declaration,
998
+ $.preproc_conditional_fieldgroups,
999
+ // Extension modifications for fieldgroups
1000
+ $.addlast_fieldgroup_modification,
1001
+ $.addfirst_fieldgroup_modification,
1002
+ )),
1003
+ '}'
1004
+ ),
1005
+
1006
+ addlast_fieldgroup_modification: $ => seq(
1007
+ kw('addlast'), '(', field('target', $._identifier_or_quoted), ';',
1008
+ field('fields', $.field_list), ')',
1009
+ optional(seq('{', '}'))
1010
+ ),
1011
+
1012
+ addfirst_fieldgroup_modification: $ => seq(
1013
+ kw('addfirst'), '(', field('target', $._identifier_or_quoted), ';',
1014
+ field('fields', $.field_list), ')',
1015
+ optional(seq('{', '}'))
1016
+ ),
1017
+
1018
+ preproc_conditional_fieldgroups: $ => seq(
1019
+ $.preproc_if,
1020
+ repeat(choice($.fieldgroup_declaration, $.preproc_conditional_fieldgroups)),
1021
+ repeat(seq($.preproc_elif, repeat(choice($.fieldgroup_declaration, $.preproc_conditional_fieldgroups)))),
1022
+ optional(seq($.preproc_else, repeat(choice($.fieldgroup_declaration, $.preproc_conditional_fieldgroups)))),
1023
+ $.preproc_endif,
1024
+ ),
1025
+
1026
+ fieldgroup_declaration: $ => seq(
1027
+ $.fieldgroup_keyword,
1028
+ '(',
1029
+ field('name', $._identifier_or_quoted),
1030
+ ';',
1031
+ field('fields', $.field_list),
1032
+ ')',
1033
+ optional(seq(
1034
+ '{',
1035
+ repeat($._body_element),
1036
+ '}'
1037
+ ))
1038
+ ),
1039
+
1040
+ // =====================================================================
1041
+ // Type system
1042
+ // =====================================================================
1043
+
1044
+ type_specification: $ => choice(
1045
+ prec(2, $.array_type),
1046
+ prec(2, $.list_type),
1047
+ prec(2, $.dictionary_type),
1048
+ prec(1, $.text_type),
1049
+ prec(1, $.code_type),
1050
+ $.option_type,
1051
+ $.record_type,
1052
+ $.dotnet_type,
1053
+ $.object_reference_type, // Codeunit, Page, Report, Query, Xmlport, Enum, Interface
1054
+ $.basic_type,
1055
+ $.identifier, // Fallback for unknown types (HttpClient, DotNet, etc.)
1056
+ $.quoted_identifier, // Quoted type references
1057
+ ),
1058
+
1059
+ // Text[100] or plain Text
1060
+ text_type: $ => choice(
1061
+ prec(11, seq(kw('text'), '[', field('length', $.integer), ']')),
1062
+ prec(10, kw('text'))
1063
+ ),
1064
+
1065
+ // Code[20] or plain Code
1066
+ code_type: $ => choice(
1067
+ prec(11, seq(kw('code'), '[', field('length', $.integer), ']')),
1068
+ prec(10, kw('code'))
1069
+ ),
1070
+
1071
+ // Option with optional member list: Option OptionA, OptionB
1072
+ // Supports leading commas: Option ,,,,"Page","Query"
1073
+ option_type: $ => prec.right(1, seq(
1074
+ kw('option'),
1075
+ optional($.option_member_list)
1076
+ )),
1077
+
1078
+ // Record "Customer" or Record Customer or Record 2000000041 [temporary]
1079
+ // Also: Record System.Reflection.Field temporary
1080
+ record_type: $ => prec.right(seq(
1081
+ prec(1, kw('record')),
1082
+ field('reference', $._namespaced_or_simple_ref),
1083
+ optional($.temporary_keyword)
1084
+ )),
1085
+
1086
+ // DotNet "System.Text.StringBuilder" or DotNet System.DateTime type reference
1087
+ dotnet_type: $ => prec.right(seq(
1088
+ $.dotnet_keyword,
1089
+ field('reference', choice(
1090
+ $.string_literal,
1091
+ $._namespaced_or_simple_ref,
1092
+ ))
1093
+ )),
1094
+
1095
+ // Object reference types: Codeunit "Sales-Post", Page "Customer Card", etc.
1096
+ // Also supports namespaced: Codeunit System.Environment."Client Type Management"
1097
+ object_reference_type: $ => prec.right(seq(
1098
+ field('object_type', choice(
1099
+ $.codeunit_keyword,
1100
+ $.page_keyword,
1101
+ $.report_keyword,
1102
+ $.query_keyword,
1103
+ $.xmlport_keyword,
1104
+ $.enum_keyword,
1105
+ $.interface_keyword,
1106
+ kw('testpage'),
1107
+ kw('testrequestpage'),
1108
+ $.controladdin_keyword,
1109
+ )),
1110
+ field('reference', $._namespaced_or_simple_ref)
1111
+ )),
1112
+
1113
+ // Handles: "Name", Name, Namespace.Name, Namespace."Name", etc.
1114
+ _namespaced_or_simple_ref: $ => choice(
1115
+ $.integer,
1116
+ prec.right(seq(
1117
+ $._identifier_or_quoted,
1118
+ repeat(seq('.', $._identifier_or_quoted))
1119
+ )),
1120
+ ),
1121
+
1122
+ // array[10] of Integer, array[10,20] of Text[100]
1123
+ array_type: $ => seq(
1124
+ prec(1, kw('array')),
1125
+ '[',
1126
+ field('sizes', seq($.integer, repeat(seq(',', $.integer)))),
1127
+ ']',
1128
+ kw('of'),
1129
+ field('element_type', $.type_specification)
1130
+ ),
1131
+
1132
+ // List of [Integer]
1133
+ list_type: $ => seq(
1134
+ kw('list'),
1135
+ kw('of'),
1136
+ '[',
1137
+ field('element_type', $.type_specification),
1138
+ ']'
1139
+ ),
1140
+
1141
+ // Dictionary of [Text, Integer]
1142
+ dictionary_type: $ => seq(
1143
+ kw('dictionary'),
1144
+ kw('of'),
1145
+ '[',
1146
+ field('key_type', $.type_specification),
1147
+ ',',
1148
+ field('value_type', $.type_specification),
1149
+ ']'
1150
+ ),
1151
+
1152
+ // Common built-in types
1153
+ basic_type: $ => choice(
1154
+ // Numeric
1155
+ prec(1, kw('integer')),
1156
+ prec(1, kw('biginteger')),
1157
+ prec(1, kw('decimal')),
1158
+ prec(1, kw('byte')),
1159
+ // Text
1160
+ prec(1, kw('char')),
1161
+ prec(10, kw('label')),
1162
+ prec(1, kw('textbuilder')),
1163
+ prec(1, kw('textconst')),
1164
+ // Date/Time
1165
+ prec(1, kw('date')),
1166
+ prec(1, kw('time')),
1167
+ prec(1, kw('datetime')),
1168
+ prec(1, kw('duration')),
1169
+ kw('dateformula'),
1170
+ // Core
1171
+ prec(1, kw('boolean')),
1172
+ prec(1, kw('guid')),
1173
+ prec(1, kw('blob')),
1174
+ prec(1, kw('recordid')),
1175
+ prec(1, kw('recordref')),
1176
+ prec(1, kw('fieldref')),
1177
+ prec(1, kw('variant')),
1178
+ prec(1, kw('dialog')),
1179
+ prec(1, kw('action')),
1180
+ prec(1, kw('secrettext')),
1181
+ // JSON
1182
+ prec(1, kw('jsontoken')),
1183
+ prec(1, kw('jsonvalue')),
1184
+ prec(1, kw('jsonarray')),
1185
+ prec(1, kw('jsonobject')),
1186
+ // Media
1187
+ prec(1, kw('media')),
1188
+ prec(1, kw('mediaset')),
1189
+ // Stream
1190
+ prec(1, kw('instream')),
1191
+ prec(1, kw('outstream')),
1192
+ // HTTP
1193
+ prec(1, kw('httpclient')),
1194
+ prec(1, kw('httpcontent')),
1195
+ prec(1, kw('httpheaders')),
1196
+ prec(1, kw('httprequestmessage')),
1197
+ prec(1, kw('httpresponsemessage')),
1198
+ // Notification
1199
+ prec(1, kw('notification')),
1200
+ // Filter/Table
1201
+ prec(1, kw('filterpagebuilder')),
1202
+ prec(1, kw('tablefilter')),
1203
+ prec(1, kw('tableconnectiontype')),
1204
+ // XML
1205
+ prec(1, kw('xmldocument')),
1206
+ prec(1, kw('xmlelement')),
1207
+ prec(1, kw('xmlnode')),
1208
+ prec(1, kw('xmlnodelist')),
1209
+ prec(1, kw('xmlattribute')),
1210
+ // Error/Session
1211
+ prec(1, kw('errorinfo')),
1212
+ prec(1, kw('sessionsettings')),
1213
+ // File
1214
+ prec(1, kw('file')),
1215
+ prec(1, kw('fileupload')),
1216
+ // Other common types
1217
+ prec(1, kw('moduleinfo')),
1218
+ prec(1, kw('verbosity')),
1219
+ prec(1, kw('datatransfer')),
1220
+ prec(1, kw('version')),
1221
+ // Web
1222
+ prec(1, kw('webserviceactioncontext')),
1223
+ ),
1224
+
1225
+ // =====================================================================
1226
+ // Enum values
1227
+ // =====================================================================
1228
+
1229
+ // value(0; "None") { Caption = 'None'; }
1230
+ enum_value_declaration: $ => seq(
1231
+ kw('value'),
1232
+ '(',
1233
+ field('value_id', $.integer),
1234
+ ';',
1235
+ field('value_name', choice($._identifier_or_quoted, $.string_literal)),
1236
+ ')',
1237
+ '{',
1238
+ repeat($._body_element),
1239
+ '}'
1240
+ ),
1241
+
1242
+ // =====================================================================
1243
+ // Labels section (report labels)
1244
+ // =====================================================================
1245
+
1246
+ labels_section: $ => seq(
1247
+ $.labels_keyword,
1248
+ '{',
1249
+ repeat($.label_declaration),
1250
+ '}'
1251
+ ),
1252
+
1253
+ // name = 'value', Locked = true, Comment = 'text';
1254
+ label_declaration: $ => seq(
1255
+ field('name', $.identifier),
1256
+ '=',
1257
+ field('value', $.string_literal),
1258
+ repeat(seq(
1259
+ ',',
1260
+ choice(
1261
+ seq(kw('comment'), '=', field('comment', $.string_literal)),
1262
+ seq(kw('locked'), '=', field('locked', $.boolean)),
1263
+ seq(kw('maxlength'), '=', field('maxlength', $.integer)),
1264
+ )
1265
+ )),
1266
+ ';'
1267
+ ),
1268
+
1269
+ // =====================================================================
1270
+ // Page layout structure
1271
+ // =====================================================================
1272
+
1273
+ layout_section: $ => seq(
1274
+ $.layout_keyword,
1275
+ '{',
1276
+ repeat($._layout_element),
1277
+ '}'
1278
+ ),
1279
+
1280
+ _layout_element: $ => choice(
1281
+ $.area_section,
1282
+ $.group_section,
1283
+ $.repeater_section,
1284
+ $.cuegroup_section,
1285
+ $.fixed_section,
1286
+ $.grid_section,
1287
+ $.page_field,
1288
+ $.part_section,
1289
+ $.systempart_section,
1290
+ $.usercontrol_section,
1291
+ $.label_section,
1292
+ // Preprocessor in layout
1293
+ $.preproc_conditional_layout,
1294
+ $.preproc_split_field,
1295
+ // Extension layout modifications
1296
+ $.addfirst_modification,
1297
+ $.addlast_modification,
1298
+ $.addafter_modification,
1299
+ $.addbefore_modification,
1300
+ $.modify_modification,
1301
+ $.movefirst_modification,
1302
+ $.movelast_modification,
1303
+ $.moveafter_modification,
1304
+ $.movebefore_modification,
1305
+ ),
1306
+
1307
+ // area(Content) { ... }
1308
+ area_section: $ => seq(
1309
+ $.area_keyword,
1310
+ '(',
1311
+ field('type', choice(
1312
+ kw('content'),
1313
+ kw('factboxes'),
1314
+ kw('processing'),
1315
+ kw('rolecenter'),
1316
+ kw('prompting'),
1317
+ kw('prompt'),
1318
+ kw('promptoptions'),
1319
+ kw('systemactions'),
1320
+ $.identifier, // Fallback for future area types
1321
+ )),
1322
+ ')',
1323
+ '{',
1324
+ repeat($._layout_element),
1325
+ '}'
1326
+ ),
1327
+
1328
+ // group(General) { ... }
1329
+ group_section: $ => seq(
1330
+ $.group_keyword,
1331
+ '(',
1332
+ field('name', $._identifier_or_quoted),
1333
+ ')',
1334
+ '{',
1335
+ repeat(choice(
1336
+ $._body_element,
1337
+ $._layout_element,
1338
+ )),
1339
+ '}'
1340
+ ),
1341
+
1342
+ // repeater(Lines) { ... }
1343
+ repeater_section: $ => seq(
1344
+ $.repeater_keyword,
1345
+ '(',
1346
+ field('name', $._identifier_or_quoted),
1347
+ ')',
1348
+ '{',
1349
+ repeat(choice(
1350
+ $._body_element,
1351
+ $._layout_element,
1352
+ )),
1353
+ '}'
1354
+ ),
1355
+
1356
+ // cuegroup(Cues) { ... }
1357
+ cuegroup_section: $ => seq(
1358
+ $.cuegroup_keyword,
1359
+ '(',
1360
+ field('name', $._identifier_or_quoted),
1361
+ ')',
1362
+ '{',
1363
+ repeat(choice(
1364
+ $._body_element,
1365
+ $._layout_element,
1366
+ )),
1367
+ '}'
1368
+ ),
1369
+
1370
+ // fixed(Fixed) { ... }
1371
+ fixed_section: $ => seq(
1372
+ $.fixed_keyword,
1373
+ '(',
1374
+ field('name', $._identifier_or_quoted),
1375
+ ')',
1376
+ '{',
1377
+ repeat(choice(
1378
+ $._body_element,
1379
+ $._layout_element,
1380
+ )),
1381
+ '}'
1382
+ ),
1383
+
1384
+ // grid(Grid) { ... }
1385
+ grid_section: $ => seq(
1386
+ $.grid_keyword,
1387
+ '(',
1388
+ field('name', $._identifier_or_quoted),
1389
+ ')',
1390
+ '{',
1391
+ repeat(choice(
1392
+ $._body_element,
1393
+ $._layout_element,
1394
+ )),
1395
+ '}'
1396
+ ),
1397
+
1398
+ // Page field: field(Name; SourceExpr) { }
1399
+ // Different from table field — no ID, no type, uses source expression
1400
+ page_field: $ => seq(
1401
+ kw('field'),
1402
+ '(',
1403
+ field('name', $._identifier_or_quoted),
1404
+ ';',
1405
+ field('source', $._field_source),
1406
+ ')',
1407
+ optional(seq(
1408
+ '{',
1409
+ repeat($._body_element),
1410
+ '}'
1411
+ ))
1412
+ ),
1413
+
1414
+ // Source expression for page fields — uses full expression grammar
1415
+ _field_source: $ => $._expression,
1416
+
1417
+ // part(PartName; PageName) { }
1418
+ part_section: $ => seq(
1419
+ $.part_keyword,
1420
+ '(',
1421
+ field('name', $._identifier_or_quoted),
1422
+ ';',
1423
+ field('source', $._identifier_or_quoted),
1424
+ ')',
1425
+ '{',
1426
+ repeat($._body_element),
1427
+ '}'
1428
+ ),
1429
+
1430
+ // systempart(Links; "Record Link") { }
1431
+ systempart_section: $ => seq(
1432
+ $.systempart_keyword,
1433
+ '(',
1434
+ field('name', $._identifier_or_quoted),
1435
+ ';',
1436
+ field('source', $._identifier_or_quoted),
1437
+ ')',
1438
+ '{',
1439
+ repeat($._body_element),
1440
+ '}'
1441
+ ),
1442
+
1443
+ // usercontrol(ControlName; ControlAddinName) { }
1444
+ usercontrol_section: $ => seq(
1445
+ $.usercontrol_keyword,
1446
+ '(',
1447
+ field('name', $._identifier_or_quoted),
1448
+ ';',
1449
+ field('source', $._identifier_or_quoted),
1450
+ ')',
1451
+ '{',
1452
+ repeat($._body_element),
1453
+ '}'
1454
+ ),
1455
+
1456
+ // Preprocessor-split page field: #if field(...) #else field(...) #endif { props }
1457
+ preproc_split_field: $ => prec(25, seq(
1458
+ $.preproc_if,
1459
+ $._field_header,
1460
+ repeat(seq($.preproc_elif, $._field_header)),
1461
+ optional(seq($.preproc_else, $._field_header)),
1462
+ $.preproc_endif,
1463
+ optional(seq('{', repeat($._body_element), '}'))
1464
+ )),
1465
+
1466
+ _field_header: $ => seq(
1467
+ kw('field'),
1468
+ '(',
1469
+ $._identifier_or_quoted,
1470
+ ';',
1471
+ $._expression,
1472
+ ')',
1473
+ ),
1474
+
1475
+ // label(LabelName) { ... } — page label (not report label)
1476
+ label_section: $ => seq(
1477
+ kw('label'),
1478
+ '(',
1479
+ field('name', $._identifier_or_quoted),
1480
+ ')',
1481
+ '{',
1482
+ repeat($._body_element),
1483
+ '}'
1484
+ ),
1485
+
1486
+ // =====================================================================
1487
+ // Extension layout modifications
1488
+ // =====================================================================
1489
+
1490
+ // addfirst(AreaName) { field(...) { } }
1491
+ addfirst_modification: $ => seq(
1492
+ kw('addfirst'),
1493
+ '(',
1494
+ field('target', $._identifier_or_quoted),
1495
+ ')',
1496
+ '{',
1497
+ repeat($._layout_element),
1498
+ '}'
1499
+ ),
1500
+
1501
+ addlast_modification: $ => seq(
1502
+ kw('addlast'),
1503
+ '(',
1504
+ field('target', $._identifier_or_quoted),
1505
+ ')',
1506
+ '{',
1507
+ repeat($._layout_element),
1508
+ '}'
1509
+ ),
1510
+
1511
+ addafter_modification: $ => seq(
1512
+ kw('addafter'),
1513
+ '(',
1514
+ field('target', $._identifier_or_quoted),
1515
+ ')',
1516
+ '{',
1517
+ repeat($._layout_element),
1518
+ '}'
1519
+ ),
1520
+
1521
+ addbefore_modification: $ => seq(
1522
+ kw('addbefore'),
1523
+ '(',
1524
+ field('target', $._identifier_or_quoted),
1525
+ ')',
1526
+ '{',
1527
+ repeat($._layout_element),
1528
+ '}'
1529
+ ),
1530
+
1531
+ // modify("Name") { Visible = false; }
1532
+ modify_modification: $ => prec(2, seq(
1533
+ kw('modify'),
1534
+ '(',
1535
+ field('target', $._identifier_or_quoted),
1536
+ ')',
1537
+ '{',
1538
+ repeat($._body_element),
1539
+ '}'
1540
+ )),
1541
+
1542
+ // movefirst(Content; "No.")
1543
+ movefirst_modification: $ => seq(
1544
+ kw('movefirst'),
1545
+ '(',
1546
+ field('target', $._identifier_or_quoted),
1547
+ ';',
1548
+ field('element', $._identifier_or_quoted),
1549
+ ')'
1550
+ ),
1551
+
1552
+ movelast_modification: $ => seq(
1553
+ kw('movelast'),
1554
+ '(',
1555
+ field('target', $._identifier_or_quoted),
1556
+ ';',
1557
+ field('element', $._identifier_or_quoted),
1558
+ ')'
1559
+ ),
1560
+
1561
+ moveafter_modification: $ => seq(
1562
+ kw('moveafter'),
1563
+ '(',
1564
+ field('target', $._identifier_or_quoted),
1565
+ ';',
1566
+ field('element', $._identifier_or_quoted),
1567
+ ')'
1568
+ ),
1569
+
1570
+ movebefore_modification: $ => seq(
1571
+ kw('movebefore'),
1572
+ '(',
1573
+ field('target', $._identifier_or_quoted),
1574
+ ';',
1575
+ field('element', $._identifier_or_quoted),
1576
+ ')'
1577
+ ),
1578
+
1579
+ // =====================================================================
1580
+ // Actions structure
1581
+ // =====================================================================
1582
+
1583
+ actions_section: $ => seq(
1584
+ $.actions_keyword,
1585
+ '{',
1586
+ repeat($._action_element),
1587
+ '}'
1588
+ ),
1589
+
1590
+ _action_element: $ => choice(
1591
+ $.action_area_section,
1592
+ $.action_group_section,
1593
+ $.action_declaration,
1594
+ $.separator_action,
1595
+ $.actionref_declaration,
1596
+ $.systemaction_declaration,
1597
+ $.fileuploadaction_declaration,
1598
+ $.customaction_declaration,
1599
+ // Properties/triggers directly in action areas
1600
+ $.property,
1601
+ $.trigger_declaration,
1602
+ $.attribute_item,
1603
+ // Extension action modifications
1604
+ $.addfirst_action_modification,
1605
+ $.addlast_action_modification,
1606
+ $.addafter_action_modification,
1607
+ $.addbefore_action_modification,
1608
+ $.modify_action_modification,
1609
+ // Preprocessor in actions
1610
+ $.preproc_conditional_actions,
1611
+ ),
1612
+
1613
+ // area(Processing) { ... }
1614
+ action_area_section: $ => seq(
1615
+ $.area_keyword,
1616
+ '(',
1617
+ field('type', choice(
1618
+ kw('processing'),
1619
+ kw('reporting'),
1620
+ kw('navigation'),
1621
+ kw('creation'),
1622
+ kw('promoted'),
1623
+ kw('systemactions'),
1624
+ kw('sections'),
1625
+ kw('embedding'),
1626
+ kw('promptguide'),
1627
+ kw('prompting'),
1628
+ $.identifier, // Fallback
1629
+ )),
1630
+ ')',
1631
+ '{',
1632
+ repeat($._action_element),
1633
+ '}'
1634
+ ),
1635
+
1636
+ // group(ActionGroup) { ... }
1637
+ action_group_section: $ => seq(
1638
+ $.group_keyword,
1639
+ '(',
1640
+ field('name', $._identifier_or_quoted),
1641
+ ')',
1642
+ '{',
1643
+ repeat(choice(
1644
+ $._action_element,
1645
+ $._body_element,
1646
+ )),
1647
+ '}'
1648
+ ),
1649
+
1650
+ // action(MyAction) { ... }
1651
+ action_declaration: $ => seq(
1652
+ kw('action'),
1653
+ '(',
1654
+ field('name', $._identifier_or_quoted),
1655
+ ')',
1656
+ '{',
1657
+ repeat($._body_element),
1658
+ '}'
1659
+ ),
1660
+
1661
+ // separator { } or separator(Name) { Caption = ''; IsHeader = true; }
1662
+ separator_action: $ => seq(
1663
+ kw('separator'),
1664
+ optional(seq('(', field('name', $._identifier_or_quoted), ')')),
1665
+ '{',
1666
+ repeat($._body_element),
1667
+ '}'
1668
+ ),
1669
+
1670
+ // actionref(RefName; ActionName) { }
1671
+ actionref_declaration: $ => seq(
1672
+ kw('actionref'),
1673
+ '(',
1674
+ field('promoted_name', $._identifier_or_quoted),
1675
+ ';',
1676
+ field('action_name', $._identifier_or_quoted),
1677
+ ')',
1678
+ '{',
1679
+ repeat($._body_element),
1680
+ '}'
1681
+ ),
1682
+
1683
+ // systemaction(Name) { }
1684
+ systemaction_declaration: $ => seq(
1685
+ kw('systemaction'),
1686
+ '(',
1687
+ field('name', $._identifier_or_quoted),
1688
+ ')',
1689
+ '{',
1690
+ repeat($._body_element),
1691
+ '}'
1692
+ ),
1693
+
1694
+ // fileuploadaction(Name) { }
1695
+ fileuploadaction_declaration: $ => seq(
1696
+ kw('fileuploadaction'),
1697
+ '(',
1698
+ field('name', $._identifier_or_quoted),
1699
+ ')',
1700
+ '{',
1701
+ repeat($._body_element),
1702
+ '}'
1703
+ ),
1704
+
1705
+ // customaction(Name) { }
1706
+ customaction_declaration: $ => seq(
1707
+ kw('customaction'),
1708
+ '(',
1709
+ field('name', $._identifier_or_quoted),
1710
+ ')',
1711
+ '{',
1712
+ repeat($._body_element),
1713
+ '}'
1714
+ ),
1715
+
1716
+ // =====================================================================
1717
+ // Extension action modifications
1718
+ // =====================================================================
1719
+
1720
+ addfirst_action_modification: $ => seq(
1721
+ kw('addfirst'),
1722
+ '(',
1723
+ field('target', $._identifier_or_quoted),
1724
+ ')',
1725
+ '{',
1726
+ repeat($._action_element),
1727
+ '}'
1728
+ ),
1729
+
1730
+ addlast_action_modification: $ => seq(
1731
+ kw('addlast'),
1732
+ '(',
1733
+ field('target', $._identifier_or_quoted),
1734
+ ')',
1735
+ '{',
1736
+ repeat($._action_element),
1737
+ '}'
1738
+ ),
1739
+
1740
+ addafter_action_modification: $ => seq(
1741
+ kw('addafter'),
1742
+ '(',
1743
+ field('target', $._identifier_or_quoted),
1744
+ ')',
1745
+ '{',
1746
+ repeat($._action_element),
1747
+ '}'
1748
+ ),
1749
+
1750
+ addbefore_action_modification: $ => seq(
1751
+ kw('addbefore'),
1752
+ '(',
1753
+ field('target', $._identifier_or_quoted),
1754
+ ')',
1755
+ '{',
1756
+ repeat($._action_element),
1757
+ '}'
1758
+ ),
1759
+
1760
+ modify_action_modification: $ => prec(2, seq(
1761
+ kw('modify'),
1762
+ '(',
1763
+ field('target', $._identifier_or_quoted),
1764
+ ')',
1765
+ '{',
1766
+ repeat($._body_element),
1767
+ '}'
1768
+ )),
1769
+
1770
+ // =====================================================================
1771
+ // Views section
1772
+ // =====================================================================
1773
+
1774
+ views_section: $ => seq(
1775
+ $.views_keyword,
1776
+ '{',
1777
+ repeat(choice(
1778
+ $.view_definition,
1779
+ // Extension modifications for views (with or without target)
1780
+ $.addfirst_modification,
1781
+ $.addlast_modification,
1782
+ $.addafter_modification,
1783
+ $.addbefore_modification,
1784
+ $.modify_modification,
1785
+ $.addfirst_views_modification,
1786
+ $.addlast_views_modification,
1787
+ $.addafter_views_modification,
1788
+ $.addbefore_views_modification,
1789
+ )),
1790
+ '}'
1791
+ ),
1792
+
1793
+ // Views extensions without target: addfirst { view(...) { } }
1794
+ addfirst_views_modification: $ => seq(
1795
+ kw('addfirst'),
1796
+ '{',
1797
+ repeat($.view_definition),
1798
+ '}'
1799
+ ),
1800
+
1801
+ addlast_views_modification: $ => seq(
1802
+ kw('addlast'),
1803
+ '{',
1804
+ repeat($.view_definition),
1805
+ '}'
1806
+ ),
1807
+
1808
+ // Views extensions with target: addafter(ViewName) { view(...) { } }
1809
+ addafter_views_modification: $ => seq(
1810
+ kw('addafter'),
1811
+ '(', field('target', $._identifier_or_quoted), ')',
1812
+ '{',
1813
+ repeat($.view_definition),
1814
+ '}'
1815
+ ),
1816
+
1817
+ addbefore_views_modification: $ => seq(
1818
+ kw('addbefore'),
1819
+ '(', field('target', $._identifier_or_quoted), ')',
1820
+ '{',
1821
+ repeat($.view_definition),
1822
+ '}'
1823
+ ),
1824
+
1825
+ view_definition: $ => seq(
1826
+ $.view_keyword,
1827
+ '(',
1828
+ field('name', $._identifier_or_quoted),
1829
+ ')',
1830
+ '{',
1831
+ repeat($._body_element),
1832
+ '}'
1833
+ ),
1834
+
1835
+ // =====================================================================
1836
+ // Report sections
1837
+ // =====================================================================
1838
+
1839
+ // dataset { dataitem(...) { column(...) { } } }
1840
+ dataset_section: $ => seq(
1841
+ $.dataset_keyword,
1842
+ '{',
1843
+ repeat(choice(
1844
+ $.report_dataitem,
1845
+ $.attribute_item,
1846
+ $.preproc_conditional_dataset,
1847
+ // Extension modifications for dataset
1848
+ $.addfirst_dataset_modification,
1849
+ $.addlast_dataset_modification,
1850
+ $.addafter_dataset_modification,
1851
+ $.addbefore_dataset_modification,
1852
+ $.add_dataset_modification,
1853
+ $.modify_modification,
1854
+ )),
1855
+ '}'
1856
+ ),
1857
+
1858
+ // Preprocessor conditionals at dataset section level (around dataitems)
1859
+ preproc_conditional_dataset: $ => seq(
1860
+ $.preproc_if,
1861
+ repeat(choice($.report_dataitem, $.attribute_item)),
1862
+ repeat(seq($.preproc_elif, repeat(choice($.report_dataitem, $.attribute_item)))),
1863
+ optional(seq($.preproc_else, repeat(choice($.report_dataitem, $.attribute_item)))),
1864
+ $.preproc_endif,
1865
+ ),
1866
+
1867
+ report_dataitem: $ => seq(
1868
+ $.dataitem_keyword,
1869
+ '(',
1870
+ field('name', $._identifier_or_quoted),
1871
+ ';',
1872
+ field('table_name', $._namespaced_or_simple_ref),
1873
+ ')',
1874
+ '{',
1875
+ repeat($._report_body_element),
1876
+ '}'
1877
+ ),
1878
+
1879
+ _report_body_element: $ => choice(
1880
+ $.report_column,
1881
+ $.report_dataitem, // Nested dataitems
1882
+ $._body_element,
1883
+ $.preproc_conditional_report,
1884
+ ),
1885
+
1886
+ preproc_conditional_report: $ => seq(
1887
+ $.preproc_if,
1888
+ repeat($._report_body_element),
1889
+ repeat(seq($.preproc_elif, repeat($._report_body_element))),
1890
+ optional(seq($.preproc_else, repeat($._report_body_element))),
1891
+ $.preproc_endif,
1892
+ ),
1893
+
1894
+ report_column: $ => seq(
1895
+ $.column_keyword,
1896
+ '(',
1897
+ field('name', $._identifier_or_quoted),
1898
+ ';',
1899
+ field('source', $._field_source),
1900
+ ')',
1901
+ '{',
1902
+ repeat($._body_element),
1903
+ '}'
1904
+ ),
1905
+
1906
+ // Extension dataset modifications
1907
+ add_dataset_modification: $ => seq(
1908
+ kw('add'), '(', field('target', $._identifier_or_quoted), ')',
1909
+ '{', repeat(choice($.report_dataitem, $.report_column, $._body_element)), '}'
1910
+ ),
1911
+ addfirst_dataset_modification: $ => seq(
1912
+ kw('addfirst'),
1913
+ optional(seq('(', field('target', $._identifier_or_quoted), ')')),
1914
+ '{', repeat(choice($.report_dataitem, $.report_column, $._body_element)), '}'
1915
+ ),
1916
+ addlast_dataset_modification: $ => seq(
1917
+ kw('addlast'), '(', field('target', $._identifier_or_quoted), ')',
1918
+ '{', repeat(choice($.report_dataitem, $.report_column, $._body_element)), '}'
1919
+ ),
1920
+ addafter_dataset_modification: $ => seq(
1921
+ kw('addafter'), '(', field('target', $._identifier_or_quoted), ')',
1922
+ '{', repeat(choice($.report_dataitem, $.report_column, $._body_element)), '}'
1923
+ ),
1924
+ addbefore_dataset_modification: $ => seq(
1925
+ kw('addbefore'), '(', field('target', $._identifier_or_quoted), ')',
1926
+ '{', repeat(choice($.report_dataitem, $.report_column, $._body_element)), '}'
1927
+ ),
1928
+
1929
+ // requestpage { layout { ... } actions { ... } }
1930
+ requestpage_section: $ => seq(
1931
+ $.requestpage_keyword,
1932
+ '{',
1933
+ repeat($._body_element),
1934
+ '}'
1935
+ ),
1936
+
1937
+ // rendering { layout(Name) { ... } }
1938
+ rendering_section: $ => seq(
1939
+ $.rendering_keyword,
1940
+ '{',
1941
+ repeat($.rendering_layout),
1942
+ '}'
1943
+ ),
1944
+
1945
+ rendering_layout: $ => seq(
1946
+ $.layout_keyword,
1947
+ '(',
1948
+ field('name', $._identifier_or_quoted),
1949
+ ')',
1950
+ '{',
1951
+ repeat($._body_element),
1952
+ '}'
1953
+ ),
1954
+
1955
+ // =====================================================================
1956
+ // Query sections
1957
+ // =====================================================================
1958
+
1959
+ // elements { dataitem(...) { column(...) { } filter(...) { } } }
1960
+ elements_section: $ => seq(
1961
+ $.elements_keyword,
1962
+ '{',
1963
+ repeat($.query_dataitem),
1964
+ '}'
1965
+ ),
1966
+
1967
+ query_dataitem: $ => seq(
1968
+ $.dataitem_keyword,
1969
+ '(',
1970
+ field('name', $._identifier_or_quoted),
1971
+ ';',
1972
+ field('table_name', $._namespaced_or_simple_ref),
1973
+ ')',
1974
+ '{',
1975
+ repeat($._query_body_element),
1976
+ '}'
1977
+ ),
1978
+
1979
+ _query_body_element: $ => choice(
1980
+ $.query_column,
1981
+ $.query_filter,
1982
+ $.query_dataitem, // Nested dataitems
1983
+ $._body_element,
1984
+ $.preproc_conditional_query,
1985
+ ),
1986
+
1987
+ preproc_conditional_query: $ => seq(
1988
+ $.preproc_if,
1989
+ repeat($._query_body_element),
1990
+ repeat(seq($.preproc_elif, repeat($._query_body_element))),
1991
+ optional(seq($.preproc_else, repeat($._query_body_element))),
1992
+ $.preproc_endif,
1993
+ ),
1994
+
1995
+ query_column: $ => seq(
1996
+ $.column_keyword,
1997
+ '(',
1998
+ choice(
1999
+ // Standard: column(Name; FieldName)
2000
+ seq(
2001
+ field('name', $._identifier_or_quoted),
2002
+ ';',
2003
+ field('field_name', $._identifier_or_quoted)
2004
+ ),
2005
+ // Computed: column(Name)
2006
+ field('name', $._identifier_or_quoted)
2007
+ ),
2008
+ ')',
2009
+ '{',
2010
+ repeat($._body_element),
2011
+ '}'
2012
+ ),
2013
+
2014
+ query_filter: $ => seq(
2015
+ $.filter_keyword,
2016
+ '(',
2017
+ field('name', $._identifier_or_quoted),
2018
+ ';',
2019
+ field('field_name', $._identifier_or_quoted),
2020
+ ')',
2021
+ '{',
2022
+ repeat($._body_element),
2023
+ '}'
2024
+ ),
2025
+
2026
+ // =====================================================================
2027
+ // XMLport sections
2028
+ // =====================================================================
2029
+
2030
+ // schema { tableelement(...) { fieldelement(...) { } } }
2031
+ schema_section: $ => seq(
2032
+ $.schema_keyword,
2033
+ '{',
2034
+ repeat($.xmlport_element),
2035
+ '}'
2036
+ ),
2037
+
2038
+ // tableelement/fieldelement/textelement(Name; Source) { ... }
2039
+ xmlport_element: $ => seq(
2040
+ field('element_type', choice(
2041
+ kw('tableelement'),
2042
+ kw('fieldelement'),
2043
+ kw('textelement'),
2044
+ )),
2045
+ '(',
2046
+ field('name', $._identifier_or_quoted),
2047
+ optional(seq(
2048
+ ';',
2049
+ field('source', $._field_source)
2050
+ )),
2051
+ ')',
2052
+ '{',
2053
+ repeat($._xmlport_body_element),
2054
+ '}'
2055
+ ),
2056
+
2057
+ _xmlport_body_element: $ => choice(
2058
+ $.xmlport_element,
2059
+ $.xmlport_attribute,
2060
+ $._body_element,
2061
+ $.preproc_conditional_xmlport,
2062
+ ),
2063
+
2064
+ preproc_conditional_xmlport: $ => seq(
2065
+ $.preproc_if,
2066
+ repeat($._xmlport_body_element),
2067
+ repeat(seq($.preproc_elif, repeat($._xmlport_body_element))),
2068
+ optional(seq($.preproc_else, repeat($._xmlport_body_element))),
2069
+ $.preproc_endif,
2070
+ ),
2071
+
2072
+ // fieldattribute/textattribute(Name; Source) { ... }
2073
+ xmlport_attribute: $ => seq(
2074
+ field('attribute_type', choice(
2075
+ kw('fieldattribute'),
2076
+ kw('textattribute'),
2077
+ )),
2078
+ '(',
2079
+ field('name', $._identifier_or_quoted),
2080
+ optional(seq(
2081
+ ';',
2082
+ field('source', $._field_source)
2083
+ )),
2084
+ ')',
2085
+ '{',
2086
+ repeat($._body_element),
2087
+ '}'
2088
+ ),
2089
+
2090
+ // =====================================================================
2091
+ // DotNet internals
2092
+ // =====================================================================
2093
+
2094
+ assembly_declaration: $ => seq(
2095
+ kw('assembly'),
2096
+ '(',
2097
+ field('name', choice(
2098
+ $.string_literal,
2099
+ $.quoted_identifier,
2100
+ $.dotnet_assembly_name
2101
+ )),
2102
+ ')',
2103
+ '{',
2104
+ repeat(choice(
2105
+ $.type_declaration,
2106
+ $.property,
2107
+ $.empty_statement,
2108
+ )),
2109
+ '}'
2110
+ ),
2111
+
2112
+ dotnet_assembly_name: $ => token(seq(
2113
+ /[\p{L}_][\p{L}\p{N}_]*/u,
2114
+ repeat(seq('.', /[\p{L}_][\p{L}\p{N}_]*/u))
2115
+ )),
2116
+
2117
+ type_declaration: $ => seq(
2118
+ kw('type'),
2119
+ '(',
2120
+ field('dotnet_type', choice(
2121
+ $.string_literal,
2122
+ $.quoted_identifier,
2123
+ $.dotnet_assembly_name
2124
+ )),
2125
+ ';',
2126
+ field('al_name', choice(
2127
+ $.string_literal,
2128
+ $.quoted_identifier,
2129
+ $.identifier
2130
+ )),
2131
+ ')',
2132
+ '{',
2133
+ repeat($._body_element),
2134
+ '}'
2135
+ ),
2136
+
2137
+ // =====================================================================
2138
+ // ControlAddIn event declarations
2139
+ // =====================================================================
2140
+
2141
+ // event OnReady();
2142
+ event_declaration: $ => prec.right(seq(
2143
+ $.event_keyword,
2144
+ field('name', $._identifier_or_quoted),
2145
+ '(',
2146
+ optional($.parameter_list),
2147
+ ')',
2148
+ optional(';')
2149
+ )),
2150
+
2151
+ // =====================================================================
2152
+ // Procedures
2153
+ // =====================================================================
2154
+
2155
+ procedure: $ => prec.right(seq(
2156
+ optional(field('modifier', $.procedure_modifier)),
2157
+ $.procedure_keyword,
2158
+ field('name', $._identifier_or_quoted),
2159
+ '(',
2160
+ optional($.parameter_list),
2161
+ ')',
2162
+ optional(choice(
2163
+ seq(
2164
+ choice(
2165
+ $._procedure_return_specification,
2166
+ $._procedure_named_return,
2167
+ ),
2168
+ optional(';')
2169
+ )
2170
+ )),
2171
+ optional(';'),
2172
+ optional(choice(
2173
+ $.var_section,
2174
+ $.preproc_conditional_var_block,
2175
+ )),
2176
+ $.code_block
2177
+ )),
2178
+
2179
+ // Preprocessor-split procedure: header variants in #if/#else, shared body
2180
+ preproc_split_procedure: $ => prec(25, seq(
2181
+ $.preproc_if,
2182
+ $._procedure_header,
2183
+ repeat(seq($.preproc_elif, $._procedure_header)),
2184
+ optional(seq($.preproc_else, $._procedure_header)),
2185
+ $.preproc_endif,
2186
+ optional(';'),
2187
+ optional(choice(
2188
+ $.var_section,
2189
+ $.preproc_conditional_var_block,
2190
+ )),
2191
+ $.code_block,
2192
+ )),
2193
+
2194
+ // Procedure header without body (used in preproc split procedures)
2195
+ _procedure_header: $ => seq(
2196
+ repeat($.attribute_item),
2197
+ optional(field('modifier', $.procedure_modifier)),
2198
+ $.procedure_keyword,
2199
+ field('name', $._identifier_or_quoted),
2200
+ '(',
2201
+ optional($.parameter_list),
2202
+ ')',
2203
+ optional(choice(
2204
+ seq(
2205
+ choice(
2206
+ $._procedure_return_specification,
2207
+ $._procedure_named_return,
2208
+ ),
2209
+ optional(';')
2210
+ )
2211
+ )),
2212
+ ),
2213
+
2214
+ // Interface procedure declaration (no body, just signature)
2215
+ // Uses prec.dynamic to prefer full procedure when body follows
2216
+ interface_procedure: $ => prec.dynamic(-1, prec.right(-5, seq(
2217
+ $.procedure_keyword,
2218
+ field('name', $._identifier_or_quoted),
2219
+ '(',
2220
+ optional($.parameter_list),
2221
+ ')',
2222
+ optional($.interface_procedure_suffix),
2223
+ ))),
2224
+
2225
+ // Suffix for interface_procedure: return type and/or semicolon
2226
+ // Separated as a named rule to avoid state-sharing with _procedure_header
2227
+ interface_procedure_suffix: $ => choice(
2228
+ seq($._procedure_return_specification, optional(';')),
2229
+ seq($._procedure_named_return, optional(';')),
2230
+ ';',
2231
+ ),
2232
+
2233
+ procedure_modifier: $ => choice(
2234
+ $.local_keyword,
2235
+ $.internal_keyword,
2236
+ $.protected_keyword,
2237
+ ),
2238
+
2239
+ // Return type: `: TypeSpec`
2240
+ _procedure_return_specification: $ => seq(
2241
+ ':',
2242
+ field('return_type', $.type_specification),
2243
+ ),
2244
+
2245
+ // Named return: `result: TypeSpec`
2246
+ _procedure_named_return: $ => prec(15, seq(
2247
+ field('return_value', $._identifier_or_quoted),
2248
+ $._procedure_return_specification,
2249
+ )),
2250
+
2251
+ // Parameter list: semicolon-separated parameters
2252
+ parameter_list: $ => seq(
2253
+ optional(repeat1($.attribute_item)),
2254
+ $.parameter,
2255
+ repeat(seq(';', optional(repeat1($.attribute_item)), $.parameter)),
2256
+ ),
2257
+
2258
+ parameter: $ => seq(
2259
+ optional(field('modifier', $.var_keyword)),
2260
+ field('name', $._identifier_or_quoted),
2261
+ ':',
2262
+ field('type', $.type_specification),
2263
+ ),
2264
+
2265
+ // code_block: begin ... end;
2266
+ // Using kw() for begin/end — anonymous tokens, NOT named rules
2267
+ code_block: $ => prec.right(seq(
2268
+ kw('begin'),
2269
+ repeat($._statement),
2270
+ kw('end'),
2271
+ optional(';'),
2272
+ )),
2273
+
2274
+ // =====================================================================
2275
+ // Triggers
2276
+ // =====================================================================
2277
+
2278
+ trigger_declaration: $ => seq(
2279
+ $.trigger_keyword,
2280
+ field('name', $._trigger_name),
2281
+ '(',
2282
+ optional($.parameter_list),
2283
+ ')',
2284
+ optional(choice(
2285
+ $._procedure_return_specification,
2286
+ $._procedure_named_return,
2287
+ )),
2288
+ optional(';'),
2289
+ optional(choice(
2290
+ $.var_section,
2291
+ $.preproc_conditional_var_block,
2292
+ )),
2293
+ $.code_block
2294
+ ),
2295
+
2296
+ // Preprocessor conditional wrapping a var section before begin
2297
+ preproc_conditional_var_block: $ => seq(
2298
+ $.preproc_if,
2299
+ optional($.var_section),
2300
+ repeat(seq(
2301
+ $.preproc_elif,
2302
+ optional($.var_section),
2303
+ )),
2304
+ optional(seq(
2305
+ $.preproc_else,
2306
+ optional($.var_section),
2307
+ )),
2308
+ $.preproc_endif,
2309
+ ),
2310
+
2311
+ // Trigger name: simple name or scoped name (UserTours::ShowTourWizard)
2312
+ _trigger_name: $ => choice(
2313
+ seq($._identifier_or_quoted, '::', $._identifier_or_quoted),
2314
+ $._identifier_or_quoted,
2315
+ ),
2316
+
2317
+ // =====================================================================
2318
+ // Variable declarations
2319
+ // =====================================================================
2320
+
2321
+ var_section: $ => prec.right(seq(
2322
+ optional(choice($.protected_keyword, $.local_keyword)),
2323
+ $.var_keyword,
2324
+ repeat(choice(
2325
+ $.variable_declaration,
2326
+ $.attribute_item,
2327
+ $.preproc_conditional_var,
2328
+ $.preproc_split_procedure,
2329
+ )),
2330
+ )),
2331
+
2332
+ preproc_conditional_var: $ => seq(
2333
+ $.preproc_if,
2334
+ repeat(choice($.variable_declaration, $.attribute_item, $._body_element)),
2335
+ repeat(seq(
2336
+ $.preproc_elif,
2337
+ repeat(choice($.variable_declaration, $.attribute_item, $._body_element)),
2338
+ )),
2339
+ optional(seq(
2340
+ $.preproc_else,
2341
+ repeat(choice($.variable_declaration, $.attribute_item, $._body_element)),
2342
+ )),
2343
+ $.preproc_endif,
2344
+ ),
2345
+
2346
+ variable_declaration: $ => choice(
2347
+ // Label variable: Name: Label 'text', Locked = true;
2348
+ prec(5, seq(
2349
+ field('name', $._identifier_or_quoted),
2350
+ ':',
2351
+ field('type', $.basic_type), // Must be Label type
2352
+ field('value', $.string_literal),
2353
+ optional(seq(
2354
+ ',',
2355
+ optional(seq(
2356
+ $.label_attribute,
2357
+ repeat(seq(',', $.label_attribute))
2358
+ ))
2359
+ )),
2360
+ ';'
2361
+ )),
2362
+ // Multi-name variable: Name1, Name2, Name3 : Type;
2363
+ prec(3, seq(
2364
+ field('name', $._identifier_or_quoted),
2365
+ repeat1(seq(',', field('name', $._identifier_or_quoted))),
2366
+ ':',
2367
+ field('type', $.type_specification),
2368
+ ';'
2369
+ )),
2370
+ // Regular variable: Name: Type;
2371
+ prec(1, seq(
2372
+ field('name', $._identifier_or_quoted),
2373
+ ':',
2374
+ field('type', $.type_specification),
2375
+ ';'
2376
+ )),
2377
+ ),
2378
+
2379
+ label_attribute: $ => seq(
2380
+ field('name', $.identifier),
2381
+ '=',
2382
+ field('value', choice($.boolean, $.string_literal, $.integer))
2383
+ ),
2384
+
2385
+ // =====================================================================
2386
+ // Attributes
2387
+ // =====================================================================
2388
+
2389
+ attribute_item: $ => seq(
2390
+ '[',
2391
+ field('attribute', $.attribute_content),
2392
+ ']'
2393
+ ),
2394
+
2395
+ attribute_content: $ => seq(
2396
+ field('name', $.identifier),
2397
+ optional(field('arguments', $.attribute_arguments)),
2398
+ ),
2399
+
2400
+ attribute_arguments: $ => seq(
2401
+ '(',
2402
+ optional($.attribute_argument_list),
2403
+ ')'
2404
+ ),
2405
+
2406
+ attribute_argument_list: $ => seq(
2407
+ $._attribute_argument,
2408
+ repeat(seq(',', $._attribute_argument)),
2409
+ ),
2410
+
2411
+ _attribute_argument: $ => choice(
2412
+ $.boolean,
2413
+ $.integer,
2414
+ $.string_literal,
2415
+ $.identifier,
2416
+ $.quoted_identifier,
2417
+ $.qualified_enum_value,
2418
+ $.database_reference,
2419
+ $.member_expression,
2420
+ ),
2421
+
2422
+ // =====================================================================
2423
+ // Preprocessor directives
2424
+ // =====================================================================
2425
+
2426
+ // Structural preprocessor conditionals: #if/#elif/#else/#endif
2427
+ preproc_conditional: $ => seq(
2428
+ $.preproc_if,
2429
+ repeat($._body_element),
2430
+ repeat(seq(
2431
+ $.preproc_elif,
2432
+ repeat($._body_element),
2433
+ )),
2434
+ optional(seq(
2435
+ $.preproc_else,
2436
+ repeat($._body_element),
2437
+ )),
2438
+ $.preproc_endif,
2439
+ ),
2440
+
2441
+ preproc_if: $ => seq(
2442
+ choice('#if', '#IF', '#If'),
2443
+ field('condition', choice(
2444
+ $.identifier,
2445
+ $.preproc_not_expression,
2446
+ ))
2447
+ ),
2448
+
2449
+ preproc_not_expression: $ => seq(
2450
+ choice('not', 'NOT', 'Not'),
2451
+ $.identifier
2452
+ ),
2453
+
2454
+ preproc_elif: $ => seq(
2455
+ choice('#elif', '#ELIF', '#Elif'),
2456
+ field('condition', choice(
2457
+ $.identifier,
2458
+ $.preproc_not_expression,
2459
+ ))
2460
+ ),
2461
+
2462
+ preproc_else: $ => choice('#else', '#ELSE', '#Else'),
2463
+
2464
+ preproc_endif: $ => choice('#endif', '#ENDIF', '#Endif'),
2465
+
2466
+ // Preprocessor-split if statement: if header varies across #if/#else, body is shared
2467
+ // Pattern 1: #if COND / if (expr) then / #else / if (expr) then / #endif / body;
2468
+ // Pattern 2: #if COND / if (expr) then / #endif / body; (no #else)
2469
+ preproc_split_if_statement: $ => prec.right(seq(
2470
+ $.preproc_if,
2471
+ $._preproc_if_header,
2472
+ repeat(seq($.preproc_elif, $._preproc_if_header)),
2473
+ optional(seq($.preproc_else, $._preproc_if_header)),
2474
+ $.preproc_endif,
2475
+ field('then_branch', choice(
2476
+ $.code_block,
2477
+ $._statement,
2478
+ )),
2479
+ optional(seq(
2480
+ $.else_keyword,
2481
+ field('else_branch', choice(
2482
+ $.code_block,
2483
+ prec(1, $.if_statement),
2484
+ $._statement,
2485
+ ))
2486
+ ))
2487
+ )),
2488
+
2489
+ // If-then header (condition only, no body) — used in preproc split if statements
2490
+ _preproc_if_header: $ => seq(
2491
+ $.if_keyword,
2492
+ field('condition', $._expression),
2493
+ $.then_keyword,
2494
+ ),
2495
+
2496
+ // Preprocessor-guarded statement: guard statements + split if-then before shared code
2497
+ // #if COND / guard_stmts; if X then / #endif / shared_statement;
2498
+ // In COND mode: guard_stmts; if X then shared_statement;
2499
+ // Otherwise: just shared_statement;
2500
+ preproc_guarded_statement: $ => prec.right(seq(
2501
+ $.preproc_if,
2502
+ $._preproc_guard_block,
2503
+ $.preproc_endif,
2504
+ field('then_branch', choice(
2505
+ $.code_block,
2506
+ $._statement,
2507
+ )),
2508
+ )),
2509
+
2510
+ // Guard block: one or more expression statements followed by an if-then header
2511
+ // Uses _expression_statement to avoid consuming if-statements that could
2512
+ // be mistaken for _preproc_if_header
2513
+ _preproc_guard_block: $ => seq(
2514
+ repeat1(seq(prec(2, $._expression_statement), ';')),
2515
+ $._preproc_if_header,
2516
+ ),
2517
+
2518
+ // Preprocessor-split if-else: full if-then-X-else inside #if, Y outside
2519
+ // #if COND / if X then Y else / #endif / Z;
2520
+ preproc_split_if_else_statement: $ => prec.right(seq(
2521
+ $.preproc_if,
2522
+ $.if_keyword,
2523
+ field('condition', $._expression),
2524
+ $.then_keyword,
2525
+ field('then_branch', choice(
2526
+ $.code_block,
2527
+ $._statement,
2528
+ )),
2529
+ $.else_keyword,
2530
+ choice(
2531
+ // Fragmented: else begin #endif stmts #if end; #endif
2532
+ // The if-then-begin-...-end-else-begin pattern where begin opens shared body
2533
+ $.preproc_fragmented_else_tail,
2534
+ // Normal: else followed by #elif/#else/#endif variants, then shared else body
2535
+ seq(
2536
+ repeat(seq($.preproc_elif,
2537
+ $.if_keyword,
2538
+ field('condition', $._expression),
2539
+ $.then_keyword,
2540
+ field('then_branch', choice(
2541
+ $.code_block,
2542
+ $._statement,
2543
+ )),
2544
+ $.else_keyword,
2545
+ )),
2546
+ optional(seq($.preproc_else,
2547
+ $.if_keyword,
2548
+ field('condition', $._expression),
2549
+ $.then_keyword,
2550
+ field('then_branch', choice(
2551
+ $.code_block,
2552
+ $._statement,
2553
+ )),
2554
+ $.else_keyword,
2555
+ )),
2556
+ $.preproc_endif,
2557
+ field('else_branch', choice(
2558
+ $.code_block,
2559
+ $._statement,
2560
+ )),
2561
+ ),
2562
+ ),
2563
+ )),
2564
+
2565
+ // Preprocessor split if-then-begin:
2566
+ // #if COND / if EXPR then begin / #endif / statements / #if COND / end; / #endif
2567
+ preproc_split_if_then_begin: $ => prec(25, seq(
2568
+ $.preproc_if,
2569
+ $.if_keyword,
2570
+ field('condition', $._expression),
2571
+ $.then_keyword,
2572
+ kw('begin'),
2573
+ $.preproc_endif,
2574
+ repeat($._statement),
2575
+ $.preproc_if,
2576
+ kw('end'),
2577
+ optional(';'),
2578
+ $.preproc_endif,
2579
+ )),
2580
+
2581
+ // Fragmented else tail: begin #endif stmts #if end; #endif
2582
+ // Used after else_keyword when the else branch's begin/end is split across preprocessor blocks
2583
+ preproc_fragmented_else_tail: $ => prec(25, seq(
2584
+ kw('begin'),
2585
+ $.preproc_endif,
2586
+ repeat($._statement),
2587
+ $.preproc_if,
2588
+ kw('end'),
2589
+ optional(';'),
2590
+ $.preproc_endif,
2591
+ )),
2592
+
2593
+ // Preprocessor conditionals in statements context
2594
+ preproc_conditional_statement: $ => prec.right(seq(
2595
+ $.preproc_if,
2596
+ repeat($._statement),
2597
+ repeat(seq(
2598
+ $.preproc_elif,
2599
+ repeat($._statement),
2600
+ )),
2601
+ optional(seq(
2602
+ $.preproc_else,
2603
+ repeat($._statement),
2604
+ )),
2605
+ $.preproc_endif,
2606
+ )),
2607
+
2608
+ // Preprocessor conditionals in actions context
2609
+ preproc_conditional_actions: $ => seq(
2610
+ $.preproc_if,
2611
+ repeat($._action_element),
2612
+ repeat(seq(
2613
+ $.preproc_elif,
2614
+ repeat($._action_element),
2615
+ )),
2616
+ optional(seq(
2617
+ $.preproc_else,
2618
+ repeat($._action_element),
2619
+ )),
2620
+ $.preproc_endif,
2621
+ ),
2622
+
2623
+ // Preprocessor conditionals in layout context
2624
+ preproc_conditional_layout: $ => seq(
2625
+ $.preproc_if,
2626
+ repeat($._layout_element),
2627
+ repeat(seq(
2628
+ $.preproc_elif,
2629
+ repeat($._layout_element),
2630
+ )),
2631
+ optional(seq(
2632
+ $.preproc_else,
2633
+ repeat($._layout_element),
2634
+ )),
2635
+ $.preproc_endif,
2636
+ ),
2637
+
2638
+ // Pragma and region directives (extras — can appear anywhere)
2639
+ pragma: $ => new RustRegex('#pragma[^\\n\\r]*'),
2640
+
2641
+ preproc_region: $ => new RustRegex('(?i)#\\s*region[^\\n\\r]*'),
2642
+
2643
+ preproc_endregion: $ => new RustRegex('(?i)#\\s*endregion[^\\n\\r]*'),
2644
+
2645
+ // =====================================================================
2646
+ // Statements
2647
+ // =====================================================================
2648
+
2649
+ _statement: $ => prec.right(seq(
2650
+ choice(
2651
+ $.assignment_statement,
2652
+ $.asserterror_statement,
2653
+ $.if_statement,
2654
+ $.exit_statement,
2655
+ $.continue_statement,
2656
+ $.break_statement,
2657
+ $.case_statement,
2658
+ $.for_statement,
2659
+ $.repeat_statement,
2660
+ $.while_statement,
2661
+ $.foreach_statement,
2662
+ $.with_statement,
2663
+ $._expression_statement,
2664
+ $.empty_statement,
2665
+ $.preproc_conditional_statement,
2666
+ $.preproc_split_if_statement,
2667
+ $.preproc_split_if_else_statement,
2668
+ $.preproc_split_if_then_begin,
2669
+ $.preproc_guarded_statement,
2670
+ ),
2671
+ optional(';')
2672
+ )),
2673
+
2674
+ _expression_statement: $ => $._expression,
2675
+
2676
+ empty_statement: $ => ';',
2677
+
2678
+ // --- Assignment ---
2679
+
2680
+ assignment_statement: $ => prec.dynamic(10, seq(
2681
+ field('left', $._expression),
2682
+ field('operator', $._assignment_operator),
2683
+ field('right', $._expression)
2684
+ )),
2685
+
2686
+ assignment_expression: $ => prec.dynamic(1, prec.right(seq(
2687
+ field('left', $._expression),
2688
+ field('operator', $._assignment_operator),
2689
+ field('right', $._expression)
2690
+ ))),
2691
+
2692
+ _assignment_operator: $ => token(choice(':=', '+=', '-=', '*=', '/=')),
2693
+
2694
+ // --- If/Then/Else ---
2695
+
2696
+ if_statement: $ => prec.right(seq(
2697
+ $.if_keyword,
2698
+ field('condition', $._expression),
2699
+ $.then_keyword,
2700
+ field('then_branch', choice(
2701
+ $.code_block,
2702
+ $._statement,
2703
+ )),
2704
+ optional(seq(
2705
+ $.else_keyword,
2706
+ field('else_branch', choice(
2707
+ $.code_block,
2708
+ prec(1, $.if_statement),
2709
+ $._statement,
2710
+ ))
2711
+ ))
2712
+ )),
2713
+
2714
+ // --- Case ---
2715
+
2716
+ case_statement: $ => prec(2, seq(
2717
+ $.case_keyword,
2718
+ field('expression', $._expression),
2719
+ $.of_keyword,
2720
+ repeat(choice(
2721
+ $.case_branch,
2722
+ $.preproc_conditional_case,
2723
+ )),
2724
+ optional($.case_else_branch),
2725
+ kw('end')
2726
+ )),
2727
+
2728
+ // Preprocessor conditionals inside case statements
2729
+ preproc_conditional_case: $ => seq(
2730
+ $.preproc_if,
2731
+ repeat($.case_branch),
2732
+ optional($.case_else_branch),
2733
+ repeat(seq($.preproc_elif, repeat($.case_branch), optional($.case_else_branch))),
2734
+ optional(seq($.preproc_else, repeat($.case_branch), optional($.case_else_branch))),
2735
+ $.preproc_endif,
2736
+ ),
2737
+
2738
+ case_branch: $ => choice(
2739
+ seq(
2740
+ field('pattern', $._case_pattern),
2741
+ ':',
2742
+ field('body', choice(
2743
+ $.code_block,
2744
+ $._statement,
2745
+ ))
2746
+ ),
2747
+ // Preprocessor-split case branch: #if wraps extra patterns before the main pattern
2748
+ // #if COND pattern1, #endif pattern2: body;
2749
+ $.preproc_split_case_branch,
2750
+ ),
2751
+
2752
+ // Case branch where some patterns are conditionally included via preprocessor
2753
+ preproc_split_case_branch: $ => prec(25, seq(
2754
+ $.preproc_if,
2755
+ repeat(seq($._single_pattern, optional(','))),
2756
+ repeat(seq(
2757
+ $.preproc_elif,
2758
+ repeat(seq($._single_pattern, optional(','))),
2759
+ )),
2760
+ optional(seq(
2761
+ $.preproc_else,
2762
+ repeat(seq($._single_pattern, optional(','))),
2763
+ )),
2764
+ $.preproc_endif,
2765
+ // Pattern(s) after the preprocessor block, ending with ':'
2766
+ field('pattern', $._case_pattern),
2767
+ ':',
2768
+ field('body', choice(
2769
+ $.code_block,
2770
+ $._statement,
2771
+ ))
2772
+ )),
2773
+
2774
+ // Case pattern list: supports preprocessor conditionals interleaved with patterns
2775
+ _case_pattern: $ => repeat1(choice(
2776
+ seq($._single_pattern, optional(',')),
2777
+ $.preproc_conditional_case_patterns,
2778
+ )),
2779
+
2780
+ // Preprocessor conditional wrapping case pattern entries mid-list
2781
+ preproc_conditional_case_patterns: $ => seq(
2782
+ $.preproc_if,
2783
+ repeat(seq($._single_pattern, optional(','))),
2784
+ repeat(seq(
2785
+ $.preproc_elif,
2786
+ repeat(seq($._single_pattern, optional(','))),
2787
+ )),
2788
+ optional(seq(
2789
+ $.preproc_else,
2790
+ repeat(seq($._single_pattern, optional(','))),
2791
+ )),
2792
+ $.preproc_endif,
2793
+ ),
2794
+
2795
+ _single_pattern: $ => choice(
2796
+ $._literal_value,
2797
+ $.qualified_enum_value,
2798
+ $.database_reference,
2799
+ $.range_expression,
2800
+ $.call_expression,
2801
+ $.identifier,
2802
+ $.quoted_identifier,
2803
+ $.member_expression,
2804
+ $.unary_expression,
2805
+ $.parenthesized_expression,
2806
+ $.keyword_identifier,
2807
+ // Expressions as case patterns (case true of: X > 0: ...)
2808
+ $.comparison_expression,
2809
+ $.logical_expression,
2810
+ $.additive_expression,
2811
+ $.multiplicative_expression,
2812
+ $.subscript_expression,
2813
+ // In expression as case pattern (case true of: X in [...]: ...)
2814
+ prec.left(5, seq(
2815
+ field('left', $._expression),
2816
+ field('operator', $.in_keyword),
2817
+ field('right', $.list_literal)
2818
+ )),
2819
+ ),
2820
+
2821
+ case_else_branch: $ => prec.left(seq(
2822
+ $.else_keyword,
2823
+ choice(
2824
+ $.code_block,
2825
+ repeat($._statement),
2826
+ )
2827
+ )),
2828
+
2829
+ // --- For loop ---
2830
+
2831
+ for_statement: $ => prec.right(seq(
2832
+ $.for_keyword,
2833
+ field('variable', choice(
2834
+ $.identifier,
2835
+ $.quoted_identifier,
2836
+ $.member_expression,
2837
+ )),
2838
+ ':=',
2839
+ field('start', $._expression),
2840
+ field('direction', choice(
2841
+ $.to_keyword,
2842
+ $.downto_keyword,
2843
+ )),
2844
+ field('end', $._expression),
2845
+ $.do_keyword,
2846
+ field('body', choice(
2847
+ $._statement,
2848
+ $.code_block,
2849
+ ))
2850
+ )),
2851
+
2852
+ // --- Foreach ---
2853
+
2854
+ foreach_statement: $ => prec.right(seq(
2855
+ $.foreach_keyword,
2856
+ field('variable', choice($.identifier, $.quoted_identifier)),
2857
+ $.in_keyword,
2858
+ field('iterable', $._expression),
2859
+ $.do_keyword,
2860
+ field('body', choice(
2861
+ $._statement,
2862
+ $.code_block,
2863
+ ))
2864
+ )),
2865
+
2866
+ // --- While ---
2867
+
2868
+ while_statement: $ => prec.right(seq(
2869
+ $.while_keyword,
2870
+ field('condition', $._expression),
2871
+ $.do_keyword,
2872
+ field('body', choice(
2873
+ $._statement,
2874
+ $.code_block,
2875
+ ))
2876
+ )),
2877
+
2878
+ // --- Repeat/Until ---
2879
+
2880
+ repeat_statement: $ => seq(
2881
+ $.repeat_keyword,
2882
+ repeat($._statement),
2883
+ $.until_keyword,
2884
+ field('condition', $._expression)
2885
+ ),
2886
+
2887
+ // --- With ---
2888
+
2889
+ with_statement: $ => prec.right(seq(
2890
+ $.with_keyword,
2891
+ field('record', $._expression),
2892
+ $.do_keyword,
2893
+ field('body', choice(
2894
+ $._statement,
2895
+ $.code_block,
2896
+ ))
2897
+ )),
2898
+
2899
+ // --- Exit ---
2900
+
2901
+ exit_statement: $ => prec(13, seq(
2902
+ $.exit_keyword,
2903
+ optional(seq(
2904
+ token.immediate('('),
2905
+ optional(field('return_value', $._expression)),
2906
+ ')'
2907
+ ))
2908
+ )),
2909
+
2910
+ // --- Continue / Break ---
2911
+
2912
+ continue_statement: $ => prec(13, $.continue_keyword),
2913
+ break_statement: $ => prec(13, $.break_keyword),
2914
+
2915
+ // --- Asserterror ---
2916
+
2917
+ asserterror_statement: $ => prec.right(14, seq(
2918
+ $.asserterror_keyword,
2919
+ optional(field('body', choice(
2920
+ $._expression,
2921
+ $.code_block,
2922
+ )))
2923
+ )),
2924
+
2925
+ // =====================================================================
2926
+ // Expressions
2927
+ // =====================================================================
2928
+
2929
+ _expression: $ => choice(
2930
+ // Binary operators
2931
+ $.range_expression,
2932
+ $.multiplicative_expression,
2933
+ $.additive_expression,
2934
+ $.comparison_expression,
2935
+ $.logical_expression,
2936
+ // In expression
2937
+ prec.left(5, seq(
2938
+ field('left', $._expression),
2939
+ field('operator', $.in_keyword),
2940
+ field('right', $.list_literal)
2941
+ )),
2942
+ // Is expression (type checking)
2943
+ prec.left(5, seq(
2944
+ field('left', $._expression),
2945
+ field('operator', kw('is', 5)),
2946
+ field('right', $.type_specification)
2947
+ )),
2948
+ // As expression (type casting)
2949
+ prec.left(5, seq(
2950
+ field('left', $._expression),
2951
+ field('operator', kw('as', 5)),
2952
+ field('right', $.type_specification)
2953
+ )),
2954
+ // Other expression forms
2955
+ $.qualified_enum_value,
2956
+ $.database_reference,
2957
+ $.call_expression,
2958
+ $.member_expression,
2959
+ $.subscript_expression,
2960
+ $.identifier,
2961
+ $.quoted_identifier,
2962
+ $._literal_value,
2963
+ $.parenthesized_expression,
2964
+ $.unary_expression,
2965
+ $.list_literal,
2966
+ // Keywords that can be used as identifiers in expressions
2967
+ $.keyword_identifier,
2968
+ // 'continue' as identifier when followed by ':='
2969
+ alias($.continue_as_identifier, $.identifier),
2970
+ // Ternary expression: condition ? then_value : else_value
2971
+ $.ternary_expression,
2972
+ // Assignment as expression (for asserterror and other contexts)
2973
+ prec.left(1, $.assignment_expression),
2974
+ ),
2975
+
2976
+ // Keywords that can appear as identifiers in expressions (e.g., Codeunit.Run())
2977
+ keyword_identifier: $ => prec(-5, choice(
2978
+ $.codeunit_keyword,
2979
+ $.page_keyword,
2980
+ $.report_keyword,
2981
+ $.query_keyword,
2982
+ $.xmlport_keyword,
2983
+ kw('record'),
2984
+ $.enum_keyword,
2985
+ kw('system'),
2986
+ kw('session'),
2987
+ kw('dialog'),
2988
+ kw('database'),
2989
+ kw('file'),
2990
+ kw('action'),
2991
+ )),
2992
+
2993
+ // --- Binary expressions ---
2994
+
2995
+ range_expression: $ => prec.left(8, seq(
2996
+ field('left', $._expression),
2997
+ '..',
2998
+ field('right', $._expression)
2999
+ )),
3000
+
3001
+ multiplicative_expression: $ => prec.left(7, seq(
3002
+ field('left', $._expression),
3003
+ field('operator', choice('*', '/', choice('div', 'DIV', 'Div'), choice('mod', 'MOD', 'Mod'))),
3004
+ field('right', $._expression)
3005
+ )),
3006
+
3007
+ additive_expression: $ => prec.left(6, seq(
3008
+ field('left', $._expression),
3009
+ field('operator', choice('+', '-')),
3010
+ field('right', $._expression)
3011
+ )),
3012
+
3013
+ comparison_expression: $ => prec.left(4, seq(
3014
+ field('left', $._expression),
3015
+ field('operator', $.comparison_operator),
3016
+ field('right', $._expression)
3017
+ )),
3018
+
3019
+ comparison_operator: $ => choice(
3020
+ '>=',
3021
+ '<=',
3022
+ '>',
3023
+ '<',
3024
+ '<>',
3025
+ '='
3026
+ ),
3027
+
3028
+ logical_expression: $ => choice(
3029
+ // AND (prec 3)
3030
+ prec.left(3, seq(
3031
+ field('left', $._expression),
3032
+ field('operator', choice('and', 'AND', 'And')),
3033
+ field('right', $._expression)
3034
+ )),
3035
+ // OR (prec 2)
3036
+ prec.left(2, seq(
3037
+ field('left', $._expression),
3038
+ field('operator', choice('or', 'OR', 'Or')),
3039
+ field('right', $._expression)
3040
+ )),
3041
+ // XOR (prec 2)
3042
+ prec.left(2, seq(
3043
+ field('left', $._expression),
3044
+ field('operator', choice('xor', 'XOR', 'Xor')),
3045
+ field('right', $._expression)
3046
+ )),
3047
+ ),
3048
+
3049
+ // --- Ternary expression ---
3050
+ // condition ? then_value : else_value
3051
+ ternary_expression: $ => prec.right(1, seq(
3052
+ field('condition', $._expression),
3053
+ '?',
3054
+ field('then_value', $._expression),
3055
+ ':',
3056
+ field('else_value', $._expression)
3057
+ )),
3058
+
3059
+ // --- Unary expression ---
3060
+
3061
+ unary_expression: $ => prec.right(7, seq(
3062
+ field('operator', choice('+', '-', choice('not', 'NOT', 'Not'))),
3063
+ field('operand', $._expression)
3064
+ )),
3065
+
3066
+ // --- Postfix expressions ---
3067
+
3068
+ call_expression: $ => prec(12, seq(
3069
+ field('function', choice(
3070
+ $.identifier,
3071
+ $.member_expression,
3072
+ $.qualified_enum_value,
3073
+ $.keyword_identifier, // System(), Dialog(), etc.
3074
+ $.subscript_expression, // X[1]()
3075
+ )),
3076
+ field('arguments', $.argument_list)
3077
+ )),
3078
+
3079
+ argument_list: $ => seq(
3080
+ '(',
3081
+ optional(seq(
3082
+ $._expression,
3083
+ repeat(seq(',', $._expression))
3084
+ )),
3085
+ ')'
3086
+ ),
3087
+
3088
+ member_expression: $ => prec.left(11, seq(
3089
+ field('object', $._expression),
3090
+ '.',
3091
+ field('member', $._identifier_or_quoted)
3092
+ )),
3093
+
3094
+ subscript_expression: $ => prec.left(9, seq(
3095
+ field('object', $._expression),
3096
+ '[',
3097
+ field('index', $._expression),
3098
+ repeat(seq(',', $._expression)),
3099
+ ']'
3100
+ )),
3101
+
3102
+ parenthesized_expression: $ => seq(
3103
+ '(',
3104
+ $._expression,
3105
+ ')'
3106
+ ),
3107
+
3108
+ // --- List literal ---
3109
+
3110
+ list_literal: $ => seq(
3111
+ '[',
3112
+ optional(seq(
3113
+ $._expression,
3114
+ repeat(seq(',', $._expression))
3115
+ )),
3116
+ ']'
3117
+ ),
3118
+
3119
+ // --- Qualified enum value ---
3120
+ // Status::Active, Enum::"Sales Line Type"::Item
3121
+
3122
+ qualified_enum_value: $ => prec.left(50, seq(
3123
+ field('enum_type', choice(
3124
+ $.identifier,
3125
+ $.quoted_identifier,
3126
+ $.member_expression,
3127
+ $.subscript_expression, // Allow X[1]::Value
3128
+ $.call_expression, // Allow Func()::Value
3129
+ $.qualified_enum_value, // Chained: Enum::"Type"::"Value"
3130
+ $.keyword_identifier, // Allow Enum::, Record::, etc.
3131
+ )),
3132
+ '::',
3133
+ field('value', $._identifier_or_quoted)
3134
+ )),
3135
+
3136
+ // --- Database reference ---
3137
+ // DATABASE::"Customer"
3138
+
3139
+ database_reference: $ => prec(300, seq(
3140
+ field('keyword', alias(
3141
+ choice(
3142
+ kw('database'),
3143
+ $.page_keyword,
3144
+ $.report_keyword,
3145
+ $.codeunit_keyword,
3146
+ $.xmlport_keyword,
3147
+ $.query_keyword,
3148
+ ),
3149
+ $.object_type_keyword
3150
+ )),
3151
+ '::',
3152
+ field('table_name', $._identifier_or_quoted)
3153
+ )),
3154
+
3155
+ // --- Literal values ---
3156
+
3157
+ _literal_value: $ => choice(
3158
+ $.integer,
3159
+ $.decimal,
3160
+ $.boolean,
3161
+ $.string_literal,
3162
+ $.datetime_literal,
3163
+ $.date_literal,
3164
+ $.time_literal,
3165
+ $.biginteger_literal,
3166
+ ),
3167
+
3168
+ // DateTime: 0DT or YYYYMMDDTHHmmssZ
3169
+ datetime_literal: $ => token(prec(2, choice(
3170
+ /\d+DT/,
3171
+ new RustRegex('\\d{8}T\\d{6}[A-Z]?'),
3172
+ ))),
3173
+
3174
+ // Date: 0D or YYYYMMDD
3175
+ date_literal: $ => token(prec(1, /\d+D/)),
3176
+
3177
+ // Time: 0T or HHmmssT or HHmmss.mmmT
3178
+ time_literal: $ => token(prec(2, choice(
3179
+ /\d+\.\d+T/, // Decimal time: 235959.999T
3180
+ /\d+T/, // Integer time: 000000T, 0T
3181
+ ))),
3182
+
3183
+ // BigInteger: 1000L
3184
+ biginteger_literal: $ => token(prec(1, /\d+L/)),
3185
+
3186
+ // =====================================================================
3187
+ // Named keyword rules
3188
+ // =====================================================================
3189
+
3190
+ // --- Tier 1: Control flow ---
3191
+
3192
+ if_keyword: $ => prec(10, choice('if', 'IF', 'If')),
3193
+ then_keyword: $ => prec(10, choice('then', 'THEN', 'Then')),
3194
+ else_keyword: $ => prec(10, choice('else', 'ELSE', 'Else')),
3195
+ case_keyword: $ => prec(10, choice('case', 'CASE', 'Case')),
3196
+ of_keyword: $ => prec(10, choice('of', 'OF', 'Of')),
3197
+ for_keyword: $ => prec(10, choice('for', 'FOR', 'For')),
3198
+ foreach_keyword: $ => prec(10, choice('foreach', 'FOREACH', 'Foreach')),
3199
+ while_keyword: $ => prec(10, choice('while', 'WHILE', 'While')),
3200
+ do_keyword: $ => prec(10, choice('do', 'DO', 'Do')),
3201
+ repeat_keyword: $ => prec(10, choice('repeat', 'REPEAT', 'Repeat')),
3202
+ until_keyword: $ => prec(10, choice('until', 'UNTIL', 'Until')),
3203
+ exit_keyword: $ => prec(10, choice('exit', 'EXIT', 'Exit')),
3204
+ continue_keyword: $ => prec(10, choice('continue', 'CONTINUE', 'Continue')),
3205
+ break_keyword: $ => prec(10, choice('break', 'BREAK', 'Break')),
3206
+ with_keyword: $ => prec(10, choice('with', 'WITH', 'With')),
3207
+ asserterror_keyword: $ => kw('asserterror', 10),
3208
+ in_keyword: $ => prec(10, choice('in', 'IN', 'In')),
3209
+ to_keyword: $ => prec(10, choice('to', 'TO', 'To')),
3210
+ downto_keyword: $ => prec(10, choice('downto', 'DOWNTO', 'Downto')),
3211
+
3212
+ // --- Tier 2: Object types ---
3213
+
3214
+ table_keyword: $ => kw('table'),
3215
+ tableextension_keyword: $ => prec(10, choice('tableextension', 'TABLEEXTENSION', 'Tableextension', 'TableExtension', 'tableExtension')),
3216
+ page_keyword: $ => kw('page'),
3217
+ pageextension_keyword: $ => prec(10, choice('pageextension', 'PAGEEXTENSION', 'Pageextension', 'PageExtension', 'pageExtension')),
3218
+ codeunit_keyword: $ => prec(10, choice('codeunit', 'CODEUNIT', 'Codeunit', 'CodeUnit', 'COdeunit', 'codeUnit')),
3219
+ report_keyword: $ => kw('report'),
3220
+ reportextension_keyword: $ => prec(10, choice('reportextension', 'REPORTEXTENSION', 'Reportextension', 'ReportExtension', 'reportExtension')),
3221
+ query_keyword: $ => kw('query'),
3222
+ xmlport_keyword: $ => prec(10, choice('xmlport', 'XMLPORT', 'Xmlport', 'XMLport', 'XMLPort', 'XmlPort')),
3223
+ enum_keyword: $ => prec(10, choice('enum', 'ENUM', 'Enum', 'eNUM', 'eNum', 'ENum')),
3224
+ enumextension_keyword: $ => prec(10, choice('enumextension', 'ENUMEXTENSION', 'Enumextension', 'EnumExtension', 'enumExtension')),
3225
+ interface_keyword: $ => kw('interface'),
3226
+ controladdin_keyword: $ => prec(10, choice('controladdin', 'CONTROLADDIN', 'Controladdin', 'ControlAddIn', 'ControlAddin', 'controlAddIn', 'controlAddin')),
3227
+ dotnet_keyword: $ => prec(10, choice('dotnet', 'DOTNET', 'Dotnet', 'DotNet', 'dotNet')),
3228
+ profile_keyword: $ => kw('profile'),
3229
+ profileextension_keyword: $ => prec(10, choice('profileextension', 'PROFILEEXTENSION', 'Profileextension', 'ProfileExtension', 'profileExtension')),
3230
+ permissionset_keyword: $ => prec(10, choice('permissionset', 'PERMISSIONSET', 'Permissionset', 'PermissionSet', 'permissionSet')),
3231
+ permissionsetextension_keyword: $ => prec(10, choice('permissionsetextension', 'PERMISSIONSETEXTENSION', 'Permissionsetextension', 'PermissionSetExtension', 'permissionSetExtension')),
3232
+ entitlement_keyword: $ => kw('entitlement'),
3233
+ pagecustomization_keyword: $ => prec(10, choice('pagecustomization', 'PAGECUSTOMIZATION', 'Pagecustomization', 'PageCustomization', 'pageCustomization')),
3234
+ namespace_keyword: $ => kw('namespace'),
3235
+ using_keyword: $ => kw('using'),
3236
+ implements_keyword: $ => kw('implements'),
3237
+ extends_keyword: $ => kw('extends'),
3238
+ customizes_keyword: $ => kw('customizes'),
3239
+
3240
+ // --- Tier 3: Declarations & modifiers ---
3241
+
3242
+ procedure_keyword: $ => kw('procedure'),
3243
+ trigger_keyword: $ => kw('trigger'),
3244
+ var_keyword: $ => kw('var'),
3245
+ local_keyword: $ => kw('local'),
3246
+ internal_keyword: $ => kw('internal'),
3247
+ protected_keyword: $ => kw('protected'),
3248
+ event_keyword: $ => kw('event'),
3249
+ temporary_keyword: $ => kw('temporary'),
3250
+
3251
+ // --- Tier 3: Sections ---
3252
+
3253
+ fields_keyword: $ => kw('fields'),
3254
+ keys_keyword: $ => kw('keys'),
3255
+ key_keyword: $ => kw('key'),
3256
+ fieldgroups_keyword: $ => kw('fieldgroups'),
3257
+ fieldgroup_keyword: $ => kw('fieldgroup'),
3258
+ actions_keyword: $ => kw('actions'),
3259
+ layout_keyword: $ => kw('layout'),
3260
+ area_keyword: $ => kw('area'),
3261
+ group_keyword: $ => kw('group'),
3262
+ repeater_keyword: $ => kw('repeater'),
3263
+ cuegroup_keyword: $ => kw('cuegroup'),
3264
+ fixed_keyword: $ => kw('fixed'),
3265
+ grid_keyword: $ => kw('grid'),
3266
+ part_keyword: $ => kw('part'),
3267
+ systempart_keyword: $ => kw('systempart'),
3268
+ usercontrol_keyword: $ => kw('usercontrol'),
3269
+ chartpart_keyword: $ => kw('chartpart'),
3270
+ dataset_keyword: $ => kw('dataset'),
3271
+ elements_keyword: $ => kw('elements'),
3272
+ dataitem_keyword: $ => kw('dataitem'),
3273
+ column_keyword: $ => kw('column'),
3274
+ filter_keyword: $ => kw('filter'),
3275
+ labels_keyword: $ => kw('labels'),
3276
+ rendering_keyword: $ => kw('rendering'),
3277
+ requestpage_keyword: $ => kw('requestpage'),
3278
+ schema_keyword: $ => kw('schema'),
3279
+ views_keyword: $ => kw('views'),
3280
+ view_keyword: $ => kw('view'),
3281
+
3282
+ // =====================================================================
3283
+ // Shared rules
3284
+ // =====================================================================
3285
+
3286
+ _identifier_or_quoted: $ => choice(
3287
+ $.identifier,
3288
+ $.quoted_identifier,
3289
+ // Contextual keywords that can also be used as identifiers
3290
+ alias($.keyword_as_identifier, $.identifier),
3291
+ ),
3292
+
3293
+ // Keywords that need to be usable as identifiers (variable names, parameter names, etc.)
3294
+ keyword_as_identifier: $ => prec(-10, choice(
3295
+ kw('field'),
3296
+ $.key_keyword,
3297
+ kw('value'),
3298
+ kw('separator'),
3299
+ $.dataset_keyword,
3300
+ kw('type'),
3301
+ kw('version'),
3302
+ kw('action'),
3303
+ )),
3304
+
3305
+ // Identifiers — Unicode-aware
3306
+ identifier: $ => token(/[\p{L}_][\p{L}\p{N}_]*/u),
3307
+
3308
+ quoted_identifier: $ => token(prec(10, seq(
3309
+ '"',
3310
+ repeat(choice(
3311
+ new RustRegex('[^"\\n]+'),
3312
+ '""' // Escaped double quote
3313
+ )),
3314
+ '"'
3315
+ ))),
3316
+
3317
+ // Comments (including XML doc comments /// ...)
3318
+ comment: $ => token(seq('//', /[^\n]*/)),
3319
+ multiline_comment: $ => token(seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/')),
3320
+
3321
+ // Literals
3322
+ string_literal: $ => token(
3323
+ choice(
3324
+ seq("'", "'"),
3325
+ seq(
3326
+ "'",
3327
+ repeat1(choice(
3328
+ new RustRegex("[^'\\n]+"),
3329
+ "''"
3330
+ )),
3331
+ "'"
3332
+ )
3333
+ )
3334
+ ),
3335
+
3336
+ integer: $ => token(/\d+/),
3337
+ },
3338
+ });