-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathParser.java
More file actions
2008 lines (1858 loc) · 86.9 KB
/
Copy pathParser.java
File metadata and controls
2008 lines (1858 loc) · 86.9 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 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 CppLexer's comment-as-first-class-
* token design (see CppLexer 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.
*/
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() {
skipCommentsAt();
return tokens.get(pos);
}
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);
}
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() + "'");
}
private CppLexerToken expectIdentifier() {
if (check(CppLexerTokenType.IDENTIFIER)) 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) {
if (check(CppLexerTokenType.PREPROCESSOR_DIRECTIVE)) {
CppLexerToken t = advance();
return List.of(new PreprocessorLine(t.text(), t.line(), t.col(), leadingComments));
}
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));
}
if (checkKeyword("namespace")) {
return List.of(parseNamespaceDecl(leadingComments));
}
if (checkKeyword("using") && peek(1).isKeyword("namespace")) {
return List.of(parseUsingNamespaceDecl(leadingComments));
}
// Destructor: "~Name() { ... }" or "virtual ~Name() { ... }" -- only
// valid inside a class body in real C++, but the parser doesn't
// enforce that positional rule; a later semantic pass can if it
// matters. Must look past an optional leading "virtual", same as
// the parseClassMember dispatch.
if (checkOp("~") || (checkKeyword("virtual") && peek(1).isOp("~"))) {
return List.of(parseFunctionOrConstructorOrDestructor(leadingComments, templateParams));
}
return parseFunctionOrVariable(leadingComments, templateParams, true);
}
/** "template<typename T, typename U, ...>" -- returns just the param names. */
private List<String> parseTemplateParamList() {
expectKeyword("template");
expectOp("<");
List<String> params = new ArrayList<>();
if (!checkOp(">")) {
params.add(parseTemplateParamName());
while (matchPunct(",")) {
params.add(parseTemplateParamName());
}
}
if (checkOp(">>")) {
splitTrailingShiftIntoTwoCloseAngles();
}
expectOp(">");
return params;
}
private String parseTemplateParamName() {
if (!matchKeyword("typename")) {
matchKeyword("class"); // "template<class T>" is also valid C++, accept both spellings
}
return expectIdentifier().text();
}
/**
* 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();
String name = expectIdentifier().text();
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() {
matchKeyword("public");
if (!checkKeyword("public")) {
matchKeyword("private");
matchKeyword("protected");
}
return parseQualifiedTypeName();
}
/**
* 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));
}
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));
}
// 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") && peek(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
// allow an optional leading "virtual" for symmetry, though
// constructors can't actually be virtual in real C++ -- tolerated
// here rather than rejected, since enforcing that isn't this parser's
// job (see grammar-scope notes on not modeling full C++ semantics).
if (check(CppLexerTokenType.IDENTIFIER) && peek().text().equals(enclosingClassName) && peek(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(":")) {
initList.add(parseConstructorInitEntry());
while (matchPunct(",")) {
initList.add(parseConstructorInitEntry());
}
}
Block body = checkPunct("{") ? parseBlock() : null;
if (body == null) expectPunct(";");
return new FunctionDecl(null, name, templateParams, params, initList, body,
!isDestructor, isDestructor, isVirtual, isOverride, isConst, false,
start.line(), start.col(), leadingComments);
}
private FunctionDecl.ConstructorInit parseConstructorInitEntry() {
String memberName = expectIdentifier().text();
expectPunct("(");
List<Expr> args = new ArrayList<>();
if (!checkPunct(")")) {
args.add(parseExpr());
while (matchPunct(",")) {
args.add(parseExpr());
}
}
expectPunct(")");
return new FunctionDecl.ConstructorInit(memberName, args);
}
/**
* Handles every remaining top-level/class-member form: an ordinary
* function declaration/definition, an operator overload, or a variable
* declaration (possibly multi-declarator, desugared into multiple
* VariableDecl nodes sharing one TypeRef).
*
* Disambiguation: parse [virtual] [static] [const-is-not-valid-here]
* returnType, then a name (ordinary identifier OR "operator" followed by
* an operator token, per the operator-overload fixture). If '(' follows
* the name, it's a function; otherwise it's a variable declaration
* (with possible comma-separated additional declarators).
*
* @param isTopLevel true when called from true file scope
* (parseTopLevelItem), false when called from inside
* a class/struct body (parseClassMember). Gates the
* bare-statement (Processing static-mode) fallback,
* which only makes sense at file scope -- a class
* member is always a declaration, never a bare
* statement. Without this gate, the fallback was
* reachable from inside class bodies too, which
* caused a real bug: "bool operator==(...) const {...}"
* as a class member was misjudged as "not a
* declaration" by the fallback's lookahead and
* incorrectly routed into statement parsing instead
* (found via the oop_features.cpp fixture).
*/
private List<TopLevelItem> parseFunctionOrVariable(List<CppLexerToken> leadingComments, List<String> templateParams, boolean isTopLevel) {
CppLexerToken start = peek();
// Bare top-level statement fallback (Processing "static mode" sketches,
// confirmed real by the example corpus's Coordinates.pde -- a flat
// sequence of statements like "size(640, 360);" with no enclosing
// setup()/draw() at all). Distinguished from a real declaration by
// lookahead: a declaration is "TypeName Identifier ...", whereas a
// bare call statement is "identifier(" with nothing in between. This
// check must run before any of virtual/static/const are consumed,
// since none of those prefix a bare statement. Only ever applies at
// true top level -- see isTopLevel notes above.
if (isTopLevel && !looksLikeTopLevelDeclarationOrFunction()) {
Statement stmt = parseStatement(List.of());
return List.of(new TopLevelStatement(stmt, start.line(), start.col(), leadingComments));
}
boolean isVirtual = matchKeyword("virtual");
boolean isStatic = matchKeyword("static");
boolean isConst = matchKeyword("const");
if (!isStatic) isStatic = matchKeyword("static"); // tolerate either order
// Raw function-pointer variable declaration: "Type (*name)(Params) = init;"
if (looksLikeFunctionPointerVariable()) {
TypeRef returnType = parseTypeRef();
NameAndFunctionPointerType decl = parseFunctionPointerDeclaratorTail(returnType);
Expr initializer = null;
if (matchOp("=")) {
initializer = parseExpr();
}
expectPunct(";");
return List.of(new VariableDecl(decl.type(), decl.name(), List.of(), initializer,
isConst, isStatic, start.line(), start.col(), leadingComments));
}
TypeRef type = parseTypeRef();
String name = parseFunctionOrVariableName();
if (checkPunct("(") && !looksLikeFunctionPointerDeclarator() && looksLikeParamList()) {
List<Param> params = parseParamList();
boolean isOverride = false;
boolean isMethodConst = false;
// trailing const/override may appear in either order
for (int i = 0; i < 2; i++) {
if (matchKeyword("const")) { isMethodConst = true; continue; }
if (matchKeyword("override")) { isOverride = true; continue; }
}
Block body = checkPunct("{") ? parseBlock() : null;
if (body == null) expectPunct(";");
FunctionDecl fn = new FunctionDecl(type, name, templateParams, params, List.of(), body,
false, false, isVirtual, isOverride, isMethodConst, isStatic,
start.line(), start.col(), leadingComments);
return List.of(fn);
}
// Variable declaration, possibly multi-declarator.
List<TopLevelItem> result = new ArrayList<>();
result.add(parseOneTopLevelDeclarator(type, name, isConst, isStatic, start, leadingComments));
while (matchPunct(",")) {
// Each declarator after the first comma can carry its OWN
// leading "*"/"&" markers, independent of every other
// declarator on the line -- real, standard C++ syntax
// ("int* a, b;" -- only a is a pointer; "int* a, *b;" --
// both are pointers). Confirmed real and necessary by
// Scrollbar.pde's real "HScrollbar* hs1, * hs2;", found via
// RealHeaderStressTest after fixing the boolean/null
// test-fidelity gap let parsing get further into that file.
//
// Build each declarator's type from the base type's
// NAME/TEMPLATE-ARGS/CONST ONLY -- never reuse "type"'s own
// pointerDepth/isReference, which already belongs
// EXCLUSIVELY to the first declarator (parseTypeRef consumes
// a "*"/"&" immediately after the base type name, before the
// first declarator's name is even parsed). Adding each
// subsequent declarator's marker count ON TOP of that would
// produce "HScrollbar**" for the second declarator instead
// of the correct "HScrollbar*" -- caught by checking
// rendered output, not just parse success.
int extraPointerDepth = 0;
boolean extraIsReference = false;
while (checkOp("*") || checkOp("&")) {
if (matchOp("*")) extraPointerDepth++;
else { matchOp("&"); extraIsReference = true; }
}
String nextName = expectIdentifier().text();
TypeRef declaratorType = type;
if (type instanceof NamedType nt) {
declaratorType = new NamedType(nt.baseName(), nt.templateArgs(),
extraPointerDepth, extraIsReference, nt.isConst());
}
result.add(parseOneTopLevelDeclarator(declaratorType, nextName, isConst, isStatic, start, List.of()));
}
expectPunct(";");
return result;
}
/**
* Lookahead-only: determines whether the upcoming tokens form a real
* top-level declaration or function definition (possibly preceded by
* virtual/static/const) rather than a bare statement. A declaration has
* the shape "[virtual] [static] [const] TypeName Identifier ..."; a bare
* statement -- the Processing static-mode case -- starts with an
* identifier immediately followed by something that isn't another
* identifier (most commonly '(' for a call, or an assignment operator
* for a plain assignment to an already-declared global).
*/
private boolean looksLikeTopLevelDeclarationOrFunction() {
int save = pos;
try {
matchKeyword("virtual");
matchKeyword("static");
matchKeyword("const");
matchKeyword("static"); // tolerate either order, mirroring the real parse path
if (looksLikeFunctionPointerVariable()) return true;
if (!(check(CppLexerTokenType.IDENTIFIER) || check(CppLexerTokenType.KEYWORD))) return false;
TypeRef type;
try {
type = parseTypeRef();
} catch (ParseException e) {
return false;
}
// "operator==" style names: 'operator' is lexed as a KEYWORD.
// Two cases reach here:
// (a) "operator" itself was parsed as the bare "type" (when
// there's no separate return type before it -- not
// actually valid C++ for operator overloads but tolerated
// defensively), OR
// (b) more commonly, a real return type was already consumed
// ("bool", "Handle", etc.) and the CURRENT token is now
// "operator" itself, e.g. "bool operator==(...)" --
// confirmed real and previously missed by the
// oop_features.cpp fixture's Handle::operator==.
// Either way, this is unambiguously a declaration.
if ((type instanceof NamedType nt && nt.baseName().equals("operator"))
|| checkKeyword("operator")) {
return true;
}
return check(CppLexerTokenType.IDENTIFIER);
} finally {
pos = save;
}
}
/**
* Parses an ordinary name, "operator" followed by an operator/comparison
* token (e.g. "operator=="), or a "::"-qualified name for out-of-class
* static member definitions (e.g. "Counter::count" in
* "int Counter::count = 0;", confirmed real by the kitchen-sink fixture).
*/
private String parseFunctionOrVariableName() {
if (checkKeyword("operator")) {
CppLexerToken opKw = advance();
CppLexerToken opTok = advance(); // the operator symbol itself, e.g. "==", "+", "<<"
return opKw.text() + opTok.text();
}
String name = expectIdentifier().text();
while (checkPunct("::")) {
advance();
name += "::" + expectIdentifier().text();
}
return name;
}
private VariableDecl parseOneTopLevelDeclarator(TypeRef type, String name, boolean isConst, boolean isStatic,
CppLexerToken start, List<CppLexerToken> leadingComments) {
List<Expr> dims = parseOptionalArrayDims();
Expr initializer = null;
if (matchOp("=")) {
initializer = checkPunct("{") ? parseInitializerList() : parseExpr();
} else if (checkPunct("(")) {
initializer = parseDirectInitAsCall(name);
} else if (checkPunct("{")) {
// Brace-direct-init: "Type name{args};" -- the unambiguous,
// most-vexing-parse-proof sibling of paren-direct-init
// ("Type name(args);"). Confirmed necessary by this project's
// own CodeGen, which deliberately renders the self-named-
// CallExpr direct-init shape using braces rather than parens
// (see CodeGen.emitDeclaratorTail's notes on why) -- without
// this branch, the parser couldn't read its own generated
// output back, which a round-trip test caught immediately.
// Represented with the exact same CallExpr shape as the paren
// form (callee = an Identifier matching the declarator's own
// name), since semantically they mean the same thing and
// CodeGen already knows how to render that shape as braces.
initializer = parseDirectInitAsCall(name);
}
return new VariableDecl(type, name, dims, initializer, isConst, isStatic,
start.line(), start.col(), leadingComments);
}
/**
* Lookahead-only: from the "(" that follows a name (in
* parseFunctionOrVariable, deciding between "this is a function decl"
* and "this is direct-init construction syntax"), determines whether the
* parenthesized content actually looks like a parameter list rather than
* a constructor-call argument list.
*
* Confirmed necessary by Arctangent.pde's "Eye e1(250, 16, 120);" --
* direct-init with literal constructor arguments. A real parameter list
* is either empty ("()") or starts with something type-shaped followed
* by a parameter name; an argument list starting with a literal,
* a unary operator, or anything else that can't start a TypeRef is
* unambiguously NOT a parameter list.
*/
private boolean looksLikeParamList() {
int save = pos;
try {
expectPunct("(");
if (checkPunct(")")) return true; // "()" -- ambiguous but treated as a function with no params, matching prior behavior
if (!(check(CppLexerTokenType.IDENTIFIER) || check(CppLexerTokenType.KEYWORD))) return false;
try {
parseTypeRef();
} catch (ParseException e) {
return false;
}
// A real parameter needs a name after its type (or a function-
// pointer declarator's own "(" -- not modeled here since no
// corpus fixture nests a function-pointer param inside a
// direct-init-ambiguous position; flagged as a known gap if it
// ever occurs).
return check(CppLexerTokenType.IDENTIFIER);
} finally {
pos = save;
}
}
private List<Param> parseParamList() {
expectPunct("(");
List<Param> params = new ArrayList<>();
if (!checkPunct(")")) {
params.add(parseParam());
while (matchPunct(",")) {
params.add(parseParam());
}
}
expectPunct(")");
return params;
}
/**
* Lookahead for the raw function-pointer VARIABLE form specifically
* (distinct from looksLikeFunctionPointerDeclarator, which checks
* starting from the "(" itself once a return type has already been
* consumed) -- this version starts from before the return type, so it
* must first skip over a tentative TypeRef.
*/
private boolean looksLikeFunctionPointerVariable() {
int save = pos;
try {
if (!(check(CppLexerTokenType.IDENTIFIER) || check(CppLexerTokenType.KEYWORD))) return false;
try {
parseTypeRef();
} catch (ParseException e) {
return false;
}
return looksLikeFunctionPointerDeclarator();
} finally {
pos = save;
}
}
private EnumDecl parseEnumDecl(List<CppLexerToken> leadingComments) {
CppLexerToken start = expectKeyword("enum");
boolean isScoped = matchKeyword("class");
String name = expectIdentifier().text();
expectPunct("{");
List<String> values = new ArrayList<>();
if (!checkPunct("}")) {
values.add(expectIdentifier().text());
while (matchPunct(",")) {
if (checkPunct("}")) break; // tolerate a trailing comma
values.add(expectIdentifier().text());
}
}
expectPunct("}");
expectPunct(";");
return new EnumDecl(name, isScoped, values, start.line(), start.col(), leadingComments);
}
private NamespaceDecl parseNamespaceDecl(List<CppLexerToken> leadingComments) {
CppLexerToken start = expectKeyword("namespace");
String name = expectIdentifier().text();
expectPunct("{");
List<TopLevelItem> items = new ArrayList<>();
while (!checkPunct("}")) {
if (isAtEnd()) throw error("unexpected end of input inside namespace '" + name + "'");
List<CppLexerToken> comments = consumeLeadingComments();
if (checkPunct("}")) break;
items.addAll(parseTopLevelItem(comments));
}
expectPunct("}");
return new NamespaceDecl(name, items, start.line(), start.col(), leadingComments);
}
private UsingNamespaceDecl parseUsingNamespaceDecl(List<CppLexerToken> leadingComments) {
CppLexerToken start = expectKeyword("using");
expectKeyword("namespace");
String name = parseQualifiedTypeName();
expectPunct(";");
return new UsingNamespaceDecl(name, start.line(), start.col(), leadingComments);
}
// ----- Type references ------------------------------------------------
/**
* Parses a TypeRef in any position: variable type, return type, param
* type, template argument, cast target type, etc.
*
* Handles, in order:
* 1. An optional leading "const".
* 2. The raw C-style function-pointer special case: "Type (*)(Params)".
* Detected by lookahead -- after parsing the return type, if we see
* "(" "*" we know this is a function pointer declarator, not an
* ordinary parenthesized expression (callers parsing a VariableDecl
* pass the name through separately -- see parseFunctionPointerDecl
* for the full "Type (*name)(Params)" variable-declaration form).
* 3. An ordinary dotted/"::"-qualified base name (e.g. "std::string").
* 4. An optional "<...>" template argument list, where each argument
* is itself parsed as either a TypeRef or -- specifically inside
* std::function's argument list -- a bare function signature
* "ReturnType(ParamType, ...)" with no name and no "(*)" (see
* tryParseFunctionSignatureArg).
* 5. Any number of trailing '*' (pointerDepth) and at most one trailing
* '&' (isReference).
*/
private TypeRef parseTypeRef() {
boolean isConst = matchKeyword("const");
String baseName = parseQualifiedTypeName();
List<TypeRef> templateArgs = List.of();
if (checkOp("<")) {
templateArgs = parseTemplateArgList();
}
int pointerDepth = 0;
while (matchOp("*")) {
pointerDepth++;
}
boolean isReference = matchOp("&");
return new NamedType(baseName, templateArgs, pointerDepth, isReference, isConst);
}
/**
* Parses a "::"-joined sequence of identifiers/keywords-used-as-typenames,
* e.g. "std::string", "MyNamespace::Handle", or a single plain name like
* "int" or "Handle". Accepts KEYWORD tokens too (not just IDENTIFIER)
* since C++ builtin type keywords ("int", "float", "bool", "void", "auto",
* "char", etc.) are lexed as KEYWORD, and "color" (a Processing-flavored
* pseudo-keyword, see CppLexer notes) needs the same treatment.
*/
/**
* Parses a "::"-joined sequence of identifiers/keywords-used-as-typenames,
* e.g. "std::string", "MyNamespace::Handle", or a single plain name like
* "int" or "Handle". Also handles compound built-in integer type
* keywords -- "signed"/"unsigned"/"long"/"short" composing with a
* following base type keyword, e.g. "signed char", "unsigned int",
* "long long", "unsigned long long" -- confirmed real and previously
* missed entirely by the original corpus walk (found via real-corpus
* testing on Datatype_Conversion.pde's "signed char b;").
*/
private String parseQualifiedTypeName() {
StringBuilder sb = new StringBuilder();
sb.append(parseTypeNameSegment());
// Compound integer-type keyword combinations: greedily consume any
// run of signed/unsigned/long/short/int/char that follows, since
// these only ever combine with each other (never with a separate
// user type name in the same position) -- e.g. "unsigned long long
// int" is valid C++, "unsigned long long ArrayList" never occurs.
while ((check(CppLexerTokenType.KEYWORD))
&& isIntegerTypeModifierOrBase(peek().text())
&& isIntegerTypeModifierOrBase(sb.toString())) {
sb.append(' ').append(parseTypeNameSegment());
}
while (checkPunct("::")) {
advance();
sb.append("::").append(parseTypeNameSegment());
}
return sb.toString();
}
private static final java.util.Set<String> INTEGER_TYPE_WORDS = java.util.Set.of(
"signed", "unsigned", "long", "short", "int", "char"
);
/** True if every space-separated word in s is one of the integer-type-modifier/base words. */
private boolean isIntegerTypeModifierOrBase(String s) {
for (String word : s.split(" ")) {
if (!INTEGER_TYPE_WORDS.contains(word)) return false;
}
return true;
}
private String parseTypeNameSegment() {
CppLexerToken t = peek();
if (t.type() == CppLexerTokenType.IDENTIFIER || t.type() == CppLexerTokenType.KEYWORD) {
advance();
return t.text();
}
throw error("expected a type name but found '" + t.text() + "'");
}
/**
* Parses "<Arg1, Arg2, ...>" where each Arg is either an ordinary
* TypeRef or a bare function-signature (for std::function<Ret(Params)>).
*/
private List<TypeRef> parseTemplateArgList() {
expectOp("<");
List<TypeRef> args = new ArrayList<>();
if (!checkOp(">")) {
args.add(parseTemplateArg());
while (matchPunct(",")) {
args.add(parseTemplateArg());
}
}
// Note: ">>" closing two nested template lists at once (e.g.
// "Pair<int, ArrayList<T>>") is lexed as a single ">>" OPERATOR token
// by the lexer's longest-match rule, not as two separate '>' tokens --
// the corpus's "template angle brackets vs shift/comparison ambiguity"
// smoke test confirmed ">>" lexes as one token. We must special-case
// that here: a closing ">>" needs to close *this* list and leave a
// single '>' behind for the enclosing list to consume.
if (checkOp(">>")) {
splitTrailingShiftIntoTwoCloseAngles();
}
expectOp(">");
return args;
}
/**
* Rewrites the current ">>" token into two consecutive ">" tokens in the
* token stream, so nested template arg lists each consume exactly one
* '>' as their closer. This mutates the token list in place rather than
* re-lexing, which is simpler than threading "are we inside a template
* list" state back into the lexer (the lexer has no such context, and
* shouldn't need one -- this is a parser-level concern only).
*/
private void splitTrailingShiftIntoTwoCloseAngles() {
CppLexerToken shift = tokens.get(pos);
CppLexerToken first = new CppLexerToken(CppLexerTokenType.OPERATOR, ">", shift.line(), shift.col());
CppLexerToken second = new CppLexerToken(CppLexerTokenType.OPERATOR, ">", shift.line(), shift.col() + 1);
tokens.set(pos, first);
tokens.add(pos + 1, second);
}
private TypeRef parseTemplateArg() {
TypeRef maybeReturnType = parseTypeRef();
// If a '(' follows immediately, this was actually the return type of a
// bare function-signature template arg (std::function<void(int)>),
// not a complete type by itself -- reinterpret.
if (checkPunct("(")) {
return parseFunctionSignatureTail(maybeReturnType);
}
return maybeReturnType;
}
/** Parses "(ParamType, ...)" given an already-parsed return type, producing a FunctionSignatureType. */
private FunctionSignatureType parseFunctionSignatureTail(TypeRef returnType) {
expectPunct("(");
List<TypeRef> paramTypes = new ArrayList<>();
if (!checkPunct(")")) {
paramTypes.add(parseTypeRef());
while (matchPunct(",")) {
paramTypes.add(parseTypeRef());
}
}
expectPunct(")");
return new FunctionSignatureType(returnType, paramTypes);
}
/**
* Detects whether the upcoming tokens form a raw C-style function pointer
* declarator: "(" "*" IDENTIFIER ")" "(" -- used by declaration parsing to
* decide whether a "(" right after a type belongs to this special form
* rather than being parsed as part of an ordinary VariableDecl. Lookahead
* only; does not consume.
*/
private boolean looksLikeFunctionPointerDeclarator() {
return checkPunct("(") && peek(1).isOp("*")
&& peek(2).type() == CppLexerTokenType.IDENTIFIER
&& peek(3).isPunct(")");
}
/**
* Parses the remainder of "Type (*name)(ParamTypes...)" given an
* already-parsed return type, returning the declared variable's name and
* its FunctionPointerType. Confirmed necessary by the corpus's
* useRawFunctionPointer fixture.
*/
private NameAndFunctionPointerType parseFunctionPointerDeclaratorTail(TypeRef returnType) {
expectPunct("(");
expectOp("*");
String name = expectIdentifier().text();
expectPunct(")");
expectPunct("(");
List<TypeRef> paramTypes = new ArrayList<>();
if (!checkPunct(")")) {
paramTypes.add(parseTypeRef());
while (matchPunct(",")) {
paramTypes.add(parseTypeRef());
}
}
expectPunct(")");
return new NameAndFunctionPointerType(name, new FunctionPointerType(returnType, paramTypes));
}
private record NameAndFunctionPointerType(String name, FunctionPointerType type) {
}
// ----- Expressions (precedence climbing, low to high) -----------------
//
// assignment -> ternary (("=") assignment)? [right-assoc]
// ternary -> logicalOr ("?" expr ":" ternary)?
// logicalOr -> logicalAnd ("||" logicalAnd)*
// logicalAnd -> bitOr ("&&" bitOr)*
// bitOr -> bitXor ("|" bitXor)*
// bitXor -> bitAnd ("^" bitAnd)*
// bitAnd -> equality ("&" equality)*
// equality -> relational (("=="|"!=") relational)*
// relational -> shift (("<"|">"|"<="|">=") shift)*
// shift -> additive (("<<"|">>") additive)*
// additive -> multiplicative (("+"|"-") multiplicative)*
// multiplicative -> unary (("*"|"/"|"%") unary)*
// unary -> ("-"|"!"|"&"|"~"|"++"|"--") unary | postfix
// postfix -> primary (call | member | index | "++" | "--")*
// primary -> literal | identifier | scopedName | "(" expr ")"
// | lambda | new | cast | initializerList
private static final java.util.Set<String> COMPOUND_ASSIGN_OPS = java.util.Set.of(
"+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>="
);
Expr parseExpr() {
return parseAssignment();
}
private Expr parseAssignment() {
Expr left = parseTernary();
if (checkOp("=")) {
CppLexerToken t = advance();
Expr right = parseAssignment(); // right-associative
return new AssignExpr(left, right, t.line(), t.col(), List.of());
}
if (peek().type() == CppLexerTokenType.OPERATOR && COMPOUND_ASSIGN_OPS.contains(peek().text())) {
CppLexerToken t = advance();
Expr right = parseAssignment();
return new BinaryExpr(t.text(), left, right, t.line(), t.col(), List.of());
}
return left;