Skip to content

Commit 9c748db

Browse files
committed
Session 2: constexpr ctors, virtual base, hoisting fixes, key constants
Parser: - constexpr constructor detection in struct body (parseClassMember) - virtual keyword preserved in base class declarations - [this] lambda capture - static_assert as TopLevelStatement (inside namespace) - parseBaseClassEntry preserves virtual keyword in output CodeGen/CppBuild: - FunctionDecl.isConstexpr added to AstDecl - constexpr functions emitted with constexpr keyword - constexpr functions hoisted to namespace scope - depResult.hoistedVariables emitted before class definitions - static member definitions (Class::member) hoisted to namespace scope - enum forward decls emitted before function forward decls - ClassHoister: forward decl replaced by full definition uses definition position PSketchInjector: - Skip _PSketch injection for classes that inherit it transitively - Prevents ambiguous base errors in diamond hierarchies Processing.h: - KEY_0-KEY_9 digit key constants - SPACE key constant
1 parent 37023e6 commit 9c748db

5 files changed

Lines changed: 120 additions & 12 deletions

File tree

mode/CppMode.jar

1.53 KB
Binary file not shown.

src/Processing.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,9 @@ static constexpr int KEY_S=83; static constexpr int KEY_T=84;
14891489
static constexpr int KEY_U=85; static constexpr int KEY_V=86;
14901490
static constexpr int KEY_W=87; static constexpr int KEY_X=88;
14911491
static constexpr int KEY_Y=89; static constexpr int KEY_Z=90;
1492+
static constexpr int KEY_0=48; static constexpr int KEY_1=49; static constexpr int KEY_2=50; static constexpr int KEY_3=51; static constexpr int KEY_4=52;
1493+
static constexpr int KEY_5=53; static constexpr int KEY_6=54; static constexpr int KEY_7=55; static constexpr int KEY_8=56; static constexpr int KEY_9=57;
1494+
static constexpr int SPACE=32;
14921495

14931496
static constexpr int PERIOD_KEY = 46;
14941497
static constexpr int SLASH_KEY = 47;

src/java/AstPasses.java

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,26 @@ public static Result hoist(List<TopLevelItem> items) {
8181
}
8282

8383
// Deduplicate: drop empty forward declarations when a full definition exists.
84+
// When a forward decl is replaced by a full definition, use the full
85+
// definition's position in the list (not the forward decl's position)
86+
// so that class ordering matches source order of definitions.
8487
java.util.Map<String, TypeDef> best = new java.util.LinkedHashMap<>();
88+
java.util.List<String> order = new java.util.ArrayList<>();
8589
for (TypeDef td : classBlocks) {
8690
String name = td.name();
8791
TypeDef existing = best.get(name);
8892
if (existing == null) {
8993
best.put(name, td);
94+
order.add(name);
9095
} else if (!td.members().isEmpty() && existing.members().isEmpty()) {
91-
best.put(name, td); // full definition beats forward declaration
96+
// Full definition replaces forward decl -- move to end to preserve source order
97+
best.put(name, td);
98+
order.remove(name);
99+
order.add(name);
92100
}
93101
}
94-
classBlocks = new ArrayList<>(best.values());
102+
classBlocks = new ArrayList<>();
103+
for (String n : order) classBlocks.add(best.get(n));
95104

96105
boolean changed = true;
97106
for (int pass = 0; pass < classBlocks.size() * 2 && changed; pass++) {
@@ -502,13 +511,61 @@ public record Result(TypeDef typeDef, boolean injected) {
502511
* never first) unless the class has no methods at all
503512
*/
504513
public static List<Result> injectAll(List<TypeDef> hoistedClasses) {
514+
// Build map of class name -> TypeDef for transitive base lookup
515+
java.util.Map<String, TypeDef> byName = new java.util.LinkedHashMap<>();
516+
for (TypeDef td : hoistedClasses) byName.put(td.name(), td);
517+
518+
// Determine which classes will get _PSketch injected
519+
// A class should NOT get _PSketch if any of its bases already
520+
// (transitively) gets _PSketch -- it will inherit it from them.
521+
// This prevents ambiguous base errors in diamond hierarchies.
522+
java.util.Set<String> willBeInjected = new java.util.LinkedHashSet<>();
523+
for (TypeDef td : hoistedClasses) {
524+
Result r = inject(td);
525+
if (r.injected()) willBeInjected.add(td.name());
526+
}
527+
528+
// For each class that would get _PSketch, check if any of its
529+
// transitive bases also gets _PSketch. If so, skip injection
530+
// since it inherits _PSketch already.
531+
java.util.Set<String> skipInjection = new java.util.HashSet<>();
532+
for (TypeDef td : hoistedClasses) {
533+
if (!willBeInjected.contains(td.name())) continue;
534+
// Check if any base (transitively) is also getting _PSketch
535+
if (transitiveBasesGetPSketch(td, willBeInjected, byName)) {
536+
skipInjection.add(td.name());
537+
}
538+
}
539+
505540
List<Result> results = new ArrayList<>(hoistedClasses.size());
506541
for (TypeDef td : hoistedClasses) {
507-
results.add(inject(td));
542+
if (skipInjection.contains(td.name())) {
543+
results.add(new Result(td, false)); // skip -- inherits via base
544+
} else {
545+
results.add(inject(td));
546+
}
508547
}
509548
return results;
510549
}
511550

551+
/** True if any transitive base of td is in the willBeInjected set. */
552+
private static boolean transitiveBasesGetPSketch(
553+
TypeDef td,
554+
java.util.Set<String> willBeInjected,
555+
java.util.Map<String, TypeDef> byName) {
556+
for (String base : td.baseClasses()) {
557+
// Strip "virtual ", "public ", "protected ", "private " qualifiers
558+
String baseName = base.replaceAll("\b(virtual|public|protected|private)\b\s*", "").trim();
559+
// Strip template args if any
560+
int lt = baseName.indexOf('<');
561+
if (lt >= 0) baseName = baseName.substring(0, lt).trim();
562+
if (willBeInjected.contains(baseName)) return true;
563+
TypeDef baseTd = byName.get(baseName);
564+
if (baseTd != null && transitiveBasesGetPSketch(baseTd, willBeInjected, byName)) return true;
565+
}
566+
return false;
567+
}
568+
512569
private static Result inject(TypeDef td) {
513570
if (!hasAnyMethod(td)) {
514571
return new Result(td, false);

src/java/CppBuild.java

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,7 @@ private File writeSketchImpl(RunnerListener listener) throws IOException {
10261026
warnReservedNames(code, listener);
10271027
checkForUnsupportedJavaArraySyntax(code, listener);
10281028
checkForArrayListGetValueCopy(code, listener);
1029+
checkForArrayListGetDotAccess(code, listener);
10291030
code = stripRawStringLiterals(code);
10301031
code = code.replaceAll("(?<=[0-9a-fA-FxXbB])'(?=[0-9a-fA-F])", "");
10311032
code = javaToC(code);
@@ -1206,6 +1207,10 @@ private File writeSketchImpl(RunnerListener listener) throws IOException {
12061207
} else if (item instanceof FunctionDecl fdce && fdce.isConstexpr()) {
12071208
// constexpr functions must be at namespace scope for static_assert to use them
12081209
autoHoisted.add(item);
1210+
} else if (item instanceof VariableDecl vdq && vdq.name().contains("::")) {
1211+
// Out-of-class static member definition: "int Agent::totalAgents = 0"
1212+
// must be at namespace scope, never inside struct Sketch
1213+
autoHoisted.add(item);
12091214
} else {
12101215
filteredRest.add(item);
12111216
}
@@ -1240,6 +1245,13 @@ private File writeSketchImpl(RunnerListener listener) throws IOException {
12401245
for (var e : enumResult.enums) out.append(CodeGen.generateNode(e, 0));
12411246
for (FunctionDecl fd : forwardDecls) out.append(CodeGen.generateNode(fd, 0));
12421247
for (var v : arrayResult.hoistedSizingConstants) out.append(CodeGen.generateNode(v, 0));
1248+
// Emit hoisted dependency variables BEFORE class definitions so
1249+
// hoisted classes can reference them (e.g. FloatList readings used by SensorBank).
1250+
java.util.Set<String> autoHoistedNames2 = new java.util.HashSet<>();
1251+
for (TopLevelItem av : autoHoisted) if (av instanceof VariableDecl avd2) autoHoistedNames2.add(avd2.name());
1252+
for (var v : depResult.hoistedVariables) {
1253+
if (!autoHoistedNames2.contains(v.name())) out.append(CodeGen.generateNode(v, 0));
1254+
}
12431255
// Forward-declare every hoisted class before any definitions -- handles
12441256
// cross-references between classes (e.g. Liquid::contains(Mover*) when
12451257
// Liquid is emitted before Mover). Template classes can't be forward-declared
@@ -1258,12 +1270,7 @@ private File writeSketchImpl(RunnerListener listener) throws IOException {
12581270
for (TopLevelItem alias : deferredAliases) out.append(CodeGen.generateNode(alias, 0));
12591271
// Emit auto-hoisted variables at namespace scope
12601272
for (TopLevelItem av : autoHoisted) out.append(CodeGen.generateNode(av, 0));
1261-
// Skip auto-hoisted variables from hoistedVariables to avoid redefinition
1262-
java.util.Set<String> autoHoistedNames = new java.util.HashSet<>();
1263-
for (TopLevelItem av : autoHoisted) if (av instanceof VariableDecl avd) autoHoistedNames.add(avd.name());
1264-
for (var v : depResult.hoistedVariables) {
1265-
if (!autoHoistedNames.contains(v.name())) out.append(CodeGen.generateNode(v, 0));
1266-
}
1273+
// hoistedVariables already emitted before class definitions above
12671274
for (var v : arrayResult.hoistedArrays) out.append(CodeGen.generateNode(v, 0));
12681275
for (FunctionDecl fd : depResult.hoistedFunctions) out.append(CodeGen.generateNode(fd, 0));
12691276
// constexprScope (the NOT-PORTED half -- see EnumScopeExtractor's
@@ -2310,6 +2317,45 @@ private void checkForArrayListGetValueCopy(String code, RunnerListener listener)
23102317
}
23112318
}
23122319
}
2320+
// [E0005b] Detect inline .get().field pattern: particles.get(0).x
2321+
// This is a pointer dereference error -- should use -> not .
2322+
private void checkForArrayListGetDotAccess(String code, RunnerListener listener) {
2323+
List<CppLexerToken> tokens;
2324+
try { tokens = new CppLexer(code).tokenize(); } catch (Exception e) { return; }
2325+
for (int i = 0; i + 6 < tokens.size(); i++) {
2326+
// Pattern: .get( ... ) . IDENTIFIER (not "(") -- member access on get() result
2327+
if (!tokens.get(i).isPunct(".")) continue;
2328+
if (!tokens.get(i + 1).text().equals("get")) continue;
2329+
if (!tokens.get(i + 2).isPunct("(")) continue;
2330+
// Consume the get(...) args
2331+
int j = i + 3; int depth = 1;
2332+
while (j < tokens.size() && depth > 0) {
2333+
if (tokens.get(j).isPunct("(")) depth++;
2334+
else if (tokens.get(j).isPunct(")")) depth--;
2335+
j++;
2336+
}
2337+
// After get(...), check if "." follows (not "->")
2338+
if (j < tokens.size() && tokens.get(j).isPunct(".")
2339+
&& j + 1 < tokens.size()
2340+
&& tokens.get(j + 1).type() == CppLexerTokenType.IDENTIFIER) {
2341+
int line = tokens.get(i).line();
2342+
String field = tokens.get(j + 1).text();
2343+
String url = getWebsiteBaseUrl() + "/error/E0005.html";
2344+
String msg =
2345+
"\n[E0005] .get() returns T* (pointer) -- use -> not . to access members.\n" +
2346+
" Line " + line + ": \"...get(...)." + field + "\"\n" +
2347+
" Fix: use -> instead of .:\n" +
2348+
" list.get(i)->" + field + ";\n" +
2349+
" Or store as pointer first:\n" +
2350+
" Type* p = list.get(i); p->" + field + ";\n" +
2351+
" Reference: " + url + "\n";
2352+
System.err.println(msg);
2353+
listener.statusError("E0005: use -> not . after ArrayList.get() -- see console. " + url);
2354+
throw new AlreadyReportedException("E0005: .get().field -- see console.");
2355+
}
2356+
}
2357+
}
2358+
23132359
private void checkForUnsupportedJavaArraySyntax(String code, RunnerListener listener) {
23142360
try {
23152361
CppJavaArrayCheck.check(code);

src/java/Parser.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -694,13 +694,13 @@ private TypeDef parseTypeDef(List<CppLexerToken> leadingComments, List<String> t
694694

695695
/** "public BaseName" / "private BaseName" / "protected BaseName" / bare "BaseName". */
696696
private String parseBaseClassEntry() {
697-
matchKeyword("virtual");
697+
boolean isVirtualBase = matchKeyword("virtual");
698698
matchKeyword("public");
699699
if (!checkKeyword("public")) {
700700
matchKeyword("private");
701701
matchKeyword("protected");
702702
}
703-
matchKeyword("virtual"); // virtual can appear before OR after access specifier
703+
if (!isVirtualBase) isVirtualBase = matchKeyword("virtual");
704704
String name = parseQualifiedTypeName();
705705
// Consume template args on base class name: "Base<Derived>" (CRTP),
706706
// "std::enable_shared_from_this<T>", etc.
@@ -720,7 +720,7 @@ private String parseBaseClassEntry() {
720720
sb.append('>');
721721
name = sb.toString();
722722
}
723-
return name;
723+
return isVirtualBase ? "virtual " + name : name;
724724
}
725725

726726
/**
@@ -2534,6 +2534,8 @@ private Capture parseCapture() {
25342534
return new Capture("", true);
25352535
}
25362536
boolean byRef = matchOp("&");
2537+
// "this" is a keyword capture: [this] or [&this]
2538+
if (checkKeyword("this")) { advance(); return new Capture("this", byRef); }
25372539
String name = expectIdentifier().text();
25382540
// Init-capture: "z = z * 2" or "w = x + y" -- encode into name string
25392541
if (checkOp("=")) {

0 commit comments

Comments
 (0)