Skip to content

Commit f84b86f

Browse files
committed
Parser: fix direct-init vs param-list ambiguity; CodeGen: ptr-to-array __pta__ sentinel; Engine: map() div-by-zero guard; Linter: use generateSketchOutput pipeline; API conflicts expanded
1 parent aaf5859 commit f84b86f

5 files changed

Lines changed: 96 additions & 87 deletions

File tree

src/Processing.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4209,7 +4209,7 @@ struct PApplet {
42094209
{ float dx=x2-x1,dy=y2-y1; return ::std::sqrt(dx*dx+dy*dy); }
42104210
static float dist(float x1,float y1,float z1,float x2,float y2,float z2)
42114211
{ float dx=x2-x1,dy=y2-y1,dz=z2-z1; return ::std::sqrt(dx*dx+dy*dy+dz*dz); }
4212-
static float map(float v,float i0,float i1,float o0,float o1) { return o0+(v-i0)*(o1-o0)/(i1-i0); }
4212+
static float map(float v,float i0,float i1,float o0,float o1) { if (i1==i0) return o0; return o0+(v-i0)*(o1-o0)/(i1-i0); }
42134213
static float constrain(float v,float lo,float hi) { return v<lo?lo:(v>hi?hi:v); }
42144214
static float max(float a,float b) { return a>b?a:b; }
42154215
static float min(float a,float b) { return a<b?a:b; }

src/java/CodeGen.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,22 @@ private static String renderTypeAndName(TypeRef type, String name) {
274274
// Without sentinel, ptr-to-array (empty paramTypes, dims outside)
275275
// and array-of-fn-ptr with no params (also empty paramTypes) are
276276
// indistinguishable. The sentinel is stripped before emission.
277+
int ptaSentinel = bareNamePart.indexOf("__pta__");
277278
int arrSentinel = bareNamePart.indexOf("__arr__");
278-
if (arrSentinel >= 0) {
279+
if (ptaSentinel >= 0) {
280+
// Array-of-ptr-to-array: e.g. "float (*kernels[8])[3]"
281+
// Split: "kernels__arr__[8]__pta__[3]" ->
282+
// innerDims="[8]" (inside parens), outerDims="[3]" (after params)
283+
String afterArr = bareNamePart.substring(0, ptaSentinel);
284+
outerDims = bareNamePart.substring(ptaSentinel + 7);
285+
int arrIdx = afterArr.indexOf("__arr__");
286+
if (arrIdx >= 0) {
287+
innerDims = afterArr.substring(arrIdx + 7);
288+
bareNamePart = afterArr.substring(0, arrIdx);
289+
} else {
290+
bareNamePart = afterArr;
291+
}
292+
} else if (arrSentinel >= 0) {
279293
// Array-of-fn-ptr: dims go inside parens
280294
innerDims = bareNamePart.substring(arrSentinel + 7); // after "__arr__"
281295
bareNamePart = bareNamePart.substring(0, arrSentinel);

src/java/CppBuild.java

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,7 @@ private File writeSketchImpl(RunnerListener listener) throws IOException {
12341234
out.append("using std::decay_t; using std::remove_reference_t; using std::common_type_t;\n");
12351235
out.append("using std::declval; using std::void_t;\n");
12361236
out.append("using std::index_sequence; using std::make_index_sequence;\n");
1237-
out.append("using std::tuple_size; using std::tuple_element; using std::get;\n");
1237+
out.append("using std::tuple_size; using std::tuple_element; // std::get intentionally omitted -- shadows PApplet::get()\n");
12381238
out.append("using std::make_tuple; using std::tie; using std::apply;\n");
12391239
out.append("using std::runtime_error; using std::logic_error; using std::exception;\n");
12401240
out.append("using std::numeric_limits;\n");
@@ -1335,16 +1335,34 @@ private File writeSketchImpl(RunnerListener listener) throws IOException {
13351335
fileScopeOnly.append(CodeGen.generateNode(item, 0));
13361336
continue;
13371337
}
1338-
// Template declarations, free functions, and ALL variables must be at
1339-
// file scope in static sketches -- main() cannot access Sketch members.
1338+
// Template declarations and free functions go to file scope.
1339+
// Variables: only those with no initializer or a simple literal/nullptr
1340+
// go to file scope. Variables with Processing API calls in their initializer
1341+
// must go into setup() body -- calling API functions at static init time
1342+
// crashes because g_papplet is null before PApplet is constructed.
13401343
boolean isAnyFn = item instanceof FunctionDecl;
1341-
boolean isAnyVar = item instanceof VariableDecl;
1342-
boolean isTemplateTd = item instanceof TypeDef td
1343-
&& (!td.templateParams().isEmpty() || td.name().contains("<"));
1344-
if (isAnyFn || isAnyVar || isTemplateTd) {
1344+
boolean isTemplateTd = item instanceof TypeDef td2
1345+
&& (!td2.templateParams().isEmpty() || td2.name().contains("<"));
1346+
if (isAnyFn || isTemplateTd) {
13451347
fileScopeOnly.append(CodeGen.generateNode(item, 0));
13461348
continue;
13471349
}
1350+
if (item instanceof VariableDecl vd2) {
1351+
// Safe at file scope: no initializer, nullptr, or numeric/string literal
1352+
Expr _init = vd2.initializer();
1353+
boolean safeAtFileScope = _init == null
1354+
|| (_init instanceof Literal lit &&
1355+
(lit.kind() == Literal.Kind.INT || lit.kind() == Literal.Kind.FLOAT
1356+
|| lit.kind() == Literal.Kind.STRING || lit.kind() == Literal.Kind.BOOL
1357+
|| lit.kind() == Literal.Kind.CHAR))
1358+
|| (_init instanceof Identifier id && id.name().equals("nullptr"));
1359+
if (safeAtFileScope) {
1360+
fileScopeOnly.append(CodeGen.generateNode(item, 0));
1361+
} else {
1362+
body.append(CodeGen.generateNode(item, 2));
1363+
}
1364+
continue;
1365+
}
13481366
String rendered = CodeGen.generateNode(item, 2);
13491367
String trimmed = rendered.strip();
13501368
if (trimmed.startsWith("size(") || trimmed.startsWith("fullScreen(")) {
@@ -2876,10 +2894,29 @@ private String javaToC(String code) {
28762894
// ── 11. API-conflicting variable names: scale/fill/stroke/etc. ───────────
28772895
// If used as a variable (after a type keyword), rename to avoid shadowing API
28782896
String[] apiConflicts = {
2879-
"scale", "stroke", "background", "translate", "rotate",
2880-
"map", "dist", "noise"
2897+
// Drawing
2898+
"arc", "box", "circle", "ellipse", "line", "point", "rect",
2899+
"square", "triangle", "vertex", "curve", "bezier",
2900+
// Color
2901+
"alpha", "blue", "brightness", "green", "hue", "red", "saturation",
2902+
"fill", "stroke", "background", "tint", "filter",
2903+
// Transform
2904+
"scale", "translate", "rotate", "push", "pop",
2905+
// Math
2906+
"map", "dist", "noise", "norm", "lerp", "mag", "sq",
2907+
"constrain", "degrees", "radians", "max", "min",
2908+
// Image/pixel
2909+
"image", "get", "set", "save",
2910+
// Text
2911+
"text",
2912+
// Lighting
2913+
"lights", "ambient", "specular", "emissive", "shininess",
2914+
// Camera
2915+
"camera", "perspective", "ortho",
2916+
// Misc
2917+
"smooth", "cursor", "loop", "random", "clear"
28812918
};
2882-
String typeKw = "(?:int|float|double|bool|char|long|unsigned|auto|color|PVector)\\s+";
2919+
String typeKw = "(?:int|float|double|bool|char|long|unsigned|auto|color|PVector\\*?|ArrayList\\s*<[^>]+>\\*?|Array\\s*<[^>]+>\\*?)\\s+";
28832920
for (String word : apiConflicts) {
28842921
// Only rename if it appears as a variable declaration
28852922
if (java.util.regex.Pattern.compile(typeKw + word + "\\b(?!\\s*\\()", java.util.regex.Pattern.MULTILINE).matcher(code).find()) {

src/java/CppLinter.java

Lines changed: 8 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -138,76 +138,16 @@ private void runCheck(Sketch sketch, int currentTab, String liveText) {
138138
CppBuild.PreparedCode prepared = CppBuild.prepareCode(sketchCode.toString());
139139
String code = prepared.code;
140140

141-
// Build lint source
142-
StringBuilder sb = new StringBuilder();
143-
for (String block : prepared.hasIncludeBlocks.values())
144-
sb.append(block);
145-
if (gch != null) {
146-
// Use PCH via -include trick: include a stub that matches the PCH
147-
sb.append("#include \"").append(processingH.getAbsolutePath()).append("\"\n");
148-
} else {
149-
sb.append("#include \"").append(processingH.getAbsolutePath()).append("\"\n");
150-
}
151-
sb.append("using namespace std;\n");
152-
sb.append("namespace Processing {\n");
153-
sb.append("using namespace std;\n");
154-
boolean hasSetup = code.contains("void setup(") || code.contains("void draw(");
155-
if (hasSetup) {
156-
sb.append("#include \"Processing_api.h\"\n");
157-
// Use ClassHoister (same as real build) to separate classes from sketch code
158-
StringBuilder hoistedSb = new StringBuilder();
159-
StringBuilder sketchSb = new StringBuilder();
160-
try {
161-
CompilationUnit lintCu = Parser.parse(code);
162-
ClassHoister.Result hr = ClassHoister.hoist(lintCu.items());
163-
for (TypeDef td : hr.hoistedClasses) {
164-
hoistedSb.append(CodeGen.generateNode(td, 0));
165-
}
166-
for (TopLevelItem item : hr.rest) {
167-
if (item instanceof VariableDecl vd && vd.name().contains("::"))
168-
hoistedSb.append(CodeGen.generateNode(item, 0));
169-
else
170-
sketchSb.append(CodeGen.generateNode(item, 0));
171-
}
172-
} catch (Exception _lintEx) {
173-
sketchSb.append(code);
174-
}
175-
sb.append("struct _PSketch : public PApplet {\n"
176-
+ " struct _W { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->logicalW : 0; } } width;\n"
177-
+ " struct _H { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->logicalH : 0; } } height;\n"
178-
+ " struct _MX { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseX : 0.f; } } mouseX;\n"
179-
+ " struct _MY { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseY : 0.f; } } mouseY;\n"
180-
+ " struct _PMX { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->pmouseX : 0.f; } } pmouseX;\n"
181-
+ " struct _PMY { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->pmouseY : 0.f; } } pmouseY;\n"
182-
+ " struct _FC { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->frameCount : 0; } } frameCount;\n"
183-
+ " struct _MP { operator bool() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_mousePressed : false; } } _mousePressed;\n"
184-
+ " struct _KP { operator bool() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_keyPressed : false; } } _keyPressed;\n"
185-
+ " struct _K { operator char() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->key : 0; } } key;\n"
186-
+ " struct _KC { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->keyCode : 0; } } keyCode;\n"
187-
+ " struct _MB { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseButton : 0; } } mouseButton;\n"
188-
+ " struct _FR { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_frameRate : 0.f; } } _frameRate;\n"
189-
+ " struct _MDX { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseDX : 0.f; } } mouseDX;\n"
190-
+ " struct _MDY { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseDY : 0.f; } } mouseDY;\n"
191-
+ " bool* keysDown() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->keysDown : nullptr; }\n"
192-
+ " bool* mouseDown() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseDown : nullptr; }\n"
193-
+ "};\n");
194-
sb.append(hoistedSb);
195-
sb.append("struct _PSketchImpl : public _PSketch {\n");
196-
sb.append("#line 1 \"sketch.pde\"\n");
197-
sb.append(sketchSb);
198-
sb.append("\n};\n");
199-
} else {
200-
// Static sketch: bare statements at file scope — wrap in a function
201-
sb.append("struct _PSketch : public PApplet {\n");
202-
sb.append("void setup() override {\n");
203-
sb.append("#line 1 \"sketch.pde\"\n");
204-
sb.append(code);
205-
sb.append("\n}\n};\n");
141+
// Use the real build pipeline to generate correct C++
142+
String lintSource;
143+
try {
144+
lintSource = CppBuild.generateSketchOutput(sketchCode.toString());
145+
} catch (Exception _genEx) {
146+
// Parse error -- nothing to lint
147+
return;
206148
}
207-
sb.append("} // namespace Processing\n");
208-
209149
tmp = Files.createTempFile("cppmode_lint_", ".cpp");
210-
Files.writeString(tmp, sb.toString());
150+
Files.writeString(tmp, lintSource);
211151

212152
// Build g++ command
213153
List<String> cmd = new ArrayList<>();

src/java/Parser.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,9 +1770,25 @@ private boolean looksLikeParamList() {
17701770
}
17711771
// Accept: named param, variadic pack "...", anonymous param ")", or
17721772
// reference/pointer-to-array: "int (&arr)[10]" -- ( follows the type
1773+
// Special case: single identifier in parens followed by ; or { is
1774+
// direct-init, not a param list. "Array<T> x(n);" -> variable, not fn.
1775+
if (checkPunct(")")) {
1776+
// Peek past the closing ) to see what follows
1777+
int closePos = pos + 1; // pos+1 because current is ")"
1778+
// anonymous param "void f(int)" is a param list
1779+
// but "Array<T> x(segments);" where next after ) is ; or { is NOT
1780+
// We already parsed the type -- if we consumed just one identifier
1781+
// and next is ), peek after ) for ; or { which means variable init
1782+
if (closePos < tokens.size()) {
1783+
String afterClose = tokens.get(closePos).text();
1784+
if (afterClose.equals(";") || afterClose.equals("{") ||
1785+
afterClose.equals("=") || afterClose.equals(","))
1786+
return false; // direct-init, not param list
1787+
}
1788+
return true; // anonymous param
1789+
}
17731790
return check(CppLexerTokenType.IDENTIFIER)
17741791
|| checkPunct("...") // variadic: "Args... args" or "T..."
1775-
|| checkPunct(")") // anonymous param: "void f(int)"
17761792
|| checkPunct(",") // next param after anonymous
17771793
|| checkPunct("("); // reference/pointer-to-array param: "int (&arr)[10]"
17781794
} finally {
@@ -2412,15 +2428,17 @@ private NameAndFunctionPointerType parseFunctionPointerDeclaratorTail(TypeRef re
24122428
}
24132429
expectPunct(")");
24142430
// Pointer-to-array or reference-to-array: "int (*p)[20]" / "int (&r)[20]"
2415-
// Encode array dims into name for CodeGen: "&refToRow[20]"
2431+
// Pointer-to-array: "float (*kernels[8])[3]" -- trailing [N] after )
2432+
// encodedName already has "__arr__[8]" from inside parens.
2433+
// Append "__pta__[N]" sentinel so CodeGen emits outer dims correctly.
24162434
if (checkPunct("[")) {
2417-
StringBuilder dimStr = new StringBuilder();
2435+
StringBuilder ptrDims = new StringBuilder();
24182436
while (checkPunct("[")) {
2419-
advance(); dimStr.append("[");
2420-
if (!checkPunct("]")) { dimStr.append(peek().text()); parseExpr(); }
2421-
expectPunct("]"); dimStr.append("]");
2437+
advance(); ptrDims.append("[");
2438+
if (!checkPunct("]")) { ptrDims.append(CodeGen.renderExpr(parseExpr())); }
2439+
expectPunct("]"); ptrDims.append("]");
24222440
}
2423-
return new NameAndFunctionPointerType(encodedName + dimStr.toString(),
2441+
return new NameAndFunctionPointerType(encodedName + "__pta__" + ptrDims.toString(),
24242442
new FunctionPointerType(returnType, List.of()));
24252443
}
24262444
expectPunct("(");

0 commit comments

Comments
 (0)