ECS: New preprocessing pipeline (breaking)

Breaks ASTGenerator and offset mapping everywhere
This commit is contained in:
Jakub Valtar
2016-03-25 23:28:47 +01:00
parent ce7d93d711
commit 3ff7370841
6 changed files with 895 additions and 776 deletions

View File

@@ -106,11 +106,10 @@ import com.google.classpath.ClassPath;
import com.google.classpath.ClassPathFactory;
import com.google.classpath.RegExpResourceFilter;
@SuppressWarnings({ "unchecked" })
public class ASTGenerator {
public static final boolean SHOW_DEBUG_TREE = true;
public static final boolean SHOW_DEBUG_TREE = false;
protected final ErrorCheckerService errorCheckerService;
protected final JavaEditor editor;
@@ -143,7 +142,7 @@ public class ASTGenerator {
List<VariableDeclarationFragment> vdfs = null;
switch (node.getNodeType()) {
case ASTNode.TYPE_DECLARATION:
return new CompletionCandidate[] { new CompletionCandidate((TypeDeclaration) node) };
return new CompletionCandidate[]{new CompletionCandidate((TypeDeclaration) node)};
case ASTNode.METHOD_DECLARATION:
MethodDeclaration md = (MethodDeclaration) node;
@@ -155,12 +154,12 @@ public class ASTGenerator {
for (int i = 0; i < params.size(); i++) {
// cand[i + 1] = new CompletionCandidate(params.get(i).toString(), "", "",
// CompletionCandidate.LOCAL_VAR);
cand[i + 1] = new CompletionCandidate((SingleVariableDeclaration)params.get(i));
cand[i + 1] = new CompletionCandidate((SingleVariableDeclaration) params.get(i));
}
return cand;
case ASTNode.SINGLE_VARIABLE_DECLARATION:
return new CompletionCandidate[] { new CompletionCandidate((SingleVariableDeclaration)node) };
return new CompletionCandidate[]{new CompletionCandidate((SingleVariableDeclaration) node)};
case ASTNode.FIELD_DECLARATION:
vdfs = ((FieldDeclaration) node).fragments();
@@ -757,8 +756,8 @@ public class ASTGenerator {
}
log("Looking in the classloader for " + className);
ArrayList<ImportStatement> imports = errorCheckerService
.getProgramImports();
// TODO: get this from last code check result
List<ImportStatement> imports = Collections.emptyList(); //errorCheckerService.getProgramImports();
for (ImportStatement impS : imports) {
String temp = impS.getPackageName();
@@ -769,7 +768,7 @@ public class ASTGenerator {
continue;
}
} else { // case of class import: pkg.foo.MyClass
if (!impS.getImportedClassName().equals(className)) {
if (!impS.getClassName().equals(className)) {
continue;
}
}
@@ -781,7 +780,9 @@ public class ASTGenerator {
//log("Doesn't exist in imp package: " + impS.getImportName());
}
for (ImportStatement impS : errorCheckerService.codeFolderImports) {
// TODO: get this from last code check result
List<ImportStatement> codeFolderImports = Collections.emptyList();
for (ImportStatement impS : codeFolderImports) {
String temp = impS.getPackageName();
if (impS.isStarredImport()) { // case of starred import: pkg.foo.*
if (className.indexOf('.') == -1) {
@@ -790,7 +791,7 @@ public class ASTGenerator {
continue;
}
} else { // case of class import: pkg.foo.MyClass
if (!impS.getImportedClassName().equals(className)) {
if (!impS.getClassName().equals(className)) {
continue;
}
}
@@ -840,8 +841,10 @@ public class ASTGenerator {
Class<?> tehClass = null;
if (className != null) {
try {
tehClass = Class.forName(className, false,
errorCheckerService.getSketchClassLoader());
// TODO: get the class loader from the last code check result
/*tehClass = Class.forName(className, false,
errorCheckerService.getSketchClassLoader());*/
tehClass = Class.forName(className);
} catch (ClassNotFoundException e) {
//log("Doesn't exist in package: ");
}

View File

@@ -25,7 +25,6 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
@@ -42,11 +41,10 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.PlainDocument;
@@ -73,15 +71,14 @@ import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import processing.app.Library;
import processing.app.Messages;
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.ui.Editor;
import processing.app.ui.EditorStatus;
import processing.app.ui.ErrorTable;
import processing.core.PApplet;
import processing.data.StringList;
import processing.mode.java.JavaMode;
import processing.mode.java.JavaEditor;
@@ -104,23 +101,6 @@ public class ErrorCheckerService {
*/
private volatile boolean running;
//protected ErrorWindow errorWindow;
/**
* Class name of current sketch
*/
protected String className;
/**
* URLs of extra imports jar files stored here.
*/
protected URL[] classPath = {};
/**
* Class loader used by compiler check and ASTGenerator, based on classPath
*/
protected URLClassLoader classLoader = new URLClassLoader(classPath);
/**
* How many lines are present till the initial class declaration? In static
* mode, this would include imports, class declaration and setup
@@ -129,39 +109,6 @@ public class ErrorCheckerService {
*/
public int mainClassOffset;
/**
* If true, compilation checker will be reloaded with updated classpath
* items.
*/
protected boolean loadCompClass;
/**
* List of jar files to be present in compilation checker's classpath
*/
protected List<URL> classpathJars = new ArrayList<>();
/**
* Stores the current import statements in the program. Used to compare for
* changed import statements and update classpath if needed.
*/
protected ArrayList<ImportStatement> programImports = new ArrayList<>();
/**
* List of imports when sketch was last checked. Used for checking for
* changed imports
*/
protected ArrayList<ImportStatement> previousImports = new ArrayList<>();
/**
* List of import statements for any .jar files in the code folder.
*/
protected final ArrayList<ImportStatement> codeFolderImports = new ArrayList<>();
/**
* Teh Preprocessor
*/
protected final XQPreprocessor xqpreproc;
/**
* ASTGenerator for operations on AST
*/
@@ -170,15 +117,14 @@ public class ErrorCheckerService {
/**
* Regexp for import statements. (Used from Processing source)
*/
// TODO: merge this with SourceUtils one
public static final String IMPORT_REGEX =
"(?:^|;)\\s*(import\\s+)((?:static\\s+)?\\S+)(\\s*;)";
public ErrorCheckerService(JavaEditor editor) {
this.editor = editor;
xqpreproc = new XQPreprocessor(editor);
astGenerator = new ASTGenerator(editor, this);
loadCompClass = true;
}
@@ -211,19 +157,15 @@ public class ErrorCheckerService {
// This is when the loaded sketch already has syntax errors.
// Completion wouldn't be complete, but it'd be still something
// better than nothing
try {
{
final DefaultMutableTreeNode tree =
ASTGenerator.buildTree(lastCodeCheckResult.compilationUnit);
EventQueue.invokeAndWait(new Runnable() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
synchronized (astGenerator) {
astGenerator.updateAST(lastCodeCheckResult.compilationUnit, tree);
}
astGenerator.updateAST(lastCodeCheckResult.compilationUnit, tree);
}
});
} catch (InterruptedException | InvocationTargetException e) {
Messages.loge("exception during initial AST update", e);
}
while (running) {
@@ -291,9 +233,10 @@ public class ErrorCheckerService {
synchronized (astGenerator) {
astGenerator.getGui().disposeAllWindows();
}
classLoader = null;
Messages.loge("Thread stopped: " + editor.getSketch().getName());
// TODO: clear last result
running = false;
}
};
@@ -391,10 +334,76 @@ public class ErrorCheckerService {
CodeCheckResult result = new CodeCheckResult();
result.sourceCode = preprocessCode();
StringBuilder rawCode = new StringBuilder();
List<ImportStatement> coreAndDefaultImports = new ArrayList<>();
List<ImportStatement> codeFolderImports = new ArrayList<>();
List<ImportStatement> programImports = new ArrayList<>();
char[] sourceCodeArray = result.sourceCode.toCharArray();
Sketch sketch = editor.getSketch();
{ // Combine code into one buffer
for (SketchCode sc : sketch.getCode()) {
if (sc.isExtension("pde")) {
if (sketch.getCurrentCode().equals(sc)) {
try {
rawCode.append(sc.getDocumentText());
} catch (BadLocationException e) {
e.printStackTrace();
}
} else {
rawCode.append(sc.getProgram());
}
rawCode.append('\n');
}
}
}
String className = sketch.getName();
{ // Prepare core and default imports
PdePreprocessor p = editor.createPreprocessor(null);
String[] defaultImports = p.getDefaultImports();
String[] coreImports = p.getCoreImports();
for (String imp : coreImports) {
coreAndDefaultImports.add(ImportStatement.parse(imp));
}
for (String imp : defaultImports) {
coreAndDefaultImports.add(ImportStatement.parse(imp));
}
}
{ // Prepare code folder imports & add to classpath
if (sketch.hasCodeFolder()) {
File codeFolder = sketch.getCodeFolder();
String codeFolderClassPath = Util.contentsToClassPath(codeFolder);
StringList codeFolderPackages = Util.packageListFromClassPath(codeFolderClassPath);
for (String item : codeFolderPackages) {
codeFolderImports.add(ImportStatement.wholePackage(item));
}
}
}
SourceUtils.substituteUnicode(rawCode);
try {
SourceUtils.scrubComments(rawCode);
} catch (RuntimeException e) {
// TODO: Unterminated block comment: add to errors
// Continue normally, comments were scrubbed
}
PdePreprocessor.Mode mode = PdePreprocessor.parseMode(rawCode);
SourceMapping mapping = new SourceMapping();
mapping.addAll(SourceUtils.insertImports(coreAndDefaultImports));
mapping.addAll(SourceUtils.insertImports(codeFolderImports));
mapping.addAll(SourceUtils.parseProgramImports(rawCode, programImports));
mapping.addAll(SourceUtils.replaceTypeConstructors(rawCode));
mapping.addAll(SourceUtils.replaceHexLiterals(rawCode));
mapping.addAll(SourceUtils.wrapSketch(mode, className, rawCode.length()));
result.sourceCode = mapping.apply(rawCode);
List<IProblem> problems;
@@ -403,13 +412,12 @@ public class ErrorCheckerService {
result.syntaxErrors = true;
result.containsErrors = true;
parser.setSource(sourceCodeArray);
parser.setSource(result.sourceCode.toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setCompilerOptions(COMPILER_OPTIONS);
parser.setStatementsRecovery(true);
result.compilationUnit = (CompilationUnit) parser.createAST(null);
// Store errors returned by the ast parser
problems = Arrays.asList(result.compilationUnit.getProblems());
@@ -425,53 +433,60 @@ public class ErrorCheckerService {
// No syntax errors, proceed for compilation check, Stage 2.
if (problems.isEmpty() && !editor.hasJavaTabs()) {
String sourceCode = xqpreproc.handle(result.sourceCode, programImports);
prepareCompilerClasspath();
{{ // COMPILE CHECK
SourceMapping mapping2 = new SourceMapping();
parser.setSource(sourceCodeArray);
mapping2.addAll(SourceUtils.addPublicToTopLeveMethods(result.compilationUnit));
mapping2.addAll(SourceUtils.replaceColorAndFixFloats(result.compilationUnit));
result.sourceCode = mapping2.apply(result.sourceCode);
char[] chars = result.sourceCode.toCharArray();
{ // Recreate the compilation unit for code completion, etc.
parser.setSource(chars);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setCompilerOptions(COMPILER_OPTIONS);
parser.setStatementsRecovery(true);
result.compilationUnit = (CompilationUnit) parser.createAST(null);
result.sourceCode = sourceCode;
}
// Currently (Sept, 2012) I'm using Java's reflection api to load the
// CompilationChecker class(from CompilationChecker.jar) that houses the
// Eclispe JDT compiler, and call its getErrorsAsObj method to obtain
// errors. This way, I'm able to add the paths of contributed libraries
// to the classpath of CompilationChecker, dynamically. The eclipse compiler
// needs all referenced libraries in the classpath. Totally a hack. If you find
// a better method, do let me know.
// TODO: set astGenerator.classPath (do this probably on EDT in UI update)
// new ClassPathFactory().createFromPath((String) classPath);
{{ // COMPILE CHECK
// TODO: get these form last result
//boolean importsChanged = checkIfImportsChanged(programImports, prevProgramImports);
//importsChanged &= checkIfImportsChanged(codeFolderImports, prevCodeFolderImports);
boolean importsChanged = true;
List<String> classPath;
URLClassLoader classLoader = null;
if (!importsChanged) {
// TODO: set classPath to previous classPath
} else {
classPath = prepareCompilerClasspath(programImports, sketch);
List<URL> urls = new ArrayList<>();
for (String path : classPath) {
try {
urls.add(new File(path).toURI().toURL());
} catch (MalformedURLException e) {
// Malformed, ignore
}
}
URL[] classPathArray = urls.toArray(new URL[urls.size()]);
classLoader = new URLClassLoader(classPathArray, null);
}
try {
// NOTE TO SELF: If classpath contains null Strings
// URLClassLoader shoots NPE bullets.
// If imports have changed, reload classes with new classpath.
if (loadCompClass) {
classPath = new URL[classpathJars.size()];
/*System.out.println("CP Jars:");
for (URL u: classpathJars) {
String fn = u.getFile();
System.out.println(fn.substring(fn.lastIndexOf('/')));
}*/
classPath = classpathJars.toArray(classPath);
classLoader = new URLClassLoader(classPath, null);
loadCompClass = false;
}
problems = compileAndReturnProblems(className, sourceCode,
problems = compileAndReturnProblems(className, chars,
COMPILER_OPTIONS, classLoader);
} catch (Exception e) {
System.err.println("compileCheck() problem." + e);
e.printStackTrace();
cancel();
} catch (NoClassDefFoundError e) {
// TODO: do we need this?
e.printStackTrace();
cancel();
}
}}
}
@@ -521,7 +536,7 @@ public class ErrorCheckerService {
* @return list of compiler errors and warnings
*/
static public List<IProblem> compileAndReturnProblems(String sourceName,
String source,
char[] source,
Map<String, String> options,
URLClassLoader classLoader) {
final List<IProblem> problems = new ArrayList<>();
@@ -533,7 +548,7 @@ public class ErrorCheckerService {
}
};
final char[] contents = source.toCharArray();
final char[] contents = source;
final char[][] packageName = new char[][]{};
final char[] mainTypeName = sourceName.toCharArray();
final char[] fileName = (sourceName + ".java").toCharArray();
@@ -552,6 +567,7 @@ public class ErrorCheckerService {
new CompilerOptions(options),
requestor,
new DefaultProblemFactory(Locale.getDefault()));
compiler.compile(new ICompilationUnit[]{unit});
return problems;
}
@@ -624,140 +640,83 @@ public class ErrorCheckerService {
}
public URLClassLoader getSketchClassLoader() {
return classLoader;
}
/**
* Processes import statements to obtain class paths of contributed
* libraries. This would be needed for compilation check. Also, adds
* stuff(jar files, class files, candy) from the code folder. And it looks
* messed up.
*/
protected void prepareCompilerClasspath() {
if (!loadCompClass) {
return;
}
protected List<String> prepareCompilerClasspath(List<ImportStatement> programImports, Sketch sketch) {
JavaMode mode = (JavaMode) editor.getMode();
StringBuilder classPath = new StringBuilder();
// log("1..");
classpathJars = new ArrayList<>();
String entry;
// boolean codeFolderChecked = false;
for (ImportStatement impstat : programImports) {
String item = impstat.getImportName();
int dot = item.lastIndexOf('.');
entry = (dot == -1) ? item : item.substring(0, dot);
String entry = impstat.getPackageName();
if (!ignorableImport(entry)) {
entry = entry.substring(6).trim();
// log("Entry--" + entry);
if (ignorableImport(entry)) {
// log("Ignoring: " + entry);
continue;
}
// Try to get the library classpath and add it to the list
try {
Library library = editor.getMode().getLibrary(entry);
String[] libraryPath =
PApplet.split(library.getClassPath().substring(1).trim(),
File.pathSeparatorChar);
for (String pathItem : libraryPath) {
classpathJars.add(new File(pathItem).toURI().toURL());
// Try to get the library classpath and add it to the list
try {
Library library = mode.getLibrary(entry);
classPath.append(library.getClassPath());
} catch (SketchException e) {
// More libraries competing, ignore
}
} catch (Exception e) {
Messages.log("Encountered " + e + " while adding library to classpath");
}
}
// Look around in the code folder for jar files and them too
if (editor.getSketch().hasCodeFolder()) {
File codeFolder = editor.getSketch().getCodeFolder();
// get a list of .jar files in the "code" folder
// (class files in subfolders should also be picked up)
if (sketch.hasCodeFolder()) {
File codeFolder = sketch.getCodeFolder();
String codeFolderClassPath = Util.contentsToClassPath(codeFolder);
// codeFolderChecked = true;
// huh? doesn't this mean .length() == 0? [fry]
if (!codeFolderClassPath.equalsIgnoreCase("")) {
Messages.log("Sketch has a code folder. Adding its jars");
String codeFolderPath[] =
PApplet.split(codeFolderClassPath.substring(1).trim(),
File.pathSeparatorChar);
try {
for (String pathItem : codeFolderPath) {
classpathJars.add(new File(pathItem).toURI().toURL());
Messages.log("Addind cf jar: " + pathItem);
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
classPath.append(codeFolderClassPath);
}
// Also add jars specified in mode's search path
String searchPath = ((JavaMode) editor.getMode()).getSearchPath();
// TODO: maybe we need mode.getCoreLibrary().getClassPath() here
String searchPath = mode.getSearchPath();
if (searchPath != null) {
String[] modeJars = PApplet.split(searchPath, File.pathSeparatorChar);
for (String mj : modeJars) {
try {
classpathJars.add(new File(mj).toURI().toURL());
} catch (MalformedURLException e) {
e.printStackTrace();
}
if (!searchPath.startsWith(File.pathSeparator)) {
classPath.append(File.pathSeparator);
}
classPath.append(searchPath);
}
// TODO: maybe we need lib.getClassPath() here
for (Library lib : mode.coreLibraries) {
classPath.append(File.pathSeparator).append(lib.getJarPath());
}
String javaClassPath = System.getProperty("java.class.path");
if (!javaClassPath.startsWith(File.pathSeparator)) {
classPath.append(File.pathSeparator);
}
classPath.append(javaClassPath);
String rtPath = System.getProperty("java.home") +
File.separator + "lib" + File.separator + "rt.jar";
if (new File(rtPath).exists()) {
classPath.append(File.pathSeparator).append(rtPath);
} else {
rtPath = System.getProperty("java.home") + File.separator + "jre" +
File.separator + "lib" + File.separator + "rt.jar";
if (new File(rtPath).exists()) {
classPath.append(File.pathSeparator).append(rtPath);
}
}
for (Library lib : editor.getMode().coreLibraries) {
try {
classpathJars.add(new File(lib.getJarPath()).toURI().toURL());
} catch (MalformedURLException e) {
e.printStackTrace();
// Make sure class path does not contain empty string (home dir)
String[] paths = classPath.toString().split(File.pathSeparator);
List<String> entries = new ArrayList<>();
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
if (path != null && !path.trim().isEmpty()) {
entries.add(path);
}
}
StringList entries = new StringList();
entries.append(System.getProperty("java.class.path").split(File.pathSeparator));
entries.append(System.getProperty("java.home") +
File.separator + "lib" + File.separator + "rt.jar");
String modeClassPath = ((JavaMode) editor.getMode()).getSearchPath();
if (modeClassPath != null) {
entries.append(modeClassPath);
}
for (URL jarPath : classpathJars) {
entries.append(jarPath.getPath());
}
// // Just in case, make sure we don't run off into oblivion
// String workingDirectory = System.getProperty("user.dir");
// if (entries.removeValue(workingDirectory) != -1) {
// System.err.println("user.dir found in classpath");
// }
// // hm, these weren't problematic either
// entries.append(System.getProperty("user.dir"));
// entries.append("");
// entries.print();
synchronized (astGenerator) {
astGenerator.classPath = astGenerator.factory.createFromPath(entries.join(File.pathSeparator));
Messages.log("Classpath created " + (astGenerator.classPath != null));
Messages.log("Sketch classpath jars loaded.");
if (Platform.isMacOS()) {
File f = new File(System.getProperty("java.home") +
File.separator + "bundle" +
File.separator + "Classes" +
File.separator + "classes.jar");
Messages.log(f.getAbsolutePath() + " | classes.jar found?" + f.exists());
} else {
File f = new File(System.getProperty("java.home") + File.separator +
"lib" + File.separator + "rt.jar" + File.separator);
Messages.log(f.getAbsolutePath() + " | rt.jar found?" + f.exists());
}
}
return entries;
}
@@ -771,7 +730,10 @@ public class ErrorCheckerService {
protected boolean ignorableSuggestionImport(String impName) {
String impNameLc = impName.toLowerCase();
// TODO: propagate these from last result
/*String impNameLc = impName.toLowerCase();
for (ImportStatement impS : programImports) {
if (impNameLc.startsWith(impS.getPackageName().toLowerCase())) {
@@ -783,7 +745,7 @@ public class ErrorCheckerService {
if (impNameLc.startsWith(impS.getPackageName().toLowerCase())) {
return false;
}
}
}*/
final String include = "include";
final String exclude = "exclude";
@@ -803,7 +765,7 @@ public class ErrorCheckerService {
return true;
}
static final Map<String, String> COMPILER_OPTIONS;
static private final Map<String, String> COMPILER_OPTIONS;
static {
Map<String, String> compilerOptions = new HashMap<>();
@@ -952,6 +914,10 @@ public class ErrorCheckerService {
* int[3] are on TODO
*/
protected int[] JavaToPdeOffsets(int line, int offset) {
return new int[] { 0, 0 }; // TODO
/*
int codeIndex = 0;
int x = line - mainClassOffset;
@@ -964,7 +930,7 @@ public class ErrorCheckerService {
ImportStatement is = programImports.get(x);
// log(is.importName + ", " + is.tab + ", "
// + is.lineNumber);
return new int[] { is.getTab(), is.getLineNumber() };
return new int[] { 0, 0 }; // TODO
} else {
// Some seriously ugly stray error, just can't find the source
@@ -1020,6 +986,7 @@ public class ErrorCheckerService {
e.printStackTrace();
}
return new int[] { codeIndex, x };
*/
}
@@ -1039,6 +1006,11 @@ public class ErrorCheckerService {
* @return int[0] - tab number, int[1] - line number
*/
protected int[] calculateTabIndexAndLineNumber(int javalineNumber) {
return new int[] { 0, 0 }; // TODO
/*
// String[] lines = {};// = PApplet.split(sourceString, '\n');
int codeIndex = 0;
@@ -1052,7 +1024,7 @@ public class ErrorCheckerService {
ImportStatement is = programImports.get(x);
// log(is.importName + ", " + is.tab + ", "
// + is.lineNumber);
return new int[] { is.getTab(), is.getLineNumber() };
return new int[] { 0, 0 }; // TODO
} else {
// Some seriously ugly stray error, just can't find the source
@@ -1105,6 +1077,8 @@ public class ErrorCheckerService {
System.err.println("Things got messed up in ErrorCheckerService.calculateTabIndexAndLineNumber()");
}
return new int[] { codeIndex, x };
*/
}
@@ -1112,159 +1086,15 @@ public class ErrorCheckerService {
* Returns line number of corresponding java source
*/
protected int getJavaLineNumFromPDElineNum(int tab, int pdeLineNum){
int jLineNum = programImports.size() + 1;
return 0;
/*int jLineNum = programImports.size() + 1;
for (int i = 0; i < tab; i++) {
SketchCode sc = editor.getSketch().getCode(i);
int len = Util.countLines(sc.getProgram()) + 1;
jLineNum += len;
}
return jLineNum;
}
/**
* Fetches code from the editor tabs and pre-processes it into parsable pure
* java source. And there's a difference between parsable and compilable.
* XQPrerocessor.java makes this code compilable. <br>
* Handles: <li>Removal of import statements <li>Conversion of int(),
* char(), etc to PApplet.parseInt(), etc. <li>Replacing '#' with 0xff for
* color representation<li>Converts all 'color' datatypes to int
* (experimental) <li>Appends class declaration statement after determining
* the mode the sketch is in - ACTIVE or STATIC
*
* @return String - Pure java representation of PDE code. Note that this
* code is not yet compile ready.
*/
protected String preprocessCode() {
ArrayList<ImportStatement> scrappedImports = new ArrayList<>();
StringBuilder rawCode = new StringBuilder();
final Sketch sketch = editor.getSketch();
try {
for (SketchCode sc : sketch.getCode()) {
if (sc.isExtension("pde")) {
try {
if (sketch.getCurrentCode().equals(sc)) {
rawCode.append(scrapImportStatements(sc.getDocumentText(),
sketch.getCodeIndex(sc),
scrappedImports));
} else {
rawCode.append(scrapImportStatements(sc.getProgram(),
sketch.getCodeIndex(sc),
scrappedImports));
}
rawCode.append('\n');
} catch (Exception e) {
e.printStackTrace();
}
rawCode.append('\n');
}
}
} catch (Exception e) {
Messages.log("Exception in preprocessCode()");
}
// Swap atomically, might blow up anyway
// TODO: this is iterated from multiple threads, synchronize properly
this.programImports = scrappedImports;
String sourceAlt = rawCode.toString();
// Replace comments with whitespaces
// sourceAlt = scrubComments(sourceAlt);
// Find all int(*), replace with PApplet.parseInt(*)
// \bint\s*\(\s*\b , i.e all exclusive "int("
String dataTypeFunc[] = { "int", "char", "float", "boolean", "byte" };
for (String dataType : dataTypeFunc) {
String dataTypeRegexp = "\\b" + dataType + "\\s*\\(";
Pattern pattern = Pattern.compile(dataTypeRegexp);
Matcher matcher = pattern.matcher(sourceAlt);
// while (matcher.find()) {
// System.out.print("Start index: " + matcher.start());
// log(" End index: " + matcher.end() + " ");
// log("-->" + matcher.group() + "<--");
// }
sourceAlt = matcher.replaceAll("PApplet.parse"
+ Character.toUpperCase(dataType.charAt(0))
+ dataType.substring(1) + "(");
}
// Find all #[web color] and replace with 0xff[webcolor]
// Should be 6 digits only.
final String webColorRegexp = "#[A-Fa-f0-9]{6}\\W";
Pattern webPattern = Pattern.compile(webColorRegexp);
Matcher webMatcher = webPattern.matcher(sourceAlt);
while (webMatcher.find()) {
// log("Found at: " + webMatcher.start());
String found = sourceAlt.substring(webMatcher.start(),
webMatcher.end());
// log("-> " + found);
sourceAlt = webMatcher.replaceFirst("0xff" + found.substring(1));
webMatcher = webPattern.matcher(sourceAlt);
}
// Replace all color data types with int
// Regex, Y U SO powerful?
final String colorTypeRegex = "color(?![a-zA-Z0-9_])(?=\\[*)(?!(\\s*\\())";
Pattern colorPattern = Pattern.compile(colorTypeRegex);
Matcher colorMatcher = colorPattern.matcher(sourceAlt);
sourceAlt = colorMatcher.replaceAll("int");
checkForChangedImports();
className = editor.getSketch().getName();
// Check whether the code is being written in STATIC mode
PdePreprocessor.Mode mode;
try {
String uncommented = PdePreprocessor.scrubComments(sourceAlt);
mode = PdePreprocessor.parseMode(uncommented);
} catch (RuntimeException r) {
String uncommented = PdePreprocessor.scrubComments(sourceAlt + "*/");
mode = PdePreprocessor.parseMode(uncommented);
}
StringBuilder sb = new StringBuilder();
// Imports
sb.append(xqpreproc.prepareImports(scrappedImports));
// Header
if (mode != PdePreprocessor.Mode.JAVA) {
sb.append("public class ").append(className).append(" extends PApplet {\n");
if (mode == PdePreprocessor.Mode.STATIC) {
sb.append("public void setup() {\n");
}
}
// Grab the offset before adding contents of the editor
mainClassOffset = 1;
for (int i = 0; i < sb.length(); i++) {
if (sb.charAt(i) == '\n') {
mainClassOffset++;
}
}
// Editor content
sb.append(sourceAlt);
// Footer
if (mode != PdePreprocessor.Mode.JAVA) {
if (mode == PdePreprocessor.Mode.STATIC) {
// no noLoop() here so it does not tell you
// "can't invoke noLoop() on obj" when you type "obj."
sb.append("\n}");
}
sb.append("\n}");
}
return substituteUnicode(sb.toString());
return jLineNum;*/
}
@@ -1382,26 +1212,19 @@ public class ErrorCheckerService {
* Checks if import statements in the sketch have changed. If they have,
* compiler classpath needs to be updated.
*/
protected void checkForChangedImports() {
// log("Imports: " + programImports.size() +
// " Prev Imp: "
// + previousImports.size());
if (programImports.size() != previousImports.size()) {
// log(1);
loadCompClass = true;
previousImports = programImports;
protected static boolean checkIfImportsChanged(List<ImportStatement> prevImports,
List<ImportStatement> imports) {
if (imports.size() != prevImports.size()) {
return true;
} else {
for (int i = 0; i < programImports.size(); i++) {
if (!programImports.get(i).getImportName().equals(previousImports
.get(i).getImportName())) {
// log(2);
loadCompClass = true;
previousImports = programImports;
break;
int count = imports.size();
for (int i = 0; i < count; i++) {
if (!imports.get(i).isSameAs(prevImports.get(i))) {
return true;
}
}
}
// log("load..? " + loadCompClass);
return false;
}
protected int pdeImportsCount;
@@ -1410,107 +1233,6 @@ public class ErrorCheckerService {
return pdeImportsCount;
}
/**
* Removes import statements from tabSource, replaces each with white spaces
* and adds the import to the list of program imports
*
* @param tabProgram
* - Code in a tab
* @param tabNumber
* - index of the tab
* @return String - Tab code with imports replaced with white spaces
*/
protected String scrapImportStatements(String tabProgram,
int tabNumber,
List<ImportStatement> outImports) {
//TODO: Commented out imports are still detected as main imports.
pdeImportsCount = 0;
String tabSource = tabProgram;
do {
// log("-->\n" + sourceAlt + "\n<--");
String[] pieces = PApplet.match(tabSource, IMPORT_REGEX);
// Stop the loop if we've removed all the import lines
if (pieces == null) {
break;
}
String piece = pieces[1] + pieces[2] + pieces[3];
int len = piece.length(); // how much to trim out
// programImports.add(piece); // the package name
// find index of this import in the program
int idx = tabSource.indexOf(piece);
// System.out.print("Import -> " + piece);
// log(" - "
// + Base.countLines(tabSource.substring(0, idx)) + " tab "
// + tabNumber);
int lineCount = Util.countLines(tabSource.substring(0, idx));
outImports.add(new ImportStatement(piece, tabNumber, lineCount));
// Remove the import from the main program
// Substitute with white spaces
String whiteSpace = "";
for (int j = 0; j < piece.length(); j++) {
whiteSpace += " ";
}
tabSource = tabSource.substring(0, idx) + whiteSpace
+ tabSource.substring(idx + len);
pdeImportsCount++;
} while (true);
// log(tabSource);
return tabSource;
}
/**
* Replaces non-ascii characters with their unicode escape sequences and
* stuff. Used as it is from
* processing.src.processing.mode.java.preproc.PdePreprocessor
*
* @param program
* - Input String containing non ascii characters
* @return String - Converted String
*/
protected static String substituteUnicode(String program) {
// check for non-ascii chars (these will be/must be in unicode format)
char p[] = program.toCharArray();
int unicodeCount = 0;
for (int i = 0; i < p.length; i++) {
if (p[i] > 127) {
unicodeCount++;
}
}
if (unicodeCount == 0) {
return program;
}
// if non-ascii chars are in there, convert to unicode escapes
// add unicodeCount * 5.. replacing each unicode char
// with six digit uXXXX sequence (xxxx is in hex)
// (except for nbsp chars which will be a replaced with a space)
int index = 0;
char p2[] = new char[p.length + unicodeCount * 5];
for (int i = 0; i < p.length; i++) {
if (p[i] < 128) {
p2[index++] = p[i];
} else if (p[i] == 160) { // unicode for non-breaking space
p2[index++] = ' ';
} else {
int c = p[i];
p2[index++] = '\\';
p2[index++] = 'u';
char str[] = Integer.toHexString(c).toCharArray();
// add leading zeros, so that the length is 4
// for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0';
for (int m = 0; m < 4 - str.length; m++)
p2[index++] = '0';
System.arraycopy(str, 0, p2, index, str.length);
index += str.length;
}
}
return new String(p2, 0, index);
}
public void handleErrorCheckingToggle() {
if (!JavaMode.errorCheckEnabled) {
Messages.log(editor.getSketch().getName() + " Error Checker disabled.");
@@ -1527,11 +1249,6 @@ public class ErrorCheckerService {
}
public ArrayList<ImportStatement> getProgramImports() {
return programImports;
}
private static class NameEnvironmentImpl implements INameEnvironment {
private final ClassLoader classLoader;

View File

@@ -20,84 +20,108 @@ along with this program; if not, write to the Free Software Foundation, Inc.
package processing.mode.java.pdex;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
/**
* Wrapper for import statements
*
*
* @author Manindra Moharana &lt;me@mkmoharana.com&gt;
*
*/
public class ImportStatement {
/**
* Ex: processing.opengl.*, java.util.*
*/
private String importName;
/**
* Which tab does it belong to?
*/
private int tab;
private static final String importKw = "import";
private static final String staticKw = "static";
private boolean isClass;
private boolean isStarred;
private boolean isStatic;
/**
* Line number(pde code) of the import
* Full class name of the import with all packages
* Ends with star for starred imports
*/
private int lineNumber;
private String className;
/**
*
* @param importName - Ex: processing.opengl.*, java.util.*
* @param tab - Which tab does it belong to?
* @param lineNumber - Line number(pde code) of the import
* Name of the package e.g. everything before last dot
*/
public ImportStatement(String importName, int tab, int lineNumber) {
this.importName = importName;
this.tab = tab;
this.lineNumber = lineNumber;
private String packageName;
private ImportStatement() { }
public static ImportStatement wholePackage(String pckg) {
ImportStatement is = new ImportStatement();
is.packageName = pckg;
is.className = "*";
is.isStarred = true;
return is;
}
public String getImportName() {
return importName;
}
public static ImportStatement singleClass(String cls) {
ImportStatement is = new ImportStatement();
int lastDot = cls.lastIndexOf('.');
is.className = lastDot >= 0 ? cls.substring(lastDot+1) : cls;
is.packageName = lastDot >= 0 ? cls.substring(0, lastDot) : "";
is.isClass = true;
return is;
}
public static ImportStatement parse(String importString) {
Matcher matcher = SourceUtils.IMPORT_REGEX_NO_KEYWORD.matcher(importString);
if (!matcher.find()) return null;
return parse(matcher.toMatchResult());
}
public static ImportStatement parse(MatchResult match) {
ImportStatement is = new ImportStatement();
is.isStatic = match.group(2) != null;
String pckg = match.group(3);
pckg = (pckg == null) ? "" : pckg.replaceAll("\\s","");
is.packageName = pckg.endsWith(".") ?
pckg.substring(0, pckg.length()-1) :
pckg;
is.className = match.group(4);
is.isStarred = is.className.equals("*");
return is;
}
public String getFullSourceLine() {
return importKw + " " + (isStatic ? (staticKw + " ") : "") + packageName + "." + className + ";";
}
public String getFullClassName(){
return packageName + "." + className;
}
public String getClassName(){
return className;
}
public String getPackageName(){
String ret = new String(importName.trim());
if(ret.startsWith("import "))
ret = ret.substring(7);
if(ret.endsWith(";"))
ret = ret.substring(0, ret.length() - 1).trim();
if(ret.endsWith(".*"))
ret = ret.substring(0, ret.length() - 2);
return ret;
}
public int getTab() {
return tab;
}
public int getLineNumber() {
return lineNumber;
return packageName;
}
public boolean isStarredImport() {
String ret = importName.trim();
if(ret.endsWith(";")) {
ret = ret.substring(0, ret.length() - 1).trim();
}
return ret.endsWith(".*");
return isStarred;
}
public String getImportedClassName() {
if (isStarredImport()) {
return null;
} else {
String ret = importName.trim();
if(ret.endsWith(";")) {
ret = ret.substring(0, ret.length() - 1).trim();
}
if (ret.lastIndexOf('.') != -1) {
return ret.substring(ret.lastIndexOf('.') + 1);
} else {
return null;
}
}
public boolean isStaticImport() {
return isStatic;
}
public boolean isSameAs(ImportStatement is) {
return packageName.equals(is.packageName) &&
className.equals(is.className) &&
isStatic == is.isStatic;
}
}

View File

@@ -0,0 +1,293 @@
package processing.mode.java.pdex;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
import processing.app.Base;
import processing.core.PApplet;
import static java.awt.SystemColor.text;
public class SourceMapping {
private static final Comparator<Edit> INPUT_OFFSET_COMP = new Comparator<Edit>() {
@Override
public int compare(Edit o1, Edit o2) {
return Integer.compare(o1.fromOffset, o2.fromOffset);
}
};
private static final Comparator<Edit> OUTPUT_OFFSET_COMP = new Comparator<Edit>() {
@Override
public int compare(Edit o1, Edit o2) {
return Integer.compare(o1.toOffset, o2.toOffset);
}
};
private static final Comparator<Edit> INPUT_OFFSET_COMP_LEX = new Comparator<Edit>() {
@Override
public int compare(Edit o1, Edit o2) {
int off = Integer.compare(o1.fromOffset, o2.fromOffset);
return (off != 0)
? off
: Integer.compare(o1.fromLength, o2.fromLength);
}
};
private static final Comparator<Edit> OUTPUT_OFFSET_COMP_LEX = new Comparator<Edit>() {
@Override
public int compare(Edit o1, Edit o2) {
int off = Integer.compare(o1.toOffset, o2.toOffset);
return (off != 0)
? off
: Integer.compare(o1.toLength, o2.toLength);
}
};
private boolean applied;
private List<Edit> edits = new ArrayList<>();
private List<Edit> inMap = new ArrayList<>();
private List<Edit> outMap = new ArrayList<>();
public void add(Edit edit) {
edits.add(edit);
}
public void addAll(Collection<Edit> edits) {
this.edits.addAll(edits);
}
public void insert(int offset, String text) {
edits.add(Edit.insert(offset, text));
}
public void delete(int offset, int length) {
edits.add(Edit.delete(offset, length));
}
public void replace(int offset, int length, String text) {
edits.add(Edit.replace(offset, length, text));
}
public void move(int fromOffset, int length, int toOffset) {
edits.add(Edit.move(fromOffset, length, toOffset));
}
public String apply(CharSequence input) {
final int inLength = input.length();
final int editCount = edits.size();
final StringBuilder output = new StringBuilder(inLength);
// Make copies of Edits to preserve original edits
List<Edit> inEdits = new ArrayList<>();
for (Edit edit : edits) {
inEdits.add(new Edit(edit));
}
List<Edit> outEdits = new ArrayList<>(inEdits);
// Edits sorted by input offsets
Collections.sort(inEdits, INPUT_OFFSET_COMP);
// Edits sorted by output offsets
Collections.sort(outEdits, OUTPUT_OFFSET_COMP);
// Check input edits for overlaps
for (int i = 0, lastEnd = 0; i < editCount; i++) {
Edit ch = inEdits.get(i);
if (ch.fromOffset < lastEnd) {
String error = "Edits overlap in input! " + inEdits.get(i - 1) + ", " + ch;
throw new IndexOutOfBoundsException(error);
}
lastEnd = ch.fromOffset + ch.fromLength;
}
// Input
ListIterator<Edit> inIt = inEdits.listIterator();
Edit inEdit = inIt.hasNext() ? inIt.next() : null;
int inEditOff = inEdit == null ? input.length() : inEdit.fromOffset;
// Output
ListIterator<Edit> outIt = outEdits.listIterator();
Edit outEdit = outIt.hasNext() ? outIt.next() : null;
int outEditOff = outEdit == null ? input.length() : outEdit.toOffset;
int offset = 0;
inMap.clear();
outMap.clear();
// Walk through the input, apply changes, create mapping
while (offset < inLength || inEdit != null || outEdit != null) {
{ // Copy the unmodified portion of the input, create mapping for it
int nextEditOffset = Math.min(inEditOff, outEditOff);
{ // Insert move block to have mapping for unmodified portions too
int length = nextEditOffset - offset;
if (length > 0) {
Edit ch = Edit.move(offset, length, output.length());
inMap.add(ch);
outMap.add(ch);
}
}
// Copy the block without changes from the input
output.append(input, offset, nextEditOffset);
// Move offset accordingly
offset = nextEditOffset;
}
// Process encountered input edits
while (inEdit != null && offset >= inEditOff) {
offset += inEdit.fromLength;
inMap.add(inEdit);
inEdit = inIt.hasNext() ? inIt.next() : null;
inEditOff = inEdit != null ? inEdit.fromOffset : inLength;
}
// Process encountered output edits
while (outEdit != null && offset >= outEditOff) {
outEdit.toOffset = output.length();
outMap.add(outEdit);
if (outEdit.toLength > 0) {
if (outEdit.outputText != null) {
output.append(outEdit.outputText);
} else {
output.append(input, outEdit.fromOffset, outEdit.fromOffset + outEdit.fromLength);
}
}
outEdit = outIt.hasNext() ? outIt.next() : null;
outEditOff = outEdit != null ? outEdit.toOffset : inLength;
}
}
applied = true;
return output.toString();
}
public int getInputOffset(int outputOffset) {
if (Base.DEBUG) checkApplied();
Edit searchKey = new Edit(0, 0, outputOffset, Integer.MAX_VALUE, null);
int i = Collections.binarySearch(outMap, searchKey, OUTPUT_OFFSET_COMP);
if (i < 0) {
i = -(i + 1);
}
i = PApplet.constrain(i-1, 0, outMap.size()-1);
Edit edit = outMap.get(i);
int diff = outputOffset - edit.toOffset;
return edit.fromOffset + Math.min(diff, Math.max(0, edit.fromLength - 1));
}
public int getOutputOffset(int inputOffset) {
if (Base.DEBUG) checkApplied();
Edit searchKey = new Edit(inputOffset, Integer.MAX_VALUE, 0, 0, null);
int i = Collections.binarySearch(inMap, searchKey, INPUT_OFFSET_COMP);
if (i < 0) {
i = -(i + 1);
}
i = PApplet.constrain(i-1, 0, inMap.size()-1);
Edit edit = inMap.get(i);
int diff = inputOffset - edit.fromOffset;
return edit.toOffset + Math.min(diff, Math.max(0, edit.toLength - 1));
}
public void clear() {
applied = false;
edits.clear();
inMap.clear();
outMap.clear();
}
private void checkNotApplied() {
if (applied) throw new RuntimeException("this mapping was already applied");
}
private void checkApplied() {
if (applied) throw new RuntimeException("this mapping was not applied yet");
}
@Override
public String toString() {
return "SourceMapping{" +
"edits=" + edits +
", applied=" + applied +
'}';
}
protected static class Edit {
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) {
return new Edit(offset, length, offset, text.length(), text);
}
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) {
return new Edit(position, length, position, 0, null);
}
Edit(Edit edit) {
this.fromOffset = edit.fromOffset;
this.fromLength = edit.fromLength;
this.toOffset = edit.toOffset;
this.toLength = edit.toLength;
this.outputText = edit.outputText;
}
Edit(int fromOffset, int fromLength, int toOffset, int toLength, String text) {
this.fromOffset = fromOffset;
this.fromLength = fromLength;
this.toOffset = toOffset;
this.toLength = toLength;
this.outputText = text;
}
final int fromOffset;
final int fromLength;
int toOffset;
final int toLength;
final String outputText;
@Override
public String toString() {
return "Edit{" +
"from=" + fromOffset + ":" + fromLength +
", to=" + toOffset + ":" + toLength +
((text != null) ? (", text='" + outputText + '\'') : "") +
'}';
}
}
}

View File

@@ -0,0 +1,297 @@
package processing.mode.java.pdex;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import processing.mode.java.pdex.SourceMapping.Edit;
import processing.mode.java.preproc.PdePreprocessor;
public class SourceUtils {
public static final Pattern IMPORT_REGEX =
Pattern.compile("(?:^|;)\\s*(import\\s+(?:(static)\\s+)?((?:\\w+\\s*\\.)*)\\s*(\\S+)\\s*;)",
Pattern.MULTILINE | Pattern.DOTALL);
public static final Pattern IMPORT_REGEX_NO_KEYWORD =
Pattern.compile("^\\s*((?:(static)\\s+)?((?:\\w+\\s*\\.)*)\\s*(\\S+))",
Pattern.MULTILINE | Pattern.DOTALL);
public static List<Edit> parseProgramImports(CharSequence source,
List<ImportStatement> outImports) {
List<Edit> result = new ArrayList<>();
Matcher matcher = IMPORT_REGEX.matcher(source);
while (matcher.find()) {
String piece = matcher.group(1);
ImportStatement is = ImportStatement.parse(matcher.toMatchResult());
outImports.add(is);
int len = piece.length();
int idx = matcher.start(1);
// Remove the import from the main program
// Substitute with white spaces
result.add(Edit.move(idx, len, 0));
result.add(Edit.insert(0, "\n"));
}
return result;
}
public static final Pattern TYPE_CONSTRUCTOR_REGEX =
Pattern.compile("(?:^|\\W)(int|char|float|boolean|byte)(?:\\s*\\()",
Pattern.MULTILINE);
public static List<Edit> replaceTypeConstructors(CharSequence source) {
List<Edit> result = new ArrayList<>();
Matcher matcher = TYPE_CONSTRUCTOR_REGEX.matcher(source);
while (matcher.find()) {
String match = matcher.group(1);
int offset = matcher.start(1);
int length = match.length();
String replace = "PApplet.parse"
+ Character.toUpperCase(match.charAt(0))
+ match.substring(1);
result.add(Edit.replace(offset, length, replace));
}
return result;
}
public static final Pattern HEX_LITERAL_REGEX =
Pattern.compile("(?:^|\\W)(#[A-Fa-f0-9]{6})(?:\\W|$)");
public static List<Edit> replaceHexLiterals(CharSequence source) {
// Find all #[webcolor] and replace with 0xff[webcolor]
// Should be 6 digits only.
List<Edit> result = new ArrayList<>();
Matcher matcher = HEX_LITERAL_REGEX.matcher(source);
while (matcher.find()) {
int offset = matcher.start(1);
result.add(Edit.replace(offset, 1, "0xff"));
}
return result;
}
public static List<Edit> insertImports(List<ImportStatement> imports) {
List<Edit> result = new ArrayList<>();
for (ImportStatement imp : imports) {
result.add(Edit.insert(0, imp.getFullSourceLine() + "\n"));
}
return result;
}
public static List<Edit> wrapSketch(PdePreprocessor.Mode mode, String className, int sourceLength) {
List<Edit> edits = new ArrayList<>();
StringBuilder b = new StringBuilder();
// Header
if (mode != PdePreprocessor.Mode.JAVA) {
b.append("\npublic class ").append(className).append(" extends PApplet {\n");
if (mode == PdePreprocessor.Mode.STATIC) {
b.append("public void setup() {\n");
}
}
edits.add(Edit.insert(0, b.toString()));
// Reset builder
b.setLength(0);
// Footer
if (mode != PdePreprocessor.Mode.JAVA) {
if (mode == PdePreprocessor.Mode.STATIC) {
// no noLoop() here so it does not tell you
// "can't invoke noLoop() on obj" when you type "obj."
b.append("\n}");
}
b.append("\n}\n");
}
edits.add(Edit.insert(sourceLength, b.toString()));
return edits;
}
public static List<Edit> addPublicToTopLeveMethods(CompilationUnit cu) {
List<Edit> edits = new ArrayList<>();
// Add public modifier to top level methods
for (Object node : cu.types()) {
if (node instanceof TypeDeclaration) {
TypeDeclaration type = (TypeDeclaration) node;
for (MethodDeclaration method : type.getMethods()) {
if (method.modifiers().isEmpty() && !method.isConstructor()) {
edits.add(Edit.insert(method.getStartPosition(), "public "));
}
}
}
}
return edits;
}
public static List<Edit> replaceColorAndFixFloats(CompilationUnit cu) {
final List<Edit> edits = new ArrayList<>();
// Walk the tree, replace "color" with "int" and add 'f' to floats
cu.accept(new ASTVisitor() {
@Override
public boolean visit(SimpleType node) {
if ("color".equals(node.getName().toString())) {
edits.add(Edit.replace(node.getStartPosition(), node.getLength(), "int"));
}
return super.visit(node);
}
@Override
public boolean visit(NumberLiteral node) {
String s = node.getToken().toLowerCase();
if (!s.endsWith("f") && !s.endsWith("d") && (s.contains(".") || s.contains("e"))) {
edits.add(Edit.insert(node.getStartPosition() + node.getLength(), "f"));
}
return super.visit(node);
}
});
return edits;
}
/**
* Replaces non-ascii characters with their unicode escape sequences and
* stuff. Used as it is from
* processing.src.processing.mode.java.preproc.PdePreprocessor
*
* @param program
* - Input String containing non ascii characters
* @return String - Converted String
*/
public static String substituteUnicode(String program) {
StringBuilder sb = new StringBuilder(program);
substituteUnicode(sb);
return sb.toString();
}
public static void substituteUnicode(StringBuilder p) {
// check for non-ascii chars (these will be/must be in unicode format)
int unicodeCount = 0;
for (int i = 0; i < p.length(); i++) {
if (p.charAt(i) > 127) {
unicodeCount++;
}
}
if (unicodeCount == 0) {
return;
}
StringBuilder p2 = new StringBuilder(p.length() + 4 * unicodeCount);
// if non-ascii chars are in there, convert to unicode escapes
// add unicodeCount * 5.. replacing each unicode char
// with six digit uXXXX sequence (xxxx is in hex)
// (except for nbsp chars which will be a replaced with a space)
for (int i = 0; i < p.length(); i++) {
int c = p.charAt(i);
if (c < 128) {
p2.append(c);
} else if (c == 160) { // unicode for non-breaking space
p2.append(' ');
} else if (c >= 128){
p2.append("\\u").append(String.format("%04X", c));
}
}
p.setLength(0);
p.append(p2);
}
static public String scrubComments(String p) {
StringBuilder sb = new StringBuilder(p);
scrubComments(sb);
return sb.toString();
}
/**
* Replace all commented portions of a given String as spaces.
* Utility function used here and in the preprocessor.
*/
static public void scrubComments(StringBuilder p) {
// Track quotes to avoid problems with code like: String t = "*/*";
// http://code.google.com/p/processing/issues/detail?id=1435
boolean insideQuote = false;
int length = p.length();
int index = 0;
while (index < length) {
// for any double slash comments, ignore until the end of the line
if (!insideQuote &&
(p.charAt(index) == '/') &&
(index < length - 1) &&
(p.charAt(index+1) == '/')) {
p.setCharAt(index++, ' ');
p.setCharAt(index++, ' ');
while ((index < length) &&
(p.charAt(index) != '\n')) {
p.setCharAt(index++, ' ');
}
// check to see if this is the start of a new multiline comment.
// if it is, then make sure it's actually terminated somewhere.
} else if (!insideQuote &&
(p.charAt(index) == '/') &&
(index < length - 1) &&
(p.charAt(index+1) == '*')) {
p.setCharAt(index++, ' ');
p.setCharAt(index++, ' ');
boolean endOfRainbow = false;
while (index < length - 1) {
if ((p.charAt(index) == '*') && (p.charAt(index+1) == '/')) {
p.setCharAt(index++, ' ');
p.setCharAt(index++, ' ');
endOfRainbow = true;
break;
} else {
// continue blanking this area
p.setCharAt(index++, ' ');
}
}
if (!endOfRainbow) {
throw new RuntimeException("Missing the */ from the end of a " +
"/* comment */");
}
} else if (p.charAt(index) == '"' && index > 0 && p.charAt(index-1) != '\\') {
insideQuote = !insideQuote;
index++;
} else { // any old character, move along
index++;
}
}
}
}

View File

@@ -1,215 +0,0 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2012-15 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.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package processing.mode.java.pdex;
import java.util.List;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import processing.app.Util;
import processing.data.StringList;
import processing.mode.java.JavaEditor;
import processing.mode.java.preproc.PdePreprocessor;
/**
* Implementation of PdePreprocessor that uses Eclipse JDT features
* instead of ANTLR.
*/
public class XQPreprocessor {
private ASTRewrite rewrite = null;
private List<ImportStatement> extraImports;
private final JavaEditor editor;
private String[] coreImports;
private String[] defaultImports;
public XQPreprocessor(JavaEditor editor) {
this.editor = editor;
// get parameters from the main preproc
// PdePreprocessor p = new PdePreprocessor(null);
PdePreprocessor p = editor.createPreprocessor(null);
defaultImports = p.getDefaultImports();
coreImports = p.getCoreImports();
}
/**
* The main preprocessing method that converts code into compilable Java.
*/
protected String handle(String source,
List<ImportStatement> programImports) {
this.extraImports = programImports;
Document doc = new Document(source);
ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setSource(doc.get().toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setCompilerOptions(ErrorCheckerService.COMPILER_OPTIONS);
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
cu.recordModifications();
rewrite = ASTRewrite.create(cu.getAST());
cu.accept(new XQVisitor());
TextEdit edits = cu.rewrite(doc, null);
try {
edits.apply(doc);
} catch (MalformedTreeException | BadLocationException e) {
e.printStackTrace();
}
return doc.get();
}
protected String prepareImports(List<ImportStatement> programImports) {
this.extraImports = programImports;
StringList imports = new StringList();
for (ImportStatement imp : extraImports) {
imports.append(imp.getImportName());
}
imports.append("// Default Imports");
for (String imp : coreImports) {
imports.append("import " + imp + ";");
}
for (String imp : defaultImports) {
imports.append("import " + imp + ";");
}
if (editor.getSketch().getCodeFolder().exists()) {
StringList codeFolderPackages = null;
String codeFolderClassPath = Util.contentsToClassPath(editor.getSketch().getCodeFolder());
codeFolderPackages = Util.packageListFromClassPath(codeFolderClassPath);
if (codeFolderPackages != null) {
editor.getErrorChecker().codeFolderImports.clear();
for (String item : codeFolderPackages) {
// Messages.log("CF import " + item);
imports.append("import " + item + ".*;");
editor.getErrorChecker().codeFolderImports.add(new ImportStatement("import " + item + ".*;",0,0));
}
}
}
return imports.join("\n") + "\n";
}
/**
* Visitor implementation that does all the substitution dirty work. <br>
* <LI>Any function not specified as being protected or private will be made
* 'public'. This means that <TT>void setup()</TT> becomes
* <TT>public void setup()</TT>.
*
* <LI>Converts doubles into floats, i.e. 12.3 becomes 12.3f so that people
* don't have to add f after their numbers all the time since it's confusing
* for beginners. Also, most functions of p5 core deal with floats only.
*/
private class XQVisitor extends ASTVisitor {
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean visit(MethodDeclaration node) {
if (node.getReturnType2() != null) {
// if return type is color, make it int
// if (node.getReturnType2().toString().equals("color")) {
// System.err.println("color type detected!");
// node.setReturnType2(rewrite.getAST().newPrimitiveType(
// PrimitiveType.INT));
// }
// The return type is not void, no need to make it public
// if (!node.getReturnType2().toString().equals("void"))
// return true;
}
// Simple method, make it public
if (node.modifiers().size() == 0 && !node.isConstructor()) {
// rewrite.set(node, node.getModifiersProperty(),
// Modifier.PUBLIC,
// null);
// rewrite.getListRewrite(node,
// node.getModifiersProperty()).insertLast(Modifier., null)
List newMod = rewrite.getAST().newModifiers(Modifier.PUBLIC);
node.modifiers().add(newMod.get(0));
}
return true;
}
public boolean visit(NumberLiteral node) {
// Need to handle both 1.0F and 1.0D cases
// https://github.com/processing/processing/issues/3707
String lower = node.getToken().toLowerCase();
if (!lower.endsWith("f") && !lower.endsWith("d")) {
for (int i = 0; i < node.getToken().length(); i++) {
if (node.getToken().charAt(i) == '.') {
String s = node.getToken() + "f";
node.setToken(s);
break;
}
}
}
return true;
}
// public boolean visit(FieldDeclaration node) {
// if (node.getType().toString().equals("color")){
// System.err.println("color type detected!");
// node.setType(rewrite.getAST().newPrimitiveType(
// PrimitiveType.INT));
// }
// return true;
// }
//
// public boolean visit(VariableDeclarationStatement node) {
// if (node.getType().toString().equals("color")){
// System.err.println("color type detected!");
// node.setType(rewrite.getAST().newPrimitiveType(
// PrimitiveType.INT));
// }
// return true;
// }
// /**
// * This is added just for debugging purposes - to make sure that all
// * instances of color type have been substituded as in by the regex
// * search in ErrorCheckerService.preprocessCode().
// */
// public boolean visit(SimpleType node) {
// if (node.toString().equals("color")) {
// System.err.println("Color type detected: please report as an issue.");
// }
// return true;
// }
}
}