Skip to content

Commit ca96c65

Browse files
committed
Added a parser, temporal commit to fix previous version
1 parent 4bd66d1 commit ca96c65

4 files changed

Lines changed: 245 additions & 27 deletions

File tree

mode/CppMode.jar

601 Bytes
Binary file not shown.

scripts/generate_dragdrop.py

Lines changed: 168 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ def generate(out_dir: Path) -> None:
5555
run_sh.chmod(0o755)
5656
(out_dir / "run.bat").write_text(RUN_BAT)
5757

58+
# Covers this folder's own generated caches: lib/libprocessing_cpp.a
59+
# and include/Processing.h.gch. The .gch in particular is large
60+
# (100MB+) -- without this, anyone who runs `git add .` after their
61+
# first ./run.sh commits a 100MB+ binary straight into their repo
62+
# history. Doesn't cover .processing-cpp-build (the compiled sketch
63+
# binary), since that lands at the PROJECT root, one level above this
64+
# folder -- a .gitignore here can't reach it. The README tells the
65+
# user to add that line to their own project's .gitignore instead.
66+
(out_dir / ".gitignore").write_text(DRAGDROP_GITIGNORE)
67+
5868
# VS Code task. This can't be shipped directly as .vscode/tasks.json
5969
# inside this folder -- VS Code only reads .vscode/ at the workspace
6070
# ROOT, which is one level up (wherever the user's own main.cpp lives),
@@ -69,7 +79,7 @@ def generate(out_dir: Path) -> None:
6979
print(f"Generated drag-and-drop package: {out_dir}")
7080
print(f" include/ ({len(ENGINE_HEADERS)} headers), "
7181
f"src/ ({len(ENGINE_SOURCES)} engine source files), "
72-
f"README.md, run.sh, run.bat, vscode-task/, examples/, LICENSE")
82+
f"README.md, run.sh, run.bat, .gitignore, vscode-task/, examples/, LICENSE")
7383

7484

7585
# =============================================================================
@@ -107,13 +117,32 @@ def generate(out_dir: Path) -> None:
107117
108118
That's the entire workflow. `run.sh` finds your `.cpp` file, compiles it
109119
against the engine, links it, and runs the result, in one step. The first
110-
time you run it, it also compiles the engine itself, which takes about
111-
10-15 seconds; every run after that reuses the compiled engine and only
112-
has to rebuild your own file, so it's fast.
120+
time you run it, it also compiles the engine itself and precompiles
121+
`Processing.h`, which together take about 15-20 seconds; every run after
122+
that reuses both of those and only has to compile your own file, so it's
123+
fast -- well under a second on most machines.
113124
114125
On Windows, run `run.bat` the same way (from an MSYS2 shell, or by
115126
double-clicking it).
116127
128+
## If your project is a git repo
129+
130+
`run.sh` caches its build outputs in two places: `processing-cpp/lib/`
131+
and `processing-cpp/include/Processing.h.gch`. This folder already
132+
includes a `.gitignore` covering both of those, so they won't get
133+
committed if you run `git add processing-cpp`.
134+
135+
It can't cover `run.sh`'s other output, though: the compiled sketch
136+
itself, `.processing-cpp-build`, lands one level up, at the root of
137+
*your* project, not inside `processing-cpp/`. Add this line to your own
138+
project's `.gitignore`:
139+
140+
```
141+
.processing-cpp-build*
142+
```
143+
144+
(The trailing `*` catches `.processing-cpp-build.exe` on Windows too.)
145+
117146
## Writing a sketch
118147
119148
```cpp
@@ -186,6 +215,11 @@ def generate(out_dir: Path) -> None:
186215
Makefile, editor build task, or something other than `run.sh` -- you don't
187216
need to read this section to just use the library.
188217
218+
(`run.sh` itself does a little more than this -- it caches the compiled
219+
engine and a precompiled header for `Processing.h` so repeated runs are
220+
fast, as described above. Neither is required for a *working* build, only
221+
for a *fast* one; the command above is enough to get a sketch running.)
222+
189223
## Installing GLFW, GLEW, and a compiler
190224
191225
```sh
@@ -272,15 +306,54 @@ def generate(out_dir: Path) -> None:
272306
# ./processing-cpp/run.sh # auto-detects .cpp files next to this folder
273307
# ./processing-cpp/run.sh main.cpp app.cpp # explicit, e.g. a multi-file sketch
274308
#
275-
# The engine itself is compiled once and cached in processing-cpp/lib/ --
276-
# only your own file(s) get recompiled on later runs, unless the engine
277-
# source in this folder changes (e.g. you updated the package).
309+
# Two things get cached here so repeated runs stay fast, both rebuilt
310+
# automatically only when their inputs change:
311+
# - the engine itself, compiled once into processing-cpp/lib/
312+
# - a precompiled header for Processing.h, written next to it as
313+
# processing-cpp/include/Processing.h.gch (g++ only looks for a .gch
314+
# file in the SAME directory as the header it precompiles -- it
315+
# can't live in lib/ alongside the engine, even though that would
316+
# read more naturally)
317+
# Neither cache is required for correctness -- delete processing-cpp/lib/
318+
# and Processing.h.gch and the next run just rebuilds both from scratch.
278319
set -e
279320
280321
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
281322
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
282323
cd "$PROJECT_DIR"
283324
325+
if ! command -v g++ >/dev/null 2>&1; then
326+
echo "error: g++ not found on PATH." >&2
327+
echo "Install a C++ compiler, then try again -- see the \\"Dependencies\\" section of processing-cpp/README.md." >&2
328+
exit 1
329+
fi
330+
331+
# Wraps a build command so a failure gets a one-line, specific cause
332+
# instead of just whatever raw error g++/ld printed. Every g++/ar call in
333+
# this script goes through here -- the engine build is the first thing
334+
# that touches GLFW/GLEW (Processing.h includes both directly), so a
335+
# missing-dependency failure can show up there, in the PCH build, or in
336+
# the final compile, depending on what is/isn't already cached.
337+
run_build_step() {
338+
local description="$1"
339+
shift
340+
local output
341+
if ! output="$("$@" 2>&1)"; then
342+
echo "$output" >&2
343+
echo "" >&2
344+
if echo "$output" | grep -qE "GLFW/glfw3\\.h|GL/glew\\.h"; then
345+
echo "error: $description failed -- GLFW or GLEW headers not found." >&2
346+
echo "See the \\"Dependencies\\" section of processing-cpp/README.md to install them." >&2
347+
elif echo "$output" | grep -qE "cannot find -lglfw|cannot find -lGLEW"; then
348+
echo "error: $description failed -- GLFW or GLEW library not found at link time." >&2
349+
echo "See the \\"Dependencies\\" section of processing-cpp/README.md to install them." >&2
350+
else
351+
echo "error: $description failed. See the output above." >&2
352+
fi
353+
exit 1
354+
fi
355+
}
356+
284357
if [ "$#" -gt 0 ]; then
285358
SOURCES=("$@")
286359
else
@@ -303,19 +376,39 @@ def generate(out_dir: Path) -> None:
303376
case "$OS" in
304377
Darwin)
305378
GL_LIBS=(-lglfw -lGLEW -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo)
379+
COMPILE_FLAGS=()
306380
;;
307381
Linux)
308382
GL_LIBS=(-lglfw -lGLEW -lGL -lGLU -lm -pthread)
383+
# -pthread isn't just a link flag -- it also defines _REENTRANT
384+
# during compilation. The PCH build below has to see the same
385+
# define, or g++ rejects the cached .gch as stale and silently
386+
# re-parses Processing.h from source every time (caught by
387+
# actually building the package and checking with -Winvalid-pch
388+
# -- it doesn't show up as a build failure, just quietly stops
389+
# being fast).
390+
COMPILE_FLAGS=(-pthread)
309391
;;
310392
*)
311393
echo "error: unrecognized OS '$OS' -- on Windows, use run.bat instead" >&2
312394
exit 1
313395
;;
314396
esac
315397
398+
# DEFINES must be IDENTICAL between the PCH build below and the final
399+
# sketch compile further down, and so must COMPILE_FLAGS above -- g++
400+
# only uses a .gch if every relevant flag matches the compile it's being
401+
# considered for. A mismatch doesn't break the build, it just silently
402+
# falls back to parsing Processing.h from source (with a -Winvalid-pch
403+
# warning if you pass that flag to check), so the caching would quietly
404+
# stop helping. Defined once here so the two invocations can't drift
405+
# apart from each other.
406+
DEFINES=(-DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE)
407+
316408
ENGINE_DIR="$SCRIPT_DIR"
317409
LIB_DIR="$ENGINE_DIR/lib"
318410
LIB_A="$LIB_DIR/libprocessing_cpp.a"
411+
PCH_FILE="$ENGINE_DIR/include/Processing.h.gch"
319412
mkdir -p "$LIB_DIR"
320413
321414
NEED_ENGINE_BUILD=0
@@ -331,20 +424,36 @@ def generate(out_dir: Path) -> None:
331424
332425
if [ "$NEED_ENGINE_BUILD" -eq 1 ]; then
333426
echo "Compiling engine (first run, or engine source changed; ~10-15s)..."
334-
g++ -std=c++17 -O2 -c -I"$ENGINE_DIR/include" \\
335-
-DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE \\
336-
"$ENGINE_DIR/src/Processing.cpp" -o "$LIB_DIR/Processing.o"
337-
g++ -std=c++17 -O2 -c -I"$ENGINE_DIR/include" \\
338-
-DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE \\
339-
"$ENGINE_DIR/src/Processing_defaults.cpp" -o "$LIB_DIR/Processing_defaults.o"
340-
ar rcs "$LIB_A" "$LIB_DIR/Processing.o" "$LIB_DIR/Processing_defaults.o"
427+
run_build_step "engine compile" \\
428+
g++ -std=c++17 -O2 -c -I"$ENGINE_DIR/include" "${DEFINES[@]}" \\
429+
"$ENGINE_DIR/src/Processing.cpp" -o "$LIB_DIR/Processing.o"
430+
run_build_step "engine compile" \\
431+
g++ -std=c++17 -O2 -c -I"$ENGINE_DIR/include" "${DEFINES[@]}" \\
432+
"$ENGINE_DIR/src/Processing_defaults.cpp" -o "$LIB_DIR/Processing_defaults.o"
433+
run_build_step "engine archive" \\
434+
ar rcs "$LIB_A" "$LIB_DIR/Processing.o" "$LIB_DIR/Processing_defaults.o"
341435
rm -f "$LIB_DIR/Processing.o" "$LIB_DIR/Processing_defaults.o"
342436
fi
343437
438+
NEED_PCH_BUILD=0
439+
if [ ! -f "$PCH_FILE" ]; then
440+
NEED_PCH_BUILD=1
441+
elif [ "$ENGINE_DIR/include/Processing.h" -nt "$PCH_FILE" ]; then
442+
NEED_PCH_BUILD=1
443+
fi
444+
445+
if [ "$NEED_PCH_BUILD" -eq 1 ]; then
446+
echo "Precompiling Processing.h (first run, or it changed; speeds up every build after this one)..."
447+
run_build_step "header precompile" \\
448+
g++ -std=c++17 -I"$ENGINE_DIR/include" "${DEFINES[@]}" "${COMPILE_FLAGS[@]}" \\
449+
-x c++-header "$ENGINE_DIR/include/Processing.h" -o "$PCH_FILE"
450+
fi
451+
344452
OUT="$PROJECT_DIR/.processing-cpp-build"
345-
g++ -std=c++17 -I"$ENGINE_DIR/include" "${SOURCES[@]}" \\
346-
-L"$LIB_DIR" -lprocessing_cpp "${GL_LIBS[@]}" \\
347-
-o "$OUT"
453+
run_build_step "sketch compile" \\
454+
g++ -std=c++17 -I"$ENGINE_DIR/include" "${DEFINES[@]}" "${SOURCES[@]}" \\
455+
-L"$LIB_DIR" -lprocessing_cpp "${GL_LIBS[@]}" \\
456+
-o "$OUT"
348457
349458
echo "Running..."
350459
exec "$OUT"
@@ -360,6 +469,14 @@ def generate(out_dir: Path) -> None:
360469
REM Usage:
361470
REM processing-cpp\\run.bat
362471
REM processing-cpp\\run.bat main.cpp app.cpp
472+
REM
473+
REM Caches two things so repeated runs stay fast: the engine itself
474+
REM (processing-cpp\\lib\\libprocessing_cpp.a) and a precompiled header
475+
REM for Processing.h (processing-cpp\\include\\Processing.h.gch -- g++
476+
REM only looks for a .gch file next to the header it precompiles, so it
477+
REM has to live in include\\, not next to the engine in lib\\). Neither
478+
REM is required for correctness; delete both and the next run rebuilds
479+
REM them from scratch.
363480
setlocal enabledelayedexpansion
364481
365482
set "SCRIPT_DIR=%~dp0"
@@ -380,31 +497,62 @@ def generate(out_dir: Path) -> None:
380497
381498
echo Building: %SOURCES%
382499
500+
REM Must match the DEFINES used for the PCH build exactly, or g++ will
501+
REM reject the cached .gch as stale and silently fall back to parsing
502+
REM Processing.h from source every time (with a -Winvalid-pch warning).
503+
set "DEFINES=-DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE -D_USE_MATH_DEFINES"
504+
383505
set "ENGINE_DIR=%SCRIPT_DIR%"
384506
set "LIB_DIR=%ENGINE_DIR%lib"
385507
set "LIB_A=%LIB_DIR%\\libprocessing_cpp.a"
508+
set "PCH_FILE=%ENGINE_DIR%include\\Processing.h.gch"
386509
if not exist "%LIB_DIR%" mkdir "%LIB_DIR%"
387510
388511
set NEED_ENGINE_BUILD=0
389512
if not exist "%LIB_A%" set NEED_ENGINE_BUILD=1
390513
391514
if %NEED_ENGINE_BUILD%==1 (
392515
echo Compiling engine ^(first run; ~10-15s^)...
393-
g++ -std=c++17 -O2 -c -I"%ENGINE_DIR%include" -DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE -D_USE_MATH_DEFINES "%ENGINE_DIR%src\\Processing.cpp" -o "%LIB_DIR%\\Processing.o"
516+
g++ -std=c++17 -O2 -c -I"%ENGINE_DIR%include" %DEFINES% "%ENGINE_DIR%src\\Processing.cpp" -o "%LIB_DIR%\\Processing.o"
394517
if errorlevel 1 exit /b 1
395-
g++ -std=c++17 -O2 -c -I"%ENGINE_DIR%include" -DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE -D_USE_MATH_DEFINES "%ENGINE_DIR%src\\Processing_defaults.cpp" -o "%LIB_DIR%\\Processing_defaults.o"
518+
g++ -std=c++17 -O2 -c -I"%ENGINE_DIR%include" %DEFINES% "%ENGINE_DIR%src\\Processing_defaults.cpp" -o "%LIB_DIR%\\Processing_defaults.o"
396519
if errorlevel 1 exit /b 1
397520
ar rcs "%LIB_A%" "%LIB_DIR%\\Processing.o" "%LIB_DIR%\\Processing_defaults.o"
398521
del "%LIB_DIR%\\Processing.o" "%LIB_DIR%\\Processing_defaults.o"
399522
)
400523
401-
g++ -std=c++17 -I"%ENGINE_DIR%include" %SOURCES% -L"%LIB_DIR%" -lprocessing_cpp -lglfw3 -lglew32 -lopengl32 -lglu32 -lcomdlg32 -lshell32 -lole32 -luuid -mwindows -pthread -D_USE_MATH_DEFINES -o "%PROJECT_DIR%\\.processing-cpp-build.exe"
524+
set NEED_PCH_BUILD=0
525+
if not exist "%PCH_FILE%" set NEED_PCH_BUILD=1
526+
527+
if %NEED_PCH_BUILD%==1 (
528+
echo Precompiling Processing.h ^(first run; speeds up every build after this one^)...
529+
REM -pthread has to be here too, matching the final compile below --
530+
REM it defines _REENTRANT during compilation, not just at link time,
531+
REM so without it here g++ rejects this .gch as stale on every build
532+
REM and silently re-parses Processing.h from source instead (caught
533+
REM by actually building this package and checking with the
534+
REM -Winvalid-pch warning flag -- it's not a build failure, the
535+
REM caching just quietly stops working).
536+
g++ -std=c++17 -I"%ENGINE_DIR%include" %DEFINES% -pthread -x c++-header "%ENGINE_DIR%include\\Processing.h" -o "%PCH_FILE%"
537+
if errorlevel 1 exit /b 1
538+
)
539+
540+
g++ -std=c++17 -I"%ENGINE_DIR%include" %DEFINES% %SOURCES% -L"%LIB_DIR%" -lprocessing_cpp -lglfw3 -lglew32 -lopengl32 -lglu32 -lcomdlg32 -lshell32 -lole32 -luuid -mwindows -pthread -o "%PROJECT_DIR%\\.processing-cpp-build.exe"
402541
if errorlevel 1 exit /b 1
403542
404543
echo Running...
405544
"%PROJECT_DIR%\\.processing-cpp-build.exe"
406545
'''
407546

547+
DRAGDROP_GITIGNORE = '''\
548+
# Generated by run.sh / run.bat -- safe to delete, will be rebuilt
549+
# automatically on the next run. Not meant to be committed: the engine
550+
# archive is rebuilt in seconds, and the precompiled header in
551+
# particular is 100MB+, which is a bad thing to put in git history.
552+
lib/
553+
include/Processing.h.gch
554+
'''
555+
408556
VSCODE_TASKS_JSON = '''\
409557
{
410558
// Lets you build-and-run with Ctrl+Shift+B (Cmd+Shift+B on macOS)

src/java/CodeGen.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ private static void emitVariableDecl(StringBuilder sb, VariableDecl vd, int dept
192192
}
193193
sb.append(renderTypeAndName(vd.type(), vd.name()));
194194
emitArrayDims(sb, vd.arrayDims());
195-
emitDeclaratorTail(sb, vd.name(), vd.initializer());
195+
emitDeclaratorTail(sb, vd.type(), vd.name(), vd.initializer());
196196
sb.append(";\n");
197197
}
198198

@@ -244,10 +244,40 @@ private static void emitArrayDims(StringBuilder sb, List<Expr> dims) {
244244
* different properties -- this was caught by reading the output, not
245245
* by an automated check, and a real test for it was added after.
246246
*/
247-
private static void emitDeclaratorTail(StringBuilder sb, String declaratorName, Expr initializer) {
247+
/**
248+
* Standard-library container/wrapper types with a real
249+
* initializer_list constructor, meaning brace-init and paren-init
250+
* are NOT interchangeable -- they have genuinely different
251+
* SEMANTICS. Found as a real bug via a real user-reported sketch
252+
* (Wolfram.pde): "std::vector<int> nextgen(cells.size(), 0);" was
253+
* being unconditionally brace-wrapped into
254+
* "std::vector<int> nextgen{cells.size(), 0};" -- confirmed via g++
255+
* that these produce different .size() results (5 vs 2 for
256+
* "(5, 0)" vs "{5, 0}"). The most-vexing-parse protection this
257+
* brace-wrapping exists for only matters for actual user-defined
258+
* class types; a template instantiation like "std::vector<int>" was
259+
* never at risk of that ambiguity (confirmed via g++: no competing
260+
* function-declaration interpretation exists for it).
261+
*/
262+
private static final java.util.Set<String> INITIALIZER_LIST_AMBIGUOUS_TYPES = java.util.Set.of(
263+
"vector", "std::vector",
264+
"string", "std::string",
265+
"wstring", "std::wstring",
266+
"array", "std::array",
267+
"deque", "std::deque",
268+
"list", "std::list",
269+
"set", "std::set",
270+
"map", "std::map",
271+
"unordered_set", "std::unordered_set",
272+
"unordered_map", "std::unordered_map",
273+
"initializer_list", "std::initializer_list"
274+
);
275+
276+
private static void emitDeclaratorTail(StringBuilder sb, TypeRef declaratorType, String declaratorName, Expr initializer) {
248277
if (initializer == null) return;
249278
if (initializer instanceof CallExpr ce && ce.callee() instanceof Identifier id
250-
&& id.name().equals(declaratorName)) {
279+
&& id.name().equals(declaratorName)
280+
&& !(declaratorType instanceof NamedType nt && INITIALIZER_LIST_AMBIGUOUS_TYPES.contains(nt.baseName()))) {
251281
sb.append('{');
252282
for (int i = 0; i < ce.args().size(); i++) {
253283
if (i > 0) sb.append(", ");
@@ -256,6 +286,15 @@ private static void emitDeclaratorTail(StringBuilder sb, String declaratorName,
256286
sb.append('}');
257287
return;
258288
}
289+
if (initializer instanceof CallExpr ce2 && ce2.callee() instanceof Identifier id2 && id2.name().equals(declaratorName)) {
290+
sb.append('(');
291+
for (int i = 0; i < ce2.args().size(); i++) {
292+
if (i > 0) sb.append(", ");
293+
sb.append(renderExpr(ce2.args().get(i)));
294+
}
295+
sb.append(')');
296+
return;
297+
}
259298
sb.append(" = ").append(renderExpr(initializer));
260299
}
261300

@@ -356,7 +395,7 @@ private static void emitStmt(StringBuilder sb, Statement s, int depth) {
356395
}
357396
sb.append(renderTypeAndName(ds.type(), ds.name()));
358397
emitArrayDims(sb, ds.arrayDims());
359-
emitDeclaratorTail(sb, ds.name(), ds.initializer());
398+
emitDeclaratorTail(sb, ds.type(), ds.name(), ds.initializer());
360399
sb.append(";\n");
361400
} else if (s instanceof ExprStatement es) {
362401
indent(sb, depth);
@@ -444,7 +483,7 @@ private static void emitForStmt(StringBuilder sb, ForStatement s, int depth) {
444483
}
445484
sb.append(renderTypeAndName(ds.type(), ds.name()));
446485
emitArrayDims(sb, ds.arrayDims());
447-
emitDeclaratorTail(sb, ds.name(), ds.initializer());
486+
emitDeclaratorTail(sb, ds.type(), ds.name(), ds.initializer());
448487
} else if (s.init() instanceof ExprStatement es) {
449488
sb.append(renderExpr(es.expr()));
450489
}

0 commit comments

Comments
 (0)