mirror of
https://github.com/processing/processing4.git
synced 2026-02-03 13:49:18 +01:00
moving the Java mode over here too
This commit is contained in:
830
java/src/processing/mode/java/AutoFormat.java
Normal file
830
java/src/processing/mode/java/AutoFormat.java
Normal file
@@ -0,0 +1,830 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Original Copyright (c) 1997, 1998 Van Di-Han HO. All Rights Reserved.
|
||||
Updates Copyright (c) 2001 Jason Pell.
|
||||
Further updates Copyright (c) 2003 Martin Gomez, Ateneo de Manila University
|
||||
Additional updates Copyright (c) 2005-10 Ben Fry and Casey Reas
|
||||
Even more updates Copyright (c) 2014 George Bateman
|
||||
|
||||
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, version 2.
|
||||
|
||||
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;
|
||||
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import processing.app.Formatter;
|
||||
import processing.app.Preferences;
|
||||
import processing.core.PApplet;
|
||||
|
||||
/**
|
||||
* Handler for dealing with auto format.
|
||||
* Contributed by Martin Gomez, additional bug fixes by Ben Fry.
|
||||
* Additional fixes by Jonathan Feinberg in March 2010.
|
||||
* <p/>
|
||||
* After some further digging, this code in fact appears to be a modified
|
||||
* version of Jason Pell's GPLed "Java Beautifier" class found
|
||||
* <a href="http://web.archive.org/web/20091018220353/http://geocities.com/jasonpell/programs.html">here</a>,
|
||||
* which is itself based on code from Van Di-Han Ho from
|
||||
* <a href="http://web.archive.org/web/20091027043013/http://www.geocities.com/~starkville/vancbj_idx.html">here</a>.
|
||||
* [Ben Fry, August 2009]
|
||||
*/
|
||||
public class AutoFormat implements Formatter {
|
||||
private char[] chars;
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
private final StringBuilder result = new StringBuilder();
|
||||
|
||||
/** The number of spaces in one indent. Constant. */
|
||||
private int indentValue;
|
||||
|
||||
/** Set when the end of the chars array is reached. */
|
||||
private boolean EOF;
|
||||
|
||||
private boolean inStatementFlag; // in a line of code
|
||||
private boolean overflowFlag; // line overrunning?
|
||||
private boolean startFlag; // No buf has yet been writen to this line.
|
||||
private boolean if_flg;
|
||||
private boolean elseFlag;
|
||||
|
||||
/** -1 if not in array or if just after it, otherwise increases from 0. */
|
||||
private int arrayLevel;
|
||||
private int arrayIndent; // Lowest value of the above for this line.
|
||||
|
||||
/** Number of ? entered without exiting at : of a?b:c structures. */
|
||||
private int conditionalLevel;
|
||||
|
||||
private int[][] sp_flg;
|
||||
private boolean[][] s_ind;
|
||||
private int if_lev;
|
||||
|
||||
/** chars[pos] is where we're at. */
|
||||
private int pos;
|
||||
private int level;
|
||||
|
||||
/** Number of curly brackets entered and not exited,
|
||||
excluding arrays. */
|
||||
private int curlyLvl;
|
||||
|
||||
/** Number of parentheses entered and not exited. */
|
||||
private int parenLevel;
|
||||
|
||||
private boolean[] ind;
|
||||
private int[] p_flg;
|
||||
private int[][] s_tabs;
|
||||
|
||||
/** At every {, this has a true pushed if it's a do-while,
|
||||
and a false otherwise. It is then popped at }. */
|
||||
private Stack<Boolean> doWhileFlags;
|
||||
|
||||
/** At every (, this has a true pushed for if, while, or for,
|
||||
and a false otherwise. Popped at ). */
|
||||
private Stack<Boolean> ifWhileForFlags;
|
||||
|
||||
private boolean jdoc_flag;
|
||||
|
||||
/** The number of times to indent at a given point */
|
||||
private int tabs;
|
||||
|
||||
/** The last non-space seen by nextChar(). */
|
||||
private char lastNonWhitespace = 0;
|
||||
|
||||
|
||||
private void handleMultiLineComment() {
|
||||
final boolean savedStartFlag = startFlag;
|
||||
buf.append(nextChar()); // So /*/ isn't self-closing.
|
||||
|
||||
for (char ch = nextChar(); !EOF; ch = nextChar()) {
|
||||
buf.append(ch);
|
||||
while (ch != '/' && !EOF) {
|
||||
if (ch == '\n') {
|
||||
writeIndentedComment();
|
||||
startFlag = true;
|
||||
}
|
||||
buf.append(ch = nextChar());
|
||||
}
|
||||
if (buf.length() >= 2 && buf.charAt(buf.length() - 2) == '*') {
|
||||
jdoc_flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writeIndentedComment();
|
||||
startFlag = savedStartFlag;
|
||||
jdoc_flag = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pumps nextChar into buf until \n or EOF, then calls
|
||||
* writeIndentedLine() and sets startFlag to true.
|
||||
*/
|
||||
private void handleSingleLineComment() {
|
||||
char ch = nextChar();
|
||||
while (ch != '\n' && !EOF) {
|
||||
buf.append(ch);
|
||||
ch = nextChar();
|
||||
}
|
||||
writeIndentedLine();
|
||||
startFlag = true;
|
||||
}
|
||||
|
||||
|
||||
private void writeIndentedLine() {
|
||||
if (buf.length() == 0) {
|
||||
if (startFlag) startFlag = elseFlag = false;
|
||||
return;
|
||||
}
|
||||
if (startFlag) {
|
||||
// Indent suppressed at eg. if<nl>{ and when
|
||||
// buf is close-brackets only followed by ';'.
|
||||
boolean indentMore = !buf.toString().matches("[\\s\\]\\}\\)]+;")
|
||||
&& (buf.charAt(0) != '{' || arrayLevel >= 0)
|
||||
&& overflowFlag;
|
||||
if (indentMore) {
|
||||
tabs++;
|
||||
if (arrayIndent > 0) tabs += arrayIndent;
|
||||
}
|
||||
printIndentation();
|
||||
startFlag = false;
|
||||
if (indentMore) {
|
||||
tabs--;
|
||||
if (arrayIndent > 0) tabs -= arrayIndent;
|
||||
}
|
||||
}
|
||||
if (lastNonSpaceChar() == '}' && bufStarts("else")) {
|
||||
result.append(' ');
|
||||
}
|
||||
|
||||
if (elseFlag) {
|
||||
if (lastNonSpaceChar() == '}') {
|
||||
trimRight(result);
|
||||
result.append(' ');
|
||||
}
|
||||
elseFlag = false;
|
||||
}
|
||||
|
||||
// If we're still in a statement at \n, that's overflow.
|
||||
overflowFlag = inStatementFlag;
|
||||
arrayIndent = arrayLevel;
|
||||
result.append(buf);
|
||||
|
||||
buf.setLength(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the last character in <tt>result</tt> not ' ' or '\n'.
|
||||
*/
|
||||
private char lastNonSpaceChar() {
|
||||
for (int i = result.length() - 1; i >= 0; i--) {
|
||||
char chI = result.charAt(i);
|
||||
if (chI != ' ' && chI != '\n') return chI;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by handleMultilineComment.<br />
|
||||
* Sets jdoc_flag if at the start of a doc comment.
|
||||
* Sends buf to result with proper indents, then clears buf.<br />
|
||||
* Does nothing if buf is empty.
|
||||
*/
|
||||
private void writeIndentedComment() {
|
||||
if (buf.length() == 0) return;
|
||||
|
||||
int firstNonSpace = 0;
|
||||
while (buf.charAt(firstNonSpace) == ' ') firstNonSpace++;
|
||||
if (lookup_com("/**")) jdoc_flag = true;
|
||||
|
||||
if (startFlag) printIndentation();
|
||||
|
||||
if (buf.charAt(firstNonSpace) == '/' && buf.charAt(firstNonSpace+1) == '*') {
|
||||
if (startFlag && lastNonWhitespace != ';') {
|
||||
result.append(buf.substring(firstNonSpace));
|
||||
} else {
|
||||
result.append(buf);
|
||||
}
|
||||
} else {
|
||||
if (buf.charAt(firstNonSpace) == '*' || !jdoc_flag) {
|
||||
result.append(" " + buf.substring(firstNonSpace));
|
||||
} else {
|
||||
result.append(" * " + buf.substring(firstNonSpace));
|
||||
}
|
||||
}
|
||||
buf.setLength(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes tabs >= 0 and appends <tt>tabs*indentValue</tt>
|
||||
* spaces to result.
|
||||
*/
|
||||
private void printIndentation() {
|
||||
if (tabs <= 0) {
|
||||
tabs = 0;
|
||||
return;
|
||||
}
|
||||
final int spaces = tabs * indentValue;
|
||||
for (int i = 0; i < spaces; i++) {
|
||||
result.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return <tt>chars[pos+1]</tt> or '\0' if out-of-bounds.
|
||||
*/
|
||||
private char peek() {
|
||||
return (pos + 1 >= chars.length) ? 0 : chars[pos + 1];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets pos to the position of the next character that is not ' '
|
||||
* in chars. If chars[pos] != ' ' already, it will still move on.
|
||||
* Then sets EOF if pos has reached the end, or reverses pos by 1 if it
|
||||
* has not.
|
||||
* <br/> Does nothing if EOF.
|
||||
* @param allWsp (boolean) Eat newlines too (all of Character.isWhiteSpace()).
|
||||
*/
|
||||
private void advanceToNonSpace(boolean allWsp) {
|
||||
if (EOF) return;
|
||||
|
||||
if (allWsp) {
|
||||
do {
|
||||
pos++;
|
||||
} while (pos < chars.length && Character.isWhitespace(chars[pos]));
|
||||
} else {
|
||||
do {
|
||||
pos++;
|
||||
} while (pos < chars.length && chars[pos] == ' ');
|
||||
}
|
||||
|
||||
if (pos == chars.length - 1) {
|
||||
EOF = true;
|
||||
} else {
|
||||
pos--; // reset for nextChar()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Increments pos, sets EOF if needed, and returns the new
|
||||
* chars[pos] or zero if out-of-bounds.
|
||||
* Sets lastNonWhitespace if chars[pos] isn't whitespace.
|
||||
* Does nothing and returns zero if already at EOF.
|
||||
*/
|
||||
private char nextChar() {
|
||||
if (EOF) return 0;
|
||||
pos++;
|
||||
if (pos >= chars.length - 1) EOF = true;
|
||||
if (pos >= chars.length) return 0;
|
||||
|
||||
char retVal = chars[pos];
|
||||
if (!Character.isWhitespace(retVal)) lastNonWhitespace = retVal;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called after else.
|
||||
*/
|
||||
private void gotElse() {
|
||||
tabs = s_tabs[curlyLvl][if_lev];
|
||||
p_flg[level] = sp_flg[curlyLvl][if_lev];
|
||||
ind[level] = s_ind[curlyLvl][if_lev];
|
||||
if_flg = true;
|
||||
// We can't expect else to be followed by a semicolon, so must
|
||||
// end the statemant manually.
|
||||
inStatementFlag = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pump any '\t' and ' ' to buf, handle any following comment,
|
||||
* and if the next character is '\n', discard it.
|
||||
* @return Whether a '\n' was found and discarded.
|
||||
*/
|
||||
private boolean readForNewLine() {
|
||||
final int savedTabs = tabs;
|
||||
char c = peek();
|
||||
while (!EOF && (c == '\t' || c == ' ')) {
|
||||
buf.append(nextChar());
|
||||
c = peek();
|
||||
}
|
||||
|
||||
if (c == '/') {
|
||||
buf.append(nextChar());
|
||||
c = peek();
|
||||
if (c == '*') {
|
||||
buf.append(nextChar());
|
||||
handleMultiLineComment();
|
||||
} else if (c == '/') {
|
||||
buf.append(nextChar());
|
||||
handleSingleLineComment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
c = peek();
|
||||
if (c == '\n') {
|
||||
// eat it
|
||||
nextChar();
|
||||
tabs = savedTabs;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return last non-wsp in result+buf, or 0 on error.
|
||||
*/
|
||||
private char prevNonWhitespace() {
|
||||
StringBuffer tot = new StringBuffer();
|
||||
tot.append(result);
|
||||
tot.append(buf);
|
||||
for (int i = tot.length()-1; i >= 0; i--) {
|
||||
if (!Character.isWhitespace(tot.charAt(i)))
|
||||
return tot.charAt(i);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sees if buf is of the form [optional whitespace][keyword][optional anything].
|
||||
* It won't allow keyword to be directly followed by an alphanumeric, _, or &.
|
||||
* Will be different if keyword contains regex codes.
|
||||
*/
|
||||
private boolean bufStarts(final String keyword) {
|
||||
return Pattern.matches("^\\s*" + keyword + "(?![a-zA-Z0-9_&]).*$", buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sees if buf is of the form [optional anything][keyword][optional whitespace].
|
||||
* It won't allow keyword to be directly preceded by an alphanumeric, _, or &.
|
||||
* Will be different if keyword contains regex codes.
|
||||
*/
|
||||
private boolean bufEnds(final String keyword) {
|
||||
return Pattern.matches("^.*(?<![a-zA-Z0-9_&])" + keyword + "\\s*$", buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allows you to increase if_lev safely. Enlarges related arrays where needed;
|
||||
* does not change if_lev itself.
|
||||
*/
|
||||
private void if_levSafe() {
|
||||
if (s_tabs[0].length <= if_lev) {
|
||||
for (int i = 0; i < s_tabs.length; i++) {
|
||||
s_tabs[i] = PApplet.expand(s_tabs[i]);
|
||||
}
|
||||
}
|
||||
if (sp_flg[0].length <= if_lev) {
|
||||
for (int i = 0; i < sp_flg.length; i++) {
|
||||
sp_flg[i] = PApplet.expand(sp_flg[i]);
|
||||
}
|
||||
}
|
||||
if (s_ind[0].length <= if_lev) {
|
||||
for (int i = 0; i < s_ind.length; i++) {
|
||||
s_ind[i] = PApplet.expand(s_ind[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sees if buf is of the form [optional whitespace][keyword][optional anything].
|
||||
* It *will* allow keyword to be directly followed by an alphanumeric, _, or &.
|
||||
* Will be different if keyword contains regex codes (except *, which is fine).
|
||||
*/
|
||||
private boolean lookup_com(final String keyword) {
|
||||
final String regex = "^\\s*" + keyword.replace("*", "\\*") + ".*$";
|
||||
return Pattern.matches(regex, buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes all whitespace off the end of its argument.
|
||||
*/
|
||||
private void trimRight(final StringBuilder sb) {
|
||||
while (sb.length() >= 1 && Character.isWhitespace(sb.charAt(sb.length() - 1)))
|
||||
sb.setLength(sb.length() - 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
public String format(final String source) {
|
||||
final String normalizedText = source.replaceAll("\r", "");
|
||||
final String cleanText =
|
||||
normalizedText + (normalizedText.endsWith("\n") ? "" : "\n");
|
||||
|
||||
// Globals' description at top of file.
|
||||
result.setLength(0);
|
||||
indentValue = Preferences.getInteger("editor.tabs.size");
|
||||
|
||||
boolean forFlag = if_flg = false;
|
||||
startFlag = true;
|
||||
int forParenthLevel = 0;
|
||||
conditionalLevel = parenLevel = curlyLvl = if_lev = level = 0;
|
||||
tabs = 0;
|
||||
jdoc_flag = inStatementFlag = overflowFlag = false;
|
||||
pos = arrayLevel = -1;
|
||||
|
||||
int[] s_level = new int[10];
|
||||
sp_flg = new int[20][10];
|
||||
s_ind = new boolean[20][10];
|
||||
int[] s_if_lev = new int[10]; // Stack
|
||||
boolean[] s_if_flg = new boolean[10]; // Stack
|
||||
ind = new boolean[10];
|
||||
p_flg = new int[10];
|
||||
s_tabs = new int[20][10];
|
||||
doWhileFlags = new Stack<Boolean>();
|
||||
ifWhileForFlags = new Stack<Boolean>();
|
||||
|
||||
chars = cleanText.toCharArray();
|
||||
|
||||
EOF = false; // set in nextChar() when EOF
|
||||
|
||||
while (!EOF) {
|
||||
char c = nextChar();
|
||||
|
||||
switch (c) {
|
||||
default:
|
||||
inStatementFlag = true;
|
||||
buf.append(c);
|
||||
break;
|
||||
|
||||
case ',':
|
||||
inStatementFlag = true;
|
||||
trimRight(buf);
|
||||
buf.append(", ");
|
||||
advanceToNonSpace(false);
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
case '\t':
|
||||
elseFlag = bufEnds("else");
|
||||
if (elseFlag) {
|
||||
gotElse();
|
||||
if (!startFlag || buf.length() > 0) {
|
||||
buf.append(c);
|
||||
}
|
||||
|
||||
writeIndentedLine();
|
||||
startFlag = false;
|
||||
break;
|
||||
}
|
||||
// Only allow in the line, to nuke the old indent.
|
||||
if (!startFlag || buf.length() > 0) buf.append(c);
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
if (EOF) break;
|
||||
|
||||
elseFlag = bufEnds("else");
|
||||
if (elseFlag) gotElse();
|
||||
|
||||
if (lookup_com("//")) {
|
||||
if (buf.charAt(buf.length() - 1) == '\n') {
|
||||
buf.setLength(buf.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (elseFlag) {
|
||||
writeIndentedLine();
|
||||
result.append("\n");
|
||||
|
||||
p_flg[level]++;
|
||||
tabs++;
|
||||
} else {
|
||||
writeIndentedLine();
|
||||
result.append("\n");
|
||||
}
|
||||
startFlag = true;
|
||||
break;
|
||||
|
||||
case '{':
|
||||
elseFlag = bufEnds("else");
|
||||
if (elseFlag) gotElse();
|
||||
|
||||
doWhileFlags.push(Boolean.valueOf(bufEnds("do")));
|
||||
|
||||
char prevChar = prevNonWhitespace();
|
||||
if (arrayLevel >= 0 || prevChar == '=' || prevChar == ']') {
|
||||
// If we're already in an array (lvl >= 0), increment level.
|
||||
// Otherwise, the presence of a = or ] indicates an array is starting
|
||||
// and we should start counting (set lvl=0).
|
||||
arrayLevel++;
|
||||
buf.append(c);
|
||||
break; // Nothing fancy.
|
||||
}
|
||||
|
||||
inStatementFlag = false; // eg. class declaration ends
|
||||
|
||||
if (s_if_lev.length == curlyLvl) {
|
||||
s_if_lev = PApplet.expand(s_if_lev);
|
||||
s_if_flg = PApplet.expand(s_if_flg);
|
||||
}
|
||||
s_if_lev[curlyLvl] = if_lev;
|
||||
s_if_flg[curlyLvl] = if_flg;
|
||||
if_lev = 0;
|
||||
if_flg = false;
|
||||
curlyLvl++;
|
||||
if (startFlag && p_flg[level] != 0) {
|
||||
p_flg[level]--;
|
||||
tabs--;
|
||||
}
|
||||
|
||||
trimRight(buf);
|
||||
if (buf.length() > 0 || (result.length() > 0 &&
|
||||
!Character.isWhitespace(result.charAt(result.length() - 1)))) {
|
||||
buf.append(" ");
|
||||
}
|
||||
buf.append(c);
|
||||
writeIndentedLine();
|
||||
readForNewLine();
|
||||
writeIndentedLine();
|
||||
|
||||
result.append('\n');
|
||||
tabs++;
|
||||
startFlag = true;
|
||||
|
||||
if (p_flg[level] > 0) {
|
||||
ind[level] = true;
|
||||
level++;
|
||||
s_level[level] = curlyLvl;
|
||||
}
|
||||
break;
|
||||
|
||||
case '}':
|
||||
if (arrayLevel >= 0) {
|
||||
// Even less fancy. Note that }s cannot end array behaviour;
|
||||
// a semicolon is needed.
|
||||
if (arrayLevel > 0) arrayLevel--;
|
||||
if (arrayIndent > arrayLevel) arrayIndent = arrayLevel;
|
||||
buf.append(c);
|
||||
break;
|
||||
}
|
||||
|
||||
inStatementFlag = false;
|
||||
|
||||
curlyLvl--;
|
||||
if (curlyLvl < 0) {
|
||||
curlyLvl = 0;
|
||||
buf.append(c);
|
||||
writeIndentedLine();
|
||||
} else {
|
||||
if_lev = s_if_lev[curlyLvl] - 1;
|
||||
if (if_lev < 0) if_lev = 0;
|
||||
if_levSafe();
|
||||
|
||||
if_flg = s_if_flg[curlyLvl];
|
||||
trimRight(buf);
|
||||
writeIndentedLine();
|
||||
tabs--;
|
||||
|
||||
trimRight(result);
|
||||
result.append('\n');
|
||||
printIndentation();
|
||||
result.append(c);
|
||||
if (peek() == ';') result.append(nextChar());
|
||||
|
||||
// doWhileFlags contains a TRUE if and only if the
|
||||
// corresponding { was preceeded (bufEnds) by "do".
|
||||
// Just checking for while would fail on if(){} while(){}.
|
||||
if (doWhileFlags.empty() || !doWhileFlags.pop().booleanValue()
|
||||
|| !new String(chars, pos+1, chars.length-pos-1).trim().startsWith("while")) {
|
||||
readForNewLine();
|
||||
writeIndentedLine();
|
||||
result.append('\n');
|
||||
startFlag = true;
|
||||
} else {
|
||||
// Correct spacing in "} while".
|
||||
result.append(' ');
|
||||
advanceToNonSpace(true);
|
||||
startFlag = false;
|
||||
}
|
||||
|
||||
if (curlyLvl < s_level[level] && level > 0) level--;
|
||||
|
||||
if (ind[level]) {
|
||||
tabs -= p_flg[level];
|
||||
p_flg[level] = 0;
|
||||
ind[level] = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
inStatementFlag = true;
|
||||
buf.append(c);
|
||||
char cc = nextChar();
|
||||
while (!EOF && cc != c) {
|
||||
buf.append(cc);
|
||||
if (cc == '\\') {
|
||||
buf.append(cc = nextChar());
|
||||
}
|
||||
if (cc == '\n') {
|
||||
writeIndentedLine();
|
||||
startFlag = true;
|
||||
}
|
||||
cc = nextChar();
|
||||
}
|
||||
buf.append(cc);
|
||||
if (readForNewLine()) {
|
||||
// push a newline into the stream
|
||||
chars[pos--] = '\n';
|
||||
}
|
||||
break;
|
||||
|
||||
case ';':
|
||||
if (forFlag) {
|
||||
// This is like a comma.
|
||||
trimRight(buf);
|
||||
buf.append("; ");
|
||||
// Not non-whitespace: allow \n.
|
||||
advanceToNonSpace(false);
|
||||
break;
|
||||
}
|
||||
buf.append(c);
|
||||
inStatementFlag = false;
|
||||
writeIndentedLine();
|
||||
if (p_flg[level] > 0 && !ind[level]) {
|
||||
tabs -= p_flg[level];
|
||||
p_flg[level] = 0;
|
||||
}
|
||||
readForNewLine();
|
||||
writeIndentedLine();
|
||||
result.append("\n");
|
||||
startFlag = true;
|
||||
// Array behaviour ends at the end of a statement.
|
||||
arrayLevel = -1;
|
||||
|
||||
if (if_lev > 0) {
|
||||
if (if_flg) {
|
||||
if_lev--;
|
||||
if_flg = false;
|
||||
} else {
|
||||
if_lev = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
buf.append(c);
|
||||
buf.append(nextChar());
|
||||
break;
|
||||
|
||||
case '?':
|
||||
conditionalLevel++;
|
||||
buf.append(c);
|
||||
break;
|
||||
|
||||
case ':':
|
||||
// Java 8 :: operator.
|
||||
if (peek() == ':') {
|
||||
result.append(c).append(nextChar());
|
||||
break;
|
||||
}
|
||||
|
||||
// End a?b:c structures.
|
||||
else if (conditionalLevel > 0) {
|
||||
conditionalLevel--;
|
||||
buf.append(c);
|
||||
break;
|
||||
}
|
||||
|
||||
else if (forFlag) {
|
||||
trimRight(buf);
|
||||
buf.append(" : ");
|
||||
// Not to non-whitespace: allow \n.
|
||||
advanceToNonSpace(false);
|
||||
break;
|
||||
}
|
||||
|
||||
buf.append(c);
|
||||
inStatementFlag = false;
|
||||
arrayLevel = -1; // Unlikely to be needed; just in case.
|
||||
|
||||
//Same format for case, default, and other labels.
|
||||
tabs--;
|
||||
writeIndentedLine();
|
||||
tabs++;
|
||||
|
||||
readForNewLine();
|
||||
writeIndentedLine();
|
||||
result.append('\n');
|
||||
startFlag = true;
|
||||
break;
|
||||
|
||||
case '/':
|
||||
final char next = peek();
|
||||
if (next == '/') {
|
||||
// call nextChar to move on.
|
||||
buf.append(c).append(nextChar());
|
||||
handleSingleLineComment();
|
||||
result.append("\n");
|
||||
} else if (next == '*') {
|
||||
if (buf.length() > 0) {
|
||||
writeIndentedLine();
|
||||
}
|
||||
buf.append(c).append(nextChar());
|
||||
handleMultiLineComment();
|
||||
} else {
|
||||
buf.append(c);
|
||||
}
|
||||
break;
|
||||
|
||||
case ')':
|
||||
parenLevel--;
|
||||
|
||||
// If we're further back than the start of a for loop, we've
|
||||
// left it.
|
||||
if (forFlag && forParenthLevel > parenLevel) forFlag = false;
|
||||
|
||||
if (parenLevel < 0) parenLevel = 0;
|
||||
buf.append(c);
|
||||
|
||||
boolean wasIfEtc = !ifWhileForFlags.empty() && ifWhileForFlags.pop().booleanValue();
|
||||
if (wasIfEtc) {
|
||||
inStatementFlag = false;
|
||||
arrayLevel = -1; // This is important as it allows arrays in if statements.
|
||||
}
|
||||
|
||||
writeIndentedLine();
|
||||
// Short-circuiting means readForNewLine is only called for if/while/for;
|
||||
// this is important.
|
||||
if (wasIfEtc && readForNewLine()) {
|
||||
chars[pos] = '\n';
|
||||
pos--; // Make nextChar() return the new '\n'.
|
||||
if (parenLevel == 0) {
|
||||
p_flg[level]++;
|
||||
tabs++;
|
||||
ind[level] = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '(':
|
||||
final boolean isFor = bufEnds("for");
|
||||
final boolean isIf = bufEnds("if");
|
||||
|
||||
if (isFor || isIf || bufEnds("while")) {
|
||||
if (!Character.isWhitespace(buf.charAt(buf.length() - 1))) {
|
||||
buf.append(' ');
|
||||
}
|
||||
ifWhileForFlags.push(true);
|
||||
} else {
|
||||
ifWhileForFlags.push(false);
|
||||
}
|
||||
|
||||
buf.append(c);
|
||||
parenLevel++;
|
||||
|
||||
// isFor says "Is it the start of a for?". If it is, we set forFlag and
|
||||
// forParenthLevel. If it is not parenth_lvl was incremented above and
|
||||
// that's it.
|
||||
if (isFor && !forFlag) {
|
||||
forParenthLevel = parenLevel;
|
||||
forFlag = true;
|
||||
} else if (isIf) {
|
||||
writeIndentedLine();
|
||||
s_tabs[curlyLvl][if_lev] = tabs;
|
||||
sp_flg[curlyLvl][if_lev] = p_flg[level];
|
||||
s_ind[curlyLvl][if_lev] = ind[level];
|
||||
if_lev++;
|
||||
if_levSafe();
|
||||
if_flg = true;
|
||||
}
|
||||
} // end switch
|
||||
} // end while not EOF
|
||||
|
||||
if (buf.length() > 0) writeIndentedLine();
|
||||
|
||||
final String formatted = result.toString();
|
||||
return formatted.equals(cleanText) ? source : formatted;
|
||||
}
|
||||
}
|
||||
404
java/src/processing/mode/java/Commander.java
Normal file
404
java/src/processing/mode/java/Commander.java
Normal file
@@ -0,0 +1,404 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2008-12 Ben Fry and Casey Reas
|
||||
|
||||
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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.app.contrib.ModeContribution;
|
||||
import processing.core.PApplet;
|
||||
import processing.mode.java.runner.*;
|
||||
|
||||
|
||||
/**
|
||||
* Class to handle running Processing from the command line.
|
||||
*
|
||||
* @author fry
|
||||
*/
|
||||
public class Commander implements RunnerListener {
|
||||
static final String helpArg = "--help";
|
||||
// static final String preprocArg = "--preprocess";
|
||||
static final String buildArg = "--build";
|
||||
static final String runArg = "--run";
|
||||
static final String presentArg = "--present";
|
||||
static final String sketchArg = "--sketch=";
|
||||
static final String forceArg = "--force";
|
||||
static final String outputArg = "--output=";
|
||||
static final String exportApplicationArg = "--export";
|
||||
static final String noJavaArg = "--no-java";
|
||||
static final String platformArg = "--platform=";
|
||||
static final String bitsArg = "--bits=";
|
||||
// static final String preferencesArg = "--preferences=";
|
||||
|
||||
static final int HELP = -1;
|
||||
static final int PREPROCESS = 0;
|
||||
static final int BUILD = 1;
|
||||
static final int RUN = 2;
|
||||
static final int PRESENT = 3;
|
||||
// static final int EXPORT_APPLET = 4;
|
||||
static final int EXPORT = 4;
|
||||
|
||||
Sketch sketch;
|
||||
|
||||
PrintStream systemOut;
|
||||
PrintStream systemErr;
|
||||
|
||||
|
||||
static public void main(String[] args) {
|
||||
// Do this early so that error messages go to the console
|
||||
Base.setCommandLine();
|
||||
// init the platform so that prefs and other native code is ready to go
|
||||
Base.initPlatform();
|
||||
// make sure a full JDK is installed
|
||||
Base.initRequirements();
|
||||
|
||||
// launch command line handler
|
||||
new Commander(args);
|
||||
}
|
||||
|
||||
|
||||
public Commander(String[] args) {
|
||||
String sketchPath = null;
|
||||
File sketchFolder = null;
|
||||
String pdePath = null; // path to the .pde file
|
||||
String outputPath = null;
|
||||
File outputFolder = null;
|
||||
boolean outputSet = false; // set an output folder
|
||||
boolean force = false; // replace that no good output folder
|
||||
// String preferencesPath = null;
|
||||
int platform = PApplet.platform; // default to this platform
|
||||
// int platformBits = 0;
|
||||
int platformBits = Base.getNativeBits();
|
||||
int task = HELP;
|
||||
boolean embedJava = true;
|
||||
|
||||
// Turns out the output goes as MacRoman or something else useless.
|
||||
// http://code.google.com/p/processing/issues/detail?id=1418
|
||||
try {
|
||||
systemOut = new PrintStream(System.out, true, "UTF-8");
|
||||
systemErr = new PrintStream(System.err, true, "UTF-8");
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// File preferencesFile = Base.getSettingsFile("preferences.txt");
|
||||
// System.out.println("Preferences file at " + preferencesFile.getAbsolutePath());
|
||||
|
||||
for (String arg : args) {
|
||||
if (arg.length() == 0) {
|
||||
// ignore it, just the crappy shell script
|
||||
|
||||
} else if (arg.equals(helpArg)) {
|
||||
// mode already set to HELP
|
||||
|
||||
// } else if (arg.equals(preprocArg)) {
|
||||
// task = PREPROCESS;
|
||||
|
||||
} else if (arg.equals(buildArg)) {
|
||||
task = BUILD;
|
||||
|
||||
} else if (arg.equals(runArg)) {
|
||||
task = RUN;
|
||||
|
||||
} else if (arg.equals(presentArg)) {
|
||||
task = PRESENT;
|
||||
|
||||
// } else if (arg.equals(exportAppletArg)) {
|
||||
// task = EXPORT_APPLET;
|
||||
|
||||
} else if (arg.equals(exportApplicationArg)) {
|
||||
task = EXPORT;
|
||||
|
||||
} else if (arg.equals(noJavaArg)) {
|
||||
embedJava = false;
|
||||
|
||||
} else if (arg.startsWith(platformArg)) {
|
||||
complainAndQuit("The --platform option has been removed from Processing 2.1.", false);
|
||||
// String platformStr = arg.substring(platformArg.length());
|
||||
// platform = Base.getPlatformIndex(platformStr);
|
||||
// if (platform == -1) {
|
||||
// complainAndQuit(platformStr + " should instead be " +
|
||||
// "'windows', 'macosx', or 'linux'.", true);
|
||||
// }
|
||||
|
||||
} else if (arg.startsWith(bitsArg)) {
|
||||
complainAndQuit("The --bits option has been removed from Processing 2.1.", false);
|
||||
// String bitsStr = arg.substring(bitsArg.length());
|
||||
// if (bitsStr.equals("32")) {
|
||||
// platformBits = 32;
|
||||
// } else if (bitsStr.equals("64")) {
|
||||
// platformBits = 64;
|
||||
// } else {
|
||||
// complainAndQuit("Bits should be either 32 or 64, not " + bitsStr, true);
|
||||
// }
|
||||
|
||||
} else if (arg.startsWith(sketchArg)) {
|
||||
sketchPath = arg.substring(sketchArg.length());
|
||||
sketchFolder = new File(sketchPath);
|
||||
if (!sketchFolder.exists()) {
|
||||
complainAndQuit(sketchFolder + " does not exist.", false);
|
||||
}
|
||||
File pdeFile = new File(sketchFolder, sketchFolder.getName() + ".pde");
|
||||
if (!pdeFile.exists()) {
|
||||
complainAndQuit("Not a valid sketch folder. " + pdeFile + " does not exist.", true);
|
||||
}
|
||||
pdePath = pdeFile.getAbsolutePath();
|
||||
|
||||
// } else if (arg.startsWith(preferencesArg)) {
|
||||
// preferencesPath = arg.substring(preferencesArg.length());
|
||||
|
||||
} else if (arg.startsWith(outputArg)) {
|
||||
outputSet = true;
|
||||
outputPath = arg.substring(outputArg.length());
|
||||
|
||||
} else if (arg.equals(forceArg)) {
|
||||
force = true;
|
||||
|
||||
} else {
|
||||
complainAndQuit("I don't know anything about " + arg + ".", true);
|
||||
}
|
||||
}
|
||||
|
||||
// if ((outputPath == null) &&
|
||||
// (task == PREPROCESS || task == BUILD ||
|
||||
// task == RUN || task == PRESENT)) {
|
||||
// complainAndQuit("An output path must be specified when using " +
|
||||
// preprocArg + ", " + buildArg + ", " +
|
||||
// runArg + ", or " + presentArg + ".");
|
||||
// }
|
||||
if (task == HELP) {
|
||||
printCommandLine(systemOut);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (outputSet) {
|
||||
if (outputPath == null) {
|
||||
complainAndQuit("An output path must be specified.", true);
|
||||
}
|
||||
|
||||
outputFolder = new File(outputPath);
|
||||
if (outputFolder.exists()) {
|
||||
if (force) {
|
||||
Base.removeDir(outputFolder);
|
||||
} else {
|
||||
complainAndQuit("The output folder already exists. " +
|
||||
"Use --force to remove it.", false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!outputFolder.mkdirs()) {
|
||||
complainAndQuit("Could not create the output folder.", false);
|
||||
}
|
||||
}
|
||||
|
||||
// // run static initialization that grabs all the prefs
|
||||
// // (also pass in a prefs path if that was specified)
|
||||
// if (preferencesPath != null) {
|
||||
// Preferences.init(preferencesPath);
|
||||
// }
|
||||
|
||||
Preferences.init();
|
||||
Base.locateSketchbookFolder();
|
||||
|
||||
if (sketchPath == null) {
|
||||
complainAndQuit("No sketch path specified.", true);
|
||||
|
||||
// } else if (!pdePath.toLowerCase().endsWith(".pde")) {
|
||||
// complainAndQuit("Sketch path must point to the main .pde file.", false);
|
||||
|
||||
} else {
|
||||
|
||||
if (outputSet) {
|
||||
if (outputPath.equals(sketchPath)) {
|
||||
complainAndQuit("The sketch path and output path cannot be identical.", false);
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
|
||||
// JavaMode javaMode =
|
||||
// new JavaMode(null, Base.getContentFile("modes/java"));
|
||||
JavaMode javaMode = (JavaMode)
|
||||
ModeContribution.load(null, Base.getContentFile("modes/java"),
|
||||
"processing.mode.java.JavaMode").getMode();
|
||||
try {
|
||||
sketch = new Sketch(pdePath, javaMode);
|
||||
|
||||
if (!outputSet) {
|
||||
outputFolder = sketch.makeTempFolder();
|
||||
}
|
||||
|
||||
if (task == BUILD || task == RUN || task == PRESENT) {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
File srcFolder = new File(outputFolder, "source");
|
||||
String className = build.build(srcFolder, outputFolder, true);
|
||||
// String className = build.build(sketchFolder, outputFolder, true);
|
||||
if (className != null) {
|
||||
success = true;
|
||||
if (task == RUN || task == PRESENT) {
|
||||
Runner runner = new Runner(build, this);
|
||||
runner.launch(task == PRESENT);
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
} else if (task == EXPORT) {
|
||||
if (outputPath == null) {
|
||||
javaMode.handleExportApplication(sketch);
|
||||
} else {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
build.build(true);
|
||||
if (build != null) {
|
||||
// if (platformBits == 0) {
|
||||
// platformBits = Base.getNativeBits();
|
||||
// }
|
||||
// if (platformBits == 0 &&
|
||||
// Library.hasMultipleArch(platform, build.getImportedLibraries())) {
|
||||
// complainAndQuit("This sketch can be exported for 32- or 64-bit, please specify one.", true);
|
||||
// }
|
||||
success = build.exportApplication(outputFolder, platform, platformBits, embedJava);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) { // error already printed
|
||||
System.exit(1);
|
||||
}
|
||||
systemOut.println("Finished.");
|
||||
System.exit(0);
|
||||
|
||||
} catch (SketchException re) {
|
||||
statusError(re);
|
||||
System.exit(1);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void statusNotice(String message) {
|
||||
systemErr.println(message);
|
||||
}
|
||||
|
||||
|
||||
public void statusError(String message) {
|
||||
systemErr.println(message);
|
||||
}
|
||||
|
||||
|
||||
public void statusError(Exception exception) {
|
||||
if (exception instanceof SketchException) {
|
||||
SketchException re = (SketchException) exception;
|
||||
|
||||
int codeIndex = re.getCodeIndex();
|
||||
if (codeIndex != -1) {
|
||||
// format the runner exception like emacs
|
||||
//blah.java:2:10:2:13: Syntax Error: This is a big error message
|
||||
// Emacs doesn't like the double line thing coming from Java
|
||||
// https://github.com/processing/processing/issues/2158
|
||||
String filename = sketch.getCode(codeIndex).getFileName();
|
||||
int line = re.getCodeLine() + 1;
|
||||
int column = re.getCodeColumn() + 1;
|
||||
//if (column == -1) column = 0;
|
||||
// TODO if column not specified, should just select the whole line.
|
||||
// But what's the correct syntax for that?
|
||||
systemErr.println(filename + ":" +
|
||||
line + ":" + column + ":" +
|
||||
line + ":" + column + ":" + " " + re.getMessage());
|
||||
|
||||
} else { // no line number, pass the trace along to the user
|
||||
exception.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void complainAndQuit(String lastWords, boolean schoolEmFirst) {
|
||||
if (schoolEmFirst) {
|
||||
printCommandLine(systemErr);
|
||||
}
|
||||
systemErr.println(lastWords);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
|
||||
static void printCommandLine(PrintStream out) {
|
||||
out.println();
|
||||
out.println("Command line edition for Processing " + Base.getVersionName() + " (Java Mode)");
|
||||
out.println();
|
||||
out.println("--help Show this help text. Congratulations.");
|
||||
out.println();
|
||||
out.println("--sketch=<name> Specify the sketch folder (required)");
|
||||
out.println("--output=<name> Specify the output folder (optional and");
|
||||
out.println(" cannot be the same as the sketch folder.)");
|
||||
out.println();
|
||||
out.println("--force The sketch will not build if the output");
|
||||
out.println(" folder already exists, because the contents");
|
||||
out.println(" will be replaced. This option erases the");
|
||||
out.println(" folder first. Use with extreme caution!");
|
||||
out.println();
|
||||
out.println("--build Preprocess and compile a sketch into .class files.");
|
||||
out.println("--run Preprocess, compile, and run a sketch.");
|
||||
out.println("--present Preprocess, compile, and run a sketch full screen.");
|
||||
out.println();
|
||||
out.println("--export Export an application.");
|
||||
out.println("--no-java Do not embed Java. Use at your own risk!");
|
||||
// out.println("--platform Specify the platform (export to application only).");
|
||||
// out.println(" Should be one of 'windows', 'macosx', or 'linux'.");
|
||||
// out.println("--bits Must be specified if libraries are used that are");
|
||||
// out.println(" 32- or 64-bit specific such as the OpenGL library.");
|
||||
// out.println(" Otherwise specify 0 or leave it out.");
|
||||
out.println();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void startIndeterminate() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void stopIndeterminate() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void statusHalt() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isHalted() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
753
java/src/processing/mode/java/Compiler.java
Normal file
753
java/src/processing/mode/java/Compiler.java
Normal file
@@ -0,0 +1,753 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
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;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.core.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
//import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
|
||||
//import org.eclipse.jdt.core.compiler.CompilationProgress;
|
||||
|
||||
|
||||
public class Compiler {
|
||||
|
||||
static HashMap<String, String> importSuggestions;
|
||||
static {
|
||||
importSuggestions = new HashMap<String, String>();
|
||||
importSuggestions.put("Arrays", "java.util.Arrays");
|
||||
importSuggestions.put("Collections", "java.util.Collections");
|
||||
importSuggestions.put("Date", "java.util.Date");
|
||||
importSuggestions.put("Frame", "java.awt.Frame");
|
||||
importSuggestions.put("Iterator", "java.util.Iterator");
|
||||
}
|
||||
|
||||
|
||||
// public Compiler() { }
|
||||
|
||||
/**
|
||||
* Compile with ECJ. See http://j.mp/8paifz for documentation.
|
||||
*
|
||||
* @param sketch Sketch object to be compiled, used for placing exceptions
|
||||
* @param buildPath Where the temporary files live and will be built from.
|
||||
* @return true if successful.
|
||||
* @throws RunnerException Only if there's a problem. Only then.
|
||||
*/
|
||||
// public boolean compile(Sketch sketch,
|
||||
// File srcFolder,
|
||||
// File binFolder,
|
||||
// String primaryClassName,
|
||||
// String sketchClassPath,
|
||||
// String bootClassPath) throws RunnerException {
|
||||
static public boolean compile(JavaBuild build) throws SketchException {
|
||||
|
||||
// This will be filled in if anyone gets angry
|
||||
SketchException exception = null;
|
||||
boolean success = false;
|
||||
|
||||
String baseCommand[] = new String[] {
|
||||
"-Xemacs",
|
||||
//"-noExit", // not necessary for ecj
|
||||
"-source", "1.6",
|
||||
"-target", "1.6",
|
||||
"-classpath", build.getClassPath(),
|
||||
"-nowarn", // we're not currently interested in warnings (works in ecj)
|
||||
"-d", build.getBinFolder().getAbsolutePath() // output the classes in the buildPath
|
||||
};
|
||||
//PApplet.println(baseCommand);
|
||||
|
||||
// make list of code files that need to be compiled
|
||||
// String[] sourceFiles = new String[sketch.getCodeCount()];
|
||||
// int sourceCount = 0;
|
||||
// sourceFiles[sourceCount++] =
|
||||
// new File(buildPath, primaryClassName + ".java").getAbsolutePath();
|
||||
//
|
||||
// for (SketchCode code : sketch.getCode()) {
|
||||
// if (code.isExtension("java")) {
|
||||
// String path = new File(buildPath, code.getFileName()).getAbsolutePath();
|
||||
// sourceFiles[sourceCount++] = path;
|
||||
// }
|
||||
// }
|
||||
String[] sourceFiles = Base.listFiles(build.getSrcFolder(), false, ".java");
|
||||
|
||||
// String[] command = new String[baseCommand.length + sourceFiles.length];
|
||||
// System.arraycopy(baseCommand, 0, command, 0, baseCommand.length);
|
||||
// // append each of the files to the command string
|
||||
// System.arraycopy(sourceFiles, 0, command, baseCommand.length, sourceCount);
|
||||
String[] command = PApplet.concat(baseCommand, sourceFiles);
|
||||
|
||||
// PApplet.println(command);
|
||||
|
||||
try {
|
||||
// Load errors into a local StringBuilder
|
||||
final StringBuilder errorBuffer = new StringBuilder();
|
||||
|
||||
// Create single method dummy writer class to slurp errors from ecj
|
||||
Writer internalWriter = new Writer() {
|
||||
public void write(char[] buf, int off, int len) {
|
||||
errorBuffer.append(buf, off, len);
|
||||
}
|
||||
|
||||
public void flush() { }
|
||||
|
||||
public void close() { }
|
||||
};
|
||||
// Wrap as a PrintWriter since that's what compile() wants
|
||||
PrintWriter writer = new PrintWriter(internalWriter);
|
||||
|
||||
//result = com.sun.tools.javac.Main.compile(command, writer);
|
||||
|
||||
PrintWriter outWriter = new PrintWriter(System.out);
|
||||
|
||||
// Version that's not dynamically loaded
|
||||
//CompilationProgress progress = null;
|
||||
//success = BatchCompiler.compile(command, outWriter, writer, progress);
|
||||
|
||||
// Version that *is* dynamically loaded. First gets the mode class loader
|
||||
// so that it can grab the compiler JAR files from it.
|
||||
ClassLoader loader = build.mode.getClassLoader();
|
||||
try {
|
||||
Class<?> batchClass =
|
||||
Class.forName("org.eclipse.jdt.core.compiler.batch.BatchCompiler", false, loader);
|
||||
Class<?> progressClass =
|
||||
Class.forName("org.eclipse.jdt.core.compiler.CompilationProgress", false, loader);
|
||||
Class<?>[] compileArgs =
|
||||
new Class<?>[] { String[].class, PrintWriter.class, PrintWriter.class, progressClass };
|
||||
Method compileMethod = batchClass.getMethod("compile", compileArgs);
|
||||
success = (Boolean)
|
||||
compileMethod.invoke(null, new Object[] { command, outWriter, writer, null });
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new SketchException("Unknown error inside the compiler.");
|
||||
}
|
||||
|
||||
// Close out the stream for good measure
|
||||
writer.flush();
|
||||
writer.close();
|
||||
|
||||
String errorString = errorBuffer.toString();
|
||||
// if (errorString.trim().length() != 0) {
|
||||
// success = false;
|
||||
// }
|
||||
BufferedReader reader =
|
||||
new BufferedReader(new StringReader(errorString));
|
||||
//System.err.println(errorBuffer.toString());
|
||||
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
//System.out.println("got line " + line); // debug
|
||||
|
||||
// get first line, which contains file name, line number,
|
||||
// and at least the first line of the error message
|
||||
String errorFormat = "([\\w\\d_]+.java):(\\d+):\\s*(.*):\\s*(.*)\\s*";
|
||||
String[] pieces = PApplet.match(line, errorFormat);
|
||||
//PApplet.println(pieces);
|
||||
|
||||
// if it's something unexpected, die and print the mess to the console
|
||||
if (pieces == null) {
|
||||
exception = new SketchException("Cannot parse error text: " + line);
|
||||
exception.hideStackTrace();
|
||||
// Send out the rest of the error message to the console.
|
||||
System.err.println(line);
|
||||
while ((line = reader.readLine()) != null) {
|
||||
System.err.println(line);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// translate the java filename and line number into a un-preprocessed
|
||||
// location inside a source file or tab in the environment.
|
||||
String dotJavaFilename = pieces[1];
|
||||
// Line numbers are 1-indexed from javac
|
||||
int dotJavaLineIndex = PApplet.parseInt(pieces[2]) - 1;
|
||||
String errorMessage = pieces[4];
|
||||
|
||||
exception = build.placeException(errorMessage,
|
||||
dotJavaFilename,
|
||||
dotJavaLineIndex);
|
||||
/*
|
||||
int codeIndex = 0; //-1;
|
||||
int codeLine = -1;
|
||||
|
||||
// first check to see if it's a .java file
|
||||
for (int i = 0; i < sketch.getCodeCount(); i++) {
|
||||
SketchCode code = sketch.getCode(i);
|
||||
if (code.isExtension("java")) {
|
||||
if (dotJavaFilename.equals(code.getFileName())) {
|
||||
codeIndex = i;
|
||||
codeLine = dotJavaLineIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it's not a .java file, codeIndex will still be 0
|
||||
if (codeIndex == 0) { // main class, figure out which tab
|
||||
//for (int i = 1; i < sketch.getCodeCount(); i++) {
|
||||
for (int i = 0; i < sketch.getCodeCount(); i++) {
|
||||
SketchCode code = sketch.getCode(i);
|
||||
|
||||
if (code.isExtension("pde")) {
|
||||
if (code.getPreprocOffset() <= dotJavaLineIndex) {
|
||||
codeIndex = i;
|
||||
//System.out.println("i'm thinkin file " + i);
|
||||
codeLine = dotJavaLineIndex - code.getPreprocOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//System.out.println("code line now " + codeLine);
|
||||
exception = new RunnerException(errorMessage, codeIndex, codeLine, -1, false);
|
||||
*/
|
||||
|
||||
if (exception == null) {
|
||||
exception = new SketchException(errorMessage);
|
||||
}
|
||||
|
||||
// for a test case once message parsing is implemented,
|
||||
// use new Font(...) since that wasn't getting picked up properly.
|
||||
|
||||
/*
|
||||
if (errorMessage.equals("cannot find symbol")) {
|
||||
handleCannotFindSymbol(reader, exception);
|
||||
|
||||
} else if (errorMessage.indexOf("is already defined") != -1) {
|
||||
reader.readLine(); // repeats the line of code w/ error
|
||||
int codeColumn = caretColumn(reader.readLine());
|
||||
exception = new RunnerException(errorMessage,
|
||||
codeIndex, codeLine, codeColumn);
|
||||
|
||||
} else if (errorMessage.startsWith("package") &&
|
||||
errorMessage.endsWith("does not exist")) {
|
||||
// Because imports are stripped out and re-added to the 0th line of
|
||||
// the preprocessed code, codeLine will always be wrong for imports.
|
||||
exception = new RunnerException("P" + errorMessage.substring(1) +
|
||||
". You might be missing a library.");
|
||||
} else {
|
||||
exception = new RunnerException(errorMessage);
|
||||
}
|
||||
*/
|
||||
if (errorMessage.startsWith("The import ") &&
|
||||
errorMessage.endsWith("cannot be resolved")) {
|
||||
// The import poo cannot be resolved
|
||||
//import poo.shoe.blah.*;
|
||||
//String what = errorMessage.substring("The import ".length());
|
||||
String[] m = PApplet.match(errorMessage, "The import (.*) cannot be resolved");
|
||||
//what = what.substring(0, what.indexOf(' '));
|
||||
if (m != null) {
|
||||
// System.out.println("'" + m[1] + "'");
|
||||
if (m[1].equals("processing.xml")) {
|
||||
exception.setMessage("processing.xml no longer exists, this code needs to be updated for 2.0.");
|
||||
System.err.println("The processing.xml library has been replaced " +
|
||||
"with a new 'XML' class that's built-in.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else {
|
||||
exception.setMessage("The package " +
|
||||
"\u201C" + m[1] + "\u201D" +
|
||||
" does not exist. " +
|
||||
"You might be missing a library.");
|
||||
System.err.println("Libraries must be " +
|
||||
"installed in a folder named 'libraries' " +
|
||||
"inside the 'sketchbook' folder.");
|
||||
}
|
||||
}
|
||||
|
||||
// // Actually create the folder and open it for the user
|
||||
// File sketchbookLibraries = Base.getSketchbookLibrariesFolder();
|
||||
// if (!sketchbookLibraries.exists()) {
|
||||
// if (sketchbookLibraries.mkdirs()) {
|
||||
// Base.openFolder(sketchbookLibraries);
|
||||
// }
|
||||
// }
|
||||
|
||||
} else if (errorMessage.endsWith("cannot be resolved to a type")) {
|
||||
// xxx cannot be resolved to a type
|
||||
//xxx c;
|
||||
|
||||
String what = errorMessage.substring(0, errorMessage.indexOf(' '));
|
||||
|
||||
if (what.equals("BFont") ||
|
||||
what.equals("BGraphics") ||
|
||||
what.equals("BImage")) {
|
||||
exception.setMessage(what + " has been replaced with P" + what.substring(1));
|
||||
handleCrustyCode();
|
||||
|
||||
} else {
|
||||
exception.setMessage("Cannot find a class or type " +
|
||||
"named \u201C" + what + "\u201D");
|
||||
|
||||
String suggestion = importSuggestions.get(what);
|
||||
if (suggestion != null) {
|
||||
System.err.println("You may need to add \"import " + suggestion + ";\" to the top of your sketch.");
|
||||
System.err.println("To make sketches more portable, imports that are not part of the Processing API have been removed from Processing 2.0.");
|
||||
System.err.println("See the changes page for more information: http://wiki.processing.org/w/Changes");
|
||||
}
|
||||
}
|
||||
|
||||
} else if (errorMessage.endsWith("cannot be resolved")) {
|
||||
// xxx cannot be resolved
|
||||
//println(xxx);
|
||||
|
||||
String what = errorMessage.substring(0, errorMessage.indexOf(' '));
|
||||
|
||||
if (what.equals("LINE_LOOP") ||
|
||||
what.equals("LINE_STRIP")) {
|
||||
exception.setMessage("LINE_LOOP and LINE_STRIP are not available, " +
|
||||
"please update your code.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (what.equals("framerate")) {
|
||||
exception.setMessage("framerate should be changed to frameRate.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (what.equals("screen")) {
|
||||
exception.setMessage("Change screen.width and screen.height to " +
|
||||
"displayWidth and displayHeight.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (what.equals("screenWidth") ||
|
||||
what.equals("screenHeight")) {
|
||||
exception.setMessage("Change screenWidth and screenHeight to " +
|
||||
"displayWidth and displayHeight.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else {
|
||||
exception.setMessage("Cannot find anything " +
|
||||
"named \u201C" + what + "\u201D");
|
||||
}
|
||||
|
||||
} else if (errorMessage.startsWith("Duplicate")) {
|
||||
// "Duplicate nested type xxx"
|
||||
// "Duplicate local variable xxx"
|
||||
|
||||
} else {
|
||||
String[] parts = null;
|
||||
|
||||
// The method xxx(String) is undefined for the type Temporary_XXXX_XXXX
|
||||
//xxx("blah");
|
||||
// The method xxx(String, int) is undefined for the type Temporary_XXXX_XXXX
|
||||
//xxx("blah", 34);
|
||||
// The method xxx(String, int) is undefined for the type PApplet
|
||||
//PApplet.sub("ding");
|
||||
String undefined =
|
||||
"The method (\\S+\\(.*\\)) is undefined for the type (.*)";
|
||||
parts = PApplet.match(errorMessage, undefined);
|
||||
if (parts != null) {
|
||||
if (parts[1].equals("framerate(int)")) {
|
||||
exception.setMessage("framerate() no longer exists, use frameRate() instead.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (parts[1].equals("push()")) {
|
||||
exception.setMessage("push() no longer exists, use pushMatrix() instead.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (parts[1].equals("pop()")) {
|
||||
exception.setMessage("pop() no longer exists, use popMatrix() instead.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else {
|
||||
String mess = "The function " + parts[1] + " does not exist.";
|
||||
exception.setMessage(mess);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exception != null) {
|
||||
// The stack trace just shows that this happened inside the compiler,
|
||||
// which is a red herring. Don't ever show it for compiler stuff.
|
||||
exception.hideStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
String bigSigh = "Error while compiling. (" + e.getMessage() + ")";
|
||||
exception = new SketchException(bigSigh);
|
||||
e.printStackTrace();
|
||||
success = false;
|
||||
}
|
||||
// In case there was something else.
|
||||
if (exception != null) throw exception;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fire up 'ole javac based on <a href="http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/javac.html#proginterface">this interface</a>.
|
||||
*
|
||||
* @param sketch Sketch object to be compiled.
|
||||
* @param buildPath Where the temporary files live and will be built from.
|
||||
* @return
|
||||
* @throws RunnerException Only if there's a problem. Only then.
|
||||
*/
|
||||
// public boolean compileJavac(Sketch sketch,
|
||||
// String buildPath) throws RunnerException {
|
||||
// // This will be filled in if anyone gets angry
|
||||
// RunnerException exception = null;
|
||||
//
|
||||
// String baseCommand[] = new String[] {
|
||||
// "-source", "1.5",
|
||||
// "-target", "1.5",
|
||||
// "-classpath", sketch.getClassPath(),
|
||||
// "-nowarn", // we're not currently interested in warnings (ignored?)
|
||||
// "-d", buildPath // output the classes in the buildPath
|
||||
// };
|
||||
// //PApplet.println(baseCommand);
|
||||
//
|
||||
// // make list of code files that need to be compiled
|
||||
// // (some files are skipped if they contain no class)
|
||||
// String[] preprocNames = new String[sketch.getCodeCount()];
|
||||
// int preprocCount = 0;
|
||||
// for (int i = 0; i < sketch.getCodeCount(); i++) {
|
||||
// if (sketch.getCode(i).preprocName != null) {
|
||||
// preprocNames[preprocCount++] = sketch.getCode(i).preprocName;
|
||||
// }
|
||||
// }
|
||||
// String[] command = new String[baseCommand.length + preprocCount];
|
||||
// System.arraycopy(baseCommand, 0, command, 0, baseCommand.length);
|
||||
// // append each of the files to the command string
|
||||
// for (int i = 0; i < preprocCount; i++) {
|
||||
// command[baseCommand.length + i] =
|
||||
// buildPath + File.separator + preprocNames[i];
|
||||
// }
|
||||
// //PApplet.println(command);
|
||||
//
|
||||
// int result = -1; // needs to be set bad by default, in case hits IOE below
|
||||
//
|
||||
// try {
|
||||
// // Load errors into a local StringBuffer
|
||||
// final StringBuffer errorBuffer = new StringBuffer();
|
||||
//
|
||||
// // Create single method dummy writer class to slurp errors from javac
|
||||
// Writer internalWriter = new Writer() {
|
||||
// public void write(char[] buf, int off, int len) {
|
||||
// errorBuffer.append(buf, off, len);
|
||||
// }
|
||||
//
|
||||
// public void flush() { }
|
||||
//
|
||||
// public void close() { }
|
||||
// };
|
||||
// // Wrap as a PrintWriter since that's what compile() wants
|
||||
// PrintWriter writer = new PrintWriter(internalWriter);
|
||||
//
|
||||
// result = com.sun.tools.javac.Main.compile(command, writer);
|
||||
//
|
||||
// // Close out the stream for good measure
|
||||
// writer.flush();
|
||||
// writer.close();
|
||||
//
|
||||
//// BufferedReader reader =
|
||||
//// new BufferedReader(new StringReader(errorBuffer.toString()));
|
||||
// //System.err.println(errorBuffer.toString());
|
||||
//
|
||||
//// String m = errorBuffer.toString();
|
||||
// //ParsePosition mp = new ParsePosition(0);
|
||||
//
|
||||
//// while (mp.getIndex() < m.length()) { // reading messages
|
||||
// String line = null;
|
||||
// int lineIndex = 0;
|
||||
// String[] lines = PApplet.split(errorBuffer.toString(), '\n');
|
||||
// int lineCount = lines.length;
|
||||
// while (lineIndex < lineCount) {
|
||||
// //while ((line = reader.readLine()) != null) {
|
||||
// //System.out.println("got line " + line); // debug
|
||||
//
|
||||
// /*
|
||||
//compiler.misc.count.error=\
|
||||
// {0} error
|
||||
//compiler.misc.count.error.plural=\
|
||||
// {0} errors
|
||||
//compiler.misc.count.warn=\
|
||||
// {0} warning
|
||||
//compiler.misc.count.warn.plural=\
|
||||
// {0} warnings
|
||||
// */
|
||||
// // Check to see if this is the last line.
|
||||
//// if ((PApplet.match(line, "\\d+ error[s]?") != null) ||
|
||||
//// (PApplet.match(line, "\\d+ warning[s]?") != null)) {
|
||||
//// break;
|
||||
//// }
|
||||
// if (isCompilerMatch(line, "compiler.misc.count.error") ||
|
||||
// isCompilerMatch(line, "compiler.misc.count.error.plural") ||
|
||||
// isCompilerMatch(line, "compiler.misc.count.warn") ||
|
||||
// isCompilerMatch(line, "compiler.misc.count.warn.plural")) {
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// // Hide these because people are getting confused
|
||||
// // http://dev.processing.org/bugs/show_bug.cgi?id=817
|
||||
// // com/sun/tools/javac/resources/compiler.properties
|
||||
// //if (line.startsWith("Note: ")) {
|
||||
// String compilerNote = compilerResources.getString("compiler.note.note");
|
||||
// MessageFormat noteFormat = new MessageFormat(compilerNote + " {0}");
|
||||
// Object[] noteFound;
|
||||
// try {
|
||||
// noteFound = noteFormat.parse(line);
|
||||
// if (noteFound != null) {
|
||||
// System.out.println("gefunden " + noteFound[0]);
|
||||
//
|
||||
// /*
|
||||
// // if you mention serialVersionUID one more time, i'm kickin' you out
|
||||
// if (line.indexOf("serialVersionUID") != -1) continue;
|
||||
// // {0} uses unchecked or unsafe operations.
|
||||
// // Some input files use unchecked or unsafe operations.
|
||||
// if (line.indexOf("or unsafe operations") != -1) continue;
|
||||
// // {0} uses or overrides a deprecated API.
|
||||
// // Some input files use or override a deprecated API.
|
||||
// if (line.indexOf("or override") != -1) continue;
|
||||
// // Recompile with -Xlint:deprecation for details.
|
||||
// // Recompile with -Xlint:unchecked for details.
|
||||
// if (line.indexOf("Recompile with -Xlint:") != -1) continue;
|
||||
// System.err.println(line);
|
||||
// */
|
||||
// continue;
|
||||
// }
|
||||
// } catch (ParseException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
//
|
||||
// // get first line, which contains file name, line number,
|
||||
// // and at least the first line of the error message
|
||||
// String errorFormat = "([\\w\\d_]+.java):(\\d+):\\s*(.*)\\s*";
|
||||
// String[] pieces = PApplet.match(line, errorFormat);
|
||||
//
|
||||
// // if it's something unexpected, die and print the mess to the console
|
||||
// if (pieces == null) {
|
||||
// exception = new RunnerException("Cannot parse error text: " + line);
|
||||
// exception.hideStackTrace();
|
||||
// // Send out the rest of the error message to the console.
|
||||
// System.err.println(line);
|
||||
// //while ((line = reader.readLine()) != null) {
|
||||
// for (int i = lineIndex; i < lineCount; i++) {
|
||||
// System.err.println(lines[i]);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// // translate the java filename and line number into a un-preprocessed
|
||||
// // location inside a source file or tab in the environment.
|
||||
// String dotJavaFilename = pieces[0];
|
||||
// // Line numbers are 1-indexed from javac
|
||||
// int dotJavaLineIndex = PApplet.parseInt(pieces[1]) - 1;
|
||||
// String errorMessage = pieces[2];
|
||||
//
|
||||
// int codeIndex = -1;
|
||||
// int codeLine = -1;
|
||||
// for (int i = 0; i < sketch.getCodeCount(); i++) {
|
||||
// String name = sketch.getCode(i).preprocName;
|
||||
// if ((name != null) && dotJavaFilename.equals(name)) {
|
||||
// codeIndex = i;
|
||||
// }
|
||||
// }
|
||||
// //System.out.println("code index/line are " + codeIndex + " " + codeLine);
|
||||
// //System.out.println("java line number " + dotJavaLineIndex + " from " + dotJavaFilename);
|
||||
//
|
||||
// if (codeIndex == 0) { // main class, figure out which tab
|
||||
// for (int i = 1; i < sketch.getCodeCount(); i++) {
|
||||
// SketchCode code = sketch.getCode(i);
|
||||
//
|
||||
// if (code.flavor == Sketch.PDE) {
|
||||
// if (code.preprocOffset <= dotJavaLineIndex) {
|
||||
// codeIndex = i;
|
||||
// //System.out.println("i'm thinkin file " + i);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// //System.out.println("preproc offset is " + sketch.getCode(codeIndex).preprocOffset);
|
||||
// codeLine = dotJavaLineIndex - sketch.getCode(codeIndex).preprocOffset;
|
||||
// //System.out.println("code line now " + codeLine);
|
||||
// exception = new RunnerException(errorMessage, codeIndex, codeLine, -1, false);
|
||||
//
|
||||
// // for a test case once message parsing is implemented,
|
||||
// // use new Font(...) since that wasn't getting picked up properly.
|
||||
//
|
||||
// if (errorMessage.equals("cannot find symbol")) {
|
||||
// handleCannotFindSymbol(reader, exception);
|
||||
//
|
||||
// } else if (errorMessage.indexOf("is already defined") != -1) {
|
||||
// reader.readLine(); // repeats the line of code w/ error
|
||||
// int codeColumn = caretColumn(reader.readLine());
|
||||
// exception = new RunnerException(errorMessage,
|
||||
// codeIndex, codeLine, codeColumn);
|
||||
//
|
||||
// } else if (errorMessage.startsWith("package") &&
|
||||
// errorMessage.endsWith("does not exist")) {
|
||||
// // Because imports are stripped out and re-added to the 0th line of
|
||||
// // the preprocessed code, codeLine will always be wrong for imports.
|
||||
// exception = new RunnerException("P" + errorMessage.substring(1) +
|
||||
// ". You might be missing a library.");
|
||||
// } else {
|
||||
// exception = new RunnerException(errorMessage);
|
||||
// }
|
||||
// if (exception != null) {
|
||||
// // The stack trace just shows that this happened inside the compiler,
|
||||
// // which is a red herring. Don't ever show it for compiler stuff.
|
||||
// exception.hideStackTrace();
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// } catch (IOException e) {
|
||||
// String bigSigh = "Error while compiling. (" + e.getMessage() + ")";
|
||||
// exception = new RunnerException(bigSigh);
|
||||
// e.printStackTrace();
|
||||
// result = 1;
|
||||
// }
|
||||
// // In case there was something else.
|
||||
// if (exception != null) throw exception;
|
||||
//
|
||||
// // Success means that 'result' is set to zero
|
||||
// return (result == 0);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// boolean isCompilerMatch(String line, String format) {
|
||||
// return compilerMatch(line, format) != null;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// Object[] compilerMatch(String line, String name) {
|
||||
// String format = compilerResources.getString(name);
|
||||
// MessageFormat mf = new MessageFormat(format);
|
||||
// Object[] matches = null;
|
||||
// try {
|
||||
// matches = mf.parse(line);
|
||||
// } catch (ParseException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return matches;
|
||||
// }
|
||||
|
||||
|
||||
// boolean isCompilerMatch(String line, ParsePosition pos, String format) {
|
||||
// return compilerMatch(line, pos, format) != null;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// Object[] compilerMatch(String line, ParsePosition pos, String name) {
|
||||
// String format = compilerResources.getString(name);
|
||||
// MessageFormat mf = new MessageFormat(format);
|
||||
// Object[] matches = mf.parse(line, pos);
|
||||
// return matches;
|
||||
// }
|
||||
|
||||
|
||||
// Tell-tale signs of old code copied and pasted from the web.
|
||||
// Detect classes BFont, BGraphics, BImage; methods framerate, push;
|
||||
// and variables LINE_LOOP and LINE_STRIP.
|
||||
// static HashMap crusties = new HashMap();
|
||||
// static {
|
||||
// crusties.put("BFont", new Object());
|
||||
// crusties.put("BGraphics", new Object());
|
||||
// crusties.put("BImage", new Object());
|
||||
// crusties.put("framerate", new Object());
|
||||
// crusties.put("push", new Object());
|
||||
// crusties.put("LINE_LOOP", new Object());
|
||||
// crusties.put("LINE_STRIP", new Object());
|
||||
// }
|
||||
|
||||
|
||||
// void handleCannotFindSymbol(BufferedReader reader,
|
||||
// RunnerException rex) throws IOException {
|
||||
// String symbolLine = reader.readLine();
|
||||
// String locationLine = reader.readLine();
|
||||
// /*String codeLine =*/ reader.readLine();
|
||||
// String caretLine = reader.readLine();
|
||||
// rex.setCodeColumn(caretColumn(caretLine));
|
||||
//
|
||||
// String[] pieces =
|
||||
// PApplet.match(symbolLine, "symbol\\s*:\\s*(\\w+)\\s+(.*)");
|
||||
// if (pieces != null) {
|
||||
// if (pieces[0].equals("class") ||
|
||||
// pieces[0].equals("variable")) {
|
||||
// rex.setMessage("Cannot find a " + pieces[0] + " " +
|
||||
// "named \u201C" + pieces[1] + "\u201D");
|
||||
// if (crusties.get(pieces[1]) != null) {
|
||||
// handleCrustyCode(rex);
|
||||
// }
|
||||
//
|
||||
// } else if (pieces[0].equals("method")) {
|
||||
// int leftParen = pieces[1].indexOf("(");
|
||||
// int rightParen = pieces[1].indexOf(")");
|
||||
//
|
||||
// String methodName = pieces[1].substring(0, leftParen);
|
||||
// String methodParams = pieces[1].substring(leftParen + 1, rightParen);
|
||||
//
|
||||
// String message =
|
||||
// "Cannot find a function named \u201C" + methodName + "\u201D";
|
||||
// if (methodParams.length() > 0) {
|
||||
// if (methodParams.indexOf(',') != -1) {
|
||||
// message += " with parameters ";
|
||||
// } else {
|
||||
// message += " with parameter ";
|
||||
// }
|
||||
// message += methodParams;
|
||||
// }
|
||||
//
|
||||
// String locationClass = "location: class ";
|
||||
// if (locationLine.startsWith(locationClass) &&
|
||||
// // don't include the class name when it's a temp class
|
||||
// locationLine.indexOf("Temporary_") == -1) {
|
||||
// String className = locationLine.substring(locationClass.length());
|
||||
// // If no dot exists, -1 + 1 is 0, so this will have no effect.
|
||||
// className = className.substring(className.lastIndexOf('.') + 1);
|
||||
// int bracket = className.indexOf('[');
|
||||
// if (bracket == -1) {
|
||||
// message += " in class " + className;
|
||||
// } else {
|
||||
// className = className.substring(0, bracket);
|
||||
// message += " for an array of " + className + " objects";
|
||||
// }
|
||||
// }
|
||||
// message += ".";
|
||||
// rex.setMessage(message);
|
||||
//
|
||||
// // On second thought, make sure this isn't just some alpha/beta code
|
||||
// if (crusties.get(methodName) != null) {
|
||||
// handleCrustyCode(rex);
|
||||
// }
|
||||
//
|
||||
// } else {
|
||||
// System.out.println(symbolLine);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
static protected void handleCrustyCode() {
|
||||
System.err.println("This code needs to be updated " +
|
||||
"for this version of Processing, " +
|
||||
"please read the Changes page on the Wiki.");
|
||||
Editor.showChanges();
|
||||
}
|
||||
|
||||
|
||||
protected int caretColumn(String caretLine) {
|
||||
return caretLine.indexOf("^");
|
||||
}
|
||||
}
|
||||
1886
java/src/processing/mode/java/JavaBuild.java
Normal file
1886
java/src/processing/mode/java/JavaBuild.java
Normal file
File diff suppressed because it is too large
Load Diff
1058
java/src/processing/mode/java/JavaEditor.java
Normal file
1058
java/src/processing/mode/java/JavaEditor.java
Normal file
File diff suppressed because it is too large
Load Diff
168
java/src/processing/mode/java/JavaMode.java
Normal file
168
java/src/processing/mode/java/JavaMode.java
Normal file
@@ -0,0 +1,168 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2010-11 Ben Fry and Casey Reas
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.mode.java.runner.Runner;
|
||||
|
||||
|
||||
public class JavaMode extends Mode {
|
||||
// classpath for all known libraries for p5
|
||||
// (both those in the p5/libs folder and those with lib subfolders
|
||||
// found in the sketchbook)
|
||||
// static public String librariesClassPath;
|
||||
|
||||
|
||||
public Editor createEditor(Base base, String path, EditorState state) {
|
||||
return new JavaEditor(base, path, state, this);
|
||||
}
|
||||
|
||||
|
||||
public JavaMode(Base base, File folder) {
|
||||
super(base, folder);
|
||||
}
|
||||
|
||||
|
||||
public String getTitle() {
|
||||
return "Java (2.0)";
|
||||
}
|
||||
|
||||
|
||||
// public EditorToolbar createToolbar(Editor editor) {
|
||||
// return new Toolbar(editor);
|
||||
// }
|
||||
|
||||
|
||||
// public Formatter createFormatter() {
|
||||
// return new AutoFormat();
|
||||
// }
|
||||
|
||||
|
||||
// public Editor createEditor(Base ibase, String path, int[] location) {
|
||||
// }
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
public File[] getExampleCategoryFolders() {
|
||||
return new File[] {
|
||||
new File(examplesFolder, "Basics"),
|
||||
new File(examplesFolder, "Topics"),
|
||||
new File(examplesFolder, "Demos"),
|
||||
new File(examplesFolder, "Books")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public String getDefaultExtension() {
|
||||
return "pde";
|
||||
}
|
||||
|
||||
|
||||
public String[] getExtensions() {
|
||||
return new String[] { "pde", "java" };
|
||||
}
|
||||
|
||||
|
||||
public String[] getIgnorable() {
|
||||
return new String[] {
|
||||
"applet",
|
||||
"application.macosx",
|
||||
"application.windows",
|
||||
"application.linux"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Library getCoreLibrary() {
|
||||
if (coreLibrary == null) {
|
||||
File coreFolder = Base.getContentFile("core");
|
||||
coreLibrary = new Library(coreFolder);
|
||||
// try {
|
||||
// coreLibrary = getLibrary("processing.core");
|
||||
// System.out.println("core found at " + coreLibrary.getLibraryPath());
|
||||
// } catch (SketchException e) {
|
||||
// Base.log("Serious problem while locating processing.core", e);
|
||||
// }
|
||||
}
|
||||
return coreLibrary;
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
public Runner handleRun(Sketch sketch, RunnerListener listener) throws SketchException {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
String appletClassName = build.build(false);
|
||||
if (appletClassName != null) {
|
||||
final Runner runtime = new Runner(build, listener);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
runtime.launch(false); // this blocks until finished
|
||||
}
|
||||
}).start();
|
||||
return runtime;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Runner handlePresent(Sketch sketch, RunnerListener listener) throws SketchException {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
String appletClassName = build.build(false);
|
||||
if (appletClassName != null) {
|
||||
final Runner runtime = new Runner(build, listener);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
runtime.launch(true);
|
||||
}
|
||||
}).start();
|
||||
return runtime;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// public void handleStop() {
|
||||
// if (runtime != null) {
|
||||
// runtime.close(); // kills the window
|
||||
// runtime = null; // will this help?
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// public boolean handleExportApplet(Sketch sketch) throws SketchException, IOException {
|
||||
// JavaBuild build = new JavaBuild(sketch);
|
||||
// return build.exportApplet();
|
||||
// }
|
||||
|
||||
|
||||
public boolean handleExportApplication(Sketch sketch) throws SketchException, IOException {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
return build.exportApplication();
|
||||
}
|
||||
}
|
||||
118
java/src/processing/mode/java/JavaToolbar.java
Normal file
118
java/src/processing/mode/java/JavaToolbar.java
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2010-11 Ben Fry and Casey Reas
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
//import javax.swing.JPopupMenu;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Editor;
|
||||
import processing.app.EditorToolbar;
|
||||
import processing.app.Language;
|
||||
|
||||
|
||||
public class JavaToolbar extends EditorToolbar {
|
||||
static protected final int RUN = 0;
|
||||
static protected final int STOP = 1;
|
||||
|
||||
// static protected final int NEW = 2;
|
||||
// static protected final int OPEN = 3;
|
||||
// static protected final int SAVE = 4;
|
||||
// static protected final int EXPORT = 5;
|
||||
|
||||
|
||||
public JavaToolbar(Editor editor, Base base) {
|
||||
super(editor, base);
|
||||
}
|
||||
|
||||
|
||||
public void init() {
|
||||
Image[][] images = loadImages();
|
||||
// for (int i = 0; i < 6; i++) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// addButton(getTitle(i, false), getTitle(i, true), images[i], i == NEW);
|
||||
addButton(getTitle(i, false), getTitle(i, true), images[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static public String getTitle(int index, boolean shift) {
|
||||
switch (index) {
|
||||
case RUN: return !shift ? Language.text("toolbar.run") : Language.text("toolbar.present");
|
||||
case STOP: return Language.text("toolbar.stop");
|
||||
// case NEW: return Language.text("toolbar.new");
|
||||
// case OPEN: return Language.text("toolbar.open");
|
||||
// case SAVE: return Language.text("toolbar.save");
|
||||
// case EXPORT: return Language.text("toolbar.export_application");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public void handlePressed(MouseEvent e, int sel) {
|
||||
boolean shift = e.isShiftDown();
|
||||
JavaEditor jeditor = (JavaEditor) editor;
|
||||
|
||||
switch (sel) {
|
||||
case RUN:
|
||||
if (shift) {
|
||||
jeditor.handlePresent();
|
||||
} else {
|
||||
jeditor.handleRun();
|
||||
}
|
||||
break;
|
||||
|
||||
case STOP:
|
||||
jeditor.handleStop();
|
||||
break;
|
||||
|
||||
/*
|
||||
case OPEN:
|
||||
// popup = menu.getPopupMenu();
|
||||
// TODO I think we need a longer chain of accessors here.
|
||||
JPopupMenu popup = editor.getMode().getToolbarMenu().getPopupMenu();
|
||||
popup.show(this, e.getX(), e.getY());
|
||||
break;
|
||||
|
||||
case NEW:
|
||||
// if (shift) {
|
||||
base.handleNew();
|
||||
// } else {
|
||||
// base.handleNewReplace();
|
||||
// }
|
||||
break;
|
||||
|
||||
case SAVE:
|
||||
jeditor.handleSave(false);
|
||||
break;
|
||||
|
||||
case EXPORT:
|
||||
// if (shift) {
|
||||
// jeditor.handleExportApplet();
|
||||
// } else {
|
||||
jeditor.handleExportApplication();
|
||||
// }
|
||||
break;
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
525
java/src/processing/mode/java/PdeKeyListener.java
Normal file
525
java/src/processing/mode/java/PdeKeyListener.java
Normal file
@@ -0,0 +1,525 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-10 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
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;
|
||||
|
||||
import processing.app.Editor;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.syntax.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* Filters key events for tab expansion/indent/etc.
|
||||
* <p/>
|
||||
* For version 0099, some changes have been made to make the indents
|
||||
* smarter. There are still issues though:
|
||||
* <UL>
|
||||
* <LI> indent happens when it picks up a curly brace on the previous line,
|
||||
* but not if there's a blank line between them.
|
||||
* <LI> It also doesn't handle single indent situations where a brace
|
||||
* isn't used (i.e. an if statement or for loop that's a single line).
|
||||
* It shouldn't actually be using braces.
|
||||
* </UL>
|
||||
* Solving these issues, however, would probably best be done by a
|
||||
* smarter parser/formatter, rather than continuing to hack this class.
|
||||
*/
|
||||
public class PdeKeyListener {
|
||||
private Editor editor;
|
||||
private JEditTextArea textarea;
|
||||
|
||||
/** ctrl-alt on windows and linux, cmd-alt on mac os x */
|
||||
static final int CTRL_ALT = ActionEvent.ALT_MASK |
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
|
||||
|
||||
|
||||
public PdeKeyListener(Editor editor, JEditTextArea textarea) {
|
||||
this.editor = editor;
|
||||
this.textarea = textarea;
|
||||
|
||||
// // let him know that i'm leechin'
|
||||
// textarea.editorListener = this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Intercepts key pressed events for JEditTextArea.
|
||||
* <p/>
|
||||
* Called by JEditTextArea inside processKeyEvent(). Note that this
|
||||
* won't intercept actual characters, because those are fired on
|
||||
* keyTyped().
|
||||
* @return true if the event has been handled (to remove it from the queue)
|
||||
*/
|
||||
public boolean keyPressed(KeyEvent event) {
|
||||
char c = event.getKeyChar();
|
||||
int code = event.getKeyCode();
|
||||
|
||||
Sketch sketch = editor.getSketch();
|
||||
|
||||
/*
|
||||
if ((event.getModifiers() & CTRL_ALT) == CTRL_ALT) {
|
||||
if (code == KeyEvent.VK_LEFT) {
|
||||
sketch.handlePrevCode();
|
||||
return true;
|
||||
} else if (code == KeyEvent.VK_RIGHT) {
|
||||
sketch.handleNextCode();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if ((event.getModifiers() & InputEvent.META_MASK) != 0) {
|
||||
//event.consume(); // does nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO i don't like these accessors. clean em up later.
|
||||
// if (!editor.getSketch().isModified()) {
|
||||
if ((code == KeyEvent.VK_BACK_SPACE) || (code == KeyEvent.VK_TAB) ||
|
||||
(code == KeyEvent.VK_ENTER) || ((c >= 32) && (c < 128))) {
|
||||
sketch.setModified(true);
|
||||
}
|
||||
// }
|
||||
|
||||
if ((code == KeyEvent.VK_UP) &&
|
||||
((event.getModifiers() & InputEvent.CTRL_MASK) != 0)) {
|
||||
// back up to the last empty line
|
||||
char contents[] = textarea.getText().toCharArray();
|
||||
//int origIndex = textarea.getCaretPosition() - 1;
|
||||
int caretIndex = textarea.getCaretPosition();
|
||||
|
||||
int index = calcLineStart(caretIndex - 1, contents);
|
||||
//System.out.println("line start " + (int) contents[index]);
|
||||
index -= 2; // step over the newline
|
||||
//System.out.println((int) contents[index]);
|
||||
boolean onlySpaces = true;
|
||||
while (index > 0) {
|
||||
if (contents[index] == 10) {
|
||||
if (onlySpaces) {
|
||||
index++;
|
||||
break;
|
||||
} else {
|
||||
onlySpaces = true; // reset
|
||||
}
|
||||
} else if (contents[index] != ' ') {
|
||||
onlySpaces = false;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
// if the first char, index will be -2
|
||||
if (index < 0) index = 0;
|
||||
|
||||
if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
|
||||
textarea.setSelectionStart(caretIndex);
|
||||
textarea.setSelectionEnd(index);
|
||||
} else {
|
||||
textarea.setCaretPosition(index);
|
||||
}
|
||||
event.consume();
|
||||
return true;
|
||||
|
||||
} else if ((code == KeyEvent.VK_DOWN) &&
|
||||
((event.getModifiers() & InputEvent.CTRL_MASK) != 0)) {
|
||||
char contents[] = textarea.getText().toCharArray();
|
||||
int caretIndex = textarea.getCaretPosition();
|
||||
|
||||
int index = caretIndex;
|
||||
int lineStart = 0;
|
||||
boolean onlySpaces = false; // don't count this line
|
||||
while (index < contents.length) {
|
||||
if (contents[index] == 10) {
|
||||
if (onlySpaces) {
|
||||
index = lineStart; // this is it
|
||||
break;
|
||||
} else {
|
||||
lineStart = index + 1;
|
||||
onlySpaces = true; // reset
|
||||
}
|
||||
} else if (contents[index] != ' ') {
|
||||
onlySpaces = false;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
// if the first char, index will be -2
|
||||
//if (index < 0) index = 0;
|
||||
|
||||
//textarea.setSelectionStart(index);
|
||||
//textarea.setSelectionEnd(index);
|
||||
if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
|
||||
textarea.setSelectionStart(caretIndex);
|
||||
textarea.setSelectionEnd(index);
|
||||
} else {
|
||||
textarea.setCaretPosition(index);
|
||||
}
|
||||
event.consume();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 9: // TAB
|
||||
if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
|
||||
// if shift is down, the user always expects an outdent
|
||||
// http://code.google.com/p/processing/issues/detail?id=458
|
||||
editor.handleOutdent();
|
||||
|
||||
} else if (textarea.isSelectionActive()) {
|
||||
editor.handleIndent();
|
||||
|
||||
} else if (Preferences.getBoolean("editor.tabs.expand")) {
|
||||
int tabSize = Preferences.getInteger("editor.tabs.size");
|
||||
textarea.setSelectedText(spaces(tabSize));
|
||||
event.consume();
|
||||
return true;
|
||||
} else if (!Preferences.getBoolean("editor.tabs.expand")) {
|
||||
textarea.setSelectedText("\t");
|
||||
event.consume();
|
||||
}
|
||||
break;
|
||||
|
||||
case 10: // auto-indent
|
||||
case 13:
|
||||
if (Preferences.getBoolean("editor.indent")) {
|
||||
char contents[] = textarea.getText().toCharArray();
|
||||
int tabSize = Preferences.getInteger("editor.tabs.size");
|
||||
|
||||
// this is the previous character
|
||||
// (i.e. when you hit return, it'll be the last character
|
||||
// just before where the newline will be inserted)
|
||||
int origIndex = textarea.getCaretPosition() - 1;
|
||||
|
||||
// NOTE all this cursing about CRLF stuff is probably moot
|
||||
// NOTE since the switch to JEditTextArea, which seems to use
|
||||
// NOTE only LFs internally (thank god). disabling for 0099.
|
||||
// walk through the array to the current caret position,
|
||||
// and count how many weirdo windows line endings there are,
|
||||
// which would be throwing off the caret position number
|
||||
/*
|
||||
int offset = 0;
|
||||
int realIndex = origIndex;
|
||||
for (int i = 0; i < realIndex-1; i++) {
|
||||
if ((contents[i] == 13) && (contents[i+1] == 10)) {
|
||||
offset++;
|
||||
realIndex++;
|
||||
}
|
||||
}
|
||||
// back up until \r \r\n or \n.. @#($* cross platform
|
||||
//System.out.println(origIndex + " offset = " + offset);
|
||||
origIndex += offset; // ARGH!#(* WINDOWS#@($*
|
||||
*/
|
||||
|
||||
// if the previous thing is a brace (whether prev line or
|
||||
// up farther) then the correct indent is the number of spaces
|
||||
// on that line + 'indent'.
|
||||
// if the previous line is not a brace, then just use the
|
||||
// identical indentation to the previous line
|
||||
|
||||
// calculate the amount of indent on the previous line
|
||||
// this will be used *only if the prev line is not an indent*
|
||||
int spaceCount = calcSpaceCount(origIndex, contents);
|
||||
|
||||
// If the last character was a left curly brace, then indent.
|
||||
// For 0122, walk backwards a bit to make sure that the there
|
||||
// isn't a curly brace several spaces (or lines) back. Also
|
||||
// moved this before calculating extraCount, since it'll affect
|
||||
// that as well.
|
||||
int index2 = origIndex;
|
||||
while ((index2 >= 0) &&
|
||||
Character.isWhitespace(contents[index2])) {
|
||||
index2--;
|
||||
}
|
||||
if (index2 != -1) {
|
||||
// still won't catch a case where prev stuff is a comment
|
||||
if (contents[index2] == '{') {
|
||||
// intermediate lines be damned,
|
||||
// use the indent for this line instead
|
||||
spaceCount = calcSpaceCount(index2, contents);
|
||||
spaceCount += tabSize;
|
||||
}
|
||||
}
|
||||
//System.out.println("spaceCount should be " + spaceCount);
|
||||
|
||||
// now before inserting this many spaces, walk forward from
|
||||
// the caret position and count the number of spaces,
|
||||
// so that the number of spaces aren't duplicated again
|
||||
int index = origIndex + 1;
|
||||
int extraCount = 0;
|
||||
while ((index < contents.length) &&
|
||||
(contents[index] == ' ')) {
|
||||
//spaceCount--;
|
||||
extraCount++;
|
||||
index++;
|
||||
}
|
||||
int braceCount = 0;
|
||||
while ((index < contents.length) && (contents[index] != '\n')) {
|
||||
if (contents[index] == '}') {
|
||||
braceCount++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
// hitting return on a line with spaces *after* the caret
|
||||
// can cause trouble. for 0099, was ignoring the case, but this is
|
||||
// annoying, so in 0122 we're trying to fix that.
|
||||
/*
|
||||
if (spaceCount - extraCount > 0) {
|
||||
spaceCount -= extraCount;
|
||||
}
|
||||
*/
|
||||
spaceCount -= extraCount;
|
||||
//if (spaceCount < 0) spaceCount = 0;
|
||||
//System.out.println("extraCount is " + extraCount);
|
||||
|
||||
// now, check to see if the current line contains a } and if so,
|
||||
// outdent again by indent
|
||||
//if (braceCount > 0) {
|
||||
//spaceCount -= 2;
|
||||
//}
|
||||
|
||||
if (spaceCount < 0) {
|
||||
// for rev 0122, actually delete extra space
|
||||
//textarea.setSelectionStart(origIndex + 1);
|
||||
textarea.setSelectionEnd(textarea.getSelectionStop() - spaceCount);
|
||||
textarea.setSelectedText("\n");
|
||||
textarea.setCaretPosition(textarea.getCaretPosition() + extraCount + spaceCount);
|
||||
} else {
|
||||
String insertion = "\n" + spaces(spaceCount);
|
||||
textarea.setSelectedText(insertion);
|
||||
textarea.setCaretPosition(textarea.getCaretPosition() + extraCount);
|
||||
}
|
||||
|
||||
// not gonna bother handling more than one brace
|
||||
if (braceCount > 0) {
|
||||
int sel = textarea.getSelectionStart();
|
||||
// sel - tabSize will be -1 if start/end parens on the same line
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=484
|
||||
if (sel - tabSize >= 0) {
|
||||
textarea.select(sel - tabSize, sel);
|
||||
String s = spaces(tabSize);
|
||||
// if these are spaces that we can delete
|
||||
if (textarea.getSelectedText().equals(s)) {
|
||||
textarea.setSelectedText("");
|
||||
} else {
|
||||
textarea.select(sel, sel);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Enter/Return was being consumed by somehow even if false
|
||||
// was returned, so this is a band-aid to simply fire the event again.
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=1073
|
||||
textarea.setSelectedText(String.valueOf(c));
|
||||
}
|
||||
// mark this event as already handled (all but ignored)
|
||||
event.consume();
|
||||
return true;
|
||||
|
||||
case '}':
|
||||
if (Preferences.getBoolean("editor.indent")) {
|
||||
// first remove anything that was there (in case this multiple
|
||||
// characters are selected, so that it's not in the way of the
|
||||
// spaces for the auto-indent
|
||||
if (textarea.getSelectionStart() != textarea.getSelectionStop()) {
|
||||
textarea.setSelectedText("");
|
||||
}
|
||||
|
||||
// if this brace is the only thing on the line, outdent
|
||||
char contents[] = textarea.getText().toCharArray();
|
||||
// index to the character to the left of the caret
|
||||
int prevCharIndex = textarea.getCaretPosition() - 1;
|
||||
|
||||
// backup from the current caret position to the last newline,
|
||||
// checking for anything besides whitespace along the way.
|
||||
// if there's something besides whitespace, exit without
|
||||
// messing any sort of indenting.
|
||||
int index = prevCharIndex;
|
||||
boolean finished = false;
|
||||
while ((index != -1) && (!finished)) {
|
||||
if (contents[index] == 10) {
|
||||
finished = true;
|
||||
index++;
|
||||
} else if (contents[index] != ' ') {
|
||||
// don't do anything, this line has other stuff on it
|
||||
return false;
|
||||
} else {
|
||||
index--;
|
||||
}
|
||||
}
|
||||
if (!finished) return false; // brace with no start
|
||||
int lineStartIndex = index;
|
||||
|
||||
int pairedSpaceCount = calcBraceIndent(prevCharIndex, contents); //, 1);
|
||||
if (pairedSpaceCount == -1) return false;
|
||||
|
||||
textarea.setSelectionStart(lineStartIndex);
|
||||
textarea.setSelectedText(spaces(pairedSpaceCount));
|
||||
|
||||
// mark this event as already handled
|
||||
event.consume();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean keyTyped(KeyEvent event) {
|
||||
char c = event.getKeyChar();
|
||||
|
||||
if ((event.getModifiers() & InputEvent.CTRL_MASK) != 0) {
|
||||
// on linux, ctrl-comma (prefs) being passed through to the editor
|
||||
if (c == KeyEvent.VK_COMMA) {
|
||||
event.consume();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return the index for the first character on this line.
|
||||
*/
|
||||
protected int calcLineStart(int index, char contents[]) {
|
||||
// backup from the current caret position to the last newline,
|
||||
// so that we can figure out how far this line was indented
|
||||
/*int spaceCount = 0;*/
|
||||
boolean finished = false;
|
||||
while ((index != -1) && (!finished)) {
|
||||
if ((contents[index] == 10) ||
|
||||
(contents[index] == 13)) {
|
||||
finished = true;
|
||||
//index++; // maybe ?
|
||||
} else {
|
||||
index--; // new
|
||||
}
|
||||
}
|
||||
// add one because index is either -1 (the start of the document)
|
||||
// or it's the newline character for the previous line
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the number of spaces on this line.
|
||||
*/
|
||||
protected int calcSpaceCount(int index, char contents[]) {
|
||||
index = calcLineStart(index, contents);
|
||||
|
||||
int spaceCount = 0;
|
||||
// now walk forward and figure out how many spaces there are
|
||||
while ((index < contents.length) && (index >= 0) &&
|
||||
(contents[index++] == ' ')) {
|
||||
spaceCount++;
|
||||
}
|
||||
return spaceCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Walk back from 'index' until the brace that seems to be
|
||||
* the beginning of the current block, and return the number of
|
||||
* spaces found on that line.
|
||||
*/
|
||||
protected int calcBraceIndent(int index, char contents[]) {
|
||||
// now that we know things are ok to be indented, walk
|
||||
// backwards to the last { to see how far its line is indented.
|
||||
// this isn't perfect cuz it'll pick up commented areas,
|
||||
// but that's not really a big deal and can be fixed when
|
||||
// this is all given a more complete (proper) solution.
|
||||
int braceDepth = 1;
|
||||
boolean finished = false;
|
||||
while ((index != -1) && (!finished)) {
|
||||
if (contents[index] == '}') {
|
||||
// aww crap, this means we're one deeper
|
||||
// and will have to find one more extra {
|
||||
braceDepth++;
|
||||
//if (braceDepth == 0) {
|
||||
//finished = true;
|
||||
//}
|
||||
index--;
|
||||
} else if (contents[index] == '{') {
|
||||
braceDepth--;
|
||||
if (braceDepth == 0) {
|
||||
finished = true;
|
||||
}
|
||||
index--;
|
||||
} else {
|
||||
index--;
|
||||
}
|
||||
}
|
||||
// never found a proper brace, be safe and don't do anything
|
||||
if (!finished) return -1;
|
||||
|
||||
// check how many spaces on the line with the matching open brace
|
||||
//int pairedSpaceCount = calcSpaceCount(index, contents);
|
||||
//System.out.println(pairedSpaceCount);
|
||||
return calcSpaceCount(index, contents);
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * Get the character array and blank out the commented areas.
|
||||
// * This hasn't yet been tested, the plan was to make auto-indent
|
||||
// * less gullible (it gets fooled by braces that are commented out).
|
||||
// */
|
||||
// protected char[] getCleanedContents() {
|
||||
// char c[] = textarea.getText().toCharArray();
|
||||
//
|
||||
// int index = 0;
|
||||
// while (index < c.length - 1) {
|
||||
// if ((c[index] == '/') && (c[index+1] == '*')) {
|
||||
// c[index++] = 0;
|
||||
// c[index++] = 0;
|
||||
// while ((index < c.length - 1) &&
|
||||
// !((c[index] == '*') && (c[index+1] == '/'))) {
|
||||
// c[index++] = 0;
|
||||
// }
|
||||
//
|
||||
// } else if ((c[index] == '/') && (c[index+1] == '/')) {
|
||||
// // clear out until the end of the line
|
||||
// while ((index < c.length) && (c[index] != 10)) {
|
||||
// c[index++] = 0;
|
||||
// }
|
||||
// if (index != c.length) {
|
||||
// index++; // skip over the newline
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return c;
|
||||
// }
|
||||
|
||||
|
||||
static String spaces(int count) {
|
||||
char[] c = new char[count];
|
||||
Arrays.fill(c, ' ');
|
||||
return new String(c);
|
||||
}
|
||||
}
|
||||
124
java/src/processing/mode/java/PresentMode.java
Normal file
124
java/src/processing/mode/java/PresentMode.java
Normal file
@@ -0,0 +1,124 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2005-09 Ben Fry and Casey Reas
|
||||
|
||||
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;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Vector;
|
||||
import javax.swing.*;
|
||||
|
||||
import processing.app.Preferences;
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for full-screen presentation mode.
|
||||
*/
|
||||
public class PresentMode {
|
||||
|
||||
static GraphicsDevice devices[];
|
||||
|
||||
/**
|
||||
* Index of the default display device, probably the one that p5 was
|
||||
* started from.
|
||||
*/
|
||||
static int defaultIndex;
|
||||
|
||||
/**
|
||||
* Menu object for preferences window
|
||||
*/
|
||||
//JMenu preferencesMenu;
|
||||
static JComboBox selector;
|
||||
|
||||
|
||||
static {
|
||||
GraphicsEnvironment environment =
|
||||
GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
devices = environment.getScreenDevices();
|
||||
GraphicsDevice defaultDevice = environment.getDefaultScreenDevice();
|
||||
|
||||
Vector<String> names = new Vector<String>();
|
||||
for (int i = 0; i < devices.length; i++) {
|
||||
String name = String.valueOf(i + 1);
|
||||
if (devices[i] == defaultDevice) {
|
||||
defaultIndex = i;
|
||||
name += " (default)";
|
||||
}
|
||||
names.add(name);
|
||||
}
|
||||
|
||||
selector = new JComboBox(names);
|
||||
selector.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
int index = selector.getSelectedIndex();
|
||||
Preferences.setInteger("run.present.display", index + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static public JComboBox getSelector() {
|
||||
int deviceIndex = Preferences.getInteger("run.present.display") - 1;
|
||||
selector.setSelectedIndex(deviceIndex);
|
||||
return selector;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
static public JFrame create() {
|
||||
int deviceIndex = PrePreferences.getInteger("run.present.display") - 1;
|
||||
if ((deviceIndex < 0) || (deviceIndex >= devices.length)) {
|
||||
Base.showWarning("Display doesn't exist",
|
||||
"Present Mode is set to use display " +
|
||||
(deviceIndex+1) +
|
||||
" but that doesn't seem to exist. \n" +
|
||||
"This preference will be reset to " +
|
||||
"use the default display.", null);
|
||||
deviceIndex = defaultIndex;
|
||||
}
|
||||
//GraphicsDevice device = devices[
|
||||
//JFrame frame = new JFrame(devices[deviceIndex]);
|
||||
PresentFrame frame = new PresentFrame(devices[deviceIndex]);
|
||||
}
|
||||
|
||||
|
||||
public void show() {
|
||||
setUndecorated(true);
|
||||
setResizable(false);
|
||||
device.setFullScreenWindow(this);
|
||||
}
|
||||
|
||||
|
||||
public Window getWindow() {
|
||||
return device.getFullScreenWindow(); // isn't this just me?
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
Window window = device.getFullScreenWindow();
|
||||
if (window != null) {
|
||||
window.dispose();
|
||||
}
|
||||
device.setFullScreenWindow(null);
|
||||
}
|
||||
*/
|
||||
}
|
||||
9
java/src/processing/mode/java/preproc/.cvsignore
Normal file
9
java/src/processing/mode/java/preproc/.cvsignore
Normal file
@@ -0,0 +1,9 @@
|
||||
*Lexer.java
|
||||
*Recognizer.java
|
||||
*TokenTypes.java
|
||||
*TokenTypes.txt
|
||||
*TreeParser.java
|
||||
*TreeParserTokenTypes.java
|
||||
*TreeParserTokenTypes.txt
|
||||
expanded*.g
|
||||
|
||||
22
java/src/processing/mode/java/preproc/ParserTests.launch
Normal file
22
java/src/processing/mode/java/preproc/ParserTests.launch
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/processing-app/test/src/test/processing/mode/java/ParserTests.java"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
|
||||
<mapEntry key="[run]" value="org.eclipse.jdt.junit.launchconfig"/>
|
||||
</mapAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/>
|
||||
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="test.processing.mode.java.ParserTests"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="processing-app"/>
|
||||
</launchConfiguration>
|
||||
773
java/src/processing/mode/java/preproc/PdeEmitter.java
Normal file
773
java/src/processing/mode/java/preproc/PdeEmitter.java
Normal file
@@ -0,0 +1,773 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.BitSet;
|
||||
import java.util.Stack;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.SketchException;
|
||||
import processing.mode.java.preproc.PdeTokenTypes;
|
||||
import antlr.CommonASTWithHiddenTokens;
|
||||
import antlr.CommonHiddenStreamToken;
|
||||
import antlr.collections.AST;
|
||||
|
||||
/* Based on original code copyright (c) 2003 Andy Tripp <atripp@comcast.net>.
|
||||
* shipped under GPL with permission.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PDEEmitter: A class that can take an ANTLR Java AST and produce
|
||||
* reasonably formatted Java code from it. To use it, create a
|
||||
* PDEEmitter object, call setOut() if you want to print to something
|
||||
* other than System.out, and then call print(), passing the
|
||||
* AST. Typically, the AST node that you pass would be the root of a
|
||||
* tree - the ROOT_ID node that represents a Java file.
|
||||
*
|
||||
* Modified March 2010 to support Java 5 type arguments and for loops by
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*/
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class PdeEmitter implements PdeTokenTypes {
|
||||
private final PdePreprocessor pdePreprocessor;
|
||||
private final PrintWriter out;
|
||||
private final PrintStream debug = System.err;
|
||||
|
||||
private final Stack<AST> stack = new Stack<AST>();
|
||||
private final static int ROOT_ID = 0;
|
||||
|
||||
public PdeEmitter(final PdePreprocessor pdePreprocessor, final PrintWriter out) {
|
||||
this.pdePreprocessor = pdePreprocessor;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a child of the given AST that has the given type
|
||||
* @returns a child AST of the given type. If it can't find a child of the
|
||||
* given type, return null.
|
||||
*/
|
||||
private AST getChild(final AST ast, final int childType) {
|
||||
AST child = ast.getFirstChild();
|
||||
while (child != null) {
|
||||
if (child.getType() == childType) {
|
||||
// debug.println("getChild: found:" + name(ast));
|
||||
return child;
|
||||
}
|
||||
child = child.getNextSibling();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the list of hidden tokens linked to after the AST node passed in.
|
||||
* Most hidden tokens are dumped from this function.
|
||||
*/
|
||||
private void dumpHiddenAfter(final AST ast) {
|
||||
dumpHiddenTokens(((CommonASTWithHiddenTokens) ast).getHiddenAfter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the list of hidden tokens linked to before the AST node passed in.
|
||||
* The only time hidden tokens need to be dumped with this function is when
|
||||
* dealing parts of the tree where automatic tree construction was
|
||||
* turned off with the ! operator in the grammar file and the nodes were
|
||||
* manually constructed in such a way that the usual tokens don't have the
|
||||
* necessary hiddenAfter links.
|
||||
*/
|
||||
private void dumpHiddenBefore(final AST ast) {
|
||||
|
||||
antlr.CommonHiddenStreamToken child = null, parent = ((CommonASTWithHiddenTokens) ast)
|
||||
.getHiddenBefore();
|
||||
|
||||
// if there aren't any hidden tokens here, quietly return
|
||||
//
|
||||
if (parent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// traverse back to the head of the list of tokens before this node
|
||||
do {
|
||||
child = parent;
|
||||
parent = child.getHiddenBefore();
|
||||
} while (parent != null);
|
||||
|
||||
// dump that list
|
||||
dumpHiddenTokens(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the list of hidden tokens linked to from the token passed in.
|
||||
*/
|
||||
private void dumpHiddenTokens(CommonHiddenStreamToken t) {
|
||||
for (; t != null; t = pdePreprocessor.getHiddenAfter(t)) {
|
||||
out.print(t.getText());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the children of the given AST
|
||||
* @param ast The AST to print
|
||||
* @returns true iff anything was printed
|
||||
*/
|
||||
private boolean printChildren(final AST ast) throws SketchException {
|
||||
boolean ret = false;
|
||||
AST child = ast.getFirstChild();
|
||||
while (child != null) {
|
||||
ret = true;
|
||||
print(child);
|
||||
child = child.getNextSibling();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether an AST has any children or not.
|
||||
* @return true iff the AST has at least one child
|
||||
*/
|
||||
private boolean hasChildren(final AST ast) {
|
||||
return (ast.getFirstChild() != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the best node in the subtree for printing. This really means
|
||||
* the next node which could potentially have hiddenBefore data. It's
|
||||
* usually the first printable leaf, but not always.
|
||||
*
|
||||
* @param includeThisNode Should this node be included in the search?
|
||||
* If false, only descendants are searched.
|
||||
*
|
||||
* @return the first printable leaf node in an AST
|
||||
*/
|
||||
private AST getBestPrintableNode(final AST ast, final boolean includeThisNode) {
|
||||
AST child;
|
||||
|
||||
if (includeThisNode) {
|
||||
child = ast;
|
||||
} else {
|
||||
child = ast.getFirstChild();
|
||||
}
|
||||
|
||||
if (child != null) {
|
||||
|
||||
switch (child.getType()) {
|
||||
|
||||
// the following node types are printing nodes that print before
|
||||
// any children, but then also recurse over children. So they
|
||||
// may have hiddenBefore chains that need to be printed first. Many
|
||||
// statements and all unary expression types qualify. Return these
|
||||
// nodes directly
|
||||
case CLASS_DEF:
|
||||
case ENUM_DEF:
|
||||
case LITERAL_if:
|
||||
case LITERAL_new:
|
||||
case LITERAL_for:
|
||||
case LITERAL_while:
|
||||
case LITERAL_do:
|
||||
case LITERAL_break:
|
||||
case LITERAL_continue:
|
||||
case LITERAL_return:
|
||||
case LITERAL_switch:
|
||||
case LITERAL_try:
|
||||
case LITERAL_throw:
|
||||
case LITERAL_synchronized:
|
||||
case LITERAL_assert:
|
||||
case BNOT:
|
||||
case LNOT:
|
||||
case INC:
|
||||
case DEC:
|
||||
case UNARY_MINUS:
|
||||
case UNARY_PLUS:
|
||||
return child;
|
||||
|
||||
// Some non-terminal node types (at the moment, I only know of
|
||||
// MODIFIERS, but there may be other such types), can be
|
||||
// leaves in the tree but not have any children. If this is
|
||||
// such a node, move on to the next sibling.
|
||||
case MODIFIERS:
|
||||
if (child.getFirstChild() == null) {
|
||||
return getBestPrintableNode(child.getNextSibling(), false);
|
||||
}
|
||||
// new jikes doesn't like fallthrough, so just duplicated here:
|
||||
return getBestPrintableNode(child, false);
|
||||
|
||||
default:
|
||||
return getBestPrintableNode(child, false);
|
||||
}
|
||||
}
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
// Because the meanings of <, >, >>, and >>> are overloaded to support
|
||||
// type arguments and type parameters, we have to treat them
|
||||
// as copyable to hidden text (or else the following syntax,
|
||||
// such as (); and what not gets lost under certain circumstances
|
||||
//
|
||||
// Since they are copied to the hidden stream, you don't want
|
||||
// to print them explicitly; they come out in the dumpHiddenXXX methods.
|
||||
// -- jdf
|
||||
private static final BitSet OTHER_COPIED_TOKENS = new BitSet() {
|
||||
{
|
||||
set(LT);
|
||||
set(GT);
|
||||
set(SR);
|
||||
set(BSR);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prints a binary operator
|
||||
*/
|
||||
private void printBinaryOperator(final AST ast) throws SketchException {
|
||||
print(ast.getFirstChild());
|
||||
if (!OTHER_COPIED_TOKENS.get(ast.getType())) {
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
}
|
||||
print(ast.getFirstChild().getNextSibling());
|
||||
}
|
||||
|
||||
private void printMethodDef(final AST ast) throws SketchException {
|
||||
final AST modifiers = ast.getFirstChild();
|
||||
final AST typeParameters, type;
|
||||
if (modifiers.getNextSibling().getType() == TYPE_PARAMETERS) {
|
||||
typeParameters = modifiers.getNextSibling();
|
||||
type = typeParameters.getNextSibling();
|
||||
} else {
|
||||
typeParameters = null;
|
||||
type = modifiers.getNextSibling();
|
||||
}
|
||||
final AST methodName = type.getNextSibling();
|
||||
// if (methodName.getText().equals("main")) {
|
||||
// pdePreprocessor.setFoundMain(true);
|
||||
// }
|
||||
pdePreprocessor.addMethod(methodName.getText());
|
||||
printChildren(ast);
|
||||
}
|
||||
|
||||
private void printIfThenElse(final AST literalIf) throws SketchException {
|
||||
out.print(literalIf.getText());
|
||||
dumpHiddenAfter(literalIf);
|
||||
|
||||
final AST condition = literalIf.getFirstChild();
|
||||
print(condition); // the "if" condition: an EXPR
|
||||
|
||||
// the "then" clause is either an SLIST or an EXPR
|
||||
final AST thenPath = condition.getNextSibling();
|
||||
print(thenPath);
|
||||
|
||||
// optional "else" clause: an SLIST or an EXPR
|
||||
// what could be simpler?
|
||||
final AST elsePath = thenPath.getNextSibling();
|
||||
if (elsePath != null) {
|
||||
out.print("else");
|
||||
final AST bestPrintableNode = getBestPrintableNode(elsePath, true);
|
||||
dumpHiddenBefore(bestPrintableNode);
|
||||
final CommonHiddenStreamToken hiddenBefore =
|
||||
((CommonASTWithHiddenTokens) elsePath).getHiddenBefore();
|
||||
if (elsePath.getType() == PdeTokenTypes.SLIST && elsePath.getNumberOfChildren() == 0 &&
|
||||
hiddenBefore == null) {
|
||||
out.print("{");
|
||||
final CommonHiddenStreamToken hiddenAfter =
|
||||
((CommonASTWithHiddenTokens) elsePath).getHiddenAfter();
|
||||
if (hiddenAfter == null) {
|
||||
out.print("}");
|
||||
} else {
|
||||
dumpHiddenTokens(hiddenAfter);
|
||||
}
|
||||
} else {
|
||||
print(elsePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the given AST. Call this function to print your PDE code.
|
||||
*
|
||||
* It works by making recursive calls to print children.
|
||||
* So the code below is one big "switch" statement on the passed AST type.
|
||||
*/
|
||||
public void print(final AST ast) throws SketchException {
|
||||
if (ast == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack.push(ast);
|
||||
|
||||
final AST child1 = ast.getFirstChild();
|
||||
AST child2 = null;
|
||||
AST child3 = null;
|
||||
if (child1 != null) {
|
||||
child2 = child1.getNextSibling();
|
||||
if (child2 != null) {
|
||||
child3 = child2.getNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
switch (ast.getType()) {
|
||||
// The top of the tree looks like this:
|
||||
// ROOT_ID "Whatever.java"
|
||||
// package
|
||||
// imports
|
||||
// class definition
|
||||
case ROOT_ID:
|
||||
dumpHiddenTokens(pdePreprocessor.getInitialHiddenToken());
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
// supporting a "package" statement in a PDE program has
|
||||
// a bunch of issues with it that need to dealt in the compilation
|
||||
// code too, so this isn't actually tested.
|
||||
case PACKAGE_DEF:
|
||||
out.print("package");
|
||||
dumpHiddenAfter(ast);
|
||||
print(ast.getFirstChild());
|
||||
break;
|
||||
|
||||
// IMPORT has exactly one child
|
||||
case IMPORT:
|
||||
out.print("import");
|
||||
dumpHiddenAfter(ast);
|
||||
print(ast.getFirstChild());
|
||||
break;
|
||||
|
||||
case STATIC_IMPORT:
|
||||
out.print("import static");
|
||||
dumpHiddenAfter(ast);
|
||||
print(ast.getFirstChild());
|
||||
break;
|
||||
|
||||
case CLASS_DEF:
|
||||
case ENUM_DEF:
|
||||
case INTERFACE_DEF:
|
||||
print(getChild(ast, MODIFIERS));
|
||||
if (ast.getType() == CLASS_DEF) {
|
||||
out.print("class");
|
||||
} else if (ast.getType() == ENUM_DEF) {
|
||||
out.print("enum");
|
||||
} else {
|
||||
out.print("interface");
|
||||
}
|
||||
dumpHiddenBefore(getChild(ast, IDENT));
|
||||
print(getChild(ast, IDENT));
|
||||
print(getChild(ast, TYPE_PARAMETERS));
|
||||
print(getChild(ast, EXTENDS_CLAUSE));
|
||||
print(getChild(ast, IMPLEMENTS_CLAUSE));
|
||||
print(getChild(ast, OBJBLOCK));
|
||||
break;
|
||||
|
||||
case EXTENDS_CLAUSE:
|
||||
if (hasChildren(ast)) {
|
||||
out.print("extends");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast, false));
|
||||
printChildren(ast);
|
||||
}
|
||||
break;
|
||||
|
||||
case IMPLEMENTS_CLAUSE:
|
||||
if (hasChildren(ast)) {
|
||||
out.print("implements");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast, false));
|
||||
printChildren(ast);
|
||||
}
|
||||
break;
|
||||
|
||||
// DOT always has exactly two children.
|
||||
case DOT:
|
||||
print(child1);
|
||||
out.print(".");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child2);
|
||||
break;
|
||||
|
||||
case MODIFIERS:
|
||||
case OBJBLOCK:
|
||||
case CTOR_DEF:
|
||||
//case METHOD_DEF:
|
||||
case PARAMETERS:
|
||||
case PARAMETER_DEF:
|
||||
case VARIABLE_PARAMETER_DEF:
|
||||
case VARIABLE_DEF:
|
||||
case ENUM_CONSTANT_DEF:
|
||||
case TYPE:
|
||||
case SLIST:
|
||||
case ELIST:
|
||||
case ARRAY_DECLARATOR:
|
||||
case TYPECAST:
|
||||
case EXPR:
|
||||
case ARRAY_INIT:
|
||||
case FOR_INIT:
|
||||
case FOR_CONDITION:
|
||||
case FOR_ITERATOR:
|
||||
case METHOD_CALL:
|
||||
case INSTANCE_INIT:
|
||||
case INDEX_OP:
|
||||
case SUPER_CTOR_CALL:
|
||||
case CTOR_CALL:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case METHOD_DEF:
|
||||
printMethodDef(ast);
|
||||
break;
|
||||
|
||||
// if we have two children, it's of the form "a=0"
|
||||
// if just one child, it's of the form "=0" (where the
|
||||
// lhs is above this AST).
|
||||
case ASSIGN:
|
||||
if (child2 != null) {
|
||||
print(child1);
|
||||
out.print("=");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child2);
|
||||
} else {
|
||||
out.print("=");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1);
|
||||
}
|
||||
break;
|
||||
|
||||
// binary operators:
|
||||
case PLUS:
|
||||
case MINUS:
|
||||
case DIV:
|
||||
case MOD:
|
||||
case NOT_EQUAL:
|
||||
case EQUAL:
|
||||
case LE:
|
||||
case GE:
|
||||
case LOR:
|
||||
case LAND:
|
||||
case BOR:
|
||||
case BXOR:
|
||||
case BAND:
|
||||
case SL:
|
||||
case SR:
|
||||
case BSR:
|
||||
case LITERAL_instanceof:
|
||||
case PLUS_ASSIGN:
|
||||
case MINUS_ASSIGN:
|
||||
case STAR_ASSIGN:
|
||||
case DIV_ASSIGN:
|
||||
case MOD_ASSIGN:
|
||||
case SR_ASSIGN:
|
||||
case BSR_ASSIGN:
|
||||
case SL_ASSIGN:
|
||||
case BAND_ASSIGN:
|
||||
case BXOR_ASSIGN:
|
||||
case BOR_ASSIGN:
|
||||
|
||||
case LT:
|
||||
case GT:
|
||||
printBinaryOperator(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_for:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
if (child1.getType() == FOR_EACH_CLAUSE) {
|
||||
printChildren(child1);
|
||||
print(child2);
|
||||
} else {
|
||||
printChildren(ast);
|
||||
}
|
||||
break;
|
||||
|
||||
case POST_INC:
|
||||
case POST_DEC:
|
||||
print(child1);
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
// unary operators:
|
||||
case BNOT:
|
||||
case LNOT:
|
||||
case INC:
|
||||
case DEC:
|
||||
case UNARY_MINUS:
|
||||
case UNARY_PLUS:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1);
|
||||
break;
|
||||
|
||||
case LITERAL_new:
|
||||
out.print("new");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_return:
|
||||
out.print("return");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1);
|
||||
break;
|
||||
|
||||
case STATIC_INIT:
|
||||
out.print("static");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast, false));
|
||||
print(child1);
|
||||
break;
|
||||
|
||||
case LITERAL_switch:
|
||||
out.print("switch");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LABELED_STAT:
|
||||
case CASE_GROUP:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_case:
|
||||
out.print("case");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_default:
|
||||
out.print("default");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case NUM_INT:
|
||||
case CHAR_LITERAL:
|
||||
case STRING_LITERAL:
|
||||
case NUM_FLOAT:
|
||||
case NUM_LONG:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_synchronized: // 0137 to fix bug #136
|
||||
case LITERAL_assert:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_private:
|
||||
case LITERAL_public:
|
||||
case LITERAL_protected:
|
||||
case LITERAL_static:
|
||||
case LITERAL_transient:
|
||||
case LITERAL_native:
|
||||
case LITERAL_threadsafe:
|
||||
//case LITERAL_synchronized: // 0137 to fix bug #136
|
||||
case LITERAL_volatile:
|
||||
case LITERAL_class: // 0176 to fix bug #1466
|
||||
case FINAL:
|
||||
case ABSTRACT:
|
||||
case LITERAL_package:
|
||||
case LITERAL_void:
|
||||
case LITERAL_boolean:
|
||||
case LITERAL_byte:
|
||||
case LITERAL_char:
|
||||
case LITERAL_short:
|
||||
case LITERAL_int:
|
||||
case LITERAL_float:
|
||||
case LITERAL_long:
|
||||
case LITERAL_double:
|
||||
case LITERAL_true:
|
||||
case LITERAL_false:
|
||||
case LITERAL_null:
|
||||
case SEMI:
|
||||
case LITERAL_this:
|
||||
case LITERAL_super:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
case EMPTY_STAT:
|
||||
case EMPTY_FIELD:
|
||||
break;
|
||||
|
||||
case LITERAL_continue:
|
||||
case LITERAL_break:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
if (child1 != null) {// maybe label
|
||||
print(child1);
|
||||
}
|
||||
break;
|
||||
|
||||
// yuck: Distinguish between "import x.y.*" and "x = 1 * 3"
|
||||
case STAR:
|
||||
if (hasChildren(ast)) { // the binary mult. operator
|
||||
printBinaryOperator(ast);
|
||||
} else { // the special "*" in import:
|
||||
out.print("*");
|
||||
dumpHiddenAfter(ast);
|
||||
}
|
||||
break;
|
||||
|
||||
case LITERAL_throws:
|
||||
out.print("throws");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_if:
|
||||
printIfThenElse(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_while:
|
||||
out.print("while");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_do:
|
||||
out.print("do");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1); // an SLIST
|
||||
out.print("while");
|
||||
dumpHiddenBefore(getBestPrintableNode(child2, false));
|
||||
print(child2); // an EXPR
|
||||
break;
|
||||
|
||||
case LITERAL_try:
|
||||
out.print("try");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_catch:
|
||||
out.print("catch");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
// the first child is the "try" and the second is the SLIST
|
||||
case LITERAL_finally:
|
||||
out.print("finally");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_throw:
|
||||
out.print("throw");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1);
|
||||
break;
|
||||
|
||||
// the dreaded trinary operator
|
||||
case QUESTION:
|
||||
print(child1);
|
||||
out.print("?");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child2);
|
||||
print(child3);
|
||||
break;
|
||||
|
||||
// pde specific or modified tokens start here
|
||||
|
||||
// Image -> BImage, Font -> BFont as appropriate
|
||||
case IDENT:
|
||||
/*
|
||||
if (ast.getText().equals("Image") &&
|
||||
Preferences.getBoolean("preproc.substitute_image")) { //, true)) {
|
||||
out.print("BImage");
|
||||
} else if (ast.getText().equals("Font") &&
|
||||
Preferences.getBoolean("preproc.substitute_font")) { //, true)) {
|
||||
out.print("BFont");
|
||||
} else {
|
||||
*/
|
||||
out.print(ast.getText());
|
||||
//}
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
// the color datatype is just an alias for int
|
||||
case LITERAL_color:
|
||||
out.print("int");
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
case WEBCOLOR_LITERAL:
|
||||
if (ast.getText().length() != 6) {
|
||||
System.err.println("Internal error: incorrect length of webcolor "
|
||||
+ "literal should have been detected sooner.");
|
||||
break;
|
||||
}
|
||||
out.print("0xff" + ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
// allow for stuff like int(43.2).
|
||||
case CONSTRUCTOR_CAST:
|
||||
final AST terminalTypeNode = child1.getFirstChild();
|
||||
final AST exprToCast = child2;
|
||||
final String pooType = terminalTypeNode.getText();
|
||||
out.print("PApplet.parse" + Character.toUpperCase(pooType.charAt(0))
|
||||
+ pooType.substring(1));
|
||||
dumpHiddenAfter(terminalTypeNode); // the left paren
|
||||
print(exprToCast);
|
||||
break;
|
||||
|
||||
// making floating point literals default to floats, not doubles
|
||||
case NUM_DOUBLE:
|
||||
final String literalDouble = ast.getText().toLowerCase();
|
||||
out.print(literalDouble);
|
||||
if (Preferences.getBoolean("preproc.substitute_floats")
|
||||
&& literalDouble.indexOf('d') == -1) { // permit literal doubles
|
||||
out.print("f");
|
||||
}
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
case TYPE_ARGUMENTS:
|
||||
case TYPE_PARAMETERS:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case TYPE_ARGUMENT:
|
||||
case TYPE_PARAMETER:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case WILDCARD_TYPE:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
print(ast.getFirstChild());
|
||||
break;
|
||||
|
||||
case TYPE_LOWER_BOUNDS:
|
||||
case TYPE_UPPER_BOUNDS:
|
||||
out.print(ast.getType() == TYPE_LOWER_BOUNDS ? "super" : "extends");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast, false));
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case ANNOTATION:
|
||||
out.print("@");
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case ANNOTATIONS:
|
||||
case ANNOTATION_ARRAY_INIT:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case ANNOTATION_MEMBER_VALUE_PAIR:
|
||||
print(ast.getFirstChild());
|
||||
out.print("=");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast.getFirstChild().getNextSibling(), false));
|
||||
print(ast.getFirstChild().getNextSibling());
|
||||
break;
|
||||
|
||||
default:
|
||||
debug.println("Unrecognized type:" + ast.getType() + " ("
|
||||
+ TokenUtil.nameOf(ast) + ")");
|
||||
break;
|
||||
}
|
||||
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
}
|
||||
1051
java/src/processing/mode/java/preproc/PdePreprocessor.java
Normal file
1051
java/src/processing/mode/java/preproc/PdePreprocessor.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import processing.app.SketchException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*
|
||||
*/
|
||||
public class PreprocessorResult {
|
||||
public final int headerOffset;
|
||||
public final String className;
|
||||
public final List<String> extraImports;
|
||||
public final PdePreprocessor.Mode programType;
|
||||
|
||||
public PreprocessorResult(PdePreprocessor.Mode programType,
|
||||
int headerOffset, String className,
|
||||
final List<String> extraImports) throws SketchException {
|
||||
if (className == null) {
|
||||
throw new SketchException("Could not find main class");
|
||||
}
|
||||
this.headerOffset = headerOffset;
|
||||
this.className = className;
|
||||
this.extraImports = Collections.unmodifiableList(new ArrayList<String>(extraImports));
|
||||
this.programType = programType;
|
||||
}
|
||||
|
||||
}
|
||||
30
java/src/processing/mode/java/preproc/TokenUtil.java
Normal file
30
java/src/processing/mode/java/preproc/TokenUtil.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import antlr.collections.AST;
|
||||
import processing.mode.java.preproc.PdeTokenTypes;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*
|
||||
*/
|
||||
public class TokenUtil {
|
||||
private static final String[] tokenNames= new String[200];
|
||||
static {
|
||||
for (int i = 0; i < tokenNames.length; i++) {
|
||||
tokenNames[i] = "ERROR:" + i;
|
||||
}
|
||||
for (final Field f : PdeTokenTypes.class.getDeclaredFields()) {
|
||||
try {
|
||||
tokenNames[f.getInt(null)] = f.getName();
|
||||
} catch (Exception unexpected) {
|
||||
throw new RuntimeException(unexpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String nameOf(final AST node) {
|
||||
return tokenNames[node.getType()];
|
||||
}
|
||||
}
|
||||
1832
java/src/processing/mode/java/preproc/java15.g
Normal file
1832
java/src/processing/mode/java/preproc/java15.g
Normal file
File diff suppressed because it is too large
Load Diff
394
java/src/processing/mode/java/preproc/pde.g
Normal file
394
java/src/processing/mode/java/preproc/pde.g
Normal file
@@ -0,0 +1,394 @@
|
||||
/* -*- mode: antlr; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
header {
|
||||
package processing.mode.java.preproc;
|
||||
}
|
||||
|
||||
class PdeRecognizer extends JavaRecognizer;
|
||||
|
||||
options {
|
||||
importVocab = Java;
|
||||
exportVocab = PdePartial;
|
||||
|
||||
//codeGenMakeSwitchThreshold=10; // this is set high for debugging
|
||||
//codeGenBitsetTestThreshold=10; // this is set high for debugging
|
||||
|
||||
// developers may to want to set this to true for better
|
||||
// debugging messages, however, doing so disables highlighting errors
|
||||
// in the editor.
|
||||
defaultErrorHandler = false; //true;
|
||||
}
|
||||
|
||||
tokens {
|
||||
CONSTRUCTOR_CAST; EMPTY_FIELD;
|
||||
}
|
||||
|
||||
{
|
||||
// this clause copied from java15.g! ANTLR does not copy this
|
||||
// section from the super grammar.
|
||||
/**
|
||||
* Counts the number of LT seen in the typeArguments production.
|
||||
* It is used in semantic predicates to ensure we have seen
|
||||
* enough closing '>' characters; which actually may have been
|
||||
* either GT, SR or BSR tokens.
|
||||
*/
|
||||
private int ltCounter = 0;
|
||||
|
||||
private PdePreprocessor pp;
|
||||
public PdeRecognizer(final PdePreprocessor pp, final TokenStream ts) {
|
||||
this(ts);
|
||||
this.pp = pp;
|
||||
}
|
||||
|
||||
private void mixed() throws RecognitionException, TokenStreamException {
|
||||
throw new RecognitionException("It looks like you're mixing \"active\" and \"static\" modes.",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}
|
||||
}
|
||||
|
||||
pdeProgram
|
||||
:
|
||||
// Some programs can be equally well interpreted as STATIC or ACTIVE;
|
||||
// this forces the parser to prefer the STATIC interpretation.
|
||||
(staticProgram) => staticProgram
|
||||
{ pp.setMode(PdePreprocessor.Mode.STATIC); }
|
||||
|
||||
| (activeProgram) => activeProgram
|
||||
{ pp.setMode(PdePreprocessor.Mode.ACTIVE); }
|
||||
|
||||
| staticProgram
|
||||
{ pp.setMode(PdePreprocessor.Mode.STATIC); }
|
||||
;
|
||||
|
||||
// advanced mode is really just a normal java file
|
||||
javaProgram
|
||||
: compilationUnit
|
||||
;
|
||||
|
||||
activeProgram
|
||||
: (
|
||||
(IDENT LPAREN) => IDENT LPAREN { mixed(); }
|
||||
| possiblyEmptyField
|
||||
)+ EOF!
|
||||
;
|
||||
|
||||
staticProgram
|
||||
: (
|
||||
statement
|
||||
)* EOF!
|
||||
;
|
||||
|
||||
// copy of the java.g rule with WEBCOLOR_LITERAL added
|
||||
constant
|
||||
: NUM_INT
|
||||
| CHAR_LITERAL
|
||||
| STRING_LITERAL
|
||||
| NUM_FLOAT
|
||||
| NUM_LONG
|
||||
| NUM_DOUBLE
|
||||
| webcolor_literal
|
||||
;
|
||||
|
||||
// fix bug http://dev.processing.org/bugs/show_bug.cgi?id=1519
|
||||
// by altering a syntactic predicate whose sole purpose is to
|
||||
// emit a useless error with no line numbers.
|
||||
// These are from Java15.g, with a few lines edited to make nice errors.
|
||||
|
||||
// Type arguments to a class or interface type
|
||||
typeArguments
|
||||
{int currentLtLevel = 0;}
|
||||
:
|
||||
{currentLtLevel = ltCounter;}
|
||||
LT! {ltCounter++;}
|
||||
typeArgument
|
||||
(options{greedy=true;}: // match as many as possible
|
||||
{if (! (inputState.guessing !=0 || ltCounter == currentLtLevel + 1)) {
|
||||
throw new RecognitionException("Maybe too many > characters?",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}}
|
||||
COMMA! typeArgument
|
||||
)*
|
||||
|
||||
( // turn warning off since Antlr generates the right code,
|
||||
// plus we have our semantic predicate below
|
||||
options{generateAmbigWarnings=false;}:
|
||||
typeArgumentsOrParametersEnd
|
||||
)?
|
||||
|
||||
// make sure we have gobbled up enough '>' characters
|
||||
// if we are at the "top level" of nested typeArgument productions
|
||||
{if (! ((currentLtLevel != 0) || ltCounter == currentLtLevel)) {
|
||||
throw new RecognitionException("Maybe too many > characters?",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}}
|
||||
|
||||
{#typeArguments = #(#[TYPE_ARGUMENTS, "TYPE_ARGUMENTS"], #typeArguments);}
|
||||
;
|
||||
|
||||
typeParameters
|
||||
{int currentLtLevel = 0;}
|
||||
:
|
||||
{currentLtLevel = ltCounter;}
|
||||
LT! {ltCounter++;}
|
||||
typeParameter (COMMA! typeParameter)*
|
||||
(typeArgumentsOrParametersEnd)?
|
||||
|
||||
// make sure we have gobbled up enough '>' characters
|
||||
// if we are at the "top level" of nested typeArgument productions
|
||||
{if (! ((currentLtLevel != 0) || ltCounter == currentLtLevel)) {
|
||||
throw new RecognitionException("Maybe too many > characters?",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}}
|
||||
|
||||
{#typeParameters = #(#[TYPE_PARAMETERS, "TYPE_PARAMETERS"], #typeParameters);}
|
||||
;
|
||||
|
||||
|
||||
// this gobbles up *some* amount of '>' characters, and counts how many
|
||||
// it gobbled.
|
||||
protected typeArgumentsOrParametersEnd
|
||||
: GT! {ltCounter-=1;}
|
||||
| SR! {ltCounter-=2;}
|
||||
| BSR! {ltCounter-=3;}
|
||||
;
|
||||
|
||||
// of the form #cc008f in PDE
|
||||
webcolor_literal
|
||||
: w:WEBCOLOR_LITERAL
|
||||
{ if (! (processing.app.Preferences.getBoolean("preproc.web_colors")
|
||||
&&
|
||||
w.getText().length() == 6)) {
|
||||
throw new RecognitionException("Web colors must be exactly 6 hex digits. This looks like " + w.getText().length() + ".",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}} // must be exactly 6 hex digits
|
||||
;
|
||||
|
||||
// copy of the java.g builtInType rule
|
||||
builtInConsCastType
|
||||
: "void"
|
||||
| "boolean"
|
||||
| "byte"
|
||||
| "char"
|
||||
| "short"
|
||||
| "int"
|
||||
| "float"
|
||||
| "long"
|
||||
| "double"
|
||||
;
|
||||
|
||||
// our types include the java types and "color". this is separated into two
|
||||
// rules so that constructor casts can just use the original typelist, since
|
||||
// we don't want to support the color type as a constructor cast.
|
||||
//
|
||||
builtInType
|
||||
: builtInConsCastType
|
||||
| "color" // aliased to an int in PDE
|
||||
{ processing.app.Preferences.getBoolean("preproc.color_datatype") }?
|
||||
;
|
||||
|
||||
// constructor style casts.
|
||||
constructorCast!
|
||||
: t:consCastTypeSpec[true]
|
||||
LPAREN!
|
||||
e:expression
|
||||
RPAREN!
|
||||
// if this is a string literal, make sure the type we're trying to cast
|
||||
// to is one of the supported ones
|
||||
//
|
||||
{ (#e == null) ||
|
||||
( (#e.getType() != STRING_LITERAL) ||
|
||||
( #t.getType() == LITERAL_boolean ||
|
||||
#t.getType() == LITERAL_double ||
|
||||
#t.getType() == LITERAL_float ||
|
||||
#t.getType() == LITERAL_int ||
|
||||
#t.getType() == LITERAL_long ||
|
||||
#t.getType() == LITERAL_short )) }?
|
||||
// create the node
|
||||
//
|
||||
{#constructorCast = #(#[CONSTRUCTOR_CAST,"CONSTRUCTOR_CAST"], t, e);}
|
||||
;
|
||||
|
||||
// A list of types that be used as the destination type in a constructor-style
|
||||
// cast. Ideally, this would include all class types, not just "String".
|
||||
// Unfortunately, it's not possible to tell whether Foo(5) is supposed to be
|
||||
// a method call or a constructor cast without have a table of all valid
|
||||
// types or methods, which requires semantic analysis (eg processing of import
|
||||
// statements). So we accept the set of built-in types plus "String".
|
||||
//
|
||||
consCastTypeSpec[boolean addImagNode]
|
||||
// : stringTypeSpec[addImagNode]
|
||||
// | builtInConsCastTypeSpec[addImagNode]
|
||||
: builtInConsCastTypeSpec[addImagNode]
|
||||
// trying to remove String() cast [fry]
|
||||
;
|
||||
|
||||
//stringTypeSpec[boolean addImagNode]
|
||||
// : id:IDENT { #id.getText().equals("String") }?
|
||||
// {
|
||||
// if ( addImagNode ) {
|
||||
// #stringTypeSpec = #(#[TYPE,"TYPE"],
|
||||
// #stringTypeSpec);
|
||||
// }
|
||||
// }
|
||||
// ;
|
||||
|
||||
builtInConsCastTypeSpec[boolean addImagNode]
|
||||
: builtInConsCastType
|
||||
{
|
||||
if ( addImagNode ) {
|
||||
#builtInConsCastTypeSpec = #(#[TYPE,"TYPE"],
|
||||
#builtInConsCastTypeSpec);
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
// Since "color" tokens are lexed as LITERAL_color now, we need to have a rule
|
||||
// that can generate a method call from an expression that starts with this
|
||||
// token
|
||||
//
|
||||
colorMethodCall
|
||||
: c:"color" {#c.setType(IDENT);} // this would default to LITERAL_color
|
||||
lp:LPAREN^ {#lp.setType(METHOD_CALL);}
|
||||
argList
|
||||
RPAREN!
|
||||
;
|
||||
|
||||
// copy of the java.g rule with added constructorCast and colorMethodCall
|
||||
// alternatives
|
||||
primaryExpression
|
||||
: (consCastTypeSpec[false] LPAREN) => constructorCast
|
||||
{ processing.app.Preferences.getBoolean("preproc.enhanced_casting") }?
|
||||
| identPrimary ( options {greedy=true;} : DOT^ "class" )?
|
||||
| constant
|
||||
| "true"
|
||||
| "false"
|
||||
| "null"
|
||||
| newExpression
|
||||
| "this"
|
||||
| "super"
|
||||
| LPAREN! assignmentExpression RPAREN!
|
||||
| colorMethodCall
|
||||
// look for int.class and int[].class
|
||||
| builtInType
|
||||
( lbt:LBRACK^ {#lbt.setType(ARRAY_DECLARATOR);} RBRACK! )*
|
||||
DOT^ "class"
|
||||
;
|
||||
|
||||
// the below variable rule hacks are needed so that it's possible for the
|
||||
// emitter to correctly output variable declarations of the form "float a, b"
|
||||
// from the AST. This means that our AST has a somewhat different form in
|
||||
// these rules than the java one does, and this new form may have its own
|
||||
// semantic issues. But it seems to fix the comma declaration issues.
|
||||
//
|
||||
variableDefinitions![AST mods, AST t]
|
||||
: vd:variableDeclarator[getASTFactory().dupTree(mods),
|
||||
getASTFactory().dupTree(t)]
|
||||
{#variableDefinitions = #(#[VARIABLE_DEF,"VARIABLE_DEF"], mods,
|
||||
t, vd);}
|
||||
;
|
||||
variableDeclarator[AST mods, AST t]
|
||||
: ( id:IDENT (lb:LBRACK^ {#lb.setType(ARRAY_DECLARATOR);} RBRACK!)*
|
||||
v:varInitializer (COMMA!)? )+
|
||||
;
|
||||
|
||||
// java.g builds syntax trees with an inconsistent structure. override one of
|
||||
// the rules there to fix this.
|
||||
//
|
||||
explicitConstructorInvocation!
|
||||
: (typeArguments)?
|
||||
t:"this" LPAREN a1:argList RPAREN SEMI
|
||||
{#explicitConstructorInvocation = #(#[CTOR_CALL, "CTOR_CALL"],
|
||||
#t, #a1);}
|
||||
| s:"super" LPAREN a2:argList RPAREN SEMI
|
||||
{#explicitConstructorInvocation = #(#[SUPER_CTOR_CALL,
|
||||
"SUPER_CTOR_CALL"],
|
||||
#s, #a2);}
|
||||
;
|
||||
|
||||
// quick-n-dirty hack to the get the advanced class name. we should
|
||||
// really be getting it from the AST and not forking this rule from
|
||||
// the java.g copy at all. Since this is a recursive descent parser, we get
|
||||
// the last class name in the file so that we don't end up with the classname
|
||||
// of an inner class. If there is more than one "outer" class in a file,
|
||||
// this heuristic will fail.
|
||||
//
|
||||
classDefinition![AST modifiers]
|
||||
: "class" i:IDENT
|
||||
// it _might_ have type paramaters
|
||||
(tp:typeParameters)?
|
||||
// it _might_ have a superclass...
|
||||
sc:superClassClause
|
||||
// it might implement some interfaces...
|
||||
ic:implementsClause
|
||||
// now parse the body of the class
|
||||
cb:classBlock
|
||||
{#classDefinition = #(#[CLASS_DEF,"CLASS_DEF"],
|
||||
modifiers,i,tp,sc,ic,cb);
|
||||
pp.setAdvClassName(i.getText());}
|
||||
;
|
||||
|
||||
possiblyEmptyField
|
||||
: classField
|
||||
| s:SEMI {#s.setType(EMPTY_FIELD);}
|
||||
;
|
||||
|
||||
class PdeLexer extends JavaLexer;
|
||||
|
||||
options {
|
||||
importVocab=PdePartial;
|
||||
exportVocab=Pde;
|
||||
}
|
||||
|
||||
// We need to preserve whitespace and commentary instead of ignoring
|
||||
// like the supergrammar does. Otherwise Jikes won't be able to give
|
||||
// us error messages that point to the equivalent PDE code.
|
||||
|
||||
// WS, SL_COMMENT, ML_COMMENT are copies of the original productions,
|
||||
// but with the SKIP assigment removed.
|
||||
|
||||
WS : ( ' '
|
||||
| '\t'
|
||||
| '\f'
|
||||
// handle newlines
|
||||
| ( options {generateAmbigWarnings=false;}
|
||||
: "\r\n" // Evil DOS
|
||||
| '\r' // Macintosh
|
||||
| '\n' // Unix (the right way)
|
||||
)
|
||||
{ newline(); }
|
||||
)+
|
||||
;
|
||||
|
||||
// Single-line comments
|
||||
SL_COMMENT
|
||||
: "//"
|
||||
(~('\n'|'\r'))* ('\n'|'\r'('\n')?)
|
||||
{newline();}
|
||||
;
|
||||
|
||||
// multiple-line comments
|
||||
ML_COMMENT
|
||||
: "/*"
|
||||
( /* '\r' '\n' can be matched in one alternative or by matching
|
||||
'\r' in one iteration and '\n' in another. I am trying to
|
||||
handle any flavor of newline that comes in, but the language
|
||||
that allows both "\r\n" and "\r" and "\n" to all be valid
|
||||
newline is ambiguous. Consequently, the resulting grammar
|
||||
must be ambiguous. I'm shutting this warning off.
|
||||
*/
|
||||
options {
|
||||
generateAmbigWarnings=false;
|
||||
}
|
||||
:
|
||||
{ LA(2)!='/' }? '*'
|
||||
| '\r' '\n' {newline();}
|
||||
| '\r' {newline();}
|
||||
| '\n' {newline();}
|
||||
| ~('*'|'\n'|'\r')
|
||||
)*
|
||||
"*/"
|
||||
;
|
||||
|
||||
WEBCOLOR_LITERAL
|
||||
: '#'! (HEX_DIGIT)+
|
||||
;
|
||||
|
||||
42
java/src/processing/mode/java/runner/MessageConsumer.java
Normal file
42
java/src/processing/mode/java/runner/MessageConsumer.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-06 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
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.runner;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for dealing with parser/compiler output.
|
||||
* <P>
|
||||
* Different instances of MessageStream need to do different things with
|
||||
* messages. In particular, a stream instance used for parsing output from
|
||||
* the compiler compiler has to interpret its messages differently than one
|
||||
* parsing output from the runtime.
|
||||
* <P>
|
||||
* Classes which consume messages and do something with them
|
||||
* should implement this interface.
|
||||
*/
|
||||
public interface MessageConsumer {
|
||||
|
||||
public void message(String s);
|
||||
|
||||
}
|
||||
92
java/src/processing/mode/java/runner/MessageSiphon.java
Normal file
92
java/src/processing/mode/java/runner/MessageSiphon.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-06 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
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.runner;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/**
|
||||
* Slurps up messages from compiler.
|
||||
*/
|
||||
public class MessageSiphon implements Runnable {
|
||||
BufferedReader streamReader;
|
||||
Thread thread;
|
||||
MessageConsumer consumer;
|
||||
|
||||
|
||||
public MessageSiphon(InputStream stream, MessageConsumer consumer) {
|
||||
this.streamReader = new BufferedReader(new InputStreamReader(stream));
|
||||
this.consumer = consumer;
|
||||
|
||||
thread = new Thread(this);
|
||||
// don't set priority too low, otherwise exceptions won't
|
||||
// bubble up in time (i.e. compile errors have a weird delay)
|
||||
//thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.setPriority(Thread.MAX_PRIORITY-1);
|
||||
//thread.start();
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
// process data until we hit EOF; this will happily block
|
||||
// (effectively sleeping the thread) until new data comes in.
|
||||
// when the program is finally done, null will come through.
|
||||
//
|
||||
String currentLine;
|
||||
while ((currentLine = streamReader.readLine()) != null) {
|
||||
// \n is added again because readLine() strips it out
|
||||
//EditorConsole.systemOut.println("messaging in");
|
||||
consumer.message(currentLine + "\n");
|
||||
//EditorConsole.systemOut.println("messaging out");
|
||||
}
|
||||
//EditorConsole.systemOut.println("messaging thread done");
|
||||
thread = null;
|
||||
|
||||
} catch (NullPointerException npe) {
|
||||
// Fairly common exception during shutdown
|
||||
thread = null;
|
||||
|
||||
} catch (Exception e) {
|
||||
// On Linux and sometimes on Mac OS X, a "bad file descriptor"
|
||||
// message comes up when closing an applet that's run externally.
|
||||
// That message just gets supressed here..
|
||||
String mess = e.getMessage();
|
||||
if ((mess != null) &&
|
||||
(mess.indexOf("Bad file descriptor") != -1)) {
|
||||
//if (e.getMessage().indexOf("Bad file descriptor") == -1) {
|
||||
//System.err.println("MessageSiphon err " + e);
|
||||
//e.printStackTrace();
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Thread getThread() {
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
62
java/src/processing/mode/java/runner/MessageStream.java
Normal file
62
java/src/processing/mode/java/runner/MessageStream.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-08 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
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.runner;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/**
|
||||
* OutputStream to handle stdout/stderr messages.
|
||||
* <P>
|
||||
* This is used by Editor, System.err is set to
|
||||
* new PrintStream(new MessageStream()).
|
||||
* It's also used by Compiler.
|
||||
*/
|
||||
class MessageStream extends OutputStream {
|
||||
|
||||
MessageConsumer messageConsumer;
|
||||
|
||||
public MessageStream(MessageConsumer messageConsumer) {
|
||||
this.messageConsumer = messageConsumer;
|
||||
}
|
||||
|
||||
public void close() { }
|
||||
|
||||
public void flush() { }
|
||||
|
||||
public void write(byte b[]) {
|
||||
// this never seems to get called
|
||||
System.out.println("leech1: " + new String(b));
|
||||
}
|
||||
|
||||
public void write(byte b[], int offset, int length) {
|
||||
//System.out.println("leech2: " + new String(b));
|
||||
this.messageConsumer.message(new String(b, offset, length));
|
||||
}
|
||||
|
||||
public void write(int b) {
|
||||
// this never seems to get called
|
||||
System.out.println("leech3: '" + ((char)b) + "'");
|
||||
}
|
||||
}
|
||||
809
java/src/processing/mode/java/runner/Runner.java
Normal file
809
java/src/processing/mode/java/runner/Runner.java
Normal file
@@ -0,0 +1,809 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-13 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.runner;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.app.exec.StreamRedirectThread;
|
||||
import processing.core.*;
|
||||
import processing.mode.java.JavaBuild;
|
||||
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Point;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import com.sun.jdi.*;
|
||||
import com.sun.jdi.connect.*;
|
||||
import com.sun.jdi.event.*;
|
||||
import com.sun.jdi.request.*;
|
||||
|
||||
|
||||
/**
|
||||
* Runs a compiled sketch. As of release 0136, all sketches are run externally
|
||||
* to the environment so that a debugging interface can be used. This opens up
|
||||
* future options for a decent debugger, but in the meantime fixes several
|
||||
* problems with output and error streams, messages getting lost on Mac OS X,
|
||||
* the run/stop buttons not working, libraries not shutting down, exceptions
|
||||
* not coming through, exceptions being printed twice, having to force quit
|
||||
* if you make a bad while() loop, and so on.
|
||||
*/
|
||||
public class Runner implements MessageConsumer {
|
||||
// private boolean presenting;
|
||||
|
||||
// Object that listens for error messages or exceptions.
|
||||
protected RunnerListener listener;
|
||||
|
||||
// Running remote VM
|
||||
protected VirtualMachine vm;
|
||||
|
||||
// Thread transferring remote error stream to our error stream
|
||||
protected Thread errThread = null;
|
||||
|
||||
// Thread transferring remote output stream to our output stream
|
||||
protected Thread outThread = null;
|
||||
|
||||
protected SketchException exception;
|
||||
protected Editor editor;
|
||||
protected JavaBuild build;
|
||||
protected Process process;
|
||||
|
||||
protected PrintStream sketchErr;
|
||||
protected PrintStream sketchOut;
|
||||
|
||||
|
||||
public Runner(JavaBuild build, RunnerListener listener) throws SketchException {
|
||||
this.listener = listener;
|
||||
// this.sketch = sketch;
|
||||
this.build = build;
|
||||
|
||||
if (listener instanceof Editor) {
|
||||
this.editor = (Editor) listener;
|
||||
sketchErr = editor.getConsole().getErr();
|
||||
sketchOut = editor.getConsole().getOut();
|
||||
} else {
|
||||
sketchErr = System.err;
|
||||
sketchOut = System.out;
|
||||
}
|
||||
|
||||
// Make sure all the imported libraries will actually run with this setup.
|
||||
int bits = Base.getNativeBits();
|
||||
for (Library library : build.getImportedLibraries()) {
|
||||
if (!library.supportsArch(PApplet.platform, bits)) {
|
||||
sketchErr.println(library.getName() + " does not run in " + bits + "-bit mode.");
|
||||
int opposite = (bits == 32) ? 64 : 32;
|
||||
if (Base.isMacOS()) {
|
||||
//if (library.supportsArch(PConstants.MACOSX, opposite)) { // should always be true
|
||||
throw new SketchException("To use " + library.getName() + ", " +
|
||||
"switch to " + opposite + "-bit mode in Preferences.");
|
||||
//}
|
||||
} else {
|
||||
throw new SketchException(library.getName() + " is only compatible " +
|
||||
"with the " + opposite + "-bit download of Processing.");
|
||||
//throw new SketchException(library.getName() + " does not run in " + bits + "-bit mode.");
|
||||
// "To use this library, switch to 32-bit mode in Preferences." (OS X)
|
||||
// "To use this library, you must use the 32-bit version of Processing."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void launch(boolean presenting) {
|
||||
if (launchVirtualMachine(presenting)) {
|
||||
generateTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean launchVirtualMachine(boolean presenting) {
|
||||
String[] vmParams = getMachineParams();
|
||||
String[] sketchParams = getSketchParams(presenting);
|
||||
int port = 8000 + (int) (Math.random() * 1000);
|
||||
String portStr = String.valueOf(port);
|
||||
|
||||
// Older (Java 1.5 and earlier) version, go figure
|
||||
// String jdwpArg = "-Xrunjdwp:transport=dt_socket,address=" + portStr + ",server=y,suspend=y";
|
||||
// String debugArg = "-Xdebug";
|
||||
// Newer (Java 1.5+) version that uses JVMTI
|
||||
String jdwpArg = "-agentlib:jdwp=transport=dt_socket,address=" + portStr + ",server=y,suspend=y";
|
||||
|
||||
// Everyone works the same under Java 7 (also on OS X)
|
||||
String[] commandArgs = new String[] { Base.getJavaPath(), jdwpArg };
|
||||
|
||||
commandArgs = PApplet.concat(commandArgs, vmParams);
|
||||
commandArgs = PApplet.concat(commandArgs, sketchParams);
|
||||
// PApplet.println(commandArgs);
|
||||
// commandArg.setValue(commandArgs);
|
||||
launchJava(commandArgs);
|
||||
|
||||
AttachingConnector connector = (AttachingConnector)
|
||||
findConnector("com.sun.jdi.SocketAttach");
|
||||
//PApplet.println(connector); // gets the defaults
|
||||
|
||||
Map arguments = connector.defaultArguments();
|
||||
|
||||
// Connector.Argument addressArg =
|
||||
// (Connector.Argument)arguments.get("address");
|
||||
// addressArg.setValue(addr);
|
||||
Connector.Argument portArg =
|
||||
(Connector.Argument)arguments.get("port");
|
||||
portArg.setValue(portStr);
|
||||
|
||||
// Connector.Argument timeoutArg =
|
||||
// (Connector.Argument)arguments.get("timeout");
|
||||
// timeoutArg.setValue("10000");
|
||||
|
||||
//PApplet.println(connector); // prints the current
|
||||
//com.sun.tools.jdi.AbstractLauncher al;
|
||||
//com.sun.tools.jdi.RawCommandLineLauncher rcll;
|
||||
|
||||
//System.out.println(PApplet.javaVersion);
|
||||
// http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#sunlaunch
|
||||
|
||||
try {
|
||||
// boolean available = false;
|
||||
// while (!available) {
|
||||
while (true) {
|
||||
try {
|
||||
vm = connector.attach(arguments);
|
||||
// vm = connector.attach(arguments);
|
||||
if (vm != null) {
|
||||
// generateTrace();
|
||||
// available = true;
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// System.out.println("waiting");
|
||||
// e.printStackTrace();
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e1) {
|
||||
e1.printStackTrace(sketchErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
// } catch (IOException exc) {
|
||||
// throw new Error("Unable to launch target VM: " + exc);
|
||||
} catch (IllegalConnectorArgumentsException exc) {
|
||||
throw new Error("Internal error: " + exc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String[] getMachineParams() {
|
||||
ArrayList<String> params = new ArrayList<String>();
|
||||
|
||||
//params.add("-Xint"); // interpreted mode
|
||||
//params.add("-Xprof"); // profiler
|
||||
//params.add("-Xaprof"); // allocation profiler
|
||||
//params.add("-Xrunhprof:cpu=samples"); // old-style profiler
|
||||
|
||||
// TODO change this to use run.args = true, run.args.0, run.args.1, etc.
|
||||
// so that spaces can be included in the arg names
|
||||
String options = Preferences.get("run.options");
|
||||
if (options.length() > 0) {
|
||||
String pieces[] = PApplet.split(options, ' ');
|
||||
for (int i = 0; i < pieces.length; i++) {
|
||||
String p = pieces[i].trim();
|
||||
if (p.length() > 0) {
|
||||
params.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// params.add("-Djava.ext.dirs=nuffing");
|
||||
|
||||
if (Preferences.getBoolean("run.options.memory")) {
|
||||
params.add("-Xms" + Preferences.get("run.options.memory.initial") + "m");
|
||||
params.add("-Xmx" + Preferences.get("run.options.memory.maximum") + "m");
|
||||
}
|
||||
|
||||
if (Base.isMacOS()) {
|
||||
params.add("-Xdock:name=" + build.getSketchClassName());
|
||||
// params.add("-Dcom.apple.mrj.application.apple.menu.about.name=" +
|
||||
// sketch.getMainClassName());
|
||||
}
|
||||
// sketch.libraryPath might be ""
|
||||
// librariesClassPath will always have sep char prepended
|
||||
params.add("-Djava.library.path=" +
|
||||
build.getJavaLibraryPath() +
|
||||
File.pathSeparator +
|
||||
System.getProperty("java.library.path"));
|
||||
|
||||
params.add("-cp");
|
||||
params.add(build.getClassPath());
|
||||
// params.add(sketch.getClassPath() +
|
||||
// File.pathSeparator +
|
||||
// Base.librariesClassPath);
|
||||
|
||||
// enable assertions
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=1188
|
||||
params.add("-ea");
|
||||
//PApplet.println(PApplet.split(sketch.classPath, ':'));
|
||||
|
||||
String outgoing[] = new String[params.size()];
|
||||
params.toArray(outgoing);
|
||||
|
||||
// PApplet.println(outgoing);
|
||||
// PApplet.println(PApplet.split(outgoing[0], ":"));
|
||||
// PApplet.println();
|
||||
// PApplet.println("class path");
|
||||
// PApplet.println(PApplet.split(outgoing[2], ":"));
|
||||
|
||||
return outgoing;
|
||||
//return (String[]) params.toArray();
|
||||
|
||||
// System.out.println("sketch class path");
|
||||
// PApplet.println(PApplet.split(sketch.classPath, ';'));
|
||||
// System.out.println();
|
||||
// System.out.println("libraries class path");
|
||||
// PApplet.println(PApplet.split(Base.librariesClassPath, ';'));
|
||||
// System.out.println();
|
||||
}
|
||||
|
||||
|
||||
protected String[] getSketchParams(boolean presenting) {
|
||||
ArrayList<String> params = new ArrayList<String>();
|
||||
|
||||
// It's dangerous to add your own main() to your code,
|
||||
// but if you've done it, we'll respect your right to hang yourself.
|
||||
// http://processing.org/bugs/bugzilla/1446.html
|
||||
if (build.getFoundMain()) {
|
||||
params.add(build.getSketchClassName());
|
||||
|
||||
} else {
|
||||
params.add("processing.core.PApplet");
|
||||
|
||||
// get the stored device index (starts at 0)
|
||||
int runDisplay = Preferences.getInteger("run.display");
|
||||
|
||||
// If there was a saved location (this guy has been run more than once)
|
||||
// then the location will be set to the last position of the sketch window.
|
||||
// This will be passed to the PApplet runner using something like
|
||||
// --location=30,20
|
||||
// Otherwise, the editor location will be passed, and the applet will
|
||||
// figure out where to place itself based on the editor location.
|
||||
// --editor-location=150,20
|
||||
if (editor != null) { // if running processing-cmd, don't do placement
|
||||
GraphicsDevice editorDevice =
|
||||
editor.getGraphicsConfiguration().getDevice();
|
||||
GraphicsEnvironment ge =
|
||||
GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice[] devices = ge.getScreenDevices();
|
||||
|
||||
// Make sure the display set in Preferences actually exists
|
||||
GraphicsDevice runDevice = editorDevice;
|
||||
if (runDisplay >= 0 && runDisplay < devices.length) {
|
||||
runDevice = devices[runDisplay];
|
||||
} else {
|
||||
runDevice = editorDevice;
|
||||
for (int i = 0; i < devices.length; i++) {
|
||||
if (devices[i] == runDevice) {
|
||||
runDisplay = i;
|
||||
break;
|
||||
// Don't set the pref, might be a temporary thing. Users can
|
||||
// open/close Preferences to reset the device themselves.
|
||||
// Preferences.setInteger("run.display", runDisplay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point windowLocation = editor.getSketchLocation();
|
||||
// if (windowLocation != null) {
|
||||
// // could check to make sure the sketch location is on the device
|
||||
// // that's specified in Preferences, but that's going to be annoying
|
||||
// // if you move a sketch to another window, then it keeps jumping
|
||||
// // back to the specified window.
|
||||
//// Rectangle screenRect =
|
||||
//// runDevice.getDefaultConfiguration().getBounds();
|
||||
// }
|
||||
if (windowLocation == null) {
|
||||
if (editorDevice == runDevice) {
|
||||
// If sketches are to be shown on the same display as the editor,
|
||||
// provide the editor location so the sketch's main() can place it.
|
||||
Point editorLocation = editor.getLocation();
|
||||
params.add(PApplet.ARGS_EDITOR_LOCATION + "=" +
|
||||
editorLocation.x + "," + editorLocation.y);
|
||||
} else {
|
||||
// The sketch's main() will set a location centered on the new
|
||||
// display. It has to happen in main() because the width/height
|
||||
// of the sketch are not known here.
|
||||
// Set a location centered on the other display
|
||||
// Rectangle screenRect =
|
||||
// runDevice.getDefaultConfiguration().getBounds();
|
||||
// int runX =
|
||||
// params.add(PApplet.ARGS_LOCATION + "=" + runX + "," + runY);
|
||||
}
|
||||
} else {
|
||||
params.add(PApplet.ARGS_LOCATION + "=" +
|
||||
windowLocation.x + "," + windowLocation.y);
|
||||
}
|
||||
params.add(PApplet.ARGS_EXTERNAL);
|
||||
}
|
||||
|
||||
params.add(PApplet.ARGS_DISPLAY + "=" + runDisplay);
|
||||
|
||||
|
||||
if (presenting) {
|
||||
params.add(PApplet.ARGS_FULL_SCREEN);
|
||||
// if (Preferences.getBoolean("run.present.exclusive")) {
|
||||
// params.add(PApplet.ARGS_EXCLUSIVE);
|
||||
// }
|
||||
params.add(PApplet.ARGS_STOP_COLOR + "=" +
|
||||
Preferences.get("run.present.stop.color"));
|
||||
params.add(PApplet.ARGS_BGCOLOR + "=" +
|
||||
Preferences.get("run.present.bgcolor"));
|
||||
}
|
||||
|
||||
params.add(build.getSketchClassName());
|
||||
params.add(PApplet.ARGS_SKETCH_FOLDER + "=" + build.getSketchPath());
|
||||
// Adding sketch path in the end coz it's likely to
|
||||
// contain spaces and things go wrong on UNIX systems.
|
||||
}
|
||||
|
||||
// String outgoing[] = new String[params.size()];
|
||||
// params.toArray(outgoing);
|
||||
// return outgoing;
|
||||
return params.toArray(new String[0]);
|
||||
}
|
||||
|
||||
|
||||
protected void launchJava(final String[] args) {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
// PApplet.println("java starting");
|
||||
process = PApplet.exec(args);
|
||||
try {
|
||||
// PApplet.println("java waiting");
|
||||
int result = process.waitFor();
|
||||
// PApplet.println("java done waiting");
|
||||
if (result != 0) {
|
||||
String[] errorStrings = PApplet.loadStrings(process.getErrorStream());
|
||||
String[] inputStrings = PApplet.loadStrings(process.getInputStream());
|
||||
|
||||
// PApplet.println("launchJava stderr:");
|
||||
// PApplet.println(errorStrings);
|
||||
// PApplet.println("launchJava stdout:");
|
||||
PApplet.printArray(inputStrings);
|
||||
|
||||
if (errorStrings != null && errorStrings.length > 1) {
|
||||
if (errorStrings[0].indexOf("Invalid maximum heap size") != -1) {
|
||||
Base.showWarning("Way Too High",
|
||||
"Please lower the value for \u201Cmaximum available memory\u201D in the\n" +
|
||||
"Preferences window. For more information, read Help \u2192 Troubleshooting.", null);
|
||||
} else {
|
||||
for (String err : errorStrings) {
|
||||
sketchErr.println(err);
|
||||
}
|
||||
sketchErr.println("Using startup command: " + PApplet.join(args, " "));
|
||||
}
|
||||
} else {
|
||||
//exc.printStackTrace();
|
||||
sketchErr.println("Could not run the sketch (Target VM failed to initialize).");
|
||||
if (Preferences.getBoolean("run.options.memory")) {
|
||||
// Only mention this if they've even altered the memory setup
|
||||
sketchErr.println("Make sure that you haven't set the maximum available memory too high.");
|
||||
}
|
||||
sketchErr.println("For more information, read revisions.txt and Help \u2192 Troubleshooting.");
|
||||
}
|
||||
// changing this to separate editor and listener [091124]
|
||||
//if (editor != null) {
|
||||
listener.statusError("Could not run the sketch.");
|
||||
//}
|
||||
// return null;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate the trace.
|
||||
* Enable events, start thread to display events,
|
||||
* start threads to forward remote error and output streams,
|
||||
* resume the remote VM, wait for the final event, and shutdown.
|
||||
*/
|
||||
protected void generateTrace() {
|
||||
//vm.setDebugTraceMode(debugTraceMode);
|
||||
// vm.setDebugTraceMode(VirtualMachine.TRACE_ALL);
|
||||
// vm.setDebugTraceMode(VirtualMachine.TRACE_NONE); // formerly, seems to have no effect
|
||||
|
||||
// Calling this seems to set something internally to make the
|
||||
// Eclipse JDI wake up. Without it, an ObjectCollectedException
|
||||
// is thrown on excReq.enable(). No idea why this works,
|
||||
// but at least exception handling has returned. (Suspect that it may
|
||||
// block until all or at least some threads are available, meaning
|
||||
// that the app has launched and we have legit objects to talk to).
|
||||
vm.allThreads();
|
||||
// The bug may not have been noticed because the test suite waits for
|
||||
// a thread to be available, and queries it by calling allThreads().
|
||||
// See org.eclipse.debug.jdi.tests.AbstractJDITest for the example.
|
||||
|
||||
EventRequestManager mgr = vm.eventRequestManager();
|
||||
// get only the uncaught exceptions
|
||||
ExceptionRequest excReq = mgr.createExceptionRequest(null, false, true);
|
||||
// System.out.println(excReq);
|
||||
// this version reports all exceptions, caught or uncaught
|
||||
// ExceptionRequest excReq = mgr.createExceptionRequest(null, true, true);
|
||||
// suspend so we can step
|
||||
excReq.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
||||
// excReq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
|
||||
// excReq.setSuspendPolicy(EventRequest.SUSPEND_NONE); // another option?
|
||||
excReq.enable();
|
||||
|
||||
Thread eventThread = new Thread() {
|
||||
public void run() {
|
||||
try {
|
||||
boolean connected = true;
|
||||
while (connected) {
|
||||
EventQueue eventQueue = vm.eventQueue();
|
||||
// remove() blocks until event(s) available
|
||||
EventSet eventSet = eventQueue.remove();
|
||||
// listener.vmEvent(eventSet);
|
||||
|
||||
for (Event event : eventSet) {
|
||||
// System.out.println("EventThread.handleEvent -> " + event);
|
||||
if (event instanceof VMStartEvent) {
|
||||
vm.resume();
|
||||
} else if (event instanceof ExceptionEvent) {
|
||||
// for (ThreadReference thread : vm.allThreads()) {
|
||||
// System.out.println("thread : " + thread);
|
||||
//// thread.suspend();
|
||||
// }
|
||||
exceptionEvent((ExceptionEvent) event);
|
||||
} else if (event instanceof VMDisconnectEvent) {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// } catch (VMDisconnectedException e) {
|
||||
// Logger.getLogger(VMEventReader.class.getName()).log(Level.INFO, "VMEventReader quit on VM disconnect");
|
||||
} catch (Exception e) {
|
||||
System.err.println("crashed in event thread due to " + e.getMessage());
|
||||
// Logger.getLogger(VMEventReader.class.getName()).log(Level.SEVERE, "VMEventReader quit", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
eventThread.start();
|
||||
|
||||
|
||||
errThread =
|
||||
new MessageSiphon(process.getErrorStream(), this).getThread();
|
||||
|
||||
outThread = new StreamRedirectThread("JVM stdout Reader",
|
||||
process.getInputStream(),
|
||||
sketchOut);
|
||||
errThread.start();
|
||||
outThread.start();
|
||||
|
||||
// Shutdown begins when event thread terminates
|
||||
try {
|
||||
if (eventThread != null) eventThread.join(); // is this the problem?
|
||||
|
||||
// System.out.println("in here");
|
||||
// Bug #852 tracked to this next line in the code.
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=852
|
||||
errThread.join(); // Make sure output is forwarded
|
||||
// System.out.println("and then");
|
||||
outThread.join(); // before we exit
|
||||
// System.out.println("finished join for errThread and outThread");
|
||||
|
||||
// At this point, disable the run button.
|
||||
// This happens when the sketch is exited by hitting ESC,
|
||||
// or the user manually closes the sketch window.
|
||||
// TODO this should be handled better, should it not?
|
||||
if (editor != null) {
|
||||
editor.deactivateRun();
|
||||
}
|
||||
} catch (InterruptedException exc) {
|
||||
// we don't interrupt
|
||||
}
|
||||
//System.out.println("and leaving");
|
||||
}
|
||||
|
||||
|
||||
protected Connector findConnector(String connectorName) {
|
||||
// List connectors =
|
||||
// com.sun.jdi.Bootstrap.virtualMachineManager().allConnectors();
|
||||
List connectors =
|
||||
org.eclipse.jdi.Bootstrap.virtualMachineManager().allConnectors();
|
||||
|
||||
// // debug: code to list available connectors
|
||||
// Iterator iter2 = connectors.iterator();
|
||||
// while (iter2.hasNext()) {
|
||||
// Connector connector = (Connector)iter2.next();
|
||||
// System.out.println("connector name is " + connector.name());
|
||||
// }
|
||||
|
||||
for (Object c : connectors) {
|
||||
Connector connector = (Connector) c;
|
||||
// System.out.println(connector.name());
|
||||
// }
|
||||
// Iterator iter = connectors.iterator();
|
||||
// while (iter.hasNext()) {
|
||||
// Connector connector = (Connector)iter.next();
|
||||
if (connector.name().equals(connectorName)) {
|
||||
return connector;
|
||||
}
|
||||
}
|
||||
Base.showError("Compiler Error",
|
||||
"findConnector() failed to find " +
|
||||
connectorName + " inside Runner", null);
|
||||
return null; // Not reachable
|
||||
}
|
||||
|
||||
|
||||
public void exceptionEvent(ExceptionEvent event) {
|
||||
ObjectReference or = event.exception();
|
||||
ReferenceType rt = or.referenceType();
|
||||
String exceptionName = rt.name();
|
||||
//Field messageField = Throwable.class.getField("detailMessage");
|
||||
Field messageField = rt.fieldByName("detailMessage");
|
||||
// System.out.println("field " + messageField);
|
||||
Value messageValue = or.getValue(messageField);
|
||||
// System.out.println("mess val " + messageValue);
|
||||
|
||||
//"java.lang.ArrayIndexOutOfBoundsException"
|
||||
int last = exceptionName.lastIndexOf('.');
|
||||
String message = exceptionName.substring(last + 1);
|
||||
if (messageValue != null) {
|
||||
String messageStr = messageValue.toString();
|
||||
if (messageStr.startsWith("\"")) {
|
||||
messageStr = messageStr.substring(1, messageStr.length() - 1);
|
||||
}
|
||||
message += ": " + messageStr;
|
||||
}
|
||||
// System.out.println("mess type " + messageValue.type());
|
||||
//StringReference messageReference = (StringReference) messageValue.type();
|
||||
|
||||
// First just report the exception and its placement
|
||||
reportException(message, or, event.thread());
|
||||
// Then try to pretty it up with a better message
|
||||
handleCommonErrors(exceptionName, message, listener, sketchErr);
|
||||
|
||||
if (editor != null) {
|
||||
editor.deactivateRun();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provide more useful explanations of common error messages, perhaps with
|
||||
* a short message in the status area, and (if necessary) a longer message
|
||||
* in the console.
|
||||
*
|
||||
* @param exceptionClass Class name causing the error (with full package name)
|
||||
* @param message The message from the exception
|
||||
* @param listener The Editor or command line interface that's listening for errors
|
||||
* @return true if the error was purtified, false otherwise
|
||||
*/
|
||||
public static boolean handleCommonErrors(final String exceptionClass,
|
||||
final String message,
|
||||
final RunnerListener listener,
|
||||
final PrintStream err) {
|
||||
if (exceptionClass.equals("java.lang.OutOfMemoryError")) {
|
||||
if (message.contains("exceeds VM budget")) {
|
||||
// TODO this is a kludge for Android, since there's no memory preference
|
||||
listener.statusError("OutOfMemoryError: This code attempts to use more memory than available.");
|
||||
err.println("An OutOfMemoryError means that your code is either using up too much memory");
|
||||
err.println("because of a bug (e.g. creating an array that's too large, or unintentionally");
|
||||
err.println("loading thousands of images), or simply that it's trying to use more memory");
|
||||
err.println("than what is supported by the current device.");
|
||||
} else {
|
||||
listener.statusError("OutOfMemoryError: You may need to increase the memory setting in Preferences.");
|
||||
err.println("An OutOfMemoryError means that your code is either using up too much memory");
|
||||
err.println("because of a bug (e.g. creating an array that's too large, or unintentionally");
|
||||
err.println("loading thousands of images), or that your sketch may need more memory to run.");
|
||||
err.println("If your sketch uses a lot of memory (for instance if it loads a lot of data files)");
|
||||
err.println("you can increase the memory available to your sketch using the Preferences window.");
|
||||
}
|
||||
} else if (exceptionClass.equals("java.lang.UnsatisfiedLinkError")) {
|
||||
listener.statusError("A library used by this sketch is not installed properly.");
|
||||
err.println("A library relies on native code that's not available.");
|
||||
err.println("Or only works properly when the sketch is run as a " +
|
||||
((Base.getNativeBits() == 32) ? "64-bit " : "32-bit ") + " application.");
|
||||
|
||||
} else if (exceptionClass.equals("java.lang.StackOverflowError")) {
|
||||
listener.statusError("StackOverflowError: This sketch is attempting too much recursion.");
|
||||
err.println("A StackOverflowError means that you have a bug that's causing a function");
|
||||
err.println("to be called recursively (it's calling itself and going in circles),");
|
||||
err.println("or you're intentionally calling a recursive function too much,");
|
||||
err.println("and your code should be rewritten in a more efficient manner.");
|
||||
|
||||
} else if (exceptionClass.equals("java.lang.UnsupportedClassVersionError")) {
|
||||
listener.statusError("UnsupportedClassVersionError: A library is using code compiled with an unsupported version of Java.");
|
||||
err.println("This version of Processing only supports libraries and JAR files compiled for Java 1.6 or earlier.");
|
||||
err.println("A library used by this sketch was compiled for Java 1.7 or later, ");
|
||||
err.println("and needs to be recompiled to be compatible with Java 1.6.");
|
||||
|
||||
} else if (exceptionClass.equals("java.lang.NoSuchMethodError") ||
|
||||
exceptionClass.equals("java.lang.NoSuchFieldError")) {
|
||||
listener.statusError(exceptionClass.substring(10) + ": " +
|
||||
"You may be using a library that's incompatible " +
|
||||
"with this version of Processing.");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// TODO: This may be called more than one time per error in the VM,
|
||||
// presumably because exceptions might be wrapped inside others,
|
||||
// and this will fire for both.
|
||||
protected void reportException(String message, ObjectReference or, ThreadReference thread) {
|
||||
listener.statusError(findException(message, or, thread));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move through a list of stack frames, searching for references to code
|
||||
* found in the current sketch. Return with a RunnerException that contains
|
||||
* the location of the error, or if nothing is found, just return with a
|
||||
* RunnerException that wraps the error message itself.
|
||||
*/
|
||||
protected SketchException findException(String message, ObjectReference or, ThreadReference thread) {
|
||||
try {
|
||||
// use to dump the stack for debugging
|
||||
// for (StackFrame frame : thread.frames()) {
|
||||
// System.out.println("frame: " + frame);
|
||||
// }
|
||||
|
||||
List<StackFrame> frames = thread.frames();
|
||||
for (StackFrame frame : frames) {
|
||||
try {
|
||||
Location location = frame.location();
|
||||
String filename = null;
|
||||
filename = location.sourceName();
|
||||
int lineNumber = location.lineNumber() - 1;
|
||||
SketchException rex =
|
||||
build.placeException(message, filename, lineNumber);
|
||||
if (rex != null) {
|
||||
return rex;
|
||||
}
|
||||
} catch (AbsentInformationException e) {
|
||||
// Any of the thread.blah() methods can throw an AbsentInformationEx
|
||||
// if that bit of data is missing. If so, just write out the error
|
||||
// message to the console.
|
||||
//e.printStackTrace(); // not useful
|
||||
exception = new SketchException(message);
|
||||
exception.hideStackTrace();
|
||||
listener.statusError(exception);
|
||||
}
|
||||
}
|
||||
} catch (IncompatibleThreadStateException e) {
|
||||
// This shouldn't happen, but if it does, print the exception in case
|
||||
// it's something that needs to be debugged separately.
|
||||
e.printStackTrace(sketchErr);
|
||||
}
|
||||
// before giving up, try to extract from the throwable object itself
|
||||
// since sometimes exceptions are re-thrown from a different context
|
||||
try {
|
||||
// assume object reference is Throwable, get stack trace
|
||||
Method method = ((ClassType) or.referenceType()).concreteMethodByName("getStackTrace", "()[Ljava/lang/StackTraceElement;");
|
||||
ArrayReference result = (ArrayReference) or.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
||||
// iterate through stack frames and pull filename and line number for each
|
||||
for (Value val: result.getValues()) {
|
||||
ObjectReference ref = (ObjectReference)val;
|
||||
method = ((ClassType) ref.referenceType()).concreteMethodByName("getFileName", "()Ljava/lang/String;");
|
||||
StringReference strref = (StringReference) ref.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
||||
String filename = strref == null ? "Unknown Source" : strref.value();
|
||||
method = ((ClassType) ref.referenceType()).concreteMethodByName("getLineNumber", "()I");
|
||||
IntegerValue intval = (IntegerValue) ref.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
||||
int lineNumber = intval.intValue() - 1;
|
||||
SketchException rex =
|
||||
build.placeException(message, filename, lineNumber);
|
||||
if (rex != null) {
|
||||
return rex;
|
||||
}
|
||||
}
|
||||
// for (Method m : ((ClassType) or.referenceType()).allMethods()) {
|
||||
// System.out.println(m + " | " + m.signature() + " | " + m.genericSignature());
|
||||
// }
|
||||
// Implemented for 2.0b9, writes a stack trace when there's an internal error inside core.
|
||||
method = ((ClassType) or.referenceType()).concreteMethodByName("printStackTrace", "()V");
|
||||
// System.err.println("got method " + method);
|
||||
or.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(sketchErr);
|
||||
}
|
||||
// Give up, nothing found inside the pile of stack frames
|
||||
SketchException rex = new SketchException(message);
|
||||
// exception is being created /here/, so stack trace is not useful
|
||||
rex.hideStackTrace();
|
||||
return rex;
|
||||
}
|
||||
|
||||
|
||||
public void close() {
|
||||
// TODO make sure stop() has already been called to exit the sketch
|
||||
|
||||
// TODO actually kill off the vm here
|
||||
if (vm != null) {
|
||||
try {
|
||||
vm.exit(0);
|
||||
|
||||
} catch (com.sun.jdi.VMDisconnectedException vmde) {
|
||||
// if the vm has disconnected on its own, ignore message
|
||||
//System.out.println("harmless disconnect " + vmde.getMessage());
|
||||
// TODO shouldn't need to do this, need to do more cleanup
|
||||
}
|
||||
vm = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// made synchronized for 0087
|
||||
// attempted to remove synchronized for 0136 to fix bug #775 (no luck tho)
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=775
|
||||
synchronized public void message(String s) {
|
||||
// System.out.println("M" + s.length() + ":" + s.trim()); // + "MMM" + s.length());
|
||||
|
||||
// this eats the CRLFs on the lines.. oops.. do it later
|
||||
//if (s.trim().length() == 0) return;
|
||||
|
||||
// this is PApplet sending a message (via System.out.println)
|
||||
// that signals that the applet has been quit.
|
||||
if (s.indexOf(PApplet.EXTERNAL_STOP) == 0) {
|
||||
//System.out.println("external: quit");
|
||||
if (editor != null) {
|
||||
// editor.internalCloseRunner(); // [091124]
|
||||
// editor.handleStop(); // prior to 0192
|
||||
editor.internalCloseRunner(); // 0192
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// this is the PApplet sending us a message that the applet
|
||||
// is being moved to a new window location
|
||||
if (s.indexOf(PApplet.EXTERNAL_MOVE) == 0) {
|
||||
String nums = s.substring(s.indexOf(' ') + 1).trim();
|
||||
int space = nums.indexOf(' ');
|
||||
int left = Integer.parseInt(nums.substring(0, space));
|
||||
int top = Integer.parseInt(nums.substring(space + 1));
|
||||
// this is only fired when connected to an editor
|
||||
editor.setSketchLocation(new Point(left, top));
|
||||
//System.out.println("external: move to " + left + " " + top);
|
||||
return;
|
||||
}
|
||||
|
||||
// these are used for debugging, in case there are concerns
|
||||
// that some errors aren't coming through properly
|
||||
// if (s.length() > 2) {
|
||||
// System.err.println(newMessage);
|
||||
// System.err.println("message " + s.length() + ":" + s);
|
||||
// }
|
||||
|
||||
// always shove out the message, since it might not fall under
|
||||
// the same setup as we're expecting
|
||||
sketchErr.print(s);
|
||||
//System.err.println("[" + s.length() + "] " + s);
|
||||
sketchErr.flush();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user