Move to ANTLR 4 with Java 11 lang features and localization. (#5)

* Move to ANTLR 4 with Java 11 lang features and localization.

Introduces ANTLR4 and Java 8 language feature support within IDE while also adding additional hooks for localization of syntax error messages, addressing https://github.com/processing/processing/issues/3054 and https://github.com/processing/processing/issues/3055.

The PR is broadly a continuation of https://github.com/processing/processing/issues/3055, bringing it up to speed with the latest Processing master plus the changes introduced at https://github.com/processing/processing/pull/5753. **Requires https://github.com/processing/processing/pull/5753 as pre-requisite.** This introduces a number of edits beyond https://github.com/processing/processing/issues/3055 beyond compatibility with current Processing master and https://github.com/processing/processing/pull/5753 including:

 - Update to the grammar itself
 - Change ANTLR listeners to emit `TextTransform.Edit` to unify JDT-based `PreprocessingService` and `JavaBuild`, removing code with duplicate purpose.
 - Introduction of syntax error rewriting with support for localization.
 - Addition of complete localized strings set for English and Spanish.
 - Addition of partial localized strings set for other languages.
 - Refactor of ANTLR-related code for testability and readability
 - Expansion of tests including full parse tests for new Java features (type inference, lambdas).

* Ask travis for ant upgrade prior to run.

* Ask Travis for java11 update.

* Add openjdk ppa

* Travis no confirmation on add ppa.

* Force newer ant on travis.

* Swtich ant download to www-us mirror.

* Switch ant to 1.10.7

* Start x for unit tests in travis.

* More complete start x in travis.

* Revert x in travis.

* Try x in services.
This commit is contained in:
A Samuel Pottinger
2019-10-05 23:34:38 -07:00
committed by GitHub
parent 00dd2803f0
commit ee299ef935
254 changed files with 9327 additions and 6468 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -854,7 +854,17 @@ public class AutoFormat implements Formatter {
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
}
}

View File

@@ -62,14 +62,17 @@ public class Compiler {
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
};

View File

@@ -24,10 +24,8 @@ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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.DefaultLogger;
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 class JavaBuild {
/**
* 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 class JavaBuild {
* 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 class JavaBuild {
StringBuilder bigCode = new StringBuilder();
int bigCount = 0;
List<Integer> 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();
}
}
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 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]);
// // 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 =
// 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);
}
}
}
/* 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());
result = preprocessor.write(
stream,
bigCode.toString(),
codeFolderPackages
);
} finally {
stream.close();
}
} 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);
}
} 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 class JavaBuild {
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 class JavaBuild {
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 class JavaBuild {
//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 class JavaBuild {
} 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 class JavaBuild {
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 class JavaBuild {
} 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 class JavaBuild {
* 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 class JavaBuild {
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 class JavaBuild {
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 @@ public class JavaBuild {
// 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");
}

View File

@@ -2296,16 +2296,19 @@ public class JavaEditor extends Editor {
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 class JavaEditor extends Editor {
@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 @@ public class JavaEditor extends Editor {
}
}
// 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

View File

@@ -240,7 +240,7 @@ public class LineID implements DocumentListener {
* 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);

View File

@@ -35,7 +35,7 @@ import processing.core.PApplet;
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

View File

@@ -84,14 +84,22 @@ class ErrorChecker {
private void handleSketchProblems(PreprocessedSketch ps) {
Map<String, String[]> suggCache =
JavaMode.importSuggestEnabled ? new HashMap<>() : Collections.emptyMap();
final List<Problem> 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<JavaProblem> curlyQuoteProblems = checkForCurlyQuotes(ps);
problems.addAll(curlyQuoteProblems);
}
@@ -188,6 +196,10 @@ class ErrorChecker {
Pattern.compile("([“”‘’])", Pattern.UNICODE_CHARACTER_CLASS);
static private List<JavaProblem> checkForCurlyQuotes(PreprocessedSketch ps) {
if (ps.compilationUnit == null) {
return new ArrayList<>();
}
List<JavaProblem> problems = new ArrayList<>(0);
// Go through the scrubbed code and look for curly quotes (they should not be any)

View File

@@ -82,7 +82,7 @@ public class JavaProblem implements Problem {
} 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);
}

View File

@@ -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<String, String> 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<String, String> 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<String, String> COMPILER_OPTIONS;
static {
Map<String, String> 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);
}
}

View File

@@ -12,6 +12,7 @@ import java.util.Arrays;
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<ImportStatement> programImports;
public final List<ImportStatement> coreAndDefaultImports;
public final List<ImportStatement> codeFolderImports;
public final List<Problem> otherProblems;
/// JAVA -> SKETCH -----------------------------------------------------------
@@ -235,6 +235,7 @@ public class PreprocessedSketch {
public final List<ImportStatement> programImports = new ArrayList<>();
public final List<ImportStatement> coreAndDefaultImports = new ArrayList<>();
public final List<ImportStatement> codeFolderImports = new ArrayList<>();
public final List<Problem> otherProblems = new ArrayList<>();
public PreprocessedSketch build() {
return new PreprocessedSketch(this);
@@ -267,6 +268,8 @@ public class PreprocessedSketch {
hasSyntaxErrors = b.hasSyntaxErrors;
hasCompilationErrors = b.hasCompilationErrors;
otherProblems = b.otherProblems;
programImports = Collections.unmodifiableList(b.programImports);
coreAndDefaultImports = Collections.unmodifiableList(b.coreAndDefaultImports);
codeFolderImports = Collections.unmodifiableList(b.codeFolderImports);

View File

@@ -21,6 +21,7 @@ along with this program; if not, write to the Free Software Foundation, Inc.
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 java.util.stream.StreamSupport;
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.
*
* <p>
* 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}.
* </p>
*/
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<Boolean> 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 class PreprocessingService {
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 @@ public class PreprocessingService {
// 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 @@ public class PreprocessingService {
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 class PreprocessingService {
}
}
/**
* 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<PreprocessedSketch> callback) {
synchronized (requestLock) {
lastCallback = preprocessingTask
@@ -191,17 +223,41 @@ public class PreprocessingService {
}
}
/**
* Register a callback to be fired when preprocessing is complete if the service is still running.
*
* <p>
* 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.
* </p>
*
* @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<PreprocessedSketch> callback) {
if (!isEnabled) return;
registerCallback(callback);
}
/**
* Wait for preprocessing to complete.
*
* <p>
* 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.
* </p>
*
* @param callback
*/
public void whenDoneBlocking(Consumer<PreprocessedSketch> 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 class PreprocessingService {
private Set<Consumer<PreprocessedSketch>> 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<PreprocessedSketch> listener) {
if (listener != null) listeners.add(listener);
}
/**
* Remove a consumer previously registered.
*
* <p>
* Remove a consumer previously registered that was receiving {PreprocessedSketch}es produced from
* this service.
* </p>
*
* @param listener The listener to remove.
*/
public void unregisterListener(Consumer<PreprocessedSketch> listener) {
listeners.remove(listener);
}
/**
* Inform consumers waiting for {PreprocessedSketch}es.
*
* <p>
* Inform all consumers registered for receiving ongoing {PreprocessedSketch}es produced from
* this service.
* </p>
*
* @param ps The sketch to be sent out to consumers.
*/
private void fireListeners(PreprocessedSketch ps) {
for (Consumer<PreprocessedSketch> listener : listeners) {
try {
@@ -239,6 +317,19 @@ public class PreprocessingService {
/// --------------------------------------------------------------------------
/**
* Transform and attempt compilation of a sketch.
*
* <p>
* 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.
* </p>
*
* @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 @@ public class PreprocessingService {
StringBuilder workBuffer = new StringBuilder();
// Combine code into one buffer
int numLines = 1;
IntList tabStartsList = new IntList();
List<Integer> 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 @@ public class PreprocessingService {
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 @@ public class PreprocessingService {
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 @@ public class PreprocessingService {
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 @@ public class PreprocessingService {
// 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<IProblem> bindingsProblems = Arrays.asList(bindingsCU.getProblems());
@@ -390,21 +519,31 @@ public class PreprocessingService {
private List<ImportStatement> 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<ImportStatement> buildCoreAndDefaultImports(PdePreprocessor p) {
List<ImportStatement> 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<ImportStatement> buildCodeFolderImports(Sketch sketch) {
if (sketch.hasCodeFolder()) {
File codeFolder = sketch.getCodeFolder();
@@ -417,7 +556,14 @@ public class PreprocessingService {
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<ImportStatement> prevImports,
List<ImportStatement> imports) {
if (imports.size() != prevImports.size()) {
@@ -442,80 +588,11 @@ public class PreprocessingService {
/// --------------------------------------------------------------------------
private static CompilationUnit makeAST(ASTParser parser,
char[] source,
Map<String, String> 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<String, String> 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<String, String> COMPILER_OPTIONS;
static {
Map<String, String> 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) {

View File

@@ -160,23 +160,23 @@ public class TextTransform {
}
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);
}

View File

@@ -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<Integer> 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<Integer> 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
);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*
* <p>
* 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.
* </p>
*/
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<Integer> 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);
}
}

View File

@@ -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
;

View File

@@ -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
| <assoc=right> 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? ')'
;

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/processing-app/test/src/test/processing/mode/java/ParserTests.java"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[run]" value="org.eclipse.jdt.junit.launchconfig"/>
</mapAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="test.processing.mode.java.ParserTests"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="processing-app"/>
</launchConfiguration>

View File

@@ -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 <atripp@comcast.net>.
* 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 &lt;jdf@pobox.com&gt;
*/
@SuppressWarnings("serial")
public class PdeEmitter implements PdeTokenTypes {
private final PdePreprocessor pdePreprocessor;
private final PrintWriter out;
private final PrintStream debug = System.err;
private final Stack<AST> stack = new Stack<AST>();
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();
}
}

View File

@@ -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);
}

View File

@@ -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.
*
* <p>
* 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}.
* </p>
*/
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<String> coreImports = new ArrayList<>();
private ArrayList<String> defaultImports = new ArrayList<>();
private ArrayList<String> codeFolderImports = new ArrayList<>();
private ArrayList<String> foundImports = new ArrayList<>();
private ArrayList<TextTransform.Edit> 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<PdeParseTreeErrorListener> 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<String> 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<String> coreImports) {
this.coreImports.clear();
this.coreImports.addAll(coreImports);
}
/**
* Indicate list of default convenience imports.
*
* <p>
* Indicate list of imports that are not required for sketch operation but included for the
* user's convenience regardless.
* </p>
*
* @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.
*
* <p>
* Indicate list of imports that are not required for sketch operation but included for the
* user's convenience regardless.
* </p>
*
* @param defaultImports The list of imports to include for user convenience.
*/
public void setDefaultImports(List<String> 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<String> allImports = new ArrayList<>();
allImports.addAll(coreImports);
allImports.addAll(defaultImports);
allImports.addAll(codeFolderImports);
allImports.addAll(foundImports);
List<TextTransform.Edit> 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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* Endpoint for ANTLR to call after parsing a method declaration, making any method "public"
* that has:
*
* <ul>
* <li>no other access modifier</li>
* <li>return type "void"</li>
* <li>is either in the context of the sketch class</li>
* <li>is in the context of a class definition that extends PApplet</li>
* </ul>
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* Endpoint for ANTLR to call after parsing a color primitive token, fixing "color type" to be
* "int" as part of the processing API.
* </p>
*
* @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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 &lt;jdf@pobox.com&gt;
*
* Result of sketch Preprocessing.
*/
public class PreprocessorResult {
public final int headerOffset;
public final String className;
public final List<String> extraImports;
public final PdePreprocessor.Mode programType;
public PreprocessorResult(PdePreprocessor.Mode programType,
int headerOffset, String className,
final List<String> extraImports) throws SketchException {
if (className == null) {
throw new SketchException("Could not find main class");
}
this.headerOffset = headerOffset;
this.className = className;
this.extraImports = Collections.unmodifiableList(new ArrayList<String>(extraImports));
this.programType = programType;
private final int headerOffset;
private final String className;
private final List<String> importStatementsStr;
private final List<ImportStatement> importStatements;
private final PdePreprocessor.Mode programType;
private final List<TextTransform.Edit> edits;
private final List<PdePreprocessIssue> 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<PdePreprocessIssue> 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<String> newExtraImports, List<TextTransform.Edit> newEdits,
String newSketchWidth, String newSketchHeight) {
if (newClassName == null) {
throw new RuntimeException("Could not find main class");
}
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<PdePreprocessIssue> 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<PdePreprocessIssue> 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<String> 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<TextTransform.Edit> getEdits() {
return edits;
}
/**
* Get the found import statements as {ImportStatement}s.
*
* @return The import statements found for the user.
*/
public List<ImportStatement> 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;
}
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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 &lt;jdf@pobox.com&gt;
*
*/
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()];
}
}

View File

@@ -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.
*
* <p>
* 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.
* </p>
*/
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
);
}
}

View File

@@ -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"
};
}
}

View File

@@ -0,0 +1,91 @@
package processing.mode.java.preproc.code;
import org.antlr.v4.runtime.TokenStreamRewriter;
/**
* Decorator around a {TokenStreamRewriter}.
*
* <p>
* 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.
* </p>
*/
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"));
}
}

View File

@@ -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<String> coreImports;
private final List<String> defaultImports;
private final List<String> codeFolderImports;
private final List<String> foundImports;
private final Optional<String> sketchWidth;
private final Optional<String> sketchHeight;
private final Optional<String> 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<String> newCoreImports,
List<String> newDefaultImports, List<String> newCodeFolderImports,
List<String> newFoundImports, Optional<String> newSketchWidth,
Optional<String> newSketchHeight, Optional<String> 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<String> getCoreImports() {
return coreImports;
}
/**
* Get the imports added for user convenience.
*
* @return The set of imports included for user convenience.
*/
public List<String> 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<String> getCodeFolderImports() {
return codeFolderImports;
}
/**
* Get the users included by the user.
*
* @return The imports included by the user.
*/
public List<String> 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<String> 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<String> 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<String> 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;
}
}

View File

@@ -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<String> sketchName;
private Optional<Boolean> isTesting;
private Optional<TokenStreamRewriter> rewriter;
private Optional<PdePreprocessor.Mode> mode;
private Optional<Boolean> foundMain;
private Optional<Integer> lineOffset;
private Optional<String> sketchWidth;
private Optional<String> sketchHeight;
private Optional<String> sketchRenderer;
private Optional<Boolean> isSizeValidInGlobal;
private Optional<Boolean> isSizeFullscreen;
private ArrayList<String> coreImports;
private ArrayList<String> defaultImports;
private ArrayList<String> codeFolderImports;
private ArrayList<String> 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<String> 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<String> 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<String> newImports) {
codeFolderImports.addAll(newImports);
}
/**
* Add imports included manually by the user.
*
* @param newImports The imports included by the user.
*/
public void addFoundImports(Collection<String> 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()
);
}
}

View File

@@ -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<TextTransform.Edit> 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<TextTransform.Edit> 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<TextTransform.Edit> getEdits() {
return edits;
}
}

View File

@@ -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<TextTransform.Edit> 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<TextTransform.Edit> getEdits() {
return edits;
}
/**
* Build a new rewrite result.
*
* @return Immutable rewrite result.
*/
public RewriteResult build() {
return new RewriteResult(lineOffset, edits);
}
}

View File

@@ -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<String> 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("}");
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
*
* <p>
* 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.
* </p>
*/
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<Integer> 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);
}
}

View File

@@ -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.
*
* <p>
* 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.
* </p>
*/
public class IssueMessageSimplification {
private final String message;
private final boolean attributeToPriorToken;
/**
* Create a new issue message simplification.
*
* <p>
* 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).
* </p>
*
* @param newMessage The message to show to the user.
*/
public IssueMessageSimplification(String newMessage) {
message = newMessage;
attributeToPriorToken = false;
}
/**
* Create a new issue message simplification.
*
* <p>
* 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".
* </p>
*
* @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;
}
}

View File

@@ -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.
*
* <p>
* 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.
* </p>
*/
public class PdeIssueEmitter extends BaseErrorListener {
private final PdePreprocessIssueListener listener;
private final Optional<SourceEmitter> sourceMaybe;
/**
* Create a new issue emitter.
*
* <p>
* 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.
* </p>
*
* @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()
));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,9 @@
package processing.mode.java.preproc.issue;
import processing.mode.java.preproc.issue.PdePreprocessIssue;
public interface PdePreprocessIssueListener {
void onIssue(PdePreprocessIssue issue);
}

View File

@@ -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.
*
* <p>
* 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.
* </p>
*
* <p>
* 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.
* </p>
*/
public class PreprocessIssueMessageSimplifierFacade {
private static AtomicReference<PreprocessIssueMessageSimplifierFacade> instance = new AtomicReference<>();
private List<PreprocIssueMessageSimplifierStrategy> 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<IssueMessageSimplification> matching = strategies.stream()
.map((x) -> x.simplify(originalMessage))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
return matching.orElse(new IssueMessageSimplification(originalMessage));
}
}

View File

@@ -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.
*
* <p>
* 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.
* </p>
*
* <p>
* 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.
* </p>
*
* <p>
* 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.
* </p>
*/
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<Integer> 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<Integer> 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.
*
* <p>
* 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
* <i>backwards</i> from the starting position in code. This is because it is looking for the
* first non-skip token immediately <i>preceding</i> a position in source.
* </p>
*
* @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.
*
* <p>
* 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).
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* 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.
* </p>
*
* @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.
*
* <p>
* 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).
* </p>
*
* @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();
}
}

View File

@@ -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");
}
}

View File

@@ -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"
);
}
}

View File

@@ -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");
}
}

View File

@@ -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<DefaultErrorLocalStrSet> instance = new AtomicReference<>();
private final Map<String, String> 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<String> get(String key) {
return Optional.ofNullable(localizations.getOrDefault(key, null));
}
}

View File

@@ -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<IssueMessageSimplification> 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)
);
}
}
}

View File

@@ -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.
*
* <p>
* Strategy to ensure that there are an even number of tokens like even number of double quotes
* for example.
* </p>
*/
public abstract class EvenCountTemplateMessageSimplifierStrategy
implements PreprocIssueMessageSimplifierStrategy {
@Override
public Optional<IssueMessageSimplification> 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<String> getFilter() {
return Optional.empty();
}
}

View File

@@ -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<IssueMessageSimplification> 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();
}
}
}

View File

@@ -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<IssueMessageSimplification> 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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<IssueMessageSimplification> 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();
}
}
}

View File

@@ -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 ">";
}
}

View File

@@ -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");
}
}

View File

@@ -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<IssueMessageSimplification> simplify(String message) {
if (!message.equals("missing ';' at '{'")) {
return Optional.empty();
}
return Optional.of(new IssueMessageSimplification(
MessageSimplifierUtil.getLocalStr("editor.status.missing.right_curly_bracket")
));
}
}

View File

@@ -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<IssueMessageSimplification> 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")
));
}
}

View File

@@ -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 "}";
}
}

View File

@@ -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<String> getFilter() {
return Optional.of("\\\"");
}
}

View File

@@ -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");
}
}

View File

@@ -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<IssueMessageSimplification> 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();
}
}
}

View File

@@ -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");
}
}

View File

@@ -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 ")";
}
}

View File

@@ -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<String> getFilter() {
return Optional.of("\\'");
}
}

View File

@@ -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");
}
}

View File

@@ -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<IssueMessageSimplification> simplify(String message);
}

View File

@@ -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<IssueMessageSimplification> 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.
*
* <p>
* 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.
* </p>
*
* @return The rendered hint template.
*/
public abstract String getHintTemplate();
}

View File

@@ -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.
*
* <p>
* Template class for message simplification strategies that check for an equal number of
* occurrences for two characters like "(" and ")".
* </p>
*/
public abstract class TokenPairTemplateMessageSimplifierStrategy
implements PreprocIssueMessageSimplifierStrategy {
@Override
public Optional<IssueMessageSimplification> simplify(String message) {
String messageContent = MessageSimplifierUtil.getOffendingArea(message);
int count1 = SyntaxUtil.getCount(messageContent, getToken1());
int count2 = SyntaxUtil.getCount(messageContent, getToken2());
if (count1 == count2) {
return Optional.empty();
}
String missingToken;
if (count1 < count2) {
missingToken = getToken1();
} else {
missingToken = getToken2();
}
String newMessage = String.format(
MessageSimplifierUtil.getLocalStr("editor.status.missing.default")
.replace("%c", "%s"), missingToken);
return Optional.of(
new IssueMessageSimplification(newMessage)
);
}
/**
* Get the first token in the pair.
*
* @return The first token whose occurrences should be counted.
*/
public abstract String getToken1();
/**
* Get the second token in the pair.
*
* @return The second token whose occurrences should be counted.
*/
public abstract String getToken2();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,394 +0,0 @@
/* -*- mode: antlr; c-basic-offset: 4; indent-tabs-mode: nil -*- */
header {
package processing.mode.java.preproc;
}
class PdeRecognizer extends JavaRecognizer;
options {
importVocab = Java;
exportVocab = PdePartial;
//codeGenMakeSwitchThreshold=10; // this is set high for debugging
//codeGenBitsetTestThreshold=10; // this is set high for debugging
// developers may to want to set this to true for better
// debugging messages, however, doing so disables highlighting errors
// in the editor.
defaultErrorHandler = false; //true;
}
tokens {
CONSTRUCTOR_CAST; EMPTY_FIELD;
}
{
// this clause copied from java15.g! ANTLR does not copy this
// section from the super grammar.
/**
* Counts the number of LT seen in the typeArguments production.
* It is used in semantic predicates to ensure we have seen
* enough closing '>' characters; which actually may have been
* either GT, SR or BSR tokens.
*/
private int ltCounter = 0;
private PdePreprocessor pp;
public PdeRecognizer(final PdePreprocessor pp, final TokenStream ts) {
this(ts);
this.pp = pp;
}
private void mixed() throws RecognitionException, TokenStreamException {
throw new RecognitionException("It looks like you're mixing \"active\" and \"static\" modes.",
getFilename(), LT(1).getLine(), LT(1).getColumn());
}
}
pdeProgram
:
// Some programs can be equally well interpreted as STATIC or ACTIVE;
// this forces the parser to prefer the STATIC interpretation.
(staticProgram) => staticProgram
{ pp.setMode(PdePreprocessor.Mode.STATIC); }
| (activeProgram) => activeProgram
{ pp.setMode(PdePreprocessor.Mode.ACTIVE); }
| staticProgram
{ pp.setMode(PdePreprocessor.Mode.STATIC); }
;
// advanced mode is really just a normal java file
javaProgram
: compilationUnit
;
activeProgram
: (
(IDENT LPAREN) => IDENT LPAREN { mixed(); }
| possiblyEmptyField
)+ EOF!
;
staticProgram
: (
statement
)* EOF!
;
// copy of the java.g rule with WEBCOLOR_LITERAL added
constant
: NUM_INT
| CHAR_LITERAL
| STRING_LITERAL
| NUM_FLOAT
| NUM_LONG
| NUM_DOUBLE
| webcolor_literal
;
// fix bug http://dev.processing.org/bugs/show_bug.cgi?id=1519
// by altering a syntactic predicate whose sole purpose is to
// emit a useless error with no line numbers.
// These are from Java15.g, with a few lines edited to make nice errors.
// Type arguments to a class or interface type
typeArguments
{int currentLtLevel = 0;}
:
{currentLtLevel = ltCounter;}
LT! {ltCounter++;}
typeArgument
(options{greedy=true;}: // match as many as possible
{if (! (inputState.guessing !=0 || ltCounter == currentLtLevel + 1)) {
throw new RecognitionException("Maybe too many > characters?",
getFilename(), LT(1).getLine(), LT(1).getColumn());
}}
COMMA! typeArgument
)*
( // turn warning off since Antlr generates the right code,
// plus we have our semantic predicate below
options{generateAmbigWarnings=false;}:
typeArgumentsOrParametersEnd
)?
// make sure we have gobbled up enough '>' characters
// if we are at the "top level" of nested typeArgument productions
{if (! ((currentLtLevel != 0) || ltCounter == currentLtLevel)) {
throw new RecognitionException("Maybe too many > characters?",
getFilename(), LT(1).getLine(), LT(1).getColumn());
}}
{#typeArguments = #(#[TYPE_ARGUMENTS, "TYPE_ARGUMENTS"], #typeArguments);}
;
typeParameters
{int currentLtLevel = 0;}
:
{currentLtLevel = ltCounter;}
LT! {ltCounter++;}
typeParameter (COMMA! typeParameter)*
(typeArgumentsOrParametersEnd)?
// make sure we have gobbled up enough '>' characters
// if we are at the "top level" of nested typeArgument productions
{if (! ((currentLtLevel != 0) || ltCounter == currentLtLevel)) {
throw new RecognitionException("Maybe too many > characters?",
getFilename(), LT(1).getLine(), LT(1).getColumn());
}}
{#typeParameters = #(#[TYPE_PARAMETERS, "TYPE_PARAMETERS"], #typeParameters);}
;
// this gobbles up *some* amount of '>' characters, and counts how many
// it gobbled.
protected typeArgumentsOrParametersEnd
: GT! {ltCounter-=1;}
| SR! {ltCounter-=2;}
| BSR! {ltCounter-=3;}
;
// of the form #cc008f in PDE
webcolor_literal
: w:WEBCOLOR_LITERAL
{ if (! (processing.app.Preferences.getBoolean("preproc.web_colors")
&&
w.getText().length() == 6)) {
throw new RecognitionException("Web colors must be exactly 6 hex digits. This looks like " + w.getText().length() + ".",
getFilename(), LT(1).getLine(), LT(1).getColumn());
}} // must be exactly 6 hex digits
;
// copy of the java.g builtInType rule
builtInConsCastType
: "void"
| "boolean"
| "byte"
| "char"
| "short"
| "int"
| "float"
| "long"
| "double"
;
// our types include the java types and "color". this is separated into two
// rules so that constructor casts can just use the original typelist, since
// we don't want to support the color type as a constructor cast.
//
builtInType
: builtInConsCastType
| "color" // aliased to an int in PDE
{ processing.app.Preferences.getBoolean("preproc.color_datatype") }?
;
// constructor style casts.
constructorCast!
: t:consCastTypeSpec[true]
LPAREN!
e:expression
RPAREN!
// if this is a string literal, make sure the type we're trying to cast
// to is one of the supported ones
//
{ (#e == null) ||
( (#e.getType() != STRING_LITERAL) ||
( #t.getType() == LITERAL_boolean ||
#t.getType() == LITERAL_double ||
#t.getType() == LITERAL_float ||
#t.getType() == LITERAL_int ||
#t.getType() == LITERAL_long ||
#t.getType() == LITERAL_short )) }?
// create the node
//
{#constructorCast = #(#[CONSTRUCTOR_CAST,"CONSTRUCTOR_CAST"], t, e);}
;
// A list of types that be used as the destination type in a constructor-style
// cast. Ideally, this would include all class types, not just "String".
// Unfortunately, it's not possible to tell whether Foo(5) is supposed to be
// a method call or a constructor cast without have a table of all valid
// types or methods, which requires semantic analysis (eg processing of import
// statements). So we accept the set of built-in types plus "String".
//
consCastTypeSpec[boolean addImagNode]
// : stringTypeSpec[addImagNode]
// | builtInConsCastTypeSpec[addImagNode]
: builtInConsCastTypeSpec[addImagNode]
// trying to remove String() cast [fry]
;
//stringTypeSpec[boolean addImagNode]
// : id:IDENT { #id.getText().equals("String") }?
// {
// if ( addImagNode ) {
// #stringTypeSpec = #(#[TYPE,"TYPE"],
// #stringTypeSpec);
// }
// }
// ;
builtInConsCastTypeSpec[boolean addImagNode]
: builtInConsCastType
{
if ( addImagNode ) {
#builtInConsCastTypeSpec = #(#[TYPE,"TYPE"],
#builtInConsCastTypeSpec);
}
}
;
// Since "color" tokens are lexed as LITERAL_color now, we need to have a rule
// that can generate a method call from an expression that starts with this
// token
//
colorMethodCall
: c:"color" {#c.setType(IDENT);} // this would default to LITERAL_color
lp:LPAREN^ {#lp.setType(METHOD_CALL);}
argList
RPAREN!
;
// copy of the java.g rule with added constructorCast and colorMethodCall
// alternatives
primaryExpression
: (consCastTypeSpec[false] LPAREN) => constructorCast
{ processing.app.Preferences.getBoolean("preproc.enhanced_casting") }?
| identPrimary ( options {greedy=true;} : DOT^ "class" )?
| constant
| "true"
| "false"
| "null"
| newExpression
| "this"
| "super"
| LPAREN! assignmentExpression RPAREN!
| colorMethodCall
// look for int.class and int[].class
| builtInType
( lbt:LBRACK^ {#lbt.setType(ARRAY_DECLARATOR);} RBRACK! )*
DOT^ "class"
;
// the below variable rule hacks are needed so that it's possible for the
// emitter to correctly output variable declarations of the form "float a, b"
// from the AST. This means that our AST has a somewhat different form in
// these rules than the java one does, and this new form may have its own
// semantic issues. But it seems to fix the comma declaration issues.
//
variableDefinitions![AST mods, AST t]
: vd:variableDeclarator[getASTFactory().dupTree(mods),
getASTFactory().dupTree(t)]
{#variableDefinitions = #(#[VARIABLE_DEF,"VARIABLE_DEF"], mods,
t, vd);}
;
variableDeclarator[AST mods, AST t]
: ( id:IDENT (lb:LBRACK^ {#lb.setType(ARRAY_DECLARATOR);} RBRACK!)*
v:varInitializer (COMMA!)? )+
;
// java.g builds syntax trees with an inconsistent structure. override one of
// the rules there to fix this.
//
explicitConstructorInvocation!
: (typeArguments)?
t:"this" LPAREN a1:argList RPAREN SEMI
{#explicitConstructorInvocation = #(#[CTOR_CALL, "CTOR_CALL"],
#t, #a1);}
| s:"super" LPAREN a2:argList RPAREN SEMI
{#explicitConstructorInvocation = #(#[SUPER_CTOR_CALL,
"SUPER_CTOR_CALL"],
#s, #a2);}
;
// quick-n-dirty hack to the get the advanced class name. we should
// really be getting it from the AST and not forking this rule from
// the java.g copy at all. Since this is a recursive descent parser, we get
// the last class name in the file so that we don't end up with the classname
// of an inner class. If there is more than one "outer" class in a file,
// this heuristic will fail.
//
classDefinition![AST modifiers]
: "class" i:IDENT
// it _might_ have type paramaters
(tp:typeParameters)?
// it _might_ have a superclass...
sc:superClassClause
// it might implement some interfaces...
ic:implementsClause
// now parse the body of the class
cb:classBlock
{#classDefinition = #(#[CLASS_DEF,"CLASS_DEF"],
modifiers,i,tp,sc,ic,cb);
pp.setAdvClassName(i.getText());}
;
possiblyEmptyField
: classField
| s:SEMI {#s.setType(EMPTY_FIELD);}
;
class PdeLexer extends JavaLexer;
options {
importVocab=PdePartial;
exportVocab=Pde;
}
// We need to preserve whitespace and commentary instead of ignoring
// like the supergrammar does. Otherwise Jikes won't be able to give
// us error messages that point to the equivalent PDE code.
// WS, SL_COMMENT, ML_COMMENT are copies of the original productions,
// but with the SKIP assigment removed.
WS : ( ' '
| '\t'
| '\f'
// handle newlines
| ( options {generateAmbigWarnings=false;}
: "\r\n" // Evil DOS
| '\r' // Macintosh
| '\n' // Unix (the right way)
)
{ newline(); }
)+
;
// Single-line comments
SL_COMMENT
: "//"
(~('\n'|'\r'))* ('\n'|'\r'('\n')?)
{newline();}
;
// multiple-line comments
ML_COMMENT
: "/*"
( /* '\r' '\n' can be matched in one alternative or by matching
'\r' in one iteration and '\n' in another. I am trying to
handle any flavor of newline that comes in, but the language
that allows both "\r\n" and "\r" and "\n" to all be valid
newline is ambiguous. Consequently, the resulting grammar
must be ambiguous. I'm shutting this warning off.
*/
options {
generateAmbigWarnings=false;
}
:
{ LA(2)!='/' }? '*'
| '\r' '\n' {newline();}
| '\r' {newline();}
| '\n' {newline();}
| ~('*'|'\n'|'\r')
)*
"*/"
;
WEBCOLOR_LITERAL
: '#'! (HEX_DIGIT)+
;