Skip to content

Commit 3e46f90

Browse files
committed
Round 12: anonymous struct, compound bool template args, new-expression, constexpr fixes
Parser: - Anonymous struct member: struct { float x, y; } position; handled in parseClassMember correctly reconstructed including && consumed by parseTypeRef as rvalue-ref - new-expression pack expansion: new T(std::forward<Args>(args)...) - Template args in expression context: AutoParam<42>::value - Deduction guide includes template<...> prefix in emitted PreprocessorLine - Ns... pack expansion preserved in template arg list emission - constexpr return type fix: isConstexprFn no longer marks return type as const - parseTypeRef(bool): explicit const in stream always marks type as const CodeGen: - operator[] and operator() not emitted as friend (valid member operators) - static constexpr members emit constexpr instead of const - NewExpr only stripped to CallExpr when declared type base name matches new type CppBuild: - stripMatchingNew: only strips new when declared type and new type share base name - preprocessMacros: strip #include lines before g++ -E to prevent system header content from appearing in preprocessed output - removePSketchFromDataStructs: template classes excluded from _PSketch injection
1 parent 8f17e52 commit 3e46f90

4 files changed

Lines changed: 153 additions & 26 deletions

File tree

mode/CppMode.jar

1.68 KB
Binary file not shown.

src/java/CodeGen.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,10 @@ private static void emitVariableDecl(StringBuilder sb, VariableDecl vd, int dept
272272
indent(sb, depth);
273273
}
274274
if (vd.isStatic()) sb.append("static ");
275-
// Only emit const here if the type itself doesn't already carry it
276-
if (vd.isConst() && !(vd.type() instanceof NamedType nt && nt.isConst())) sb.append("const ");
275+
// Static const non-integral members need constexpr in C++
276+
if (vd.isConst() && vd.isStatic()) sb.append("constexpr ");
277+
// Only emit const here if the type itself doesn't already carry it and not already constexpr
278+
else if (vd.isConst() && !(vd.type() instanceof NamedType nt && nt.isConst())) sb.append("const ");
277279
sb.append(renderTypeAndName(vd.type(), vd.name()));
278280
emitArrayDims(sb, vd.arrayDims());
279281
emitDeclaratorTail(sb, vd.type(), vd.name(), vd.initializer(), true);
@@ -447,9 +449,13 @@ private static void emitDeclaratorTail(StringBuilder sb, TypeRef declaratorType,
447449
// If initializer is "new Foo(args)" but declared type is a value (not pointer),
448450
// strip "new" and emit as constructor call -- the clean fix for Java's
449451
// "ArrayList<T> x = new ArrayList<T>()" idiom in CppMode.
452+
// Only strip when type names match (not pointer aliases like node_ptr)
450453
if (initializer instanceof NewExpr ne
451454
&& declaratorType instanceof NamedType nt
452-
&& nt.pointerDepth() == 0 && !nt.isReference()) {
455+
&& nt.pointerDepth() == 0 && !nt.isReference()
456+
&& ne.type() instanceof NamedType nnt
457+
&& (nnt.baseName().equals(nt.baseName())
458+
|| nt.baseName().startsWith(nnt.baseName()))) {
453459
sb.append(" = ").append(renderTypeRef(ne.type())).append("(");
454460
for (int i = 0; i < ne.args().size(); i++) {
455461
if (i > 0) sb.append(", ");

src/java/CppBuild.java

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,7 +1886,9 @@ private String removePSketchFromDataStructs(String code) {
18861886
boolean hasMethod = body.matches("(?s).*\\b(void|int|float|bool|double|color|auto|std::\\w+)\\s+\\w+\\s*\\(.*")
18871887
&& !body.contains("constexpr"); // skip constexpr structs
18881888
result.append(code, i, lineStart);
1889-
if (hasMethod) {
1889+
// Also remove _PSketch from template classes -- they can't use Processing API
1890+
boolean isTemplate = lineStart > 0 && code.substring(Math.max(0, lineStart - 200), lineStart).contains("template<");
1891+
if (hasMethod && !isTemplate) {
18901892
// Keep _PSketch injection
18911893
result.append(code, lineStart, j);
18921894
} else {
@@ -2481,11 +2483,9 @@ private String javaToC(String code) {
24812483
// Scan for pattern: TypeName<Args> varName = new TypeName<>();
24822484
code = expandDiamondOperator(code);
24832485
// Strip "new" from value-type assignments: "ArrayList<T> x = new ArrayList<T>()"
2484-
// Pattern: TypeName<...> varName = new TypeName<...>( -> TypeName<...> varName = TypeName<...>(
2485-
// Also handles non-template: "MyClass x = new MyClass(" -> "MyClass x = MyClass("
2486-
code = code.replaceAll(
2487-
"((?:\\w+(?:<[^;=]*>)?)\\s+\\w+\\s*=\\s*)new\\s+(\\w+(?:<[^;=]*>)?\\s*\\()",
2488-
"$1$2");
2486+
// Only when the declared type and new type have the same base name (Java idiom).
2487+
// Do NOT strip when types differ (e.g. "node_ptr n = new node_type(val)")
2488+
code = stripMatchingNew(code);
24892489
// Run color type propagation first
24902490
code = fixColorTypes(code);
24912491

@@ -2861,6 +2861,29 @@ else if (!cmd.contains(defaultsCpp.getAbsolutePath()))
28612861
* macro-expansion step is a secondary concern -- correctness of the
28622862
* expanded code is the primary goal.
28632863
*/
2864+
/** Strip "new TypeName(" only when TypeName matches the declared variable type. */
2865+
private static String stripMatchingNew(String code) {
2866+
// Match: BaseType<...> varName = new SameBase<...>(
2867+
// Capture group 1: base type name; group 2: full lhs; group 3: new type base
2868+
java.util.regex.Pattern p = java.util.regex.Pattern.compile(
2869+
"(\\w+)(?:<[^;={]*>)?\\s+\\w+\\s*=\\s*new\\s+(\\w+)(?:<[^;={]*>)?\\s*\\(");
2870+
java.util.regex.Matcher m = p.matcher(code);
2871+
StringBuffer sb = new StringBuffer();
2872+
while (m.find()) {
2873+
String declBase = m.group(1);
2874+
String newBase = m.group(2);
2875+
if (declBase.equals(newBase)) {
2876+
// Same base type: strip "new "
2877+
String replaced = m.group(0).replaceFirst("new\\s+", "");
2878+
m.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(replaced));
2879+
} else {
2880+
m.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(m.group(0)));
2881+
}
2882+
}
2883+
m.appendTail(sb);
2884+
return sb.toString();
2885+
}
2886+
28642887
private String preprocessMacros(String code) {
28652888
// Fast path: if there's no #define/#ifdef/#undef/#include anywhere,
28662889
// skip invoking an external process entirely.
@@ -2870,8 +2893,20 @@ private String preprocessMacros(String code) {
28702893
return code;
28712894
}
28722895
try {
2896+
// Strip #include lines before preprocessing -- we only want macro
2897+
// expansion (#define/#ifdef), not system header inclusion which
2898+
// causes g++ -E to interleave sketch code with STL internals.
2899+
StringBuilder noIncludes = new StringBuilder();
2900+
for (String line : code.split("\n", -1)) {
2901+
String t = line.strip();
2902+
if (t.startsWith("#include")) {
2903+
noIncludes.append("\n"); // preserve line numbers
2904+
} else {
2905+
noIncludes.append(line).append("\n");
2906+
}
2907+
}
28732908
File scratch = new File(buildDir, "_macro_preprocess_input.cpp");
2874-
Files.writeString(scratch.toPath(), code);
2909+
Files.writeString(scratch.toPath(), noIncludes.toString());
28752910

28762911
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");
28772912
String gpp = findGpp(isWindows);
@@ -2910,11 +2945,14 @@ private String preprocessMacros(String code) {
29102945
String t = line.strip();
29112946
// GNU line marker: "# N \"filename\" flags"
29122947
if (t.startsWith("# ") && t.length() > 2 && Character.isDigit(t.charAt(2))) {
2913-
// Check if this marker is for our scratch file
2914-
inScratchFile = t.contains(scratchPath) || t.contains("<command-line>") || t.contains("<built-in>");
2915-
// Also allow returning to scratch file from included user code
2916-
// but exclude system headers (/usr/include, /usr/lib, etc.)
2917-
if (t.contains("/usr/") || t.contains("/lib/") || t.contains("c++/")) inScratchFile = false;
2948+
// System headers have /usr/, /lib/, or c++/ in path
2949+
boolean isSystemHeader = t.contains("/usr/") || t.contains("/lib/") || t.contains("c++/");
2950+
// Scratch file or built-ins
2951+
boolean isScratch = t.contains(scratchPath) || t.contains("<command-line>") || t.contains("<built-in>") || t.contains("<stdin>");
2952+
if (isSystemHeader) inScratchFile = false;
2953+
else if (isScratch) inScratchFile = true;
2954+
// flag=2 means "returned to file after include" -- always re-enable for scratch
2955+
else inScratchFile = true; // unknown file -- keep (could be user header)
29182956
continue; // never emit line markers themselves
29192957
}
29202958
if (t.startsWith("#line")) continue;

src/java/Parser.java

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -478,8 +478,16 @@ private List<TopLevelItem> parseTopLevelItem(List<CppLexerToken> leadingComments
478478
int startPos = pos;
479479
while (!isAtEnd() && !checkPunct(";")) advance();
480480
matchPunct(";");
481-
// Reconstruct without spaces, up to but not including the ";" we consumed
481+
// Reconstruct: include template params prefix if present
482482
StringBuilder raw = new StringBuilder();
483+
if (!templateParams.isEmpty()) {
484+
raw.append("template<");
485+
for (int i = 0; i < templateParams.size(); i++) {
486+
if (i > 0) raw.append(", ");
487+
raw.append(templateParams.get(i));
488+
}
489+
raw.append("> ");
490+
}
483491
for (int i = startPos; i < pos - 1; i++) { if (i > startPos) raw.append(" "); raw.append(tokens.get(i).text()); }
484492
raw.append(";");
485493
return List.of(new PreprocessorLine(raw.toString(), tokens.get(startPos).line(), tokens.get(startPos).col(), leadingComments));
@@ -712,6 +720,7 @@ private TypeDef parseTypeDef(List<CppLexerToken> leadingComments, List<String> t
712720
}
713721
// Also consume [[attributes]] before the name
714722
consumeAttributes();
723+
715724
String name = expectIdentifier().text();
716725

717726
// Partial or explicit specialization: "template<typename T> struct Foo<T*>"
@@ -857,6 +866,26 @@ private List<TopLevelItem> parseClassMember(List<CppLexerToken> leadingComments,
857866
consumeLeadingComments();
858867
}
859868
if (checkKeyword("class") || checkKeyword("struct")) {
869+
// Anonymous struct: "struct { float x, y; } position;"
870+
if (pos + 1 < tokens.size() && tokens.get(pos + 1).isPunct("{")) {
871+
int anonStart = pos;
872+
advance(); // consume struct/class
873+
int bd = 0;
874+
while (!isAtEnd()) {
875+
if (checkPunct("{")) { bd++; advance(); }
876+
else if (checkPunct("}")) { bd--; advance(); if (bd == 0) break; }
877+
else advance();
878+
}
879+
// pos is now after }, next is memberName then ;
880+
int beforeMember = pos;
881+
String memberName = check(CppLexerTokenType.IDENTIFIER) ? advance().text() : "";
882+
matchPunct(";");
883+
StringBuilder raw = new StringBuilder();
884+
for (int i = anonStart; i < beforeMember; i++) { if (i > anonStart) raw.append(" "); raw.append(tokens.get(i).text()); }
885+
if (!memberName.isEmpty()) raw.append(" ").append(memberName);
886+
raw.append(";");
887+
return List.of(new PreprocessorLine(raw.toString(), tokens.get(anonStart).line(), tokens.get(anonStart).col(), leadingComments));
888+
}
860889
return List.of(parseTypeDef(leadingComments, templateParams));
861890
}
862891
if (checkKeyword("enum")) {
@@ -1075,7 +1104,8 @@ private List<TopLevelItem> parseFunctionOrVariable(List<CppLexerToken> leadingCo
10751104
matchKeyword("inline");
10761105
matchKeyword("volatile"); // consume volatile qualifier
10771106
boolean isConst = matchKeyword("const");
1078-
boolean isConstexprFn = matchKeyword("constexpr") || matchKeyword("consteval"); if (isConstexprFn) isConst = true;
1107+
boolean isConstexprFn = matchKeyword("constexpr") || matchKeyword("consteval");
1108+
if (isConstexprFn && !isConst) isConst = true;
10791109
matchKeyword("constinit");
10801110
if (!isVirtual) isVirtual = matchKeyword("virtual"); // constexpr virtual
10811111
if (!isStatic) isStatic = matchKeyword("static");
@@ -1095,7 +1125,7 @@ private List<TopLevelItem> parseFunctionOrVariable(List<CppLexerToken> leadingCo
10951125
isConst, isStatic, start.line(), start.col(), leadingComments));
10961126
}
10971127

1098-
TypeRef type = parseTypeRef(isConst); // pass leading const to mark return type correctly
1128+
TypeRef type = parseTypeRef(isConstexprFn ? false : isConst); // constexpr should not mark return type as const
10991129
// Constructor: if ( follows the type with no name AND we have leading constexpr/virtual,
11001130
// the type name IS the function name (e.g. "constexpr RGBA(...)")
11011131
String name;
@@ -1605,8 +1635,9 @@ private TypeRef parseTypeRef(boolean leadingConst) {
16051635
// set; we still MUST consume the token so parseQualifiedTypeName doesn't see
16061636
// "const" as the type name (e.g. "constexpr const char* p" -- constexpr sets
16071637
// leadingConst=true, then "const" must still be consumed before "char").
1608-
matchKeyword("const");
1609-
return parseTypeRefAfterConst(leadingConst);
1638+
// If const is explicitly present, always mark type as const regardless of leadingConst.
1639+
boolean hasExplicitConst = matchKeyword("const");
1640+
return parseTypeRefAfterConst(leadingConst || hasExplicitConst);
16101641
}
16111642

16121643
private TypeRef parseTypeRefAfterConst(boolean isConst) {
@@ -1778,12 +1809,16 @@ private List<TypeRef> parseTemplateArgList() {
17781809
expectOp("<");
17791810
List<TypeRef> args = new ArrayList<>();
17801811
if (!checkOp(">")) {
1781-
args.add(parseTemplateArg());
1782-
matchPunct("..."); // trailing pack expansion: "Ts..."
1812+
TypeRef _a0 = parseTemplateArg();
1813+
if (matchPunct("...") && _a0 instanceof NamedType _nt0)
1814+
_a0 = new NamedType(_nt0.baseName() + "...", _nt0.templateArgs(), _nt0.pointerDepth(), _nt0.isReference(), _nt0.isConst(), _nt0.isRvalueRef());
1815+
args.add(_a0);
17831816
while (matchPunct(",")) {
17841817
if (checkOp(">") || checkOp(">>")) break;
1785-
args.add(parseTemplateArg());
1786-
matchPunct("..."); // trailing pack expansion
1818+
TypeRef _ai = parseTemplateArg();
1819+
if (matchPunct("...") && _ai instanceof NamedType _nti)
1820+
_ai = new NamedType(_nti.baseName() + "...", _nti.templateArgs(), _nti.pointerDepth(), _nti.isReference(), _nti.isConst(), _nti.isRvalueRef());
1821+
args.add(_ai);
17871822
}
17881823
}
17891824
// Note: ">>" closing two nested template lists at once (e.g.
@@ -1816,6 +1851,24 @@ private void splitTrailingShiftIntoTwoCloseAngles() {
18161851
tokens.add(pos + 1, second);
18171852
}
18181853

1854+
/** Render a NamedType back to its source string including template args. */
1855+
private static String renderNamedTypeAsString(NamedType nt) {
1856+
StringBuilder sb = new StringBuilder(nt.baseName());
1857+
if (!nt.templateArgs().isEmpty()) {
1858+
sb.append("<");
1859+
for (int i = 0; i < nt.templateArgs().size(); i++) {
1860+
if (i > 0) sb.append(", ");
1861+
TypeRef a = nt.templateArgs().get(i);
1862+
if (a instanceof NamedType na) sb.append(renderNamedTypeAsString(na));
1863+
else sb.append(a.toString());
1864+
}
1865+
sb.append(">");
1866+
}
1867+
if (nt.pointerDepth() > 0) sb.append("*".repeat(nt.pointerDepth()));
1868+
if (nt.isReference()) sb.append("&");
1869+
return sb.toString();
1870+
}
1871+
18191872
private TypeRef parseTemplateArg() {
18201873
if (checkKeyword("true") || checkKeyword("false")) {
18211874
String val = advance().text();
@@ -1876,6 +1929,32 @@ private TypeRef parseTemplateArg() {
18761929
return new NamedType("sizeof(...)", List.of(), 0, false, false, false);
18771930
}
18781931
TypeRef maybeReturnType = parseTypeRef();
1932+
// Compound boolean expression in template arg: "is_arithmetic_v<T> && !is_same_v<T,bool>"
1933+
// The && was consumed as rvalue-ref by parseTypeRef; check if ! or || follows
1934+
// The && was already consumed by parseTypeRef as rvalue-ref
1935+
boolean trailingRvalueRef = maybeReturnType instanceof NamedType ntrr && ntrr.isRvalueRef();
1936+
if (trailingRvalueRef || checkOp("||") || checkOp("!")) {
1937+
// Strip the falsely-consumed && from the type
1938+
if (trailingRvalueRef && maybeReturnType instanceof NamedType ntrr2) {
1939+
maybeReturnType = new NamedType(ntrr2.baseName(), ntrr2.templateArgs(),
1940+
ntrr2.pointerDepth(), ntrr2.isReference(), ntrr2.isConst(), false);
1941+
}
1942+
StringBuilder expr = new StringBuilder(
1943+
maybeReturnType instanceof NamedType nt ? renderNamedTypeAsString(nt) : "");
1944+
if (trailingRvalueRef) expr.append(" && ");
1945+
while (!isAtEnd()) {
1946+
if (checkOp("&&")) { expr.append(" && "); advance(); }
1947+
else if (checkOp("||")) { expr.append(" || "); advance(); }
1948+
else if (checkOp("!")) { expr.append("!"); advance(); }
1949+
else if (checkOp(">") || checkOp(">>") || checkPunct(",")) break;
1950+
else if (checkPunct("(") || check(CppLexerTokenType.IDENTIFIER)
1951+
|| check(CppLexerTokenType.KEYWORD)) {
1952+
TypeRef sub = parseTypeRef();
1953+
if (sub instanceof NamedType nts) expr.append(renderNamedTypeAsString(nts));
1954+
} else break;
1955+
}
1956+
return new NamedType(expr.toString(), List.of(), 0, false, false, false);
1957+
}
18791958
if (checkPunct("(")) {
18801959
return parseFunctionSignatureTail(maybeReturnType);
18811960
}
@@ -2259,9 +2338,13 @@ private Expr parseNew() {
22592338
List<Expr> args = new ArrayList<>();
22602339
if (matchPunct("(")) {
22612340
if (!checkPunct(")")) {
2262-
args.add(parseExpr());
2341+
Expr a0 = parseExpr();
2342+
if (matchPunct("...")) a0 = new PostfixExpr("...", a0, a0.line(), a0.col(), List.of());
2343+
args.add(a0);
22632344
while (matchPunct(",")) {
2264-
args.add(parseExpr());
2345+
Expr ai = parseExpr();
2346+
if (matchPunct("...")) ai = new PostfixExpr("...", ai, ai.line(), ai.col(), List.of());
2347+
args.add(ai);
22652348
}
22662349
}
22672350
expectPunct(")");

0 commit comments

Comments
 (0)