pred-recdec 0.2.1

Predicated Recursive Descent Parsing with BNF and impure hooks
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
# C99 grammar with a few extensions needed to make more syntax tests designed for gcc/clang extensions work (e.g. they always support some c11/c23-isms because they originated as GNU extensions)

__COMMENTS ::= "//" | "#"
__COMMENT_PAIRS ::= /* */
__BRACKET_PAIRS ::= { } | ( ) | [ ]
# only used by the peekres command (not peekr, not regex terminals)
__RESERVED_WORDS ::=
    auto break case char const continue default do double else enum extern float for goto
    if inline __inline__ __inline int long
    register restrict __cdecl __stdcall __restrict__ return short signed sizeof static struct switch typedef union unsigned void volatile
    while _Bool _Complex _Imaginary _Float16 __bf16 __int128

S ::= translation_unit

declaration_specifiers ::=
    declaration_specifier $become declaration_specifiers_star
declaration_specifiers_star ::=
    @peekr(1, r`[\[\),;:=]`r) #empty
    | @peekr(0, r`__attribute__|__attribute`r)  declaration_specifiers_star_guarded
    | @peekr(1, r`\(`r) $become declaration_specifiers_star_guard2
    | $become declaration_specifiers_star_guarded
declaration_specifiers_star_guard2 ::=
    @peekr(2, r`\*|__attribute__|__attribute`r) $become declaration_specifiers_star_guarded
    | #empty
declaration_specifiers_star_guarded ::=
    @guard(is_declaration_indicator) declaration_specifier $become declaration_specifiers_star
    | #empty
declaration_specifier ::=
    @peekr(0, r`typedef|extern|static|auto|register`r) storage_class_specifier $become attribute_list
    | @peekr(0, r`const|restrict|__cdecl|__stdcall|__restrict__|volatile`r) type_qualifier $become attribute_list
    | @peekr(0, r`inline|__inline__|__inline`r) function_specifier $become attribute_list
    | type_specifier $become attribute_list

# gcc extension
attribute_list ::=
    @peekr(0, r`__attribute__|__attribute`r) attribute $become attribute_list
    | #empty

storage_class_specifier ::=
    @auto R`typedef|extern|static|auto|register`r
type_specifier ::=
    @auto R`void|char|short|int|__builtin_va_list|long|float|double|signed|unsigned|_Bool|_Complex|_Imaginary`r
    | @peek(0, "enum") enum_specifier
    | @peekr(0, r`struct|union`r) struct_or_union_specifier
    | @peekr(0, r`typeof|__typeof__`r) typeof_specifier
    | typedef_name
type_qualifier ::=
    @auto R`const|restrict|__cdecl|__stdcall|__restrict__|volatile`r

typeof_specifier ::=
    R`typeof|__typeof__`r "(" many_balanced ")"
struct_or_union_specifier ::=
    struct_or_union $become struct_or_union_specifier_next
struct_or_union_specifier_next ::=
    @peekr(0, r`__attribute__|__attribute`r) attribute_list $become struct_or_union_specifier_info
    | $become struct_or_union_specifier_info
struct_or_union_specifier_info ::=
    @auto "{" !hook(scope_push) struct_declaration_list !hook(scope_pop) "}"
    | identifier attribute_list $become struct_or_union_specifier_info2
struct_or_union_specifier_info2 ::=
    @auto "{" !hook(scope_push) struct_declaration_list !hook(scope_pop) "}" | #empty

struct_or_union ::=
    @auto "struct" | "union"
struct_declaration_list ::=
    @peek(0, "}") #empty
    | struct_declaration $become struct_declaration_star
struct_declaration_star ::=
    @peek(0, "}") #empty (FOLLOW set logic)
    | struct_declaration $become struct_declaration_star
struct_declaration ::=
    specifier_qualifier_list struct_declarator_list ";"

specifier_qualifier_list ::=
    specifier_qualifier specifier_qualifier_star
specifier_qualifier ::=
    @peekr(0, r`const|restrict|__cdecl|__stdcall|__restrict__|volatile`r) type_qualifier
    | type_specifier
specifier_qualifier_star ::=
    @peekr(1, r`[\[;:=]`r) #empty
    | @peekr(0, r`const|restrict|__cdecl|__stdcall|__restrict__|volatile`r) type_qualifier $become specifier_qualifier_star
    | @guard(is_type_specifier_start) type_specifier $become specifier_qualifier_star
    | #empty
struct_declarator_list ::=
    struct_declarator $become struct_declarator_list_tail
struct_declarator_list_tail ::=
    @auto "," struct_declarator $become struct_declarator_list_tail
    | #empty

struct_declarator ::=
    @peek(0, ";") #empty
    | @auto ":" constant_expression
    | declarator $become struct_declarator_declarator_tail
struct_declarator_declarator_tail ::=
    @auto ":" constant_expression
    | #empty

enum_specifier ::=
    "enum" attribute_list $become enum_specifier_contents
enum_specifier_contents ::=
    @peek(0, "{") $become enum_specifier_info
    | identifier $become enum_specifier_info_maybe
enum_specifier_info_maybe ::=
    @peek(0, "{") $become enum_specifier_info
    | #empty
enum_specifier_info ::=
    "{" enumerator_list "}" !hook(enums_log)

enumerator_list ::=
    enumerator $become enumerator_list_tail
enumerator_list_tail ::=
    @auto "," $become enumerator_list_tail2
    | #empty
enumerator_list_tail2 ::=
    @peek(0, "}") #empty # FOLLOW set logic
    | enumerator $become enumerator_list_tail
enumerator ::=
    enumeration_constant attribute_list $become enumerator_value
enumerator_value ::=
    @auto "=" constant_expression
    | #empty
function_specifier ::=
    @auto "inline" | @auto "__inline__" | "__inline"

identifier_list ::=
    identifier $become identifier_list_tail
identifier_list_tail ::=
    @auto "," identifier $become identifier_list_tail
    | #empty

pointer ::=
    pointer_info $become pointer_star
pointer_info ::=
    "*" type_qualifier_star
pointer_star ::=
    @peek(0, "*") pointer_info $become pointer_star
    | #empty

type_qualifier_list ::=
    type_qualifier type_qualifier_star
type_qualifier_star ::=
    @peekr(0, r`const|restrict|__cdecl|__stdcall|__restrict__|volatile`r) type_qualifier $become type_qualifier_star
    | #empty

parameter_type_list ::=
    parameter_declaration $become parameter_list_tail
parameter_list_tail ::=
    @auto "," $become parameter_list_tail2
    | @peek(0, ")") #empty # FOLOW set logic
parameter_list_tail2 ::=
    @auto "..."
    | parameter_declaration $become parameter_list_tail
parameter_declaration ::=
    attribute_list declaration_specifiers attribute_list parameter_declarator !hook(typedefs_log)
parameter_declarator ::=
    @peek(0, "*") pointer $become parameter_declarator2
    | @peekr(0, r`\(|\[`r) NA_direct_abstract_declarator
    | @peekr(0, r`[^,)]*`r) declarator
    | #empty (no variable name)
parameter_declarator2 ::=
    @peekr(0, r`\(|\[`r) NA_direct_abstract_declarator
    | @peekr(0, r`[^,)]*`r) direct_declarator attribute_list
    | #empty (no variable name)

type_name ::=
    specifier_qualifier_list abstract_declarator_maybe
abstract_declarator_maybe ::=
    @peekr(0, r`\(|\[|\*`r) abstract_declarator
    | #empty

declarator ::=
    @peek(0, "*") pointer attribute_list direct_declarator maybe_asm_statement attribute_list
    | direct_declarator maybe_asm_statement attribute_list
direct_declarator ::=
    @auto "(" attribute_list declarator ")" $become direct_declarator_corelist
    | attribute_list identifier direct_declarator_corelist

direct_declarator_core ::=
    @auto "[" any_direct_declarator_core_sq
    | "(" direct_declarator_core_p
direct_declarator_core_p ::=
    @auto ")"
    | @auto "..." ")"
    | @peekr(0, r`__attribute__|__attribute`r) !hook(scope_push) parameter_type_list !hook(scope_pop) ")"
    | @guard(is_declaration_indicator) !hook(scope_push) parameter_type_list !hook(scope_pop) ")"
    | !hook(scope_push) identifier_list !hook(scope_pop) ")" # Blame this rule specifically for concrete and abstract declarators not being merged.
direct_declarator_corelist ::=
    @peekr(0, r`\[|\(`r) direct_declarator_core $become direct_declarator_corelist
    | #empty

direct_abstract_declarator_core ::=
    @auto "[" any_direct_declarator_core_sq
    | "(" direct_abstract_declarator_core_p
direct_abstract_declarator_core_p ::=
    @auto ")"
    | @auto "..." ")"
    | !hook(scope_push) parameter_type_list !hook(scope_pop) ")"
    # Note the lack of identifier_list rule.
direct_abstract_declarator_corelist ::=
    @peekr(0, r`\[|\(`r) direct_abstract_declarator_core direct_abstract_declarator_corelist
    | #empty


abstract_declarator ::=
    @peek(0, "*") pointer $become direct_abstract_declarator_maybe
    | direct_abstract_declarator
direct_abstract_declarator_maybe ::=
    @peekr(0, r`\(|\[`r) direct_abstract_declarator
    | #empty
direct_abstract_declarator ::=
    attribute_list abstract_declarator_wrapped_and_maybe direct_abstract_declarator_corelist
abstract_declarator_wrapped_and_maybe ::=
    @peek(0, "(") $become abstract_declarator_wrapped_and_maybe2
    | #empty
abstract_declarator_wrapped_and_maybe2 ::=
    @peekr(1, r`\*|\(|\[|__attribute__|__attribute`r) "(" attribute_list abstract_declarator ")"
    | #empty

# "name allowed" variants
NA_abstract_declarator ::=
    @peek(0, "*") pointer identifier_maybe NA_direct_abstract_declarator_maybe
    | identifier_maybe NA_direct_abstract_declarator
NA_direct_abstract_declarator_maybe ::=
    @peekr(0, r`\(|\[`r) NA_direct_abstract_declarator
    | #empty
NA_direct_abstract_declarator ::=
    attribute_list NA_abstract_declarator_wrapped_and_maybe direct_abstract_declarator_corelist
    # non-empty identifier_list terms are forbidden outside of function definitions
    # AND: function definitions cannot be abstract
    # THEREFORE: it's safe to use direct_abstract_declarator_corelist instead of direct_declarator_corelist
NA_abstract_declarator_wrapped_and_maybe ::=
    @peek(0, "(") $become NA_abstract_declarator_wrapped_and_maybe2
    | #empty
NA_abstract_declarator_wrapped_and_maybe2 ::=
    @peekr(1, r`\*|\(|\[|__attribute__|__attribute`r) "(" attribute_list NA_abstract_declarator ")"
    | #empty


# agnostic to what kind of declarator we're in
any_direct_declarator_core_sq ::=
    @auto "static" type_qualifier_star assignment_expression "]"
    | @peekr(0, r`const|restrict|__cdecl|__stdcall|__restrict__|volatile`r) type_qualifier_list $become any_direct_declarator_chunk_square3
    | @auto "]"
    | @peek(0, "*") $become any_declarator_chunk_square_ptrpart
    | assignment_expression "]"
any_direct_declarator_chunk_square3 ::=
    @auto "static" assignment_expression "]"
    | @auto "]"
    | @peek(0, "*") $become any_declarator_chunk_square_ptrpart
    | assignment_expression "]"
any_declarator_chunk_square_ptrpart ::=
    @peek(1, "]") "*" "]"
    | assignment_expression "]"



typedef_name ::=
    identifier
initializer ::=
    @auto "{" initializer_list $become initializer_comma_chomp
    | assignment_expression
initializer_comma_chomp ::=
    @auto "," "}"
    | "}"
designated_initializer ::=
    @peekr(0, r`[\[.]`r) designation initializer
    | initializer
initializer_list ::=
    @peek(0, "}") #empty # C23 extension: empty initializer lists
    | designated_initializer $become initializer_list_tail
initializer_list_tail ::=
    @auto "," $become initializer_list_tail_guarded
    | #empty
initializer_list_tail_guarded ::=
    @peek(0, "}") #empty
    | designated_initializer $become initializer_list_tail
designation ::=
    designator_list "="
designator_list ::=
    designator $become designator_star
designator_star ::=
    @peekr(0, r`[\[.]`r) designator $become designator_star
    | #empty

designator ::=
    @auto "[" constant_expression "]"
    | "." identifier

primary_expression ::=
    @peekr(0, r`[L]?\x22.*\x22`r) string_literal
    | @auto "_Generic" "(" many_balanced ")"
    | @guard(is_ident_not_known_enum) $become_as identifier
    | $become constant
postfix_expression_atom ::=
    @auto "(" $become postfix_expression_atom_choice
    | $become_as primary_expression
postfix_expression_atom_choice ::=
    @guard(is_type_specifier_start) $become_as compound_lit_expression
    | $become_as primary_expression_2

primary_expression_2 ::= expression ")"
compound_lit_expression ::= type_name ")" "{" initializer_list initializer_comma_chomp

postfix_expression ::= 
    @peekr(0, r`__builtin_.*`r) $become postfix_expression_builtin_guard
    | postfix_expression_atom $become postfix_trailer_star
postfix_expression_builtin_guard ::=
    @peek(1, "(") ext_builtin
    | postfix_expression_atom $become postfix_trailer_star
ext_builtin ::= # builtins can take non-expressions as arguments. bypass argument parsing.
    R`__builtin_[a-zA-Z_0-9]*`r "(" many_balanced ")" $become postfix_trailer_star

postfix_trailer_star ::=
    @peekr(0, r`\[|\(|\.|->|\+\+|--`r) postfix_trailer $become postfix_trailer_star
    | #empty
postfix_trailer ::=
    @auto "[" expression "]"
    | @auto "(" $become postfix_trailer_paren
    | @auto "." identifier
    | @auto "->" identifier
    | @auto "++"
    | "--"
postfix_trailer_paren ::=
    @auto ")"
    | $become_as argument_expression_list
argument_expression_list ::=
    assignment_expression $become argument_expression_list_tail
argument_expression_list_tail ::=
    @auto "," assignment_expression $become argument_expression_list_tail
    | ")" #empty
unary_expression ::=
    @auto "++" $become unary_expression
    | @auto "--" $become unary_expression
    | @auto "sizeof" $become sizeof_argument
    | @auto "_Alignof" $become sizeof_argument
    | @auto R`[&*\+\-~!]`r cast_expression
    | $become_as postfix_expression

sizeof_argument ::=
    @peek(0, "(") $become sizeof_argument_guarded
    | $become_as unary_expression
sizeof_argument_guarded ::=
    @guard(is_postfix_not_sizeof_argument) assignment_expression
    | "(" $become sizeof_argument_trail

sizeof_argument_trail ::=
    @guard(is_type_specifier_start) type_name ")"
    | assignment_expression ")"

cast_expression ::=
    @guard(is_primitive_cast) "(" type_name ")" $become cast_expression
    | $become_as unary_expression

infix_expression ::=
    cast_expression $become infix_expression_tail
infix_expression_tail ::=
    @auto R`(?x)(?:\
        \|\||&&|==|>=|<=|!=|>>|<<|\
        \*|\+|\^|\||&|-|/|%|>|<\
    )`r cast_expression $become infix_expression_tail
    | !hook(fix_infix_precedence) #empty

conditional_expression ::=
    infix_expression $become conditional_expression_tail
conditional_expression_tail ::=
    @auto "?" expression ":" conditional_expression | #empty

# The C standard tried encoding semantics in syntax.
# It doesn't even work. Examples: ++x = 5; and (x + 3) = 5;
#assignment_expression ::=
#    conditional_expression
#    unary_expression r`(?:\*|/|%|\+|-|<<|>>|&|\^|\|)?=`r assignment_expression
# Slightly more permissive rule that is less incredibly awful to parse:
assignment_expression ::=
    @auto "__extension__" $become assignment_expression
    | conditional_expression $become assignment_expression_tail
assignment_expression_tail ::=
    @auto R`(?:\*|/|%|\+|-|<<|>>|&|\^|\|)?=`r conditional_expression $become assignment_expression_tail
    | #empty
# I suspect this is roughly what real C compilers do and that they enforce the semantics rule afterwards.
# For example in GCC this is a semantics error instead of a syntax error, saying that x+5 isn't a valid lvalue. Clang fails similarly:
# int myfunc() {
#     int* x;
#     5+x = 3;
# }
# ... and using x+5 instead of 5+x gives the same error. And using ++0 (yes, really) instead of 5+x gives the same error.

expression ::=
    @auto "__extension__" $become expression
    | @peekr(1, r`\]`r) primary_expression $become expression_tail
    | assignment_expression $become expression_tail
expression_tail ::=
    @auto "," assignment_expression $become expression_tail
    | #empty

constant_expression ::=
    $become conditional_expression

maybe_asm_statement ::=
    @peekr(0, r`__asm|__asm__|asm`r) asm_statement
    | #empty

statement ::=
    @peekr(0, r`__attribute__|__attribute`r) attribute_list $become core_statement
    | $become core_statement

core_statement ::=
    @peekr(0, r`__asm|__asm__|asm`r) asm_statement ";"
    | @guard(is_label) $become_as labeled_statement
    | @peekr(0, r`switch|if`r) $become_as selection_statement
    | @peekr(0, r`while|do|for`r) $become_as iteration_statement
    | @peekr(0, r`goto|continue|break|return`r) $become_as jump_statement
    | @peek(0, "{") $become_as scoped_compound_statement
    | $become_as expression_statement

# gcc extension
asm_statement ::=
    R`__asm|__asm__|asm`r asm_qualifiers "(" asm_body ")"
asm_qualifiers ::=
    @auto "volatile" $become asm_qualifiers
    | @auto "__volatile__" $become asm_qualifiers
    | @auto "inline" $become asm_qualifiers
    | @auto "__inline__" $become asm_qualifiers
    | @auto "goto" $become asm_qualifiers
    | #empty
asm_body ::=
    string_literal asm_body_2
asm_body_2 ::=
    @auto ":" asm_var_regs asm_body_3
    | #empty
asm_body_3 ::=
    @auto ":" asm_var_regs asm_body_4
    | #empty
asm_body_4 ::=
    @auto ":" asm_clobbers asm_body_5
    | #empty
asm_body_5 ::=
    @auto ":" asm_gotos
    | #empty

asm_var_regs ::=
    @auto "[" identifier "]" string_literal "(" expression ")" $become asm_var_regs_tail
    | @peekr(0, r`[L]?\x22.*\x22`r) string_literal "(" expression ")" $become asm_var_regs_tail
    | #empty
asm_var_regs_tail ::=
    @auto "," $become asm_var_regs
    | #empty

asm_clobbers ::=
    @peek(0, "[") "[" identifier "]" string_literal $become asm_var_regs_tail
    | @peekr(0, r`[L]?\x22.*\x22`r) string_literal $become asm_var_regs_tail
    | #empty
asm_clobbers_tail ::=
    @auto "," $become asm_clobbers
    | #empty

asm_gotos ::=
    @peekr(0, r`[a-zA-Z_].*`r) identifier $become asm_var_regs_tail
    | #empty
asm_gotos_tail ::=
    @auto "," $become asm_gotos
    | #empty



labeled_statement ::=
    @auto "case" constant_expression ":" statement
    | @auto "default" ":" statement
    | identifier ":" statement
scoped_compound_statement ::=
    !hook(scope_push) compound_statement !hook(scope_pop)
compound_statement ::=
    @peekr(0, r`__attribute__|__attribute`r) attribute_list $become compound_statement
    | "{" $become compound_statement_trail
attribute ::=
    $pruned r`__attribute__|__attribute`r "(" "(" many_balanced ")" ")"
compound_statement_trail ::=
    @auto "}"
    | block_item_list "}"
block_item_list ::=
    block_item $become block_item_star
block_item_star ::=
    @peek(0, "}") #empty # FOLLOW set logic
    | block_item $become block_item_star
block_item ::=
    attribute_list $become block_item_unguarded
block_item_unguarded ::=
    @peek(1, ":") $become_as statement # namelookup-bug-2.c
    | @guard(is_declaration_indicator) $become_as declaration
    | $become_as statement
expression_statement ::=
    @auto ";"
    | expression ";"
selection_statement ::=
    @auto "switch" $pruned "(" expression ")" statement
    | "if" $pruned "(" expression ")" statement $become else_maybe
else_maybe ::=
    @auto "else" statement
    | #empty
iteration_statement ::=
    @auto "while" $pruned "(" expression ")" statement
    | @auto "do" $pruned statement "while" "(" expression ")" ";"
    | "for" $pruned "(" forloop_head_contents ")" statement
forloop_head_contents ::=
    @auto ";" $become forloop_head_contents2
    | @guard(is_declaration_indicator) declaration $become forloop_head_contents2
    | expression ";" $become forloop_head_contents2
forloop_head_contents2 ::=
    @auto ";" $become forloop_head_contents3
    | expression ";" $become forloop_head_contents3
forloop_head_contents3 ::=
    @peek(0, ")") #empty # FOLLOW set logic
    | expression
jump_statement ::=
    @auto "goto" identifier $pruned ";"
    | @auto "continue" $pruned ";"
    | @auto "break" $pruned ";"
    | "return" $become return_operand
return_operand ::=
    @auto ";"
    | expression $pruned ";"
translation_unit ::=
    external_declaration $become external_declaration_star
external_declaration_star ::=
    @eof #empty
    | external_declaration $become external_declaration_star


declaration ::=
    declaration_specifiers $become declaration_tail
declaration_tail ::=
    @auto ";"
    | init_declarator_list ";" !hook(typedefs_log)
#function_definition:
#    declaration_specifiers declarator $become function_definition_cont
#external_declaration ::=
#    @guard(is_funcdef) function_definition
#    | declaration
# UHHHH yeah it looks like this isn't how real C compilers do it and also it's insane.
# The insanity is the arbitrarily long ambiguity until the first declarator stops.
# Let's do something tractible instead.

external_declaration ::=
    @auto ";" #stray semicolon
    | @peekr(0, r`(?:_S|s)tatic_assert`r) static_assert
    | attribute_list $become external_declaration_deattributed
    
external_declaration_deattributed ::=
    @peekr(0, r`__asm|__asm__|asm`r) asm_statement ";"
    | declaration_specifiers $become extdec_chooser

extdec_chooser ::=
    @peek(0, ";") $become_as empty_declaration
    | declarator $become extdec_declarator_choice
empty_declaration ::= ";"
extdec_declarator_choice ::=
    @peekr(0, r`=|;|,|asm|__asm|__asm__`r) $become_as extdec_declaration
    | $become_as function_definition
extdec_declaration_init ::=
    init_declarator $become init_declarator_list_tail 
extdec_declaration ::=
    @auto "," init_declarator_list ";" !hook(typedefs_log)
    | @auto "=" initializer $become extdec_declaration_init_rest
    | ";" !hook(typedefs_log)
extdec_declaration_init_rest ::=
    @auto "," init_declarator_list ";" !hook(typedefs_log)
    | ";" !hook(typedefs_log)

function_definition ::=
    attribute_list func_knr_declaration_star $become func_scope

func_scope ::=
    @peekr(0, r`__attribute__|__attribute`r) attribute_list $become func_scope
    | @peek(0, "{") !hook(scope_push) !hook(entered_function) compound_statement !hook(scope_pop)

func_knr_declaration_star ::=
    @peek(0, "{") #empty
    | declaration $become func_knr_declaration_star

static_assert ::=
    R`(?:_S|s)tatic_assert`r "(" assignment_expression many_balanced ")" ";"


init_declarator_list ::=
    init_declarator $become init_declarator_list_tail
init_declarator_list_tail ::=
    @auto "," init_declarator $become init_declarator_list_tail
    | #empty
init_declarator ::=
    declarator $become init_declarator_tail
init_declarator_tail ::=
    @auto "=" initializer
    | #empty

constant ::=
      @auto R`(?x)   (?:(?:  0[xX][a-fA-F0-9]+  |  [1-9][0-9]*  |  0[0-7]*  )       (?:[uU](?:ll|LL|l|L)?|(?:ll|LL|l|L)[uU]?)?)`r
    | @auto R`(?x)   (?:(?:   (?: [0-9]*\.[0-9]+ | [0-9]+\. )  (?:[eE](?:-|\+)?[0-9]+)?   |   [0-9]+[eE](?:-|\+)?[0-9]+   )   (?:f16|F16|f|l|F|L)?)`r
    | @auto R`(?x)   (?:0[xX]  (?:  [a-fA-F0-9]*\.[a-fA-F0-9]+  |  [a-fA-F0-9]+(?:\.)?  )  [pP](?:-|\+)?  [0-9]+   (?:f16|F16|f|l|F|L)?)`r
    #| @auto R`(?x)   [L]?  \x27  (?:  [^\x27\\\n]  |  \\[\x27\x22\\abefnrtv?]  |  \\[0-7]{1,3}  |  \\x[0-9a-fA-F]+  |  (?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})  )+  \x27`r
    | @auto R`(?x)   [L]?  \x27  (?:  [^\x27\\\n]  |  \\[^xuU]  |  \\[0-7]{1,3}  |  \\x[0-9a-fA-F]+  |  (?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})  )+  \x27`r
    | @guard(is_known_enum) enumeration_constant

enumeration_constant ::=
    identifier

string_literal ::=
    string_literal_core $become string_literal_star
string_literal_star ::=
    #@peekr(0, r`(?x)  [L]?  \x22  (?:[^\x22\\\n]  |  \\[\x27\x22\\abefnrtv?]  |  \\[0-7]{1,3}  |  \\x[0-9a-fA-F]+  |  (?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})  )*\x22`r)
    @peekr(0, r`(?x)  [L]?  \x22  (?:[^\x22\\\n]  |  \\[^xuU]  |  \\[0-7]{1,3}  |  \\x[0-9a-fA-F]+  |  (?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})  )*\x22`r)
    string_literal_core $become string_literal_star
    | #empty
string_literal_core ::=
    #R`(?x)  [L]?  \x22  (?:[^\x22\\\n]  |  \\[\x27\x22\\abefnrtv?]  |  \\[0-7]{1,3}  |  \\x[0-9a-fA-F]+  |  (?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})  )*\x22`r
    R`(?x)  [L]?  \x22  (?:[^\x22\\\n]  |  \\[^xuU]  |  \\[0-7]{1,3}  |  \\x[0-9a-fA-F]+  |  (?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})  )*\x22`r

identifier_maybe ::=
    @auto R`(?:[a-zA-Z_]|(?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8}))(?:[a-zA-Z_]|(?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})|[0-9])*`r
    | #empty
identifier ::=
    R`(?:[a-zA-Z_]|(?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8}))(?:[a-zA-Z_]|(?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})|[0-9])*`r

# Dummy for tokenization
_token_dummy ::=
    r`(?x)(?:(?:   (?: [0-9]*\.[0-9]+ | [0-9]+\. )  (?:[eE](?:-|\+)?[0-9]+)?   |   [0-9]+[eE](?:-|\+)?[0-9]+   )   (?:f16|F16|f|l|F|L)?)|\
          (?:0[xX]  (?:  [a-fA-F0-9]*\.[a-fA-F0-9]+  |  [a-fA-F0-9]+(?:\.)?  )  [pP](?:-|\+)?  [0-9]+   (?:f16|F16|f|l|F|L)?)|\
          (?:[L]?  \x27  (?:[^\x27\\\n]  |  \\[^xuU]  |  \\[0-7]{1,3}  |  \\x[0-9a-fA-F]+  |  (?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})  )+\x27)|\
          (?:[L]?  \x22  (?:[^\x22\\\n]  |  \\[^xuU]  |  \\[0-7]{1,3}  |  \\x[0-9a-fA-F]+  |  (?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})  )*\x22)|\
          (?:(?:[a-zA-Z_]|(?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8}))(?:[a-zA-Z_]|(?:\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,8})|[0-9])*)|\
          (?:(?:  0[xX][a-fA-F0-9]+  |  [1-9][0-9]*  |  0[0-7]*  )       (?:[uU](?:ll|LL|l|L)?|(?:ll|LL|l|L)[uU]?)?)|\
          (?:(?:\*|/|%|\+|-|<<|>>|&|\^|\|)=|<<|>>|<=|>=|==|!=|&&|\|\||[&*/\+\-~!%<>=\^\|])`r


many_balanced ::=
    @peek(0, ")") #empty
    | @peek(0, "]") #empty
    | @peek(0, "}") #empty
    | @auto "(" many_balanced ")" $become many_balanced
    | @auto "[" many_balanced "]" $become many_balanced
    | @auto "{" many_balanced "}" $become many_balanced
    | $any $become many_balanced