Skip to content

Commit f82ef48

Browse files
committed
GCC-style error formatter with source context and caret
1 parent 5f8cf30 commit f82ef48

7 files changed

Lines changed: 253 additions & 144 deletions

File tree

src/Processing.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,10 @@ void PApplet::fullScreen() {
587587
glfwSetWindowMonitor(gWindow, m, 0, 0, v->width, v->height, v->refreshRate);
588588
}
589589
}
590+
void PApplet::fullScreen(int mode) {
591+
size(displayWidth, displayHeight, mode);
592+
fullScreen();
593+
}
590594
void PApplet::frameRate(int fps){ targetFrameTime = 1.0/fps; }
591595
void PApplet::noLoop(){looping=false;}
592596
void PApplet::loop() {looping=true;}

src/java/AstPasses.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,28 @@ public static Result hoist(List<TopLevelItem> items) {
217217
}
218218
}
219219

220-
// --- PHASE 2: hoist every array declaration, unconditionally ---
220+
// --- PHASE 2: hoist array declarations, but only for primitive/known types ---
221+
// Collect user-defined type names (structs/classes/enums) so we don't
222+
// hoist arrays of those before their definition appears.
223+
java.util.Set<String> userTypes = new java.util.HashSet<>();
224+
java.util.Set<String> primitives = java.util.Set.of(
225+
"int","float","double","bool","char","long","short","byte",
226+
"color","unsigned","signed","size_t","uint8_t","uint16_t",
227+
"uint32_t","int8_t","int16_t","int32_t","string","String");
228+
for (TopLevelItem item : afterPhase1) {
229+
if (item instanceof TypeDef td) userTypes.add(td.name());
230+
}
221231
for (TopLevelItem item : afterPhase1) {
222232
if (item instanceof VariableDecl vd && !vd.arrayDims().isEmpty()) {
223-
result.hoistedArrays.add(vd);
233+
String baseType = vd.type() instanceof NamedType nt ? nt.baseName() : "";
234+
// Only hoist if element type is a primitive -- user-defined types
235+
// must stay in place so the struct definition comes first.
236+
// Hoist only if primitive; keep user-defined type arrays in place
237+
if (primitives.contains(baseType) && !userTypes.contains(baseType)) {
238+
result.hoistedArrays.add(vd);
239+
} else {
240+
result.rest.add(item);
241+
}
224242
} else {
225243
result.rest.add(item);
226244
}

src/java/CppBuild.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,10 @@ public File compile(RunnerListener listener) throws Exception {
424424
catch (NumberFormatException ignored) {}
425425
}
426426
}
427-
if (line.contains("error:") || line.contains("warning:")) {
428-
for (String w : wordWrap(line, 120)) System.err.println(w);
427+
if (line.contains("error:") || line.contains("warning:") || line.contains("note:") || line.contains("Processing::")) {
428+
for (String w : wordWrap(rewriteGccError(line), 120)) System.err.println(w);
429429
} else if (!line.isBlank()) {
430-
for (String w : wordWrap(line, 120)) System.out.println(w);
430+
for (String w : wordWrap(rewriteGccError(line), 120)) System.out.println(w);
431431
}
432432
}
433433
}
@@ -4393,4 +4393,39 @@ private boolean downloadMuslToolchain(ExportTarget t, RunnerListener listener) {
43934393
}
43944394
}
43954395

4396+
4397+
private String rewriteGccError(String line) {
4398+
line = line.replace("std::__cxx11::basic_string<char>", "String");
4399+
line = line.replace("std::basic_string<char>", "String");
4400+
line = line.replace("‘std::__cxx11::basic_string<char>’", "String");
4401+
line = line.replace("‘std::basic_string<char>’", "String");
4402+
line = line.replace("{aka ‘std::__cxx11::basic_string<char>’}", "");
4403+
if (line.contains("std::strong_ordering") || line.contains("std::weak_ordering") || line.contains("std::partial_ordering"))
4404+
line = line.replaceAll("std::(strong|weak|partial)_ordering", "comparison_result") + " — note: <=> returns a comparison result, not an int; use <, >, or == instead";
4405+
line = line.replace("Processing::Sketch::", "");
4406+
line = line.replace("Processing::PApplet::", "");
4407+
line = line.replace("Processing::", "");
4408+
if (line.contains("return-statement with a value") && line.contains("returning"))
4409+
return line.replaceAll("return-statement with a value.*", "cannot return a value from a void function");
4410+
if (line.contains("too few arguments to function"))
4411+
return line.replaceAll("too few arguments to function .(.+?).", "too few arguments to $1");
4412+
return line;
4413+
}
4414+
4415+
public static String generateSketchOutput(String rawCode) throws Exception {
4416+
CppBuild b = new CppBuild(null, null);
4417+
String code = b.sanitize(rawCode);
4418+
code = b.removeUserIncludes(code);
4419+
code = code.replaceAll("(\\bfinal_suspend\\s*\\([^)]*\\))(\\s*\\{)", "$1 noexcept$2");
4420+
code = code.replaceAll("(\\binitial_suspend\\s*\\([^)]*\\))(\\s*\\{)", "$1 noexcept$2");
4421+
code = code.replaceAll("\\btranslate\\s*\\(\\s*0+\\.?0*f?\\s*,\\s*0+\\.?0*f?\\s*\\)\\s*;", "");
4422+
code = code.replaceAll("\\btranslate\\s*\\(\\s*0+\\.?0*f?\\s*,\\s*0+\\.?0*f?\\s*,\\s*0+\\.?0*f?\\s*\\)\\s*;", "");
4423+
code = b.stripRawStringLiterals(code);
4424+
code = code.replaceAll("(?<=[0-9a-fA-FxXbB])'(?=[0-9a-fA-F])", "");
4425+
code = b.javaToC(code);
4426+
code = b.stripNamespaceProcessing(code);
4427+
code = b.preprocessMacros(code);
4428+
return code;
4429+
}
4430+
43964431
}

src/java/CppCLI.java

Lines changed: 71 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -11,163 +11,137 @@
1111
*/
1212
public class CppCLI {
1313

14-
private static final Set<String> LIFECYCLE_METHOD_NAMES = Set.of(
14+
private static final Set<String> LIFECYCLE = Set.of(
1515
"setup","draw","mousePressed","mouseReleased","mouseClicked",
1616
"mouseMoved","mouseDragged","mouseWheel",
1717
"keyPressed","keyReleased","keyTyped","settings"
1818
);
1919

2020
public static void main(String[] args) throws Exception {
2121
String input = new String(System.in.readAllBytes(), StandardCharsets.UTF_8);
22-
CppBuild build = allocate();
22+
try {
23+
System.out.print(translate(input));
24+
} catch (Exception e) {
25+
System.err.println(e.getMessage());
26+
System.exit(1);
27+
}
28+
}
2329

24-
// String-level pre-processing (mirrors writeSketchImpl)
30+
private static String translate(String input) throws Exception {
31+
CppBuild b = allocate();
2532
String code = input;
26-
code = invoke(build, "sanitize", code);
27-
code = invoke(build, "removeUserIncludes", code);
33+
code = invoke(b, "sanitize", code);
34+
code = invoke(b, "removeUserIncludes", code);
2835
code = code.replaceAll("(\\bfinal_suspend\\s*\\([^)]*\\))(\\s*\\{)", "$1 noexcept$2");
2936
code = code.replaceAll("(\\binitial_suspend\\s*\\([^)]*\\))(\\s*\\{)", "$1 noexcept$2");
3037
code = code.replaceAll("\\btranslate\\s*\\(\\s*0+\\.?0*f?\\s*,\\s*0+\\.?0*f?\\s*\\)\\s*;", "");
3138
code = code.replaceAll("\\btranslate\\s*\\(\\s*0+\\.?0*f?\\s*,\\s*0+\\.?0*f?\\s*,\\s*0+\\.?0*f?\\s*\\)\\s*;", "");
32-
code = invoke(build, "stripRawStringLiterals", code);
39+
code = invoke(b, "stripRawStringLiterals", code);
3340
code = code.replaceAll("(?<=[0-9a-fA-FxXbB])'(?=[0-9a-fA-F])", "");
34-
code = invoke(build, "javaToC", code);
35-
code = invoke(build, "stripNamespaceProcessing", code);
36-
code = invoke(build, "preprocessMacros", code);
41+
code = invoke(b, "javaToC", code);
42+
code = invoke(b, "stripNamespaceProcessing", code);
43+
code = invoke(b, "preprocessMacros", code);
3744

3845
boolean hasSetup = code.contains("void setup(");
3946
boolean hasDraw = code.contains("void draw(");
4047

48+
StringBuilder out = new StringBuilder();
4149
StringBuilder preNs = new StringBuilder();
42-
StringBuilder header = new StringBuilder();
43-
appendHeader(header);
50+
appendHeader(out);
4451

4552
if (hasSetup || hasDraw) {
46-
try {
47-
String result = runFullPipeline(code, preNs);
48-
System.out.print(preNs.toString());
49-
System.out.print(header.toString());
50-
System.out.print(result);
51-
System.out.print("\nint main() { Processing::_PSketch sketch; sketch.run(); return 0; }\n");
52-
} catch (Exception e) {
53-
System.err.println(e.getMessage());
54-
System.exit(1);
55-
}
53+
String result = runPipeline(code, preNs);
54+
System.out.print(preNs);
55+
System.out.print(out);
56+
System.out.print(result);
57+
System.out.print("\nint main() { Processing::_PSketch sketch; sketch.run(); return 0; }\n");
58+
return "";
5659
} else {
57-
System.out.print(header.toString());
58-
System.out.print("\nnamespace Processing {\n\n");
59-
System.out.print(code);
60-
System.out.print("\n} // namespace Processing\n\n");
61-
System.out.print("int main() { Processing::_PSketch sketch; sketch.run(); return 0; }\n");
60+
out.append("\nnamespace Processing {\n\n");
61+
out.append(code);
62+
out.append("\n} // namespace Processing\n\n");
63+
out.append("int main() { Processing::_PSketch sketch; sketch.run(); return 0; }\n");
64+
return out.toString();
6265
}
6366
}
6467

65-
private static String runFullPipeline(String code, StringBuilder preNs) throws Exception {
66-
// Step 0: parse
68+
private static String runPipeline(String code, StringBuilder preNs) throws Exception {
6769
CompilationUnit cu = Parser.parse(code);
68-
69-
// Step 1: enum extraction
7070
EnumScopeExtractor.Result enumResult = EnumScopeExtractor.extract(cu.items());
71-
72-
// Step 2: lifecycle rewriting
73-
List<TopLevelItem> afterLifecycle = LifecycleRewriter.rewrite(enumResult.rest, LIFECYCLE_METHOD_NAMES);
74-
75-
// Step 3: class hoisting
71+
List<TopLevelItem> afterLifecycle = LifecycleRewriter.rewrite(enumResult.rest, LIFECYCLE);
7672
ClassHoister.Result classResult = ClassHoister.hoist(afterLifecycle);
77-
78-
// Step 4: _PSketch injection
79-
List<PSketchInjector.Result> injectedClasses = PSketchInjector.injectAll(classResult.hoistedClasses);
80-
List<TypeDef> finalClasses = injectedClasses.stream().map(PSketchInjector.Result::typeDef).toList();
81-
82-
// Step 5: array hoisting
73+
List<PSketchInjector.Result> injected = PSketchInjector.injectAll(classResult.hoistedClasses);
74+
List<TypeDef> finalClasses = injected.stream().map(PSketchInjector.Result::typeDef).toList();
8375
ArrayHoister.Result arrayResult = ArrayHoister.hoist(classResult.rest);
84-
85-
// Step 6: dependency hoisting
86-
DependencyHoister.Result depResult = DependencyHoister.hoist(arrayResult.rest, finalClasses, LIFECYCLE_METHOD_NAMES);
87-
88-
// Step 7: forward declarations
76+
DependencyHoister.Result depResult = DependencyHoister.hoist(arrayResult.rest, finalClasses, LIFECYCLE);
8977
List<FunctionDecl> forwardDecls = ForwardDeclGenerator.generate(depResult.hoistedFunctions);
9078

91-
// Build output
9279
StringBuilder sb = new StringBuilder();
9380
sb.append("\nnamespace Processing {\n\n");
9481

95-
// Hoist preprocessor directives and namespaces to file scope
9682
List<TopLevelItem> filteredRest = new ArrayList<>();
9783
for (TopLevelItem item : depResult.rest) {
9884
if (item instanceof PreprocessorLine pl) {
99-
if (pl.rawText().startsWith("#include")) {
100-
preNs.append(CodeGen.generateNode(item, 0));
101-
} else {
102-
sb.append(CodeGen.generateNode(item, 0));
103-
}
85+
if (pl.rawText().startsWith("#include")) preNs.append(CodeGen.generateNode(item, 0));
86+
else sb.append(CodeGen.generateNode(item, 0));
10487
continue;
10588
}
10689
if (item instanceof NamespaceDecl || item instanceof UsingNamespaceDecl) {
107-
sb.append(CodeGen.generateNode(item, 0));
108-
continue;
90+
sb.append(CodeGen.generateNode(item, 0)); continue;
10991
}
11092
filteredRest.add(item);
11193
}
11294

113-
// Forward decls
114-
for (FunctionDecl fd : forwardDecls) {
115-
sb.append(CodeGen.generateNode(fd, 0));
116-
}
117-
118-
// Enum extracted items
119-
for (TopLevelItem item : enumResult.enums) {
120-
sb.append(CodeGen.generateNode(item, 0));
121-
}
95+
for (FunctionDecl fd : forwardDecls) sb.append(CodeGen.generateNode(fd, 0));
96+
for (TopLevelItem e : enumResult.enums) sb.append(CodeGen.generateNode(e, 0));
12297

123-
// Hoisted functions and variables from dependency hoister
98+
// Collect lifecycle bodies from hoisted functions
99+
List<FunctionDecl> lifecycleBodies = new ArrayList<>();
124100
for (TopLevelItem item : depResult.hoistedFunctions) {
125-
sb.append(CodeGen.generateNode(item, 0));
126-
}
127-
for (TopLevelItem item : depResult.hoistedVariables) {
128-
sb.append(CodeGen.generateNode(item, 0));
129-
}
130-
131-
// Hoisted arrays
132-
for (TopLevelItem item : arrayResult.hoistedArrays) {
133-
sb.append(CodeGen.generateNode(item, 0));
134-
}
135-
136-
// Final classes (_PSketch injected)
137-
for (TypeDef td : finalClasses) {
138-
sb.append(CodeGen.generateNode(td, 0));
101+
if (item instanceof FunctionDecl fd && LIFECYCLE.contains(fd.name()) && fd.body() != null)
102+
lifecycleBodies.add(fd);
103+
else sb.append(CodeGen.generateNode(item, 0));
139104
}
105+
for (TopLevelItem item : depResult.hoistedVariables) sb.append(CodeGen.generateNode(item, 0));
106+
for (TopLevelItem item : arrayResult.hoistedArrays) sb.append(CodeGen.generateNode(item, 0));
107+
for (TypeDef td : finalClasses) sb.append(CodeGen.generateNode(td, 0));
140108

141-
// Remaining items
109+
// Also collect from filteredRest
110+
List<TopLevelItem> nonLifecycle = new ArrayList<>();
142111
for (TopLevelItem item : filteredRest) {
143-
sb.append(CodeGen.generateNode(item, 0));
112+
if (item instanceof FunctionDecl fd && LIFECYCLE.contains(fd.name()) && fd.body() != null)
113+
lifecycleBodies.add(fd);
114+
else nonLifecycle.add(item);
144115
}
145-
116+
for (TopLevelItem item : nonLifecycle) sb.append(CodeGen.generateNode(item, 0));
117+
118+
// Build _PSketch with inline lifecycle bodies
119+
sb.append("class _PSketch : public PApplet {\npublic:\n");
120+
Map<String,FunctionDecl> bodyMap = new LinkedHashMap<>();
121+
for (FunctionDecl fd : lifecycleBodies) bodyMap.put(fd.name(), fd);
122+
for (String name : LIFECYCLE) {
123+
FunctionDecl fd = bodyMap.get(name);
124+
if (fd == null) continue;
125+
String rendered = CodeGen.generateNode(fd, 0).strip();
126+
if (!rendered.contains("override"))
127+
rendered = rendered.replaceFirst("(\\b" + name + "\\s*\\([^)]*\\)\\s*)", "$1 override ");
128+
for (String line : rendered.split("\n", -1))
129+
sb.append(" ").append(line).append("\n");
130+
}
131+
sb.append("};\n");
146132
sb.append("\n} // namespace Processing\n");
147133
return sb.toString();
148134
}
149135

150136
private static void appendHeader(StringBuilder out) {
151137
out.append("#include \"Processing.h\"\n");
152-
out.append("using std::vector; using std::string; using std::wstring;\n");
153-
out.append("using std::pair; using std::make_pair; using std::tuple;\n");
154-
out.append("using std::deque; using std::list; using std::stack; using std::queue;\n");
155-
out.append("using std::unordered_map; using std::unordered_set;\n");
156-
out.append("using std::sort; using std::shuffle; using std::reverse;\n");
157-
out.append("using std::unique_ptr; using std::shared_ptr;\n");
158-
out.append("using std::make_unique; using std::make_shared;\n");
159-
out.append("using std::to_string; using std::stoi; using std::stof; using std::stod;\n");
160-
out.append("using std::function;\n");
161-
out.append("using std::map; using std::set;\n");
162-
out.append("using std::array; using std::optional;\n");
163-
out.append("using std::runtime_error; using std::logic_error; using std::exception;\n");
164-
out.append("using std::numeric_limits;\n");
138+
out.append("using namespace std;\n");
165139
}
166140

167-
private static String invoke(CppBuild build, String method, String code) throws Exception {
141+
private static String invoke(CppBuild b, String method, String code) throws Exception {
168142
Method m = getMethod(CppBuild.class, method, String.class);
169143
m.setAccessible(true);
170-
return (String) m.invoke(build, code);
144+
return (String) m.invoke(b, code);
171145
}
172146

173147
private static Method getMethod(Class<?> cls, String name, Class<?>... params) throws NoSuchMethodException {

src/java/CppLexer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ private CppLexerToken lexStringLiteral(int startLine, int startCol) {
199199
}
200200
if (pos < len && peekChar() == '"') {
201201
sb.append(advanceChar()); // closing "
202+
} else {
203+
throw new ParseException("unterminated string literal", startLine, startCol);
202204
}
203205
return new CppLexerToken(CppLexerTokenType.STRING_LITERAL, sb.toString(), startLine, startCol);
204206
}
@@ -218,6 +220,8 @@ private CppLexerToken lexCharLiteral(int startLine, int startCol) {
218220
}
219221
if (pos < len && peekChar() == '\'') {
220222
sb.append(advanceChar());
223+
} else {
224+
throw new ParseException("unterminated character literal", startLine, startCol);
221225
}
222226
return new CppLexerToken(CppLexerTokenType.CHAR_LITERAL, sb.toString(), startLine, startCol);
223227
}

0 commit comments

Comments
 (0)