Skip to content

Commit 97bd6cd

Browse files
committed
Parser: fix trailing declarator bug eating top-level declarations; linter: hoist user classes, add free env vars
1 parent 2d9c6d9 commit 97bd6cd

3 files changed

Lines changed: 101 additions & 10 deletions

File tree

src/Processing_api.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,24 @@ inline void exit_sketch() { if(::Processing::PApple
183183
//
184184
// For now we define them since most sketches need width/height as globals.
185185
// PImage members can be accessed via the struct directly: (*img).height
186-
// or by temporarily undefining: #undef height ...
186+
// or by temporarily undefining: #undef height ...
187+
// ── Environment variables as free values ─────────────────────────────────────
188+
// These let user-defined classes (hoisted outside _PSketch) access
189+
// width/height/mouseX/mouseY/frameCount etc. as free variables.
190+
namespace {
191+
struct _FreeWidth { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->logicalW : 0; } } width;
192+
struct _FreeHeight { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->logicalH : 0; } } height;
193+
struct _FreeMouseX { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseX : 0.f; } } mouseX;
194+
struct _FreeMouseY { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseY : 0.f; } } mouseY;
195+
struct _FreePMouseX { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->pmouseX : 0.f; } } pmouseX;
196+
struct _FreePMouseY { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->pmouseY : 0.f; } } pmouseY;
197+
struct _FreeFC { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->frameCount : 0; } } frameCount;
198+
struct _FreeKey { operator char() const { return ::Processing::PApplet::g_papplet ? (char)::Processing::PApplet::g_papplet->key : 0; } } key;
199+
struct _FreeKeyCode { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->keyCode : 0; } } keyCode;
200+
struct _FreeMB { operator int() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseButton : 0; } } mouseButton;
201+
struct _FreeMP { operator bool() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_mousePressed : false; } } mousePressed;
202+
struct _FreeKP { operator bool() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_keyPressed : false; } } keyPressed;
203+
struct _FreeFR { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_frameRate : 0.f; } } frameRate;
204+
struct _FreeMDX { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseDX : 0.f; } } mouseDX;
205+
struct _FreeMDY { operator float() const { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseDY : 0.f; } } mouseDY;
206+
} // namespace

src/java/CppLinter.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,51 @@ private void runCheck(Sketch sketch, int currentTab, String liveText) {
151151
sb.append("using namespace std;\n");
152152
sb.append("namespace Processing {\n");
153153
sb.append("using namespace std;\n");
154-
sb.append("struct _PSketch : public PApplet {\n");
155-
sb.append("#line 1 \"sketch.pde\"\n");
156-
sb.append(code);
157-
sb.append("\n};\n");
154+
boolean hasSetup = code.contains("void setup(") || code.contains("void draw(");
155+
if (hasSetup) {
156+
sb.append("#include \"Processing_api.h\"\n");
157+
// Hoist top-level class/struct/union definitions before _PSketch
158+
// so their methods can call Processing API free functions.
159+
StringBuilder hoisted = new StringBuilder();
160+
StringBuilder rest = new StringBuilder();
161+
String[] lintLines = code.split("\n", -1);
162+
int li = 0;
163+
while (li < lintLines.length) {
164+
String lt = lintLines[li].strip();
165+
if ((lt.startsWith("class ") || lt.startsWith("struct ") || lt.startsWith("union "))
166+
&& !lt.endsWith(";")) {
167+
// Consume until matching closing };
168+
int depth = 0; boolean found = false;
169+
StringBuilder block = new StringBuilder();
170+
while (li < lintLines.length) {
171+
String bl = lintLines[li];
172+
block.append(bl).append("\n");
173+
for (char ch : bl.toCharArray()) {
174+
if (ch == '{') depth++;
175+
else if (ch == '}') { depth--; if (depth == 0) found = true; }
176+
}
177+
li++;
178+
if (found) { if (li < lintLines.length && lintLines[li].strip().equals(";")) { block.append(lintLines[li]).append("\n"); li++; } break; }
179+
}
180+
hoisted.append(block);
181+
} else {
182+
rest.append(lintLines[li]).append("\n");
183+
li++;
184+
}
185+
}
186+
sb.append(hoisted);
187+
sb.append("struct _PSketch : public PApplet {\n");
188+
sb.append("#line 1 \"sketch.pde\"\n");
189+
sb.append(rest);
190+
sb.append("\n};\n");
191+
} else {
192+
// Static sketch: bare statements at file scope — wrap in a function
193+
sb.append("struct _PSketch : public PApplet {\n");
194+
sb.append("void setup() override {\n");
195+
sb.append("#line 1 \"sketch.pde\"\n");
196+
sb.append(code);
197+
sb.append("\n}\n};\n");
198+
}
158199
sb.append("} // namespace Processing\n");
159200

160201
tmp = Files.createTempFile("cppmode_lint_", ".cpp");

src/java/Parser.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,16 @@ private List<TopLevelItem> parseTopLevelItem(List<CppLexerToken> leadingComments
551551
}
552552
}
553553

554+
// Bare statement at top level: control-flow keywords can only be statements,
555+
// never declarations. Route directly to statement parsing (static-mode sketches).
556+
if (checkKeyword("for") || checkKeyword("while") || checkKeyword("do") ||
557+
checkKeyword("if") || checkKeyword("switch") || checkKeyword("return") ||
558+
checkKeyword("break") || checkKeyword("continue") ||
559+
checkKeyword("try") || checkKeyword("throw")) {
560+
CppLexerToken start = peek();
561+
Statement stmt = parseStatement(leadingComments);
562+
return List.of(new TopLevelStatement(stmt, start.line(), start.col(), leadingComments));
563+
}
554564
return parseFunctionOrVariable(leadingComments, templateParams, true);
555565
}
556566

@@ -866,11 +876,9 @@ else if (checkOp(">>")) {
866876
// "class HScrollbar { ... }" has no trailing ';' at all. Processing's
867877
// own preprocessing tolerates this; this parser does too rather than
868878
// hard-requiring strict C++ grammar here.
869-
matchPunct(";");
870-
871879
TypeDef typeDef = new TypeDef(kind, name, templateParams, baseClasses, members, start.line(), start.col(), leadingComments);
872880
// Trailing declarators: "class Foo { ... } *ptr;" or "} a, b, *c;"
873-
// Only enter if next token is an identifier or pointer star, not a keyword.
881+
// Only enter if there is NO trailing semicolon yet.
874882
boolean nextIsDeclarator = checkOp("*") || checkPunct("*")
875883
|| peek().type() == CppLexerTokenType.IDENTIFIER;
876884
if (!matchPunct(";") && !isAtEnd() && !checkPunct("}") && nextIsDeclarator) {
@@ -1519,8 +1527,30 @@ private boolean looksLikeTopLevelDeclarationOrFunction() {
15191527
|| checkKeyword("operator")) {
15201528
return true;
15211529
}
1522-
// Constructor: type name followed by ( -- "RGBA(...)" inside struct RGBA
1523-
if (checkPunct("(")) return true;
1530+
// If the parsed type has template args (e.g. ArrayList<Ball>),
1531+
// it is unambiguously a declaration — template types are never bare calls.
1532+
if (type instanceof NamedType nt2 && !nt2.templateArgs().isEmpty()) return true;
1533+
// Constructor or bare call: type name followed by (
1534+
// Disambiguate: scan ahead to find matching ) then check next token.
1535+
// If next is { or : it's a constructor/function definition.
1536+
// If next is ; or another statement-like token it's a bare call.
1537+
if (checkPunct("(")) {
1538+
int scan = pos + 1; int depth = 1;
1539+
while (scan < tokens.size() && depth > 0) {
1540+
if (tokens.get(scan).isPunct("(")) depth++;
1541+
else if (tokens.get(scan).isPunct(")")) depth--;
1542+
scan++;
1543+
}
1544+
// scan is now after the closing )
1545+
if (scan < tokens.size()) {
1546+
String after = tokens.get(scan).text();
1547+
// Function/constructor definition: followed by { or :
1548+
if (after.equals("{") || after.equals(":")) return true;
1549+
// Bare call statement: followed by ; or next statement
1550+
return false;
1551+
}
1552+
return true;
1553+
}
15241554
return check(CppLexerTokenType.IDENTIFIER);
15251555
} finally {
15261556
pos = save;

0 commit comments

Comments
 (0)