mirror of
https://github.com/processing/processing4.git
synced 2026-02-03 21:59:20 +01:00
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:
committed by
GitHub
parent
00dd2803f0
commit
ee299ef935
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
106
java/src/processing/mode/java/pdex/JdtCompilerUtil.java
Normal file
106
java/src/processing/mode/java/pdex/JdtCompilerUtil.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
89
java/src/processing/mode/java/pdex/util/ProblemFactory.java
Normal file
89
java/src/processing/mode/java/pdex/util/ProblemFactory.java
Normal 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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
75
java/src/processing/mode/java/pdex/util/SyntaxProblem.java
Normal file
75
java/src/processing/mode/java/pdex/util/SyntaxProblem.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
55
java/src/processing/mode/java/pdex/util/TabLine.java
Normal file
55
java/src/processing/mode/java/pdex/util/TabLine.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
38
java/src/processing/mode/java/pdex/util/TabLineFactory.java
Normal file
38
java/src/processing/mode/java/pdex/util/TabLineFactory.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
208
java/src/processing/mode/java/preproc/JavaLexer.g4
Normal file
208
java/src/processing/mode/java/preproc/JavaLexer.g4
Normal 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
|
||||
;
|
||||
631
java/src/processing/mode/java/preproc/JavaParser.g4
Normal file
631
java/src/processing/mode/java/preproc/JavaParser.g4
Normal 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? ')'
|
||||
;
|
||||
@@ -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>
|
||||
@@ -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 <jdf@pobox.com>
|
||||
*/
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class PdeEmitter implements PdeTokenTypes {
|
||||
private final PdePreprocessor pdePreprocessor;
|
||||
private final PrintWriter out;
|
||||
private final PrintStream debug = System.err;
|
||||
|
||||
private final Stack<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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
708
java/src/processing/mode/java/preproc/PdeParseTreeListener.java
Normal file
708
java/src/processing/mode/java/preproc/PdeParseTreeListener.java
Normal 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
@@ -1,32 +1,198 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-19 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import processing.mode.java.pdex.ImportStatement;
|
||||
import processing.mode.java.pdex.TextTransform;
|
||||
import processing.mode.java.preproc.issue.PdePreprocessIssue;
|
||||
|
||||
import processing.app.SketchException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*
|
||||
* Result of sketch Preprocessing.
|
||||
*/
|
||||
public class PreprocessorResult {
|
||||
public final int headerOffset;
|
||||
public final String className;
|
||||
public final List<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;
|
||||
}
|
||||
}
|
||||
|
||||
130
java/src/processing/mode/java/preproc/Processing.g4
Normal file
130
java/src/processing/mode/java/preproc/Processing.g4
Normal 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
|
||||
|
||||
37
java/src/processing/mode/java/preproc/SourceEmitter.java
Normal file
37
java/src/processing/mode/java/preproc/SourceEmitter.java
Normal 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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import antlr.collections.AST;
|
||||
import processing.mode.java.preproc.PdeTokenTypes;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*
|
||||
*/
|
||||
public class TokenUtil {
|
||||
private static final String[] tokenNames= new String[200];
|
||||
static {
|
||||
for (int i = 0; i < tokenNames.length; i++) {
|
||||
tokenNames[i] = "ERROR:" + i;
|
||||
}
|
||||
for (final Field f : PdeTokenTypes.class.getDeclaredFields()) {
|
||||
try {
|
||||
tokenNames[f.getInt(null)] = f.getName();
|
||||
} catch (Exception unexpected) {
|
||||
throw new RuntimeException(unexpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String nameOf(final AST node) {
|
||||
return tokenNames[node.getType()];
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
45
java/src/processing/mode/java/preproc/code/ImportUtil.java
Normal file
45
java/src/processing/mode/java/preproc/code/ImportUtil.java
Normal 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"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
228
java/src/processing/mode/java/preproc/code/RewriteParams.java
Normal file
228
java/src/processing/mode/java/preproc/code/RewriteParams.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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("}");
|
||||
}
|
||||
|
||||
}
|
||||
80
java/src/processing/mode/java/preproc/code/SyntaxUtil.java
Normal file
80
java/src/processing/mode/java/preproc/code/SyntaxUtil.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
107
java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java
Normal file
107
java/src/processing/mode/java/preproc/issue/PdeIssueEmitter.java
Normal 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()
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package processing.mode.java.preproc.issue;
|
||||
|
||||
import processing.mode.java.preproc.issue.PdePreprocessIssue;
|
||||
|
||||
public interface PdePreprocessIssueListener {
|
||||
|
||||
void onIssue(PdePreprocessIssue issue);
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ">";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 "}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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("\\\"");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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("\\'");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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
@@ -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)+
|
||||
;
|
||||
|
||||
Reference in New Issue
Block a user