diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java
index dc3c8c0da1..d2c58921e5 100644
--- a/app/src/processing/app/Platform.java
+++ b/app/src/processing/app/Platform.java
@@ -75,6 +75,10 @@ public class Platform {
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+ static public boolean isInit() {
+ return inst != null;
+ }
+
static public void init() {
try {
diff --git a/app/src/processing/app/tools/InstallCommander.java b/app/src/processing/app/tools/InstallCommander.java
index 518789d4ce..a846208dae 100644
--- a/app/src/processing/app/tools/InstallCommander.java
+++ b/app/src/processing/app/tools/InstallCommander.java
@@ -161,4 +161,4 @@ public boolean accept(File dir, String name) {
list.append(jar.getAbsolutePath());
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java
index 6780a53100..0e9ab769eb 100644
--- a/app/src/processing/app/ui/Editor.java
+++ b/app/src/processing/app/ui/Editor.java
@@ -2898,7 +2898,6 @@ public void statusError(String what) {
* Show an exception in the editor status bar.
*/
public void statusError(Exception e) {
- e.printStackTrace();
// if (e == null) {
// System.err.println("Editor.statusError() was passed a null exception.");
// return;
@@ -2909,9 +2908,7 @@ public void statusError(Exception e) {
// Make sure something is printed into the console
// Status bar is volatile
- if (!re.isStackTraceEnabled()) {
- System.err.println(re.getMessage());
- }
+ System.err.println(re.getMessage());
// Move the cursor to the line before updating the status bar, otherwise
// status message might get hidden by a potential message caused by moving
@@ -2940,6 +2937,8 @@ public void statusError(Exception e) {
textarea.getLineStopOffset(line) - 1);
}
}
+ } else {
+ e.printStackTrace();
}
// Since this will catch all Exception types, spend some time figuring
diff --git a/app/test/resources/annotations.expected b/app/test/resources/annotations.expected
deleted file mode 100644
index 76d6b79a88..0000000000
--- a/app/test/resources/annotations.expected
+++ /dev/null
@@ -1,53 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.io.Serializable;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class annotations extends PApplet {
-
-
-
-public void setup() {
- size(200,200);
-}
-
-@Deprecated
-public void banana() {
- println("hey");
-}
-
-@SuppressWarnings({"serial", "rawtypes"})
-class Banana implements Serializable {
-
-}
-
-@SuppressWarnings("serial")
-class Apple implements Serializable {
-
-}
-
-@javax.annotation.Generated(value = {"com.mrfeinberg.ImmortalAroma"
-},
- comments="Shazam!",
- date="2001-07-04T12:08:56.235-0700")
-class Pear {}
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "annotations" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1064.expected b/app/test/resources/bug1064.expected
deleted file mode 100644
index d77173b442..0000000000
--- a/app/test/resources/bug1064.expected
+++ /dev/null
@@ -1,29 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1064 extends PApplet {
- public void setup() {
-// import ";
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1064" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug136.expected b/app/test/resources/bug136.expected
deleted file mode 100644
index 9909098a32..0000000000
--- a/app/test/resources/bug136.expected
+++ /dev/null
@@ -1,41 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.Collections;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug136 extends PApplet {
-
-
-java.util.List alist = Collections.synchronizedList(new ArrayList());
-
-public void setup() {
-size(400, 200);
-alist.add("hello");
-}
-
-public void draw() {
-rect(width/4, height/4, width/2, height/2);
-synchronized(alist) {
-alist.get(0);
-}
-}
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug136" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1362.expected b/app/test/resources/bug1362.expected
deleted file mode 100644
index 00c81e622b..0000000000
--- a/app/test/resources/bug1362.expected
+++ /dev/null
@@ -1,29 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1362 extends PApplet {
- public void setup() {
-if (true) {} else { new String(); }
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1362" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1442.expected b/app/test/resources/bug1442.expected
deleted file mode 100644
index d48380587e..0000000000
--- a/app/test/resources/bug1442.expected
+++ /dev/null
@@ -1,28 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1442 extends PApplet {
-
-public float a() {
- return 1.0f;
-}
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1442" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1511.expected b/app/test/resources/bug1511.expected
deleted file mode 100644
index ca423630f9..0000000000
--- a/app/test/resources/bug1511.expected
+++ /dev/null
@@ -1,37 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.io.StringWriter;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1511 extends PApplet {
- public void setup() {
-// \u00df
-
-/**
-* a
-*/
-
-
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1511" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1512.expected b/app/test/resources/bug1512.expected
deleted file mode 100644
index bb2bd9aa09..0000000000
--- a/app/test/resources/bug1512.expected
+++ /dev/null
@@ -1,29 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1512 extends PApplet {
- public void setup() {
-println("oi/*");
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1512" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1518a.expected b/app/test/resources/bug1518a.expected
deleted file mode 100644
index 49c81c14c3..0000000000
--- a/app/test/resources/bug1518a.expected
+++ /dev/null
@@ -1,45 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1518a extends PApplet {
-
-
-
-
-public void setup()
-{
-List list = new ArrayList();
-list.add("foo");
-list.add("bar");
-list.add("baz");
-
-binarySearch(list, "bar");
-}
-
-static int binarySearch(List extends Comparable super T>> list, T
-key) {
-return 0;
-}
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1518a" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1518b.expected b/app/test/resources/bug1518b.expected
deleted file mode 100644
index 2ca376adfe..0000000000
--- a/app/test/resources/bug1518b.expected
+++ /dev/null
@@ -1,43 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1518b extends PApplet {
-
-
-
-
-public void setup()
-{
-List list = new ArrayList();
-list.add("foo");
-list.add("bar");
-list.add("baz");
-}
-
-static int binarySearch(List extends Comparable super T>> list, T
-key) {
-return 0;
-}
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1518b" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1519.pde b/app/test/resources/bug1519.pde
deleted file mode 100644
index 6f60573040..0000000000
--- a/app/test/resources/bug1519.pde
+++ /dev/null
@@ -1,9 +0,0 @@
-import java.util.ArrayList;
-import java.util.List;
-
-void setup()
-{
-List list = new ArrayList();
-List>> listOfLists = new ArrayList>();
-listOfLists.add(list);
-}
diff --git a/app/test/resources/bug1525.expected b/app/test/resources/bug1525.expected
deleted file mode 100644
index c1337f7b23..0000000000
--- a/app/test/resources/bug1525.expected
+++ /dev/null
@@ -1,31 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1525 extends PApplet {
- public void setup() {
-if (frameCount > (frameRate - 1)) {
- println("My head asplode!");
-}
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1525" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug1534.expected b/app/test/resources/bug1534.expected
deleted file mode 100644
index 1b61ca49c6..0000000000
--- a/app/test/resources/bug1534.expected
+++ /dev/null
@@ -1,29 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1534 extends PApplet {
- public void setup() {
-char c = '\"';
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1534" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug16.pde b/app/test/resources/bug16.pde
deleted file mode 100644
index d787284790..0000000000
--- a/app/test/resources/bug16.pde
+++ /dev/null
@@ -1,9 +0,0 @@
-println("Here comes an unterminated comment!")
-
-/*
- banana
- apple
- pear
-* /
-
-println("Do you see what I did there?")
diff --git a/app/test/resources/bug1936.expected b/app/test/resources/bug1936.expected
deleted file mode 100644
index dd06677b05..0000000000
--- a/app/test/resources/bug1936.expected
+++ /dev/null
@@ -1,29 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug1936 extends PApplet {
- public void setup() {
-char a = PApplet.parseChar(PApplet.parseByte(PApplet.parseInt("15")));
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug1936" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug281.expected b/app/test/resources/bug281.expected
deleted file mode 100644
index ffc6ea5194..0000000000
--- a/app/test/resources/bug281.expected
+++ /dev/null
@@ -1,32 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug281 extends PApplet {
- public void setup() {
-if ( "monopoly".charAt( 3 ) == '(' )
-{
- println("parcheesi");
-}
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug281" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug315g.expected b/app/test/resources/bug315g.expected
deleted file mode 100644
index ef5cdffa67..0000000000
--- a/app/test/resources/bug315g.expected
+++ /dev/null
@@ -1,35 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug315g extends PApplet {
- public void setup() {
-size(480, 120);
-smooth();
-int y;
-y = 60;
-int d;
-d = 80;
-ellipse(75, y, d, d);
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug315g" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug4.expected b/app/test/resources/bug4.expected
deleted file mode 100644
index 661de7b807..0000000000
--- a/app/test/resources/bug4.expected
+++ /dev/null
@@ -1,30 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug4 extends PApplet {
- public void setup() {
-int x = 12;
-float u = (PApplet.parseFloat(x)/width);
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug4" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug4.pde b/app/test/resources/bug4.pde
deleted file mode 100644
index ee3e1a2349..0000000000
--- a/app/test/resources/bug4.pde
+++ /dev/null
@@ -1,2 +0,0 @@
-int x = 12;
-float u = (float(x)/width);
\ No newline at end of file
diff --git a/app/test/resources/bug400g.expected b/app/test/resources/bug400g.expected
deleted file mode 100644
index 92dc4f5776..0000000000
--- a/app/test/resources/bug400g.expected
+++ /dev/null
@@ -1,34 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug400g extends PApplet {
-
-////
-public void setup(){
- size(100,100);
-
- if(true){
- }
- else{ // Syntax error on token "else", } expected
- }
-}
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug400g" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug405.expected b/app/test/resources/bug405.expected
deleted file mode 100644
index 48497bec0b..0000000000
--- a/app/test/resources/bug405.expected
+++ /dev/null
@@ -1,3 +0,0 @@
-for (int i : new int[] {1,2,3}) {
- println(i);
-}
diff --git a/app/test/resources/bug420.expected b/app/test/resources/bug420.expected
deleted file mode 100644
index 8ea69d2a79..0000000000
--- a/app/test/resources/bug420.expected
+++ /dev/null
@@ -1,6 +0,0 @@
-int[] a = new int[] {
- 1, 2, 3, 4, 5
-};
-for (int i: a) {
- print(i);
-}
diff --git a/app/test/resources/bug427g.expected b/app/test/resources/bug427g.expected
deleted file mode 100644
index aebd136729..0000000000
--- a/app/test/resources/bug427g.expected
+++ /dev/null
@@ -1,38 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug427g extends PApplet {
-
-static final boolean DEBUG = true;
-
-public void setup() {
- MyClass x = new MyClass();
-}
-
-public class MyClass {
- public MyClass() {
- if (DEBUG) println("Debug");
- }
-}
-
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug427g" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug481.expected b/app/test/resources/bug481.expected
deleted file mode 100644
index 10d76993ba..0000000000
--- a/app/test/resources/bug481.expected
+++ /dev/null
@@ -1,32 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.applet.Applet;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug481 extends PApplet {
- public void setup() {
-
-Class[] abc = new Class[]{Applet.class};
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug481" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug598.expected b/app/test/resources/bug598.expected
deleted file mode 100644
index e3c91eef61..0000000000
--- a/app/test/resources/bug598.expected
+++ /dev/null
@@ -1,70 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import static java.lang.Math.tanh;
-import java.util.concurrent.Callable;
-import java.util.List;
-import java.util.Comparator;
-import java.util.Map;
-import java.util.Collection;
-import java.util.Arrays;
-import java.util.HashSet;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug598 extends PApplet {
-
-// java 5 torture test
-
-
-
-
-
-
-
-
-
-
-
-private static Comparator rotarapmoc = new Comparator() {
- public int compare(final String o1, final String o2)
- {
- return o1.charAt(o1.length() - 1) - o2.charAt(o2.length() - 1);
- }
-};
-
-final void printClass(T t) {
- println(t.getClass());
-}
-public final List sortem(final String... strings) {
- Arrays.sort(strings, rotarapmoc);
- return Arrays.asList(strings);
-}
-
-final Map>
-charlesDeGaulle = new HashMap>();
-
-public void setup() {
- charlesDeGaulle.put("banana", new HashSet());
- charlesDeGaulle.get("banana").add(0);
- System.out.println(sortem("aztec", "maya", "spanish", "portuguese"));
- printClass(12.d);
-}
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug598" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug5a.expected b/app/test/resources/bug5a.expected
deleted file mode 100644
index c29588dfcb..0000000000
--- a/app/test/resources/bug5a.expected
+++ /dev/null
@@ -1,30 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug5a extends PApplet {
- public void setup() {
-println("The next line should not cause a failure.");
-// no newline after me
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug5a" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/resources/bug5b.expected b/app/test/resources/bug5b.expected
deleted file mode 100644
index 058be9ab30..0000000000
--- a/app/test/resources/bug5b.expected
+++ /dev/null
@@ -1,30 +0,0 @@
-import processing.core.*;
-import processing.data.*;
-import processing.event.*;
-import processing.opengl.*;
-
-import java.util.HashMap;
-import java.util.ArrayList;
-import java.io.File;
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.IOException;
-
-public class bug5b extends PApplet {
- public void setup() {
-println("The next line should not cause a failure.");
-/* no newline after me */
- noLoop();
- }
-
- static public void main(String[] passedArgs) {
- String[] appletArgs = new String[] { "bug5b" };
- if (passedArgs != null) {
- PApplet.main(concat(appletArgs, passedArgs));
- } else {
- PApplet.main(appletArgs);
- }
- }
-}
diff --git a/app/test/src/test/processing/mode/java/ParserTests.java b/app/test/src/test/processing/mode/java/ParserTests.java
deleted file mode 100644
index 4f565a7d07..0000000000
--- a/app/test/src/test/processing/mode/java/ParserTests.java
+++ /dev/null
@@ -1,304 +0,0 @@
-package test.processing.mode.java;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static test.processing.mode.java.ProcessingTestUtil.COMPILER;
-import static test.processing.mode.java.ProcessingTestUtil.preprocess;
-import static test.processing.mode.java.ProcessingTestUtil.res;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import processing.app.SketchException;
-import processing.app.exec.ProcessResult;
-import antlr.RecognitionException;
-
-public class ParserTests {
-
- @BeforeClass
- public static void init() {
- ProcessingTestUtil.init();
- }
-
- static void expectRecognitionException(final String id,
- final String expectedMessage,
- final int expectedLine) {
- try {
- preprocess(id, res(id + ".pde"));
- fail("Expected to fail with \"" + expectedMessage + "\" on line "
- + expectedLine);
- } catch (RecognitionException e) {
- assertEquals(expectedMessage, e.getMessage());
- assertEquals(expectedLine, e.getLine());
- } catch (Exception e) {
- if (!e.equals(e.getCause()) && e.getCause() != null)
- fail(e.getCause().toString());
- else
- fail(e.toString());
- }
- }
-
- static void expectRunnerException(final String id,
- final String expectedMessage,
- final int expectedLine) {
- try {
- preprocess(id, res(id + ".pde"));
- fail("Expected to fail with \"" + expectedMessage + "\" on line "
- + expectedLine);
- } catch (SketchException e) {
- assertEquals(expectedMessage, e.getMessage());
- assertEquals(expectedLine, e.getCodeLine());
- } catch (Exception e) {
- if (!e.equals(e.getCause()) && e.getCause() != null)
- fail(e.getCause().toString());
- else
- fail(e.toString());
- }
- }
-
- static void expectCompilerException(final String id,
- final String expectedMessage,
- final int expectedLine) {
- try {
- final String program = ProcessingTestUtil
- .preprocess(id, res(id + ".pde"));
- final ProcessResult compilerResult = COMPILER.compile(id, program);
- if (compilerResult.succeeded()) {
- fail("Expected to fail with \"" + expectedMessage + "\" on line "
- + expectedLine);
- }
- final String e = compilerResult.getStderr().split("\n")[0];
- final Matcher m = Pattern.compile(":(\\d+):\\s+(.+)$").matcher(e);
- m.find();
- assertEquals(expectedMessage, m.group(2));
- assertEquals(String.valueOf(expectedLine), m.group(1));
- } catch (Exception e) {
- if (!e.equals(e.getCause()) && e.getCause() != null)
- fail(e.getCause().toString());
- else
- fail(e.toString());
- }
- }
-
- static void expectGood(final String id) {
- try {
- final String program = ProcessingTestUtil
- .preprocess(id, res(id + ".pde"));
- final ProcessResult compilerResult = COMPILER.compile(id, program);
- if (!compilerResult.succeeded()) {
- System.err.println(program);
- System.err.println("----------------------------");
- System.err.println(compilerResult.getStderr());
- fail("Compilation failed with status " + compilerResult.getResult());
- }
-
- final File expectedFile = res(id + ".expected");
- if (expectedFile.exists()) {
- final String expected = ProcessingTestUtil.read(expectedFile);
- assertEquals(expected, program);
- } else {
- System.err.println("WARN: " + id
- + " does not have an expected output file. Generating.");
- final FileWriter sug = new FileWriter(res(id + ".expected"));
- sug.write(ProcessingTestUtil.normalize(program));
- sug.close();
- }
-
- } catch (Exception e) {
- if (!e.equals(e.getCause()) && e.getCause() != null)
- fail(e.getCause().toString());
- else
- fail(e.toString());
- }
- }
-
- @Test
- public void bug4() {
- expectGood("bug4");
- }
-
- @Test
- public void bug5a() {
- expectGood("bug5a");
- }
-
- @Test
- public void bug5b() {
- expectGood("bug5b");
- }
-
- @Test
- public void bug6() {
- expectRecognitionException("bug6", "expecting EOF, found '/'", 1);
- }
-
- @Test
- public void bug16() {
- expectRunnerException("bug16", "Unclosed /* comment */", 2);
- }
-
- @Test
- public void bug136() {
- expectGood("bug136");
- }
-
- @Test
- public void bug196() {
- expectRecognitionException("bug196",
- "Web colors must be exactly 6 hex digits. This looks like 5.", 4);
- }
-
- @Test
- public void bug281() {
- expectGood("bug281");
- }
-
- @Test
- public void bug481() {
- expectGood("bug481");
- }
-
- @Test
- public void bug507() {
- expectRecognitionException("bug507", "expecting EOF, found 'else'", 5);
- }
-
- @Test
- public void bug598() {
- expectGood("bug598");
- }
-
- @Test
- public void bug631() {
- expectGood("bug631");
- }
-
- @Test
- public void bug763() {
- expectRunnerException("bug763", "Unterminated string constant", 6);
- }
-
- @Test
- public void bug820() {
- expectCompilerException("bug820", "error: variable x1 is already defined in method setup()", 18);
- }
-
- @Test
- public void bug1064() {
- expectGood("bug1064");
- }
-
- @Test
- public void bug1145() {
- expectCompilerException("bug1145", "error: '.' expected", 6);
- }
-
- @Test
- public void bug1362() {
- expectGood("bug1362");
- }
-
- @Test
- public void bug1390() {
- expectGood("bug1390");
- }
-
- @Test
- public void bug1442() {
- expectGood("bug1442");
- }
-
- @Test
- public void bug1511() {
- expectGood("bug1511");
- }
-
- @Test
- public void bug1512() {
- expectGood("bug1512");
- }
-
- @Test
- public void bug1514a() {
- expectGood("bug1514a");
- }
-
- @Test
- public void bug1514b() {
- expectGood("bug1514b");
- }
-
- @Test
- public void bug1515() {
- expectGood("bug1515");
- }
-
- @Test
- public void bug1516() {
- expectGood("bug1516");
- }
-
- @Test
- public void bug1517() {
- expectGood("bug1517");
- }
-
- @Test
- public void bug1518a() {
- expectGood("bug1518a");
- }
-
- @Test
- public void bug1518b() {
- expectGood("bug1518b");
- }
-
- @Test
- public void bug1519() {
- expectRecognitionException("bug1519", "Maybe too many > characters?", 7);
- }
-
- @Test
- public void bug1525() {
- expectGood("bug1525");
- }
-
- @Test
- public void bug1532() {
- expectRecognitionException("bug1532", "unexpected token: break", 50);
- }
-
- @Test
- public void bug1534() {
- expectGood("bug1534");
- }
-
- @Test
- public void bug1936() {
- expectGood("bug1936");
- }
-
- @Test
- public void bug315g() {
- expectGood("bug315g");
- }
-
- @Test
- public void bug400g() {
- expectGood("bug400g");
- }
-
- @Test
- public void bug427g() {
- expectGood("bug427g");
- }
-
- @Test
- public void annotations() {
- expectGood("annotations");
- }
-}
diff --git a/build/.gitignore b/build/.gitignore
index 290988977d..177ce2a9bf 100644
--- a/build/.gitignore
+++ b/build/.gitignore
@@ -1,2 +1,8 @@
work
javadoc
+
+jre/bin-test
+macosx/javafx-sdk-11.0.2
+macosx/jdk-0u4.tgz
+macosx/jdk-11.0.4+11
+macosx/jfx-11.0.2.zip
diff --git a/build/build.xml b/build/build.xml
index 93f2b05415..2500ded8a5 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -87,8 +87,8 @@
-
-
+
+
@@ -440,7 +440,7 @@
-
+
@@ -740,7 +740,7 @@
-
+
@@ -775,7 +775,7 @@
-->
-
+
@@ -1685,7 +1685,7 @@ remove the spaces for depth since it should be double dash, but screws up commen
-
+
@@ -1726,7 +1726,7 @@ remove the spaces for depth since it should be double dash, but screws up commen
-
+
diff --git a/build/linux/processing b/build/linux/processing
index a7a008e334..711147d917 100755
--- a/build/linux/processing
+++ b/build/linux/processing
@@ -104,7 +104,7 @@ cmd_name='processing-java'
if [ $current_name = $cmd_name ]
then
- java -Djna.nosys=true -Xmx256m processing.mode.java.Commander "$@"
+ java -Djna.nosys=true -Xmx512m processing.mode.java.Commander "$@"
exit $?
else
# Start Processing in the same directory as this script
@@ -115,5 +115,5 @@ else
fi
cd "$APPDIR"
- java -splash:lib/about-1x.png -Djna.nosys=true -Xmx256m processing.app.Base "$SKETCH" &
+ java -splash:lib/about-1x.png -Djna.nosys=true -Xmx512m processing.app.Base "$SKETCH" &
fi
diff --git a/build/shared/lib/defaults.txt b/build/shared/lib/defaults.txt
index 97381d81c9..843f706ef4 100644
--- a/build/shared/lib/defaults.txt
+++ b/build/shared/lib/defaults.txt
@@ -195,7 +195,7 @@ run.options =
# settings for the -XmsNNNm and -XmxNNNm command line option
run.options.memory = false
run.options.memory.initial = 64
-run.options.memory.maximum = 256
+run.options.memory.maximum = 512
# By default, Mac OS X 10.6 launches applications in 32-bit mode,
# which is more compatible with libraries (many have not updated to 64-bit).
diff --git a/build/shared/lib/languages/PDE.properties b/build/shared/lib/languages/PDE.properties
index 0f312730fd..c8fe9c28e2 100644
--- a/build/shared/lib/languages/PDE.properties
+++ b/build/shared/lib/languages/PDE.properties
@@ -377,6 +377,7 @@ editor.status.archiver.cancel = Archive sketch canceled.
# Errors
editor.status.warning = Warning
editor.status.error = Error
+editor.status.error.syntax = Syntax Error - %s
editor.status.error_on = Error on "%s"
editor.status.missing.default = Missing "%c"
editor.status.missing.semicolon = Missing a semicolon ";"
@@ -404,6 +405,17 @@ editor.status.uninitialized_variable = The local variable "%s" may not have been
editor.status.no_effect_assignment = The assignment to variable "%s" has no effect
editor.status.hiding_enclosing_type = The class "%s" cannot have the same name as your sketch or its enclosing class
+editor.status.bad.assignment = Possible error on variable assignment near '%s'?
+editor.status.bad.generic = Possibly missing type in generic near '%s'?
+editor.status.bad.identifier = Bad identifier? Did you forget a variable or start an identifier with digits near '%s'?
+editor.status.bad.parameter = Error on parameter or method declaration near '%s'?
+editor.status.bad.import = Import not allowed here.
+editor.status.extraneous = Incomplete statement or extra code near '%s'?
+editor.status.mismatched = Missing operator, semicolon, or '}' near '%s'?
+editor.status.missing.name = Missing name or ; near '%s'?
+editor.status.missing.type = Missing name or ; or type near '%s'?
+
+
# Footer buttons
editor.footer.errors = Errors
editor.footer.errors.problem = Problem
diff --git a/build/shared/lib/languages/PDE_ar.properties b/build/shared/lib/languages/PDE_ar.properties
index 008f027517..9da0e12463 100644
--- a/build/shared/lib/languages/PDE_ar.properties
+++ b/build/shared/lib/languages/PDE_ar.properties
@@ -375,6 +375,9 @@ editor.status.uninitialized_variable = لم يتم تعريف المتغير ا
editor.status.no_effect_assignment = الإسناد للمتغير "%s" ليس لديه أي مفعول
editor.status.hiding_enclosing_type = لا يمكن أن يكون إسم الصنف "%s" كإسم المخطوط أو إسم الصنف المحتوي
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error.syntax = خطأ قواعدي - %s
+
# Footer buttons
editor.footer.errors = أخطاء
editor.footer.errors.problem = مشاكل
diff --git a/build/shared/lib/languages/PDE_de.properties b/build/shared/lib/languages/PDE_de.properties
index 21d057439e..52ca595955 100644
--- a/build/shared/lib/languages/PDE_de.properties
+++ b/build/shared/lib/languages/PDE_de.properties
@@ -366,6 +366,9 @@ contrib.progress.downloading = Herunterladen ...
contrib.download_error = Es trat ein Fehler beim Download auf.
contrib.unsupported_operating_system = Dein Betriebssystem wird nicht unterstützt. Rufe %s für weitere Informationen auf.
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error = Fehler
+editor.status.error.syntax = Syntaxfehler - %s
# ---------------------------------------
# Warnings
diff --git a/build/shared/lib/languages/PDE_el.properties b/build/shared/lib/languages/PDE_el.properties
index 07c348ab2e..5b7ad5b810 100644
--- a/build/shared/lib/languages/PDE_el.properties
+++ b/build/shared/lib/languages/PDE_el.properties
@@ -364,6 +364,9 @@ editor.status.no_effect_assignment = Η ανάθεση στη μεταβλητή
editor.status.archiver.create = Δημιουργήθηκε το αρχείο "%s".
editor.status.archiver.cancel = H αρχειοθέτηση σχεδίου ακυρώθηκε.
+# Limited syntax error support
+editor.status.error.syntax = Σφάλμα - %s
+
# Footer buttons
editor.footer.errors = Σφάλματα
editor.footer.errors.problem = Πρόβλημα
diff --git a/build/shared/lib/languages/PDE_es.properties b/build/shared/lib/languages/PDE_es.properties
index 8752a1bd0f..005cc96dd7 100644
--- a/build/shared/lib/languages/PDE_es.properties
+++ b/build/shared/lib/languages/PDE_es.properties
@@ -356,6 +356,7 @@ editor.status.archiver.cancel = Archivado del sketch cancelado.
# Errors
editor.status.warning = Advertencia
editor.status.error = Error
+editor.status.error.syntax = Error de sintaxis - %s
editor.status.error_on = Error en "%s"
editor.status.missing.default = Falta un "%c".
editor.status.missing.semicolon = Falta un punto y coma ";"
@@ -381,6 +382,17 @@ editor.status.uninitialized_variable = Puede que la variable local "%s" no haya
editor.status.no_effect_assignment = La asignación a la variable "%s" no tiene ningún efecto
editor.status.hiding_enclosing_type = Las clase "%s" no puede tener el mismo nombre que el sketch o su clase envolvente
+# Extended syntax error translation
+editor.status.bad.assignment = Error en este asignación de variable cerca '%s'?
+editor.status.bad.identifier = Error en este identificador? Es posible que tu olvidaste un variable o empezaste un identificador con un numero cerca '%s'?
+editor.status.bad.generic = Error en genérico cerca '%s'. Falta un tipo?
+editor.status.bad.parameter = Error en un parámetro o una declaración de método cerca '%s'?
+editor.status.bad.import = Una declaración de importación no es permitida en una definición de una clasa.
+editor.status.extraneous = Una declaración incompleta o un imprevisto clave cerca '%s'?
+editor.status.mismatched = Falta un punto y coma, un operador, o un '}' cerca '%s'?
+editor.status.missing.name = Falta ; o nombre cerca '%s'?
+editor.status.missing.type = Falta ; o nombre o tipo cerca '%s'?
+
# Footer buttons
editor.footer.errors = Errores
editor.footer.errors.problem = Problema
diff --git a/build/shared/lib/languages/PDE_fr.properties b/build/shared/lib/languages/PDE_fr.properties
index 37209e9991..a30a5dc51e 100644
--- a/build/shared/lib/languages/PDE_fr.properties
+++ b/build/shared/lib/languages/PDE_fr.properties
@@ -256,6 +256,10 @@ editor.status.printing.done = Imprimerie terminé.
editor.status.printing.error = Erreur lors de l'imprimerie.
editor.status.printing.canceled = Imprimerie annulé.
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error = Erreur
+editor.status.error.syntax = Erreur de syntaxe
+
# Footer buttons
editor.footer.errors = Erreurs
editor.footer.errors.problem = Problèmes
diff --git a/build/shared/lib/languages/PDE_it.properties b/build/shared/lib/languages/PDE_it.properties
index 4a1334e82d..f032124960 100644
--- a/build/shared/lib/languages/PDE_it.properties
+++ b/build/shared/lib/languages/PDE_it.properties
@@ -375,6 +375,9 @@ editor.status.uninitialized_variable = La variabile locale "%s" potrebbe non ess
editor.status.no_effect_assignment = L'assegnazione alla variabile "%s" non ha effetto
editor.status.hiding_enclosing_type = La classe "%s" non può avere lo stesso nome del tuo sketch o della classe che la contiene
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error.syntax = Errore di sintassi - %s
+
# Footer buttons
editor.footer.errors = Errori
editor.footer.errors.problem = Problemi
diff --git a/build/shared/lib/languages/PDE_ja.properties b/build/shared/lib/languages/PDE_ja.properties
index f0bc510298..e513c1e4cf 100644
--- a/build/shared/lib/languages/PDE_ja.properties
+++ b/build/shared/lib/languages/PDE_ja.properties
@@ -374,6 +374,9 @@ editor.status.unused_variable = ローカル変数 "%s" の値は使われてい
editor.status.uninitialized_variable = The local variable "%s" may not have been initialized
editor.status.no_effect_assignment = The assignment to variable "%s" has no effect
+# Limited syntax error support
+editor.status.error.syntax = "%s" でエラー
+
# Footer buttons
editor.footer.errors = エラー
editor.footer.errors.problem = 問題
diff --git a/build/shared/lib/languages/PDE_ko.properties b/build/shared/lib/languages/PDE_ko.properties
index e60e088be7..c7bba127e1 100644
--- a/build/shared/lib/languages/PDE_ko.properties
+++ b/build/shared/lib/languages/PDE_ko.properties
@@ -294,6 +294,10 @@ contrib.progress.downloading = 다운로드 중
contrib.download_error = 해당 파일 다운로드 중 에러 발생하였습니다.
contrib.unsupported_operating_system = 해당 파일 해당 컴퓨터의 운영체제를 지원하지 않습니다. %s의 웹페이지에 방문하여 확인해 보세요.
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error = 오류
+editor.status.error.syntax = 구문 오류 - %s
+
# ---------------------------------------
# Warnings
@@ -311,4 +315,4 @@ update_check.updates_available.contributions = 설치된 컨트리뷰션(도구,
# ---------------------------------------
# Color Chooser
-color_chooser = 색상 선택
\ No newline at end of file
+color_chooser = 색상 선택
diff --git a/build/shared/lib/languages/PDE_nl.properties b/build/shared/lib/languages/PDE_nl.properties
index ee753069f1..0fc163fe5d 100644
--- a/build/shared/lib/languages/PDE_nl.properties
+++ b/build/shared/lib/languages/PDE_nl.properties
@@ -262,6 +262,9 @@ editor.status.printing.done = Afdrukken gereed.
editor.status.printing.error = Fout tijdens het afdrukken.
editor.status.printing.canceled = Afdrukken geannuleerd.
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error = Fout
+editor.status.error.syntax = Fout - %s
# ---------------------------------------
# Contribution Panel
diff --git a/build/shared/lib/languages/PDE_pt.properties b/build/shared/lib/languages/PDE_pt.properties
index fbf823c3b3..40e88a37e8 100644
--- a/build/shared/lib/languages/PDE_pt.properties
+++ b/build/shared/lib/languages/PDE_pt.properties
@@ -236,6 +236,10 @@ editor.status.printing.done = Impresso com sucesso.
editor.status.printing.error = Erro a imprimir.
editor.status.printing.canceled = Impressão cancelada.
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error = Erro
+editor.status.error.syntax = Erro - %s
+
# ---------------------------------------
# Contribution Panel
diff --git a/build/shared/lib/languages/PDE_ru.properties b/build/shared/lib/languages/PDE_ru.properties
index 4995272df2..6673cf6421 100644
--- a/build/shared/lib/languages/PDE_ru.properties
+++ b/build/shared/lib/languages/PDE_ru.properties
@@ -377,6 +377,9 @@ editor.status.uninitialized_variable = Локальная переменная "
editor.status.no_effect_assignment = Присвоение переменной "%s" не имеет эффекта
editor.status.hiding_enclosing_type = Класс "%s" не может иметь имя наброска
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error.syntax = Синтаксическая ошибка - %s
+
# Footer buttons
editor.footer.errors = Ошибки
editor.footer.errors.problem = Проблема
diff --git a/build/shared/lib/languages/PDE_tr.properties b/build/shared/lib/languages/PDE_tr.properties
index 8773b8860a..9b40ea0708 100644
--- a/build/shared/lib/languages/PDE_tr.properties
+++ b/build/shared/lib/languages/PDE_tr.properties
@@ -223,6 +223,9 @@ editor.header.next_tab = Sonraki Sekme
editor.header.delete.warning.title = Evet, hayır.
editor.header.delete.warning.text = Aktif sketchteki son sekmeyi silemezsin.
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error = Hata
+editor.status.error.syntax = Hata - %s
# ---------------------------------------
# Contribution Panel
diff --git a/build/shared/lib/languages/PDE_uk.properties b/build/shared/lib/languages/PDE_uk.properties
index 8a34b5a39f..68236008b0 100644
--- a/build/shared/lib/languages/PDE_uk.properties
+++ b/build/shared/lib/languages/PDE_uk.properties
@@ -376,6 +376,9 @@ editor.status.unused_variable = Локальна змінна "%s" ніде не
editor.status.uninitialized_variable = Локальна змінна "%s" може бути не ініціалізована
editor.status.no_effect_assignment = Присвоєння змінної "%s" не має чинності
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error.syntax = Помилка - %s
+
# Footer buttons
editor.footer.errors = Помилки
editor.footer.errors.problem = Проблема
diff --git a/build/shared/lib/languages/PDE_zh.properties b/build/shared/lib/languages/PDE_zh.properties
index 8cf6da2166..b438e3acb6 100644
--- a/build/shared/lib/languages/PDE_zh.properties
+++ b/build/shared/lib/languages/PDE_zh.properties
@@ -257,6 +257,10 @@ editor.header.next_tab = 后一个标签
editor.header.delete.warning.title = 这样不行
editor.header.delete.warning.text = 无法删除最后一个速写本的最后一个标签
+# Limited syntax error support, Wikipedia CC BY-SA
+editor.status.error = 錯誤
+editor.status.error.syntax = 语法错误 - %s
+
# Tabs
editor.tab.new = 新文件名
editor.tab.new.description = 新文件名称
diff --git a/build/windows/config-cmd.xml b/build/windows/config-cmd.xml
index 527c0d75e8..8036f2b968 100755
--- a/build/windows/config-cmd.xml
+++ b/build/windows/config-cmd.xml
@@ -34,12 +34,10 @@
-Djna.nosys=true
-
- -Dsun.java2d.uiScale=11.8.0_60
- 256
+ 512
diff --git a/build/windows/config.xml b/build/windows/config.xml
index e2e69575f5..e2ff35461b 100755
--- a/build/windows/config.xml
+++ b/build/windows/config.xml
@@ -19,7 +19,7 @@
core/library/core.jarlib/jna.jarlib/jna-platform.jar
- lib/antlr.jar
+ lib/antlr-4.7.2-complete.jarlib/ant.jarlib/ant-launcher.jar
@@ -44,12 +44,10 @@
-Dsun.java2d.d3d=false-Dsun.java2d.ddoffscreen=false-Dsun.java2d.noddraw=true
-
- -Dsun.java2d.uiScale=11.8.0_60
- 256
+ 512about.bmp
diff --git a/build/windows/processing.bat b/build/windows/processing.bat
index 8f71b553dd..ab0a36bf27 100755
--- a/build/windows/processing.bat
+++ b/build/windows/processing.bat
@@ -1,3 +1,3 @@
@echo off
-.\java\bin\java -cp lib\pde.jar;core\library\core.jar;lib\jna.jar;lib\jna-platform.jar;lib\antlr.jar;lib\ant.jar;lib\ant-launcher.jar processing.app.Base
+.\java\bin\java -cp lib\pde.jar;core\library\core.jar;lib\jna.jar;lib\jna-platform.jar;lib\antlr-4.7.2-complete.jar;lib\ant.jar;lib\ant-launcher.jar processing.app.Base
diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java
index db2f562ba4..3b1612fe26 100644
--- a/core/src/processing/core/PApplet.java
+++ b/core/src/processing/core/PApplet.java
@@ -775,6 +775,10 @@ public class PApplet implements PConstants {
// static public final String ARGS_SPAN_DISPLAYS = "--span";
+ static public final String ARGS_BGCOLOR = "--bgcolor";
+
+ static public final String ARGS_FULL_SCREEN = "--full-screen";
+
static public final String ARGS_WINDOW_COLOR = "--window-color";
static public final String ARGS_PRESENT = "--present";
diff --git a/java/.classpath b/java/.classpath
index 839265b88d..ed2b536c42 100644
--- a/java/.classpath
+++ b/java/.classpath
@@ -2,7 +2,7 @@
-
+
@@ -19,6 +19,7 @@
+
diff --git a/java/build.xml b/java/build.xml
index 5edeb36e80..32893549b0 100644
--- a/java/build.xml
+++ b/java/build.xml
@@ -9,7 +9,7 @@
value="${basedir}/src/processing/mode/java/preproc" />
+ value="${basedir}/mode/antlr-4.7.2-complete.jar" />
@@ -24,52 +24,26 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
@@ -80,7 +54,7 @@
-
+
@@ -147,7 +121,7 @@
-
+
@@ -157,6 +131,7 @@
+
diff --git a/java/mode/antlr-4.7.2-complete.jar b/java/mode/antlr-4.7.2-complete.jar
new file mode 100644
index 0000000000..9794f19b2c
Binary files /dev/null and b/java/mode/antlr-4.7.2-complete.jar differ
diff --git a/java/src/antlr/ExtendedCommonASTWithHiddenTokens.java b/java/src/antlr/ExtendedCommonASTWithHiddenTokens.java
deleted file mode 100644
index 1bcf2911da..0000000000
--- a/java/src/antlr/ExtendedCommonASTWithHiddenTokens.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package antlr;
-
-/* ANTLR Translator Generator
- * Project led by Terence Parr at http://www.jGuru.com
- * Software rights: http://www.antlr.org/RIGHTS.html
- *
- * $Id$
- */
-
-import java.io.*;
-
-import antlr.collections.*;
-
-
-/** A CommonAST whose initialization copies hidden token
- * information from the Token used to create a node.
- */
-public class ExtendedCommonASTWithHiddenTokens
- extends CommonASTWithHiddenTokens {
-
- public ExtendedCommonASTWithHiddenTokens() {
- super();
- }
-
- public ExtendedCommonASTWithHiddenTokens(Token tok) {
- super(tok);
- }
-
- public void initialize(AST ast) {
- ExtendedCommonASTWithHiddenTokens a =
- (ExtendedCommonASTWithHiddenTokens)ast;
- super.initialize(a);
- hiddenBefore = a.getHiddenBefore();
- hiddenAfter = a.getHiddenAfter();
- }
-
- public String getHiddenAfterString() {
-
- CommonHiddenStreamToken t;
- StringBuilder hiddenAfterString = new StringBuilder(100);
-
- for ( t = hiddenAfter ; t != null ; t = t.getHiddenAfter() ) {
- hiddenAfterString.append(t.getText());
- }
-
- return hiddenAfterString.toString();
- }
-
- public String getHiddenBeforeString() {
-
- antlr.CommonHiddenStreamToken
- child = null,
- parent = hiddenBefore;
-
- // if there aren't any hidden tokens here, quietly return
- //
- if (parent == null) {
- return "";
- }
-
- // traverse back to the head of the list of tokens before this node
- do {
- child = parent;
- parent = child.getHiddenBefore();
- } while (parent != null);
-
- // dump that list
-
- StringBuilder hiddenBeforeString = new StringBuilder(100);
-
- for ( CommonHiddenStreamToken t = child; t != null ;
- t = t.getHiddenAfter() ) {
- hiddenBeforeString.append(t.getText());
- }
-
- return hiddenBeforeString.toString();
- }
-
- public void xmlSerializeNode(Writer out) throws IOException {
- StringBuilder sb = new StringBuilder(100);
- sb.append("<");
- sb.append(getClass().getName() + " ");
-
- sb.append("hiddenBeforeString=\"" +
- encode(getHiddenBeforeString()) +
- "\" text=\"" + encode(getText()) + "\" type=\"" +
- getType() + "\" hiddenAfterString=\"" +
- encode(getHiddenAfterString()) + "\"/>");
- out.write(sb.toString());
- }
-
- public void xmlSerializeRootOpen(Writer out) throws IOException {
- StringBuilder sb = new StringBuilder(100);
- sb.append("<");
- sb.append(getClass().getName() + " ");
- sb.append("hiddenBeforeString=\"" +
- encode(getHiddenBeforeString()) +
- "\" text=\"" + encode(getText()) + "\" type=\"" +
- getType() + "\" hiddenAfterString=\"" +
- encode(getHiddenAfterString()) + "\">\n");
- out.write(sb.toString());
- }
-
- public void xmlSerializeRootClose(Writer out)
- throws IOException {
- out.write("" + getClass().getName() + ">\n");
- }
-
- public void xmlSerialize(Writer out) throws IOException {
- // print out this node and all siblings
- for (AST node = this;
- node != null;
- node = node.getNextSibling()) {
- if (node.getFirstChild() == null) {
- // print guts (class name, attributes)
- ((BaseAST)node).xmlSerializeNode(out);
- }
- else {
- ((BaseAST)node).xmlSerializeRootOpen(out);
-
- // print children
- ((BaseAST)node.getFirstChild()).xmlSerialize(out);
-
- // print end tag
- ((BaseAST)node).xmlSerializeRootClose(out);
- }
- }
- }
-
-}
diff --git a/java/src/antlr/TokenStreamCopyingHiddenTokenFilter.java b/java/src/antlr/TokenStreamCopyingHiddenTokenFilter.java
deleted file mode 100644
index 82055ea5cb..0000000000
--- a/java/src/antlr/TokenStreamCopyingHiddenTokenFilter.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-package antlr;
-
-
-import antlr.collections.impl.BitSet;
-
-/**
- * This class provides TokenStreamHiddenTokenFilters with the concept of
- * tokens which can be copied so that they are seen by both the hidden token
- * stream as well as the parser itself. This is useful when one wants to use
- * an existing parser (like the Java parser included with ANTLR) that throws
- * away some tokens to create a parse tree which can be used to spit out
- * a copy of the code with only minor modifications.
- *
- * This code is partially derived from the public domain ANLTR TokenStream
- */
-public class TokenStreamCopyingHiddenTokenFilter
- extends TokenStreamHiddenTokenFilter
- implements TokenStream {
-
- protected BitSet copyMask;
- CommonHiddenStreamToken hiddenCopy = null;
-
- public TokenStreamCopyingHiddenTokenFilter(TokenStream input) {
- super(input);
- copyMask = new BitSet();
- }
-
- /**
- * Indicate that all tokens of type tokenType should be copied. The copy
- * is put in the stream of hidden tokens, and the original is returned in the
- * stream of normal tokens.
- *
- * @param tokenType integer representing the token type to copied
- */
- public void copy(int tokenType) {
- copyMask.add(tokenType);
- }
-
- /**
- * Create a clone of the important parts of the given token. Note that this
- * does NOT copy the hiddenBefore and hiddenAfter fields.
- *
- * @param t token to partially clone
- * @return newly created partial clone
- */
- public CommonHiddenStreamToken partialCloneToken(CommonHiddenStreamToken t) {
-
- CommonHiddenStreamToken u = new CommonHiddenStreamToken(t.getType(),
- t.getText());
- u.setColumn(t.getColumn());
- u.setLine(t.getLine());
- u.setFilename(t.getFilename());
-
- return u;
- }
-
- public void linkAndCopyToken(CommonHiddenStreamToken prev,
- CommonHiddenStreamToken monitored) {
- // create a copy of the token in the lookahead for use as hidden token
- hiddenCopy = partialCloneToken(LA(1));
-
- // attach copy to the previous token, whether hidden or monitored
- prev.setHiddenAfter(hiddenCopy);
-
- // if previous token was hidden, set the hiddenBefore pointer of the
- // copy to point back to it
- if (prev != monitored) {
- hiddenCopy.setHiddenBefore(prev);
- }
-
- // we don't want the non-hidden copy to link back to the hidden
- // copy on the next pass through this function, so we leave
- // lastHiddenToken alone
-
- //System.err.println("hidden copy: " + hiddenCopy.toString());
-
- return;
- }
-
- private void consumeFirst() throws TokenStreamException {
- consume(); // get first token of input stream
-
- // Handle situation where hidden or discarded tokens
- // appear first in input stream
- CommonHiddenStreamToken p=null;
-
- // while hidden, copied, or discarded scarf tokens
- while ( hideMask.member(LA(1).getType()) ||
- discardMask.member(LA(1).getType()) ||
- copyMask.member(LA(1).getType()) ) {
-
- // if we've hit one of the tokens that needs to be copied, we copy it
- // and then break out of the loop, because the parser needs to see it
- // too
- //
- if (copyMask.member(LA(1).getType())) {
-
- // copy the token in the lookahead
- hiddenCopy = partialCloneToken(LA(1));
-
- // if there's an existing token before this, link that and the
- // copy together
- if (p != null) {
- p.setHiddenAfter(hiddenCopy);
- hiddenCopy.setHiddenBefore(p); // double-link
- }
-
- lastHiddenToken = hiddenCopy;
- if (firstHidden == null) {
- firstHidden = hiddenCopy;
- }
-
- // we don't want to consume this token, because it also needs to
- // be passed through to the parser, so break out of the while look
- // entirely
- //
- break;
- } else if (hideMask.member(LA(1).getType())) {
- if (p != null) {
- p.setHiddenAfter(LA(1));
- LA(1).setHiddenBefore(p); // double-link
- }
- p = LA(1);
-
- lastHiddenToken = p;
- if (firstHidden == null) {
- firstHidden = p; // record hidden token if first
- }
- }
- consume();
- }
- }
-
- /** Return the next monitored token.
- * Test the token following the monitored token.
- * If following is another monitored token, save it
- * for the next invocation of nextToken (like a single
- * lookahead token) and return it then.
- * If following is unmonitored, nondiscarded (hidden)
- * channel token, add it to the monitored token.
- *
- * Note: EOF must be a monitored Token.
- */
- public Token nextToken() throws TokenStreamException {
- // handle an initial condition; don't want to get lookahead
- // token of this splitter until first call to nextToken
- if (LA(1) == null) {
- consumeFirst();
- }
-
- //System.err.println();
-
- // we always consume hidden tokens after monitored, thus,
- // upon entry LA(1) is a monitored token.
- CommonHiddenStreamToken monitored = LA(1);
-
- // point to hidden tokens found during last invocation
- monitored.setHiddenBefore(lastHiddenToken);
- lastHiddenToken = null;
-
- // Look for hidden tokens, hook them into list emanating
- // from the monitored tokens.
- consume();
- CommonHiddenStreamToken prev = monitored;
-
- // deal with as many not-purely-monitored tokens as possible
- while ( hideMask.member(LA(1).getType()) ||
- discardMask.member(LA(1).getType()) ||
- copyMask.member(LA(1).getType()) ) {
-
- if (copyMask.member(LA(1).getType())) {
-
- // copy the token and link it backwards
- if (hiddenCopy != null) {
- linkAndCopyToken(hiddenCopy, monitored);
- } else {
- linkAndCopyToken(prev, monitored);
- }
-
- // we now need to parse it as a monitored token, so we return, which
- // avoids the consume() call at the end of this loop. the next call
- // will parse it as a monitored token.
- //System.err.println("returned: " + monitored.toString());
- return monitored;
-
- } else if (hideMask.member(LA(1).getType())) {
-
- // attach the hidden token to the monitored in a chain
- // link forwards
- prev.setHiddenAfter(LA(1));
-
- // link backwards
- if (prev != monitored) { //hidden cannot point to monitored tokens
- LA(1).setHiddenBefore(prev);
- } else if (hiddenCopy != null) {
- hiddenCopy.setHiddenAfter(LA(1));
- LA(1).setHiddenBefore(hiddenCopy);
- hiddenCopy = null;
- }
-
- //System.err.println("hidden: " + prev.getHiddenAfter().toString() + "\" after: " + prev.toString());
- prev = lastHiddenToken = LA(1);
- }
-
- consume();
- }
-
- // remember the last hidden token for next time around
- if (hiddenCopy != null) {
- lastHiddenToken = hiddenCopy;
- hiddenCopy = null;
- }
-
- //System.err.println("returned: " + monitored.toString());
- return monitored;
- }
-}
diff --git a/java/src/processing/mode/java/AutoFormat.java b/java/src/processing/mode/java/AutoFormat.java
index 8aa3cbeaa8..0e0e41247c 100644
--- a/java/src/processing/mode/java/AutoFormat.java
+++ b/java/src/processing/mode/java/AutoFormat.java
@@ -854,7 +854,17 @@ else if (forFlag) {
if (buf.length() > 0) writeIndentedLine();
- final String formatted = result.toString();
+ final String formatted = simpleRegexCleanup(result.toString());
return formatted.equals(cleanText) ? source : formatted;
}
+
+ /**
+ * Make minor regex-based find / replace changes to execute simple fixes to limited artifacts.
+ *
+ * @param result The code to format.
+ * @return The formatted code.
+ */
+ private String simpleRegexCleanup(String result) {
+ return result.replaceAll("([^ \n]+) +\n", "$1\n"); // Remove trail whitespace
+ }
}
diff --git a/java/src/processing/mode/java/Compiler.java b/java/src/processing/mode/java/Compiler.java
index cfe9eb97d9..9bc4baa9e2 100644
--- a/java/src/processing/mode/java/Compiler.java
+++ b/java/src/processing/mode/java/Compiler.java
@@ -62,14 +62,17 @@ static public boolean compile(JavaBuild build) throws SketchException {
SketchException exception = null;
boolean success = false;
+ String classpath = build.getClassPath();
+ String classpathEmptyRemoved = classpath.replace("::", ":");
+
String baseCommand[] = new String[] {
"-g",
"-Xemacs",
//"-noExit", // not necessary for ecj
- "-source", "1.7",
- "-target", "1.7",
+ "-source", "11",
+ "-target", "11",
"-encoding", "utf8",
- "-classpath", build.getClassPath(),
+ "-classpath", classpathEmptyRemoved,
"-nowarn", // we're not currently interested in warnings (works in ecj)
"-d", build.getBinFolder().getAbsolutePath() // output the classes in the buildPath
};
diff --git a/java/src/processing/mode/java/JavaBuild.java b/java/src/processing/mode/java/JavaBuild.java
index b50b1816d3..5c3e79016b 100644
--- a/java/src/processing/mode/java/JavaBuild.java
+++ b/java/src/processing/mode/java/JavaBuild.java
@@ -24,10 +24,8 @@
package processing.mode.java;
import java.io.*;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
@@ -37,28 +35,15 @@
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
-import processing.app.Base;
-import processing.app.Language;
-import processing.app.Library;
-import processing.app.Messages;
-import processing.app.Mode;
-import processing.app.Platform;
-import processing.app.Preferences;
-import processing.app.Sketch;
-import processing.app.SketchCode;
-import processing.app.SketchException;
-import processing.app.Util;
+import processing.app.*;
import processing.app.exec.ProcessHelper;
import processing.core.PApplet;
import processing.core.PConstants;
import processing.data.StringList;
import processing.data.XML;
-import processing.mode.java.pdex.SourceUtils;
-import processing.mode.java.pdex.util.runtime.RuntimeConst;
-import processing.mode.java.pdex.util.runtime.strategy.JavaFxRuntimePathFactory;
+import processing.mode.java.pdex.util.ProblemFactory;
import processing.mode.java.preproc.PdePreprocessor;
import processing.mode.java.preproc.PreprocessorResult;
-import processing.mode.java.preproc.SurfaceInfo;
public class JavaBuild {
@@ -100,7 +85,7 @@ public JavaBuild(Sketch sketch) {
/**
* Run the build inside a temporary build folder. Used for run/present.
* @return null if compilation failed, main class name if not
- * @throws RunnerException
+ * @throws SketchException
*/
public String build(boolean sizeWarning) throws SketchException {
return build(sketch.makeTempFolder(), sketch.makeTempFolder(), sizeWarning);
@@ -150,7 +135,7 @@ public String getSketchClassName() {
* with purty set to false to make sure there are no errors, then once
* successful, re-export with purty set to true.
*
- * @param buildPath Location to copy all the .java files
+ * @param srcFolder Location to copy all the .java files
* @return null if compilation failed, main class name if not
*/
public String preprocess(File srcFolder, boolean sizeWarning) throws SketchException {
@@ -201,189 +186,64 @@ public String preprocess(File srcFolder,
StringBuilder bigCode = new StringBuilder();
int bigCount = 0;
+ List linesPerTab = new ArrayList<>();
for (SketchCode sc : sketch.getCode()) {
if (sc.isExtension("pde")) {
sc.setPreprocOffset(bigCount);
bigCode.append(sc.getProgram());
bigCode.append('\n');
+ linesPerTab.add(bigCount);
bigCount += sc.getLineCount();
}
}
-
- // initSketchSize() sets the internal sketchWidth/Height/Renderer vars
- // in the preprocessor. Those are used in preproc.write() so that they
- // can be used to add methods (settings() or sketchXxxx())
- //String[] sizeParts =
- SurfaceInfo sizeInfo =
- preprocessor.initSketchSize(sketch.getMainProgram(), sizeWarning);
- if (sizeInfo == null) {
- // An error occurred while trying to pull out the size, so exit here
- return null;
- }
- //System.out.format("size() is '%s'%n", info[0]);
-
- // Remove the entries being moved to settings(). They will be re-inserted
- // by writeFooter() when it emits the settings() method.
- // If the user already has a settings() method, don't mess with anything.
- // https://github.com/processing/processing/issues/4703
- if (!PdePreprocessor.hasSettingsMethod(bigCode.toString()) &&
- sizeInfo != null && sizeInfo.hasSettings()) {
- for (String stmt : sizeInfo.getStatements()) {
- // Don't remove newlines (and while you're at it, just keep spaces)
- // https://github.com/processing/processing/issues/3654
- stmt = stmt.trim();
- int index = bigCode.indexOf(stmt);
- if (index != -1) {
- bigCode.delete(index, index + stmt.length());
- } else {
- // TODO remove once we hit final; but prevent an exception like in
- // https://github.com/processing/processing/issues/3531
- System.err.format("Error removing '%s' from the code.", stmt);
- }
- }
- }
+ linesPerTab.add(bigCount);
+
+// // initSketchSize() sets the internal sketchWidth/Height/Renderer vars
+// // in the preprocessor. Those are used in preproc.write() so that they
+// // can be turned into sketchXxxx() methods.
+// // This also returns the size info as an array so that we can figure out
+// // if this fella is OpenGL, and if so, to add the import. It's messy and
+// // gross and someday we'll just always include OpenGL.
+// String[] sizeInfo =
+
+/* next line commented out for ANTLR 4 - PdePreprocessor now does this when
+ * walking the tree
+ */
+// preprocessor.initSketchSize(sketch.getMainProgram(), sizeWarning);
+
+// //PdePreprocessor.parseSketchSize(sketch.getMainProgram(), false);
+// if (sizeInfo != null) {
+// String sketchRenderer = sizeInfo[3];
+// if (sketchRenderer != null) {
+// if (sketchRenderer.equals("P2D") ||
+// sketchRenderer.equals("P3D") ||
+// sketchRenderer.equals("OPENGL")) {
+// bigCode.insert(0, "import processing.opengl.*; ");
+// }
+// }
+// }
PreprocessorResult result;
try {
File outputFolder = (packageName == null) ?
- srcFolder : new File(srcFolder, packageName.replace('.', '/'));
+ srcFolder : new File(srcFolder, packageName.replace('.', '/'));
outputFolder.mkdirs();
+// Base.openFolder(outputFolder);
final File java = new File(outputFolder, sketch.getName() + ".java");
+ final PrintWriter stream = new PrintWriter(new FileWriter(java, StandardCharsets.UTF_8));
try {
- final PrintWriter writer = PApplet.createWriter(java);
- try {
- result = preprocessor.write(writer, bigCode.toString(), codeFolderPackages);
- } finally {
- writer.close();
- }
- } catch (RuntimeException re) {
- re.printStackTrace();
- throw new SketchException("Could not write " + java.getAbsolutePath());
- }
- } catch (antlr.RecognitionException re) {
- // re also returns a column that we're not bothering with for now
- // first assume that it's the main file
-// int errorFile = 0;
- int errorLine = re.getLine() - 1;
-
- // then search through for anyone else whose preprocName is null,
- // since they've also been combined into the main pde.
- int errorFile = findErrorFile(errorLine);
- errorLine -= sketch.getCode(errorFile).getPreprocOffset();
-
- String msg = re.getMessage();
-
- if (msg.contains("expecting RCURLY") || msg.contains("expecting LCURLY")) {
- for (int i = 0; i < sketch.getCodeCount(); i++) {
- SketchCode sc = sketch.getCode(i);
- if (sc.isExtension("pde")) {
- String s = sc.getProgram();
- int[] braceTest = SourceUtils.checkForMissingBraces(
- SourceUtils.scrubCommentsAndStrings(s) + "\n", 0, s.length()+1);
- if (braceTest[0] == 0) continue;
-
- // Completely ignoring the errorFile/errorLine given since it's
- // likely to be the wrong tab. For the same reason, I'm not showing
- // the result of PApplet.match(msg, "found ('.*')") on missing
- // LCURLY.
- throw new SketchException(braceTest[0] > 0
- ? "Found an extra { character without a } to match it."
- : "Found an extra } character without a { to match it.",
- i, braceTest[1], braceTest[2], false);
- }
- }
- // If we're still here, there's the right brackets, just not in the
- // right place. Passing on the original error.
- throw new SketchException(
- msg.replace("LCURLY", "{").replace("RCURLY", "}"),
- errorFile, errorLine, re.getColumn(), false);
- }
-
- if (msg.indexOf("expecting RBRACK") != -1) {
- System.err.println(msg);
- throw new SketchException("Syntax error, " +
- "maybe a missing ] character?",
- errorFile, errorLine, re.getColumn(), false);
- }
-
- if (msg.indexOf("expecting SEMI") != -1) {
- System.err.println(msg);
- throw new SketchException("Syntax error, " +
- "maybe a missing semicolon?",
- errorFile, errorLine, re.getColumn(), false);
- }
-
- if (msg.indexOf("expecting RPAREN") != -1) {
- System.err.println(msg);
- throw new SketchException("Syntax error, " +
- "maybe a missing right parenthesis?",
- errorFile, errorLine, re.getColumn(), false);
- }
-
- if (msg.indexOf("preproc.web_colors") != -1) {
- throw new SketchException("A web color (such as #ffcc00) " +
- "must be six digits.",
- errorFile, errorLine, re.getColumn(), false);
- }
-
- //System.out.println("msg is " + msg);
- throw new SketchException(msg, errorFile,
- errorLine, re.getColumn(), false);
-
- } catch (antlr.TokenStreamRecognitionException tsre) {
- // while this seems to store line and column internally,
- // there doesn't seem to be a method to grab it..
- // so instead it's done using a regexp
-
-// System.err.println("and then she tells me " + tsre.toString());
- // TODO not tested since removing ORO matcher.. ^ could be a problem
- String locationRegex = "^line (\\d+):(\\d+):\\s";
- String message = tsre.getMessage();
- String[] m;
-
- if (null != (m = PApplet.match(tsre.toString(),
- "unexpected char: (.*)"))) {
- char c = 0;
- if (m[1].startsWith("0x")) { // Hex
- c = (char) PApplet.unhex(m[1].substring(2));
- } else if (m[1].length() == 3) { // Quoted
- c = m[1].charAt(1);
- } else if (m[1].length() == 1) { // Alone
- c = m[1].charAt(0);
- }
- if (c == '\u201C' || c == '\u201D' || // “”
- c == '\u2018' || c == '\u2019') { // ‘’
- message = Language.interpolate("editor.status.bad_curly_quote", c);
- } else if (c != 0) {
- message = "Not expecting symbol " + m[1] +
- ", which is " + Character.getName(c) + ".";
- }
- }
-
- String[] matches = PApplet.match(tsre.toString(), locationRegex);
- if (matches != null) {
- int errorLine = Integer.parseInt(matches[1]) - 1;
- int errorColumn = Integer.parseInt(matches[2]);
-
- int errorFile = 0;
- for (int i = 1; i < sketch.getCodeCount(); i++) {
- SketchCode sc = sketch.getCode(i);
- if (sc.isExtension("pde") &&
- (sc.getPreprocOffset() < errorLine)) {
- errorFile = i;
- }
- }
- errorLine -= sketch.getCode(errorFile).getPreprocOffset();
-
- throw new SketchException(message,
- errorFile, errorLine, errorColumn);
-
- } else {
- // this is bad, defaults to the main class.. hrm.
- String msg = tsre.toString();
- throw new SketchException(msg, 0, -1, -1);
+ result = preprocessor.write(
+ stream,
+ bigCode.toString(),
+ codeFolderPackages
+ );
+ } finally {
+ stream.close();
}
-
+ } catch (FileNotFoundException fnfe) {
+ fnfe.printStackTrace();
+ String msg = "Build folder disappeared or could not be written";
+ throw new SketchException(msg);
} catch (SketchException pe) {
// RunnerExceptions are caught here and re-thrown, so that they don't
// get lost in the more general "Exception" handler below.
@@ -396,6 +256,20 @@ public String preprocess(File srcFolder,
throw new SketchException(ex.toString());
}
+ if (result.getPreprocessIssues().size() > 0) {
+ Problem problem = ProblemFactory.build(
+ result.getPreprocessIssues().get(0),
+ linesPerTab
+ );
+
+ throw new SketchException(
+ problem.getMessage(),
+ problem.getTabIndex(),
+ problem.getLineNumber() - 1,
+ 0
+ );
+ }
+
// grab the imports from the code just preprocessed
importedLibraries = new ArrayList<>();
@@ -406,7 +280,7 @@ public String preprocess(File srcFolder,
javaLibraryPath += File.pathSeparator + core.getNativePath();
}
- for (String item : result.extraImports) {
+ for (String item : result.getImportStatementsStr()) {
// remove things up to the last dot
int dot = item.lastIndexOf('.');
// http://dev.processing.org/bugs/show_bug.cgi?id=1145
@@ -464,7 +338,6 @@ public String preprocess(File srcFolder,
//String[] classPieces = PApplet.split(classPath, File.pathSeparator);
// Nah, nevermind... we'll just create the @!#$! folder until they fix it.
-
// 3. then loop over the code[] and save each .java file
for (SketchCode sc : sketch.getCode()) {
@@ -494,7 +367,7 @@ public String preprocess(File srcFolder,
} else {
if (packageMatch == null) {
// use the default package name, since mixing with package-less code will break
- packageMatch = new String[] { "", packageName };
+ packageMatch = new String[]{"", packageName};
// add the package name to the source before writing it
javaCode = "package " + packageName + ";" + javaCode;
}
@@ -502,7 +375,6 @@ public String preprocess(File srcFolder,
packageFolder.mkdirs();
Util.saveFile(javaCode, new File(packageFolder, filename));
}
-
} catch (IOException e) {
e.printStackTrace();
String msg = "Problem moving " + filename + " to the build folder";
@@ -511,11 +383,11 @@ public String preprocess(File srcFolder,
} else if (sc.isExtension("pde")) {
// The compiler and runner will need this to have a proper offset
- sc.addPreprocOffset(result.headerOffset);
+ sc.addPreprocOffset(result.getHeaderOffset());
}
}
- foundMain = preprocessor.hasMethod("main");
- return result.className;
+ foundMain = preprocessor.hasMain();
+ return result.getClassName();
}
@@ -612,8 +484,8 @@ public List getImportedLibraries() {
* Map an error from a set of processed .java files back to its location
* in the actual sketch.
* @param message The error message.
- * @param filename The .java file where the exception was found.
- * @param line Line number of the .java file for the exception (0-indexed!)
+ * @param dotJavaFilename The .java file where the exception was found.
+ * @param dotJavaLine Line number of the .java file for the exception (0-indexed!)
* @return A RunnerException to be sent to the editor, or null if it wasn't
* possible to place the exception to the sketch code.
*/
@@ -623,8 +495,8 @@ public SketchException placeException(String message,
int codeIndex = 0; //-1;
int codeLine = -1;
-// System.out.println("placing " + dotJavaFilename + " " + dotJavaLine);
-// System.out.println("code count is " + getCodeCount());
+ //System.out.println(message + " placing " + dotJavaFilename + " " + dotJavaLine);
+ //System.out.println("code count is " + getCodeCount());
// first check to see if it's a .java file
for (int i = 0; i < sketch.getCodeCount(); i++) {
@@ -650,8 +522,8 @@ public SketchException placeException(String message,
SketchCode code = sketch.getCode(i);
if (code.isExtension("pde")) {
-// System.out.println("preproc offset is " + code.getPreprocOffset());
-// System.out.println("looking for line " + dotJavaLine);
+ //System.out.println("preproc offset is " + code.getPreprocOffset());
+ //System.out.println("looking for line " + dotJavaLine);
if (code.getPreprocOffset() <= dotJavaLine) {
codeIndex = i;
// System.out.println("i'm thinkin file " + i);
@@ -976,10 +848,6 @@ protected boolean exportApplication(File destFolder,
// https://github.com/processing/processing/issues/2559
if (exportPlatform == PConstants.WINDOWS) {
runOptions.append("-Djava.library.path=\"%EXEDIR%\\lib\"");
-
- // No scaling of swing (see #5753) on zoomed displays until some issues regarding JEP 263
- // with rendering artifacts are sorted out.
- runOptions.append("-Dsun.java2d.uiScale=1");
}
diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java
index 1e68198fdf..61fd151444 100644
--- a/java/src/processing/mode/java/JavaEditor.java
+++ b/java/src/processing/mode/java/JavaEditor.java
@@ -2296,16 +2296,19 @@ public void updateErrorTable(List problems) {
errorTable.clearRows();
for (Problem p : problems) {
- JavaProblem jp = (JavaProblem) p;
String message = p.getMessage();
- if (JavaMode.importSuggestEnabled &&
- jp.getImportSuggestions() != null &&
- jp.getImportSuggestions().length > 0) {
- message += " (double-click for suggestions)";
+
+ if (p.getClass().equals(JavaProblem.class)) {
+ JavaProblem jp = (JavaProblem) p;
+ if (JavaMode.importSuggestEnabled &&
+ jp.getImportSuggestions() != null &&
+ jp.getImportSuggestions().length > 0) {
+ message += " (double-click for suggestions)";
+ }
}
errorTable.addRow(p, message,
- sketch.getCode(jp.getTabIndex()).getPrettyName(),
+ sketch.getCode(p.getTabIndex()).getPrettyName(),
Integer.toString(p.getLineNumber() + 1));
// Added +1 because lineNumbers internally are 0-indexed
}
@@ -2314,6 +2317,10 @@ public void updateErrorTable(List problems) {
@Override
public void errorTableDoubleClick(Object item) {
+ if (!item.getClass().equals(JavaProblem.class)) {
+ errorTableClick(item);
+ }
+
JavaProblem p = (JavaProblem) item;
// MouseEvent evt = null;
@@ -2677,7 +2684,6 @@ protected boolean automateSketch(Sketch sketch, SketchParser parser) {
}
}
-
// Copy current program to interactive program
// modify the code below, replace all numbers with their variable names
// loop through all tabs in the current sketch
diff --git a/java/src/processing/mode/java/debug/LineID.java b/java/src/processing/mode/java/debug/LineID.java
index 251a29ceee..35f1b79c11 100644
--- a/java/src/processing/mode/java/debug/LineID.java
+++ b/java/src/processing/mode/java/debug/LineID.java
@@ -240,7 +240,7 @@ protected static int nonWhiteSpaceOffset(String str) {
* is edited. This happens when text is inserted or removed.
*/
protected void editEvent(DocumentEvent de) {
- //System.out.println("document edit @ " + de.getOffset());
+ //System.out.println("document edit @ " + de.getCharPosition());
if (de.getOffset() <= pos.getOffset()) {
updatePosition();
//System.out.println("updating, new line no: " + lineNo);
diff --git a/java/src/processing/mode/java/pdex/ErrorMessageSimplifier.java b/java/src/processing/mode/java/pdex/CompileErrorMessageSimplifier.java
similarity index 99%
rename from java/src/processing/mode/java/pdex/ErrorMessageSimplifier.java
rename to java/src/processing/mode/java/pdex/CompileErrorMessageSimplifier.java
index f165798d0b..2e48d9b3ed 100644
--- a/java/src/processing/mode/java/pdex/ErrorMessageSimplifier.java
+++ b/java/src/processing/mode/java/pdex/CompileErrorMessageSimplifier.java
@@ -35,7 +35,7 @@
import processing.data.StringList;
-public class ErrorMessageSimplifier {
+public class CompileErrorMessageSimplifier {
/**
* Mapping between ProblemID constant and the constant name. Holds about 650
* of them. Also, this is just temporary, will be used to find the common
diff --git a/java/src/processing/mode/java/pdex/ErrorChecker.java b/java/src/processing/mode/java/pdex/ErrorChecker.java
index 246eb75d00..9db5c0f1fa 100644
--- a/java/src/processing/mode/java/pdex/ErrorChecker.java
+++ b/java/src/processing/mode/java/pdex/ErrorChecker.java
@@ -84,14 +84,22 @@ public void dispose() {
private void handleSketchProblems(PreprocessedSketch ps) {
+
Map suggCache =
JavaMode.importSuggestEnabled ? new HashMap<>() : Collections.emptyMap();
final List problems = new ArrayList<>();
- IProblem[] iproblems = ps.compilationUnit.getProblems();
+ IProblem[] iproblems;
+ if (ps.compilationUnit == null) {
+ iproblems = new IProblem[0];
+ } else {
+ iproblems = ps.compilationUnit.getProblems();
+ }
- { // Check for curly quotes
+ problems.addAll(ps.otherProblems);
+
+ if (problems.isEmpty()) { // Check for curly quotes
List curlyQuoteProblems = checkForCurlyQuotes(ps);
problems.addAll(curlyQuoteProblems);
}
@@ -188,6 +196,10 @@ static private boolean isMissingBraceProblem(IProblem iproblem) {
Pattern.compile("([“”‘’])", Pattern.UNICODE_CHARACTER_CLASS);
static private List checkForCurlyQuotes(PreprocessedSketch ps) {
+ if (ps.compilationUnit == null) {
+ return new ArrayList<>();
+ }
+
List problems = new ArrayList<>(0);
// Go through the scrubbed code and look for curly quotes (they should not be any)
diff --git a/java/src/processing/mode/java/pdex/JavaProblem.java b/java/src/processing/mode/java/pdex/JavaProblem.java
index b8ef76c0ae..06f3953219 100644
--- a/java/src/processing/mode/java/pdex/JavaProblem.java
+++ b/java/src/processing/mode/java/pdex/JavaProblem.java
@@ -82,7 +82,7 @@ public static JavaProblem fromIProblem(IProblem iProblem,
} else if (iProblem.isWarning()) {
type = WARNING;
}
- String message = ErrorMessageSimplifier.getSimplifiedErrorMessage(iProblem, badCode);
+ String message = CompileErrorMessageSimplifier.getSimplifiedErrorMessage(iProblem, badCode);
return new JavaProblem(message, type, tabIndex, lineNumber);
}
diff --git a/java/src/processing/mode/java/pdex/JdtCompilerUtil.java b/java/src/processing/mode/java/pdex/JdtCompilerUtil.java
new file mode 100644
index 0000000000..7c938f24c6
--- /dev/null
+++ b/java/src/processing/mode/java/pdex/JdtCompilerUtil.java
@@ -0,0 +1,106 @@
+package processing.mode.java.pdex;
+
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Utility to help run a compilation through the JDT.
+ */
+public class JdtCompilerUtil {
+
+ /**
+ * Create a JDT compilation unit.
+ *
+ * @param parser The parser to use to read the source.
+ * @param source The source after processing with ANTLR.
+ * @param options The JDT compiler options.
+ * @return The JDT parsed compilation unit.
+ */
+ public static CompilationUnit makeAST(ASTParser parser,
+ char[] source,
+ Map options) {
+ parser.setSource(source);
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+ parser.setCompilerOptions(options);
+ parser.setStatementsRecovery(true);
+
+ return (CompilationUnit) parser.createAST(null);
+ }
+
+ /**
+ * Establish parser options before creating a JDT compilation unit.
+ *
+ * @param parser The parser to use to read the source.
+ * @param source The source after processing with ANTLR.
+ * @param options The JDT compiler options.
+ * @param className The name of the sketch.
+ * @param classPath The classpath to use in compliation.
+ * @return The JDT parsed compilation unit.
+ */
+ public static CompilationUnit makeASTWithBindings(ASTParser parser,
+ char[] source,
+ Map options,
+ String className,
+ String[] classPath) {
+ parser.setSource(source);
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+ parser.setCompilerOptions(options);
+ parser.setStatementsRecovery(true);
+ parser.setUnitName(className);
+ parser.setEnvironment(classPath, null, null, false);
+ parser.setResolveBindings(true);
+
+ return (CompilationUnit) parser.createAST(null);
+ }
+
+
+ static public final Map COMPILER_OPTIONS;
+ static {
+ Map compilerOptions = new HashMap<>();
+
+ compilerOptions.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_11);
+ compilerOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_11);
+ compilerOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_11);
+
+ // See http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Fguide%2Fjdt_api_options.htm&anchor=compiler
+
+ final String[] generate = {
+ JavaCore.COMPILER_LINE_NUMBER_ATTR,
+ JavaCore.COMPILER_SOURCE_FILE_ATTR
+ };
+
+ final String[] ignore = {
+ JavaCore.COMPILER_PB_UNUSED_IMPORT,
+ JavaCore.COMPILER_PB_MISSING_SERIAL_VERSION,
+ JavaCore.COMPILER_PB_RAW_TYPE_REFERENCE,
+ JavaCore.COMPILER_PB_REDUNDANT_TYPE_ARGUMENTS,
+ JavaCore.COMPILER_PB_UNCHECKED_TYPE_OPERATION
+ };
+
+ final String[] warn = {
+ JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT,
+ JavaCore.COMPILER_PB_NULL_REFERENCE,
+ JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE,
+ JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK,
+ JavaCore.COMPILER_PB_POSSIBLE_ACCIDENTAL_BOOLEAN_ASSIGNMENT,
+ JavaCore.COMPILER_PB_UNUSED_LABEL,
+ JavaCore.COMPILER_PB_UNUSED_LOCAL,
+ JavaCore.COMPILER_PB_UNUSED_OBJECT_ALLOCATION,
+ JavaCore.COMPILER_PB_UNUSED_PARAMETER,
+ JavaCore.COMPILER_PB_UNUSED_PRIVATE_MEMBER
+ };
+
+ for (String s : generate) compilerOptions.put(s, JavaCore.GENERATE);
+ for (String s : ignore) compilerOptions.put(s, JavaCore.IGNORE);
+ for (String s : warn) compilerOptions.put(s, JavaCore.WARNING);
+
+ COMPILER_OPTIONS = Collections.unmodifiableMap(compilerOptions);
+ }
+
+}
diff --git a/java/src/processing/mode/java/pdex/PreprocessedSketch.java b/java/src/processing/mode/java/pdex/PreprocessedSketch.java
index 3dc328967e..730527982b 100644
--- a/java/src/processing/mode/java/pdex/PreprocessedSketch.java
+++ b/java/src/processing/mode/java/pdex/PreprocessedSketch.java
@@ -12,6 +12,7 @@
import java.util.Collections;
import java.util.List;
+import processing.app.Problem;
import processing.app.Sketch;
import processing.core.PApplet;
import processing.mode.java.pdex.TextTransform.OffsetMapper;
@@ -42,8 +43,7 @@ public class PreprocessedSketch {
public final List programImports;
public final List coreAndDefaultImports;
public final List codeFolderImports;
-
-
+ public final List otherProblems;
/// JAVA -> SKETCH -----------------------------------------------------------
@@ -235,6 +235,7 @@ public static class Builder {
public final List programImports = new ArrayList<>();
public final List coreAndDefaultImports = new ArrayList<>();
public final List codeFolderImports = new ArrayList<>();
+ public final List otherProblems = new ArrayList<>();
public PreprocessedSketch build() {
return new PreprocessedSketch(this);
@@ -267,6 +268,8 @@ private PreprocessedSketch(Builder b) {
hasSyntaxErrors = b.hasSyntaxErrors;
hasCompilationErrors = b.hasCompilationErrors;
+ otherProblems = b.otherProblems;
+
programImports = Collections.unmodifiableList(b.programImports);
coreAndDefaultImports = Collections.unmodifiableList(b.coreAndDefaultImports);
codeFolderImports = Collections.unmodifiableList(b.codeFolderImports);
diff --git a/java/src/processing/mode/java/pdex/PreprocessingService.java b/java/src/processing/mode/java/pdex/PreprocessingService.java
index 8fdaaf43ba..6ee14d30e8 100644
--- a/java/src/processing/mode/java/pdex/PreprocessingService.java
+++ b/java/src/processing/mode/java/pdex/PreprocessingService.java
@@ -21,6 +21,7 @@
package processing.mode.java.pdex;
import java.io.File;
+import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -36,34 +37,42 @@
import javax.swing.text.BadLocationException;
-import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
-import processing.app.Messages;
-import processing.app.Sketch;
-import processing.app.SketchCode;
-import processing.app.Util;
+import processing.app.*;
import processing.data.IntList;
import processing.data.StringList;
import processing.mode.java.JavaEditor;
import processing.mode.java.JavaMode;
import processing.mode.java.pdex.TextTransform.OffsetMapper;
+import processing.mode.java.pdex.util.ProblemFactory;
import processing.mode.java.pdex.util.runtime.RuntimePathBuilder;
import processing.mode.java.preproc.PdePreprocessor;
-import processing.mode.java.preproc.PdePreprocessor.Mode;
+import processing.mode.java.preproc.PreprocessorResult;
+import processing.mode.java.preproc.code.ImportUtil;
+import processing.mode.java.preproc.code.SyntaxUtil;
/**
- * The main error checking service
+ * Service which preprocesses code to check for and report on issues.
+ *
+ *
+ * Service running in a background thread which checks for grammatical issues via ANTLR and performs
+ * code analysis via the JDT to check for other issues and related development services. These are
+ * reported as {Problem} instances via a callback registered by an {Editor}.
+ *
*/
public class PreprocessingService {
+ private final static int TIMEOUT_MILLIS = 100;
+ private final static int BLOCKING_TIMEOUT_SECONDS = 3000;
+
protected final JavaEditor editor;
- protected final ASTParser parser = ASTParser.newParser(AST.JLS8);
+ protected final ASTParser parser = ASTParser.newParser(AST.JLS11);
private final Thread preprocessingThread;
private final BlockingQueue requestQueue = new ArrayBlockingQueue<>(1);
@@ -83,7 +92,12 @@ public class PreprocessingService {
private volatile boolean isEnabled = true;
-
+ /**
+ * Create a new preprocessing service to support an editor.
+ *
+ * @param editor The editor that will be supported by this service and to which issues should be
+ * reported.
+ */
public PreprocessingService(JavaEditor editor) {
this.editor = editor;
isEnabled = !editor.hasJavaTabs();
@@ -95,7 +109,9 @@ public PreprocessingService(JavaEditor editor) {
preprocessingThread.start();
}
-
+ /**
+ * The "main loop" for the background thread that checks for code issues.
+ */
private void mainLoop() {
running = true;
PreprocessedSketch prevResult = null;
@@ -118,7 +134,7 @@ private void mainLoop() {
// If new request arrives while waiting, break out and start preprocessing
while (requestQueue.isEmpty() && runningCallbacks != null) {
try {
- runningCallbacks.get(10, TimeUnit.MILLISECONDS);
+ runningCallbacks.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
runningCallbacks = null;
} catch (TimeoutException e) { }
}
@@ -137,19 +153,25 @@ private void mainLoop() {
Messages.log("PPS: Bye!");
}
-
+ /**
+ * End and clean up the background preprocessing thread.
+ */
public void dispose() {
cancel();
running = false;
preprocessingThread.interrupt();
}
-
+ /**
+ * Cancel any pending code checks.
+ */
public void cancel() {
requestQueue.clear();
}
-
+ /**
+ * Indicate to this service that the sketch code has changed.
+ */
public void notifySketchChanged() {
if (!isEnabled) return;
synchronized (requestLock) {
@@ -162,21 +184,31 @@ public void notifySketchChanged() {
}
}
-
+ /**
+ * Indicate to this service that the sketch libarries have changed.
+ */
public void notifyLibrariesChanged() {
Messages.log("PPS: notified libraries changed");
librariesChanged.set(true);
notifySketchChanged();
}
-
+ /**
+ * Indicate to this service that the folder housing sketch code has changed.
+ */
public void notifyCodeFolderChanged() {
Messages.log("PPS: snotified code folder changed");
codeFolderChanged.set(true);
notifySketchChanged();
}
-
+ /**
+ * Register a callback to be fired when preprocessing is complete.
+ *
+ * @param callback The consumer to inform when preprocessing is complete which will provide a
+ * {PreprocessedSketch} that has any {Problem} instances that were resultant.
+ * @return A future that will be fulfilled when preprocessing is complete.
+ */
private CompletableFuture> registerCallback(Consumer callback) {
synchronized (requestLock) {
lastCallback = preprocessingTask
@@ -191,17 +223,41 @@ private CompletableFuture> registerCallback(Consumer callb
}
}
-
+ /**
+ * Register a callback to be fired when preprocessing is complete if the service is still running.
+ *
+ *
+ * Register a callback to be fired when preprocessing is complete if the service is still running,
+ * turning this into a no-op if it is no longer running. Note that this callback will only be
+ * executed once and it is distinct from registering a listener below which will receive all
+ * future updates.
+ *
+ *
+ * @param callback The consumer to inform when preprocessing is complete which will provide a
+ * {PreprocessedSketch} that has any {Problem} instances that were resultant.
+ */
public void whenDone(Consumer callback) {
if (!isEnabled) return;
registerCallback(callback);
}
-
+ /**
+ * Wait for preprocessing to complete.
+ *
+ *
+ * Register a callback to be fired when preprocessing is complete if the service is still running,
+ * turning this into a no-op if it is no longer running. However, wait up to
+ * BLOCKING_TIMEOUT_SECONDS in a blocking manner until preprocessing is complete.
+ * Note that this callback will only be executed once and it is distinct from registering a
+ * listener below which will receive all future updates.
+ *
+ *
+ * @param callback
+ */
public void whenDoneBlocking(Consumer callback) {
if (!isEnabled) return;
try {
- registerCallback(callback).get(3000, TimeUnit.SECONDS);
+ registerCallback(callback).get(BLOCKING_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// Don't care
}
@@ -214,17 +270,39 @@ public void whenDoneBlocking(Consumer callback) {
private Set> listeners = new CopyOnWriteArraySet<>();
-
+ /**
+ * Register a consumer that will receive all {PreprocessedSketch}es produced from this service.
+ *
+ * @param listener The listener to receive all future {PreprocessedSketch}es.
+ */
public void registerListener(Consumer listener) {
if (listener != null) listeners.add(listener);
}
-
+ /**
+ * Remove a consumer previously registered.
+ *
+ *
+ * Remove a consumer previously registered that was receiving {PreprocessedSketch}es produced from
+ * this service.
+ *
+ *
+ * @param listener The listener to remove.
+ */
public void unregisterListener(Consumer listener) {
listeners.remove(listener);
}
-
+ /**
+ * Inform consumers waiting for {PreprocessedSketch}es.
+ *
+ *
+ * Inform all consumers registered for receiving ongoing {PreprocessedSketch}es produced from
+ * this service.
+ *
+ *
+ * @param ps The sketch to be sent out to consumers.
+ */
private void fireListeners(PreprocessedSketch ps) {
for (Consumer listener : listeners) {
try {
@@ -239,6 +317,19 @@ private void fireListeners(PreprocessedSketch ps) {
/// --------------------------------------------------------------------------
+ /**
+ * Transform and attempt compilation of a sketch.
+ *
+ *
+ * Transform a sketch via ANTLR first to detect and explain grammatical issues before executing a
+ * build via the JDT to detect other non-grammatical compilation issues and to support developer
+ * services in the editor.
+ *
+ *
+ * @param prevResult The last produced preprocessed sketch or null if never preprocessed
+ * beforehand.
+ * @return The newly generated preprocessed sketch.
+ */
private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
boolean firstCheck = prevResult == null;
@@ -255,34 +346,42 @@ private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
StringBuilder workBuffer = new StringBuilder();
// Combine code into one buffer
+ int numLines = 1;
IntList tabStartsList = new IntList();
+ List tabLineStarts = new ArrayList<>();
for (SketchCode sc : sketch.getCode()) {
if (sc.isExtension("pde")) {
tabStartsList.append(workBuffer.length());
+ tabLineStarts.add(numLines);
+
+ StringBuilder newPiece = new StringBuilder();
if (sc.getDocument() != null) {
try {
- workBuffer.append(sc.getDocumentText());
+ newPiece.append(sc.getDocumentText());
} catch (BadLocationException e) {
e.printStackTrace();
}
} else {
- workBuffer.append(sc.getProgram());
+ newPiece.append(sc.getProgram());
}
- workBuffer.append('\n');
+ newPiece.append('\n');
+
+ String newPieceBuilt = newPiece.toString();
+ numLines += SyntaxUtil.getCount(newPieceBuilt, "\n");
+ workBuffer.append(newPieceBuilt);
}
}
result.tabStartOffsets = tabStartsList.array();
String pdeStage = result.pdeCode = workBuffer.toString();
-
boolean reloadCodeFolder = firstCheck || codeFolderChanged.getAndSet(false);
boolean reloadLibraries = firstCheck || librariesChanged.getAndSet(false);
// Core and default imports
+ PdePreprocessor preProcessor = editor.createPreprocessor(editor.getSketch().getName());
if (coreAndDefaultImports == null) {
- PdePreprocessor p = editor.createPreprocessor(null);
- coreAndDefaultImports = buildCoreAndDefaultImports(p);
+ coreAndDefaultImports = buildCoreAndDefaultImports(preProcessor);
}
result.coreAndDefaultImports.addAll(coreAndDefaultImports);
@@ -299,17 +398,37 @@ private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
result.scrubbedPdeCode = workBuffer.toString();
- Mode sketchMode = PdePreprocessor.parseMode(workBuffer);
+ PreprocessorResult preprocessorResult;
+ try {
+ preprocessorResult = preProcessor.write(
+ new StringWriter(),
+ result.scrubbedPdeCode,
+ codeFolderImports.stream()
+ .map(ImportStatement::getFullClassName)
+ .collect(Collectors.toList())
+ );
+ } catch (SketchException e) {
+ throw new RuntimeException("Unexpected sketch exception in preprocessing: " + e);
+ }
+
+ if (preprocessorResult.getPreprocessIssues().size() > 0) {
+ final int endNumLines = numLines;
+
+ preprocessorResult.getPreprocessIssues().stream()
+ .map((x) -> ProblemFactory.build(x, tabLineStarts, endNumLines, editor))
+ .forEach(result.otherProblems::add);
+
+ result.hasSyntaxErrors = true;
+ return result.build();
+ }
+
+ // Save off the imports
+ programImports.addAll(preprocessorResult.getImportStatements());
+ result.programImports.addAll(preprocessorResult.getImportStatements());
// Prepare transforms to convert pde code into parsable code
TextTransform toParsable = new TextTransform(pdeStage);
- toParsable.addAll(SourceUtils.insertImports(coreAndDefaultImports));
- toParsable.addAll(SourceUtils.insertImports(codeFolderImports));
- toParsable.addAll(SourceUtils.parseProgramImports(workBuffer, programImports));
- toParsable.addAll(SourceUtils.replaceTypeConstructors(workBuffer));
- toParsable.addAll(SourceUtils.replaceHexLiterals(workBuffer));
- toParsable.addAll(SourceUtils.wrapSketch(sketchMode, className, workBuffer.length()));
-
+ toParsable.addAll(preprocessorResult.getEdits());
{ // Refresh sketch classloader and classpath if imports changed
if (reloadLibraries) {
runtimePathBuilder.markLibrariesChanged();
@@ -345,8 +464,12 @@ private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
OffsetMapper parsableMapper = toParsable.getMapper();
// Create intermediate AST for advanced preprocessing
- CompilationUnit parsableCU =
- makeAST(parser, parsableStage.toCharArray(), COMPILER_OPTIONS);
+ //System.out.println(new String(parsableStage.toCharArray()));
+ CompilationUnit parsableCU = JdtCompilerUtil.makeAST(
+ parser,
+ parsableStage.toCharArray(),
+ JdtCompilerUtil.COMPILER_OPTIONS
+ );
// Prepare advanced transforms which operate on AST
TextTransform toCompilable = new TextTransform(parsableStage);
@@ -358,8 +481,10 @@ private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
char[] compilableStageChars = compilableStage.toCharArray();
// Create compilable AST to get syntax problems
+ // System.out.println(new String(compilableStageChars));
+ // System.out.println("-----");
CompilationUnit compilableCU =
- makeAST(parser, compilableStageChars, COMPILER_OPTIONS);
+ JdtCompilerUtil.makeAST(parser, compilableStageChars, JdtCompilerUtil.COMPILER_OPTIONS);
// Get syntax problems from compilable AST
result.hasSyntaxErrors |= Arrays.stream(compilableCU.getProblems())
@@ -367,9 +492,13 @@ private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
// Generate bindings after getting problems - avoids
// 'missing type' errors when there are syntax problems
- CompilationUnit bindingsCU =
- makeASTWithBindings(parser, compilableStageChars, COMPILER_OPTIONS,
- className, result.classPathArray);
+ CompilationUnit bindingsCU = JdtCompilerUtil.makeASTWithBindings(
+ parser,
+ compilableStageChars,
+ JdtCompilerUtil.COMPILER_OPTIONS,
+ className,
+ result.classPathArray
+ );
// Get compilation problems
List bindingsProblems = Arrays.asList(bindingsCU.getProblems());
@@ -390,21 +519,31 @@ private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
private List coreAndDefaultImports;
-
+ /**
+ * Determine which imports need to be available for core processing services.
+ *
+ * @param p The preprocessor to operate on.
+ * @return The import statements that need to be present.
+ */
private static List buildCoreAndDefaultImports(PdePreprocessor p) {
List result = new ArrayList<>();
- for (String imp : p.getCoreImports()) {
+ for (String imp : ImportUtil.getCoreImports()) {
result.add(ImportStatement.parse(imp));
}
- for (String imp : p.getDefaultImports()) {
+ for (String imp : ImportUtil.getDefaultImports()) {
result.add(ImportStatement.parse(imp));
}
return result;
}
-
+ /**
+ * Create import statements for items in the code folder itself.
+ *
+ * @param sketch The sketch for which the import statements should be created.
+ * @return The new import statements.
+ */
private static List buildCodeFolderImports(Sketch sketch) {
if (sketch.hasCodeFolder()) {
File codeFolder = sketch.getCodeFolder();
@@ -417,7 +556,14 @@ private static List buildCodeFolderImports(Sketch sketch) {
return Collections.emptyList();
}
-
+ /**
+ * Determine if imports have changed.
+ *
+ * @param prevImports The last iteration imports.
+ * @param imports The current iterations imports.
+ * @return True if the list of imports changed and false otherwise.
+ * This includes change in order.
+ */
private static boolean checkIfImportsChanged(List prevImports,
List imports) {
if (imports.size() != prevImports.size()) {
@@ -442,80 +588,11 @@ private static boolean checkIfImportsChanged(List prevImports,
/// --------------------------------------------------------------------------
-
- private static CompilationUnit makeAST(ASTParser parser,
- char[] source,
- Map options) {
- parser.setSource(source);
- parser.setKind(ASTParser.K_COMPILATION_UNIT);
- parser.setCompilerOptions(options);
- parser.setStatementsRecovery(true);
-
- return (CompilationUnit) parser.createAST(null);
- }
-
-
- private static CompilationUnit makeASTWithBindings(ASTParser parser,
- char[] source,
- Map options,
- String className,
- String[] classPath) {
- parser.setSource(source);
- parser.setKind(ASTParser.K_COMPILATION_UNIT);
- parser.setCompilerOptions(options);
- parser.setStatementsRecovery(true);
- parser.setUnitName(className);
- parser.setEnvironment(classPath, null, null, false);
- parser.setResolveBindings(true);
-
- return (CompilationUnit) parser.createAST(null);
- }
-
-
- static private final Map COMPILER_OPTIONS;
- static {
- Map compilerOptions = new HashMap<>();
-
- compilerOptions.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_11);
- compilerOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_11);
- compilerOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_11);
-
- // See http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Fguide%2Fjdt_api_options.htm&anchor=compiler
-
- final String[] generate = {
- JavaCore.COMPILER_LINE_NUMBER_ATTR,
- JavaCore.COMPILER_SOURCE_FILE_ATTR
- };
-
- final String[] ignore = {
- JavaCore.COMPILER_PB_UNUSED_IMPORT,
- JavaCore.COMPILER_PB_MISSING_SERIAL_VERSION,
- JavaCore.COMPILER_PB_RAW_TYPE_REFERENCE,
- JavaCore.COMPILER_PB_REDUNDANT_TYPE_ARGUMENTS,
- JavaCore.COMPILER_PB_UNCHECKED_TYPE_OPERATION
- };
-
- final String[] warn = {
- JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT,
- JavaCore.COMPILER_PB_NULL_REFERENCE,
- JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE,
- JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK,
- JavaCore.COMPILER_PB_POSSIBLE_ACCIDENTAL_BOOLEAN_ASSIGNMENT,
- JavaCore.COMPILER_PB_UNUSED_LABEL,
- JavaCore.COMPILER_PB_UNUSED_LOCAL,
- JavaCore.COMPILER_PB_UNUSED_OBJECT_ALLOCATION,
- JavaCore.COMPILER_PB_UNUSED_PARAMETER,
- JavaCore.COMPILER_PB_UNUSED_PRIVATE_MEMBER
- };
-
- for (String s : generate) compilerOptions.put(s, JavaCore.GENERATE);
- for (String s : ignore) compilerOptions.put(s, JavaCore.IGNORE);
- for (String s : warn) compilerOptions.put(s, JavaCore.WARNING);
-
- COMPILER_OPTIONS = Collections.unmodifiableMap(compilerOptions);
- }
-
-
+ /**
+ * Emit events and update internal state (isEnabled) if java tabs added or modified.
+ *
+ * @param hasJavaTabs True if java tabs are in the sketch and false otherwise.
+ */
public void handleHasJavaTabsChange(boolean hasJavaTabs) {
isEnabled = !hasJavaTabs;
if (isEnabled) {
diff --git a/java/src/processing/mode/java/pdex/TextTransform.java b/java/src/processing/mode/java/pdex/TextTransform.java
index 1805464394..6cba64eb7c 100644
--- a/java/src/processing/mode/java/pdex/TextTransform.java
+++ b/java/src/processing/mode/java/pdex/TextTransform.java
@@ -160,23 +160,23 @@ public String toString() {
}
- protected static class Edit {
+ public static class Edit {
- static Edit insert(int offset, String text) {
+ public static Edit insert(int offset, String text) {
return new Edit(offset, 0, offset, text.length(), text);
}
- static Edit replace(int offset, int length, String text) {
+ public static Edit replace(int offset, int length, String text) {
return new Edit(offset, length, offset, text.length(), text);
}
- static Edit move(int fromOffset, int length, int toOffset) {
+ public static Edit move(int fromOffset, int length, int toOffset) {
Edit result = new Edit(fromOffset, length, toOffset, length, null);
result.toOffset = toOffset;
return result;
}
- static Edit delete(int position, int length) {
+ public static Edit delete(int position, int length) {
return new Edit(position, length, position, 0, null);
}
diff --git a/java/src/processing/mode/java/pdex/util/ProblemFactory.java b/java/src/processing/mode/java/pdex/util/ProblemFactory.java
new file mode 100644
index 0000000000..31363211e0
--- /dev/null
+++ b/java/src/processing/mode/java/pdex/util/ProblemFactory.java
@@ -0,0 +1,89 @@
+package processing.mode.java.pdex.util;
+
+import processing.app.Problem;
+import processing.app.ui.Editor;
+import processing.mode.java.preproc.issue.PdePreprocessIssue;
+
+import java.util.List;
+
+
+/**
+ * Factory which helps create {Problem}s during preprocessing.
+ */
+public class ProblemFactory {
+
+ /**
+ * Create a new {Problem}.
+ *
+ * @param pdePreprocessIssue The preprocess issue found.
+ * @param tabStarts The list of line numbers on which each tab starts.
+ * @param editor The editor in which errors will appear.
+ * @return Newly created problem.
+ */
+ public static Problem build(PdePreprocessIssue pdePreprocessIssue, List tabStarts,
+ int numLines, Editor editor) {
+
+ int line = pdePreprocessIssue.getLine();
+
+ // Sometimes errors are reported one line past end of sketch. Fix that.
+ if (line >= numLines) {
+ line = numLines - 1;
+ }
+
+ // Get local area
+ TabLine tabLine = TabLineFactory.getTab(tabStarts, line);
+
+ int tab = tabLine.getTab();
+ int localLine = tabLine.getLineInTab(); // Problems emitted in 0 index
+
+ // Generate syntax problem
+ String message = pdePreprocessIssue.getMsg();
+
+ int lineStart = editor.getLineStartOffset(localLine);
+ int lineStop = editor.getLineStopOffset(localLine) - 1;
+
+ if (lineStart == lineStop) {
+ lineStop++;
+ }
+
+ return new SyntaxProblem(
+ tab,
+ localLine,
+ message,
+ lineStart,
+ lineStop
+ );
+ }
+
+ /**
+ * Create a new {Problem}.
+ *
+ * @param pdePreprocessIssue The preprocess issue found.
+ * @param tabStarts The list of line numbers on which each tab starts.
+ * @return Newly created problem.
+ */
+ public static Problem build(PdePreprocessIssue pdePreprocessIssue, List tabStarts) {
+ int line = pdePreprocessIssue.getLine();
+
+ TabLine tabLine = TabLineFactory.getTab(tabStarts, line);
+
+ int tab = tabLine.getTab();
+ int localLine = tabLine.getLineInTab();
+ int col = pdePreprocessIssue.getCharPositionInLine();
+
+ String message = pdePreprocessIssue.getMsg();
+
+ if (col == 0) {
+ col = 1;
+ }
+
+ return new SyntaxProblem(
+ tab,
+ localLine,
+ message,
+ localLine,
+ localLine + col
+ );
+ }
+
+}
diff --git a/java/src/processing/mode/java/pdex/util/SyntaxProblem.java b/java/src/processing/mode/java/pdex/util/SyntaxProblem.java
new file mode 100644
index 0000000000..6e140a7c7e
--- /dev/null
+++ b/java/src/processing/mode/java/pdex/util/SyntaxProblem.java
@@ -0,0 +1,75 @@
+package processing.mode.java.pdex.util;
+
+import processing.mode.java.pdex.JavaProblem;
+
+
+/**
+ * Problem identifying a syntax error found in preprocessing.
+ */
+public class SyntaxProblem extends JavaProblem {
+
+ private final int tabIndex;
+ private final int lineNumber;
+ private final String message;
+ private final int startOffset;
+ private final int stopOffset;
+
+ /**
+ * Create a new syntax problem.
+ *
+ * @param newTabIndex The tab number containing the source with the syntax issue.
+ * @param newLineNumber The line number within the tab at which the offending code can be found.
+ * @param newMessage Human readable message describing the issue.
+ * @param newStartOffset The character index at which the issue starts. This is relative to start
+ * of tab / file not relative to start of line.
+ * @param newStopOffset The character index at which the issue ends. This is relative to start
+ * * of tab / file not relative to start of line.
+ */
+ public SyntaxProblem(int newTabIndex, int newLineNumber, String newMessage, int newStartOffset,
+ int newStopOffset) {
+
+ super(newMessage, JavaProblem.ERROR, newLineNumber, newLineNumber);
+
+ tabIndex = newTabIndex;
+ lineNumber = newLineNumber;
+ message = newMessage;
+ startOffset = newStartOffset;
+ stopOffset = newStopOffset;
+ }
+
+ @Override
+ public boolean isError() {
+ return true;
+ }
+
+ @Override
+ public boolean isWarning() {
+ return false;
+ }
+
+ @Override
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
+ @Override
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public int getStartOffset() {
+ return startOffset;
+ }
+
+ @Override
+ public int getStopOffset() {
+ return stopOffset;
+ }
+
+}
diff --git a/java/src/processing/mode/java/pdex/util/TabLine.java b/java/src/processing/mode/java/pdex/util/TabLine.java
new file mode 100644
index 0000000000..b89862dcca
--- /dev/null
+++ b/java/src/processing/mode/java/pdex/util/TabLine.java
@@ -0,0 +1,55 @@
+package processing.mode.java.pdex.util;
+
+
+/**
+ * Identifier of a line within a tab.
+ */
+public class TabLine {
+
+ private final int tab;
+ private final int globalLine;
+ private final int lineInTab;
+
+ /**
+ * Create a new tab line identifier.
+ *
+ * @param newTab The zero indexed tab number in which the line of code appears.
+ * @param newGlobalLine The line of that code within the concatenated "global" java file version
+ * of the sketch.
+ * @param newLineIntTab The line of the code within the tab.
+ */
+ public TabLine(int newTab, int newGlobalLine, int newLineIntTab) {
+ tab = newTab;
+ globalLine = newGlobalLine;
+ lineInTab = newLineIntTab;
+ }
+
+ /**
+ * The tab number within the sketch in which the line of code appears.
+ *
+ * @return The tab number on which the code appears.
+ */
+ public int getTab() {
+ return tab;
+ }
+
+ /**
+ * Get the location of the source as a line within the "global" concatenated java file.
+ *
+ * @return Line within the concatenated java file version of this sketch.
+ */
+ public int getGlobalLine() {
+ return globalLine;
+ }
+
+ /**
+ * Get the location of the source within the tab.
+ *
+ * @return The "local" line for the source or, in other words, the line number within the tab
+ * housing the code.
+ */
+ public int getLineInTab() {
+ return lineInTab;
+ }
+
+}
diff --git a/java/src/processing/mode/java/pdex/util/TabLineFactory.java b/java/src/processing/mode/java/pdex/util/TabLineFactory.java
new file mode 100644
index 0000000000..c8883afb89
--- /dev/null
+++ b/java/src/processing/mode/java/pdex/util/TabLineFactory.java
@@ -0,0 +1,38 @@
+package processing.mode.java.pdex.util;
+
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+
+/**
+ * Utility which determines the tab and local line number on which a global line number appears.
+ *
+ *
+ * Processing concatenates tabs into single file for compilation as Java where a source line
+ * from a tab is a "local" line and the same line in the concatenated file is "global". This
+ * utility determines the local line and tab number given a global line number.
+ *
+ */
+public class TabLineFactory {
+
+ /**
+ * Get the local tab and line number for a global line.
+ *
+ * @param tabStarts The lines on which each tab starts.
+ * @param line The global line to locate as a local line.
+ * @return The local tab number and local line number.
+ */
+ public static TabLine getTab(List tabStarts, int line) {
+ OptionalInt tabMaybe = IntStream.range(0, tabStarts.size())
+ .filter((index) -> line >= tabStarts.get(index))
+ .max();
+
+ int tab = tabMaybe.orElse(0);
+
+ int localLine = line - tabStarts.get(tab);
+
+ return new TabLine(tab, line, localLine);
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/JavaLexer.g4 b/java/src/processing/mode/java/preproc/JavaLexer.g4
new file mode 100644
index 0000000000..27b188cb38
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/JavaLexer.g4
@@ -0,0 +1,208 @@
+/*
+ Part of the Processing project - http://processing.org
+ Copyright (c) 2012-19 The Processing Foundation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ [The "BSD licence" originally included as part of this source]
+ Copyright (c) 2013 Terence Parr, Sam Harwell
+ Copyright (c) 2017 Ivan Kochurkin (upgrade to Java 8)
+ All rights reserved.
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+lexer grammar JavaLexer;
+
+// Keywords
+
+ABSTRACT: 'abstract';
+ASSERT: 'assert';
+BOOLEAN: 'boolean';
+BREAK: 'break';
+BYTE: 'byte';
+CASE: 'case';
+CATCH: 'catch';
+CHAR: 'char';
+CLASS: 'class';
+CONST: 'const';
+CONTINUE: 'continue';
+DEFAULT: 'default';
+DO: 'do';
+DOUBLE: 'double';
+ELSE: 'else';
+ENUM: 'enum';
+EXTENDS: 'extends';
+FINAL: 'final';
+FINALLY: 'finally';
+FLOAT: 'float';
+FOR: 'for';
+IF: 'if';
+GOTO: 'goto';
+IMPLEMENTS: 'implements';
+IMPORT: 'import';
+INSTANCEOF: 'instanceof';
+INT: 'int';
+INTERFACE: 'interface';
+LONG: 'long';
+NATIVE: 'native';
+NEW: 'new';
+PACKAGE: 'package';
+PRIVATE: 'private';
+PROTECTED: 'protected';
+PUBLIC: 'public';
+RETURN: 'return';
+SHORT: 'short';
+STATIC: 'static';
+STRICTFP: 'strictfp';
+SUPER: 'super';
+SWITCH: 'switch';
+SYNCHRONIZED: 'synchronized';
+THIS: 'this';
+THROW: 'throw';
+THROWS: 'throws';
+TRANSIENT: 'transient';
+TRY: 'try';
+VAR: 'var';
+VOID: 'void';
+VOLATILE: 'volatile';
+WHILE: 'while';
+
+// Literals
+
+DECIMAL_LITERAL: ('0' | [1-9] (Digits? | '_'+ Digits)) [lL]?;
+HEX_LITERAL: '0' [xX] [0-9a-fA-F] ([0-9a-fA-F_]* [0-9a-fA-F])? [lL]?;
+OCT_LITERAL: '0' '_'* [0-7] ([0-7_]* [0-7])? [lL]?;
+BINARY_LITERAL: '0' [bB] [01] ([01_]* [01])? [lL]?;
+
+FLOAT_LITERAL: (Digits '.' Digits? | '.' Digits) ExponentPart? [fFdD]?
+ | Digits (ExponentPart [fFdD]? | [fFdD])
+ ;
+
+HEX_FLOAT_LITERAL: '0' [xX] (HexDigits '.'? | HexDigits? '.' HexDigits) [pP] [+-]? Digits [fFdD]?;
+
+BOOL_LITERAL: 'true'
+ | 'false'
+ ;
+
+CHAR_LITERAL: '\'' (~['\\\r\n] | EscapeSequence) '\'';
+
+STRING_LITERAL: '"' (~["\\\r\n] | EscapeSequence)* '"';
+NULL_LITERAL: 'null';
+// Separators
+LPAREN: '(';
+RPAREN: ')';
+LBRACE: '{';
+RBRACE: '}';
+LBRACK: '[';
+RBRACK: ']';
+SEMI: ';';
+COMMA: ',';
+DOT: '.';
+// Operators
+ASSIGN: '=';
+GT: '>';
+LT: '<';
+BANG: '!';
+TILDE: '~';
+QUESTION: '?';
+COLON: ':';
+EQUAL: '==';
+LE: '<=';
+GE: '>=';
+NOTEQUAL: '!=';
+AND: '&&';
+OR: '||';
+INC: '++';
+DEC: '--';
+ADD: '+';
+SUB: '-';
+MUL: '*';
+DIV: '/';
+BITAND: '&';
+BITOR: '|';
+CARET: '^';
+MOD: '%';
+ADD_ASSIGN: '+=';
+SUB_ASSIGN: '-=';
+MUL_ASSIGN: '*=';
+DIV_ASSIGN: '/=';
+AND_ASSIGN: '&=';
+OR_ASSIGN: '|=';
+XOR_ASSIGN: '^=';
+MOD_ASSIGN: '%=';
+LSHIFT_ASSIGN: '<<=';
+RSHIFT_ASSIGN: '>>=';
+URSHIFT_ASSIGN: '>>>=';
+// Java 8 tokens
+ARROW: '->';
+COLONCOLON: '::';
+// Additional symbols not defined in the lexical specification
+AT: '@';
+ELLIPSIS: '...';
+// Whitespace and comments
+WS: [ \t\r\n\u000C]+ -> channel(HIDDEN);
+COMMENT: '/*' .*? '*/' -> channel(HIDDEN);
+LINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN);
+
+// Identifiers
+
+IDENTIFIER: Letter LetterOrDigit*;
+
+// Fragment rules
+
+fragment ExponentPart
+ : [eE] [+-]? Digits
+ ;
+
+fragment EscapeSequence
+ : '\\' [btnfr"'\\]
+ | '\\' ([0-3]? [0-7])? [0-7]
+ | '\\' 'u'+ HexDigit HexDigit HexDigit HexDigit
+ ;
+fragment HexDigits
+ : HexDigit ((HexDigit | '_')* HexDigit)?
+ ;
+fragment HexDigit
+ : [0-9a-fA-F]
+ ;
+fragment Digits
+ : [0-9] ([0-9_]* [0-9])?
+ ;
+fragment LetterOrDigit
+ : Letter
+ | [0-9]
+ ;
+fragment Letter
+ : [a-zA-Z$_] // these are the "java letters" below 0x7F
+ | ~[\u0000-\u007F\uD800-\uDBFF] // covers all characters above 0x7F which are not a surrogate
+ | [\uD800-\uDBFF] [\uDC00-\uDFFF] // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF
+ ;
diff --git a/java/src/processing/mode/java/preproc/JavaParser.g4 b/java/src/processing/mode/java/preproc/JavaParser.g4
new file mode 100644
index 0000000000..9974cb2bbe
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/JavaParser.g4
@@ -0,0 +1,631 @@
+/*
+ Part of the Processing project - http://processing.org
+ Copyright (c) 2012-19 The Processing Foundation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ [The "BSD licence" originally included as part of this source]
+ Copyright (c) 2013 Terence Parr, Sam Harwell
+ Copyright (c) 2017 Ivan Kochurkin (upgrade to Java 8)
+ All rights reserved.
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+parser grammar JavaParser;
+
+import JavaLexer;
+
+options { tokenVocab=JavaLexer; }
+
+compilationUnit
+ : packageDeclaration? importDeclaration* typeDeclaration* EOF
+ ;
+
+packageDeclaration
+ : annotation* PACKAGE qualifiedName ';'
+ ;
+
+importDeclaration
+ : IMPORT STATIC? qualifiedName ('.' '*')? ';'
+ ;
+
+typeDeclaration
+ : classOrInterfaceModifier*
+ (classDeclaration | enumDeclaration | interfaceDeclaration | annotationTypeDeclaration)
+ | ';'
+ ;
+
+modifier
+ : classOrInterfaceModifier
+ | NATIVE
+ | SYNCHRONIZED
+ | TRANSIENT
+ | VOLATILE
+ ;
+
+classOrInterfaceModifier
+ : annotation
+ | PUBLIC
+ | PROTECTED
+ | PRIVATE
+ | STATIC
+ | ABSTRACT
+ | FINAL // FINAL for class only -- does not apply to interfaces
+ | STRICTFP
+ ;
+
+variableModifier
+ : FINAL
+ | annotation
+ ;
+
+classDeclaration
+ : CLASS IDENTIFIER typeParameters?
+ (EXTENDS typeType)?
+ (IMPLEMENTS typeList)?
+ classBody
+ ;
+
+typeParameters
+ : '<' typeParameter (',' typeParameter)* '>'
+ ;
+
+typeParameter
+ : annotation* IDENTIFIER (EXTENDS typeBound)?
+ ;
+
+typeBound
+ : typeType ('&' typeType)*
+ ;
+
+enumDeclaration
+ : ENUM IDENTIFIER (IMPLEMENTS typeList)? '{' enumConstants? ','? enumBodyDeclarations? '}'
+ ;
+
+enumConstants
+ : enumConstant (',' enumConstant)*
+ ;
+
+enumConstant
+ : annotation* IDENTIFIER arguments? classBody?
+ ;
+
+enumBodyDeclarations
+ : ';' classBodyDeclaration*
+ ;
+
+interfaceDeclaration
+ : INTERFACE IDENTIFIER typeParameters? (EXTENDS typeList)? interfaceBody
+ ;
+
+classBody
+ : '{' classBodyDeclaration* '}'
+ ;
+
+interfaceBody
+ : '{' interfaceBodyDeclaration* '}'
+ ;
+
+classBodyDeclaration
+ : ';'
+ | importDeclaration
+ | STATIC? block
+ | modifier* memberDeclaration
+ ;
+
+memberDeclaration
+ : methodDeclaration
+ | genericMethodDeclaration
+ | fieldDeclaration
+ | constructorDeclaration
+ | genericConstructorDeclaration
+ | interfaceDeclaration
+ | annotationTypeDeclaration
+ | classDeclaration
+ | enumDeclaration
+ ;
+
+/* We use rule this even for void methods which cannot have [] after parameters.
+ This simplifies grammar and we can consider void to be a type, which
+ renders the [] matching as a context-sensitive issue or a semantic check
+ for invalid return type after parsing.
+ */
+methodDeclaration
+ : typeTypeOrVoid IDENTIFIER formalParameters ('[' ']')*
+ (THROWS qualifiedNameList)?
+ methodBody
+ ;
+
+methodBody
+ : block
+ | ';'
+ ;
+
+typeTypeOrVoid
+ : typeType
+ | VOID
+ ;
+
+genericMethodDeclaration
+ : typeParameters methodDeclaration
+ ;
+
+genericConstructorDeclaration
+ : typeParameters constructorDeclaration
+ ;
+
+constructorDeclaration
+ : IDENTIFIER formalParameters (THROWS qualifiedNameList)? constructorBody=block
+ ;
+
+fieldDeclaration
+ : typeType variableDeclarators ';'
+ ;
+
+interfaceBodyDeclaration
+ : modifier* interfaceMemberDeclaration
+ | ';'
+ ;
+
+interfaceMemberDeclaration
+ : constDeclaration
+ | interfaceMethodDeclaration
+ | genericInterfaceMethodDeclaration
+ | interfaceDeclaration
+ | annotationTypeDeclaration
+ | classDeclaration
+ | enumDeclaration
+ ;
+
+constDeclaration
+ : typeType constantDeclarator (',' constantDeclarator)* ';'
+ ;
+
+constantDeclarator
+ : IDENTIFIER ('[' ']')* '=' variableInitializer
+ ;
+
+// see matching of [] comment in methodDeclaratorRest
+// methodBody from Java8
+interfaceMethodDeclaration
+ : interfaceMethodModifier* (typeTypeOrVoid | typeParameters annotation* typeTypeOrVoid)
+ IDENTIFIER formalParameters ('[' ']')* (THROWS qualifiedNameList)? methodBody
+ ;
+
+// Java8
+interfaceMethodModifier
+ : annotation
+ | PUBLIC
+ | ABSTRACT
+ | DEFAULT
+ | STATIC
+ | STRICTFP
+ ;
+
+genericInterfaceMethodDeclaration
+ : typeParameters interfaceMethodDeclaration
+ ;
+
+variableDeclarators
+ : variableDeclarator (',' variableDeclarator)*
+ ;
+
+variableDeclarator
+ : variableDeclaratorId ('=' variableInitializer)?
+ ;
+
+variableDeclaratorId
+ : IDENTIFIER ('[' ']')*
+ ;
+
+variableInitializer
+ : arrayInitializer
+ | expression
+ ;
+
+arrayInitializer
+ : '{' (variableInitializer (',' variableInitializer)* (',')? )? '}'
+ ;
+
+classOrInterfaceType
+ : IDENTIFIER typeArguments? ('.' IDENTIFIER typeArguments?)*
+ ;
+
+typeArgument
+ : typeType
+ | '?' ((EXTENDS | SUPER) typeType)?
+ ;
+
+qualifiedNameList
+ : qualifiedName (',' qualifiedName)*
+ ;
+
+formalParameters
+ : '(' formalParameterList? ')'
+ ;
+
+formalParameterList
+ : formalParameter (',' formalParameter)* (',' lastFormalParameter)?
+ | lastFormalParameter
+ ;
+
+formalParameter
+ : variableModifier* typeType variableDeclaratorId
+ ;
+
+lastFormalParameter
+ : variableModifier* typeType '...' variableDeclaratorId
+ ;
+
+qualifiedName
+ : IDENTIFIER ('.' IDENTIFIER)*
+ ;
+
+literal
+ : integerLiteral
+ | floatLiteral
+ | CHAR_LITERAL
+ | STRING_LITERAL
+ | BOOL_LITERAL
+ | NULL_LITERAL
+ ;
+
+integerLiteral
+ : DECIMAL_LITERAL
+ | HEX_LITERAL
+ | OCT_LITERAL
+ | BINARY_LITERAL
+ ;
+
+floatLiteral
+ : FLOAT_LITERAL
+ | HEX_FLOAT_LITERAL
+ ;
+
+// ANNOTATIONS
+
+annotation
+ : '@' qualifiedName ('(' ( elementValuePairs | elementValue )? ')')?
+ ;
+
+elementValuePairs
+ : elementValuePair (',' elementValuePair)*
+ ;
+
+elementValuePair
+ : IDENTIFIER '=' elementValue
+ ;
+
+elementValue
+ : expression
+ | annotation
+ | elementValueArrayInitializer
+ ;
+
+elementValueArrayInitializer
+ : '{' (elementValue (',' elementValue)*)? (',')? '}'
+ ;
+
+annotationTypeDeclaration
+ : '@' INTERFACE IDENTIFIER annotationTypeBody
+ ;
+
+annotationTypeBody
+ : '{' (annotationTypeElementDeclaration)* '}'
+ ;
+
+annotationTypeElementDeclaration
+ : modifier* annotationTypeElementRest
+ | ';' // this is not allowed by the grammar, but apparently allowed by the actual compiler
+ ;
+
+annotationTypeElementRest
+ : typeType annotationMethodOrConstantRest ';'
+ | classDeclaration ';'?
+ | interfaceDeclaration ';'?
+ | enumDeclaration ';'?
+ | annotationTypeDeclaration ';'?
+ ;
+
+annotationMethodOrConstantRest
+ : annotationMethodRest
+ | annotationConstantRest
+ ;
+
+annotationMethodRest
+ : IDENTIFIER '(' ')' defaultValue?
+ ;
+
+annotationConstantRest
+ : variableDeclarators
+ ;
+
+defaultValue
+ : DEFAULT elementValue
+ ;
+
+// STATEMENTS / BLOCKS
+
+block
+ : '{' blockStatement* '}'
+ ;
+
+blockStatement
+ : localVariableDeclaration ';'
+ | statement
+ | localTypeDeclaration
+ ;
+
+localVariableDeclaration
+ : variableModifier* typeType variableDeclarators
+ ;
+
+localTypeDeclaration
+ : classOrInterfaceModifier*
+ (classDeclaration | interfaceDeclaration)
+ | ';'
+ ;
+
+statement
+ : blockLabel=block
+ | ASSERT expression (':' expression)? ';'
+ | IF parExpression statement (ELSE statement)?
+ | FOR '(' forControl ')' statement
+ | WHILE parExpression statement
+ | DO statement WHILE parExpression ';'
+ | TRY block (catchClause+ finallyBlock? | finallyBlock)
+ | TRY resourceSpecification block catchClause* finallyBlock?
+ | SWITCH parExpression '{' switchBlockStatementGroup* switchLabel* '}'
+ | SYNCHRONIZED parExpression block
+ | RETURN expression? ';'
+ | THROW expression ';'
+ | BREAK IDENTIFIER? ';'
+ | CONTINUE IDENTIFIER? ';'
+ | SEMI
+ | statementExpression=expression ';'
+ | identifierLabel=IDENTIFIER ':' statement
+ ;
+
+catchClause
+ : CATCH '(' variableModifier* catchType IDENTIFIER ')' block
+ ;
+
+catchType
+ : qualifiedName ('|' qualifiedName)*
+ ;
+
+finallyBlock
+ : FINALLY block
+ ;
+
+resourceSpecification
+ : '(' resources ';'? ')'
+ ;
+
+resources
+ : resource (';' resource)*
+ ;
+
+resource
+ : variableModifier* classOrInterfaceType variableDeclaratorId '=' expression
+ ;
+
+/** Matches cases then statements, both of which are mandatory.
+ * To handle empty cases at the end, we add switchLabel* to statement.
+ */
+switchBlockStatementGroup
+ : switchLabel+ blockStatement+
+ ;
+
+switchLabel
+ : CASE (constantExpression=expression | enumConstantName=IDENTIFIER) ':'
+ | DEFAULT ':'
+ ;
+
+forControl
+ : enhancedForControl
+ | forInit? ';' expression? ';' forUpdate=expressionList?
+ ;
+
+forInit
+ : localVariableDeclaration
+ | expressionList
+ ;
+
+enhancedForControl
+ : variableModifier* typeType variableDeclaratorId ':' expression
+ ;
+
+// EXPRESSIONS
+
+parExpression
+ : '(' expression ')'
+ ;
+
+expressionList
+ : expression (',' expression)*
+ ;
+
+methodCall
+ : IDENTIFIER '(' expressionList? ')'
+ | THIS '(' expressionList? ')'
+ | SUPER '(' expressionList? ')'
+ ;
+
+expression
+ : primary
+ | expression bop='.'
+ ( IDENTIFIER
+ | methodCall
+ | THIS
+ | NEW nonWildcardTypeArguments? innerCreator
+ | SUPER superSuffix
+ | explicitGenericInvocation
+ )
+ | expression '[' expression ']'
+ | methodCall
+ | NEW creator
+ | '(' typeType ')' expression
+ | expression postfix=('++' | '--')
+ | prefix=('+'|'-'|'++'|'--') expression
+ | prefix=('~'|'!') expression
+ | expression bop=('*'|'/'|'%') expression
+ | expression bop=('+'|'-') expression
+ | expression ('<' '<' | '>' '>' '>' | '>' '>') expression
+ | expression bop=('<=' | '>=' | '>' | '<') expression
+ | expression bop=INSTANCEOF typeType
+ | expression bop=('==' | '!=') expression
+ | expression bop='&' expression
+ | expression bop='^' expression
+ | expression bop='|' expression
+ | expression bop='&&' expression
+ | expression bop='||' expression
+ | expression bop='?' expression ':' expression
+ | expression
+ bop=('=' | '+=' | '-=' | '*=' | '/=' | '&=' | '|=' | '^=' | '>>=' | '>>>=' | '<<=' | '%=')
+ expression
+ | lambdaExpression // Java8
+
+ // Java 8 methodReference
+ | expression '::' typeArguments? IDENTIFIER
+ | typeType '::' (typeArguments? IDENTIFIER | NEW)
+ | classType '::' typeArguments? NEW
+ ;
+
+// Java8
+lambdaExpression
+ : lambdaParameters '->' lambdaBody
+ ;
+
+// Java8
+lambdaParameters
+ : IDENTIFIER
+ | '(' formalParameterList? ')'
+ | '(' IDENTIFIER (',' IDENTIFIER)* ')'
+ ;
+
+// Java8
+lambdaBody
+ : expression
+ | block
+ ;
+
+primary
+ : '(' expression ')'
+ | THIS
+ | SUPER
+ | literal
+ | IDENTIFIER
+ | typeTypeOrVoid '.' CLASS
+ | nonWildcardTypeArguments (explicitGenericInvocationSuffix | THIS arguments)
+ ;
+
+classType
+ : (classOrInterfaceType '.')? annotation* IDENTIFIER typeArguments?
+ ;
+
+creator
+ : nonWildcardTypeArguments createdName classCreatorRest
+ | createdName (arrayCreatorRest | classCreatorRest)
+ ;
+
+createdName
+ : IDENTIFIER typeArgumentsOrDiamond? ('.' IDENTIFIER typeArgumentsOrDiamond?)*
+ | primitiveType
+ ;
+
+innerCreator
+ : IDENTIFIER nonWildcardTypeArgumentsOrDiamond? classCreatorRest
+ ;
+
+arrayCreatorRest
+ : '[' (']' ('[' ']')* arrayInitializer | expression ']' ('[' expression ']')* ('[' ']')*)
+ ;
+
+classCreatorRest
+ : arguments classBody?
+ ;
+
+explicitGenericInvocation
+ : nonWildcardTypeArguments explicitGenericInvocationSuffix
+ ;
+
+typeArgumentsOrDiamond
+ : '<' '>'
+ | typeArguments
+ ;
+
+nonWildcardTypeArgumentsOrDiamond
+ : '<' '>'
+ | nonWildcardTypeArguments
+ ;
+
+nonWildcardTypeArguments
+ : '<' typeList '>'
+ ;
+
+typeList
+ : typeType (',' typeType)*
+ ;
+
+typeType
+ : annotation? (classOrInterfaceType | primitiveType | VAR) ('[' ']')*
+ ;
+
+primitiveType
+ : BOOLEAN
+ | CHAR
+ | BYTE
+ | SHORT
+ | INT
+ | LONG
+ | FLOAT
+ | DOUBLE
+ ;
+
+typeArguments
+ : '<' typeArgument (',' typeArgument)* '>'
+ ;
+
+superSuffix
+ : arguments
+ | '.' IDENTIFIER arguments?
+ ;
+
+explicitGenericInvocationSuffix
+ : SUPER superSuffix
+ | IDENTIFIER arguments
+ ;
+
+arguments
+ : '(' expressionList? ')'
+ ;
diff --git a/java/src/processing/mode/java/preproc/ParserTests.launch b/java/src/processing/mode/java/preproc/ParserTests.launch
deleted file mode 100644
index 346f4a3838..0000000000
--- a/java/src/processing/mode/java/preproc/ParserTests.launch
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/java/src/processing/mode/java/preproc/PdeEmitter.java b/java/src/processing/mode/java/preproc/PdeEmitter.java
deleted file mode 100644
index c2b5a4ed15..0000000000
--- a/java/src/processing/mode/java/preproc/PdeEmitter.java
+++ /dev/null
@@ -1,776 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-package processing.mode.java.preproc;
-
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.util.BitSet;
-import java.util.Stack;
-import processing.app.Preferences;
-import processing.app.SketchException;
-import processing.mode.java.preproc.PdeTokenTypes;
-import antlr.CommonASTWithHiddenTokens;
-import antlr.CommonHiddenStreamToken;
-import antlr.collections.AST;
-
-/* Based on original code copyright (c) 2003 Andy Tripp .
- * shipped under GPL with permission.
- */
-
-/**
- * PDEEmitter: A class that can take an ANTLR Java AST and produce
- * reasonably formatted Java code from it. To use it, create a
- * PDEEmitter object, call setOut() if you want to print to something
- * other than System.out, and then call print(), passing the
- * AST. Typically, the AST node that you pass would be the root of a
- * tree - the ROOT_ID node that represents a Java file.
- *
- * Modified March 2010 to support Java 5 type arguments and for loops by
- * @author Jonathan Feinberg <jdf@pobox.com>
- */
-
-@SuppressWarnings("serial")
-public class PdeEmitter implements PdeTokenTypes {
- private final PdePreprocessor pdePreprocessor;
- private final PrintWriter out;
- private final PrintStream debug = System.err;
-
- private final Stack stack = new Stack();
- private final static int ROOT_ID = 0;
-
- public PdeEmitter(final PdePreprocessor pdePreprocessor, final PrintWriter out) {
- this.pdePreprocessor = pdePreprocessor;
- this.out = out;
- }
-
- /**
- * Find a child of the given AST that has the given type
- * @returns a child AST of the given type. If it can't find a child of the
- * given type, return null.
- */
- static private AST getChild(final AST ast, final int childType) {
- AST child = ast.getFirstChild();
- while (child != null) {
- if (child.getType() == childType) {
- // debug.println("getChild: found:" + name(ast));
- return child;
- }
- child = child.getNextSibling();
- }
- return null;
- }
-
- /**
- * Dump the list of hidden tokens linked to after the AST node passed in.
- * Most hidden tokens are dumped from this function.
- */
- private void dumpHiddenAfter(final AST ast) {
- dumpHiddenTokens(((CommonASTWithHiddenTokens) ast).getHiddenAfter());
- }
-
- /**
- * Dump the list of hidden tokens linked to before the AST node passed in.
- * The only time hidden tokens need to be dumped with this function is when
- * dealing parts of the tree where automatic tree construction was
- * turned off with the ! operator in the grammar file and the nodes were
- * manually constructed in such a way that the usual tokens don't have the
- * necessary hiddenAfter links.
- */
- private void dumpHiddenBefore(final AST ast) {
-
- antlr.CommonHiddenStreamToken child = null, parent = ((CommonASTWithHiddenTokens) ast)
- .getHiddenBefore();
-
- // if there aren't any hidden tokens here, quietly return
- //
- if (parent == null) {
- return;
- }
-
- // traverse back to the head of the list of tokens before this node
- do {
- child = parent;
- parent = child.getHiddenBefore();
- } while (parent != null);
-
- // dump that list
- dumpHiddenTokens(child);
- }
-
- /**
- * Dump the list of hidden tokens linked to from the token passed in.
- */
- private void dumpHiddenTokens(CommonHiddenStreamToken t) {
- for (; t != null; t = pdePreprocessor.getHiddenAfter(t)) {
- out.print(t.getText());
- }
- }
-
- /**
- * Print the children of the given AST
- * @param ast The AST to print
- * @returns true iff anything was printed
- */
- private boolean printChildren(final AST ast) throws SketchException {
- boolean ret = false;
- AST child = ast.getFirstChild();
- while (child != null) {
- ret = true;
- print(child);
- child = child.getNextSibling();
- }
- return ret;
- }
-
- /**
- * Tells whether an AST has any children or not.
- * @return true iff the AST has at least one child
- */
- static private boolean hasChildren(final AST ast) {
- return (ast.getFirstChild() != null);
- }
-
- /**
- * Gets the best node in the subtree for printing. This really means
- * the next node which could potentially have hiddenBefore data. It's
- * usually the first printable leaf, but not always.
- *
- * @param includeThisNode Should this node be included in the search?
- * If false, only descendants are searched.
- *
- * @return the first printable leaf node in an AST
- */
- private AST getBestPrintableNode(final AST ast, final boolean includeThisNode) {
- AST child;
-
- if (includeThisNode) {
- child = ast;
- } else {
- child = ast.getFirstChild();
- }
-
- if (child != null) {
-
- switch (child.getType()) {
-
- // the following node types are printing nodes that print before
- // any children, but then also recurse over children. So they
- // may have hiddenBefore chains that need to be printed first. Many
- // statements and all unary expression types qualify. Return these
- // nodes directly
- case CLASS_DEF:
- case ENUM_DEF:
- case LITERAL_if:
- case LITERAL_new:
- case LITERAL_for:
- case LITERAL_while:
- case LITERAL_do:
- case LITERAL_break:
- case LITERAL_continue:
- case LITERAL_return:
- case LITERAL_switch:
- case LITERAL_try:
- case LITERAL_throw:
- case LITERAL_synchronized:
- case LITERAL_assert:
- case BNOT:
- case LNOT:
- case INC:
- case DEC:
- case UNARY_MINUS:
- case UNARY_PLUS:
- return child;
-
- // Some non-terminal node types (at the moment, I only know of
- // MODIFIERS, but there may be other such types), can be
- // leaves in the tree but not have any children. If this is
- // such a node, move on to the next sibling.
- case MODIFIERS:
- if (child.getFirstChild() == null) {
- return getBestPrintableNode(child.getNextSibling(), false);
- }
- // new jikes doesn't like fallthrough, so just duplicated here:
- return getBestPrintableNode(child, false);
-
- default:
- return getBestPrintableNode(child, false);
- }
- }
-
- return ast;
- }
-
- // Because the meanings of <, >, >>, and >>> are overloaded to support
- // type arguments and type parameters, we have to treat them
- // as copyable to hidden text (or else the following syntax,
- // such as (); and what not gets lost under certain circumstances
- //
- // Since they are copied to the hidden stream, you don't want
- // to print them explicitly; they come out in the dumpHiddenXXX methods.
- // -- jdf
- private static final BitSet OTHER_COPIED_TOKENS = new BitSet() {
- {
- set(LT);
- set(GT);
- set(SR);
- set(BSR);
- }
- };
-
- /**
- * Prints a binary operator
- */
- private void printBinaryOperator(final AST ast) throws SketchException {
- print(ast.getFirstChild());
- if (!OTHER_COPIED_TOKENS.get(ast.getType())) {
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- }
- print(ast.getFirstChild().getNextSibling());
- }
-
- private void printMethodDef(final AST ast) throws SketchException {
- final AST modifiers = ast.getFirstChild();
- final AST typeParameters, type;
- if (modifiers.getNextSibling().getType() == TYPE_PARAMETERS) {
- typeParameters = modifiers.getNextSibling();
- type = typeParameters.getNextSibling();
- } else {
- typeParameters = null;
- type = modifiers.getNextSibling();
- }
- final AST methodName = type.getNextSibling();
-// if (methodName.getText().equals("main")) {
-// pdePreprocessor.setFoundMain(true);
-// }
- pdePreprocessor.addMethod(methodName.getText());
- printChildren(ast);
- }
-
- private void printIfThenElse(final AST literalIf) throws SketchException {
- out.print(literalIf.getText());
- dumpHiddenAfter(literalIf);
-
- final AST condition = literalIf.getFirstChild();
- print(condition); // the "if" condition: an EXPR
-
- // the "then" clause is either an SLIST or an EXPR
- final AST thenPath = condition.getNextSibling();
- print(thenPath);
-
- // optional "else" clause: an SLIST or an EXPR
- // what could be simpler?
- final AST elsePath = thenPath.getNextSibling();
- if (elsePath != null) {
- out.print("else");
- final AST bestPrintableNode = getBestPrintableNode(elsePath, true);
- dumpHiddenBefore(bestPrintableNode);
- final CommonHiddenStreamToken hiddenBefore =
- ((CommonASTWithHiddenTokens) elsePath).getHiddenBefore();
- if (elsePath.getType() == PdeTokenTypes.SLIST && elsePath.getNumberOfChildren() == 0 &&
- hiddenBefore == null) {
- out.print("{");
- final CommonHiddenStreamToken hiddenAfter =
- ((CommonASTWithHiddenTokens) elsePath).getHiddenAfter();
- if (hiddenAfter == null) {
- out.print("}");
- } else {
- dumpHiddenTokens(hiddenAfter);
- }
- } else {
- print(elsePath);
- }
- }
- }
-
- /**
- * Print the given AST. Call this function to print your PDE code.
- *
- * It works by making recursive calls to print children.
- * So the code below is one big "switch" statement on the passed AST type.
- */
- public void print(final AST ast) throws SketchException {
- if (ast == null) {
- return;
- }
-
- stack.push(ast);
-
- final AST child1 = ast.getFirstChild();
- AST child2 = null;
- AST child3 = null;
- if (child1 != null) {
- child2 = child1.getNextSibling();
- if (child2 != null) {
- child3 = child2.getNextSibling();
- }
- }
-
- switch (ast.getType()) {
- // The top of the tree looks like this:
- // ROOT_ID "Whatever.java"
- // package
- // imports
- // class definition
- case ROOT_ID:
- dumpHiddenTokens(pdePreprocessor.getInitialHiddenToken());
- printChildren(ast);
- break;
-
- // supporting a "package" statement in a PDE program has
- // a bunch of issues with it that need to dealt in the compilation
- // code too, so this isn't actually tested.
- case PACKAGE_DEF:
- out.print("package");
- dumpHiddenAfter(ast);
- print(ast.getFirstChild());
- break;
-
- // IMPORT has exactly one child
- case IMPORT:
- out.print("import");
- dumpHiddenAfter(ast);
- print(ast.getFirstChild());
- break;
-
- case STATIC_IMPORT:
- out.print("import static");
- dumpHiddenAfter(ast);
- print(ast.getFirstChild());
- break;
-
- case CLASS_DEF:
- case ENUM_DEF:
- case INTERFACE_DEF:
- print(getChild(ast, MODIFIERS));
- if (ast.getType() == CLASS_DEF) {
- out.print("class");
- } else if (ast.getType() == ENUM_DEF) {
- out.print("enum");
- } else {
- out.print("interface");
- }
- dumpHiddenBefore(getChild(ast, IDENT));
- print(getChild(ast, IDENT));
- print(getChild(ast, TYPE_PARAMETERS));
- print(getChild(ast, EXTENDS_CLAUSE));
- print(getChild(ast, IMPLEMENTS_CLAUSE));
- print(getChild(ast, OBJBLOCK));
- break;
-
- case EXTENDS_CLAUSE:
- if (hasChildren(ast)) {
- out.print("extends");
- dumpHiddenBefore(getBestPrintableNode(ast, false));
- printChildren(ast);
- }
- break;
-
- case IMPLEMENTS_CLAUSE:
- if (hasChildren(ast)) {
- out.print("implements");
- dumpHiddenBefore(getBestPrintableNode(ast, false));
- printChildren(ast);
- }
- break;
-
- // DOT
- case DOT:
- print(child1);
- out.print(".");
- dumpHiddenAfter(ast);
- print(child2);
- if (child3 != null) {
- print(child3);
- }
- break;
-
- case MODIFIERS:
- case OBJBLOCK:
- case CTOR_DEF:
- //case METHOD_DEF:
- case PARAMETERS:
- case PARAMETER_DEF:
- case VARIABLE_PARAMETER_DEF:
- case VARIABLE_DEF:
- case ENUM_CONSTANT_DEF:
- case TYPE:
- case SLIST:
- case ELIST:
- case ARRAY_DECLARATOR:
- case TYPECAST:
- case EXPR:
- case ARRAY_INIT:
- case FOR_INIT:
- case FOR_CONDITION:
- case FOR_ITERATOR:
- case METHOD_CALL:
- case INSTANCE_INIT:
- case INDEX_OP:
- case SUPER_CTOR_CALL:
- case CTOR_CALL:
- printChildren(ast);
- break;
-
- case METHOD_DEF:
- printMethodDef(ast);
- break;
-
- // if we have two children, it's of the form "a=0"
- // if just one child, it's of the form "=0" (where the
- // lhs is above this AST).
- case ASSIGN:
- if (child2 != null) {
- print(child1);
- out.print("=");
- dumpHiddenAfter(ast);
- print(child2);
- } else {
- out.print("=");
- dumpHiddenAfter(ast);
- print(child1);
- }
- break;
-
- // binary operators:
- case PLUS:
- case MINUS:
- case DIV:
- case MOD:
- case NOT_EQUAL:
- case EQUAL:
- case LE:
- case GE:
- case LOR:
- case LAND:
- case BOR:
- case BXOR:
- case BAND:
- case SL:
- case SR:
- case BSR:
- case LITERAL_instanceof:
- case PLUS_ASSIGN:
- case MINUS_ASSIGN:
- case STAR_ASSIGN:
- case DIV_ASSIGN:
- case MOD_ASSIGN:
- case SR_ASSIGN:
- case BSR_ASSIGN:
- case SL_ASSIGN:
- case BAND_ASSIGN:
- case BXOR_ASSIGN:
- case BOR_ASSIGN:
-
- case LT:
- case GT:
- printBinaryOperator(ast);
- break;
-
- case LITERAL_for:
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- if (child1.getType() == FOR_EACH_CLAUSE) {
- printChildren(child1);
- print(child2);
- } else {
- printChildren(ast);
- }
- break;
-
- case POST_INC:
- case POST_DEC:
- print(child1);
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- break;
-
- // unary operators:
- case BNOT:
- case LNOT:
- case INC:
- case DEC:
- case UNARY_MINUS:
- case UNARY_PLUS:
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- print(child1);
- break;
-
- case LITERAL_new:
- out.print("new");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case LITERAL_return:
- out.print("return");
- dumpHiddenAfter(ast);
- print(child1);
- break;
-
- case STATIC_INIT:
- out.print("static");
- dumpHiddenBefore(getBestPrintableNode(ast, false));
- print(child1);
- break;
-
- case LITERAL_switch:
- out.print("switch");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case LABELED_STAT:
- case CASE_GROUP:
- printChildren(ast);
- break;
-
- case LITERAL_case:
- out.print("case");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case LITERAL_default:
- out.print("default");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case NUM_INT:
- case CHAR_LITERAL:
- case STRING_LITERAL:
- case NUM_FLOAT:
- case NUM_LONG:
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- break;
-
- case LITERAL_synchronized: // 0137 to fix bug #136
- case LITERAL_assert:
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case LITERAL_private:
- case LITERAL_public:
- case LITERAL_protected:
- case LITERAL_static:
- case LITERAL_transient:
- case LITERAL_native:
- case LITERAL_threadsafe:
- //case LITERAL_synchronized: // 0137 to fix bug #136
- case LITERAL_volatile:
- case LITERAL_class: // 0176 to fix bug #1466
- case FINAL:
- case ABSTRACT:
- case LITERAL_package:
- case LITERAL_void:
- case LITERAL_boolean:
- case LITERAL_byte:
- case LITERAL_char:
- case LITERAL_short:
- case LITERAL_int:
- case LITERAL_float:
- case LITERAL_long:
- case LITERAL_double:
- case LITERAL_true:
- case LITERAL_false:
- case LITERAL_null:
- case SEMI:
- case LITERAL_this:
- case LITERAL_super:
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- break;
-
- case EMPTY_STAT:
- case EMPTY_FIELD:
- break;
-
- case LITERAL_continue:
- case LITERAL_break:
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- if (child1 != null) {// maybe label
- print(child1);
- }
- break;
-
- // yuck: Distinguish between "import x.y.*" and "x = 1 * 3"
- case STAR:
- if (hasChildren(ast)) { // the binary mult. operator
- printBinaryOperator(ast);
- } else { // the special "*" in import:
- out.print("*");
- dumpHiddenAfter(ast);
- }
- break;
-
- case LITERAL_throws:
- out.print("throws");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case LITERAL_if:
- printIfThenElse(ast);
- break;
-
- case LITERAL_while:
- out.print("while");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case LITERAL_do:
- out.print("do");
- dumpHiddenAfter(ast);
- print(child1); // an SLIST
- out.print("while");
- dumpHiddenBefore(getBestPrintableNode(child2, false));
- print(child2); // an EXPR
- break;
-
- case LITERAL_try:
- out.print("try");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case LITERAL_catch:
- out.print("catch");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- // the first child is the "try" and the second is the SLIST
- case LITERAL_finally:
- out.print("finally");
- dumpHiddenAfter(ast);
- printChildren(ast);
- break;
-
- case LITERAL_throw:
- out.print("throw");
- dumpHiddenAfter(ast);
- print(child1);
- break;
-
- // the dreaded trinary operator
- case QUESTION:
- print(child1);
- out.print("?");
- dumpHiddenAfter(ast);
- print(child2);
- print(child3);
- break;
-
- // pde specific or modified tokens start here
-
- // Image -> BImage, Font -> BFont as appropriate
- case IDENT:
- /*
- if (ast.getText().equals("Image") &&
- Preferences.getBoolean("preproc.substitute_image")) { //, true)) {
- out.print("BImage");
- } else if (ast.getText().equals("Font") &&
- Preferences.getBoolean("preproc.substitute_font")) { //, true)) {
- out.print("BFont");
- } else {
- */
- out.print(ast.getText());
- //}
- dumpHiddenAfter(ast);
- break;
-
- // the color datatype is just an alias for int
- case LITERAL_color:
- out.print("int");
- dumpHiddenAfter(ast);
- break;
-
- case WEBCOLOR_LITERAL:
- if (ast.getText().length() != 6) {
- System.err.println("Internal error: incorrect length of webcolor "
- + "literal should have been detected sooner.");
- break;
- }
- out.print("0xff" + ast.getText());
- dumpHiddenAfter(ast);
- break;
-
- // allow for stuff like int(43.2).
- case CONSTRUCTOR_CAST:
- final AST terminalTypeNode = child1.getFirstChild();
- final AST exprToCast = child2;
- final String pooType = terminalTypeNode.getText();
- out.print("PApplet.parse" + Character.toUpperCase(pooType.charAt(0))
- + pooType.substring(1));
- dumpHiddenAfter(terminalTypeNode); // the left paren
- print(exprToCast);
- break;
-
- // making floating point literals default to floats, not doubles
- case NUM_DOUBLE:
- final String literalDouble = ast.getText().toLowerCase();
- out.print(literalDouble);
- if (Preferences.getBoolean("preproc.substitute_floats")
- && literalDouble.indexOf('d') == -1) { // permit literal doubles
- out.print("f");
- }
- dumpHiddenAfter(ast);
- break;
-
- case TYPE_ARGUMENTS:
- case TYPE_PARAMETERS:
- printChildren(ast);
- break;
-
- case TYPE_ARGUMENT:
- case TYPE_PARAMETER:
- printChildren(ast);
- break;
-
- case WILDCARD_TYPE:
- out.print(ast.getText());
- dumpHiddenAfter(ast);
- print(ast.getFirstChild());
- break;
-
- case TYPE_LOWER_BOUNDS:
- case TYPE_UPPER_BOUNDS:
- out.print(ast.getType() == TYPE_LOWER_BOUNDS ? "super" : "extends");
- dumpHiddenBefore(getBestPrintableNode(ast, false));
- printChildren(ast);
- break;
-
- case ANNOTATION:
- out.print("@");
- printChildren(ast);
- break;
-
- case ANNOTATIONS:
- case ANNOTATION_ARRAY_INIT:
- printChildren(ast);
- break;
-
- case ANNOTATION_MEMBER_VALUE_PAIR:
- print(ast.getFirstChild());
- out.print("=");
- dumpHiddenBefore(getBestPrintableNode(ast.getFirstChild().getNextSibling(), false));
- print(ast.getFirstChild().getNextSibling());
- break;
-
- default:
- debug.println("Unrecognized type:" + ast.getType() + " ("
- + TokenUtil.nameOf(ast) + ")");
- break;
- }
-
- stack.pop();
- }
-
-}
diff --git a/java/src/processing/mode/java/preproc/PdeParseTreeErrorListener.java b/java/src/processing/mode/java/preproc/PdeParseTreeErrorListener.java
new file mode 100644
index 0000000000..2662ada7bd
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/PdeParseTreeErrorListener.java
@@ -0,0 +1,39 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc;
+
+import processing.mode.java.preproc.issue.PdePreprocessIssue;
+
+
+/**
+ * Listener for issues encountered while processing a valid pde parse tree.
+ */
+interface PdeParseTreeErrorListener {
+
+ /**
+ * Callback to invoke when an issue is encountered while processing a valid PDE parse tree.
+ *
+ * @param issue The issue reported.
+ */
+ void onError(PdePreprocessIssue issue);
+
+}
diff --git a/java/src/processing/mode/java/preproc/PdeParseTreeListener.java b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java
new file mode 100644
index 0000000000..cc37a06d3c
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/PdeParseTreeListener.java
@@ -0,0 +1,708 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2019 The Processing Foundation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc;
+
+import java.util.*;
+
+import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.misc.Interval;
+
+import org.antlr.v4.runtime.tree.ParseTree;
+import processing.core.PApplet;
+import processing.mode.java.pdex.TextTransform;
+import processing.mode.java.preproc.PdePreprocessor.Mode;
+import processing.mode.java.preproc.code.*;
+import processing.mode.java.preproc.issue.PdePreprocessIssue;
+import processing.mode.java.preproc.issue.strategy.MessageSimplifierUtil;
+
+/**
+ * ANTLR tree traversal listener that preforms code rewrites as part of sketch preprocessing.
+ *
+ *
+ * ANTLR tree traversal listener that preforms code rewrites as part of sketch preprocessing,
+ * turning sketch source into compilable Java code. Note that this emits both the Java source
+ * when using javac directly as part of {JavaBuild} as well as {TextTransform.Edit}s when using
+ * the JDT via the {PreprocessingService}.
+ *
+ */
+public class PdeParseTreeListener extends ProcessingBaseListener {
+
+ private final static String VERSION_STR = "3.0.0";
+ private static final String SIZE_METHOD_NAME = "size";
+ private static final String FULLSCREEN_METHOD_NAME = "fullScreen";
+ private final int tabSize;
+
+ private int headerOffset;
+
+ private String sketchName;
+ private boolean isTesting;
+ private TokenStreamRewriter rewriter;
+
+ protected Mode mode = Mode.JAVA;
+ private boolean foundMain;
+
+ private int lineOffset;
+
+ private ArrayList coreImports = new ArrayList<>();
+ private ArrayList defaultImports = new ArrayList<>();
+ private ArrayList codeFolderImports = new ArrayList<>();
+ private ArrayList foundImports = new ArrayList<>();
+ private ArrayList edits = new ArrayList<>();
+
+ private String sketchWidth;
+ private String sketchHeight;
+ private String sketchRenderer;
+
+ private boolean sizeRequiresRewrite = false;
+ private boolean sizeIsFullscreen = false;
+ private RewriteResult headerResult;
+ private RewriteResult footerResult;
+
+ private Optional pdeParseTreeErrorListenerMaybe;
+
+ /**
+ * Create a new listener.
+ *
+ * @param tokens The tokens over which to rewrite.
+ * @param newSketchName The name of the sketch being traversed.
+ * @param newTabSize Size of tab / indent.
+ */
+ PdeParseTreeListener(TokenStream tokens, String newSketchName, int newTabSize) {
+ rewriter = new TokenStreamRewriter(tokens);
+ sketchName = newSketchName;
+ tabSize = newTabSize;
+
+ pdeParseTreeErrorListenerMaybe = Optional.empty();
+ }
+
+ /**
+ * Indicate imports for code folders.
+ *
+ * @param codeFolderImports List of imports for sources sitting in the sketch code folder.
+ */
+ public void setCodeFolderImports(List codeFolderImports) {
+ this.codeFolderImports.clear();
+ this.codeFolderImports.addAll(codeFolderImports);
+ }
+
+ /**
+ * Indicate list of imports required for all sketches to be inserted in preprocessing.
+ *
+ * @param coreImports The list of imports required for all sketches.
+ */
+ public void setCoreImports(String[] coreImports) {
+ setCoreImports(Arrays.asList(coreImports));
+ }
+
+ /**
+ * Indicate list of imports required for all sketches to be inserted in preprocessing.
+ *
+ * @param coreImports The list of imports required for all sketches.
+ */
+ public void setCoreImports(List coreImports) {
+ this.coreImports.clear();
+ this.coreImports.addAll(coreImports);
+ }
+
+ /**
+ * Indicate list of default convenience imports.
+ *
+ *
+ * Indicate list of imports that are not required for sketch operation but included for the
+ * user's convenience regardless.
+ *
+ *
+ * @param defaultImports The list of imports to include for user convenience.
+ */
+ public void setDefaultImports(String[] defaultImports) {
+ setDefaultImports(Arrays.asList(defaultImports));
+ }
+
+ /**
+ * Indicate list of default convenience imports.
+ *
+ *
+ * Indicate list of imports that are not required for sketch operation but included for the
+ * user's convenience regardless.
+ *
+ *
+ * @param defaultImports The list of imports to include for user convenience.
+ */
+ public void setDefaultImports(List defaultImports) {
+ this.defaultImports.clear();
+ this.defaultImports.addAll(defaultImports);
+ }
+
+ /**
+ * Indicate if running in unit tests.
+ *
+ * @param isTesting True if running as part of tests and false otherwise.
+ */
+ public void setTesting(boolean isTesting) {
+ this.isTesting = isTesting;
+ }
+
+ /**
+ * Indicate which listener should be informed of parse tree processing issues.
+ *
+ * @param newListener listener to be informed when an issue is encoutnered in processing the
+ * parse tree.
+ */
+ public void setTreeErrorListener(PdeParseTreeErrorListener newListener) {
+ pdeParseTreeErrorListenerMaybe = Optional.of(newListener);
+ }
+
+ /**
+ * Determine if the user provided their own "main" method.
+ *
+ * @return True if the sketch code provides a main method. False otherwise.
+ */
+ public boolean foundMain() {
+ return foundMain;
+ }
+
+ /**
+ * Get the sketch code transformed to grammatical Java.
+ *
+ * @return Complete sketch code as Java.
+ */
+ public String getOutputProgram() {
+ return rewriter.getText();
+ }
+
+ /**
+ * Get the rewriter used by this listener.
+ *
+ * @return Listener's rewriter.
+ */
+ public TokenStreamRewriter getRewriter() {
+ return rewriter;
+ }
+
+ /**
+ * Get the result of the last preprocessing.
+ *
+ * @return The result of the last preprocessing.
+ */
+ public PreprocessorResult getResult() {
+ List allImports = new ArrayList<>();
+
+ allImports.addAll(coreImports);
+ allImports.addAll(defaultImports);
+ allImports.addAll(codeFolderImports);
+ allImports.addAll(foundImports);
+
+ List allEdits = new ArrayList<>();
+ allEdits.addAll(headerResult.getEdits());
+ allEdits.addAll(edits);
+ allEdits.addAll(footerResult.getEdits());
+
+ return new PreprocessorResult(
+ mode,
+ lineOffset,
+ sketchName,
+ allImports,
+ allEdits,
+ sketchWidth,
+ sketchHeight
+ );
+ }
+
+ // --------------------------------------------------- listener impl
+
+ /**
+ * Endpoint for ANTLR to call when having finished parsing a processing sketch.
+ *
+ * @param ctx The context from ANTLR for the processing sketch.
+ */
+ public void exitProcessingSketch(ProcessingParser.ProcessingSketchContext ctx) {
+ // header
+ RewriteParams rewriteParams = createRewriteParams();
+
+ RewriterCodeGenerator codeGen = new RewriterCodeGenerator(tabSize);
+
+ headerResult = codeGen.writeHeader(rewriter, rewriteParams);
+
+ lineOffset = headerResult.getLineOffset();
+
+ // footer
+ TokenStream tokenStream = rewriter.getTokenStream();
+ int tokens = tokenStream.size();
+ int length = tokenStream.get(tokens-1).getStopIndex();
+
+ footerResult = codeGen.writeFooter(rewriter, rewriteParams, length);
+ }
+
+ /**
+ * Endpoint for ANTLR to call when finished parsing a method invocatino.
+ *
+ * @param ctx The ANTLR context for the method call.
+ */
+ public void exitMethodCall(ProcessingParser.MethodCallContext ctx) {
+ String methodName = ctx.getChild(0).getText();
+
+ if (SIZE_METHOD_NAME.equals(methodName) || FULLSCREEN_METHOD_NAME.equals(methodName)) {
+ handleSizeCall(ctx);
+ }
+ }
+
+ /**
+ * Manage parsing out a size or fullscreen call.
+ *
+ * @param ctx The context of the call.
+ */
+ private void handleSizeCall(ParserRuleContext ctx) {
+ ParserRuleContext testCtx = ctx.getParent()
+ .getParent()
+ .getParent()
+ .getParent();
+
+ boolean isInGlobal =
+ testCtx instanceof ProcessingParser.StaticProcessingSketchContext;
+
+ boolean isInSetup;
+ if (!isInGlobal) {
+ ParserRuleContext methodDeclaration = testCtx.getParent()
+ .getParent();
+
+ isInSetup = isMethodSetup(methodDeclaration);
+ } else {
+ isInSetup = false;
+ }
+
+ ParseTree argsContext = ctx.getChild(2);
+
+ boolean thisRequiresRewrite = false;
+
+ boolean isSize = ctx.getChild(0).getText().equals(SIZE_METHOD_NAME);
+ boolean isFullscreen = ctx.getChild(0).getText().equals(FULLSCREEN_METHOD_NAME);
+
+ if (isInGlobal || isInSetup) {
+ thisRequiresRewrite = true;
+
+ if (isSize && argsContext.getChildCount() > 2) {
+ sketchWidth = argsContext.getChild(0).getText();
+ if (PApplet.parseInt(sketchWidth, -1) == -1 &&
+ !sketchWidth.equals("displayWidth")) {
+ thisRequiresRewrite = false;
+ }
+
+ sketchHeight = argsContext.getChild(2).getText();
+ if (PApplet.parseInt(sketchHeight, -1) == -1 &&
+ !sketchHeight.equals("displayHeight")) {
+ thisRequiresRewrite = false;
+ }
+
+ if (argsContext.getChildCount() > 3) {
+ sketchRenderer = argsContext.getChild(4).getText();
+ if (!(sketchRenderer.equals("P2D") ||
+ sketchRenderer.equals("P3D") ||
+ sketchRenderer.equals("OPENGL") ||
+ sketchRenderer.equals("JAVA2D") ||
+ sketchRenderer.equals("FX2D"))) {
+ thisRequiresRewrite = false;
+ }
+ }
+ }
+
+ if (isFullscreen) {
+ sketchWidth = "displayWidth";
+ sketchWidth = "displayHeight";
+
+ thisRequiresRewrite = true;
+ sizeIsFullscreen = true;
+
+ if (argsContext.getChildCount() > 0) {
+ sketchRenderer = argsContext.getChild(0).getText();
+ if (!(sketchRenderer.equals("P2D") ||
+ sketchRenderer.equals("P3D") ||
+ sketchRenderer.equals("OPENGL") ||
+ sketchRenderer.equals("JAVA2D") ||
+ sketchRenderer.equals("FX2D"))) {
+ thisRequiresRewrite = false;
+ }
+ }
+ }
+ }
+
+ if (thisRequiresRewrite) {
+ createDelete(ctx.start, ctx.stop);
+ createInsertAfter(ctx.stop, "/* size commented out by preprocessor */");
+ sizeRequiresRewrite = true;
+ }
+ }
+
+ /**
+ * Determine if a method declaration is for setup.
+ *
+ * @param declaration The method declaration to parse.
+ * @return True if setup and false otherwise.
+ */
+ private boolean isMethodSetup(ParserRuleContext declaration) {
+ if (declaration.getChildCount() < 2) {
+ return false;
+ }
+ return declaration.getChild(1).getText().equals("setup");
+ }
+
+ /**
+ * Endpoint for ANTLR to call when finished parsing an import declaration.
+ *
+ *
+ * Endpoint for ANTLR to call when finished parsing an import declaration, remvoing those
+ * declarations from sketch body so that they can be included in the header.
+ *
+ *
+ * @param ctx ANTLR context for the import declaration.
+ */
+ public void exitImportDeclaration(ProcessingParser.ImportDeclarationContext ctx) {
+ ProcessingParser.QualifiedNameContext startCtx = null;
+
+ // Due to imports pre-procesing, cannot allow class-body imports
+ if (ctx.getParent() instanceof ProcessingParser.ClassBodyDeclarationContext) {
+ pdeParseTreeErrorListenerMaybe.ifPresent((listener) -> {
+ Token token = ctx.getStart();
+ int line = token.getLine();
+ int charOffset = token.getCharPositionInLine();
+
+ listener.onError(new PdePreprocessIssue(
+ line,
+ charOffset,
+ MessageSimplifierUtil.getLocalStr("editor.status.bad.import")
+ ));
+ });
+ }
+
+ for(int i = 0; i < ctx.getChildCount(); i++) {
+ ParseTree candidate = ctx.getChild(i);
+ if (candidate instanceof ProcessingParser.QualifiedNameContext) {
+ startCtx = (ProcessingParser.QualifiedNameContext) ctx.getChild(i);
+ }
+ }
+
+ if (startCtx == null) {
+ return;
+ }
+
+ Interval interval =
+ new Interval(startCtx.start.getStartIndex(), ctx.stop.getStopIndex());
+ String importString = ctx.start.getInputStream().getText(interval);
+ String importStringNoSemi = importString.substring(0, importString.length() - 1);
+ foundImports.add(importStringNoSemi);
+
+ createDelete(ctx.start, ctx.stop);
+ }
+
+ /**
+ * Endpoint for ANTLR to call after parsing a decimal point literal.
+ *
+ *
+ * Endpoint for ANTLR to call when finished parsing a floating point literal, adding an 'f' at
+ * the end to force it float instead of double for API compatability.
+ *
+ *
+ * @param ctx ANTLR context for the literal.
+ */
+ public void exitFloatLiteral(ProcessingParser.FloatLiteralContext ctx) {
+ String cTxt = ctx.getText().toLowerCase();
+ if (!cTxt.endsWith("f") && !cTxt.endsWith("d")) {
+ createInsertAfter(ctx.stop, "f");
+ }
+ }
+
+ /**
+ * Endpoint for ANTLR to call after parsing a static processing sketch.
+ *
+ *
+ * Endpoint for ANTLR to call after parsing a static processing sketch, informing this parser
+ * that it is operating on a static sketch (no method or class declarations) so that it writes
+ * the correct header / footer.
+ *
+ *
+ * @param ctx ANTLR context for the sketch.
+ */
+ public void exitStaticProcessingSketch(ProcessingParser.StaticProcessingSketchContext ctx) {
+ mode = Mode.STATIC;
+ }
+
+ /**
+ * Endpoint for ANTLR to call after parsing a "active" processing sketch.
+ *
+ *
+ * Endpoint for ANTLR to call after parsing a "active" processing sketch, informing this parser
+ * that it is operating on an active sketch so that it writes the correct header / footer.
+ *
+ *
+ * @param ctx ANTLR context for the sketch.
+ */
+ public void exitActiveProcessingSketch(ProcessingParser.ActiveProcessingSketchContext ctx) {
+ mode = Mode.ACTIVE;
+ }
+
+ /**
+ * Endpoint for ANTLR to call after parsing a method declaration.
+ *
+ *
+ * Endpoint for ANTLR to call after parsing a method declaration, making any method "public"
+ * that has:
+ *
+ *
+ *
no other access modifier
+ *
return type "void"
+ *
is either in the context of the sketch class
+ *
is in the context of a class definition that extends PApplet
+ *
+ *
+ *
+ * @param ctx ANTLR context for the method declaration
+ */
+ public void exitMethodDeclaration(ProcessingParser.MethodDeclarationContext ctx) {
+ ParserRuleContext memCtx = ctx.getParent();
+ ParserRuleContext clsBdyDclCtx = memCtx.getParent();
+ ParserRuleContext clsBdyCtx = clsBdyDclCtx.getParent();
+ ParserRuleContext clsDclCtx = clsBdyCtx.getParent();
+
+ boolean inSketchContext =
+ clsBdyCtx instanceof ProcessingParser.StaticProcessingSketchContext ||
+ clsBdyCtx instanceof ProcessingParser.ActiveProcessingSketchContext;
+
+ boolean inPAppletContext =
+ inSketchContext || (
+ clsDclCtx instanceof ProcessingParser.ClassDeclarationContext &&
+ clsDclCtx.getChildCount() >= 4 &&
+ clsDclCtx.getChild(2).getText().equals("extends") &&
+ clsDclCtx.getChild(3).getText().endsWith("PApplet"));
+
+ // Find modifiers
+ ParserRuleContext possibleModifiers = ctx;
+
+ while (!(possibleModifiers instanceof ProcessingParser.ClassBodyDeclarationContext)) {
+ possibleModifiers = possibleModifiers.getParent();
+ }
+
+ // Look for visibility modifiers and annotations
+ boolean hasVisibilityModifier = false;
+
+ int numChildren = possibleModifiers.getChildCount();
+
+ ParserRuleContext annoationPoint = null;
+
+ for (int i = 0; i < numChildren; i++) {
+ boolean childIsVisibility;
+
+ ParseTree child = possibleModifiers.getChild(i);
+ String childText = child.getText();
+
+ childIsVisibility = childText.equals("public");
+ childIsVisibility = childIsVisibility || childText.equals("private");
+ childIsVisibility = childIsVisibility || childText.equals("protected");
+
+ hasVisibilityModifier = hasVisibilityModifier || childIsVisibility;
+
+ boolean isModifier = child instanceof ProcessingParser.ModifierContext;
+ if (isModifier && isAnnoation((ProcessingParser.ModifierContext) child)) {
+ annoationPoint = (ParserRuleContext) child;
+ }
+ }
+
+ // Insert at start of method or after annoation
+ if (!hasVisibilityModifier) {
+ if (annoationPoint == null) {
+ createInsertBefore(possibleModifiers.getStart(), " public ");
+ } else {
+ createInsertAfter(annoationPoint.getStop(), " public ");
+ }
+ }
+
+ // Check if this was main
+ if ((inSketchContext || inPAppletContext) &&
+ hasVisibilityModifier &&
+ ctx.getChild(1).getText().equals("main")) {
+ foundMain = true;
+ }
+ }
+
+ /**
+ * Check if this contains an annation.
+ *
+ * @param child The modifier context to check.
+ * @return True if annotation. False otherwise
+ */
+ private boolean isAnnoation(ProcessingParser.ModifierContext context) {
+ if (context.getChildCount() == 0) {
+ return false;
+ }
+
+ ProcessingParser.ClassOrInterfaceModifierContext classModifierCtx;
+ if (!(context.getChild(0) instanceof ProcessingParser.ClassOrInterfaceModifierContext)) {
+ return false;
+ }
+
+ classModifierCtx = (ProcessingParser.ClassOrInterfaceModifierContext) context.getChild(0);
+
+ return classModifierCtx.getChild(0) instanceof ProcessingParser.AnnotationContext;
+ }
+
+ /**
+ * Endpoint for ANTLR to call after parsing a primitive type name.
+ *
+ *
+ * Endpoint for ANTLR to call after parsing a primitive type name, possibly converting that type
+ * to a parse function as part of the Processing API.
+ *
+ *
+ * @param ctx ANTLR context for the primitive name token.
+ */
+ public void exitFunctionWithPrimitiveTypeName(
+ ProcessingParser.FunctionWithPrimitiveTypeNameContext ctx) {
+
+ String fn = ctx.getChild(0).getText();
+ if (!fn.equals("color")) {
+ fn = "PApplet.parse" + fn.substring(0,1).toUpperCase() + fn.substring(1);
+ createInsertBefore(ctx.start, fn);
+ createDelete(ctx.start);
+ }
+ }
+
+ /**
+ * Endpoint for ANTLR to call after parsing a color primitive token.
+ *
+ *
+ * Endpoint for ANTLR to call after parsing a color primitive token, fixing "color type" to be
+ * "int" as part of the processing API.
+ *
+ *
+ * @param ctx ANTLR context for the type token.
+ */
+ public void exitColorPrimitiveType(ProcessingParser.ColorPrimitiveTypeContext ctx) {
+ if (ctx.getText().equals("color")) {
+ createInsertBefore(ctx.start, "int");
+ createDelete(ctx.start, ctx.stop);
+ }
+ }
+
+ /**
+ * Endpoint for ANTLR to call after parsing a hex color literal.
+ *
+ * @param ctx ANTLR context for the literal.
+ */
+ public void exitHexColorLiteral(ProcessingParser.HexColorLiteralContext ctx) {
+ if (ctx.getText().length() == 7) {
+ createInsertBefore(
+ ctx.start,
+ ctx.getText().toUpperCase().replace("#","0xFF")
+ );
+ } else {
+ createInsertBefore(
+ ctx.start,
+ ctx.getText().toUpperCase().replace("#", "0x")
+ );
+ }
+
+ createDelete(ctx.start, ctx.stop);
+ }
+
+ // -- Wrappers around CodeEditOperationUtil --
+
+ /**
+ * Insert text before a token.
+ *
+ * @param location The token before which code should be added.
+ * @param text The text to add.
+ */
+ private void createInsertBefore(Token location, String text) {
+ edits.add(CodeEditOperationUtil.createInsertBefore(location, text, rewriter));
+ }
+
+ /**
+ * Insert text before a location in code.
+ *
+ * @param locationToken Character offset from start.
+ * @param locationOffset
+ * @param text Text to add.
+ */
+ private void createInsertBefore(int locationToken, int locationOffset, String text) {
+ edits.add(CodeEditOperationUtil.createInsertBefore(
+ locationToken,
+ locationOffset,
+ text,
+ rewriter
+ ));
+ }
+
+ /**
+ * Insert text after a location in code.
+ *
+ * @param location The token after which to insert code.
+ * @param text The text to insert.
+ */
+ private void createInsertAfter(Token location, String text) {
+ edits.add(CodeEditOperationUtil.createInsertAfter(location, text, rewriter));
+ }
+
+ /**
+ * Delete from a token to a token inclusive.
+ *
+ * @param start First token to delete.
+ * @param stop Last token to delete.
+ */
+ private void createDelete(Token start, Token stop) {
+ edits.add(CodeEditOperationUtil.createDelete(start, stop, rewriter));
+ }
+
+ /**
+ * Delete a single token.
+ *
+ * @param location Token to delete.
+ */
+ private void createDelete(Token location) {
+ edits.add(CodeEditOperationUtil.createDelete(location, rewriter));
+ }
+
+ /**
+ * Create parameters required by the RewriterCodeGenerator.
+ *
+ * @return Newly created rewrite params.
+ */
+ private RewriteParams createRewriteParams() {
+ RewriteParamsBuilder builder = new RewriteParamsBuilder(VERSION_STR);
+
+ builder.setSketchName(sketchName);
+ builder.setisTesting(isTesting);
+ builder.setRewriter(rewriter);
+ builder.setMode(mode);
+ builder.setFoundMain(foundMain);
+ builder.setLineOffset(lineOffset);
+ builder.setSketchWidth(sketchWidth);
+ builder.setSketchHeight(sketchHeight);
+ builder.setSketchRenderer(sketchRenderer);
+ builder.setIsSizeValidInGlobal(sizeRequiresRewrite);
+ builder.setIsSizeFullscreen(sizeIsFullscreen);
+
+ builder.addCoreImports(coreImports);
+ builder.addDefaultImports(defaultImports);
+ builder.addCodeFolderImports(codeFolderImports);
+ builder.addFoundImports(foundImports);
+
+ return builder.build();
+ }
+}
diff --git a/java/src/processing/mode/java/preproc/PdePreprocessor.java b/java/src/processing/mode/java/preproc/PdePreprocessor.java
index c11b229667..44cfd2e5cc 100644
--- a/java/src/processing/mode/java/preproc/PdePreprocessor.java
+++ b/java/src/processing/mode/java/preproc/PdePreprocessor.java
@@ -1,1338 +1,259 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
- PdePreprocessor - wrapper for default ANTLR-generated parser
- Part of the Processing project - http://processing.org
+Part of the Processing project - http://processing.org
- Copyright (c) 2012-19 The Processing Foundation
- Copyright (c) 2004-12 Ben Fry and Casey Reas
- Copyright (c) 2001-04 Massachusetts Institute of Technology
+Copyright (c) 2012-19 The Processing Foundation
- ANTLR-generated parser and several supporting classes written
- by Dan Mosedale via funding from the Interaction Institute IVREA.
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.mode.java.preproc;
-import java.awt.EventQueue;
-import java.io.*;
-import java.util.*;
-import java.util.regex.MatchResult;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
-import processing.app.Messages;
import processing.app.Preferences;
import processing.app.SketchException;
-import processing.core.PApplet;
-import processing.data.StringList;
-
-import antlr.*;
-import antlr.collections.AST;
+import processing.mode.java.preproc.code.ImportUtil;
+import processing.mode.java.preproc.issue.PdeIssueEmitter;
+import processing.mode.java.preproc.issue.PdePreprocessIssue;
/**
- * Class that orchestrates preprocessing p5 syntax into straight Java.
- *
- * Current Preprocessor Subsitutions:
- *
- *
any function not specified as being protected or private will
- * be made 'public'. this means that void setup() becomes
- * public void setup(). This is important to note when
- * coding with core.jar outside of the PDE.
- *
compiler.substitute_floats (currently "substitute_f")
- * treat doubles as floats, i.e. 12.3 becomes 12.3f so that people
- * don't have to add f after their numbers all the time since it's
- * confusing for beginners.
- *
compiler.enhanced_casting byte(), char(), int(), float()
- * works for casting. this is basic in the current implementation, but
- * should be expanded as described above. color() works similarly to int(),
- * however there is also a *function* called color(r, g, b) in p5.
- *
compiler.color_datatype 'color' is aliased to 'int'
- * as a datatype to represent ARGB packed into a single int, commonly
- * used in p5 for pixels[] and other color operations. this is just a
- * search/replace type thing, and it can be used interchangeably with int.
- *
compiler.web_colors (currently "inline_web_colors")
- * color c = #cc0080; should unpack to 0xffcc0080 (the ff at the top is
- * so that the color is opaque), which is just an int.
- *
- * Other preprocessor functionality
- *
- *
detects what 'mode' the program is in: static (no function
- * brackets at all, just assumes everything is in draw), active
- * (setup plus draw or loop), and java mode (full java support).
- * http://processing.org/reference/environment/
- *
- *
- * The PDE Preprocessor is based on the Java Grammar that comes with
- * ANTLR 2.7.2. Moving it forward to a new version of the grammar
- * shouldn't be too difficult.
- *
- * Here's some info about the various files in this directory:
- *
- * java.g: this is the ANTLR grammar for Java 1.3/1.4 from the
- * ANTLR distribution. It is in the public domain. The only change to
- * this file from the original this file is the uncommenting of the
- * clauses required to support assert().
- *
- * java.tree.g: this describes the Abstract Syntax Tree (AST)
- * generated by java.g. It is only here as a reference for coders hacking
- * on the preprocessor, it is not built or used at all. Note that pde.g
- * overrides some of the java.g rules so that in PDE ASTs, there are a
- * few minor differences. Also in the public domain.
- *
- * pde.g: this is the grammar and lexer for the PDE language
- * itself. It subclasses the java.g grammar and lexer. There are a couple
- * of overrides to java.g that I hope to convince the ANTLR folks to fold
- * back into their grammar, but most of this file is highly specific to
- * PDE itself.
- * PdeEmitter.java: this class traverses the AST generated by
- * the PDE Recognizer, and emits it as Java code, doing any necessary
- * transformations along the way. It is based on JavaEmitter.java,
- * available from antlr.org, written by Andy Tripp ,
- * who has given permission for it to be distributed under the GPL.
- *
- * ExtendedCommonASTWithHiddenTokens.java: this adds a necessary
- * initialize() method, as well as a number of methods to allow for XML
- * serialization of the parse tree in a such a way that the hidden tokens
- * are visible. Much of the code is taken from the original
- * CommonASTWithHiddenTokens class. I hope to convince the ANTLR folks
- * to fold these changes back into that class so that this file will be
- * unnecessary.
- *
- * TokenStreamCopyingHiddenTokenFilter.java: this class provides
- * TokenStreamHiddenTokenFilters with the concept of tokens which can be
- * copied so that they are seen by both the hidden token stream as well
- * as the parser itself. This is useful when one wants to use an
- * existing parser (like the Java parser included with ANTLR) that throws
- * away some tokens to create a parse tree which can be used to spit out
- * a copy of the code with only minor modifications. Partially derived
- * from ANTLR code. I hope to convince the ANTLR folks to fold this
- * functionality back into ANTLR proper as well.
- *
- * whitespace_test.pde: a torture test to ensure that the
- * preprocessor is correctly preserving whitespace, comments, and other
- * hidden tokens correctly. See the comments in the code for details about
- * how to run the test.
- *
- * All other files in this directory are generated at build time by ANTLR
- * itself. The ANTLR manual goes into a fair amount of detail about the
- * what each type of file is for.
- *
+ * Utility to preprocess sketches prior to comilation.
*/
public class PdePreprocessor {
- protected static final String UNICODE_ESCAPES = "0123456789abcdefABCDEF";
-
- // used for calling the ASTFactory to get the root node
- private static final int ROOT_ID = 0;
-
- protected final String indent;
- private final String name;
- public enum Mode {
+ public static enum Mode {
STATIC, ACTIVE, JAVA
}
- private TokenStreamCopyingHiddenTokenFilter filter;
+ private String sketchName;
+ private int tabSize;
- private String advClassName = "";
- protected Mode mode;
- Set foundMethods;
-
- SurfaceInfo sizeInfo;
- boolean settingsMethod;
+ private boolean hasMain;
+ private final boolean isTesting;
/**
- * Regular expression for parsing the size() method. This should match
- * against any uses of the size() function, whether numbers or variables
- * or whatever. This way, no warning is shown if size() isn't actually used
- * in the sketch, which is the case especially for anyone who is cutting
- * and pasting from the reference.
+ * Create a new preprocessor.
+ *
+ * @param sketchName The name of the sketch.
*/
-// public static final String SIZE_REGEX =
-// "(?:^|\\s|;)size\\s*\\(\\s*([^\\s,]+)\\s*,\\s*([^\\s,\\)]+)\\s*,?\\s*([^\\)]*)\\s*\\)\\s*\\;";
-// static private final String SIZE_CONTENTS_REGEX =
-// "(?:^|\\s|;)size\\s*\\(([^\\)]+)\\)\\s*\\;";
-// static private final String FULL_SCREEN_CONTENTS_REGEX =
-// "(?:^|\\s|;)fullScreen\\s*\\(([^\\)]+)\\)\\s*\\;";
-// /** Test whether there's a void somewhere (the program has functions). */
-// static private final String VOID_REGEX =
-// "(?:^|\\s|;)void\\s";
- /** Used to grab the start of setup() so we can mine it for size() */
- static private final Pattern VOID_SETUP_REGEX =
- Pattern.compile("(?:^|\\s|;)void\\s+setup\\s*\\(", Pattern.MULTILINE);
-
- static private final Pattern VOID_SETTINGS_REGEX =
- Pattern.compile("(?:^|\\s|;)void\\s+settings\\s*\\(", Pattern.MULTILINE);
-
- // Can't only match any 'public class', needs to be a PApplet
- // http://code.google.com/p/processing/issues/detail?id=551
- static private final Pattern PUBLIC_CLASS =
- Pattern.compile("(^|;)\\s*public\\s+class\\s+\\S+\\s+extends\\s+PApplet", Pattern.MULTILINE);
-
-
- static private final Pattern FUNCTION_DECL =
- Pattern.compile("(^|;)\\s*((public|private|protected|final|static)\\s+)*" +
- "(void|int|float|double|String|char|byte|boolean)" +
- "(\\s*\\[\\s*\\])?\\s+[a-zA-Z0-9]+\\s*\\(",
- Pattern.MULTILINE);
-
- static private final Pattern CLOSING_BRACE = Pattern.compile("\\}");
-
-
public PdePreprocessor(final String sketchName) {
- this(sketchName, Preferences.getInteger("editor.tabs.size"));
- }
-
-
- public PdePreprocessor(final String sketchName, final int tabSize) {
- this.name = sketchName;
- final char[] indentChars = new char[tabSize];
- Arrays.fill(indentChars, ' ');
- indent = new String(indentChars);
- }
-
-
- /** Parse the sketch size and set the internal sizeInfo variable */
- public SurfaceInfo initSketchSize(String code,
- boolean sizeWarning) throws SketchException {
- sizeInfo = parseSketchSize(code, sizeWarning);
- return sizeInfo;
- }
-
-
- /**
- * Break on commas, except those inside quotes,
- * e.g.: size(300, 200, PDF, "output,weirdname.pdf");
- * No special handling implemented for escaped (\") quotes.
- */
- static private StringList breakCommas(String contents) {
- StringList outgoing = new StringList();
-
- boolean insideQuote = false;
- // The current word being read
- StringBuilder current = new StringBuilder();
- char[] chars = contents.toCharArray();
- for (int i = 0; i < chars.length; i++) {
- char c = chars[i];
- if (insideQuote) {
- current.append(c);
- if (c == '\"') {
- insideQuote = false;
- }
- } else {
- if (c == ',') {
- if (current.length() != 0) {
- outgoing.append(current.toString());
- current.setLength(0);
- }
- } else {
- current.append(c);
- if (c == '\"') {
- insideQuote = true;
- }
- }
- }
- }
- if (current.length() != 0) {
- outgoing.append(current.toString());
- }
- return outgoing;
- }
-
-
- // if there's a settings() method, we do less moving things around
- static public boolean hasSettingsMethod(String code) {
- final String uncommented = scrubComments(code);
- return findInCurrentScope(VOID_SETTINGS_REGEX, uncommented) != null;
- }
-
-
- /**
- * Parse a chunk of code and extract the size() command and its contents.
- * Also goes after fullScreen(), smooth(), and noSmooth().
- * @param code The code from the main tab in the sketch
- * @param fussy true if it should show an error message if bad size()
- * @return null if there was an error, otherwise an array (might contain some/all nulls)
- */
- static public SurfaceInfo parseSketchSize(String code,
- boolean fussy) throws SketchException {
- // This matches against any uses of the size() function, whether numbers
- // or variables or whatever. This way, no warning is shown if size() isn't
- // actually used in the applet, which is the case especially for anyone
- // who is cutting/pasting from the reference.
-
- // 1. no size() or fullScreen() method at all
- // will use the non-overridden settings() method in PApplet
- // 2. size() or fullScreen() found inside setup() (static mode sketch or otherwise)
- // make sure that it uses numbers (or displayWidth/Height), copy into settings
- // 3. size() or fullScreen() already in settings()
- // don't mess with the sketch, don't insert any defaults
- //
- // really only need to deal with situation #2.. nothing to be done for 1 and 3
-
- // if static mode sketch, all we need is regex
- // easy proxy for static in this case is whether [^\s]void\s is present
-
- String uncommented = scrubComments(code);
-
- Mode mode = parseMode(uncommented);
-
- String searchArea = null;
-
- if (mode == Mode.JAVA) {
- // it's up to the user
- searchArea = null;
-
- } else if (mode == Mode.ACTIVE) {
- // active mode, limit scope to setup
-
- // Find setup() in global scope
- MatchResult setupMatch = findInCurrentScope(VOID_SETUP_REGEX, uncommented);
- if (setupMatch != null) {
- int start = uncommented.indexOf("{", setupMatch.end());
- if (start >= 0) {
- // Find a closing brace
- MatchResult match = findInCurrentScope(CLOSING_BRACE, uncommented, start);
- if (match != null) {
- searchArea = uncommented.substring(start + 1, match.end() - 1);
- } else {
- throw new SketchException("Found a { that's missing a matching }", false);
- }
- }
- }
- } else if (mode == Mode.STATIC) {
- // static mode, look everywhere
- searchArea = uncommented;
- }
-
- if (searchArea == null) {
- return new SurfaceInfo();
- }
-
- StringList extraStatements = new StringList();
-
- // First look for noSmooth() or smooth(N) so we can hoist it into settings.
- String[] smoothContents = matchMethod("smooth", searchArea);
- if (smoothContents != null) {
- extraStatements.append(smoothContents[0]);
- }
- String[] noContents = matchMethod("noSmooth", searchArea);
- if (noContents != null) {
- if (extraStatements.size() != 0) {
- throw new SketchException("smooth() and noSmooth() cannot be used in the same sketch");
- } else {
- extraStatements.append(noContents[0]);
- }
- }
- String[] pixelDensityContents = matchMethod("pixelDensity", searchArea);
- if (pixelDensityContents != null) {
- extraStatements.append(pixelDensityContents[0]);
- } else {
- pixelDensityContents = matchDensityMess(searchArea);
- if (pixelDensityContents != null) {
- extraStatements.append(pixelDensityContents[0]);
- }
- }
-
- String[] sizeContents = matchMethod("size", searchArea);
- String[] fullContents = matchMethod("fullScreen", searchArea);
- // First check and make sure they aren't both being used, otherwise it'll
- // throw a confusing state exception error that one "can't be used here".
- if (sizeContents != null && fullContents != null) {
- throw new SketchException("size() and fullScreen() cannot be used in the same sketch", false);
- }
-
- // Get everything inside the parens for the size() method
- //String[] contents = PApplet.match(searchArea, SIZE_CONTENTS_REGEX);
- if (sizeContents != null) {
- StringList args = breakCommas(sizeContents[1]);
- SurfaceInfo info = new SurfaceInfo();
-// info.statement = sizeContents[0];
- info.addStatement(sizeContents[0]);
- info.width = args.get(0).trim();
- info.height = args.get(1).trim();
- info.renderer = (args.size() >= 3) ? args.get(2).trim() : null;
- info.path = (args.size() >= 4) ? args.get(3).trim() : null;
-
- // Trying to remember why we wanted to allow people to use displayWidth
- // as the height or displayHeight as the width, but maybe it's for
- // making a square sketch window? Not going to
-
- if (info.hasOldSyntax()) {
-// return null;
- throw new SketchException("Please update your code to continue.", false);
- }
-
- if (info.hasBadSize() && fussy) {
- // found a reference to size, but it didn't seem to contain numbers
- final String message =
- "The size of this sketch could not be determined from your code.\n" +
- "Use only numbers (not variables) for the size() command.\n" +
- "Read the size() reference for more details.";
- EventQueue.invokeLater(() -> {
- Messages.showWarning("Could not find sketch size", message, null);
- });
-// new Exception().printStackTrace(System.out);
-// return null;
- throw new SketchException("Please fix the size() line to continue.", false);
- }
-
- info.addStatements(extraStatements);
- info.checkEmpty();
- return info;
- //return new String[] { contents[0], width, height, renderer, path };
- }
- // if no size() found, check for fullScreen()
- //contents = PApplet.match(searchArea, FULL_SCREEN_CONTENTS_REGEX);
- if (fullContents != null) {
- SurfaceInfo info = new SurfaceInfo();
-// info.statement = fullContents[0];
- info.addStatement(fullContents[0]);
- StringList args = breakCommas(fullContents[1]);
- if (args.size() > 0) { // might have no args
- String args0 = args.get(0).trim();
- if (args.size() == 1) {
- // could be either fullScreen(1) or fullScreen(P2D), figure out which
- if (args0.equals("SPAN") || PApplet.parseInt(args0, -1) != -1) {
- // it's the display parameter, not the renderer
- info.display = args0;
- } else {
- info.renderer = args0;
- }
- } else if (args.size() == 2) {
- info.renderer = args0;
- info.display = args.get(1).trim();
- } else {
- throw new SketchException("That's too many parameters for fullScreen()");
- }
- }
- info.width = "displayWidth";
- info.height = "displayHeight";
- info.addStatements(extraStatements);
- info.checkEmpty();
- return info;
- }
-
- // Made it this far, but no size() or fullScreen(), and still
- // need to pull out the noSmooth() and smooth(N) methods.
- if (extraStatements.size() != 0) {
- SurfaceInfo info = new SurfaceInfo();
- info.addStatements(extraStatements);
- return info;
- }
-
- // not an error, just no size() specified
- return new SurfaceInfo();
+ this(sketchName, Preferences.getInteger("editor.tabs.size"), false);
}
-
/**
- * Parses the code and determines the mode of the sketch.
- * @param code code without comments
- * @return determined mode
- */
- static public Mode parseMode(CharSequence code) {
- // See if we can find any function in the global scope
- if (findInCurrentScope(FUNCTION_DECL, code) != null) {
- return Mode.ACTIVE;
- }
-
- // See if we can find any public class extending PApplet
- if (findInCurrentScope(PUBLIC_CLASS, code) != null) {
- return Mode.JAVA;
- }
-
- return Mode.STATIC;
- }
-
-
- /**
- * Calls {@link #findInScope(Pattern, String, int, int, int, int) findInScope}
- * on the whole string with min and max target scopes set to zero.
- */
- static protected MatchResult findInCurrentScope(Pattern pattern, CharSequence code) {
- return findInScope(pattern, code, 0, code.length(), 0, 0);
- }
-
-
- /**
- * Calls {@link #findInScope(Pattern, String, int, int, int, int) findInScope}
- * starting at start char with min and max target scopes set to zero.
- */
- static protected MatchResult findInCurrentScope(Pattern pattern, CharSequence code,
- int start) {
- return findInScope(pattern, code, start, code.length(), 0, 0);
- }
-
-
- /**
- * Looks for the pattern at a specified target scope depth relative
- * to the scope depth of the starting position.
- *
- * Example: Calling this with starting position inside a method body
- * and target depth 0 would search only in the method body, while
- * using target depth -1 would look only in the body of the enclosing class
- * (but not in any methods of the class or outside of the class).
+ * Create a new preprocessor.
*
- * By using a scope range, you can e.g. search in the whole class including
- * bodies of methods and inner classes.
- *
- * @param pattern matching is realized by find() method of this pattern
- * @param code Java code without comments
- * @param start starting position in the code String (inclusive)
- * @param stop ending position in the code Sting (exclusive)
- * @param minTargetScopeDepth desired min scope depth of the match relative to the
- * scope of the starting position
- * @param maxTargetScopeDepth desired max scope depth of the match relative to the
- * scope of the starting position
- * @return first match at a desired relative scope depth,
- * null if there isn't one
+ * @param sketchName The name of the sketch.
+ * @param tabSize The number of tabs.
*/
- static protected MatchResult findInScope(Pattern pattern, CharSequence code,
- int start, int stop,
- int minTargetScopeDepth,
- int maxTargetScopeDepth) {
- if (minTargetScopeDepth > maxTargetScopeDepth) {
- int temp = minTargetScopeDepth;
- minTargetScopeDepth = maxTargetScopeDepth;
- maxTargetScopeDepth = temp;
- }
-
- Matcher m = pattern.matcher(code);
- m.region(start, stop);
- int depth = 0;
- int position = start;
-
- // We should not escape the enclosing scope. It can be either the original
- // scope, or the min target scope, whichever is more out there (lower depth)
- int minScopeDepth = PApplet.min(depth, minTargetScopeDepth);
-
- while (m.find()) {
- int newPosition = m.end();
- int depthDiff = scopeDepthDiff(code, position, newPosition);
- // Process this match only if it is not in string or char literal
- if (depthDiff != Integer.MAX_VALUE) {
- depth += depthDiff;
- if (depth < minScopeDepth) break; // out of scope!
- if (depth >= minTargetScopeDepth &&
- depth <= maxTargetScopeDepth) {
- return m.toMatchResult(); // jackpot
- }
- position = newPosition;
- }
- }
- return null;
+ public PdePreprocessor(final String sketchName, final int tabSize) {
+ this(sketchName, tabSize, false);
}
-
/**
- * Walks the specified region (not including stop) and determines difference
- * in scope depth. Adds one to depth on opening curly brace, subtracts one
- * from depth on closing curly brace. Ignores string and char literals.
- *
- * @param code code without comments
- * @param start start of the region, must not be in string literal,
- * char literal or second char of escaped sequence
- * @param stop end of the region (exclusive)
+ * Create a new preprocessor.
*
- * @return scope depth difference between start and stop,
- * Integer.MAX_VALUE if end is in string literal,
- * char literal or second char of escaped sequence
+ * @param sketchName The name of the sketch.
+ * @param tabSize The number of tabs.
+ * @param isTesting Flag indicating if this is running in unit tests (true) or in production
+ * (false).
*/
- static protected int scopeDepthDiff(CharSequence code, int start, int stop) {
- boolean insideString = false;
- boolean insideChar = false;
- boolean escapedChar = false;
- int depth = 0;
- for (int i = start; i < stop; i++) {
- if (!escapedChar) {
- char ch = code.charAt(i);
- switch (ch) {
- case '\\':
- escapedChar = true;
- break;
- case '{':
- if (!insideChar && !insideString) depth++;
- break;
- case '}':
- if (!insideChar && !insideString) depth--;
- break;
- case '\"':
- if (!insideChar) insideString = !insideString;
- break;
- case '\'':
- if (!insideString) insideChar = !insideChar;
- break;
- }
- } else {
- escapedChar = false;
- }
- }
- if (insideChar || insideString || escapedChar) {
- return Integer.MAX_VALUE; // signal invalid location
- }
- return depth;
+ public PdePreprocessor(final String sketchName, final int tabSize, boolean isTesting) {
+ this.sketchName = sketchName;
+ this.tabSize = tabSize;
+ this.isTesting = isTesting;
}
-
/**
- * Looks for the specified method in the base scope of the search area.
+ * Create the preprocessed sketch code without any code folder packages.
+ *
+ * @param out The writer into which the preprocessed code should be written. This is
+ * preferred over returning the full string result as this string may be large.
+ * @param program The sketch ("PDE") code.
+ * @return Information about the preprocessing operation.
*/
- static protected String[] matchMethod(String methodName, String searchArea) {
- final String left = "(?:^|\\s|;)";
- // doesn't match empty pairs of parens
- //final String right = "\\s*\\(([^\\)]+)\\)\\s*;";
- final String right = "\\s*\\(([^\\)]*)\\)\\s*;";
- String regexp = left + methodName + right;
- Pattern p = matchPatterns.get(regexp);
- if (p == null) {
- p = Pattern.compile(regexp, Pattern.MULTILINE | Pattern.DOTALL);
- matchPatterns.put(regexp, p);
- }
- MatchResult match = findInCurrentScope(p, searchArea);
- if (match != null) {
- int count = match.groupCount() + 1;
- String[] groups = new String[count];
- for (int i = 0; i < count; i++) {
- groups[i] = match.group(i);
- }
- return groups;
- }
- return null;
- }
-
-
- static protected LinkedHashMap matchPatterns =
- new LinkedHashMap(16, 0.75f, true) {
- @Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
- // Limit the number of match patterns at 10 most recently used
- return size() == 10;
- }
- };
-
-
- static protected String[] matchDensityMess(String searchArea) {
- final String regexp =
- "(?:^|\\s|;)pixelDensity\\s*\\(\\s*displayDensity\\s*\\([^\\)]*\\)\\s*\\)\\s*\\;";
- return PApplet.match(searchArea, regexp);
+ public PreprocessorResult write(final Writer out, String program) throws SketchException {
+ return write(out, program, null);
}
-
/**
- * Replace all commented portions of a given String as spaces.
- * Utility function used here and in the preprocessor.
+ * Create the preprocessed sketch code.
+ *
+ * @param outWriter The writer into which the preprocessed code should be written. This is
+ * preferred over returning the full string result as this string may be large.
+ * @param inProgram The sketch ("PDE") code.
+ * @param codeFolderPackages The packages included by default for the user by virtue of those
+ * packages being in the code folder.
+ * @return Information about the preprocessing operation.
*/
- static public String scrubComments(String what) {
- char p[] = what.toCharArray();
- // Track quotes to avoid problems with code like: String t = "*/*";
- // http://code.google.com/p/processing/issues/detail?id=1435
- boolean insideQuote = false;
-
- int index = 0;
- while (index < p.length) {
- // for any double slash comments, ignore until the end of the line
- if (!insideQuote &&
- (p[index] == '/') &&
- (index < p.length - 1) &&
- (p[index+1] == '/')) {
- p[index++] = ' ';
- p[index++] = ' ';
- while ((index < p.length) &&
- (p[index] != '\n')) {
- p[index++] = ' ';
- }
-
- // check to see if this is the start of a new multiline comment.
- // if it is, then make sure it's actually terminated somewhere.
- } else if (!insideQuote &&
- (p[index] == '/') &&
- (index < p.length - 1) &&
- (p[index+1] == '*')) {
- p[index++] = ' ';
- p[index++] = ' ';
- boolean endOfRainbow = false;
- while (index < p.length - 1) {
- if ((p[index] == '*') && (p[index+1] == '/')) {
- p[index++] = ' ';
- p[index++] = ' ';
- endOfRainbow = true;
- break;
-
- } else {
- // continue blanking this area
- p[index++] = ' ';
- }
- }
- if (!endOfRainbow) {
- throw new RuntimeException("Missing the */ from the end of a " +
- "/* comment */");
- }
-
- // switch in/out of quoted region
- } else if (p[index] == '"') {
- insideQuote = !insideQuote;
- index++;
-
- // skip the escaped char
- } else if (insideQuote && p[index] == '\\') {
- index += 2;
-
- } else { // any old character, move along
- index++;
- }
- }
- return new String(p);
- }
-
-
- public void addMethod(String methodName) {
- foundMethods.add(methodName);
- }
-
-
- public boolean hasMethod(String methodName) {
- return foundMethods.contains(methodName);
- }
-
-
- public void setAdvClassName(final String advClassName) {
- this.advClassName = advClassName;
- }
-
-
- public void setMode(final Mode mode) {
- this.mode = mode;
- }
-
-
- CommonHiddenStreamToken getHiddenAfter(final CommonHiddenStreamToken t) {
- return filter.getHiddenAfter(t);
- }
-
-
- CommonHiddenStreamToken getInitialHiddenToken() {
- return filter.getInitialHiddenToken();
- }
-
-
- private static int countNewlines(final String s) {
- int count = 0;
- for (int pos = s.indexOf('\n', 0); pos >= 0; pos = s.indexOf('\n', pos + 1))
- count++;
- return count;
- }
-
-
- private static void checkForUnterminatedMultilineComment(final String program)
- throws SketchException {
- final int length = program.length();
- for (int i = 0; i < length; i++) {
- // for any double slash comments, ignore until the end of the line
- if ((program.charAt(i) == '/') && (i < length - 1)
- && (program.charAt(i + 1) == '/')) {
- i += 2;
- while ((i < length) && (program.charAt(i) != '\n')) {
- i++;
- }
- // check to see if this is the start of a new multiline comment.
- // if it is, then make sure it's actually terminated somewhere.
- } else if ((program.charAt(i) == '/') && (i < length - 1)
- && (program.charAt(i + 1) == '*')) {
- final int startOfComment = i;
- i += 2;
- boolean terminated = false;
- while (i < length - 1) {
- if ((program.charAt(i) == '*') && (program.charAt(i + 1) == '/')) {
- i++; // advance to the ending '/'
- terminated = true;
- break;
- } else {
- i++;
- }
- }
- if (!terminated) {
- throw new SketchException("Unclosed /* comment */", 0,
- countNewlines(program.substring(0,
- startOfComment)));
- }
- } else if (program.charAt(i) == '"') {
- final int stringStart = i;
- boolean terminated = false;
- for (i++; i < length; i++) {
- final char c = program.charAt(i);
- if (c == '"') {
- terminated = true;
- break;
- } else if (c == '\\') {
- if (i == length - 1) {
- break;
- }
- i++;
- } else if (c == '\n') {
- break;
- }
- }
- if (!terminated) {
- throw new SketchException("Unterminated string constant", 0,
- countNewlines(program.substring(0,
- stringStart)));
- }
- } else if (program.charAt(i) == '\'') {
- i++; // step over the initial quote
- if (i >= length) {
- throw new SketchException("Unterminated character constant (after initial quote)", 0,
- countNewlines(program.substring(0, i)));
- }
- boolean escaped = false;
- if (program.charAt(i) == '\\') {
- i++; // step over the backslash
- escaped = true;
- }
- if (i >= length) {
- throw new SketchException("Unterminated character constant (after backslash)", 0,
- countNewlines(program.substring(0, i)));
- }
- if (escaped && program.charAt(i) == 'u') { // unicode escape sequence?
- i++; // step over the u
- //i += 4; // and the four digit unicode constant
- for (int j = 0; j < 4; j++) {
- if (UNICODE_ESCAPES.indexOf(program.charAt(i)) == -1) {
- throw new SketchException("Bad or unfinished \\uXXXX sequence " +
- "(malformed Unicode character constant)", 0,
- countNewlines(program.substring(0, i)));
- }
- i++;
- }
- } else {
- i++; // step over a single character
- }
- if (i >= length) {
- throw new SketchException("Unterminated character constant", 0,
- countNewlines(program.substring(0, i)));
- }
- if (program.charAt(i) != '\'') {
- throw new SketchException("Badly formed character constant " +
- "(expecting quote, got " + program.charAt(i) + ")", 0,
- countNewlines(program.substring(0, i)));
- }
- }
- }
- }
-
-
- public PreprocessorResult write(final Writer out, final String program)
- throws SketchException, RecognitionException, TokenStreamException {
- return write(out, program, null);
- }
-
-
- public PreprocessorResult write(Writer out, String program,
- StringList codeFolderPackages)
- throws SketchException, RecognitionException, TokenStreamException {
-
- // these ones have the .* at the end, since a class name might be at the end
- // instead of .* which would make trouble other classes using this can lop
- // off the . and anything after it to produce a package name consistently.
- final ArrayList programImports = new ArrayList<>();
-
- // imports just from the code folder, treated differently
- // than the others, since the imports are auto-generated.
- final ArrayList codeFolderImports = new ArrayList<>();
-
- // need to reset whether or not this has a main()
-// foundMain = false;
- foundMethods = new HashSet<>();
-
- // http://processing.org/bugs/bugzilla/5.html
- if (!program.endsWith("\n")) {
- program += "\n";
- }
-
- checkForUnterminatedMultilineComment(program);
-
- // For 0215, adding } as a legitimate prefix to the import (along with
- // newline and semicolon) for cases where a tab ends with } and an import
- // statement starts the next tab.
- final String importRegexp =
- "((?:^|;|\\})\\s*)(import\\s+)((?:static\\s+)?\\S+)(\\s*;)";
- final Pattern importPattern = Pattern.compile(importRegexp);
- String scrubbed = scrubComments(program);
- Matcher m = null;
- int offset = 0;
- boolean found = false;
- do {
- m = importPattern.matcher(scrubbed);
- found = m.find(offset);
- if (found) {
- String before = m.group(1);
- String piece = m.group(2) + m.group(3) + m.group(4);
-
- if (!ignoreImport(m.group(3))) {
- programImports.add(m.group(3)); // the package name
- }
-
- // find index of this import in the program
- int start = m.start() + before.length();
- int stop = start + piece.length();
-
- // Remove the import from the main program
- program = program.substring(0, start) + program.substring(stop);
- scrubbed = scrubbed.substring(0, start) + scrubbed.substring(stop);
- // Set the offset to start, because everything between
- // start and stop has been deleted.
- offset = m.start();
- }
- } while (found);
+ public PreprocessorResult write(Writer outWriter, String inProgram,
+ Iterable codeFolderPackages)
+ throws SketchException {
+ // Determine inports
+ ArrayList codeFolderImports = new ArrayList<>();
if (codeFolderPackages != null) {
for (String item : codeFolderPackages) {
- codeFolderImports.add(item + ".*");
- }
- }
-
- final PrintWriter stream = new PrintWriter(out);
- final int headerOffset =
- writeImports(stream, programImports, codeFolderImports);
- return new PreprocessorResult(mode, headerOffset + 2,
- write(program, stream), programImports);
- }
+ String fullItem;
-
- /**
- * preprocesses a pde file and writes out a java file
- * @return the class name of the exported Java
- */
- private String write(final String program, final PrintWriter stream)
- throws SketchException, RecognitionException, TokenStreamException {
-
- // Match on the uncommented version, otherwise code inside comments used
- // http://code.google.com/p/processing/issues/detail?id=1404
- String uncomment = scrubComments(program);
- PdeRecognizer parser = createParser(program);
- Mode mode = parseMode(uncomment);
-
- if (mode == Mode.JAVA) {
- try {
- final PrintStream saved = System.err;
- try {
- // throw away stderr for this tentative parse
- System.setErr(new PrintStream(new ByteArrayOutputStream()));
- parser.javaProgram();
- } finally {
- System.setErr(saved);
+ if (item.endsWith(".*")) {
+ fullItem = item;
+ } else {
+ fullItem = item + ".*";
}
- setMode(Mode.JAVA);
- } catch (Exception e) {
- // I can't figure out any other way of resetting the parser.
- parser = createParser(program);
- parser.pdeProgram();
- }
- } else if (mode == Mode.ACTIVE) {
- setMode(Mode.ACTIVE);
- parser.activeProgram();
- } else if (mode == Mode.STATIC) {
- parser.pdeProgram();
+ codeFolderImports.add(fullItem);
+ }
}
- // set up the AST for traversal by PdeEmitter
- ASTFactory factory = new ASTFactory();
- AST parserAST = parser.getAST();
- AST rootNode = factory.create(ROOT_ID, "AST ROOT");
- rootNode.setFirstChild(parserAST);
-
- makeSimpleMethodsPublic(rootNode);
-
- // unclear if this actually works, but it's worth a shot
- //((CommonAST)parserAST).setVerboseStringConversion(
- // true, parser.getTokenNames());
- // (made to use the static version because of jikes 1.22 warning)
- BaseAST.setVerboseStringConversion(true, parser.getTokenNames());
-
- final String className;
- if (mode == Mode.JAVA) {
- // in this mode, the class name is already defined.
- className = getFirstClassName(parserAST);
- } else {
- className = this.name;
+ if (Preferences.getBoolean("preproc.substitute_unicode")) {
+ inProgram = substituteUnicode(inProgram);
}
- // if 'null' was passed in for the name, but this isn't
- // a 'java' mode class, then there's a problem, so punt.
- if (className == null)
- return null;
-
- // debug
- if (false) {
- final StringWriter buf = new StringWriter();
- final PrintWriter bufout = new PrintWriter(buf);
- writeDeclaration(bufout, className);
- new PdeEmitter(this, bufout).print(rootNode);
- writeFooter(bufout, className);
- debugAST(rootNode, true);
- System.err.println(buf.toString());
+ // Ensure ends with single newline
+ while (inProgram.endsWith("\n")) {
+ inProgram = inProgram.substring(0, inProgram.length() - 1);
}
- writeDeclaration(stream, className);
- new PdeEmitter(this, stream).print(rootNode);
- writeFooter(stream, className);
+ inProgram = inProgram + "\n";
- // if desired, serialize the parse tree to an XML file. can
- // be viewed usefully with Mozilla or IE
- if (Preferences.getBoolean("preproc.output_parse_tree")) {
- writeParseTree("parseTree.xml", parserAST);
+ // Lexer
+ CommonTokenStream tokens;
+ {
+ CharStream antlrInStream = CharStreams.fromString(inProgram);
+ ProcessingLexer lexer = new ProcessingLexer(antlrInStream);
+ lexer.removeErrorListeners();
+ tokens = new CommonTokenStream(lexer);
}
- return className;
- }
-
-
- private PdeRecognizer createParser(final String program) {
- // create a lexer with the stream reader, and tell it to handle
- // hidden tokens (eg whitespace, comments) since we want to pass these
- // through so that the line numbers when the compiler reports errors
- // match those that will be highlighted in the PDE IDE
- //
- PdeLexer lexer = new PdeLexer(new StringReader(program));
- lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
+ // Parser
+ final List preprocessIssues = new ArrayList<>();
+ final List treeIssues = new ArrayList<>();
+ PdeParseTreeListener listener = createListener(tokens, sketchName);
+ listener.setTesting(isTesting);
+ listener.setCoreImports(ImportUtil.getCoreImports());
+ listener.setDefaultImports(ImportUtil.getDefaultImports());
+ listener.setCodeFolderImports(codeFolderImports);
+ listener.setTreeErrorListener((x) -> { treeIssues.add(x); });
- // create the filter for hidden tokens and specify which tokens to
- // hide and which to copy to the hidden text
- //
- filter = new TokenStreamCopyingHiddenTokenFilter(lexer);
- filter.hide(PdePartialTokenTypes.SL_COMMENT);
- filter.hide(PdePartialTokenTypes.ML_COMMENT);
- filter.hide(PdePartialTokenTypes.WS);
- filter.copy(PdePartialTokenTypes.SEMI);
- filter.copy(PdePartialTokenTypes.LPAREN);
- filter.copy(PdePartialTokenTypes.RPAREN);
- filter.copy(PdePartialTokenTypes.LCURLY);
- filter.copy(PdePartialTokenTypes.RCURLY);
- filter.copy(PdePartialTokenTypes.COMMA);
- filter.copy(PdePartialTokenTypes.RBRACK);
- filter.copy(PdePartialTokenTypes.LBRACK);
- filter.copy(PdePartialTokenTypes.COLON);
- filter.copy(PdePartialTokenTypes.TRIPLE_DOT);
+ final String finalInProgram = inProgram;
+ ParseTree tree;
+ {
+ ProcessingParser parser = new ProcessingParser(tokens);
+ parser.removeErrorListeners();
+ parser.addErrorListener(new PdeIssueEmitter(
+ (x) -> { preprocessIssues.add(x); },
+ () -> finalInProgram
+ ));
+ parser.setBuildParseTree(true);
+ tree = parser.processingSketch();
- // Because the meanings of < and > are overloaded to support
- // type arguments and type parameters, we have to treat them
- // as copyable to hidden text (or else the following syntax,
- // such as (); and what not gets lost under certain circumstances)
- // -- jdf
- filter.copy(PdePartialTokenTypes.LT);
- filter.copy(PdePartialTokenTypes.GT);
- filter.copy(PdePartialTokenTypes.SR);
- filter.copy(PdePartialTokenTypes.BSR);
-
- // create a parser and set what sort of AST should be generated
- //
- final PdeRecognizer parser = new PdeRecognizer(this, filter);
-
- // use our extended AST class
- //
- parser.setASTNodeClass("antlr.ExtendedCommonASTWithHiddenTokens");
- return parser;
- }
-
- /**
- * Walk the tree looking for METHOD_DEFs. Any simple METHOD_DEF (one
- * without TYPE_PARAMETERS) lacking an
- * access specifier is given public access.
- * @param node
- */
- private void makeSimpleMethodsPublic(final AST node) {
- if (node.getType() == PdeTokenTypes.METHOD_DEF) {
- final AST mods = node.getFirstChild();
- final AST oldFirstMod = mods.getFirstChild();
- for (AST mod = oldFirstMod; mod != null; mod = mod.getNextSibling()) {
- final int t = mod.getType();
- if (t == PdeTokenTypes.LITERAL_private ||
- t == PdeTokenTypes.LITERAL_protected ||
- t == PdeTokenTypes.LITERAL_public) {
- return;
- }
+ if (preprocessIssues.size() > 0) {
+ return PreprocessorResult.reportPreprocessIssues(preprocessIssues);
}
- if (mods.getNextSibling().getType() == PdeTokenTypes.TYPE_PARAMETERS) {
- return;
- }
- final CommonHiddenStreamToken publicToken =
- new CommonHiddenStreamToken(PdeTokenTypes.LITERAL_public, "public") {
- {
- setHiddenAfter(new CommonHiddenStreamToken(PdeTokenTypes.WS, " "));
- }
- };
- final AST publicNode = new CommonASTWithHiddenTokens(publicToken);
- publicNode.setNextSibling(oldFirstMod);
- mods.setFirstChild(publicNode);
- } else {
- for (AST kid = node.getFirstChild(); kid != null; kid = kid
- .getNextSibling())
- makeSimpleMethodsPublic(kid);
}
- }
- protected void writeParseTree(String filename, AST ast) {
- try {
- PrintStream stream = new PrintStream(new FileOutputStream(filename));
- stream.println("");
- stream.println("");
- OutputStreamWriter writer = new OutputStreamWriter(stream);
- if (ast != null) {
- ((CommonAST) ast).xmlSerialize(writer);
- }
- writer.flush();
- stream.println("");
- writer.close();
- } catch (IOException e) {
+ ParseTreeWalker treeWalker = new ParseTreeWalker();
+ treeWalker.walk(listener, tree);
+ // Check for issues encountered in walk
+ if (treeIssues.size() > 0) {
+ return PreprocessorResult.reportPreprocessIssues(treeIssues);
}
- }
- /**
- *
- * @param out
- * @param programImports
- * @param codeFolderImports
- * @return the header offset
- */
- protected int writeImports(final PrintWriter out,
- final List programImports,
- final List codeFolderImports) {
- int count = writeImportList(out, getCoreImports());
- count += writeImportList(out, programImports);
- count += writeImportList(out, codeFolderImports);
- count += writeImportList(out, getDefaultImports());
- return count;
- }
+ // Return resulting program
+ String outputProgram = listener.getOutputProgram();
+ PrintWriter outPrintWriter = new PrintWriter(outWriter);
+ outPrintWriter.print(outputProgram);
- protected int writeImportList(PrintWriter out, List imports) {
- return writeImportList(out, imports.toArray(new String[0]));
- }
+ hasMain = listener.foundMain();
- protected int writeImportList(PrintWriter out, String[] imports) {
- int count = 0;
- if (imports != null && imports.length != 0) {
- for (String item : imports) {
- out.println("import " + item + "; ");
- count++;
- }
- out.println();
- count++;
- }
- return count;
+ return listener.getResult();
}
/**
- * Write any required header material (eg imports, class decl stuff)
+ * Determine if the main method was found during preprocessing.
*
- * @param out PrintStream to write it to.
- * @param exporting Is this being exported from PDE?
- * @param className Name of the class being created.
+ * @return True if a main method was found. False otherwise.
*/
- protected void writeDeclaration(PrintWriter out, String className) {
- if (mode == Mode.JAVA) {
- // Print two blank lines so that the offset doesn't change
- out.println();
- out.println();
-
- } else if (mode == Mode.ACTIVE) {
- // Print an extra blank line so the offset is identical to the others
- out.println("public class " + className + " extends PApplet {");
- out.println();
-
- } else if (mode == Mode.STATIC) {
- out.println("public class " + className + " extends PApplet {");
- out.println(indent + "public void setup() {");
- }
+ public boolean hasMain() {
+ return hasMain;
}
/**
- * Write any necessary closing text.
+ * Factory function to create a {PdeParseTreeListener} for use in preprocessing
*
- * @param out PrintStream to write it to.
- */
- protected void writeFooter(PrintWriter out, String className) {
- if (mode == Mode.STATIC) {
- // close off setup() definition
- out.println(indent + indent + "noLoop();");
- out.println(indent + "}");
- out.println();
- }
-
- if ((mode == Mode.STATIC) || (mode == Mode.ACTIVE)) {
- // doesn't remove the original size() method,
- // but calling size() again in setup() is harmless.
- if (!hasMethod("settings") && sizeInfo.hasSettings()) {
- out.println(indent + "public void settings() { " + sizeInfo.getSettings() + " }");
- }
-
- if (!hasMethod("main")) {
- out.println(indent + "static public void main(String[] passedArgs) {");
- out.print(indent + indent + "String[] appletArgs = new String[] { ");
-
- if (Preferences.getBoolean("export.application.present")) {
- out.print("\"" + PApplet.ARGS_PRESENT + "\", ");
-
- String farbe = Preferences.get("run.present.bgcolor");
- out.print("\"" + PApplet.ARGS_WINDOW_COLOR + "=" + farbe + "\", ");
-
- if (Preferences.getBoolean("export.application.stop")) {
- farbe = Preferences.get("run.present.stop.color");
- out.print("\"" + PApplet.ARGS_STOP_COLOR + "=" + farbe + "\", ");
- } else {
- out.print("\"" + PApplet.ARGS_HIDE_STOP + "\", ");
- }
- }
- out.println("\"" + className + "\" };");
-
- out.println(indent + indent + "if (passedArgs != null) {");
- out.println(indent + indent + " PApplet.main(concat(appletArgs, passedArgs));");
- out.println(indent + indent + "} else {");
- out.println(indent + indent + " PApplet.main(appletArgs);");
- out.println(indent + indent + "}");
-
- out.println(indent + "}");
- }
-
- // close off the class definition
- out.println("}");
- }
- }
-
- public String[] getCoreImports() {
- return new String[] {
- "processing.core.*",
- "processing.data.*",
- "processing.event.*",
- "processing.opengl.*"
- };
- }
-
- public String[] getDefaultImports() {
- // These may change in-between (if the prefs panel adds this option)
- //String prefsLine = Preferences.get("preproc.imports");
- //return PApplet.splitTokens(prefsLine, ", ");
- return new String[] {
- "java.util.HashMap",
- "java.util.ArrayList",
- "java.io.File",
- "java.io.BufferedReader",
- "java.io.PrintWriter",
- "java.io.InputStream",
- "java.io.OutputStream",
- "java.io.IOException"
- };
- }
-
- /**
- * Return true if this import should be removed from the code. This is used
- * for packages like processing.xml which no longer exist.
- * @param pkg something like processing.xml.XMLElement or processing.xml.*
- * @return true if this shouldn't be added to the final code
+ * @param tokens The token stream for which the listener needs to be created.
+ * @param sketchName The name of the sketch being preprocessed.
+ * @return Newly created listener suitable for use in this {PdePreprocessor}.
*/
- public boolean ignoreImport(String pkg) {
- return false;
-// return pkg.startsWith("processing.xml.");
+ private PdeParseTreeListener createListener(CommonTokenStream tokens, String sketchName) {
+ return new PdeParseTreeListener(tokens, sketchName, tabSize);
}
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
/**
- * Find the first CLASS_DEF node in the tree, and return the name of the
- * class in question.
+ * Utility function to substitute non ascii characters for escaped unicode character sequences.
*
- * TODO [dmose] right now, we're using a little hack to the grammar to get
- * this info. In fact, we should be descending the AST passed in.
+ * @param program The program source in which to execute the replace.
+ * @return The program source after replacement.
*/
- String getFirstClassName(AST ast) {
- String t = advClassName;
- advClassName = "";
- return t;
- }
-
-
- public void debugAST(final AST ast, final boolean includeHidden) {
- System.err.println("------------------");
- debugAST(ast, includeHidden, 0);
- }
-
-
- private void debugAST(final AST ast, final boolean includeHidden,
- final int indent) {
- for (int i = 0; i < indent; i++)
- System.err.print(" ");
- if (includeHidden) {
- System.err.print(debugHiddenBefore(ast));
- }
- if (ast.getType() > 0 && !ast.getText().equals(TokenUtil.nameOf(ast))) {
- System.err.print(TokenUtil.nameOf(ast) + "/");
- }
- System.err.print(ast.getText().replace("\n", "\\n"));
- if (includeHidden) {
- System.err.print(debugHiddenAfter(ast));
- }
- System.err.println();
- for (AST kid = ast.getFirstChild(); kid != null; kid = kid.getNextSibling())
- debugAST(kid, includeHidden, indent + 1);
- }
-
-
- private String debugHiddenAfter(AST ast) {
- return (ast instanceof antlr.CommonASTWithHiddenTokens) ?
- debugHiddenTokens(((antlr.CommonASTWithHiddenTokens) ast).getHiddenAfter()) : "";
- }
-
- private String debugHiddenBefore(AST ast) {
- if (!(ast instanceof antlr.CommonASTWithHiddenTokens)) {
- return "";
- }
- antlr.CommonHiddenStreamToken parent =
- ((antlr.CommonASTWithHiddenTokens) ast).getHiddenBefore();
-
- if (parent == null) {
- return "";
- }
-
- antlr.CommonHiddenStreamToken child = null;
- do {
- child = parent;
- parent = child.getHiddenBefore();
- } while (parent != null);
-
- return debugHiddenTokens(child);
- }
-
-
- private String debugHiddenTokens(antlr.CommonHiddenStreamToken t) {
- final StringBuilder sb = new StringBuilder();
- for (; t != null; t = filter.getHiddenAfter(t)) {
- if (sb.length() == 0) {
- sb.append("[");
+ private static String substituteUnicode(String program) {
+ // check for non-ascii chars (these will be/must be in unicode format)
+ char p[] = program.toCharArray();
+ int unicodeCount = 0;
+ for (int i = 0; i < p.length; i++) {
+ if (p[i] > 127)
+ unicodeCount++;
+ }
+ if (unicodeCount == 0)
+ return program;
+ // if non-ascii chars are in there, convert to unicode escapes
+ // add unicodeCount * 5.. replacing each unicode char
+ // with six digit uXXXX sequence (xxxx is in hex)
+ // (except for nbsp chars which will be a replaced with a space)
+ int index = 0;
+ char p2[] = new char[p.length + unicodeCount * 5];
+ for (int i = 0; i < p.length; i++) {
+ if (p[i] < 128) {
+ p2[index++] = p[i];
+ } else if (p[i] == 160) { // unicode for non-breaking space
+ p2[index++] = ' ';
+ } else {
+ int c = p[i];
+ p2[index++] = '\\';
+ p2[index++] = 'u';
+ char str[] = Integer.toHexString(c).toCharArray();
+ // add leading zeros, so that the length is 4
+ //for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0';
+ for (int m = 0; m < 4 - str.length; m++)
+ p2[index++] = '0';
+ System.arraycopy(str, 0, p2, index, str.length);
+ index += str.length;
}
- sb.append(t.getText().replace("\n", "\\n"));
}
- if (sb.length() > 0) {
- sb.append("]");
- }
- return sb.toString();
+ return new String(p2, 0, index);
}
+
}
diff --git a/java/src/processing/mode/java/preproc/PreprocessorResult.java b/java/src/processing/mode/java/preproc/PreprocessorResult.java
index 3485e2c7d8..6e47d5fb74 100644
--- a/java/src/processing/mode/java/preproc/PreprocessorResult.java
+++ b/java/src/processing/mode/java/preproc/PreprocessorResult.java
@@ -1,32 +1,198 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
package processing.mode.java.preproc;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
+
+import processing.mode.java.pdex.ImportStatement;
+import processing.mode.java.pdex.TextTransform;
+import processing.mode.java.preproc.issue.PdePreprocessIssue;
-import processing.app.SketchException;
/**
- *
- * @author Jonathan Feinberg <jdf@pobox.com>
- *
+ * Result of sketch Preprocessing.
*/
public class PreprocessorResult {
- public final int headerOffset;
- public final String className;
- public final List extraImports;
- public final PdePreprocessor.Mode programType;
-
- public PreprocessorResult(PdePreprocessor.Mode programType,
- int headerOffset, String className,
- final List extraImports) throws SketchException {
- if (className == null) {
- throw new SketchException("Could not find main class");
+
+ private final int headerOffset;
+ private final String className;
+ private final List importStatementsStr;
+ private final List importStatements;
+ private final PdePreprocessor.Mode programType;
+ private final List edits;
+ private final List preprocessIssues;
+ private final String sketchWidth;
+ private final String sketchHeight;
+
+ /**
+ * Create a new PreprocessorResult indicating that there were issues in preprocessing.
+ *
+ * @param newPreprocessIssues The list of issues encoutnered.
+ * @return New preprocessor result.
+ */
+ public static PreprocessorResult reportPreprocessIssues(
+ List newPreprocessIssues) {
+
+ assert newPreprocessIssues.size() > 0;
+ return new PreprocessorResult(newPreprocessIssues);
+ }
+
+ /**
+ * Create a new preprocessing result.
+ *
+ * @param newProgramType The type of program that has be preprocessed.
+ * @param newHeaderOffset The offset (in number of chars) from the start of the program at which
+ * the header finishes.
+ * @param newClassName The name of the class containing the sketch.
+ * @param newExtraImports Additional imports beyond the defaults and code folder.
+ * @param newEdits The edits made during preprocessing.
+ * @param newSketchWidth The width of the sketch in pixels or special value like displayWidth;
+ * @param newSketchHeight The height of the sketch in pixels or special value like displayWidth;
+ */
+ public PreprocessorResult(PdePreprocessor.Mode newProgramType, int newHeaderOffset,
+ String newClassName, List newExtraImports, List newEdits,
+ String newSketchWidth, String newSketchHeight) {
+
+ if (newClassName == null) {
+ throw new RuntimeException("Could not find main class");
}
- this.headerOffset = headerOffset;
- this.className = className;
- this.extraImports = Collections.unmodifiableList(new ArrayList(extraImports));
- this.programType = programType;
+
+ headerOffset = newHeaderOffset;
+ className = newClassName;
+ importStatementsStr = Collections.unmodifiableList(new ArrayList<>(newExtraImports));
+ programType = newProgramType;
+ edits = newEdits;
+ preprocessIssues = new ArrayList<>();
+
+ importStatements = importStatementsStr.stream()
+ .map(ImportStatement::parse)
+ .collect(Collectors.toList());
+
+ sketchWidth = newSketchWidth;
+ sketchHeight = newSketchHeight;
+ }
+
+ /**
+ * Private constructor allowing creation of result indicating preprocess issues.
+ *
+ * @param newPreprocessIssues The list of preprocess issues encountered.
+ */
+ private PreprocessorResult(List newPreprocessIssues) {
+ preprocessIssues = Collections.unmodifiableList(newPreprocessIssues);
+ headerOffset = 0;
+ className = "unknown";
+ importStatementsStr = new ArrayList<>();
+ programType = PdePreprocessor.Mode.STATIC;
+ edits = new ArrayList<>();
+ importStatements = new ArrayList<>();
+
+ sketchWidth = null;
+ sketchHeight = null;
+ }
+
+ /**
+ * Get the list of preprocess issues encountered.
+ *
+ * @return List of preprocess issues encountered.
+ */
+ public List getPreprocessIssues() {
+ return preprocessIssues;
+ }
+
+ /**
+ * Get the end point of the header.
+ *
+ * @return The offset (in number of lines) from the start of the program at which the header
+ * finishes.
+ */
+ public int getHeaderOffset() {
+ return headerOffset;
+ }
+
+ /**
+ * Get the name of the Java class containing the sketch after preprocessing.
+ *
+ * @return The name of the class containing the sketch.
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ /**
+ * Get the imports beyond the default set that are included in the sketch.
+ *
+ * @return Additional imports beyond the defaults and code folder.
+ */
+ public List getImportStatementsStr() {
+ return importStatementsStr;
}
+ /**
+ * Get the type of program that was parsed.
+ *
+ * @return Type of program parsed like STATIC (no function) or ACTIVE.
+ */
+ public PdePreprocessor.Mode getProgramType() {
+ return programType;
+ }
+
+ /**
+ * Get the edits generated during preprocessing.
+ *
+ * @return List of edits generated during preprocessing.
+ */
+ public List getEdits() {
+ return edits;
+ }
+
+ /**
+ * Get the found import statements as {ImportStatement}s.
+ *
+ * @return The import statements found for the user.
+ */
+ public List getImportStatements() {
+ return importStatements;
+ }
+
+ /**
+ * Get the user provided width of this sketch.
+ *
+ * @return The width of the sketch in pixels or special value like displayWidth or null if none
+ * given.
+ */
+ public String getSketchWidth() {
+ return sketchWidth;
+ }
+
+ /**
+ * Get the user provided height of this sketch.
+ *
+ * @return The height of the sketch in pixels or special value like displayHeight or null if none
+ * given.
+ */
+ public String getSketchHeight() {
+ return sketchWidth;
+ }
}
diff --git a/java/src/processing/mode/java/preproc/Processing.g4 b/java/src/processing/mode/java/preproc/Processing.g4
new file mode 100644
index 0000000000..2b822f9d92
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/Processing.g4
@@ -0,0 +1,130 @@
+/**
+ * Based on Java 1.7 grammar for ANTLR 4, see Java.g4
+ *
+ * - changes main entry point to reflect sketch types 'static' | 'active'
+ * - adds support for type converter functions like "int()"
+ * - adds pseudo primitive type "color"
+ * - adds HTML hex notation with hash symbol: #ff5522
+ */
+
+grammar Processing;
+
+@lexer::members {
+ public static final int WHITESPACE = 1;
+ public static final int COMMENTS = 2;
+}
+
+// import Java grammar
+import JavaParser;
+
+// main entry point, select sketch type
+processingSketch
+ : javaProcessingSketch
+ | staticProcessingSketch
+ | activeProcessingSketch
+ ;
+
+// java mode, is a compilation unit
+javaProcessingSketch
+ : packageDeclaration? importDeclaration* typeDeclaration+ EOF
+ ;
+
+staticProcessingSketch
+ : (importDeclaration | blockStatement)* EOF
+ ;
+
+// active mode, has function definitions
+activeProcessingSketch
+ : (importDeclaration | classBodyDeclaration)* EOF
+ ;
+
+variableDeclaratorId
+ : warnTypeAsVariableName
+ | IDENTIFIER ('[' ']')*
+ ;
+
+// bug #93
+// https://github.com/processing/processing/issues/93
+// prevent from types being used as variable names
+warnTypeAsVariableName
+ : primitiveType ('[' ']')* {
+ notifyErrorListeners("Type names are not allowed as variable names: "+$primitiveType.text);
+ }
+ ;
+
+// catch special API function calls that we are interested in
+methodCall
+ : functionWithPrimitiveTypeName
+ | IDENTIFIER '(' expressionList? ')'
+ | THIS '(' expressionList? ')'
+ | SUPER '(' expressionList? ')'
+ ;
+
+// these are primitive type names plus "()"
+// "color" is a special Processing primitive (== int)
+functionWithPrimitiveTypeName
+ : ( 'boolean'
+ | 'byte'
+ | 'char'
+ | 'float'
+ | 'int'
+ | 'color'
+ ) '(' expressionList? ')'
+ ;
+
+// adding support for "color" primitive
+primitiveType
+ : BOOLEAN
+ | CHAR
+ | BYTE
+ | SHORT
+ | INT
+ | LONG
+ | FLOAT
+ | DOUBLE
+ | colorPrimitiveType
+ ;
+
+colorPrimitiveType
+ : 'color'
+ ;
+
+// added HexColorLiteral
+literal
+ : integerLiteral
+ | floatLiteral
+ | CHAR_LITERAL
+ | STRING_LITERAL
+ | BOOL_LITERAL
+ | NULL_LITERAL
+ | hexColorLiteral
+ ;
+
+// As parser rule so this produces a separate listener
+// for us to alter its value.
+hexColorLiteral
+ : HexColorLiteral
+ ;
+
+// add color literal notations for
+// #ff5522
+HexColorLiteral
+ : '#' (HexDigit HexDigit)? HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit
+ ;
+
+
+// hide but do not remove whitespace and comments
+
+WS : [ \t\r\n\u000C]+ -> channel(1)
+ ;
+
+COMMENT
+ : '/*' .*? '*/' -> channel(2)
+ ;
+
+LINE_COMMENT
+ : '//' ~[\r\n]* -> channel(2)
+ ;
+
+CHAR_LITERAL: '\'' (~['\\\r\n] | EscapeSequence)* '\''; // A bit nasty but let JDT tackle invalid chars
+
diff --git a/java/src/processing/mode/java/preproc/SourceEmitter.java b/java/src/processing/mode/java/preproc/SourceEmitter.java
new file mode 100644
index 0000000000..747a0106af
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/SourceEmitter.java
@@ -0,0 +1,37 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc;
+
+
+/**
+ * Simple interface for strategy which can emit the full body of a processing sketch.
+ */
+public interface SourceEmitter {
+
+ /**
+ * Get the full body of the processing sketch.
+ *
+ * @return String processing sketch source code across all tabs.
+ */
+ String getSource();
+
+}
diff --git a/java/src/processing/mode/java/preproc/SurfaceInfo.java b/java/src/processing/mode/java/preproc/SurfaceInfo.java
deleted file mode 100644
index 04834de8b2..0000000000
--- a/java/src/processing/mode/java/preproc/SurfaceInfo.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- SizeInfo - parsed elements of a size() or fullScreen() call
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2015 The Processing Foundation
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
-package processing.mode.java.preproc;
-
-import processing.app.Messages;
-import processing.core.PApplet;
-import processing.data.StringList;
-
-
-public class SurfaceInfo {
- StringList statements = new StringList();
-
- String width;
- String height;
- String renderer;
- String path;
-
- String display;
- /** null for nothing in setup(), 0 for noSmooth(), N for smooth(N) */
- //Integer quality;
-// String smooth;
-
-
- boolean hasOldSyntax() {
- if (width.equals("screenWidth") ||
- width.equals("screenHeight") ||
- height.equals("screenHeight") ||
- height.equals("screenWidth")) {
- final String message =
- "The screenWidth and screenHeight variables are named\n" +
- "displayWidth and displayHeight in Processing 3.\n" +
- "Or you can use the fullScreen() method instead of size().";
- Messages.showWarning("Time for a quick update", message, null);
- return true;
- }
- if (width.equals("screen.width") ||
- width.equals("screen.height") ||
- height.equals("screen.height") ||
- height.equals("screen.width")) {
- final String message =
- "The screen.width and screen.height variables are named\n" +
- "displayWidth and displayHeight in Processing 3.\n" +
- "Or you can use the fullScreen() method instead of size().";
- Messages.showWarning("Time for a quick update", message, null);
- return true;
- }
- return false;
- }
-
-
- boolean hasBadSize() {
- if (!width.equals("displayWidth") &&
- !width.equals("displayHeight") &&
- PApplet.parseInt(width, -1) == -1) {
- return true;
- }
- if (!height.equals("displayWidth") &&
- !height.equals("displayHeight") &&
- PApplet.parseInt(height, -1) == -1) {
- return true;
- }
- return false;
- }
-
-
- void checkEmpty() {
- if (renderer != null) {
- if (renderer.length() == 0) { // if empty, set null
- renderer = null;
- }
- }
- if (path != null) {
- if (path.length() == 0) {
- path = null;
- }
- }
- if (display != null) {
- if (display.length() == 0) {
- display = null;
- }
- }
- }
-
-
-// public String getStatements() {
-// return statements.join(" ");
-// }
-
-
- public StringList getStatements() {
- return statements;
- }
-
-
- /**
- * Add an item that will be moved from size() into the settings() method.
- * This needs to be the exact version of the statement so that it can be
- * matched against and removed from the size() method in the code.
- */
- public void addStatement(String stmt) {
- statements.append(stmt);
- }
-
-
- public void addStatements(StringList list) {
- statements.append(list);
- }
-
-
- /** @return true if there's code to be inserted for a settings() method. */
- public boolean hasSettings() {
- return statements.size() != 0;
- }
-
-
- /** @return the contents of the settings() method to be inserted */
- public String getSettings() {
- return statements.join(" ");
- }
-
-
- // Added for Android Mode to check whether OpenGL is in use
- // https://github.com/processing/processing/issues/4441
- /**
- * Return the renderer specified (null if none specified).
- * @since 3.2.2
- */
- public String getRenderer() {
- return renderer;
- }
-}
diff --git a/java/src/processing/mode/java/preproc/TokenUtil.java b/java/src/processing/mode/java/preproc/TokenUtil.java
deleted file mode 100644
index e6c4721eab..0000000000
--- a/java/src/processing/mode/java/preproc/TokenUtil.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package processing.mode.java.preproc;
-
-import java.lang.reflect.Field;
-import antlr.collections.AST;
-import processing.mode.java.preproc.PdeTokenTypes;
-
-/**
- *
- * @author Jonathan Feinberg <jdf@pobox.com>
- *
- */
-public class TokenUtil {
- private static final String[] tokenNames= new String[200];
- static {
- for (int i = 0; i < tokenNames.length; i++) {
- tokenNames[i] = "ERROR:" + i;
- }
- for (final Field f : PdeTokenTypes.class.getDeclaredFields()) {
- try {
- tokenNames[f.getInt(null)] = f.getName();
- } catch (Exception unexpected) {
- throw new RuntimeException(unexpected);
- }
- }
- }
-
- public static String nameOf(final AST node) {
- return tokenNames[node.getType()];
- }
-}
diff --git a/java/src/processing/mode/java/preproc/code/CodeEditOperationUtil.java b/java/src/processing/mode/java/preproc/code/CodeEditOperationUtil.java
new file mode 100644
index 0000000000..0d5e138250
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/CodeEditOperationUtil.java
@@ -0,0 +1,129 @@
+package processing.mode.java.preproc.code;
+
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenStreamRewriter;
+import processing.mode.java.pdex.TextTransform;
+
+
+/**
+ * Utility which generates and performs code edit operations.
+ *
+ *
+ * Utility which generates and performs code edit operations, performing the edit immediately
+ * within a ANTLR rewriter but also generating a {TextTransform.Edit} for use with the JDT.
+ *
+ */
+public class CodeEditOperationUtil {
+
+ /**
+ * Delete a single token.
+ *
+ * @param start The token to be deleted.
+ * @param rewriter The rewriter in which to immediately edit.
+ * @return The {TextTransform.Edit} corresponding to this change.
+ */
+ public static TextTransform.Edit createDelete(Token start, TokenStreamRewriter rewriter) {
+ rewriter.delete(start);
+ return TextTransform.Edit.delete(start.getStartIndex(), start.getText().length());
+ }
+
+ /**
+ * Delete tokens between a start end end token inclusive.
+ *
+ * @param start The token to be deleted.
+ * @param stop The final token to be deleted.
+ * @param rewriter The rewriter in which to immediately edit.
+ * @return The {TextTransform.Edit} corresponding to this change.
+ */
+ public static TextTransform.Edit createDelete(Token start, Token stop,
+ TokenStreamRewriter rewriter) {
+
+ rewriter.delete(start, stop);
+
+ int startIndex = start.getStartIndex();
+ int length = stop.getStopIndex() - startIndex + 1;
+
+ return TextTransform.Edit.delete(
+ startIndex,
+ length
+ );
+ }
+
+ /**
+ * Insert text after a token.
+ *
+ * @param start The position after which the text should be inserted.
+ * @param text The text to insert.
+ * @param rewriter The rewriter in which to immediately edit.
+ * @return The {TextTransform.Edit} corresponding to this change.
+ */
+ public static TextTransform.Edit createInsertAfter(int start, String text,
+ TokenStreamRewriter rewriter) {
+
+ rewriter.insertAfter(start, text);
+
+ return TextTransform.Edit.insert(
+ start + 1,
+ text
+ );
+ }
+
+ /**
+ * Insert text after a token.
+ *
+ * @param start The token after which the text should be inserted.
+ * @param text The text to insert.
+ * @param rewriter The rewriter in which to immediately edit.
+ * @return The {TextTransform.Edit} corresponding to this change.
+ */
+ public static TextTransform.Edit createInsertAfter(Token start, String text,
+ TokenStreamRewriter rewriter) {
+
+ rewriter.insertAfter(start, text);
+
+ return TextTransform.Edit.insert(
+ start.getStopIndex() + 1,
+ text
+ );
+ }
+
+ /**
+ * Insert text before a token.
+ *
+ * @param before Token before which the text should be inserted.
+ * @param text The text to insert.
+ * @param rewriter The rewriter in which to immediately edit.
+ * @return The {TextTransform.Edit} corresponding to this change.
+ */
+ public static TextTransform.Edit createInsertBefore(Token before, String text,
+ TokenStreamRewriter rewriter) {
+
+ rewriter.insertBefore(before, text);
+
+ return TextTransform.Edit.insert(
+ before.getStartIndex(),
+ text
+ );
+ }
+
+ /**
+ * Insert text before a position in code.
+ *
+ * @param before The location before which to insert the text in tokens.
+ * @param beforeOffset THe location before which to insert the text in chars.
+ * @param text The text to insert.
+ * @param rewriter The rewriter in which to immediately edit.
+ * @return The {TextTransform.Edit} corresponding to this change.
+ */
+ public static TextTransform.Edit createInsertBefore(int before, int beforeOffset, String text,
+ TokenStreamRewriter rewriter) {
+
+ rewriter.insertBefore(before, text);
+
+ return TextTransform.Edit.insert(
+ beforeOffset,
+ text
+ );
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/code/ImportUtil.java b/java/src/processing/mode/java/preproc/code/ImportUtil.java
new file mode 100644
index 0000000000..6b814914d5
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/ImportUtil.java
@@ -0,0 +1,45 @@
+package processing.mode.java.preproc.code;
+
+
+/**
+ * Utility to assist with preprocessing imports.
+ */
+public class ImportUtil {
+
+ /**
+ * Get the imports required by processing itself.
+ *
+ * @return List of imports required by processing itself.
+ */
+ public static String[] getCoreImports() {
+ return new String[] {
+ "processing.core.*",
+ "processing.data.*",
+ "processing.event.*",
+ "processing.opengl.*"
+ };
+ }
+
+ /**
+ * Get the list of imports included by default on behalf of the user.
+ *
+ * @return List of "default" imports not required for processing but included for user
+ * convenience.
+ */
+ public static String[] getDefaultImports() {
+ // These may change in-between (if the prefs panel adds this option)
+ //String prefsLine = Preferences.get("preproc.imports");
+ //return PApplet.splitTokens(prefsLine, ", ");
+ return new String[] {
+ "java.util.HashMap",
+ "java.util.ArrayList",
+ "java.io.File",
+ "java.io.BufferedReader",
+ "java.io.PrintWriter",
+ "java.io.InputStream",
+ "java.io.OutputStream",
+ "java.io.IOException"
+ };
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/code/PrintWriterWithEditGen.java b/java/src/processing/mode/java/preproc/code/PrintWriterWithEditGen.java
new file mode 100644
index 0000000000..502356e110
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/PrintWriterWithEditGen.java
@@ -0,0 +1,91 @@
+package processing.mode.java.preproc.code;
+
+import org.antlr.v4.runtime.TokenStreamRewriter;
+
+
+/**
+ * Decorator around a {TokenStreamRewriter}.
+ *
+ *
+ * Decorator around a {TokenStreamRewriter} which converts input commands into something that the
+ * rewriter can understand but also generates edits saved to an input RewriteResultBuilder.
+ * Requires a call to finish() after completion of preprocessing.
+ *
+ */
+public class PrintWriterWithEditGen {
+
+ private final TokenStreamRewriter writer;
+ private final RewriteResultBuilder rewriteResultBuilder;
+ private final int insertPoint;
+ private final StringBuilder editBuilder;
+ private final boolean before;
+
+ /**
+ * Create a new edit generator decorator.
+ *
+ * @param writer The writer to which edits should be immediately made.
+ * @param newRewriteResultBuilder The builder to which edits should be saved.
+ * @param newInsertPoint The point at which new values should be inserted.
+ * @param newBefore If true, the values will be inserted before the given insert point. If false,
+ * will, insert after the insertion point.
+ */
+ public PrintWriterWithEditGen(TokenStreamRewriter writer,
+ RewriteResultBuilder newRewriteResultBuilder, int newInsertPoint, boolean newBefore) {
+
+ this.writer = writer;
+ rewriteResultBuilder = newRewriteResultBuilder;
+ insertPoint = newInsertPoint;
+ editBuilder = new StringBuilder();
+ before = newBefore;
+ }
+
+ /**
+ * Add an empty line into the code.
+ */
+ public void addEmptyLine() {
+ addCode("\n");
+ }
+
+ /**
+ * Add code with a newline automatically appended.
+ *
+ * @param newCode The code to add.
+ */
+ public void addCodeLine(String newCode) {
+ addCode(newCode + "\n");
+ }
+
+ /**
+ * Add code without a new line.
+ *
+ * @param newCode The code to add.
+ */
+ public void addCode(String newCode) {
+ editBuilder.append(newCode);
+ }
+
+ /**
+ * Finalize edits made through this decorator.
+ */
+ public void finish() {
+ String newCode = editBuilder.toString();
+
+ if (before) {
+ rewriteResultBuilder.addEdit(CodeEditOperationUtil.createInsertBefore(
+ insertPoint,
+ insertPoint,
+ newCode,
+ writer
+ ));
+ } else {
+ rewriteResultBuilder.addEdit(CodeEditOperationUtil.createInsertAfter(
+ insertPoint,
+ newCode,
+ writer
+ ));
+ }
+
+ rewriteResultBuilder.addOffset(SyntaxUtil.getCount(newCode, "\n"));
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/code/RewriteParams.java b/java/src/processing/mode/java/preproc/code/RewriteParams.java
new file mode 100644
index 0000000000..00adcdff47
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/RewriteParams.java
@@ -0,0 +1,228 @@
+package processing.mode.java.preproc.code;
+
+import org.antlr.v4.runtime.TokenStreamRewriter;
+import processing.mode.java.preproc.PdePreprocessor;
+
+import java.util.List;
+import java.util.Optional;
+
+
+/**
+ * Set of parameters required for re-writing as part of sketch preprocessing.
+ */
+public class RewriteParams {
+
+ private final String version;
+ private final String sketchName;
+ private final boolean isTesting;
+ private final TokenStreamRewriter rewriter;
+ private final PdePreprocessor.Mode mode;
+ private final boolean foundMain;
+ private final int lineOffset;
+ private final List coreImports;
+ private final List defaultImports;
+ private final List codeFolderImports;
+ private final List foundImports;
+ private final Optional sketchWidth;
+ private final Optional sketchHeight;
+ private final Optional sketchRenderer;
+ private final boolean isSizeValidInGlobal;
+ private final boolean isSizeFullscreen;
+
+ /**
+ * Create a new set of parameters.
+ *
+ * @param newVersion The version of the preprocessor.
+ * @param newSketchName The name of the sketch.
+ * @param newisTesting Flag indicating if this is being run as part of automated testing.
+ * @param newRewriter The rewriter into which edits should be made.
+ * @param newMode The mode (like STATIC) in which processing is being run.
+ * @param newFoundMain Flag indicating if a user-provided main method was found in preprocessing.
+ * @param newLineOffset The line offset of the preprocessor prior to rewrite.
+ * @param newCoreImports The set of imports to include that are required for processing.
+ * @param newDefaultImports The set of imports included for user convenience.
+ * @param newCodeFolderImports The imports required to include other code in the code folder.
+ * @param newFoundImports The imports included by the user.
+ * @param newSketchWidth The width of the sketch or code used to generate it. If not included,
+ * call to size will not be made.
+ * @param newSketchHeight The height of the sketch or code used to generate it. If not included,
+ * call to size will not be made.
+ * @param newSketchRenderer The renderer like P2D.
+ * @param newIsSizeValidInGlobal Flag indicating if a call to size is valid when that call to size
+ * is made from sketch global context.
+ * @param newSizeIsFullscreen Indicate if in fullscreen mode.
+ */
+ public RewriteParams(String newVersion, String newSketchName, boolean newisTesting,
+ TokenStreamRewriter newRewriter, PdePreprocessor.Mode newMode,
+ boolean newFoundMain, int newLineOffset, List newCoreImports,
+ List newDefaultImports, List newCodeFolderImports,
+ List newFoundImports, Optional newSketchWidth,
+ Optional newSketchHeight, Optional newSketchRenderer,
+ boolean newIsSizeValidInGlobal, boolean newSizeIsFullscreen) {
+
+ version = newVersion;
+ sketchName = newSketchName;
+ isTesting = newisTesting;
+ rewriter = newRewriter;
+ mode = newMode;
+ foundMain = newFoundMain;
+ lineOffset = newLineOffset;
+ coreImports = newCoreImports;
+ defaultImports = newDefaultImports;
+ codeFolderImports = newCodeFolderImports;
+ foundImports = newFoundImports;
+ sketchWidth = newSketchWidth;
+ sketchHeight = newSketchHeight;
+ sketchRenderer = newSketchRenderer;
+ isSizeValidInGlobal = newIsSizeValidInGlobal;
+ isSizeFullscreen = newSizeIsFullscreen;
+ }
+
+ /**
+ * Get the version of the preprocessor.
+ *
+ * @return The version of the preprocessor.
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /**
+ * The user provided or automated name of the sketch.
+ *
+ * @return The name of the sketch.
+ */
+ public String getSketchName() {
+ return sketchName;
+ }
+
+ /**
+ * Determine if this code is being exercised in automated test.
+ *
+ * @return Flag indicating if this is being run as part of automated testing.
+ */
+ public boolean getisTesting() {
+ return isTesting;
+ }
+
+ /**
+ * Get the rewriter to be used in rewriting.
+ *
+ * @return The rewriter into which edits should be made.
+ */
+ public TokenStreamRewriter getRewriter() {
+ return rewriter;
+ }
+
+ /**
+ * Get the mode in which processing is being run.
+ *
+ * @return The mode (like STATIC) in which processing is being run.
+ */
+ public PdePreprocessor.Mode getMode() {
+ return mode;
+ }
+
+ /**
+ * Determine if the user provided their own main method.
+ *
+ * @return Flag indicating if a user-provided main method was found in preprocessing.
+ */
+ public boolean getFoundMain() {
+ return foundMain;
+ }
+
+ /**
+ * Determine the line offset of the preprocessor prior to rewrite.
+ *
+ * @return The line offset of the preprocessor prior to rewrite.
+ */
+ public int getLineOffset() {
+ return lineOffset;
+ }
+
+ /**
+ * Get imports required for processing.
+ *
+ * @return The set of imports to include that are required for processing.
+ */
+ public List getCoreImports() {
+ return coreImports;
+ }
+
+ /**
+ * Get the imports added for user convenience.
+ *
+ * @return The set of imports included for user convenience.
+ */
+ public List getDefaultImports() {
+ return defaultImports;
+ }
+
+ /**
+ * The imports required to access other code in the code folder.
+ *
+ * @return The imports required to include other code in the code folder.
+ */
+ public List getCodeFolderImports() {
+ return codeFolderImports;
+ }
+
+ /**
+ * Get the users included by the user.
+ *
+ * @return The imports included by the user.
+ */
+ public List getFoundImports() {
+ return foundImports;
+ }
+
+ /**
+ * Get the code used to determine sketch width if given.
+ *
+ * @return The width of the sketch or code used to generate it. If not included, call to size will
+ * not be made. Not included means it is an empty optional.
+ */
+ public Optional getSketchWidth() {
+ return sketchWidth;
+ }
+
+ /**
+ * Get the code used to determine sketch height if given.
+ *
+ * @return The height of the sketch or code used to generate it. If not included, call to size
+ * will not be made. Not included means it is an empty optional.
+ */
+ public Optional getSketchHeight() {
+ return sketchHeight;
+ }
+
+ /**
+ * Get the user provided renderer or an empty optional if user has not provided renderer.
+ *
+ * @return The renderer like P2D if given.
+ */
+ public Optional getSketchRenderer() {
+ return sketchRenderer;
+ }
+
+ /**
+ * Determine if a call to size has been made in sketch global context.
+ *
+ * @return Flag indicating if a call to size is valid when that call to size is made from sketch
+ * global context.
+ */
+ public boolean getIsSizeValidInGlobal() {
+ return isSizeValidInGlobal;
+ }
+
+ /**
+ * Determine if running in fullscreen.
+ *
+ * @return Flag indicating if in running in fullscreen.
+ */
+ public boolean getIsSizeFullscreen() {
+ return isSizeFullscreen;
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/code/RewriteParamsBuilder.java b/java/src/processing/mode/java/preproc/code/RewriteParamsBuilder.java
new file mode 100644
index 0000000000..dd03183b82
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/RewriteParamsBuilder.java
@@ -0,0 +1,256 @@
+package processing.mode.java.preproc.code;
+
+import org.antlr.v4.runtime.TokenStreamRewriter;
+import processing.mode.java.preproc.PdePreprocessor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Optional;
+
+
+/**
+ * Builder to help generate a {RewriteParams}.
+ */
+public class RewriteParamsBuilder {
+
+ private final String version;
+
+ private Optional sketchName;
+ private Optional isTesting;
+ private Optional rewriter;
+ private Optional mode;
+ private Optional foundMain;
+ private Optional lineOffset;
+ private Optional sketchWidth;
+ private Optional sketchHeight;
+ private Optional sketchRenderer;
+ private Optional isSizeValidInGlobal;
+ private Optional isSizeFullscreen;
+
+ private ArrayList coreImports;
+ private ArrayList defaultImports;
+ private ArrayList codeFolderImports;
+ private ArrayList foundImports;
+
+ /**
+ * Create a new params build.
+ *
+ * @param newVersion The version to include in generated RewriteParams.
+ */
+ public RewriteParamsBuilder(String newVersion) {
+ version = newVersion;
+
+ coreImports = new ArrayList<>();
+ defaultImports = new ArrayList<>();
+ codeFolderImports = new ArrayList<>();
+ foundImports = new ArrayList<>();
+
+ sketchName = Optional.empty();
+ isTesting = Optional.empty();
+ rewriter = Optional.empty();
+ mode = Optional.empty();
+ foundMain = Optional.empty();
+ lineOffset = Optional.empty();
+ sketchWidth = Optional.empty();
+ sketchHeight = Optional.empty();
+ sketchRenderer = Optional.empty();
+ isSizeValidInGlobal = Optional.empty();
+ isSizeFullscreen = Optional.empty();
+ }
+
+ /**
+ * Specify the name of the sketch.
+ *
+ * @param newSketchName The name of the sketch.
+ */
+ public void setSketchName(String newSketchName) {
+ sketchName = Optional.ofNullable(newSketchName);
+ }
+
+ /**
+ * Specify if this is being run as part of automated testing.
+ *
+ * @param newisTesting Flag indicating if this is being run as part of automated testing.
+ */
+ public void setisTesting(boolean newisTesting) {
+ isTesting = Optional.of(newisTesting);
+ }
+
+ /**
+ * Specify rewriter into which edits should be made.
+ *
+ * @param newRewriter The rewriter into which edits should be made.
+ */
+ public void setRewriter(TokenStreamRewriter newRewriter) {
+ rewriter = Optional.ofNullable(newRewriter);
+ }
+
+ /**
+ * Specify mode (like STATIC) in which processing is being run.
+ *
+ * @param newMode The mode (like STATIC) in which processing is being run.
+ */
+ public void setMode(PdePreprocessor.Mode newMode) {
+ mode = Optional.ofNullable(newMode);
+ }
+
+ /**
+ * Specify if a user-provided main method was found in preprocessing.
+ *
+ * @param newFoundMain Flag indicating if a user-provided main method was found in preprocessing.
+ */
+ public void setFoundMain(boolean newFoundMain) {
+ foundMain = Optional.of(newFoundMain);
+ }
+
+ /**
+ * Specify line offset of the preprocessor prior to rewrite.
+ *
+ * @param newLineOffset The line offset of the preprocessor prior to rewrite.
+ */
+ public void setLineOffset(int newLineOffset) {
+ lineOffset = Optional.of(newLineOffset);
+ }
+
+ /**
+ * Specify width of the sketch.
+ *
+ * @param newSketchWidth The width of the sketch or code used to generate it. If not included,
+ * call to size will not be made.
+ */
+ public void setSketchWidth(String newSketchWidth) {
+ sketchWidth = Optional.ofNullable(newSketchWidth);
+ }
+
+ /**
+ * Specify height of the sketch.
+ *
+ * @param newSketchHeight The height of the sketch or code used to generate it. If not included,
+ * call to size will not be made.
+ */
+ public void setSketchHeight(String newSketchHeight) {
+ sketchHeight = Optional.ofNullable(newSketchHeight);
+ }
+
+ /**
+ * Specify renderer like P2D.
+ *
+ * @param newSketchRenderer The renderer like P2D.
+ */
+ public void setSketchRenderer(String newSketchRenderer) {
+ sketchRenderer = Optional.ofNullable(newSketchRenderer);
+ }
+
+ /**
+ * Specify if the user made a valid call to size in sketch global context.
+ *
+ * @param newIsSizeValidInGlobal Flag indicating if a call to size is valid when that call to size
+ * is made from sketch global context.
+ */
+ public void setIsSizeValidInGlobal(boolean newIsSizeValidInGlobal) {
+ isSizeValidInGlobal = Optional.of(newIsSizeValidInGlobal);
+ }
+
+ /**
+ * Specify if running in fullscreen.
+ *
+ * @param newIsSizeFullscreen Flag indicating if running in fullscreen.
+ */
+ public void setIsSizeFullscreen(boolean newIsSizeFullscreen) {
+ isSizeFullscreen = Optional.of(newIsSizeFullscreen);
+ }
+
+ /**
+ * Add imports required for processing to function.
+ *
+ * @param newImports The set of imports to include that are required for processing.
+ */
+ public void addCoreImports(Collection newImports) {
+ coreImports.addAll(newImports);
+ }
+
+ /**
+ * Add imports that are included ahead of time for the user.
+ *
+ * @param newImports The set of imports included for user convenience.
+ */
+ public void addDefaultImports(Collection newImports) {
+ defaultImports.addAll(newImports);
+ }
+
+ /**
+ * Add imports required for the sketch to reach code in its own code folder.
+ *
+ * @param newImports The imports required to include other code in the code folder.
+ */
+ public void addCodeFolderImports(Collection newImports) {
+ codeFolderImports.addAll(newImports);
+ }
+
+ /**
+ * Add imports included manually by the user.
+ *
+ * @param newImports The imports included by the user.
+ */
+ public void addFoundImports(Collection newImports) {
+ foundImports.addAll(newImports);
+ }
+
+ /**
+ * Build a new set of rewrite parameters.
+ *
+ * @return Parameters required to execute {RewriterCodeGenerator};
+ */
+ public RewriteParams build() {
+ if (sketchName.isEmpty()) {
+ throw new RuntimeException("Expected sketchName to be set");
+ }
+
+ if (isTesting.isEmpty()) {
+ throw new RuntimeException("Expected isTesting to be set");
+ }
+
+ if (rewriter.isEmpty()) {
+ throw new RuntimeException("Expected rewriter to be set");
+ }
+
+ if (mode.isEmpty()) {
+ throw new RuntimeException("Expected mode to be set");
+ }
+
+ if (foundMain.isEmpty()) {
+ throw new RuntimeException("Expected foundMain to be set");
+ }
+
+ if (lineOffset.isEmpty()) {
+ throw new RuntimeException("Expected lineOffset to be set");
+ }
+
+ if (isSizeValidInGlobal.isEmpty()) {
+ throw new RuntimeException("Expected isSizeValidInGlobal to be set");
+ }
+
+ if (isSizeFullscreen.isEmpty()) {
+ throw new RuntimeException("Expected isSizeFullscreen to be set");
+ }
+
+ return new RewriteParams(
+ version,
+ sketchName.get(),
+ isTesting.get(),
+ rewriter.get(),
+ mode.get(),
+ foundMain.get(),
+ lineOffset.get(),
+ coreImports,
+ defaultImports,
+ codeFolderImports,
+ foundImports,
+ sketchWidth,
+ sketchHeight,
+ sketchRenderer,
+ isSizeValidInGlobal.get(),
+ isSizeFullscreen.get()
+ );
+ }
+}
diff --git a/java/src/processing/mode/java/preproc/code/RewriteResult.java b/java/src/processing/mode/java/preproc/code/RewriteResult.java
new file mode 100644
index 0000000000..c5c23785e9
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/RewriteResult.java
@@ -0,0 +1,45 @@
+package processing.mode.java.preproc.code;
+
+import processing.mode.java.pdex.TextTransform;
+
+import java.util.List;
+
+
+/**
+ * Data structure describing the result of preprocessor rewrite.
+ */
+public class RewriteResult {
+
+ private final int lineOffset;
+ private final List edits;
+
+ /**
+ * Create a new rewrite result structure.
+ *
+ * @param newLineOffset The number of lines added during rewrite.
+ * @param newEdits The edits generated during rewrite.
+ */
+ public RewriteResult(int newLineOffset, List newEdits) {
+ lineOffset = newLineOffset;
+ edits = newEdits;
+ }
+
+ /**
+ * Get the number of lines added during rewrite.
+ *
+ * @return The additional offset to add to the preprocessor line offset.
+ */
+ public int getLineOffset() {
+ return lineOffset;
+ }
+
+ /**
+ * Get the edits generated during rewrite.
+ *
+ * @return Edits generated during rewrite.
+ */
+ public List getEdits() {
+ return edits;
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/code/RewriteResultBuilder.java b/java/src/processing/mode/java/preproc/code/RewriteResultBuilder.java
new file mode 100644
index 0000000000..2434a8a57d
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/RewriteResultBuilder.java
@@ -0,0 +1,69 @@
+package processing.mode.java.preproc.code;
+
+import processing.mode.java.pdex.TextTransform;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Builder to help create a {RewriteResult}.
+ */
+public class RewriteResultBuilder {
+
+ private int lineOffset;
+ private List edits;
+
+ /**
+ * Create a new rewrite result builder.
+ */
+ public RewriteResultBuilder() {
+ lineOffset = 0;
+ edits = new ArrayList<>();
+ }
+
+ /**
+ * Indicate that lines were added to the sketch.
+ *
+ * @param offset By how much to change the current offset.
+ */
+ public void addOffset(int offset) {
+ lineOffset += offset;
+ }
+
+ /**
+ * Record an edit made during rewrite.
+ *
+ * @param edit The edit made.
+ */
+ public void addEdit(TextTransform.Edit edit) {
+ edits.add(edit);
+ }
+
+ /**
+ * Get the number of lines written.
+ *
+ * @return The offset to add to current preprocessor offset.
+ */
+ public int getLineOffset() {
+ return lineOffset;
+ }
+
+ /**
+ * Get the edits generated during rewrite.
+ *
+ * @return The edits generated during rewrite.
+ */
+ public List getEdits() {
+ return edits;
+ }
+
+ /**
+ * Build a new rewrite result.
+ *
+ * @return Immutable rewrite result.
+ */
+ public RewriteResult build() {
+ return new RewriteResult(lineOffset, edits);
+ }
+}
diff --git a/java/src/processing/mode/java/preproc/code/RewriterCodeGenerator.java b/java/src/processing/mode/java/preproc/code/RewriterCodeGenerator.java
new file mode 100644
index 0000000000..339b9045f4
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/RewriterCodeGenerator.java
@@ -0,0 +1,332 @@
+package processing.mode.java.preproc.code;
+
+import org.antlr.v4.runtime.TokenStreamRewriter;
+import processing.app.Preferences;
+import processing.core.PApplet;
+import processing.mode.java.preproc.PdePreprocessor;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.StringJoiner;
+
+
+/**
+ * Utility to rewrite code as part of preprocessing.
+ */
+public class RewriterCodeGenerator {
+
+ private final String indent1;
+ private final String indent2;
+ private final String indent3;
+
+ /**
+ * Create a new rewriter.
+ *
+ * @param indentSize Number of spaces in the indent.
+ */
+ public RewriterCodeGenerator(int indentSize) {
+ final char[] indentChars = new char[indentSize];
+ Arrays.fill(indentChars, ' ');
+ indent1 = new String(indentChars);
+ indent2 = indent1 + indent1;
+ indent3 = indent2 + indent1;
+ }
+
+ /**
+ * Write preface code to wrap sketch code so that it is contained within a proper Java definition.
+ *
+ * @param headerWriter The writer into which the header should be written.
+ * @param params The parameters for the rewrite.
+ * @return Information about the completed rewrite.
+ */
+ public RewriteResult writeHeader(TokenStreamRewriter headerWriter, RewriteParams params) {
+
+ RewriteResultBuilder resultBuilder = new RewriteResultBuilder();
+
+ PrintWriterWithEditGen decoratedWriter = new PrintWriterWithEditGen(
+ headerWriter,
+ resultBuilder,
+ 0,
+ true
+ );
+
+ if (!params.getisTesting()) writePreprocessorComment(decoratedWriter, params, resultBuilder);
+ writeImports(decoratedWriter, params, resultBuilder);
+
+ PdePreprocessor.Mode mode = params.getMode();
+
+ boolean requiresClassHeader = mode == PdePreprocessor.Mode.STATIC;
+ requiresClassHeader = requiresClassHeader || mode == PdePreprocessor.Mode.ACTIVE;
+
+ boolean requiresStaticSketchHeader = mode == PdePreprocessor.Mode.STATIC;
+
+ if (requiresClassHeader) {
+ writeClassHeader(decoratedWriter, params, resultBuilder);
+ }
+
+ if (requiresStaticSketchHeader) {
+ writeStaticSketchHeader(decoratedWriter, params, resultBuilder);
+ }
+
+ decoratedWriter.finish();
+
+ return resultBuilder.build();
+ }
+
+ /**
+ * Write the footer for a sketch (finishes the constructs introduced in header like class def).
+ *
+ * @param footerWriter The writer through which the footer should be introduced.
+ * @param params The parameters for the rewrite.
+ * @param insertPoint The loction at which the footer should be written.
+ * @return Information about the completed rewrite.
+ */
+ public RewriteResult writeFooter(TokenStreamRewriter footerWriter, RewriteParams params,
+ int insertPoint) {
+
+ RewriteResultBuilder resultBuilder = new RewriteResultBuilder();
+
+ PrintWriterWithEditGen decoratedWriter = new PrintWriterWithEditGen(
+ footerWriter,
+ resultBuilder,
+ insertPoint,
+ false
+ );
+
+ decoratedWriter.addEmptyLine();
+
+ PdePreprocessor.Mode mode = params.getMode();
+
+ boolean requiresStaticSketchFooter = mode == PdePreprocessor.Mode.STATIC;
+ boolean requiresClassWrap = mode == PdePreprocessor.Mode.STATIC;
+ requiresClassWrap = requiresClassWrap || mode == PdePreprocessor.Mode.ACTIVE;
+
+ if (requiresStaticSketchFooter) {
+ writeStaticSketchFooter(decoratedWriter, params, resultBuilder);
+ }
+
+ if (requiresClassWrap) {
+ writeExtraFieldsAndMethods(decoratedWriter, params, resultBuilder);
+ if (!params.getFoundMain()) writeMain(decoratedWriter, params, resultBuilder);
+ writeClassFooter(decoratedWriter, params, resultBuilder);
+ }
+
+ decoratedWriter.finish();
+
+ return resultBuilder.build();
+ }
+
+ /**
+ * Comment out sketch code before it is moved elsewhere in resulting Java.
+ *
+ * @param headerWriter The writer though which the comment should be introduced.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writePreprocessorComment(PrintWriterWithEditGen headerWriter, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ String dateStr = new SimpleDateFormat("YYYY-MM-dd").format(new Date());
+
+ String newCode = String.format(
+ "/* autogenerated by Processing preprocessor v%s on %s */",
+ params.getVersion(),
+ dateStr
+ );
+
+ headerWriter.addCodeLine(newCode);
+ }
+
+ /**
+ * Add imports as part of conversion from processing sketch to Java code.
+ *
+ * @param headerWriter The writer though which the imports should be introduced.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeImports(PrintWriterWithEditGen headerWriter, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ writeImportList(headerWriter, params.getCoreImports(), params, resultBuilder);
+ writeImportList(headerWriter, params.getCodeFolderImports(), params, resultBuilder);
+ writeImportList(headerWriter, params.getFoundImports(), params, resultBuilder);
+ writeImportList(headerWriter, params.getDefaultImports(), params, resultBuilder);
+ }
+
+ /**
+ * Write a list of imports.
+ *
+ * @param headerWriter The writer though which the imports should be introduced.
+ * @param imports Collection of imports to introduce.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeImportList(PrintWriterWithEditGen headerWriter, List imports, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ writeImportList(headerWriter, imports.toArray(new String[0]), params, resultBuilder);
+ }
+
+ /**
+ * Write a list of imports.
+ *
+ * @param headerWriter The writer though which the imports should be introduced.
+ * @param imports Collection of imports to introduce.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeImportList(PrintWriterWithEditGen headerWriter, String[] imports, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ for (String importDecl : imports) {
+ headerWriter.addCodeLine("import " + importDecl + ";");
+ }
+ if (imports.length > 0) {
+ headerWriter.addEmptyLine();
+ }
+ }
+
+ /**
+ * Write the prefix which defines the enclosing class for the sketch.
+ *
+ * @param headerWriter The writer through which the header should be introduced.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeClassHeader(PrintWriterWithEditGen headerWriter, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ headerWriter.addCodeLine("public class " + params.getSketchName() + " extends PApplet {");
+
+ headerWriter.addEmptyLine();
+ }
+
+ /**
+ * Write the header for a static sketch (no methods).
+ *
+ * @param headerWriter The writer through which the header should be introduced.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeStaticSketchHeader(PrintWriterWithEditGen headerWriter, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ headerWriter.addCodeLine(indent1 + "public void setup() {");
+ }
+
+ /**
+ * Write the bottom of the sketch code for static mode.
+ *
+ * @param footerWriter The footer into which the text should be written.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeStaticSketchFooter(PrintWriterWithEditGen footerWriter, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ footerWriter.addCodeLine(indent2 + "noLoop();");
+ footerWriter.addCodeLine(indent1 + "}");
+ }
+
+ /**
+ * Write code supporting speical functions like size.
+ *
+ * @param classBodyWriter The writer into which the code should be written. Should be for class
+ * body.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeExtraFieldsAndMethods(PrintWriterWithEditGen classBodyWriter, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ if (!params.getIsSizeValidInGlobal()) {
+ return;
+ }
+
+ String settingsOuterTemplate = indent1 + "public void settings() { %s }";
+
+ String settingsInner;
+ if (params.getIsSizeFullscreen()) {
+ String fullscreenInner = params.getSketchRenderer().orElse("");
+ settingsInner = String.format("fullScreen(%s);", fullscreenInner);
+ } else {
+
+ if (params.getSketchWidth().isEmpty() || params.getSketchHeight().isEmpty()) {
+ return;
+ }
+
+ StringJoiner argJoiner = new StringJoiner(",");
+ argJoiner.add(params.getSketchWidth().get());
+ argJoiner.add(params.getSketchHeight().get());
+
+ if (params.getSketchRenderer().isPresent()) {
+ argJoiner.add(params.getSketchRenderer().get());
+ }
+
+ settingsInner = String.format("size(%s);", argJoiner.toString());
+ }
+
+
+ String newCode = String.format(settingsOuterTemplate, settingsInner);
+
+ classBodyWriter.addEmptyLine();
+ classBodyWriter.addCodeLine(newCode);
+ }
+
+ /**
+ * Write the main method.
+ *
+ * @param footerWriter The writer into which the footer should be written.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeMain(PrintWriterWithEditGen footerWriter, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ footerWriter.addEmptyLine();
+ footerWriter.addCodeLine(indent1 + "static public void main(String[] passedArgs) {");
+ footerWriter.addCode(indent2 + "String[] appletArgs = new String[] { ");
+
+ { // assemble line with applet args
+ if (Preferences.getBoolean("export.application.fullscreen")) {
+ footerWriter.addCode("\"" + PApplet.ARGS_FULL_SCREEN + "\", ");
+
+ String bgColor = Preferences.get("run.present.bgcolor");
+ footerWriter.addCode("\"" + PApplet.ARGS_BGCOLOR + "=" + bgColor + "\", ");
+
+ if (Preferences.getBoolean("export.application.stop")) {
+ String stopColor = Preferences.get("run.present.stop.color");
+ footerWriter.addCode("\"" + PApplet.ARGS_STOP_COLOR + "=" + stopColor + "\", ");
+ } else {
+ footerWriter.addCode("\"" + PApplet.ARGS_HIDE_STOP + "\", ");
+ }
+ }
+ footerWriter.addCode("\"" + params.getSketchName() + "\"");
+ }
+
+ footerWriter.addCodeLine(" };");
+
+ footerWriter.addCodeLine(indent2 + "if (passedArgs != null) {");
+ footerWriter.addCodeLine(indent3 + "PApplet.main(concat(appletArgs, passedArgs));");
+ footerWriter.addCodeLine(indent2 + "} else {");
+ footerWriter.addCodeLine(indent3 + "PApplet.main(appletArgs);");
+ footerWriter.addCodeLine(indent2 + "}");
+ footerWriter.addCodeLine(indent1 + "}");
+ }
+
+ /**
+ * Write the end of the class body for the footer.
+ *
+ * @param footerWriter The writer into which the footer should be written.
+ * @param params The parameters for the rewrite.
+ * @param resultBuilder Builder for reporting out results to the caller.
+ */
+ private void writeClassFooter(PrintWriterWithEditGen footerWriter, RewriteParams params,
+ RewriteResultBuilder resultBuilder) {
+
+ footerWriter.addCodeLine("}");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/code/SyntaxUtil.java b/java/src/processing/mode/java/preproc/code/SyntaxUtil.java
new file mode 100644
index 0000000000..a98d07754c
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/code/SyntaxUtil.java
@@ -0,0 +1,80 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.code;
+
+/**
+ * Convenience functions useful for working on syntax checking for source.
+ */
+public class SyntaxUtil {
+
+ /**
+ * Determine how many times a string appears in another.
+ *
+ * @param body The string in which occurrences should be counted.
+ * @param search The string to look for.
+ * @return The number of times search appears in body.
+ */
+ public static int getCount(String body, String search) {
+ if (search.length() == 1) {
+ return getCountChar(body, search.charAt(0));
+ } else {
+ return getCountString(body, search);
+ }
+ }
+
+ /**
+ * Determine how many times a string appears in another.
+ *
+ * @param body The string in which occurrences should be counted.
+ * @param search The string to look for.
+ * @return The number of times search appears in body.
+ */
+ private static int getCountString(String body, String search) {
+ int count = 0;
+
+ for(int i = 0; i < body.length(); i++)
+ {
+ count += body.substring(i).startsWith(search) ? 1 : 0;
+ }
+
+ return count;
+ }
+
+ /**
+ * Determine how many times a character appears in another.
+ *
+ * @param body The string in which occurrences should be counted.
+ * @param search The character to look for.
+ * @return The number of times search appears in body.
+ */
+ private static int getCountChar(String body, char search) {
+ int count = 0;
+
+ for(int i = 0; i < body.length(); i++)
+ {
+ count += body.charAt(i) == search ? 1 : 0;
+ }
+
+ return count;
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/IssueLocation.java b/java/src/processing/mode/java/preproc/issue/IssueLocation.java
new file mode 100644
index 0000000000..7188e26df3
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/IssueLocation.java
@@ -0,0 +1,65 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue;
+
+
+/**
+ * Data structure describing where an issue occurred.
+ */
+public class IssueLocation {
+
+ private final int line;
+ private final int charPosition;
+
+ /**
+ * Create a new issue location structure.
+ *
+ * @param newLine The line (1-indexed) where the issue occurred. This should be in the global file
+ * generated by the preprocessor and not relative to the start of the tab.
+ * @param newCharPosition The position on the line.
+ */
+ public IssueLocation(int newLine, int newCharPosition) {
+ line = newLine;
+ charPosition = newCharPosition;
+ }
+
+ /**
+ * Get the 1-indexed line on which this error occurred.
+ *
+ * @return The line on which this error occurred. Note that this will be relative to the global
+ * file generated by the preprocessor and not relative to the start of the tab.
+ */
+ public int getLine() {
+ return line;
+ }
+
+ /**
+ * The the position of the error within the line.
+ *
+ * @return The number of characters including whitespace from the start of the line at which the
+ * error occurred.
+ */
+ public int getCharPosition() {
+ return charPosition;
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/IssueLocationFactory.java b/java/src/processing/mode/java/preproc/issue/IssueLocationFactory.java
new file mode 100644
index 0000000000..6ba0f44f10
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/IssueLocationFactory.java
@@ -0,0 +1,127 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue;
+
+import processing.mode.java.preproc.code.SyntaxUtil;
+
+import java.util.Optional;
+
+
+/**
+ * Utility that can help clean up where in source an issue should be reported.
+ *
+ *
+ * For some errors, the location of the "mistake" does not appear close to where the actual error
+ * is generated. For example, consider omitting a semicolon. Though the "mistake" is arguably on
+ * the line on which a semicolon is forgotten, the grammatical error appears in the first
+ * non-skip token after the omitted character. This means that the issue shown to the user may
+ * be far away from the line they would want to edit. This utility helps determine if an issue
+ * requires a new location and, if so, where the location should be.
+ *
+ */
+public class IssueLocationFactory {
+
+ /**
+ * Determine where an issue should be reported.
+ *
+ * @param simplification The issue simplification generated from {PreprocessIssueMessageSimplifierFacade}.
+ * @param originalLine The original line (1 indexed) on which the issue was reported.
+ * @param originalOffset The original number of characters from the start of the line where the
+ * the issue was reported.
+ * @param source The full concatenated source of the sketch being built.
+ * @param lineCount The total
+ * @return The new location where the issue should be reported. This may be identical to the
+ * original location if the issue was not moved.
+ */
+ public static IssueLocation getLineWithOffset(IssueMessageSimplification simplification,
+ int originalLine, int originalOffset, String source) {
+
+ // Determine if the issue should be relocated
+ boolean shouldAttributeToPrior = simplification.getAttributeToPriorToken();
+ shouldAttributeToPrior = shouldAttributeToPrior && originalLine != 0;
+
+ if (!shouldAttributeToPrior) {
+ return new IssueLocation(originalLine, originalOffset);
+ }
+
+ // Find the code prior the issue
+ String priorCode = getContentsUpToLine(source, originalLine);
+
+ // Find the token immediately prior to the issue
+ PriorTokenFinder finder = new PriorTokenFinder();
+ int charPos = priorCode.length();
+ while (!finder.isDone() && charPos > 0) {
+ charPos--;
+ finder.step(priorCode.charAt(charPos));
+ }
+
+ // Find the location offset depending on if the prior token could be found
+ Optional foundStartOfMatchMaybe = finder.getTokenPositionMaybe();
+ int startOfMatch;
+ int linesOffset;
+
+ if (foundStartOfMatchMaybe.isPresent()) {
+ startOfMatch = priorCode.length() - foundStartOfMatchMaybe.get();
+ String contentsOfMatch = priorCode.substring(startOfMatch);
+ linesOffset = SyntaxUtil.getCount(contentsOfMatch, "\n");
+ } else {
+ startOfMatch = priorCode.length();
+ linesOffset = 0;
+ }
+
+ // Apply the location offset and highlight to the end of the line
+ String contentsPriorToMatch = priorCode.substring(0, startOfMatch);
+ int newLine = originalLine - linesOffset;
+ int lengthIncludingLine = contentsPriorToMatch.length();
+ int lengthExcludingLine = contentsPriorToMatch.lastIndexOf('\n');
+ int lineLength = lengthIncludingLine - lengthExcludingLine;
+ int col = lineLength - 1; // highlight from start of line to end
+
+ // Build the new issue location
+ return new IssueLocation(newLine, col);
+ }
+
+ /**
+ * Get all of the contents of source leading up to a line.
+ *
+ * @param source The full concatenated sketch source.
+ * @param endLineExclusive The line up to which code should be returned. Note that this is an
+ * "exclusive" boundary. Code from this line itself will not be included.
+ * @return All of the sketch code leading up to but not including the line given.
+ */
+ private static String getContentsUpToLine(String source, int endLineExclusive) {
+ int line = 0;
+ int stringCursor = 0;
+ int strLength = source.length();
+
+ while (line < endLineExclusive-1 && stringCursor < strLength) {
+ if (source.charAt(stringCursor) == '\n') {
+ line++;
+ }
+
+ stringCursor++;
+ }
+
+ return source.substring(0, stringCursor);
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/IssueMessageSimplification.java b/java/src/processing/mode/java/preproc/issue/IssueMessageSimplification.java
new file mode 100644
index 0000000000..eb8377584a
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/IssueMessageSimplification.java
@@ -0,0 +1,96 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue;
+
+
+/**
+ * Data structure describing an issue simplification or explanation.
+ *
+ *
+ * Data structure describing an edit that was made to an error message or warning to be shown to
+ * the user based on a series of rules that attempts to make error messages easier to understand
+ * for the user.
+ *
+ */
+public class IssueMessageSimplification {
+
+ private final String message;
+ private final boolean attributeToPriorToken;
+
+ /**
+ * Create a new issue message simplification.
+ *
+ *
+ * Create a new issue message simplification that leaves the token attribution alone (the token
+ * on which the error was reported will be the same before error message simplification).
+ *
+ *
+ * @param newMessage The message to show to the user.
+ */
+ public IssueMessageSimplification(String newMessage) {
+ message = newMessage;
+ attributeToPriorToken = false;
+ }
+
+ /**
+ * Create a new issue message simplification.
+ *
+ *
+ * Create a new issue message simplification. Note that there is an option to have the error
+ * attributed to the "prior token". This is helpful, for example, when a semicolon is missing.
+ * The error is generated on the token after the line on which the semicolon was omitted so,
+ * while the error technically emerges on the next line, it is better for the user for it to
+ * appear earlier. Specifically, it is most sensible for it to appear on the "prior token".
+ *
+ *
+ * @param newMessage The message to show to the user.
+ * @param newAttributeToPriorToken Boolean flag indicating if the error should be shown on the
+ * token prior to the one on which the error was originally generated. True if the error should
+ * be attributed to the prior token. False otherwise.
+ */
+ public IssueMessageSimplification(String newMessage, boolean newAttributeToPriorToken) {
+ message = newMessage;
+ attributeToPriorToken = newAttributeToPriorToken;
+ }
+
+ /**
+ * Get the error message text that should be shown to the user.
+ *
+ * @return The error message text that should be shown to the user.
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Flag indicating if the error should be attributed to the prior token.
+ *
+ * @return True if the error should be attributed to the prior non-skip token (not whitepsace or
+ * comment). This is useful when a mistake on a prior line like omitted semicolon causes an
+ * error on a later line but one wants error highlighting closer to the mistake itself. False
+ * if the error should be attributed to the original offending token.
+ */
+ public boolean getAttributeToPriorToken() {
+ return attributeToPriorToken;
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java b/java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java
new file mode 100644
index 0000000000..876f54aeed
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java
@@ -0,0 +1,107 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2019 The Processing Foundation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue;
+
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.antlr.v4.runtime.atn.ATNConfigSet;
+import org.antlr.v4.runtime.dfa.DFA;
+
+import processing.mode.java.preproc.SourceEmitter;
+
+import java.util.BitSet;
+import java.util.Optional;
+
+
+/**
+ * ANTLR error listener to inform a preprocess issue listener when syntax errors are encountered.
+ *
+ *
+ * A {BaseErrorListener} which looks for syntax errors reported by ANTLR and converts them to
+ * {PdePreprocessIssue}s that are consumable by a {PdePreprocessIssueListener}. It does this by
+ * running the {PreprocessIssueMessageSimplifierFacade} to generate a more user-friendly error message
+ * before informing the provided listener.
+ *
+ */
+public class PdeIssueEmitter extends BaseErrorListener {
+
+ private final PdePreprocessIssueListener listener;
+ private final Optional sourceMaybe;
+
+ /**
+ * Create a new issue emitter.
+ *
+ *
+ * Create a new issue emitter when access to the processing sketch source is not available.
+ * Note that this will not allow some error beautification and, if sketch source is available,
+ * use other constructor.
+ *
+ *
+ * @param newListener The listener to inform when encountering a syntax error.
+ */
+ public PdeIssueEmitter(PdePreprocessIssueListener newListener) {
+ listener = newListener;
+ sourceMaybe = Optional.empty();
+ }
+
+ /**
+ * Create a new issue emitter.
+ *
+ * @param newListener The listener to inform when encountering a syntax error.
+ * @param newSourceEmitter The sketch source to use when helping beautify certain syntax error
+ * messages.
+ */
+ public PdeIssueEmitter(PdePreprocessIssueListener newListener, SourceEmitter newSourceEmitter) {
+ listener = newListener;
+ sourceMaybe = Optional.of(newSourceEmitter);
+ }
+
+ @Override
+ public void syntaxError(Recognizer, ?> recognizer, Object offendingSymbol, int line,
+ int charPositionInLine, String msg, RecognitionException e) {
+
+ PreprocessIssueMessageSimplifierFacade facade = PreprocessIssueMessageSimplifierFacade.get();
+ IssueMessageSimplification simplification = facade.simplify(msg);
+
+ IssueLocation issueLocation;
+
+ if (sourceMaybe.isPresent()) {
+ issueLocation = IssueLocationFactory.getLineWithOffset(
+ simplification,
+ line,
+ charPositionInLine,
+ sourceMaybe.get().getSource()
+ );
+ } else {
+ issueLocation = new IssueLocation(line, charPositionInLine);
+ }
+
+ listener.onIssue(new PdePreprocessIssue(
+ issueLocation.getLine(),
+ issueLocation.getCharPosition(),
+ simplification.getMessage()
+ ));
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/PdePreprocessIssue.java b/java/src/processing/mode/java/preproc/issue/PdePreprocessIssue.java
new file mode 100644
index 0000000000..df40536035
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/PdePreprocessIssue.java
@@ -0,0 +1,27 @@
+package processing.mode.java.preproc.issue;
+
+public class PdePreprocessIssue {
+
+ private final int line;
+ private final int charPositionInLine;
+ private final String msg;
+
+ public PdePreprocessIssue(int newLine, int newCharPositionInLine, String newMsg) {
+ line = newLine;
+ charPositionInLine = newCharPositionInLine;
+ msg = newMsg;
+ }
+
+ public int getLine() {
+ return line;
+ }
+
+ public int getCharPositionInLine() {
+ return charPositionInLine;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueException.java b/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueException.java
new file mode 100644
index 0000000000..de1b49b292
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueException.java
@@ -0,0 +1,18 @@
+package processing.mode.java.preproc.issue;
+
+import processing.mode.java.preproc.issue.PdePreprocessIssue;
+
+public class PdePreprocessIssueException extends RuntimeException {
+
+ private final PdePreprocessIssue preprocessIssue;
+
+ public PdePreprocessIssueException(PdePreprocessIssue newPreprocessIssue) {
+ super(newPreprocessIssue.getMsg());
+ preprocessIssue = newPreprocessIssue;
+ }
+
+ public PdePreprocessIssue getIssue() {
+ return preprocessIssue;
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueListener.java b/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueListener.java
new file mode 100644
index 0000000000..2f0e46d651
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/PdePreprocessIssueListener.java
@@ -0,0 +1,9 @@
+package processing.mode.java.preproc.issue;
+
+import processing.mode.java.preproc.issue.PdePreprocessIssue;
+
+public interface PdePreprocessIssueListener {
+
+ void onIssue(PdePreprocessIssue issue);
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/PreprocessIssueMessageSimplifierFacade.java b/java/src/processing/mode/java/preproc/issue/PreprocessIssueMessageSimplifierFacade.java
new file mode 100644
index 0000000000..bb6191ff96
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/PreprocessIssueMessageSimplifierFacade.java
@@ -0,0 +1,108 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue;
+
+
+import processing.mode.java.preproc.issue.strategy.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+/**
+ * Facade that tries to create a better error message for syntax issues in input source.
+ *
+ *
+ * Facade that interprets error messages from ANTLR in an attempt to generate an improved error
+ * message when describing grammatically incorrect input. This is distinct from compiler errors
+ * caused after generating an AST.
+ *
+ *
+ *
+ * Note that this is distinct from the {CompileErrorMessageSimplifier}. This operates on issues
+ * caused in parsing and services all users whereas the {CompileErrorMessageSimplifier} only
+ * operates on issues generated after preprocessing has been successful.
+ *
+ */
+public class PreprocessIssueMessageSimplifierFacade {
+
+ private static AtomicReference instance = new AtomicReference<>();
+
+ private List strategies;
+
+ /**
+ * Get a shared instance of this singleton.
+ *
+ * @return Shared instance of this singleton, creating that shared instance if one did not exist
+ * previously.
+ */
+ public static PreprocessIssueMessageSimplifierFacade get() {
+ instance.compareAndSet(null, new PreprocessIssueMessageSimplifierFacade());
+ return instance.get();
+ }
+
+ /**
+ * Create a new syntax issue message simplifier with the default simplifier strategies.
+ */
+ private PreprocessIssueMessageSimplifierFacade() {
+ strategies = new ArrayList<>();
+ strategies.add(new MissingCurlyAtStartMessageSimplifierStrategy());
+ strategies.add(new MissingCurlyAtSemicolonMessageSimplifierStrategy());
+ strategies.add(new MissingGenericTypeMessageSimplifierStrategy());
+ strategies.add(new MissingIdentifierMessageSimplifierStrategy());
+ strategies.add(new KnownMissingMessageSimplifierStrategy());
+ strategies.add(new ExtraneousInputMessageSimplifierStrategy());
+ strategies.add(new MismatchedInputMessageSimplifierStrategy());
+ strategies.add(new AssignmentMessageSimplifierStrategy());
+ strategies.add(new MissingVariableNameMessageSimplifierStrategy());
+ strategies.add(new BadIdentifierMessageSimplifierStrategy());
+ strategies.add(new MissingClassNameMessageSimplifierStrategy());
+ strategies.add(new MissingMethodNameMessageSimplifierStrategy());
+ strategies.add(new BadParamMessageSimplifierStrategy());
+ strategies.add(new MissingDoubleQuoteMessageSimplifierStrategy());
+ strategies.add(new MissingSingleQuoteMessageSimplifierStrategy());
+ strategies.add(new MissingParenMessageSimplifierStrategy());
+ strategies.add(new MissingChevMessageSimplifierStrategy());
+ strategies.add(new MissingCurlyMessageSimplifierStrategy());
+ strategies.add(new DefaultMessageSimplifier());
+ }
+
+ /**
+ * Attempt to improve an error message.
+ *
+ * @param originalMessage Error message generated from ANTLR.
+ * @return An improved error message or the originalMessage if no improvements could be made.
+ */
+ public IssueMessageSimplification simplify(String originalMessage) {
+ //System.err.println(originalMessage);
+ Optional matching = strategies.stream()
+ .map((x) -> x.simplify(originalMessage))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findFirst();
+
+ return matching.orElse(new IssueMessageSimplification(originalMessage));
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/PriorTokenFinder.java b/java/src/processing/mode/java/preproc/issue/PriorTokenFinder.java
new file mode 100644
index 0000000000..51e8106a81
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/PriorTokenFinder.java
@@ -0,0 +1,333 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue;
+
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+
+/**
+ * Simple automaton that reads backwards from a position in source to find the prior token.
+ *
+ *
+ * When helping generate messages for the user, it is often useful to be able to locate the
+ * position of the first token immediately before another location in source. For example,
+ * consider error reporting when a semicolon is missing. The error is generated on the token after
+ * the line on which the semicolon was omitted so, while the error technically emerges on the next
+ * line, it is better for the user for it to appear earlier. Specifically, it is most sensible for
+ * it to appear on the "prior token" because this is where it was forgotten.
+ *
+ *
+ *
+ * To that end, this finite state automaton can read backwards from a position in source to locate
+ * the first "non-skip token" preceding that location. Here a "skip" token means one that is
+ * ignored by the preprocessor and does not impact output code (this includes comments and
+ * whitespace). This automaton will read character by character from source until it knows it has
+ * seen a non-skip token, returning the location of that non-skip token.
+ *
+ *
+ *
+ * A formalized FSA is useful here in order to traverse code which can have a complex grammar.
+ * As there are a number of ways in the Java / Processing grammar one can encounter skip tokens,
+ * this formalized implementation describes the state machine directly in order to provide
+ * hopefully more readability / transparency compared to a regex without requiring the use of
+ * something heavier like ANTLR.
+ *
+ */
+public class PriorTokenFinder {
+
+ // Simple regex matching all "whitespace" characters recognized by the ANTLR grammar.
+ private static final String WS_PATTERN = "[ \\t\\r\\n\\u000C]";
+
+ // Possible states for this FSA
+ private enum AutomatonState {
+
+ // Automaton is not certain if it is parsing a skip or non-skip character
+ UNKNOWN,
+
+ // Automaton has found a possible token but it is not sure if inside a comment
+ POSSIBLE_TOKEN,
+
+ // Automaton has found a token but also a forward slash so, if the next character is also a "/",
+ // it is inside a single line comment.
+ TOKEN_OR_MAYBE_SL_COMMENT,
+
+ // Automaton has found a forward slash so, depending on the next character, it may be inside a
+ // single line comment, multi-line comment, or it may have found a standalone token.
+ TOKEN_OR_MAYBE_COMMENT,
+
+ // Automaton has found a token and hit its terminal state.
+ TOKEN,
+
+ // Automaton is current traversing a multi-line comment.
+ MULTI_LINE_COMMENT,
+
+ // Automaton is maybe leaving a multi line comment because it found an "*". If it picks up a "/"
+ // next, the automaton knows it is no longer within a multi-line comment.
+ MAYBE_LEAVE_MULTI_LINE_COMMENT
+ }
+
+ private boolean done;
+ private Optional tokenPosition;
+ private AutomatonState state;
+ private int charPosition;
+ private Pattern whitespacePattern;
+
+ /**
+ * Create a new automaton in unknown state and a character position of zero.
+ */
+ public PriorTokenFinder() {
+ whitespacePattern = Pattern.compile(WS_PATTERN);
+ reset();
+ }
+
+ /**
+ * Determine if this automaton has found a token.
+ *
+ * @return True if this automaton has found a token and, thus, is in terminal state (so will
+ * ignore all future input). False if this autoamton has not yet found a token since creation
+ * or last call to reset.
+ */
+ public boolean isDone() {
+ return done;
+ }
+
+ /**
+ * Get the position of the token found.
+ *
+ * @return Optional containing the number of characters processed prior to finding the token or
+ * empty if no token found. Note that this is different the number of total characters
+ * processed as some extra characters have to be read prior to the token itself to ensure it is
+ * not part of a comment or something similar.
+ */
+ public Optional getTokenPositionMaybe() {
+ return tokenPosition;
+ }
+
+ /**
+ * Reset this automaton to UNKNOWN state with a character count of zero.
+ */
+ public void reset() {
+ done = false;
+ tokenPosition = Optional.empty();
+ state = AutomatonState.UNKNOWN;
+ charPosition = 0;
+ }
+
+ /**
+ * Process a character.
+ *
+ *
+ * Process the next character in an effort to find the "prior token". Note that this is
+ * expecting the processing sketch source code to be fed one character at a time
+ * backwards from the starting position in code. This is because it is looking for the
+ * first non-skip token immediately preceding a position in source.
+ *
+ *
+ * @param input The next character to process.
+ */
+ public void step(char input) {
+ switch(state) {
+ case UNKNOWN: stepUnknown(input); break;
+ case POSSIBLE_TOKEN: stepPossibleToken(input); break;
+ case TOKEN_OR_MAYBE_SL_COMMENT: stepTokenOrMaybeSingleLineComment(input); break;
+ case TOKEN_OR_MAYBE_COMMENT: stepTokenOrMaybeComment(input); break;
+ case MULTI_LINE_COMMENT: stepMultiLineComment(input); break;
+ case MAYBE_LEAVE_MULTI_LINE_COMMENT: stepMaybeLeaveMultiLineComment(input); break;
+ case TOKEN: /* Already have token. Nothing to be done. */ break;
+ }
+
+ charPosition++;
+ }
+
+ /**
+ * Process the next character while in the UNKNOWN state.
+ *
+ *
+ * While not certain if looking at a skip or non-skip token, read the next character. If
+ * whitespace, can ignore. If a forward slash, could indicate either a comment or a possible
+ * token (move to TOKEN_OR_MAYBE_COMMENT). If anything else, may have found token but need to
+ * ensure this line isn't part of a comment (move to POSSIBLE_TOKEN).
+ *
+ *
+ * @param input The next character to process.
+ */
+ private void stepUnknown(char input) {
+ if (isWhitespace(input)) {
+ return;
+ }
+
+ tokenPosition = Optional.of(charPosition);
+
+ if (input == '/') {
+ state = AutomatonState.TOKEN_OR_MAYBE_COMMENT;
+ } else {
+ state = AutomatonState.POSSIBLE_TOKEN;
+ }
+ }
+
+ /**
+ * Process the next character while in the POSSIBLE_TOKEN state.
+ *
+ *
+ * After having found a character that could indicate a token, need to ensure that the token
+ * wasn't actually part of a single line comment ("//") so look for forward slashes (if found
+ * move to TOKEN_OR_MAYBE_SL_COMMENT). If encountered a newline, the earlier found token was
+ * not part of a comment so enter TOKEN state.
+ *
+ *
+ * @param input The next character to process.
+ */
+ private void stepPossibleToken(char input) {
+ if (input == '\n') {
+ enterNonSkipTokenState();
+ } else if (input == '/') {
+ state = AutomatonState.TOKEN_OR_MAYBE_SL_COMMENT;
+ }
+
+ // Else stay put
+ }
+
+ /**
+ * Process the next character while in the TOKEN_OR_MAYBE_SL_COMMENT state.
+ *
+ *
+ * After having found a forward slash after encountering something else which may be a non-skip
+ * token, one needs to check that it is preceded by another forward slash to have detected a
+ * single line comment (return to UNKNOWN state). If found a new line, that forward slash was
+ * actually a non-skip token itself so enter TOKEN state. Finally, if anything else, it is still
+ * possible that we are traversing a single line comment so return to POSSIBLE_TOKEN state.
+ *
+ *
+ * @param input The next character to process.
+ */
+ private void stepTokenOrMaybeSingleLineComment(char input) {
+ if (input == '\n') {
+ enterNonSkipTokenState();
+ } else if (input == '/') {
+ returnToUnknownState();
+ } else {
+ state = AutomatonState.POSSIBLE_TOKEN;
+ }
+ }
+
+ /**
+ * Process the next character while in the TOKEN_OR_MAYBE_COMMENT state.
+ *
+ *
+ * After having found a forward slash without encountering something else that may be a non-skip
+ * token: that forward slash is a non-skip token if preceded by a newline, could be a single
+ * line comment if preceded by a forward slash, could be a multi-line comment if preceded
+ * by an asterisk, or could by a non-skip token otherwise.
+ *
+ *
+ * @param input The next character to process.
+ */
+ private void stepTokenOrMaybeComment(char input) {
+ if (input == '\n') {
+ enterNonSkipTokenState();
+ } else if (input == '/') {
+ returnToUnknownState();
+ } else if (input == '*') {
+ enterMultilineComment();
+ } else {
+ state = AutomatonState.POSSIBLE_TOKEN;
+ }
+ }
+
+ /**
+ * Process the next character while in the MULTI_LINE_COMMENT state.
+ *
+ *
+ * Process the next character while traversing a multi-line comment. If an asterisk, we may be
+ * encountering the end of the multiline comment (move to MAYBE_LEAVE_MULTI_LINE_COMMENT).
+ * Otherwise, can ignore character.
+ *
+ *
+ * @param input The next character to process.
+ */
+ private void stepMultiLineComment(char input) {
+ if (input == '*') {
+ state = AutomatonState.MAYBE_LEAVE_MULTI_LINE_COMMENT;
+ }
+
+ // else stay put
+ }
+
+ /**
+ * Process the next character while in the MAYBE_LEAVE_MULTI_LINE_COMMENT state.
+ *
+ *
+ * If already found an asterisk while inside a multi-line comment, one may be leaving the multi-
+ * line comment depending on the next character. If forward slash, at end of comment (return to
+ * UNKNOWN state). If another asterisk, could still end comment depending on next character
+ * (stay in current state). Finally, if anything else, we are still in the body of the multi-
+ * line comment and not about to leave (return to MULTI_LINE_COMMENT state).
+ *
+ *
+ * @param input
+ */
+ private void stepMaybeLeaveMultiLineComment(char input) {
+ if (input == '/') {
+ state = AutomatonState.UNKNOWN;
+ } else if (input != '*') {
+ state = AutomatonState.MULTI_LINE_COMMENT;
+ }
+
+ // If * stay put
+ }
+
+ /**
+ * Convenience function to set up internal FSA state when entering a multi-line comment.
+ */
+ private void enterMultilineComment() {
+ tokenPosition = Optional.of(charPosition);
+ state = AutomatonState.MULTI_LINE_COMMENT;
+ }
+
+ /**
+ * Convenience function to set up internal FSA state when having found a non-skip token.
+ */
+ private void enterNonSkipTokenState() {
+ done = true;
+ state = AutomatonState.TOKEN;
+ }
+
+ /**
+ * Convenience function to set up internal FSA state when entering UNKNOWN state.
+ */
+ private void returnToUnknownState() {
+ tokenPosition = Optional.empty();
+ state = AutomatonState.UNKNOWN;
+ }
+
+ /**
+ * Convenience function which determines if a character is whitespace.
+ *
+ * @param input The character to test.
+ * @return True if whitespace. False otherwise.
+ */
+ private boolean isWhitespace(char input) {
+ return whitespacePattern.matcher("" + input).find();
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/AssignmentMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/AssignmentMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..1e3b7a070c
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/AssignmentMessageSimplifierStrategy.java
@@ -0,0 +1,40 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+
+/**
+ * Strategy to describe an issue in an assignment.
+ */
+public class AssignmentMessageSimplifierStrategy extends RegexTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getRegexPattern() {
+ return "[.\\n]*[0-9a-zA-Z\\_<>]+\\s*=[\\s';]*$";
+ }
+
+ @Override
+ public String getHintTemplate() {
+ return MessageSimplifierUtil.getLocalStr("editor.status.bad.assignment");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/BadIdentifierMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/BadIdentifierMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..f0d8d06e3e
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/BadIdentifierMessageSimplifierStrategy.java
@@ -0,0 +1,41 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+/**
+ * Strategy to describe issue in an identifier name like an identifier starting with a digit.
+ */
+public class BadIdentifierMessageSimplifierStrategy extends RegexTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getRegexPattern() {
+ return "([.\\s]*[0-9]+[a-zA-Z_<>]+[0-9a-zA-Z_<>]*|\\s+\\d+[a-zA-Z_<>]+|[0-9a-zA-Z_<>]+\\s+[0-9]+)";
+ }
+
+ @Override
+ public String getHintTemplate() {
+ return MessageSimplifierUtil.getLocalStr(
+ "editor.status.bad.identifier"
+ );
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/BadParamMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/BadParamMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..dcf398928f
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/BadParamMessageSimplifierStrategy.java
@@ -0,0 +1,40 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+/**
+ * Strategy to check for an error in specifying a parameter value.
+ */
+public class BadParamMessageSimplifierStrategy
+ extends RegexTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getRegexPattern() {
+ return "([a-zA-Z0-9_]+\\s*,|[a-zA-Z0-9_]+\\)|\\([^\\)]+)";
+ }
+
+ @Override
+ public String getHintTemplate() {
+ return MessageSimplifierUtil.getLocalStr("editor.status.bad.parameter");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/DefaultErrorLocalStrSet.java b/java/src/processing/mode/java/preproc/issue/strategy/DefaultErrorLocalStrSet.java
new file mode 100644
index 0000000000..3623471d30
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/DefaultErrorLocalStrSet.java
@@ -0,0 +1,55 @@
+package processing.mode.java.preproc.issue.strategy;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Singleton with fallback error localizations.
+ */
+public class DefaultErrorLocalStrSet {
+
+ private static final AtomicReference instance = new AtomicReference<>();
+
+ private final Map localizations = new HashMap<>();
+
+ /**
+ * Get shared copy of this singleton.
+ *
+ * @return Shared singleton copy.
+ */
+ public static DefaultErrorLocalStrSet get() {
+ instance.compareAndSet(null, new DefaultErrorLocalStrSet());
+ return instance.get();
+ }
+
+ /**
+ * Private hidden constructor.
+ */
+ private DefaultErrorLocalStrSet() {
+ localizations.put("editor.status.error", "Error");
+ localizations.put("editor.status.error.syntax", "Syntax Error - %s");
+ localizations.put("editor.status.bad.assignment", "Error on variable assignment near %s?");
+ localizations.put("editor.status.bad.identifier", "Identifier cannot start with digits near %s?");
+ localizations.put("editor.status.bad.parameter", "Error on parameter or method declaration near %s?");
+ localizations.put("editor.status.extraneous", "Unexpected extra code near %s?");
+ localizations.put("editor.status.mismatched", "Missing operator or semicolon near %s?");
+ localizations.put("editor.status.missing.name", "Missing name near %s?");
+ localizations.put("editor.status.missing.type", "Missing name or type near %s?");
+ localizations.put("editor.status.missing.default", "Missing '%s'?");
+ localizations.put("editor.status.missing.right_curly_bracket", "Missing '}'");
+ localizations.put("editor.status.missing.left_curly_bracket", "Missing '{'");
+ }
+
+ /**
+ * Lookup localization.
+ *
+ * @param key Name of string.
+ * @return Value of string or empty if not given.
+ */
+ public Optional get(String key) {
+ return Optional.ofNullable(localizations.getOrDefault(key, null));
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/DefaultMessageSimplifier.java b/java/src/processing/mode/java/preproc/issue/strategy/DefaultMessageSimplifier.java
new file mode 100644
index 0000000000..68a15a8327
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/DefaultMessageSimplifier.java
@@ -0,0 +1,51 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+
+
+/**
+ * Default strategy to use if other message simplification strategies have failed.
+ */
+public class DefaultMessageSimplifier implements PreprocIssueMessageSimplifierStrategy {
+
+ @Override
+ public Optional simplify(String message) {
+ if (message.contains("viable alternative")) {
+ String newMessage = String.format(
+ MessageSimplifierUtil.getLocalizedGenericError("%s"),
+ MessageSimplifierUtil.getOffendingArea(message)
+ );
+ return Optional.of(
+ new IssueMessageSimplification(newMessage)
+ );
+ } else {
+ return Optional.of(
+ new IssueMessageSimplification(message)
+ );
+ }
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/EvenCountTemplateMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/EvenCountTemplateMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..6aaf6e7f0d
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/EvenCountTemplateMessageSimplifierStrategy.java
@@ -0,0 +1,81 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.app.Language;
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+import processing.mode.java.preproc.code.SyntaxUtil;
+
+import java.util.Optional;
+
+
+/**
+ * Strategy to check to make sure that the number of occurrences of a token are even.
+ *
+ *
+ * Strategy to ensure that there are an even number of tokens like even number of double quotes
+ * for example.
+ *
+ */
+public abstract class EvenCountTemplateMessageSimplifierStrategy
+ implements PreprocIssueMessageSimplifierStrategy {
+
+ @Override
+ public Optional simplify(String message) {
+ String messageContent = MessageSimplifierUtil.getOffendingArea(message);
+
+ if (getFilter().isPresent()) {
+ messageContent = messageContent.replace(getFilter().get(), "");
+ }
+
+ int count = SyntaxUtil.getCount(messageContent, getToken());
+
+ if (count % 2 == 0) {
+ return Optional.empty();
+ } else {
+ String newMessage = String.format(
+ MessageSimplifierUtil.getLocalStr("editor.status.missing.default").replace("%c", "%s"),
+ getToken()
+ );
+ return Optional.of(
+ new IssueMessageSimplification(newMessage)
+ );
+ }
+ }
+
+ /**
+ * Get the token that should be counted.
+ *
+ * @return The token whose occurrences should be even.
+ */
+ public abstract String getToken();
+
+ /**
+ * Get the text that should be removed before counting.
+ *
+ * @return An optional string whose occurrences will be removed prior to counting.
+ */
+ public Optional getFilter() {
+ return Optional.empty();
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/ExtraneousInputMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/ExtraneousInputMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..a060fa2a98
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/ExtraneousInputMessageSimplifierStrategy.java
@@ -0,0 +1,51 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+
+
+/**
+ * Strategy to handle extraneous input messages.
+ */
+public class ExtraneousInputMessageSimplifierStrategy
+ implements PreprocIssueMessageSimplifierStrategy {
+
+ @Override
+ public Optional simplify(String message) {
+ if (message.toLowerCase().contains("extraneous")) {
+ String innerMsg = MessageSimplifierUtil.getOffendingArea(message);
+
+ String newMessageOuter = MessageSimplifierUtil.getLocalStr("editor.status.extraneous");
+ String newMessage = String.format(newMessageOuter, innerMsg);
+
+ return Optional.of(
+ new IssueMessageSimplification(newMessage)
+ );
+ } else {
+ return Optional.empty();
+ }
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/KnownMissingMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/KnownMissingMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..c285f2a3f5
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/KnownMissingMessageSimplifierStrategy.java
@@ -0,0 +1,66 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Strategy to handle missing token messages.
+ */
+public class KnownMissingMessageSimplifierStrategy implements PreprocIssueMessageSimplifierStrategy {
+
+ private static final String PARSE_PATTERN_STR = ".*missing '(.*)' at .*";
+
+ private final Pattern parsePattern;
+
+ public KnownMissingMessageSimplifierStrategy() {
+ parsePattern = Pattern.compile(PARSE_PATTERN_STR);
+ }
+
+ @Override
+ public Optional simplify(String message) {
+ if (message.toLowerCase().contains("missing")) {
+ String missingPiece;
+ Matcher matcher = parsePattern.matcher(message);
+ if (matcher.find()) {
+ missingPiece = matcher.group(1);
+ } else {
+ missingPiece = "character";
+ }
+
+ String langTemplate = MessageSimplifierUtil.getLocalStr("editor.status.missing.default")
+ .replace("%c", "%s");
+
+ String newMessage = String.format(langTemplate, missingPiece);
+
+ return Optional.of(new IssueMessageSimplification(newMessage));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MessageSimplifierUtil.java b/java/src/processing/mode/java/preproc/issue/strategy/MessageSimplifierUtil.java
new file mode 100644
index 0000000000..f02b20dcb0
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MessageSimplifierUtil.java
@@ -0,0 +1,105 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.app.Language;
+import processing.app.Platform;
+
+import java.util.Arrays;
+
+
+/**
+ * Convenience functions useful for generating simplified messages.
+ */
+public class MessageSimplifierUtil {
+
+ /**
+ * Get the snippet of "offending code" from an error message if given.
+ *
+ * @param area The area from which to extract the offending code.
+ * @return The offending code described in the error message or the original message if the subset
+ * describing the offending code could not be found.
+ */
+ public static String getOffendingArea(String area) {
+ return getOffendingArea(area, true);
+ }
+
+ /**
+ * Get the snippet of "offending code" from an error message if given.
+ *
+ * @param area The area from which to extract the offending code.
+ * @param removeNewline Flag indicating if newlines should be removed or not.
+ * @return The offending code described in the error message or the original message if the subset
+ * describing the offending code could not be found.
+ */
+ public static String getOffendingArea(String area, boolean removeNewline) {
+ if (!area.contains("viable alternative")) {
+ return area;
+ }
+
+ String content = area.replace("no viable alternative at input \'", "");
+
+ if (removeNewline) {
+ String[] contentLines = content.replace("\n", "\\n").split("\\\\n");
+ content = contentLines[contentLines.length - 1];
+ }
+
+ if (content.endsWith("'")) {
+ return content.substring(0, content.length() - 1);
+ } else {
+ return content;
+ }
+ }
+
+ /**
+ * Generate an generic error message.
+ *
+ * @param unlocalized The unlocalized string. Will be included in resulting message but with
+ * surrounding localized text.
+ * @return Semi-localized message.
+ */
+ public static String getLocalizedGenericError(String unlocalized) {
+ String template = getLocalStr("editor.status.error_on");
+ return String.format(template, unlocalized);
+ }
+
+ /**
+ * Get a localized template string.
+ *
+ * @param stringName Name of the template.
+ * @return The template's contents prior to rendering.
+ */
+ public static String getLocalStr(String stringName) {
+ String errStr;
+ String retStr;
+
+ if (Platform.isInit()) {
+ errStr = Language.text("editor.status.error.syntax");
+ retStr = Language.text(stringName);
+ } else {
+ errStr = DefaultErrorLocalStrSet.get().get("editor.status.error.syntax").orElse("Error");
+ retStr = DefaultErrorLocalStrSet.get().get(stringName).orElse(stringName);
+ }
+
+ return String.format(errStr, retStr);
+ }
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MismatchedInputMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MismatchedInputMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..394bca4b0d
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MismatchedInputMessageSimplifierStrategy.java
@@ -0,0 +1,66 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Strategy to explain a mismatched input issue.
+ */
+public class MismatchedInputMessageSimplifierStrategy implements PreprocIssueMessageSimplifierStrategy {
+
+ private static final String PARSER_STR = "mismatched input '(.*)' expecting ";
+ private final Pattern parser;
+
+ /**
+ * Create a new strategy for mismatched input.
+ */
+ public MismatchedInputMessageSimplifierStrategy() {
+ parser = Pattern.compile(PARSER_STR);
+ }
+
+ @Override
+ public Optional simplify(String message) {
+ if (message.toLowerCase().contains("mismatched input")) {
+ Matcher matcher = parser.matcher(message);
+
+ String newMessage = String.format(
+ MessageSimplifierUtil.getLocalStr("editor.status.mismatched"),
+ matcher.find() ? matcher.group(1) : message
+ );
+
+ return Optional.of(
+ new IssueMessageSimplification(
+ newMessage
+ )
+ );
+ } else {
+ return Optional.empty();
+ }
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingChevMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingChevMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..24220d6de5
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingChevMessageSimplifierStrategy.java
@@ -0,0 +1,40 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+/**
+ * Strategy to check for a missing chevron.
+ */
+public class MissingChevMessageSimplifierStrategy
+ extends TokenPairTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getToken1() {
+ return "<";
+ }
+
+ @Override
+ public String getToken2() {
+ return ">";
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingClassNameMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingClassNameMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..f1d3d9473e
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingClassNameMessageSimplifierStrategy.java
@@ -0,0 +1,39 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+/**
+ * Strategy to check for a class definition without a name.
+ */
+public class MissingClassNameMessageSimplifierStrategy extends RegexTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getRegexPattern() {
+ return ".*(class|interface)\\s*[a-zA-Z0-9_]*\\s+(extends|implements|<.*>)?\\s*[a-zA-Z0-9_]*\\s*\\{.*";
+ }
+
+ @Override
+ public String getHintTemplate() {
+ return MessageSimplifierUtil.getLocalStr("editor.status.missing.name");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyAtSemicolonMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyAtSemicolonMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..df52c2fa76
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyAtSemicolonMessageSimplifierStrategy.java
@@ -0,0 +1,25 @@
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+
+
+/**
+ * Strategy to catch a missing curly at a semicolon.
+ */
+public class MissingCurlyAtSemicolonMessageSimplifierStrategy
+ implements PreprocIssueMessageSimplifierStrategy {
+
+ @Override
+ public Optional simplify(String message) {
+ if (!message.equals("missing ';' at '{'")) {
+ return Optional.empty();
+ }
+
+ return Optional.of(new IssueMessageSimplification(
+ MessageSimplifierUtil.getLocalStr("editor.status.missing.right_curly_bracket")
+ ));
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyAtStartMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyAtStartMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..ab0d5eb3ce
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyAtStartMessageSimplifierStrategy.java
@@ -0,0 +1,24 @@
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+
+public class MissingCurlyAtStartMessageSimplifierStrategy
+ implements PreprocIssueMessageSimplifierStrategy {
+
+ @Override
+ public Optional simplify(String message) {
+ boolean matches = message.endsWith("expecting {'throws', '{'}");
+ matches = matches || message.endsWith("expecting {'throws', '{', '[', ';'}");
+
+ if (!matches) {
+ return Optional.empty();
+ }
+
+ return Optional.of(new IssueMessageSimplification(
+ MessageSimplifierUtil.getLocalStr("editor.status.missing.left_curly_bracket")
+ ));
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..cd8c0f6229
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingCurlyMessageSimplifierStrategy.java
@@ -0,0 +1,41 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+
+/**
+ * Strategy to check that every open curly has a corresponding close curly.
+ */
+public class MissingCurlyMessageSimplifierStrategy
+ extends TokenPairTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getToken1() {
+ return "{";
+ }
+
+ @Override
+ public String getToken2() {
+ return "}";
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingDoubleQuoteMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingDoubleQuoteMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..3717eae1f9
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingDoubleQuoteMessageSimplifierStrategy.java
@@ -0,0 +1,43 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import java.util.Optional;
+
+
+/**
+ * Strategy to check that double quotes are balanced.
+ */
+public class MissingDoubleQuoteMessageSimplifierStrategy
+ extends EvenCountTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getToken() {
+ return "\"";
+ }
+
+ @Override
+ public Optional getFilter() {
+ return Optional.of("\\\"");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingGenericTypeMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingGenericTypeMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..332108d3f3
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingGenericTypeMessageSimplifierStrategy.java
@@ -0,0 +1,20 @@
+package processing.mode.java.preproc.issue.strategy;
+
+
+/**
+ * Missing type in a generic.
+ */
+public class MissingGenericTypeMessageSimplifierStrategy
+ extends RegexTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getRegexPattern() {
+ return "<>'?$";
+ }
+
+ @Override
+ public String getHintTemplate() {
+ return MessageSimplifierUtil.getLocalStr("editor.status.bad.generic");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingIdentifierMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingIdentifierMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..bd694e2e24
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingIdentifierMessageSimplifierStrategy.java
@@ -0,0 +1,50 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+
+
+/**
+ * Strategy to check for an error indicating that an identifier was expected but not given.
+ */
+public class MissingIdentifierMessageSimplifierStrategy
+ implements PreprocIssueMessageSimplifierStrategy {
+
+ @Override
+ public Optional simplify(String message) {
+ if (message.toLowerCase().contains("missing identifier at")) {
+ String newMessage = String.format(
+ MessageSimplifierUtil.getLocalStr("editor.status.missing.name"),
+ message.replace("missing Identifier at", "")
+ );
+ return Optional.of(
+ new IssueMessageSimplification(newMessage)
+ );
+ } else {
+ return Optional.empty();
+ }
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingMethodNameMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingMethodNameMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..9f76dbc9cc
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingMethodNameMessageSimplifierStrategy.java
@@ -0,0 +1,41 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+
+/**
+ * Strategy to check for a method declaration without a name or return type.
+ */
+public class MissingMethodNameMessageSimplifierStrategy
+ extends RegexTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getRegexPattern() {
+ return "[a-zA-Z0-9_]+\\s*\\(.*\\)\\s*\\{";
+ }
+
+ @Override
+ public String getHintTemplate() {
+ return MessageSimplifierUtil.getLocalStr("editor.status.missing.name");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingParenMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingParenMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..c3b24678be
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingParenMessageSimplifierStrategy.java
@@ -0,0 +1,41 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+
+/**
+ * Strategy to check for an opening parentheses without a close parantheses.
+ */
+public class MissingParenMessageSimplifierStrategy
+ extends TokenPairTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getToken1() {
+ return "(";
+ }
+
+ @Override
+ public String getToken2() {
+ return ")";
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingSingleQuoteMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingSingleQuoteMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..fd2c1c2c8b
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingSingleQuoteMessageSimplifierStrategy.java
@@ -0,0 +1,43 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import java.util.Optional;
+
+
+/**
+ * Strategy to check for an open single quote without a corresponding close single quote.
+ */
+public class MissingSingleQuoteMessageSimplifierStrategy
+ extends EvenCountTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getToken() {
+ return "\'";
+ }
+
+ @Override
+ public Optional getFilter() {
+ return Optional.of("\\'");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/MissingVariableNameMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/MissingVariableNameMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..a3bb3fde3f
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/MissingVariableNameMessageSimplifierStrategy.java
@@ -0,0 +1,42 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.app.Language;
+
+/**
+ * Strategy that checks for a variable decalaration missing its name or its type.
+ */
+public class MissingVariableNameMessageSimplifierStrategy
+ extends RegexTemplateMessageSimplifierStrategy {
+
+ @Override
+ public String getRegexPattern() {
+ return "[a-zA-Z_]+[0-9a-zA-Z_]*\\s*(=[^\n\\n;]*)?;'?$";
+ }
+
+ @Override
+ public String getHintTemplate() {
+ return MessageSimplifierUtil.getLocalStr("editor.status.missing.type");
+ }
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/PreprocIssueMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/PreprocIssueMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..cf0c6231f1
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/PreprocIssueMessageSimplifierStrategy.java
@@ -0,0 +1,43 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+
+
+/**
+ * Interface for strategies that improve preprocess error messages before showing them to the user.
+ */
+public interface PreprocIssueMessageSimplifierStrategy {
+
+ /**
+ * Attempt to simplify an error message.
+ *
+ * @param message The message to be simplified.
+ * @return An optional with an improved message or an empty optional if no improvements could be
+ * made by this strategy.
+ */
+ Optional simplify(String message);
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/RegexTemplateMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/RegexTemplateMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..5dd4d7c2ae
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/RegexTemplateMessageSimplifierStrategy.java
@@ -0,0 +1,92 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+
+/**
+ * Strategy that cleans up errors based on a regex matching the error message.
+ */
+public abstract class RegexTemplateMessageSimplifierStrategy
+ implements PreprocIssueMessageSimplifierStrategy {
+
+ private Pattern pattern;
+
+ /**
+ * Create a new instance of this strategy.
+ */
+ public RegexTemplateMessageSimplifierStrategy() {
+ pattern = Pattern.compile(getRegexPattern());
+ }
+
+ @Override
+ public Optional simplify(String message) {
+ if (pattern.matcher(message).find()) {
+ String newMessage = String.format(
+ getHintTemplate(),
+ MessageSimplifierUtil.getOffendingArea(message)
+ );
+
+ return Optional.of(
+ new IssueMessageSimplification(newMessage, getAttributeToPrior())
+ );
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Determine if this issue should be attributed to the prior token.
+ *
+ * @return True if should be attributed to prior token. False otherwise.
+ */
+ public boolean getAttributeToPrior() {
+ return false;
+ }
+
+
+ /**
+ * Get the regex that should be matched against the error message for this strategy to apply.
+ *
+ * @return The regex that should be matched in order to activate this strategy.
+ */
+ public abstract String getRegexPattern();
+
+ /**
+ * Get the hint template for this strategy.
+ *
+ *
+ * Get a template string with a "%s" where the "offending snippet of code" can be inserted where
+ * the resulting rendered template can be used as an error hint for the user. For example,
+ * "Invalid identifier near %s" may be rendered to the user like "Syntax error. Hint: Invalid
+ * identifier near ,1a);" for example.
+ *
+ *
+ * @return The rendered hint template.
+ */
+ public abstract String getHintTemplate();
+
+}
diff --git a/java/src/processing/mode/java/preproc/issue/strategy/TokenPairTemplateMessageSimplifierStrategy.java b/java/src/processing/mode/java/preproc/issue/strategy/TokenPairTemplateMessageSimplifierStrategy.java
new file mode 100644
index 0000000000..29773a53e8
--- /dev/null
+++ b/java/src/processing/mode/java/preproc/issue/strategy/TokenPairTemplateMessageSimplifierStrategy.java
@@ -0,0 +1,84 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+Part of the Processing project - http://processing.org
+
+Copyright (c) 2012-19 The Processing Foundation
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.mode.java.preproc.issue.strategy;
+
+import processing.app.Language;
+import processing.mode.java.preproc.issue.IssueMessageSimplification;
+import processing.mode.java.preproc.code.SyntaxUtil;
+
+import java.util.Optional;
+
+
+/**
+ * Template class for checking that two tokens appear in pairs.
+ *
+ *
+ * Template class for message simplification strategies that check for an equal number of
+ * occurrences for two characters like "(" and ")".
+ *