-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathParser.java
More file actions
3672 lines (3485 loc) · 178 KB
/
Copy pathParser.java
File metadata and controls
3672 lines (3485 loc) · 178 KB
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
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package processing.mode.cpp;
import processing.mode.cpp.FunctionPointerType;
import processing.mode.cpp.FunctionSignatureType;
import processing.mode.cpp.NamedType;
import processing.mode.cpp.Param;
import processing.mode.cpp.TypeRef;
import java.util.ArrayList;
import java.util.List;
/**
* Recursive-descent parser for CppMode's Java-flavored-C++ grammar subset.
*
* Design notes:
* - Comments are consumed by skipComments() and attached as leadingComments
* on the next real node produced, per the Lexer's comment-as-first-class-
* token design (see Lexer notes). This is the direct replacement for the
* old blankCommentsAndLiterals regex pass.
* - Fail-fast: the first parse error throws ParseException immediately.
* No error recovery is attempted (see ParseException's notes for why).
* - Multi-declarator statements ("int rectX, rectY;") are desugared into
* multiple single-name nodes at this layer -- see parseVariableDeclTail.
*/
public final class Parser {
private final List<CppLexerToken> tokens;
private int pos = 0;
public Parser(List<CppLexerToken> tokens) {
this.tokens = tokens;
}
public static CompilationUnit parse(String source) {
List<CppLexerToken> tokens = new CppLexer(source).tokenize();
return new Parser(tokens).parseCompilationUnit();
}
/** Test-only entry point: parses a single TypeRef from source text in isolation. */
static TypeRef parseTypeRefFromString(String source) {
List<CppLexerToken> tokens = new CppLexer(source).tokenize();
return new Parser(tokens).parseTypeRef();
}
// ----- Core cursor helpers -----------------------------------------
//
// peek()/peek(ahead)/advance() transparently skip over comment tokens.
// This is deliberate and fixes a real bug class found via real-corpus
// testing: a line comment can legally appear in the MIDDLE of a
// multi-line expression (e.g. a function call whose arguments are each
// commented, confirmed real by Move_Eye.pde's
// "camera(x, y, z, // eyeX, eyeY, eyeZ \n ...")). The original design
// only collected leading comments at statement/declaration boundaries
// via consumeLeadingComments(), which left every OTHER parse point
// (inside parseArgList, parsePostfix, anywhere mid-expression) just as
// comment-blind as the regex passes this parser exists to replace.
// Routing comment-skipping through the cursor primitives themselves
// fixes every call site at once instead of needing a fix at each one.
//
// consumeLeadingComments() still exists and is still called explicitly
// at statement/declaration start -- it now serves only to COLLECT the
// comment tokens for attachment to the next node's leadingComments
// field, not to make parsing work correctly (that's now peek/advance's
// job). It works correctly alongside this since skipCommentsAt() is
// idempotent -- calling it twice in a row from the same position is a
// no-op the second time.
private void skipCommentsAt() {
while (pos < tokens.size()
&& (tokens.get(pos).type() == CppLexerTokenType.LINE_COMMENT
|| tokens.get(pos).type() == CppLexerTokenType.BLOCK_COMMENT)) {
pos++;
}
}
private CppLexerToken peek() {
int p = pos;
while (p < tokens.size() && (tokens.get(p).type() == CppLexerTokenType.LINE_COMMENT || tokens.get(p).type() == CppLexerTokenType.BLOCK_COMMENT)) p++;
return p < tokens.size() ? tokens.get(p) : tokens.get(tokens.size() - 1);
}
private CppLexerToken peek(int ahead) {
// Walk forward from pos, skipping comment tokens, to find the
// "ahead"-th real (non-comment) token after the current position.
skipCommentsAt();
int p = pos;
int remaining = ahead;
while (remaining > 0 && p < tokens.size() - 1) {
p++;
while (p < tokens.size() && (tokens.get(p).type() == CppLexerTokenType.LINE_COMMENT
|| tokens.get(p).type() == CppLexerTokenType.BLOCK_COMMENT)) {
p++;
}
remaining--;
}
return p < tokens.size() ? tokens.get(p) : tokens.get(tokens.size() - 1);
}
private CppLexerToken advance() {
skipCommentsAt();
CppLexerToken t = tokens.get(pos);
if (t.type() != CppLexerTokenType.EOF) pos++;
return t;
}
private boolean isAtEnd() {
return peek().type() == CppLexerTokenType.EOF;
}
private boolean check(CppLexerTokenType type) {
return peek().type() == type;
}
private boolean checkKeyword(String kw) {
return peek().isKeyword(kw);
}
private boolean checkPunct(String p) {
return peek().isPunct(p);
}
private boolean checkOp(String op) {
return peek().isOp(op);
}
/** True if the current token is the integer literal "0" specifically
* (not any other literal or expression) -- used to recognize the
* pure-virtual specifier "= 0" precisely, without accidentally
* matching some other "= <expr>" shape. */
private boolean checkLiteralZero() {
CppLexerToken t = peek();
return t.type() == CppLexerTokenType.INT_LITERAL && t.text().equals("0");
}
private boolean matchKeyword(String kw) {
if (checkKeyword(kw)) { advance(); return true; }
return false;
}
private boolean matchPunct(String p) {
if (checkPunct(p)) { advance(); return true; }
return false;
}
private boolean matchOp(String op) {
if (checkOp(op)) { advance(); return true; }
return false;
}
private CppLexerToken expectPunct(String p) {
if (checkPunct(p)) return advance();
throw error("expected '" + p + "' but found '" + peek().text() + "'");
}
private CppLexerToken expectOp(String op) {
if (checkOp(op)) return advance();
throw error("expected '" + op + "' but found '" + peek().text() + "'");
}
private CppLexerToken expectKeyword(String kw) {
if (checkKeyword(kw)) return advance();
throw error("expected '" + kw + "' but found '" + peek().text() + "'");
}
/**
* Set of KEYWORD-classified tokens that are NOT actually reserved
* words in real Processing/Java/C++ -- they're lexed as keywords
* purely so they can be recognized as TYPE names in a type-name
* position (see Lexer's KEYWORDS set and its own comment on this),
* but that classification incorrectly also blocked them from ever
* being used as a declarator/variable NAME, which real Processing
* code is free to do.
*
* Found via a real sketch (RayTracer.pde's "Vec3 color;" struct
* field -- "color" the Processing pseudo-type, used as an ordinary
* field name, which is completely legal Processing/C++ and not
* something the language itself reserves) hitting
* "expected identifier but found 'color'" outright.
*
* Deliberately a narrow, explicit allow-list -- NOT a general
* relaxation letting any KEYWORD stand in for an identifier, which
* would incorrectly let real reserved words ("int", "if", "class",
* etc.) be used as names too. Only pseudo-type keywords that this
* project itself introduced for type-name recognition belong here.
*/
private static final java.util.Set<String> PSEUDO_TYPE_KEYWORDS_USABLE_AS_NAMES = java.util.Set.of(
"color"
);
private CppLexerToken expectIdentifier() {
if (check(CppLexerTokenType.IDENTIFIER)) return advance();
if (check(CppLexerTokenType.KEYWORD) && PSEUDO_TYPE_KEYWORDS_USABLE_AS_NAMES.contains(peek().text())) {
return advance();
}
throw error("expected identifier but found '" + peek().text() + "'");
}
private ParseException error(String message) {
CppLexerToken t = peek();
return new ParseException(message, t.line(), t.col());
}
/**
* Consumes and discards any whitespace-adjacent comment tokens immediately
* preceding the current position, returning them so the caller can attach
* them as leadingComments on the node it's about to build. Must be called
* at the start of every "parse one item/statement/declaration" entry point
* so comments are never silently dropped nor mistaken for code.
*/
/**
* Consumes and collects any comment tokens sitting at the current raw
* position, for attachment to the next node's leadingComments field.
*
* Deliberately reads tokens.get(pos) directly rather than calling
* peek()/check() -- those now transparently skip comment tokens (see
* the cursor-helpers notes above, added after the Move_Eye.pde bug),
* so they can never see a comment to report back here. This method is
* the one place that still needs to look at raw, unfiltered token-
* stream position.
*/
private List<CppLexerToken> consumeLeadingComments() {
List<CppLexerToken> comments = new ArrayList<>();
while (pos < tokens.size()
&& (tokens.get(pos).type() == CppLexerTokenType.LINE_COMMENT
|| tokens.get(pos).type() == CppLexerTokenType.BLOCK_COMMENT)) {
comments.add(tokens.get(pos));
pos++;
}
return comments;
}
// ----- Entry point ---------------------------------------------------
public CompilationUnit parseCompilationUnit() {
List<TopLevelItem> items = new ArrayList<>();
consumeLeadingComments(); // leading file-level comments currently dropped
// at EOF if nothing follows; fine for now
while (!isAtEnd()) {
List<CppLexerToken> comments = consumeLeadingComments();
if (isAtEnd()) break;
items.addAll(parseTopLevelItem(comments));
}
return new CompilationUnit(items);
}
/**
* Dispatches on lookahead to the appropriate top-level (or class-member,
* since both positions share this same dispatch per TopLevelItem's design
* notes) sub-parser. Returns a List since a single source statement can
* desugar into multiple VariableDecl nodes (multi-declarator decls,
* confirmed at both top-level and class-member scope by the Button and
* Handles fixtures respectively) -- every other branch returns a
* single-element list.
*/
private List<TopLevelItem> parseTopLevelItem(List<CppLexerToken> leadingComments) {
consumeAttributes();
if (check(CppLexerTokenType.PREPROCESSOR_DIRECTIVE)) {
CppLexerToken t = advance();
return List.of(new PreprocessorLine(t.text(), t.line(), t.col(), leadingComments));
}
// C++20 concept definition: "concept Name = constraint-expr;"
// "concept" is lexed as IDENTIFIER (not in the keyword set), so
// use text comparison rather than checkKeyword.
if (check(CppLexerTokenType.IDENTIFIER) && peek().text().equals("concept")) {
int startPos = pos;
{ int _d=0; while(!isAtEnd()){if(checkPunct("{"))_d++;else if(checkPunct("}")){if(_d==0){advance();break;}_d--;}else if(checkPunct(";")&&_d==0)break;advance();} matchPunct(";"); }
// Reconstruct the raw text and emit as-is (concept defs are valid at
// namespace scope and must be visible before use in template params).
StringBuilder raw = new StringBuilder();
for (int i = startPos; i < pos - 1; i++) {
if (i > startPos) raw.append(' ');
raw.append(tokens.get(i).text());
}
raw.append(';');
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
List<String> templateParams = List.of();
// extern "C" / extern "C++" linkage specification -- consume verbatim
if (checkKeyword("extern") && pos + 1 < tokens.size()
&& tokens.get(pos + 1).type() == CppLexerTokenType.STRING_LITERAL) {
advance(); advance(); // consume extern "C++"
if (checkPunct("{")) {
// extern "C++" { ... } block -- parse contents as top-level items
advance(); // consume {
List<TopLevelItem> result = new ArrayList<>();
while (!isAtEnd() && !checkPunct("}")) {
List<CppLexerToken> innerComments = consumeLeadingComments();
if (checkPunct("}")) break;
result.addAll(parseTopLevelItem(innerComments));
}
matchPunct("}");
return result;
} else {
// extern "C++" single-declaration
return parseTopLevelItem(leadingComments);
}
}
if (matchKeyword("template")) {
// Explicit template instantiation: "template class Foo<int>;" (no < after template)
if (!checkOp("<")) {
int startPos = pos - 1;
while (!isAtEnd() && !checkPunct(";")) advance();
matchPunct(";");
StringBuilder raw = new StringBuilder();
for (int i = startPos; i < pos - 1; i++) { if (i > startPos) raw.append(" "); raw.append(tokens.get(i).text()); }
raw.append(";");
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
templateParams = parseTemplateParamList();
consumeLeadingComments();
// C++20 concept definition: "template<typename T> concept Foo = ..."
if (check(CppLexerTokenType.IDENTIFIER) && peek().text().equals("concept")) {
int startPos = pos - templateParams.size() - 3; // approximate; use simpler reconstruction
// Build the full concept declaration text
StringBuilder raw = new StringBuilder("template<");
for (int i = 0; i < templateParams.size(); i++) {
if (i > 0) raw.append(", ");
raw.append(templateParams.get(i));
}
raw.append("> ");
// Capture from "concept" through ";"
int conceptStart = pos;
{ int _d=0; while(!isAtEnd()){if(checkPunct("{"))_d++;else if(checkPunct("}")){if(_d==0){advance();break;}_d--;}else if(checkPunct(";")&&_d==0)break;advance();} } matchPunct(";");
for (int i = conceptStart; i < pos - 1; i++) {
if (i > conceptStart) raw.append(' ');
raw.append(tokens.get(i).text());
}
raw.append(';');
return List.of(new PreprocessorLine(raw.toString(), tokens.get(conceptStart).line(), tokens.get(conceptStart).col(), leadingComments));
}
// C++20 "requires" clause after template params.
// "requires" is lexed as IDENTIFIER (not in the keyword set).
if (check(CppLexerTokenType.IDENTIFIER) && peek().text().equals("requires")) {
advance(); // 'requires'
int depth = 0;
while (!isAtEnd()) {
if (checkPunct("(") || checkPunct("[")) { depth++; advance(); continue; }
if (checkPunct(")") || checkPunct("]")) { depth--; advance(); continue; }
if (checkOp("<")) { depth++; advance(); continue; }
if (checkOp(">")) { depth--; advance(); continue; }
if (checkOp(">>")) { depth -= 2; advance(); continue; }
if (checkPunct("{") || checkPunct(";")) break;
if (depth == 0) {
if (checkKeyword("void") || checkKeyword("auto") ||
checkKeyword("int") || checkKeyword("float") ||
checkKeyword("bool") || checkKeyword("char") ||
checkKeyword("double")|| checkKeyword("long") ||
checkKeyword("class") || checkKeyword("struct") ||
checkKeyword("const") || checkKeyword("static") ||
checkKeyword("inline")|| checkKeyword("virtual") ||
checkKeyword("explicit")|| checkKeyword("friend")) {
break;
}
if (check(CppLexerTokenType.IDENTIFIER) && pos + 1 < tokens.size()) {
CppLexerToken next = tokens.get(pos + 1);
if (next.type() == CppLexerTokenType.IDENTIFIER) break;
}
}
advance();
}
}
// C++20 concept used as a constraint directly in the template head:
// "template<Concept T>" -- Concept is an identifier, not typename/class.
// This is handled in parseTemplateParamName already.
}
if (checkKeyword("class") || checkKeyword("struct")) {
// Partial specialization: "template<typename T> struct Foo<T*> { ... }"
// After parsing the class/struct, check for a specialization arg list.
return List.of(parseTypeDef(leadingComments, templateParams));
}
if (checkKeyword("enum")) {
return List.of(parseEnumDecl(leadingComments));
}
if (checkKeyword("namespace") || (checkKeyword("inline") && pos + 1 < tokens.size() && tokens.get(pos + 1).isKeyword("namespace"))) {
return List.of(parseNamespaceDecl(leadingComments));
}
// typedef -- emit verbatim as-is (covers function-pointer typedefs, etc.)
if (checkKeyword("typedef")) {
int startPos = pos;
{ int _d=0; while(!isAtEnd()){if(checkPunct("{"))_d++;else if(checkPunct("}")){if(_d==0){advance();break;}_d--;}else if(checkPunct(";")&&_d==0)break;advance();} matchPunct(";"); }
StringBuilder raw = new StringBuilder();
for (int i = startPos; i < pos - 1; i++) {
if (i > startPos) raw.append(' ');
raw.append(tokens.get(i).text());
}
raw.append(';');
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
if (checkKeyword("typedef")) {
int startPos = pos;
{ int _d=0; while(!isAtEnd()){if(checkPunct("{"))_d++;else if(checkPunct("}")){if(_d==0){advance();break;}_d--;}else if(checkPunct(";")&&_d==0)break;advance();} } matchPunct(";");
StringBuilder raw = new StringBuilder();
for (int i = startPos; i < pos - 1; i++) {
if (i > startPos) raw.append(' ');
raw.append(tokens.get(i).text());
}
raw.append(';');
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
if (checkKeyword("typedef")) {
int startPos = pos;
{ int _d=0; while(!isAtEnd()){if(checkPunct("{"))_d++;else if(checkPunct("}")){if(_d==0){advance();break;}_d--;}else if(checkPunct(";")&&_d==0)break;advance();} } matchPunct(";");
StringBuilder raw = new StringBuilder();
for (int i = startPos; i < pos - 1; i++) {
if (i > startPos) raw.append(' ');
raw.append(tokens.get(i).text());
}
raw.append(';');
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
// static_assert at top level: emit verbatim as PreprocessorLine
if (checkKeyword("static_assert")) {
int startPos = pos; advance();
expectPunct("("); int _d=1;
while (!isAtEnd() && _d > 0) {
if (checkPunct("(")) _d++; else if (checkPunct(")")) _d--;
advance();
}
matchPunct(";");
StringBuilder raw = new StringBuilder();
for (int i = startPos; i < pos; i++) { if (i > startPos) raw.append(" "); raw.append(tokens.get(i).text()); }
if (!raw.toString().endsWith(";")) raw.append(";");
CppLexerToken t0 = tokens.get(startPos);
return List.of(new TopLevelStatement(
new ExprStatement(new Identifier(raw.toString(), t0.line(), t0.col(), List.of()),
t0.line(), t0.col(), List.of()),
t0.line(), t0.col(), leadingComments));
}
if (checkKeyword("using") && tokens.get(pos + 1).isKeyword("namespace")) {
return List.of(parseUsingNamespaceDecl(leadingComments));
}
// using T = Type; -- type alias. Consume to ';' and emit as-is.
// template<typename T> using Alias = ...; -- template alias, prepend template params.
if (checkKeyword("using")) {
int startPos = pos;
{ int _d=0; while(!isAtEnd()){if(checkPunct("{"))_d++;else if(checkPunct("}")){if(_d==0){advance();break;}_d--;}else if(checkPunct(";")&&_d==0)break;advance();} matchPunct(";"); }
StringBuilder raw = new StringBuilder();
// Prepend template params if this is a template alias
if (!templateParams.isEmpty()) {
raw.append("template<");
for (int ti = 0; ti < templateParams.size(); ti++) {
if (ti > 0) raw.append(", ");
raw.append(templateParams.get(ti));
}
raw.append("> ");
}
for (int i = startPos; i < pos - 1; i++) {
if (i > startPos) raw.append(' ');
raw.append(tokens.get(i).text());
}
raw.append(';');
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
if (checkOp("~") || (checkKeyword("virtual") && tokens.get(pos + 1).isOp("~"))) {
return List.of(parseFunctionOrConstructorOrDestructor(leadingComments, templateParams));
}
// Global structured binding: "auto [a, b] = expr;"
if (isStructuredBindingStart()) {
Statement sb2 = parseStructuredBinding(leadingComments);
return List.of(new TopLevelStatement(sb2, sb2.line(), sb2.col(), leadingComments));
}
// Deduction guide: "Wrapper(const char*) -> Wrapper<std::string>;"
if (check(CppLexerTokenType.IDENTIFIER) && pos + 1 < tokens.size() && tokens.get(pos + 1).isPunct("(")) {
int scan = pos + 2; int depth = 1;
while (scan < tokens.size() && depth > 0) {
if (tokens.get(scan).isPunct("(")) depth++;
else if (tokens.get(scan).isPunct(")")) depth--;
scan++;
}
if (scan < tokens.size() && tokens.get(scan).isPunct("->")) {
int startPos = pos;
while (!isAtEnd() && !checkPunct(";")) advance();
matchPunct(";");
StringBuilder raw = new StringBuilder();
for (int i = startPos; i < pos; i++) { if (i > startPos) raw.append(" "); raw.append(tokens.get(i).text()); }
raw.append(";");
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
}
return parseFunctionOrVariable(leadingComments, templateParams, true);
}
/** "template<typename T, typename U, ...>" -- returns just the param names. */
private List<String> parseTemplateParamList() {
// "template" already consumed by the caller; just consume "<...>"
if (checkKeyword("template")) advance(); // in case called with template still present
expectOp("<");
List<String> params = new ArrayList<>();
// template<> is a valid explicit full specialization marker -- emit as empty list
if (!checkOp(">")) {
params.add(parseTemplateParamText());
while (matchPunct(",")) {
params.add(parseTemplateParamText());
}
}
if (checkOp(">>")) {
splitTrailingShiftIntoTwoCloseAngles();
}
expectOp(">");
return params;
}
/**
* Parses one template parameter and returns its FULL TEXT as a String,
* preserving everything needed for CodeGen to emit it verbatim:
* "typename T"
* "typename... Args"
* "class T"
* "template<typename> class Container"
* "auto V"
* "int N = sizeof(T)"
* "typename std::enable_if<std::is_integral<T>::value, int>::type = 0"
* "Numeric T" (concept-constrained)
*
* The AST stores List<String> for template params. Previously it stored only
* the param name, which lost all type/variadic/default information. Now it
* stores the full text so CodeGen can round-trip it as "template<FULL_TEXT>".
*/
private String parseTemplateParamText() {
int startPos = pos;
// We'll capture tokens until we hit ',' or '>' or '>>' at depth 0.
// Complex cases (template-template, SFINAE) are handled by parseTemplateParamName
// for semantic purposes, but here we just need the text.
parseTemplateParamName(); // advance pos past the parameter
int endPos = pos;
// Reconstruct the text from the tokens we consumed
StringBuilder sb = new StringBuilder();
for (int i = startPos; i < endPos; i++) {
if (i > startPos) {
CppLexerToken prev = tokens.get(i - 1);
CppLexerToken cur = tokens.get(i);
// Add spacing between tokens where needed
if (needsSpace(prev, cur)) sb.append(' ');
}
sb.append(tokens.get(i).text());
}
return sb.toString();
}
/** Whether two adjacent tokens need a space between them in output. */
private boolean needsSpace(CppLexerToken prev, CppLexerToken cur) {
String p = prev.text(), c = cur.text();
// No space needed before/after punctuation pairs
if (p.equals("<") || p.equals(">") || p.equals(">>") || p.equals("*")
|| p.equals("&") || p.equals("&&") || p.equals("::")) return false;
if (c.equals(">") || c.equals(">>") || c.equals("<") || c.equals("::")
|| c.equals("(") || c.equals(")") || c.equals(",")) return false;
// Space between two word tokens (keywords, identifiers)
if ((prev.type() == CppLexerTokenType.KEYWORD || prev.type() == CppLexerTokenType.IDENTIFIER)
&& (cur.type() == CppLexerTokenType.KEYWORD || cur.type() == CppLexerTokenType.IDENTIFIER)) return true;
// Space before/after ellipsis in most contexts
if (p.equals("...") || c.equals("...")) return true;
// Space after "=" for defaults
if (p.equals("=") || c.equals("=")) return true;
return false;
}
/**
* Parses one template parameter in any of the C++ forms:
*
* typename T -- type param
* class T -- type param (alternate spelling)
* typename... Args -- variadic type param pack
* class... Args -- variadic type param pack
* template<typename> class C -- template-template param
* template<typename> class... C -- variadic template-template param
* int N -- non-type param
* auto V -- non-type param (C++17 auto NTTP)
* typename T = Default -- type param with default
* int N = 42 -- non-type param with default
* typename std::enable_if<...>::type = 0 -- SFINAE sink param
* Concept T -- constrained type param (C++20)
*
* Returns just the name string (or a descriptive placeholder for complex
* params the AST doesn't fully model -- consistent with existing practice
* where template params are stored as strings, not as typed AST nodes).
*/
private String parseTemplateParamName() {
// --- template-template parameter: "template<...> class Name" ---
if (checkKeyword("template")) {
parseTemplateParamList(); // consume the nested <...> (recursive)
matchKeyword("class");
matchKeyword("typename");
// variadic template-template: "template<typename> class... C"
if (checkPunct("...")) advance();
if (check(CppLexerTokenType.IDENTIFIER)) return advance().text();
return "<template-template>";
}
// --- concept-constrained type parameter: "Numeric T" ---
if (check(CppLexerTokenType.IDENTIFIER) && pos + 1 < tokens.size()
&& tokens.get(pos + 1).type() == CppLexerTokenType.IDENTIFIER
&& pos + 2 < tokens.size()
&& (tokens.get(pos + 2).isOp(">") || tokens.get(pos + 2).isOp(">>")
|| tokens.get(pos + 2).isPunct(",")
|| tokens.get(pos + 2).isPunct("...")
|| tokens.get(pos + 2).isOp("="))) {
advance();
if (checkPunct("...")) advance();
String name = advance().text();
if (matchOp("=")) parseTemplateDefaultValue();
return name;
}
// --- typename / class type parameter ---
if (checkKeyword("typename") || checkKeyword("class")) {
advance(); // consume typename/class
// variadic pack: "typename... Args"
if (checkPunct("...")) advance();
// SFINAE sink: "typename std::enable_if<cond, T>::type = 0"
// After typename, if what follows is a qualified name (has :: or
// template args), it's a non-type SFINAE param, not a plain
// type-param name. Parse it as a full type and grab any default.
if (check(CppLexerTokenType.IDENTIFIER) || check(CppLexerTokenType.KEYWORD)) {
boolean isSfinaeType = checkPunct("::")
|| (pos + 1 < tokens.size() && (
tokens.get(pos + 1).isPunct("::") ||
tokens.get(pos + 1).isOp("<")));
if (isSfinaeType) {
try { parseTypeRef(); } catch (ParseException ignored) {}
if (matchOp("=")) parseTemplateDefaultValue();
return "<sfinae>";
}
String name = advance().text();
if (matchOp("=")) parseTemplateDefaultValue();
return name;
}
// Anonymous typename with default: "typename = std::enable_if<true>"
if (matchOp("=")) parseTemplateDefaultValue();
return "<anon>";
}
// --- auto NTTP: "auto V" or "auto... Vs" ---
if (checkKeyword("auto")) {
advance();
if (checkPunct("...")) advance();
String name = check(CppLexerTokenType.IDENTIFIER) ? advance().text() : "<anon>";
if (matchOp("=")) parseTemplateDefaultValue();
return name;
}
// --- Non-type parameter: "int N", "std::size_t N", "const char* S",
// or member-pointer NTTP: "int T::* Field"
TypeRef type = parseTypeRef();
if (checkPunct("...")) advance();
String name;
if (check(CppLexerTokenType.IDENTIFIER)) {
name = advance().text();
// Member pointer NTTP: "int T::* Field" -- consume ::* and actual name
if (checkPunct("::") && pos + 1 < tokens.size() && tokens.get(pos + 1).isOp("*")) {
advance(); advance(); // :: and *
if (check(CppLexerTokenType.IDENTIFIER)) name = advance().text();
}
} else if (checkPunct("(")) {
// Member function pointer NTTP: "int (T::* Method)(int) const"
// or pointer-to-array NTTP: "int (*Arr)[N]"
advance(); // consume (
// consume optional Class:: qualifier
if (check(CppLexerTokenType.IDENTIFIER) && pos + 1 < tokens.size()
&& tokens.get(pos + 1).isPunct("::")) {
advance(); advance(); // Class ::
}
matchOp("*"); matchOp("&"); // * or &
name = check(CppLexerTokenType.IDENTIFIER) ? advance().text() : "<nttp-ptr>";
expectPunct(")");
// consume trailing (params) and optional const
if (checkPunct("(")) {
advance(); int d = 1;
while (!isAtEnd() && d > 0) {
if (checkPunct("(")) d++; else if (checkPunct(")")) d--;
advance();
}
matchKeyword("const");
}
// consume trailing [dims] for pointer-to-array
while (checkPunct("[")) {
advance();
if (!checkPunct("]")) parseExpr();
expectPunct("]");
}
} else {
name = type instanceof NamedType nt ? nt.baseName() : "<nttp>";
}
// optional default value: "int N = 42" or "::type = 0"
if (matchOp("=")) parseTemplateDefaultValue();
return name;
}
/**
* Parses a class/struct declaration: "class Name [: public Base1, ...] { members };".
* "public"/"private"/"protected" access specifiers within the body are
* consumed and discarded (not modeled in the AST -- not yet confirmed
* necessary by any semantic pass; if member visibility ever matters,
* this is the place to start tracking it).
*/
private TypeDef parseTypeDef(List<CppLexerToken> leadingComments, List<String> templateParams) {
CppLexerToken start = peek();
String kind = checkKeyword("class") ? "class" : "struct";
advance();
// alignas specifier: "struct alignas(64) CacheLine"
if (checkKeyword("alignas") && pos + 1 < tokens.size() && tokens.get(pos + 1).isPunct("(")) {
advance(); advance(); int _ad=1;
while (!isAtEnd() && _ad > 0) { if (checkPunct("(")) _ad++; else if (checkPunct(")")) _ad--; advance(); }
}
// Also consume [[attributes]] before the name
consumeAttributes();
String name = expectIdentifier().text();
// Partial or explicit specialization: "template<typename T> struct Foo<T*>"
// or "template<> struct TypeName<int>" -- capture the specialization arg
// text and fold it into the name so CodeGen emits "TypeName<int>" correctly.
if (checkOp("<")) {
// Specialization args: "Foo<T, N, false>" -- scan to matching > manually
int argStart = pos;
advance(); // consume <
int depth = 1;
while (!isAtEnd() && depth > 0) {
if (checkOp("<")) { depth++; advance(); }
else if (checkOp(">")) { depth--; if (depth > 0) advance(); else advance(); }
else if (checkOp(">>")) { depth -= 2; splitTrailingShiftIntoTwoCloseAngles(); if (depth > 0) advance(); else advance(); }
else advance();
}
int argEnd = pos;
// Reconstruct the specialization suffix verbatim from tokens
StringBuilder spec = new StringBuilder();
for (int i = argStart; i < argEnd; i++) spec.append(tokens.get(i).text());
name = name + spec.toString();
}
// Forward declaration: "struct Node;" or "class Container;" with no body.
// Also handles "template<typename T = int> class Container;" with default args.
if (checkPunct(";")) {
advance();
return new TypeDef(kind, name, templateParams, List.of(), List.of(),
start.line(), start.col(), leadingComments);
}
List<String> baseClasses = new ArrayList<>();
if (matchPunct(":")) {
baseClasses.add(parseBaseClassEntry());
while (matchPunct(",")) {
baseClasses.add(parseBaseClassEntry());
}
}
expectPunct("{");
List<TopLevelItem> members = new ArrayList<>();
while (!checkPunct("}")) {
if (isAtEnd()) throw error("unexpected end of input inside class/struct body for '" + name + "'");
List<CppLexerToken> comments = consumeLeadingComments();
if (checkPunct("}")) break;
if (matchKeyword("public") || matchKeyword("private") || matchKeyword("protected")) {
expectPunct(":");
continue;
}
members.addAll(parseClassMember(comments, name));
}
expectPunct("}");
// The trailing ';' after a class/struct body is standard C++, but
// confirmed OPTIONAL in real .pde input -- Scrollbar.pde's
// "class HScrollbar { ... }" has no trailing ';' at all. Processing's
// own preprocessing tolerates this; this parser does too rather than
// hard-requiring strict C++ grammar here.
matchPunct(";");
return new TypeDef(kind, name, templateParams, baseClasses, members, start.line(), start.col(), leadingComments);
}
/** "public BaseName" / "private BaseName" / "protected BaseName" / bare "BaseName". */
private String parseBaseClassEntry() {
boolean isVirtualBase = matchKeyword("virtual");
matchKeyword("public");
if (!checkKeyword("public")) {
matchKeyword("private");
matchKeyword("protected");
}
if (!isVirtualBase) isVirtualBase = matchKeyword("virtual");
String name = parseQualifiedTypeName();
// Consume template args on base class name: "Base<Derived>" (CRTP),
// "std::enable_shared_from_this<T>", etc.
if (checkOp("<") && looksLikeTemplateArgList()) {
StringBuilder sb = new StringBuilder(name);
sb.append('<');
advance(); // '<'
int depth = 1;
while (!isAtEnd() && depth > 0) {
if (checkOp("<")) depth++;
else if (checkOp(">")) { depth--; if (depth == 0) break; }
else if (checkOp(">>")) { depth -= 2; if (depth <= 0) { splitTrailingShiftIntoTwoCloseAngles(); break; } }
sb.append(peek().text());
advance();
}
expectOp(">");
sb.append('>');
name = sb.toString();
}
boolean isPackExpansion = matchPunct("..."); // pack expansion in base class list: "Bases..."
String suffix = isPackExpansion ? "..." : "";
return isVirtualBase ? "virtual " + name + suffix : name + suffix;
}
/**
* A class member is almost always parseTopLevelItem's domain (field decl,
* method, nested type) -- but constructors/destructors named after the
* enclosing class need special handling parseTopLevelItem's generic
* function/variable dispatch can't do alone (no return type at all,
* possible initializer list), so this wraps that in enclosing-class
* context.
*/
private List<TopLevelItem> parseClassMember(List<CppLexerToken> leadingComments, String enclosingClassName) {
if (check(CppLexerTokenType.PREPROCESSOR_DIRECTIVE)) {
CppLexerToken t = advance();
return List.of(new PreprocessorLine(t.text(), t.line(), t.col(), leadingComments));
}
consumeAttributes();
// friend declarations: "friend class Foo;" or "friend void func(...);"
// Consume entirely -- friend declarations don't produce AST members we need.
if (checkKeyword("friend")) {
advance(); // consume 'friend'
// Friend function with a body: "friend T operator*(...) { ... }"
// Must be parsed as a real function, not consumed to ';'.
// Detect by scanning ahead: if we find '{' before ';', it has a body.
int scan = pos;
int depth = 0;
boolean hasBody = false;
while (scan < tokens.size()) {
String txt = tokens.get(scan).text();
if (txt.equals("{")) { if (depth++ == 0) { hasBody = true; break; } }
else if (txt.equals("}")) { if (--depth == 0) break; }
else if (txt.equals(";") && depth == 0) break;
scan++;
}
if (hasBody) {
// Friend function WITH a body is a free function defined in-class
// but semantically at namespace scope. Return it as a TopLevelItem
// to be hoisted out of the class body.
// We must NOT emit it as a class member since free functions with 2+
// parameters can't be member functions.
List<TopLevelItem> items = parseFunctionOrVariable(leadingComments, List.of(), false);
return items; // will be added to class members and hoisted by ClassHoister
}
// Friend declaration (no body) -- consume to ';'
{ int _d=0; while(!isAtEnd()){if(checkPunct("{"))_d++;else if(checkPunct("}")){if(_d==0){advance();break;}_d--;}else if(checkPunct(";")&&_d==0)break;advance();} matchPunct(";"); }
return List.of();
}
List<String> templateParams = List.of();
if (checkKeyword("template")) {
templateParams = parseTemplateParamList();
consumeLeadingComments();
}
if (checkKeyword("class") || checkKeyword("struct")) {
return List.of(parseTypeDef(leadingComments, templateParams));
}
if (checkKeyword("enum")) {
return List.of(parseEnumDecl(leadingComments));
}
// using X = Type; inside a struct/class body -- consume verbatim
if (checkKeyword("using") && !tokens.get(pos + 1).isKeyword("namespace")) {
int startPos = pos;
{ int _d=0; while(!isAtEnd()){if(checkPunct("{"))_d++;else if(checkPunct("}")){if(_d==0){advance();break;}_d--;}else if(checkPunct(";")&&_d==0)break;advance();} matchPunct(";"); }
StringBuilder raw = new StringBuilder();
for (int i = startPos; i < pos - 1; i++) { if (i > startPos) raw.append(" "); raw.append(tokens.get(i).text()); }
raw.append(";");
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
// Destructor dispatch: "~Name() {...}" or "virtual ~Name() {...}" --
// must look one token past an optional leading "virtual", since that
// keyword (confirmed common on destructors, e.g. "virtual ~LSystem()")
// precedes the '~' that would otherwise be the dispatch signal.
if (checkOp("~") || (checkKeyword("virtual") && tokens.get(pos + 1).isOp("~"))) {
return List.of(parseFunctionOrConstructorOrDestructor(leadingComments, templateParams));
}
// Constructor dispatch: identifier matching the enclosing class name,
// directly followed by '(' with no return type preceding it.
// Also handle "constexpr ClassName(...)" -- consume constexpr first.
// friend declaration: "friend class Foo;" or "friend Box<U> makeBox(U v);"
if (checkKeyword("friend")) {
int startPos = pos; advance(); // consume friend
// Consume to ; skipping over <> and ()
int _fd = 0;
while (!isAtEnd()) {
if (checkPunct("(") || checkOp("<")) { _fd++; advance(); }
else if (checkPunct(")") || checkOp(">")) { _fd--; advance(); }
else if (checkOp(">>")) { _fd -= 2; advance(); }
else if (checkPunct(";") && _fd == 0) { advance(); break; }
else if (checkPunct("{") && _fd == 0) {
// friend function with body
int bd = 1; advance();
while (!isAtEnd() && bd > 0) { if (checkPunct("{")) bd++; else if (checkPunct("}")) bd--; advance(); }
break;
}
else advance();
}
return List.of(new PreprocessorLine("// friend", tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
}
// explicit(...): conditional explicit specifier (C++20)
if (checkKeyword("explicit") && pos + 1 < tokens.size() && tokens.get(pos + 1).isPunct("(")) {
advance(); // consume explicit
advance(); int _ed=1; // consume (
while (!isAtEnd() && _ed > 0) { if (checkPunct("(")) _ed++; else if (checkPunct(")")) _ed--; advance(); }
} else {
matchKeyword("explicit"); // plain explicit
}
boolean isConstexprCtor = false;
if (checkKeyword("constexpr") && pos + 1 < tokens.size()
&& tokens.get(pos + 1).type() == CppLexerTokenType.IDENTIFIER
&& tokens.get(pos + 1).text().equals(enclosingClassName)
&& pos + 2 < tokens.size() && tokens.get(pos + 2).isPunct("(")) {
advance(); // consume constexpr
isConstexprCtor = true;
}
// Constructor: match enclosingClassName OR just its base name (for specializations like Grid<T,N,false>)
String enclosingBase = enclosingClassName.contains("<") ? enclosingClassName.substring(0, enclosingClassName.indexOf("<")) : enclosingClassName;
if (check(CppLexerTokenType.IDENTIFIER) && (peek().text().equals(enclosingClassName) || peek().text().equals(enclosingBase)) && tokens.get(pos + 1).isPunct("(")) {
return List.of(parseFunctionOrConstructorOrDestructor(leadingComments, templateParams));
}
return parseFunctionOrVariable(leadingComments, templateParams, false);
}
/**
* Parses a destructor ("~Name() { ... }") or a constructor (handled here
* too since both share the "no return type, name/~name immediately
* followed by '('" shape, including the optional virtual/override/const
* qualifiers and, for constructors, an initializer list).
*/
private FunctionDecl parseFunctionOrConstructorOrDestructor(List<CppLexerToken> leadingComments, List<String> templateParams) {
CppLexerToken start = peek();
boolean isVirtual = matchKeyword("virtual");
boolean isDestructor = matchOp("~");
String name = expectIdentifier().text();
if (isDestructor) name = "~" + name;
List<Param> params = parseParamList();
boolean isConst = matchKeyword("const");
boolean isOverride = matchKeyword("override");
// order-flexible: also accept override before const, just in case
if (!isConst) isConst = matchKeyword("const");
List<FunctionDecl.ConstructorInit> initList = new ArrayList<>();
if (!isDestructor && matchPunct(":")) {
FunctionDecl.ConstructorInit ci1 = parseConstructorInitEntry();
if (matchPunct("...")) ci1 = new FunctionDecl.ConstructorInit(ci1.memberName() + "...", ci1.args());
initList.add(ci1);
while (matchPunct(",")) {
FunctionDecl.ConstructorInit ci = parseConstructorInitEntry();
if (matchPunct("...")) ci = new FunctionDecl.ConstructorInit(ci.memberName() + "...", ci.args());
initList.add(ci);
}
}
// Consume trailing qualifiers: noexcept, noexcept(expr), override, final, requires
while (true) {
if (checkKeyword("noexcept")) {
advance();
if (checkPunct("(")) { advance(); int d=1; while(!isAtEnd()&&d>0){if(checkPunct("("))d++;else if(checkPunct(")"))d--;advance();} }
} else if (checkKeyword("override")) {
advance();
} else if (check(CppLexerTokenType.IDENTIFIER) && peek().text().equals("final")) {
advance();
} else if (check(CppLexerTokenType.IDENTIFIER) && peek().text().equals("requires")) {
advance();
if (checkPunct("(")) { advance(); int d=1; while(!isAtEnd()&&d>0){if(checkPunct("("))d++;else if(checkPunct(")"))d--;advance();} }
else { while (!isAtEnd() && !checkPunct("{") && !checkPunct(";") && !checkOp("=")) { if (checkOp("<")) { advance(); int d=1; while(!isAtEnd()&&d>0){if(checkOp("<"))d++;else if(checkOp(">"))d--;advance();} } else advance(); } }
} else break;
}
// Pure-virtual specifier ("= 0"), e.g. "virtual ~A() = 0;" -- a
// real, valid C++ idiom for ensuring polymorphic deletion through
// an abstract base. Found via a realistic builder-pattern sketch
// using "virtual void draw() = 0;" on an abstract base class --
// confirmed real, ordinary OOP, not exotic. Must be checked
// BEFORE the body/";" check below, since "= 0;" replaces both.
boolean isPureVirtual = false, isDefault = false, isDelete = false;
if (checkOp("=")) {
int save = pos;
advance();
if (checkKeyword("default")) {
advance(); isDefault = true;
} else if (checkKeyword("delete")) {
advance(); isDelete = true;
} else if (checkLiteralZero()) {
advance();
isPureVirtual = true;
} else {
pos = save;
}
}
Block body = checkPunct("{") ? parseBlock() : null;
if (body == null && !isPureVirtual && !isDefault && !isDelete) expectPunct(";");
else if (isPureVirtual || isDefault || isDelete) expectPunct(";");