Skip to content

Commit 17ed6af

Browse files
committed
Parser/CodeGen/Build fixes: fn-ptr params, template defaults, bitfields, ref-qualifiers, throw-expr, Windows wizard
Parser fixes: - fn-ptr params with defaults: float (*fn)(float) = nullptr now parsed correctly (ref-to-array branch discriminates on ( vs [ after (*name)) - encode fn-ptr param as name__fnptr__(sig) for CodeGen round-trip - template default values: separate parenDepth/angleDepth so > inside (sizeof(T) > 2 ? 8 : 4) no longer closes the template arg list early - explicit empty <> now stores sentinel NamedType('<>') so CodeGen emits <> instead of bare name (Ec06Buf<> ec06_b was emitting Ec06Buf ec06_b) - array-new value-init: new float[n]() trailing () now consumed - trailing & and && ref-qualifiers on member functions now consumed - bitfield width: named (int x : 4) and unnamed (int : 2) now parsed; unnamed handled by returning empty name before expectIdentifier() throws - throw as expression in ternary else branch: parseTernary() now handles throw before delegating to parseLogicalOr() - looksLikeTemplateArgList: bare ? at depth 0 now rejects speculation (was treating v < lo ? lo : v > hi as a template instantiation) CodeGen fixes: - fn-ptr param emission: decodes name__fnptr__(sig) -> rettype (*name)(sig) - array-of-fn-ptr dims: void (*ops[4])(int) now emits [4] inside parens (was emitting void (*ops)[4] treating it as ptr-to-array) - float literal suffix: L/l suffix now excluded from f-append (180.0L was becoming 180.0Lf breaking long double literals) CppBuild fixes: - E0005b false positive: Array<T>.get().field no longer triggers E0005 (now requires positive ArrayList confirmation before firing) - Windows wizard: PATH g++ no longer trusted on Windows; only MSYS2 mingw64 paths accepted (Git for Windows g++ would pass detection but fail at link time due to missing GLFW/GLEW) - InstallWizard: same fix -- detectWindowsMissing() now checks only known MSYS2 paths, not commandExists('g++') which hits PATH Other: - rename drag-and-drop to plug-and-play throughout (zip, scripts) - rebuild-jar.py now syncs jar to Processing bundled mode dirs
1 parent ff997b1 commit 17ed6af

11 files changed

Lines changed: 283 additions & 96 deletions

CppMode.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ url=https://github.com/processing-cpp/processing.cpp
55
sentence=Write and run Processing sketches in C++.
66
paragraph=C++ Mode lets you write sketches using the familiar Processing API — size(), ellipse(), mouseX, draw() — but compiled to a native binary via g++. Runs on Linux and Windows (via MSYS2). Requires g++ and OpenGL (GLFW + GLEW).
77
version=8
8-
prettyVersion=0.2.3
8+
prettyVersion=0.3.0
99
minRevision=1280
1010
maxRevision=0

deploy_processing_h.py

Lines changed: 0 additions & 36 deletions
This file was deleted.

mode.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ url=https://github.com/processing-cpp/processing.cpp
55
sentence=Write and run Processing sketches in C++.
66
paragraph=C++ Mode lets you write sketches using the familiar Processing API — size(), ellipse(), mouseX, draw() — but compiled to a native binary via g++. Runs on Linux, macOS, and Windows (via MSYS2). Requires g++ and OpenGL (GLFW + GLEW).
77
version=8
8-
prettyVersion=0.2.3
8+
prettyVersion=0.3.0
99
minRevision=1280
1010
maxRevision=0

mode/CppMode.jar

1.69 KB
Binary file not shown.
0 Bytes
Binary file not shown.
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
"""
3-
generate_dragdrop.py -- builds the drag-and-drop release of processing-cpp:
3+
generate_plugandplay.py -- builds the drag-and-drop release of processing-cpp:
44
a zip containing the engine, two examples, and a README that only ever
55
shows plain g++ commands. No build system assumed.
66
@@ -14,9 +14,9 @@
1414
a separate release built for that audience, not a flag on this one.
1515
1616
Usage:
17-
scripts/generate_dragdrop.py # writes dist/processing-cpp-dragdrop.zip
18-
scripts/generate_dragdrop.py --no-zip # leave the folder unzipped, for inspection
19-
scripts/generate_dragdrop.py --out PATH # write the zip somewhere else
17+
scripts/generate_plugandplay.py # writes dist/processing-cpp-plugandplay.zip
18+
scripts/generate_plugandplay.py --no-zip # leave the folder unzipped, for inspection
19+
scripts/generate_plugandplay.py --out PATH # write the zip somewhere else
2020
2121
dist/ is gitignored -- this is a generated release artifact, not
2222
something to hand-edit or commit.
@@ -78,7 +78,7 @@ def generate(out_dir: Path) -> None:
7878
shutil.copy2(REPO_ROOT / "LICENSE", out_dir / "LICENSE")
7979

8080
version = get_library_version()
81-
print(f"Generated drag-and-drop package: {out_dir}")
81+
print(f"Generated plug-and-play package: {out_dir}")
8282
print(f" version {version}")
8383
print(f" include/ ({len(ENGINE_HEADERS)} headers), "
8484
f"src/ ({len(ENGINE_SOURCES)} engine source files), "
@@ -92,7 +92,7 @@ def generate(out_dir: Path) -> None:
9292
# =============================================================================
9393

9494
README_MD = '''\
95-
# processing-cpp (drag-and-drop)
95+
# processing-cpp (plug-and-play)
9696
9797
This is [Processing](https://processing.org)'s API -- `size()`, `ellipse()`,
9898
`mouseX`, `draw()`, and the rest of it -- implemented natively in C++.
@@ -686,7 +686,7 @@ def main() -> None:
686686
help="Where to write the release. A directory ending in .zip writes "
687687
"a zip there; anything else is treated as a folder to write "
688688
"unzipped, as if --no-zip were also given. "
689-
"Default: dist/processing-cpp-dragdrop.zip"
689+
"Default: dist/processing-cpp-plugandplay.zip"
690690
)
691691
parser.add_argument(
692692
"--no-zip", action="store_true",
@@ -701,8 +701,8 @@ def main() -> None:
701701
folder = args.out
702702
zip_path = None
703703
else:
704-
folder = DIST_DIR / "processing-cpp-dragdrop"
705-
zip_path = DIST_DIR / "processing-cpp-dragdrop.zip"
704+
folder = DIST_DIR / "processing-cpp-plugandplay"
705+
zip_path = DIST_DIR / "processing-cpp-plugandplay.zip"
706706

707707
generate(folder)
708708

scripts/rebuild-jar.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,14 @@ def main():
7777

7878
if __name__ == "__main__":
7979
main()
80+
81+
# Sync to Processing bundled mode dirs so IDE picks up changes immediately
82+
import shutil, pathlib
83+
jar = pathlib.Path("/home/pep/sketchbook/modes/CppMode/mode/CppMode.jar")
84+
for dest in [
85+
"/home/pep/Projects/processing4/app/build/resources-bundled/common/modes/CppMode/mode/CppMode.jar",
86+
"/home/pep/Projects/processing4/app/build/compose/tmp/prepareAppResources/modes/CppMode/mode/CppMode.jar",
87+
]:
88+
pathlib.Path(dest).parent.mkdir(parents=True, exist_ok=True)
89+
shutil.copy2(jar, dest)
90+
print(f"Synced to {dest}")

src/java/CodeGen.java

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,6 @@ private static String renderTypeAndName(TypeRef type, String name) {
249249
if (type instanceof FunctionPointerType fpt) {
250250
boolean isConst = name.endsWith("__const__");
251251
if (isConst) name = name.substring(0, name.length() - 9);
252-
String dimSuffix = "";
253-
int dimIdx = name.indexOf("[");
254-
if (dimIdx >= 0) { dimSuffix = name.substring(dimIdx); name = name.substring(0, dimIdx); }
255252
int colonIdx = name.indexOf("::");
256253
String classPrefix = "";
257254
String ptrChar = "*";
@@ -264,12 +261,31 @@ private static String renderTypeAndName(TypeRef type, String name) {
264261
else { bareNamePart = rest; }
265262
} else if (name.startsWith("&")) { ptrChar = "&"; bareNamePart = name.substring(1); }
266263
else if (name.startsWith("*")) { ptrChar = "*"; bareNamePart = name.substring(1); }
264+
265+
// Split bare name into identifier and optional subscript dims:
266+
// "ec02_ops[4]" -> bareName="ec02_ops", innerDims="[4]", outerDims=""
267+
// "pArr[10]" -> same (array-of-fn-ptr: dims stay inside parens)
268+
// For ptr-to-array "refToRow[20]" the dims come AFTER the param list,
269+
// but the parser encodes that case with an empty paramTypes list AND
270+
// appends dims to the name -- so we detect: empty paramTypes = ptr-to-array.
271+
String innerDims = ""; // dims inside (*name[N]) -- array of fn-ptrs
272+
String outerDims = ""; // dims after (params) -- ptr to array
273+
int dimIdx = bareNamePart.indexOf("[");
274+
if (dimIdx >= 0) {
275+
String dims = bareNamePart.substring(dimIdx);
276+
bareNamePart = bareNamePart.substring(0, dimIdx);
277+
if (fpt.paramTypes().isEmpty()) {
278+
outerDims = dims; // ptr-to-array: "void (*p)[20]"
279+
} else {
280+
innerDims = dims; // array-of-fn-ptr: "void (*arr[4])(int)"
281+
}
282+
}
267283
StringBuilder sb = new StringBuilder(renderTypeRef(fpt.returnType()));
268-
sb.append(" (").append(classPrefix).append(ptrChar).append(bareNamePart);
269-
if (!dimSuffix.isEmpty()) {
270-
sb.append(")").append(dimSuffix);
284+
sb.append(" (").append(classPrefix).append(ptrChar).append(bareNamePart).append(innerDims).append(")");
285+
if (!outerDims.isEmpty()) {
286+
sb.append(outerDims);
271287
} else {
272-
sb.append(")(");
288+
sb.append("(");
273289
for (int i = 0; i < fpt.paramTypes().size(); i++) {
274290
if (i > 0) sb.append(", ");
275291
sb.append(renderTypeRef(fpt.paramTypes().get(i)));
@@ -283,6 +299,10 @@ private static String renderTypeAndName(TypeRef type, String name) {
283299
}
284300

285301
private static void emitVariableDecl(StringBuilder sb, VariableDecl vd, int depth) {
302+
// Unnamed bitfield "unsigned int : 2" -- parser stores name="" after consuming
303+
// the width specifier. Skip emission entirely; the bit width info is discarded
304+
// (CppMode doesn't model bitfields in the AST) but the struct compiles cleanly.
305+
if (vd.name() == null || vd.name().isEmpty()) return;
286306
indent(sb, depth);
287307
if (!vd.templateParams().isEmpty()) {
288308
sb.append("template<");
@@ -294,6 +314,7 @@ private static void emitVariableDecl(StringBuilder sb, VariableDecl vd, int dept
294314
indent(sb, depth);
295315
}
296316
if (vd.isStatic()) sb.append("static ");
317+
// Emit constexpr when explicitly marked, or for static const members
297318
// Static const non-integral members need constexpr in C++
298319
if (vd.isConst() && vd.isStatic()) sb.append("constexpr ");
299320
// Only emit const here if the type itself doesn't already carry it and not already constexpr
@@ -626,6 +647,19 @@ private static void emitParamList(StringBuilder sb, List<Param> params) {
626647
if (isConst) sigPart = sigPart.substring(0, sigPart.length() - 9);
627648
sb.append(retType).append(" (").append(mpPart).append(")").append(sigPart);
628649
if (isConst) sb.append(" const");
650+
} else if (p.name().contains("__fnptr__")) {
651+
// Fn-ptr param encoded by parser as "name__fnptr__(paramtypes)"
652+
// e.g. "fn__fnptr__(float)" -> "float (*fn)(float)"
653+
int fnIdx = p.name().indexOf("__fnptr__");
654+
String fnName = p.name().substring(0, fnIdx);
655+
String fnSig = p.name().substring(fnIdx + 9); // "(float)" etc.
656+
// Undo the pointerDepth bump the parser added to carry return type
657+
TypeRef fnPtrRetType = p.type();
658+
if (fnPtrRetType instanceof NamedType nt && nt.pointerDepth() > 0) {
659+
fnPtrRetType = new NamedType(nt.baseName(), nt.templateArgs(),
660+
nt.pointerDepth() - 1, nt.isReference(), nt.isConst(), false);
661+
}
662+
sb.append(renderTypeRef(fnPtrRetType)).append(" (*").append(fnName).append(")").append(fnSig);
629663
} else {
630664
sb.append(renderTypeAndName(p.type(), p.name()));
631665
}
@@ -849,7 +883,13 @@ public static String renderTypeRef(TypeRef t) {
849883
sb.append('<');
850884
for (int i = 0; i < nt.templateArgs().size(); i++) {
851885
if (i > 0) sb.append(", ");
852-
sb.append(renderTypeRef(nt.templateArgs().get(i)));
886+
TypeRef ta = nt.templateArgs().get(i);
887+
// Sentinel for explicit empty "<>": a NamedType whose baseName is "<>"
888+
if (ta instanceof NamedType sta && sta.baseName().equals("<>")) {
889+
// emit nothing -- the "<>" wrapper is handled below
890+
break;
891+
}
892+
sb.append(renderTypeRef(ta));
853893
}
854894
sb.append('>');
855895
}
@@ -892,7 +932,8 @@ public static String renderExpr(Expr e) {
892932
// double/float ambiguity on overloaded Processing API functions
893933
if (lit.kind() == Literal.Kind.FLOAT
894934
&& !txt.endsWith("f") && !txt.endsWith("F")
895-
&& !txt.endsWith("d") && !txt.endsWith("D")) {
935+
&& !txt.endsWith("d") && !txt.endsWith("D")
936+
&& !txt.endsWith("l") && !txt.endsWith("L")) {
896937
txt = txt + "f";
897938
}
898939
return txt;

src/java/CppBuild.java

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,9 @@ private void checkGppAvailable(RunnerListener listener) throws Exception {
252252
System.getProperty("user.home") + "\\msys64\\mingw64\\bin\\g++.exe",
253253
"C:\\msys64\\ucrt64\\bin\\g++.exe"})
254254
if (new File(p).exists()) return;
255-
try { if (Runtime.getRuntime().exec(new String[]{"g++","--version"}).waitFor()==0) return; }
256-
catch (Exception ignored) {}
257-
258-
// Try the guided installer first. If it succeeds, we're done. If the
259-
// user cancels, InstallWizard throws CancelledByUser, which stops
260-
// the build entirely instead of falling through. Only a genuine
261-
// wizard failure (not a cancel) falls through to the dialog below.
255+
// Do NOT fall back to PATH g++ -- it won't have GLFW/GLEW and will
256+
// produce linker errors. Only MSYS2 mingw64 g++ is accepted on Windows.
257+
// Run the wizard unconditionally if MSYS2 g++ not found at known paths.
262258
if (InstallWizard.run(listener)) return;
263259

264260
Object[] opts = { "Install Automatically", "Download MSYS2", "Cancel" };
@@ -2400,6 +2396,10 @@ private void checkForArrayListGetValueCopy(String code, RunnerListener listener)
24002396
}
24012397
// [E0005b] Detect inline .get().field pattern: particles.get(0).x
24022398
// This is a pointer dereference error -- should use -> not .
2399+
// Only fires for ArrayList (pointer-storage), not Array (value-storage).
2400+
// Distinguish by looking at the token before .get(): ArrayList<T> uses
2401+
// pointer storage and get() returns T*; Array<T> uses value storage and
2402+
// get() returns T by value, so .field is correct there.
24032403
private void checkForArrayListGetDotAccess(String code, RunnerListener listener) {
24042404
List<CppLexerToken> tokens;
24052405
try { tokens = new CppLexer(code).tokenize(); } catch (Exception e) { return; }
@@ -2408,6 +2408,53 @@ private void checkForArrayListGetDotAccess(String code, RunnerListener listener)
24082408
if (!tokens.get(i).isPunct(".")) continue;
24092409
if (!tokens.get(i + 1).text().equals("get")) continue;
24102410
if (!tokens.get(i + 2).isPunct("(")) continue;
2411+
// Only fire E0005b when we can positively identify the receiver as ArrayList
2412+
// (pointer-storage). Array<T> is value-storage and get() returns T by value,
2413+
// so .field is correct there. Since we have no symbol table, we can't resolve
2414+
// arbitrary expression types -- so we require a positive signal: the variable
2415+
// name immediately before .get( must appear in a nearby ArrayList<...> declaration.
2416+
// If we can't confirm ArrayList, skip -- false negatives are safer than false
2417+
// positives that block valid code.
2418+
{
2419+
// Find the identifier token immediately before this ".get("
2420+
// It may be: "name.get(" or "name[i].get(" or "fn().get(" etc.
2421+
// Walk back to find the base name token.
2422+
int back = i - 1;
2423+
while (back > 0 && (tokens.get(back).isPunct("]") || tokens.get(back).isPunct(")"))) {
2424+
String closeStr = tokens.get(back).text();
2425+
String openStr = closeStr.equals("]") ? "[" : "(";
2426+
int bd = 1; back--;
2427+
while (back >= 0 && bd > 0) {
2428+
if (tokens.get(back).text().equals(closeStr)) bd++;
2429+
else if (tokens.get(back).text().equals(openStr)) bd--;
2430+
back--;
2431+
}
2432+
}
2433+
String baseName = (back >= 0 && tokens.get(back).type() == CppLexerTokenType.IDENTIFIER)
2434+
? tokens.get(back).text() : null;
2435+
// Require positive confirmation: scan nearby tokens for "ArrayList < ... > baseName"
2436+
boolean confirmedArrayList = false;
2437+
if (baseName != null) {
2438+
for (int k = Math.max(0, i - 40); k < Math.min(tokens.size(), i + 5); k++) {
2439+
if (tokens.get(k).text().equals("ArrayList")
2440+
&& k + 1 < tokens.size() && tokens.get(k+1).isOp("<")) {
2441+
// Scan forward past the template args to find the variable name
2442+
int m = k + 2; int ad = 1;
2443+
while (m < tokens.size() && ad > 0) {
2444+
if (tokens.get(m).isOp("<")) ad++;
2445+
else if (tokens.get(m).isOp(">") || tokens.get(m).isOp(">>")) ad--;
2446+
m++;
2447+
}
2448+
// m now points just after ">", optionally "*", then the variable name
2449+
while (m < tokens.size() && tokens.get(m).isOp("*")) m++;
2450+
if (m < tokens.size() && tokens.get(m).text().equals(baseName)) {
2451+
confirmedArrayList = true; break;
2452+
}
2453+
}
2454+
}
2455+
}
2456+
if (!confirmedArrayList) continue; // can't confirm ArrayList -- skip
2457+
}
24112458
// Consume the get(...) args
24122459
int j = i + 3; int depth = 1;
24132460
while (j < tokens.size() && depth > 0) {

0 commit comments

Comments
 (0)