mirror of
https://github.com/processing/processing4.git
synced 2026-02-03 21:59:20 +01:00
1046 lines
37 KiB
Java
1046 lines
37 KiB
Java
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
PdePreprocessor - wrapper for default ANTLR-generated parser
|
|
Part of the Processing project - http://processing.org
|
|
|
|
Copyright (c) 2004-12 Ben Fry and Casey Reas
|
|
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
|
|
|
ANTLR-generated parser and several supporting classes written
|
|
by Dan Mosedale via funding from the Interaction Institute IVREA.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License 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 java.io.*;
|
|
import java.util.*;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import processing.app.Base;
|
|
import processing.app.Preferences;
|
|
import processing.app.SketchException;
|
|
import processing.core.PApplet;
|
|
import processing.mode.java.preproc.PdeLexer;
|
|
import processing.mode.java.preproc.PdeRecognizer;
|
|
import processing.mode.java.preproc.PdeTokenTypes;
|
|
import antlr.*;
|
|
import antlr.collections.AST;
|
|
|
|
/**
|
|
* Class that orchestrates preprocessing p5 syntax into straight Java.
|
|
* <P/>
|
|
* <B>Current Preprocessor Subsitutions:</B>
|
|
* <UL>
|
|
* <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>. This is important to note when
|
|
* coding with core.jar outside of the PDE.
|
|
* <LI><TT>compiler.substitute_floats</TT> (currently "substitute_f")
|
|
* treat doubles as floats, i.e. 12.3 becomes 12.3f so that people
|
|
* don't have to add f after their numbers all the time since it's
|
|
* confusing for beginners.
|
|
* <LI><TT>compiler.enhanced_casting</TT> byte(), char(), int(), float()
|
|
* works for casting. this is basic in the current implementation, but
|
|
* should be expanded as described above. color() works similarly to int(),
|
|
* however there is also a *function* called color(r, g, b) in p5.
|
|
* <LI><TT>compiler.color_datatype</TT> 'color' is aliased to 'int'
|
|
* as a datatype to represent ARGB packed into a single int, commonly
|
|
* used in p5 for pixels[] and other color operations. this is just a
|
|
* search/replace type thing, and it can be used interchangeably with int.
|
|
* <LI><TT>compiler.web_colors</TT> (currently "inline_web_colors")
|
|
* color c = #cc0080; should unpack to 0xffcc0080 (the ff at the top is
|
|
* so that the color is opaque), which is just an int.
|
|
* </UL>
|
|
* <B>Other preprocessor functionality</B>
|
|
* <UL>
|
|
* <LI>detects what 'mode' the program is in: static (no function
|
|
* brackets at all, just assumes everything is in draw), active
|
|
* (setup plus draw or loop), and java mode (full java support).
|
|
* http://processing.org/reference/environment/
|
|
* </UL>
|
|
* <P/>
|
|
* The PDE Preprocessor is based on the Java Grammar that comes with
|
|
* ANTLR 2.7.2. Moving it forward to a new version of the grammar
|
|
* shouldn't be too difficult.
|
|
* <P/>
|
|
* Here's some info about the various files in this directory:
|
|
* <P/>
|
|
* <TT>java.g:</TT> this is the ANTLR grammar for Java 1.3/1.4 from the
|
|
* ANTLR distribution. It is in the public domain. The only change to
|
|
* this file from the original this file is the uncommenting of the
|
|
* clauses required to support assert().
|
|
* <P/>
|
|
* <TT>java.tree.g:</TT> this describes the Abstract Syntax Tree (AST)
|
|
* generated by java.g. It is only here as a reference for coders hacking
|
|
* on the preprocessor, it is not built or used at all. Note that pde.g
|
|
* overrides some of the java.g rules so that in PDE ASTs, there are a
|
|
* few minor differences. Also in the public domain.
|
|
* <P/>
|
|
* <TT>pde.g:</TT> this is the grammar and lexer for the PDE language
|
|
* itself. It subclasses the java.g grammar and lexer. There are a couple
|
|
* of overrides to java.g that I hope to convince the ANTLR folks to fold
|
|
* back into their grammar, but most of this file is highly specific to
|
|
* PDE itself.
|
|
* <TT>PdeEmitter.java:</TT> this class traverses the AST generated by
|
|
* the PDE Recognizer, and emits it as Java code, doing any necessary
|
|
* transformations along the way. It is based on JavaEmitter.java,
|
|
* available from antlr.org, written by Andy Tripp <atripp@comcast.net>,
|
|
* who has given permission for it to be distributed under the GPL.
|
|
* <P/>
|
|
* <TT>ExtendedCommonASTWithHiddenTokens.java:</TT> this adds a necessary
|
|
* initialize() method, as well as a number of methods to allow for XML
|
|
* serialization of the parse tree in a such a way that the hidden tokens
|
|
* are visible. Much of the code is taken from the original
|
|
* CommonASTWithHiddenTokens class. I hope to convince the ANTLR folks
|
|
* to fold these changes back into that class so that this file will be
|
|
* unnecessary.
|
|
* <P/>
|
|
* <TT>TokenStreamCopyingHiddenTokenFilter.java:</TT> this class provides
|
|
* TokenStreamHiddenTokenFilters with the concept of tokens which can be
|
|
* copied so that they are seen by both the hidden token stream as well
|
|
* as the parser itself. This is useful when one wants to use an
|
|
* existing parser (like the Java parser included with ANTLR) that throws
|
|
* away some tokens to create a parse tree which can be used to spit out
|
|
* a copy of the code with only minor modifications. Partially derived
|
|
* from ANTLR code. I hope to convince the ANTLR folks to fold this
|
|
* functionality back into ANTLR proper as well.
|
|
* <P/>
|
|
* <TT>whitespace_test.pde:</TT> a torture test to ensure that the
|
|
* preprocessor is correctly preserving whitespace, comments, and other
|
|
* hidden tokens correctly. See the comments in the code for details about
|
|
* how to run the test.
|
|
* <P/>
|
|
* All other files in this directory are generated at build time by ANTLR
|
|
* itself. The ANTLR manual goes into a fair amount of detail about the
|
|
* what each type of file is for.
|
|
* <P/>
|
|
*
|
|
* Hacked to death in 2010 by
|
|
* @author Jonathan Feinberg <jdf@pobox.com>
|
|
*/
|
|
public class PdePreprocessor {
|
|
protected static final String UNICODE_ESCAPES = "0123456789abcdefABCDEF";
|
|
|
|
// used for calling the ASTFactory to get the root node
|
|
private static final int ROOT_ID = 0;
|
|
|
|
protected final String indent;
|
|
private final String name;
|
|
|
|
public static enum Mode {
|
|
STATIC, ACTIVE, JAVA
|
|
}
|
|
|
|
private TokenStreamCopyingHiddenTokenFilter filter;
|
|
|
|
// private boolean foundMain;
|
|
private String advClassName = "";
|
|
protected Mode mode;
|
|
HashMap<String, Object> foundMethods;
|
|
|
|
protected String sizeStatement;
|
|
protected String sketchWidth;
|
|
protected String sketchHeight;
|
|
protected String sketchRenderer;
|
|
|
|
/**
|
|
* Regular expression for parsing the size() method. This should match
|
|
* against any uses of the size() function, whether numbers or variables
|
|
* or whatever. This way, no warning is shown if size() isn't actually used
|
|
* in the sketch, which is the case especially for anyone who is cutting
|
|
* and pasting from the reference.
|
|
*/
|
|
public static final String SIZE_REGEX =
|
|
"(?:^|\\s|;)size\\s*\\(\\s*([^\\s,]+)\\s*,\\s*([^\\s,\\)]+)\\s*,?\\s*([^\\)]*)\\s*\\)\\s*\\;";
|
|
//"(?:^|\\s|;)size\\s*\\(\\s*(\\S+)\\s*,\\s*([^\\s,\\)]+),?\\s*([^\\)]*)\\s*\\)\\s*\\;";
|
|
|
|
|
|
private static final Pattern PUBLIC_CLASS =
|
|
Pattern.compile("(^|;)\\s*public\\s+class\\s+\\S+\\s+extends\\s+PApplet", Pattern.MULTILINE);
|
|
// Can't only match any 'public class', needs to be a PApplet
|
|
// http://code.google.com/p/processing/issues/detail?id=551
|
|
//Pattern.compile("(^|;)\\s*public\\s+class", Pattern.MULTILINE);
|
|
|
|
|
|
private static final Pattern FUNCTION_DECL =
|
|
Pattern.compile("(^|;)\\s*((public|private|protected|final|static)\\s+)*" +
|
|
"(void|int|float|double|String|char|byte)" +
|
|
"(\\s*\\[\\s*\\])?\\s+[a-zA-Z0-9]+\\s*\\(",
|
|
Pattern.MULTILINE);
|
|
|
|
|
|
public PdePreprocessor(final String sketchName) {
|
|
this(sketchName, Preferences.getInteger("editor.tabs.size"));
|
|
}
|
|
|
|
|
|
public PdePreprocessor(final String sketchName, final int tabSize) {
|
|
this.name = sketchName;
|
|
final char[] indentChars = new char[tabSize];
|
|
Arrays.fill(indentChars, ' ');
|
|
indent = new String(indentChars);
|
|
}
|
|
|
|
|
|
public String[] initSketchSize(String code, boolean sizeWarning) throws SketchException {
|
|
String[] info = parseSketchSize(code, sizeWarning);
|
|
if (info != null) {
|
|
sizeStatement = info[0];
|
|
sketchWidth = info[1];
|
|
sketchHeight = info[2];
|
|
sketchRenderer = info[3];
|
|
}
|
|
return info;
|
|
}
|
|
|
|
|
|
/**
|
|
* Parse a chunk of code and extract the size() command and its contents.
|
|
* @param code Usually the code from the main tab in the sketch
|
|
* @param fussy true if it should show an error message if bad size()
|
|
* @return null if there was an error, otherwise an array (might contain some/all nulls)
|
|
*/
|
|
static public String[] parseSketchSize(String code, boolean fussy) {
|
|
// This matches against any uses of the size() function, whether numbers
|
|
// or variables or whatever. This way, no warning is shown if size() isn't
|
|
// actually used in the applet, which is the case especially for anyone
|
|
// who is cutting/pasting from the reference.
|
|
|
|
// String scrubbed = scrubComments(sketch.getCode(0).getProgram());
|
|
// String[] matches = PApplet.match(scrubbed, SIZE_REGEX);
|
|
String[] matches = PApplet.match(scrubComments(code), SIZE_REGEX);
|
|
|
|
if (matches != null) {
|
|
boolean badSize = false;
|
|
|
|
if (matches[1].equals("screenWidth") ||
|
|
matches[1].equals("screenHeight") ||
|
|
matches[2].equals("screenWidth") ||
|
|
matches[2].equals("screenHeight")) {
|
|
final String message =
|
|
"The screenWidth and screenHeight variables\n" +
|
|
"are named displayWidth and displayHeight\n" +
|
|
"in this release of Processing.";
|
|
Base.showWarning("Time for a quick update", message, null);
|
|
return null;
|
|
}
|
|
|
|
if (!matches[1].equals("displayWidth") &&
|
|
!matches[1].equals("displayHeight") &&
|
|
PApplet.parseInt(matches[1], -1) == -1) {
|
|
badSize = true;
|
|
}
|
|
if (!matches[2].equals("displayWidth") &&
|
|
!matches[2].equals("displayHeight") &&
|
|
PApplet.parseInt(matches[2], -1) == -1) {
|
|
badSize = true;
|
|
}
|
|
|
|
if (badSize && fussy) {
|
|
// found a reference to size, but it didn't seem to contain numbers
|
|
final String message =
|
|
"The size of this applet could not automatically\n" +
|
|
"be determined from your code. Use only numeric\n" +
|
|
"values (not variables) for the size() command.\n" +
|
|
"See the size() reference for an explanation.";
|
|
Base.showWarning("Could not find sketch size", message, null);
|
|
// new Exception().printStackTrace(System.out);
|
|
return null;
|
|
}
|
|
|
|
// Remove additional space 'round the renderer
|
|
matches[3] = matches[3].trim();
|
|
|
|
// if the renderer entry is empty, set it to null
|
|
if (matches[3].length() == 0) {
|
|
matches[3] = null;
|
|
}
|
|
return matches;
|
|
}
|
|
return new String[] { null, null, null, null }; // not an error, just empty
|
|
}
|
|
|
|
|
|
/**
|
|
* Replace all commented portions of a given String as spaces.
|
|
* Utility function used here and in the preprocessor.
|
|
*/
|
|
static public String scrubComments(String what) {
|
|
char p[] = what.toCharArray();
|
|
// Track quotes to avoid problems with code like: String t = "*/*";
|
|
// http://code.google.com/p/processing/issues/detail?id=1435
|
|
boolean insideQuote = false;
|
|
|
|
int index = 0;
|
|
while (index < p.length) {
|
|
// for any double slash comments, ignore until the end of the line
|
|
if (!insideQuote &&
|
|
(p[index] == '/') &&
|
|
(index < p.length - 1) &&
|
|
(p[index+1] == '/')) {
|
|
p[index++] = ' ';
|
|
p[index++] = ' ';
|
|
while ((index < p.length) &&
|
|
(p[index] != '\n')) {
|
|
p[index++] = ' ';
|
|
}
|
|
|
|
// check to see if this is the start of a new multiline comment.
|
|
// if it is, then make sure it's actually terminated somewhere.
|
|
} else if (!insideQuote &&
|
|
(p[index] == '/') &&
|
|
(index < p.length - 1) &&
|
|
(p[index+1] == '*')) {
|
|
p[index++] = ' ';
|
|
p[index++] = ' ';
|
|
boolean endOfRainbow = false;
|
|
while (index < p.length - 1) {
|
|
if ((p[index] == '*') && (p[index+1] == '/')) {
|
|
p[index++] = ' ';
|
|
p[index++] = ' ';
|
|
endOfRainbow = true;
|
|
break;
|
|
|
|
} else {
|
|
// continue blanking this area
|
|
p[index++] = ' ';
|
|
}
|
|
}
|
|
if (!endOfRainbow) {
|
|
throw new RuntimeException("Missing the */ from the end of a " +
|
|
"/* comment */");
|
|
}
|
|
} else if (p[index] == '"' && index > 0 && p[index-1] != '\\') {
|
|
insideQuote = !insideQuote;
|
|
index++;
|
|
|
|
} else { // any old character, move along
|
|
index++;
|
|
}
|
|
}
|
|
return new String(p);
|
|
}
|
|
|
|
|
|
public void addMethod(String methodName) {
|
|
foundMethods.put(methodName, new Object());
|
|
}
|
|
|
|
|
|
public boolean hasMethod(String methodName) {
|
|
return foundMethods.containsKey(methodName);
|
|
}
|
|
|
|
|
|
// public void setFoundMain(boolean foundMain) {
|
|
// this.foundMain = foundMain;
|
|
// }
|
|
|
|
|
|
// public boolean getFoundMain() {
|
|
// return foundMain;
|
|
// }
|
|
|
|
|
|
public void setAdvClassName(final String advClassName) {
|
|
this.advClassName = advClassName;
|
|
}
|
|
|
|
|
|
public void setMode(final Mode mode) {
|
|
//System.err.println("Setting mode to " + mode);
|
|
this.mode = mode;
|
|
}
|
|
|
|
|
|
CommonHiddenStreamToken getHiddenAfter(final CommonHiddenStreamToken t) {
|
|
return filter.getHiddenAfter(t);
|
|
}
|
|
|
|
|
|
CommonHiddenStreamToken getInitialHiddenToken() {
|
|
return filter.getInitialHiddenToken();
|
|
}
|
|
|
|
|
|
private static int countNewlines(final String s) {
|
|
int count = 0;
|
|
for (int pos = s.indexOf('\n', 0); pos >= 0; pos = s.indexOf('\n', pos + 1))
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
|
|
private static void checkForUnterminatedMultilineComment(final String program)
|
|
throws SketchException {
|
|
final int length = program.length();
|
|
for (int i = 0; i < length; i++) {
|
|
// for any double slash comments, ignore until the end of the line
|
|
if ((program.charAt(i) == '/') && (i < length - 1)
|
|
&& (program.charAt(i + 1) == '/')) {
|
|
i += 2;
|
|
while ((i < length) && (program.charAt(i) != '\n')) {
|
|
i++;
|
|
}
|
|
// check to see if this is the start of a new multiline comment.
|
|
// if it is, then make sure it's actually terminated somewhere.
|
|
} else if ((program.charAt(i) == '/') && (i < length - 1)
|
|
&& (program.charAt(i + 1) == '*')) {
|
|
final int startOfComment = i;
|
|
i += 2;
|
|
boolean terminated = false;
|
|
while (i < length - 1) {
|
|
if ((program.charAt(i) == '*') && (program.charAt(i + 1) == '/')) {
|
|
i += 2;
|
|
terminated = true;
|
|
break;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
if (!terminated) {
|
|
throw new SketchException("Unclosed /* comment */", 0,
|
|
countNewlines(program.substring(0,
|
|
startOfComment)));
|
|
}
|
|
} else if (program.charAt(i) == '"') {
|
|
final int stringStart = i;
|
|
boolean terminated = false;
|
|
for (i++; i < length; i++) {
|
|
final char c = program.charAt(i);
|
|
if (c == '"') {
|
|
terminated = true;
|
|
break;
|
|
} else if (c == '\\') {
|
|
if (i == length - 1) {
|
|
break;
|
|
}
|
|
i++;
|
|
} else if (c == '\n') {
|
|
break;
|
|
}
|
|
}
|
|
if (!terminated) {
|
|
throw new SketchException("Unterminated string constant", 0,
|
|
countNewlines(program.substring(0,
|
|
stringStart)));
|
|
}
|
|
} else if (program.charAt(i) == '\'') {
|
|
i++; // step over the initial quote
|
|
if (i >= length) {
|
|
throw new SketchException("Unterminated character constant (after initial quote)", 0,
|
|
countNewlines(program.substring(0, i)));
|
|
}
|
|
boolean escaped = false;
|
|
if (program.charAt(i) == '\\') {
|
|
i++; // step over the backslash
|
|
escaped = true;
|
|
}
|
|
if (i >= length) {
|
|
throw new SketchException("Unterminated character constant (after backslash)", 0,
|
|
countNewlines(program.substring(0, i)));
|
|
}
|
|
if (escaped && program.charAt(i) == 'u') { // unicode escape sequence?
|
|
i++; // step over the u
|
|
//i += 4; // and the four digit unicode constant
|
|
for (int j = 0; j < 4; j++) {
|
|
if (UNICODE_ESCAPES.indexOf(program.charAt(i)) == -1) {
|
|
throw new SketchException("Bad or unfinished \\uXXXX sequence " +
|
|
"(malformed Unicode character constant)", 0,
|
|
countNewlines(program.substring(0, i)));
|
|
}
|
|
i++;
|
|
}
|
|
} else {
|
|
i++; // step over a single character
|
|
}
|
|
if (i >= length) {
|
|
throw new SketchException("Unterminated character constant", 0,
|
|
countNewlines(program.substring(0, i)));
|
|
}
|
|
if (program.charAt(i) != '\'') {
|
|
throw new SketchException("Badly formed character constant " +
|
|
"(expecting quote, got " + program.charAt(i) + ")", 0,
|
|
countNewlines(program.substring(0, i)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public PreprocessorResult write(final Writer out, String program)
|
|
throws SketchException, RecognitionException, TokenStreamException {
|
|
return write(out, program, null);
|
|
}
|
|
|
|
|
|
public PreprocessorResult write(Writer out, String program,
|
|
String codeFolderPackages[])
|
|
throws SketchException, RecognitionException, TokenStreamException {
|
|
|
|
// these ones have the .* at the end, since a class name might be at the end
|
|
// instead of .* which would make trouble other classes using this can lop
|
|
// off the . and anything after it to produce a package name consistently.
|
|
final ArrayList<String> programImports = new ArrayList<String>();
|
|
|
|
// imports just from the code folder, treated differently
|
|
// than the others, since the imports are auto-generated.
|
|
final ArrayList<String> codeFolderImports = new ArrayList<String>();
|
|
|
|
// need to reset whether or not this has a main()
|
|
// foundMain = false;
|
|
foundMethods = new HashMap<String, Object>();
|
|
|
|
// http://processing.org/bugs/bugzilla/5.html
|
|
if (!program.endsWith("\n")) {
|
|
program += "\n";
|
|
}
|
|
|
|
checkForUnterminatedMultilineComment(program);
|
|
|
|
if (Preferences.getBoolean("preproc.substitute_unicode")) {
|
|
program = substituteUnicode(program);
|
|
}
|
|
|
|
// For 0215, adding } as a legitimate prefix to the import (along with
|
|
// newline and semicolon) for cases where a tab ends with } and an import
|
|
// statement starts the next tab.
|
|
final String importRegexp =
|
|
"((?:^|;|\\})\\s*)(import\\s+)((?:static\\s+)?\\S+)(\\s*;)";
|
|
final Pattern importPattern = Pattern.compile(importRegexp);
|
|
String scrubbed = scrubComments(program);
|
|
Matcher m = null;
|
|
int offset = 0;
|
|
boolean found = false;
|
|
do {
|
|
m = importPattern.matcher(scrubbed);
|
|
found = m.find(offset);
|
|
if (found) {
|
|
// System.out.println("found " + m.groupCount() + " groups");
|
|
String before = m.group(1);
|
|
String piece = m.group(2) + m.group(3) + m.group(4);
|
|
// int len = piece.length(); // how much to trim out
|
|
|
|
if (!ignoreImport(m.group(3))) {
|
|
programImports.add(m.group(3)); // the package name
|
|
}
|
|
|
|
// find index of this import in the program
|
|
int start = m.start() + before.length();
|
|
int stop = start + piece.length();
|
|
// System.out.println(start + " " + stop + " " + piece);
|
|
//System.out.println("found " + m.group(3));
|
|
// System.out.println("removing '" + program.substring(start, stop) + "'");
|
|
|
|
// Remove the import from the main program
|
|
program = program.substring(0, start) + program.substring(stop);
|
|
scrubbed = scrubbed.substring(0, start) + scrubbed.substring(stop);
|
|
// Set the offset to start, because everything between
|
|
// start and stop has been deleted.
|
|
offset = m.start();
|
|
}
|
|
} while (found);
|
|
// System.out.println("program now:");
|
|
// System.out.println(program);
|
|
|
|
if (codeFolderPackages != null) {
|
|
for (String item : codeFolderPackages) {
|
|
codeFolderImports.add(item + ".*");
|
|
}
|
|
}
|
|
|
|
final PrintWriter stream = new PrintWriter(out);
|
|
final int headerOffset =
|
|
writeImports(stream, programImports, codeFolderImports);
|
|
return new PreprocessorResult(mode, headerOffset + 2,
|
|
write(program, stream), programImports);
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/**
|
|
* preprocesses a pde file and writes out a java file
|
|
* @return the class name of the exported Java
|
|
*/
|
|
private String write(final String program, final PrintWriter stream)
|
|
throws SketchException, RecognitionException, TokenStreamException {
|
|
|
|
// Match on the uncommented version, otherwise code inside comments used
|
|
// http://code.google.com/p/processing/issues/detail?id=1404
|
|
String uncomment = scrubComments(program);
|
|
PdeRecognizer parser = createParser(program);
|
|
if (PUBLIC_CLASS.matcher(uncomment).find()) {
|
|
try {
|
|
final PrintStream saved = System.err;
|
|
try {
|
|
// throw away stderr for this tentative parse
|
|
System.setErr(new PrintStream(new ByteArrayOutputStream()));
|
|
parser.javaProgram();
|
|
} finally {
|
|
System.setErr(saved);
|
|
}
|
|
setMode(Mode.JAVA);
|
|
} catch (Exception e) {
|
|
// I can't figure out any other way of resetting the parser.
|
|
parser = createParser(program);
|
|
parser.pdeProgram();
|
|
}
|
|
} else if (FUNCTION_DECL.matcher(uncomment).find()) {
|
|
setMode(Mode.ACTIVE);
|
|
parser.activeProgram();
|
|
} else {
|
|
parser.pdeProgram();
|
|
}
|
|
|
|
// set up the AST for traversal by PdeEmitter
|
|
//
|
|
ASTFactory factory = new ASTFactory();
|
|
AST parserAST = parser.getAST();
|
|
AST rootNode = factory.create(ROOT_ID, "AST ROOT");
|
|
rootNode.setFirstChild(parserAST);
|
|
|
|
makeSimpleMethodsPublic(rootNode);
|
|
|
|
// unclear if this actually works, but it's worth a shot
|
|
//
|
|
//((CommonAST)parserAST).setVerboseStringConversion(
|
|
// true, parser.getTokenNames());
|
|
// (made to use the static version because of jikes 1.22 warning)
|
|
BaseAST.setVerboseStringConversion(true, parser.getTokenNames());
|
|
|
|
final String className;
|
|
if (mode == Mode.JAVA) {
|
|
// if this is an advanced program, the classname is already defined.
|
|
className = getFirstClassName(parserAST);
|
|
} else {
|
|
className = this.name;
|
|
}
|
|
|
|
// if 'null' was passed in for the name, but this isn't
|
|
// a 'java' mode class, then there's a problem, so punt.
|
|
//
|
|
if (className == null)
|
|
return null;
|
|
|
|
// debug
|
|
if (false) {
|
|
final StringWriter buf = new StringWriter();
|
|
final PrintWriter bufout = new PrintWriter(buf);
|
|
writeDeclaration(bufout, className);
|
|
new PdeEmitter(this, bufout).print(rootNode);
|
|
writeFooter(bufout, className);
|
|
debugAST(rootNode, true);
|
|
System.err.println(buf.toString());
|
|
}
|
|
|
|
writeDeclaration(stream, className);
|
|
new PdeEmitter(this, stream).print(rootNode);
|
|
writeFooter(stream, className);
|
|
|
|
// if desired, serialize the parse tree to an XML file. can
|
|
// be viewed usefully with Mozilla or IE
|
|
if (Preferences.getBoolean("preproc.output_parse_tree")) {
|
|
writeParseTree("parseTree.xml", parserAST);
|
|
}
|
|
|
|
return className;
|
|
}
|
|
|
|
|
|
private PdeRecognizer createParser(final String program) {
|
|
// create a lexer with the stream reader, and tell it to handle
|
|
// hidden tokens (eg whitespace, comments) since we want to pass these
|
|
// through so that the line numbers when the compiler reports errors
|
|
// match those that will be highlighted in the PDE IDE
|
|
//
|
|
PdeLexer lexer = new PdeLexer(new StringReader(program));
|
|
lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken");
|
|
|
|
// create the filter for hidden tokens and specify which tokens to
|
|
// hide and which to copy to the hidden text
|
|
//
|
|
filter = new TokenStreamCopyingHiddenTokenFilter(lexer);
|
|
filter.hide(PdePartialTokenTypes.SL_COMMENT);
|
|
filter.hide(PdePartialTokenTypes.ML_COMMENT);
|
|
filter.hide(PdePartialTokenTypes.WS);
|
|
filter.copy(PdePartialTokenTypes.SEMI);
|
|
filter.copy(PdePartialTokenTypes.LPAREN);
|
|
filter.copy(PdePartialTokenTypes.RPAREN);
|
|
filter.copy(PdePartialTokenTypes.LCURLY);
|
|
filter.copy(PdePartialTokenTypes.RCURLY);
|
|
filter.copy(PdePartialTokenTypes.COMMA);
|
|
filter.copy(PdePartialTokenTypes.RBRACK);
|
|
filter.copy(PdePartialTokenTypes.LBRACK);
|
|
filter.copy(PdePartialTokenTypes.COLON);
|
|
filter.copy(PdePartialTokenTypes.TRIPLE_DOT);
|
|
|
|
// Because the meanings of < and > are overloaded to support
|
|
// type arguments and type parameters, we have to treat them
|
|
// as copyable to hidden text (or else the following syntax,
|
|
// such as (); and what not gets lost under certain circumstances)
|
|
// -- jdf
|
|
filter.copy(PdePartialTokenTypes.LT);
|
|
filter.copy(PdePartialTokenTypes.GT);
|
|
filter.copy(PdePartialTokenTypes.SR);
|
|
filter.copy(PdePartialTokenTypes.BSR);
|
|
|
|
// create a parser and set what sort of AST should be generated
|
|
//
|
|
final PdeRecognizer parser = new PdeRecognizer(this, filter);
|
|
|
|
// use our extended AST class
|
|
//
|
|
parser.setASTNodeClass("antlr.ExtendedCommonASTWithHiddenTokens");
|
|
return parser;
|
|
}
|
|
|
|
/**
|
|
* Walk the tree looking for METHOD_DEFs. Any simple METHOD_DEF (one
|
|
* without TYPE_PARAMETERS) lacking an
|
|
* access specifier is given public access.
|
|
* @param node
|
|
*/
|
|
private void makeSimpleMethodsPublic(final AST node) {
|
|
if (node.getType() == PdeTokenTypes.METHOD_DEF) {
|
|
final AST mods = node.getFirstChild();
|
|
final AST oldFirstMod = mods.getFirstChild();
|
|
for (AST mod = oldFirstMod; mod != null; mod = mod.getNextSibling()) {
|
|
final int t = mod.getType();
|
|
if (t == PdeTokenTypes.LITERAL_private ||
|
|
t == PdeTokenTypes.LITERAL_protected ||
|
|
t == PdeTokenTypes.LITERAL_public) {
|
|
return;
|
|
}
|
|
}
|
|
if (mods.getNextSibling().getType() == PdeTokenTypes.TYPE_PARAMETERS) {
|
|
return;
|
|
}
|
|
final CommonHiddenStreamToken publicToken =
|
|
new CommonHiddenStreamToken(PdeTokenTypes.LITERAL_public, "public") {
|
|
{
|
|
setHiddenAfter(new CommonHiddenStreamToken(PdeTokenTypes.WS, " "));
|
|
}
|
|
};
|
|
final AST publicNode = new CommonASTWithHiddenTokens(publicToken);
|
|
publicNode.setNextSibling(oldFirstMod);
|
|
mods.setFirstChild(publicNode);
|
|
} else {
|
|
for (AST kid = node.getFirstChild(); kid != null; kid = kid
|
|
.getNextSibling())
|
|
makeSimpleMethodsPublic(kid);
|
|
}
|
|
}
|
|
|
|
protected void writeParseTree(String filename, AST ast) {
|
|
try {
|
|
PrintStream stream = new PrintStream(new FileOutputStream(filename));
|
|
stream.println("<?xml version=\"1.0\"?>");
|
|
stream.println("<document>");
|
|
OutputStreamWriter writer = new OutputStreamWriter(stream);
|
|
if (ast != null) {
|
|
((CommonAST) ast).xmlSerialize(writer);
|
|
}
|
|
writer.flush();
|
|
stream.println("</document>");
|
|
writer.close();
|
|
} catch (IOException e) {
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param out
|
|
* @param programImports
|
|
* @param codeFolderImports
|
|
* @return the header offset
|
|
*/
|
|
protected int writeImports(final PrintWriter out,
|
|
final List<String> programImports,
|
|
final List<String> codeFolderImports) {
|
|
int count = writeImportList(out, getCoreImports());
|
|
count += writeImportList(out, programImports);
|
|
count += writeImportList(out, codeFolderImports);
|
|
count += writeImportList(out, getDefaultImports());
|
|
return count;
|
|
}
|
|
|
|
protected int writeImportList(PrintWriter out, List<String> imports) {
|
|
return writeImportList(out, imports.toArray(new String[0]));
|
|
}
|
|
|
|
protected int writeImportList(PrintWriter out, String[] imports) {
|
|
int count = 0;
|
|
if (imports != null && imports.length != 0) {
|
|
for (String item : imports) {
|
|
out.println("import " + item + "; ");
|
|
count++;
|
|
}
|
|
out.println();
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Write any required header material (eg imports, class decl stuff)
|
|
*
|
|
* @param out PrintStream to write it to.
|
|
* @param exporting Is this being exported from PDE?
|
|
* @param className Name of the class being created.
|
|
*/
|
|
protected void writeDeclaration(PrintWriter out, String className) {
|
|
if (mode == Mode.JAVA) {
|
|
// Print two blank lines so that the offset doesn't change
|
|
out.println();
|
|
out.println();
|
|
|
|
} else if (mode == Mode.ACTIVE) {
|
|
// Print an extra blank line so the offset is identical to the others
|
|
out.println("public class " + className + " extends PApplet {");
|
|
out.println();
|
|
|
|
} else if (mode == Mode.STATIC) {
|
|
out.println("public class " + className + " extends PApplet {");
|
|
out.println(indent + "public void setup() {");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write any necessary closing text.
|
|
*
|
|
* @param out PrintStream to write it to.
|
|
*/
|
|
protected void writeFooter(PrintWriter out, String className) {
|
|
if (mode == Mode.STATIC) {
|
|
// close off setup() definition
|
|
out.println(indent + indent + "noLoop();");
|
|
out.println(indent + "}");
|
|
out.println();
|
|
}
|
|
|
|
if ((mode == Mode.STATIC) || (mode == Mode.ACTIVE)) {
|
|
if (sketchWidth != null && !hasMethod("sketchWidth")) {
|
|
// Only include if it's a number (a variable will be a problem)
|
|
if (PApplet.parseInt(sketchWidth, -1) != -1 || sketchWidth.equals("displayWidth")) {
|
|
out.println(indent + "public int sketchWidth() { return " + sketchWidth + "; }");
|
|
}
|
|
}
|
|
if (sketchHeight != null && !hasMethod("sketchHeight")) {
|
|
// Only include if it's a number
|
|
if (PApplet.parseInt(sketchHeight, -1) != -1 || sketchHeight.equals("displayHeight")) {
|
|
out.println(indent + "public int sketchHeight() { return " + sketchHeight + "; }");
|
|
}
|
|
}
|
|
if (sketchRenderer != null && !hasMethod("sketchRenderer")) {
|
|
// Only include if it's a known renderer (otherwise it might be a variable)
|
|
if (sketchRenderer.equals("P2D") ||
|
|
sketchRenderer.equals("P3D") ||
|
|
sketchRenderer.equals("OPENGL") ||
|
|
sketchRenderer.equals("JAVA2D")) {
|
|
out.println(indent + "public String sketchRenderer() { return " + sketchRenderer + "; }");
|
|
}
|
|
}
|
|
|
|
if (!hasMethod("main")) {
|
|
out.println(indent + "static public void main(String[] passedArgs) {");
|
|
//out.print(indent + indent + "PApplet.main(new String[] { ");
|
|
out.print(indent + indent + "String[] appletArgs = new String[] { ");
|
|
|
|
if (Preferences.getBoolean("export.application.fullscreen")) {
|
|
out.print("\"" + PApplet.ARGS_FULL_SCREEN + "\", ");
|
|
|
|
String farbe = Preferences.get("run.present.bgcolor");
|
|
out.print("\"" + PApplet.ARGS_BGCOLOR + "=" + farbe + "\", ");
|
|
|
|
if (Preferences.getBoolean("export.application.stop")) {
|
|
farbe = Preferences.get("run.present.stop.color");
|
|
out.print("\"" + PApplet.ARGS_STOP_COLOR + "=" + farbe + "\", ");
|
|
} else {
|
|
out.print("\"" + PApplet.ARGS_HIDE_STOP + "\", ");
|
|
}
|
|
// } else {
|
|
// // This is set initially based on the system control color, just
|
|
// // sets the color for what goes behind the sketch before it's added.
|
|
// String farbe = Preferences.get("run.window.bgcolor");
|
|
// out.print("\"" + PApplet.ARGS_BGCOLOR + "=" + farbe + "\", ");
|
|
}
|
|
out.println("\"" + className + "\" };");
|
|
|
|
out.println(indent + indent + "if (passedArgs != null) {");
|
|
out.println(indent + indent + " PApplet.main(concat(appletArgs, passedArgs));");
|
|
out.println(indent + indent + "} else {");
|
|
out.println(indent + indent + " PApplet.main(appletArgs);");
|
|
out.println(indent + indent + "}");
|
|
|
|
out.println(indent + "}");
|
|
}
|
|
|
|
// close off the class definition
|
|
out.println("}");
|
|
}
|
|
}
|
|
|
|
public String[] getCoreImports() {
|
|
return new String[] {
|
|
"processing.core.*",
|
|
"processing.data.*",
|
|
"processing.event.*",
|
|
"processing.opengl.*"
|
|
};
|
|
}
|
|
|
|
public String[] getDefaultImports() {
|
|
// These may change in-between (if the prefs panel adds this option)
|
|
//String prefsLine = Preferences.get("preproc.imports");
|
|
//return PApplet.splitTokens(prefsLine, ", ");
|
|
return new String[] {
|
|
"java.util.HashMap",
|
|
"java.util.ArrayList",
|
|
"java.io.File",
|
|
"java.io.BufferedReader",
|
|
"java.io.PrintWriter",
|
|
"java.io.InputStream",
|
|
"java.io.OutputStream",
|
|
"java.io.IOException"
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Return true if this import should be removed from the code. This is used
|
|
* for packages like processing.xml which no longer exist.
|
|
* @param pkg something like processing.xml.XMLElement or processing.xml.*
|
|
* @return true if this shouldn't be added to the final code
|
|
*/
|
|
public boolean ignoreImport(String pkg) {
|
|
return false;
|
|
// return pkg.startsWith("processing.xml.");
|
|
}
|
|
|
|
/**
|
|
* Find the first CLASS_DEF node in the tree, and return the name of the
|
|
* class in question.
|
|
*
|
|
* TODO [dmose] right now, we're using a little hack to the grammar to get
|
|
* this info. In fact, we should be descending the AST passed in.
|
|
*/
|
|
String getFirstClassName(AST ast) {
|
|
String t = advClassName;
|
|
advClassName = "";
|
|
return t;
|
|
}
|
|
|
|
public void debugAST(final AST ast, final boolean includeHidden) {
|
|
System.err.println("------------------");
|
|
debugAST(ast, includeHidden, 0);
|
|
}
|
|
|
|
private void debugAST(final AST ast, final boolean includeHidden,
|
|
final int indent) {
|
|
for (int i = 0; i < indent; i++)
|
|
System.err.print(" ");
|
|
if (includeHidden) {
|
|
System.err.print(debugHiddenBefore(ast));
|
|
}
|
|
if (ast.getType() > 0 && !ast.getText().equals(TokenUtil.nameOf(ast))) {
|
|
System.err.print(TokenUtil.nameOf(ast) + "/");
|
|
}
|
|
System.err.print(ast.getText().replace("\n", "\\n"));
|
|
if (includeHidden) {
|
|
System.err.print(debugHiddenAfter(ast));
|
|
}
|
|
System.err.println();
|
|
for (AST kid = ast.getFirstChild(); kid != null; kid = kid.getNextSibling())
|
|
debugAST(kid, includeHidden, indent + 1);
|
|
}
|
|
|
|
private String debugHiddenAfter(AST ast) {
|
|
if (!(ast instanceof antlr.CommonASTWithHiddenTokens))
|
|
return "";
|
|
return debugHiddenTokens(((antlr.CommonASTWithHiddenTokens) ast)
|
|
.getHiddenAfter());
|
|
}
|
|
|
|
private String debugHiddenBefore(AST ast) {
|
|
if (!(ast instanceof antlr.CommonASTWithHiddenTokens))
|
|
return "";
|
|
antlr.CommonHiddenStreamToken child = null, parent = ((antlr.CommonASTWithHiddenTokens) ast)
|
|
.getHiddenBefore();
|
|
|
|
if (parent == null) {
|
|
return "";
|
|
}
|
|
|
|
do {
|
|
child = parent;
|
|
parent = child.getHiddenBefore();
|
|
} while (parent != null);
|
|
|
|
return debugHiddenTokens(child);
|
|
}
|
|
|
|
private String debugHiddenTokens(antlr.CommonHiddenStreamToken t) {
|
|
final StringBuilder sb = new StringBuilder();
|
|
for (; t != null; t = filter.getHiddenAfter(t)) {
|
|
if (sb.length() == 0)
|
|
sb.append("[");
|
|
sb.append(t.getText().replace("\n", "\\n"));
|
|
}
|
|
if (sb.length() > 0)
|
|
sb.append("]");
|
|
return sb.toString();
|
|
}
|
|
} |