From 851bd9faa26ee71be54d090861c9216d89f04d83 Mon Sep 17 00:00:00 2001 From: GKFX Date: Wed, 4 Jun 2014 14:45:38 +0100 Subject: [PATCH 1/5] Autoformat bug fixes Miscellaneous fixes. I broke the indent-on-continued-lines feature, but it was bad to start with. --- app/src/processing/mode/java/AutoFormat.java | 322 +++++++++---------- 1 file changed, 157 insertions(+), 165 deletions(-) diff --git a/app/src/processing/mode/java/AutoFormat.java b/app/src/processing/mode/java/AutoFormat.java index 91d8a7cecd..b862510b55 100644 --- a/app/src/processing/mode/java/AutoFormat.java +++ b/app/src/processing/mode/java/AutoFormat.java @@ -48,33 +48,42 @@ public class AutoFormat implements Formatter { 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 a_flg, if_flg, s_flag, elseFlag; + + private boolean if_flg; + private boolean s_flag; + private boolean elseFlag; /** Number of ? entered without exiting at : of a?b:c structures. */ private int conditionalLevel; private int pos; - private int c_level; + private int level; private int[][] sp_flg; private int[][] s_ind; private int if_lev; /** Number of curly brackets entered and not exited. */ - private int level; + private int curlyLvl; /** Number of parentheses entered and not exited. */ private int parenLevel; private int[] ind; private int[] p_flg; - private char l_char; private int[][] s_tabs; private boolean jdoc_flag; - private char cc; + + /** The number of times to indent at a given point */ private int tabs; - private char c; + + /** + * The last non-space seen by nextChar() + */ private char lastNonWhitespace = 0; private final Stack castFlags = new Stack(); @@ -85,9 +94,9 @@ private void handleMultiLineComment() { char ch; buf.append(ch = nextChar()); // extra char - while (true) { + while (!EOF) { buf.append(ch = nextChar()); - while (ch != '/') { + while (ch != '/' && !EOF) { if (ch == '\n') { // lineNumber++; writeIndentedComment(); @@ -107,10 +116,14 @@ private void handleMultiLineComment() { return; } - + + /** + * Pumps nextChar into buf until \n or EOF, then calls + * writeIndentedLine() and set s_flag to true. + */ private void handleSingleLineComment() { char ch = nextChar(); - while (ch != '\n') { + while (ch != '\n' && !EOF) { buf.append(ch); ch = nextChar(); } @@ -119,57 +132,25 @@ private void handleSingleLineComment() { s_flag = true; } - -// /** -// * Transfers buf to result until a character is reached that -// * is neither \, \n, or in a string. \n is not written, but -// * writeIndentedLine is called on finding it. -// * @return The first character not listed above. -// */ -// private char get_string() { -// char ch1, ch2; -// while (true) { -// buf.append(ch1 = nextChar()); -// if (ch1 == '\\') { -// buf.append(nextChar()); -// } else if (ch1 == '\'' || ch1 == '\"') { -// buf.append(ch2 = nextChar()); -// while (!EOF && ch2 != ch1) { -// if (ch2 == '\\') { -// // Next char is escaped, ignore. -// buf.append(nextChar()); -// } -// buf.append(ch2 = nextChar()); -// } -// } else if (ch1 == '\n') { -// writeIndentedLine(); -// a_flg = true; -// } else { -// return ch1; -// } -// } -// } - + /** + * Prints correct indent and the contents of buf to result, + * and sets elseFlag and s_flag to new values. + * If buf is empty, prints nothing but can change flags. + */ private void writeIndentedLine() { if (buf.length() == 0) { if (s_flag) { - s_flag = a_flg = elseFlag = false; + s_flag = indentFlag = elseFlag = false; } return; } if (s_flag) { - final boolean shouldIndent = - (tabs > 0) && (buf.charAt(0) != '{') && a_flg; - if (shouldIndent) { - tabs++; - } + boolean shouldIndent = (buf.charAt(0) != '{'); + if (shouldIndent) tabs++; printIndentation(); s_flag = false; - if (shouldIndent) { - tabs--; - } - a_flg = false; + if (shouldIndent) tabs--; } if (elseFlag) { if (lastNonSpaceChar() == '}') { @@ -181,120 +162,120 @@ private void writeIndentedLine() { result.append(buf); buf.setLength(0); } - + + /** + * @return the last character in result not ' ' or '\n'. + */ private char lastNonSpaceChar() { for (int i = result.length() - 1; i >= 0; i--) { - char c_i = result.charAt(i); - if (c_i != ' ' && c_i != '\n') return c_i; + char chI = result.charAt(i); + if (chI != ' ' && chI != '\n') return chI; } return 0; } + /** + * Called by handleMultilineComment.
+ * Sets jdoc_flag if at the start of a doc comment. + * Sends buf to result with proper indents, then clears buf.
+ * Does nothing if buf is empty. + */ private void writeIndentedComment() { - final boolean saved_s_flag = s_flag; - if (buf.length() > 0) { - if (s_flag) { - printIndentation(); - s_flag = false; - } - int i = 0; - while (buf.charAt(i) == ' ') { - i++; - } - if (lookup_com("/**")) { - jdoc_flag = true; - } - if (buf.charAt(i) == '/' && buf.charAt(i + 1) == '*') { - if (saved_s_flag && getLastNonWhitespace() != ';') { - result.append(buf.substring(i)); - } else { - result.append(buf); - } + if (buf.length() == 0) return; + + int firstNonSpace = 0; + while (buf.charAt(firstNonSpace) == ' ') firstNonSpace++; + if (lookup_com("/**")) jdoc_flag = true; + + if (s_flag) printIndentation(); + + if (buf.charAt(firstNonSpace) == '/' && buf.charAt(firstNonSpace+1) == '*') { + if (s_flag && lastNonWhitespace != ';') { + result.append(buf.substring(firstNonSpace)); + } else { result.append(buf); } + } else { + if (buf.charAt(firstNonSpace) == '*' || !jdoc_flag) { + result.append(" " + buf.substring(firstNonSpace)); } else { - if (buf.charAt(i) == '*' || !jdoc_flag) { - result.append((" " + buf.substring(i))); - } else { - result.append((" * " + buf.substring(i))); - } + result.append(" * " + buf.substring(firstNonSpace)); } - buf.setLength(0); } + buf.setLength(0); } + /** + * Makes tabs >= 0 and appends tabs*indentValue + * spaces to result. + */ private void printIndentation() { - if (tabs < 0) { + if (tabs <= 0) { tabs = 0; - } - if (tabs == 0) { return; } final int spaces = tabs * indentValue; - for (int k = 0; k < spaces; k++) { + for (int i = 0; i < spaces; i++) { result.append(' '); } } + /** + * @return chars[pos+1] or '\0' if out-of-bounds. + */ private char peek() { - if (pos + 1 >= chars.length) { - return 0; - } - return chars[pos + 1]; - } - - - private int getLastNonWhitespace() { - return lastNonWhitespace; + 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. + *
Does nothing if EOF. + */ private void advanceToNonSpace() { - if (EOF) { - return; - } + if (EOF) return; + do { pos++; } while (pos < chars.length && chars[pos] == ' '); - if (pos == chars.length - 1) { - EOF = true; - } else { - pos--; // reset for nextChar() - } + + 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 EOF was set when it was called. + */ private char nextChar() { - if (EOF) { - return '\0'; - } + if (EOF) return 0; pos++; - char retVal; - if (pos < chars.length) { - retVal = chars[pos]; - if (!Character.isWhitespace(retVal)) - lastNonWhitespace = retVal; - } else { - retVal = '\0'; - } - if (pos == chars.length-1) { - EOF = true; - } + 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; } private void gotElse() { - tabs = s_tabs[c_level][if_lev]; - p_flg[level] = sp_flg[c_level][if_lev]; - ind[level] = s_ind[c_level][if_lev]; + tabs = s_tabs[curlyLvl][if_lev]; + p_flg[level] = sp_flg[curlyLvl][if_lev]; + ind[level] = s_ind[curlyLvl][if_lev]; if_flg = true; } - private boolean readUntilNewLine() { + private boolean readForNewLine() { final int savedTabs = tabs; char c = peek(); while (!EOF && (c == '\t' || c == ' ')) { @@ -327,24 +308,39 @@ private boolean readUntilNewLine() { } + /** + * 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 lookup(final String keyword) { return Pattern.matches("^\\s*" + keyword + "(?![a-zA-Z0-9_&]).*$", buf); } + /** + * 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.replaceAll("\\*", "\\\\*") + ".*$"; + 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))) + 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 = @@ -354,12 +350,13 @@ public String format(final String source) { indentValue = Preferences.getInteger("editor.tabs.size"); // lineNumber = 0; - boolean forFlag = a_flg = if_flg = false; + boolean forFlag = if_flg = false; s_flag = true; int forParenthLevel = 0; - conditionalLevel = parenLevel = c_level = if_lev = level = 0; + conditionalLevel = parenLevel = curlyLvl = if_lev = level = 0; tabs = 0; jdoc_flag = false; + char cc; int[] s_level = new int[10]; sp_flg = new int[20][10]; @@ -376,11 +373,10 @@ public String format(final String source) { EOF = false; // set in nextChar() when EOF while (!EOF) { - c = nextChar(); + char c = nextChar(); switch (c) { default: buf.append(c); - l_char = c; break; case ',': @@ -411,15 +407,14 @@ public String format(final String source) { case '\n': // lineNumber++; if (EOF) { + writeIndentedLine(); break; } elseFlag = lookup("else"); - if (elseFlag) { - gotElse(); - } + if (elseFlag) gotElse(); + if (lookup_com("//")) { - final char lastChar = buf.charAt(buf.length() - 1); - if (lastChar == '\n') { + if (buf.charAt(buf.length() - 1) == '\n') { buf.setLength(buf.length() - 1); } } @@ -430,24 +425,22 @@ public String format(final String source) { if (elseFlag) { p_flg[level]++; tabs++; - } else if (getLastNonWhitespace() == l_char) { - a_flg = true; - } + } break; case '{': elseFlag = lookup("else"); if (elseFlag) gotElse(); - if (s_if_lev.length == c_level) { + 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[c_level] = if_lev; - s_if_flg[c_level] = if_flg; + s_if_lev[curlyLvl] = if_lev; + s_if_flg[curlyLvl] = if_flg; if_lev = 0; if_flg = false; - c_level++; + curlyLvl++; if (s_flag && p_flg[level] != 0) { p_flg[level]--; tabs--; @@ -458,7 +451,7 @@ public String format(final String source) { buf.append(" "); buf.append(c); writeIndentedLine(); - readUntilNewLine(); + readForNewLine(); writeIndentedLine(); result.append("\n"); @@ -467,23 +460,22 @@ public String format(final String source) { if (p_flg[level] > 0) { ind[level] = 1; level++; - s_level[level] = c_level; + s_level[level] = curlyLvl; } break; case '}': - c_level--; - if (c_level < 0) { - c_level = 0; + curlyLvl--; + if (curlyLvl < 0) { + curlyLvl = 0; buf.append(c); writeIndentedLine(); - } else { - if_lev = s_if_lev[c_level] - 1; + if_lev = s_if_lev[curlyLvl] - 1; if (if_lev < 0) { if_lev = 0; } - if_flg = s_if_flg[c_level]; + if_flg = s_if_flg[curlyLvl]; trimRight(buf); writeIndentedLine(); tabs--; @@ -496,11 +488,11 @@ public String format(final String source) { result.append(nextChar()); } - readUntilNewLine(); + readForNewLine(); writeIndentedLine(); result.append('\n'); s_flag = true; - if (c_level < s_level[level]) { + if (curlyLvl < s_level[level]) { if (level > 0) { level--; } @@ -530,8 +522,7 @@ public String format(final String source) { cc = nextChar(); } buf.append(cc); - if (readUntilNewLine()) { - l_char = cc; + if (readForNewLine()) { // push a newline into the stream chars[pos--] = '\n'; } @@ -552,7 +543,7 @@ public String format(final String source) { tabs -= p_flg[level]; p_flg[level] = 0; } - readUntilNewLine(); + readForNewLine(); writeIndentedLine(); result.append("\n"); s_flag = true; @@ -579,13 +570,12 @@ public String format(final String source) { case ':': // Java 8 :: operator. if (peek() == ':') { - writeIndentedLine(); result.append(c).append(nextChar()); break; } // End a?b:c structures. - else if (conditionalLevel>0) { + else if (conditionalLevel > 0) { conditionalLevel--; buf.append(c); break; @@ -612,7 +602,7 @@ else if (forFlag) { if (peek() == ';') { result.append(nextChar()); } - readUntilNewLine(); + readForNewLine(); writeIndentedLine(); result.append('\n'); s_flag = true; @@ -637,7 +627,6 @@ else if (forFlag) { break; case ')': - final boolean isCast = castFlags.isEmpty() ? false : castFlags.pop(); parenLevel--; @@ -648,16 +637,13 @@ else if (forFlag) { forFlag = false; } - if (parenLevel < 0) { - parenLevel = 0; - } + if (parenLevel < 0) parenLevel = 0; buf.append(c); writeIndentedLine(); - if (readUntilNewLine()) { + if (readForNewLine()) { chars[pos--] = '\n'; if (parenLevel != 0) { - a_flg = true; } else if (tabs > 0 && !isCast) { p_flg[level]++; tabs++; @@ -681,26 +667,32 @@ else if (forFlag) { buf.append(c); parenLevel++; - // isFor says "is it the start of a for?". If it is, we set forFlag and + // 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) { - if (!forFlag) { + if (!forFlag) { forParenthLevel = parenLevel; forFlag = true; } } else if (isIf) { writeIndentedLine(); - s_tabs[c_level][if_lev] = tabs; - sp_flg[c_level][if_lev] = p_flg[level]; - s_ind[c_level][if_lev] = ind[level]; + s_tabs[curlyLvl][if_lev] = tabs; + sp_flg[curlyLvl][if_lev] = p_flg[level]; + s_ind[curlyLvl][if_lev] = ind[level]; if_lev++; if_flg = true; } } // end switch } // end while not EOF - + final String formatted = result.toString(); + if (!formatted.replaceAll("[\\s\\*/]","") + .equals(source.replaceAll("[\\s\\*/]",""))) { + // Double-check - this thing's so buggy it might be best to have one. + throw new RuntimeException("The autoformatter did something funny. Please" + + "copy and paste your sketch into a new issue on GitHub."); + } return formatted.equals(cleanText) ? source : formatted; } } From b33b047ed1b8e4191a349f242d96acfd0f623a51 Mon Sep 17 00:00:00 2001 From: GKFX Date: Wed, 4 Jun 2014 16:09:11 +0100 Subject: [PATCH 2/5] Add indents on statements continued over new lines This wraps up my changes to the autoformatter. --- app/src/processing/mode/java/AutoFormat.java | 50 +++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/app/src/processing/mode/java/AutoFormat.java b/app/src/processing/mode/java/AutoFormat.java index b862510b55..38c29a512a 100644 --- a/app/src/processing/mode/java/AutoFormat.java +++ b/app/src/processing/mode/java/AutoFormat.java @@ -51,7 +51,7 @@ public class AutoFormat implements Formatter { /** The number of spaces in one indent. Constant. */ private int indentValue; - /** Set when the end of the chars array is reached */ + /** Set when the end of the chars array is reached. */ private boolean EOF; private boolean if_flg; @@ -67,6 +67,12 @@ public class AutoFormat implements Formatter { private int[][] s_ind; private int if_lev; + /** Is this statement (or whatever) unfinished? */ + private boolean inStatementFlag; + + /** Did the previous line overrun? */ + private boolean continuationFlag; + /** Number of curly brackets entered and not exited. */ private int curlyLvl; @@ -81,9 +87,7 @@ public class AutoFormat implements Formatter { /** The number of times to indent at a given point */ private int tabs; - /** - * The last non-space seen by nextChar() - */ + /** The last non-space seen by nextChar(). */ private char lastNonWhitespace = 0; private final Stack castFlags = new Stack(); @@ -141,16 +145,15 @@ private void handleSingleLineComment() { private void writeIndentedLine() { if (buf.length() == 0) { if (s_flag) { - s_flag = indentFlag = elseFlag = false; + s_flag = elseFlag = false; } return; } if (s_flag) { - boolean shouldIndent = (buf.charAt(0) != '{'); - if (shouldIndent) tabs++; + if (continuationFlag) tabs++; printIndentation(); s_flag = false; - if (shouldIndent) tabs--; + if (continuationFlag) tabs--; } if (elseFlag) { if (lastNonSpaceChar() == '}') { @@ -355,7 +358,7 @@ public String format(final String source) { int forParenthLevel = 0; conditionalLevel = parenLevel = curlyLvl = if_lev = level = 0; tabs = 0; - jdoc_flag = false; + jdoc_flag = continuationFlag = inStatementFlag = false; char cc; int[] s_level = new int[10]; @@ -376,6 +379,7 @@ public String format(final String source) { char c = nextChar(); switch (c) { default: + inStatementFlag = true; buf.append(c); break; @@ -425,10 +429,12 @@ public String format(final String source) { if (elseFlag) { p_flg[level]++; tabs++; - } + } + continuationFlag = inStatementFlag; break; case '{': + inStatementFlag = false; elseFlag = lookup("else"); if (elseFlag) gotElse(); @@ -465,7 +471,8 @@ public String format(final String source) { break; case '}': - curlyLvl--; + inStatementFlag = false; + curlyLvl--; if (curlyLvl < 0) { curlyLvl = 0; buf.append(c); @@ -507,6 +514,7 @@ public String format(final String source) { case '"': case '\'': + inStatementFlag = true; buf.append(c); cc = nextChar(); while (!EOF && cc != c) { @@ -537,6 +545,7 @@ public String format(final String source) { advanceToNonSpace(); break; } + inStatementFlag = false; buf.append(c); writeIndentedLine(); if (p_flg[level] > 0 && ind[level] == 0) { @@ -628,14 +637,11 @@ else if (forFlag) { case ')': final boolean isCast = castFlags.isEmpty() ? false : castFlags.pop(); - parenLevel--; // If we're further back than the start of a for loop, we've // left it. - if (forFlag && forParenthLevel > parenLevel) { - forFlag = false; - } + if (forFlag && forParenthLevel > parenLevel) forFlag = false; if (parenLevel < 0) parenLevel = 0; @@ -643,8 +649,7 @@ else if (forFlag) { writeIndentedLine(); if (readForNewLine()) { chars[pos--] = '\n'; - if (parenLevel != 0) { - } else if (tabs > 0 && !isCast) { + if (parenLevel == 0 && tabs > 0 && !isCast) { p_flg[level]++; tabs++; ind[level] = 0; @@ -670,11 +675,9 @@ else if (forFlag) { // 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) { - if (!forFlag) { - forParenthLevel = parenLevel; - forFlag = true; - } + if (isFor && !forFlag) { + forParenthLevel = parenLevel; + forFlag = true; } else if (isIf) { writeIndentedLine(); s_tabs[curlyLvl][if_lev] = tabs; @@ -691,7 +694,8 @@ else if (forFlag) { .equals(source.replaceAll("[\\s\\*/]",""))) { // Double-check - this thing's so buggy it might be best to have one. throw new RuntimeException("The autoformatter did something funny. Please" + - "copy and paste your sketch into a new issue on GitHub."); + "copy and paste your sketch into a new issue on GitHub. \n" + + "https://github.com/processing/processing/issues/new"); } return formatted.equals(cleanText) ? source : formatted; } From d62425901018551abac297198a92001422a982ba Mon Sep 17 00:00:00 2001 From: GKFX Date: Wed, 4 Jun 2014 16:21:02 +0100 Subject: [PATCH 3/5] Autoformatter: avoid code being left in buf issue #2540 --- app/src/processing/mode/java/AutoFormat.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/processing/mode/java/AutoFormat.java b/app/src/processing/mode/java/AutoFormat.java index 38c29a512a..5e4080ecac 100644 --- a/app/src/processing/mode/java/AutoFormat.java +++ b/app/src/processing/mode/java/AutoFormat.java @@ -688,15 +688,10 @@ else if (forFlag) { } } // end switch } // end while not EOF + + if (buf.length() > 0) writeIndentedLine(); final String formatted = result.toString(); - if (!formatted.replaceAll("[\\s\\*/]","") - .equals(source.replaceAll("[\\s\\*/]",""))) { - // Double-check - this thing's so buggy it might be best to have one. - throw new RuntimeException("The autoformatter did something funny. Please" + - "copy and paste your sketch into a new issue on GitHub. \n" + - "https://github.com/processing/processing/issues/new"); - } return formatted.equals(cleanText) ? source : formatted; } } From f8b296c85c2870d082e8f0677eeaa6255cee39d6 Mon Sep 17 00:00:00 2001 From: GKFX Date: Wed, 18 Jun 2014 14:28:46 +0100 Subject: [PATCH 4/5] Mostly finished AutoFormat.java - a few bugs left. Am commiting before attempting to fix by mass deletion of code. --- app/src/processing/mode/java/AutoFormat.java | 212 +++++++++++-------- 1 file changed, 122 insertions(+), 90 deletions(-) diff --git a/app/src/processing/mode/java/AutoFormat.java b/app/src/processing/mode/java/AutoFormat.java index 5e4080ecac..63f6f98468 100644 --- a/app/src/processing/mode/java/AutoFormat.java +++ b/app/src/processing/mode/java/AutoFormat.java @@ -54,31 +54,30 @@ public class AutoFormat implements Formatter { /** 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 arrayFlag; // in an array/after it in same statement? private boolean if_flg; - private boolean s_flag; + private boolean startFlag; // No buf has yet been writen to this line. private boolean elseFlag; - + /** Number of ? entered without exiting at : of a?b:c structures. */ private int conditionalLevel; - + + /** chars[pos] is where we're at. */ private int pos; private int level; private int[][] sp_flg; private int[][] s_ind; private int if_lev; - /** Is this statement (or whatever) unfinished? */ - private boolean inStatementFlag; - - /** Did the previous line overrun? */ - private boolean continuationFlag; - - /** Number of curly brackets entered and not exited. */ + /** Number of curly brackets entered and not exited, + excluding arrays. */ private int curlyLvl; /** Number of parentheses entered and not exited. */ private int parenLevel; - + private int[] ind; private int[] p_flg; private int[][] s_tabs; @@ -94,17 +93,15 @@ public class AutoFormat implements Formatter { private void handleMultiLineComment() { - final boolean saved_s_flag = s_flag; + final boolean saved_startFlag = startFlag; + buf.append(nextChar()); // So /*/ isn't self-closing. - char ch; - buf.append(ch = nextChar()); // extra char - while (!EOF) { - buf.append(ch = nextChar()); + for (char ch = nextChar(); !EOF; ch = nextChar()) { + buf.append(ch); while (ch != '/' && !EOF) { if (ch == '\n') { -// lineNumber++; writeIndentedComment(); - s_flag = true; + startFlag = true; } buf.append(ch = nextChar()); } @@ -115,15 +112,15 @@ private void handleMultiLineComment() { } writeIndentedComment(); - s_flag = saved_s_flag; + startFlag = saved_startFlag; jdoc_flag = false; return; } /** - * Pumps nextChar into buf until \n or EOF, then calls - * writeIndentedLine() and set s_flag to true. + * Pumps nextChar into buf until \n or EOF, then calls + * writeIndentedLine() and sets startFlag to true. */ private void handleSingleLineComment() { char ch = nextChar(); @@ -131,29 +128,28 @@ private void handleSingleLineComment() { buf.append(ch); ch = nextChar(); } -// lineNumber++; writeIndentedLine(); - s_flag = true; + startFlag = true; } - /** - * Prints correct indent and the contents of buf to result, - * and sets elseFlag and s_flag to new values. - * If buf is empty, prints nothing but can change flags. - */ private void writeIndentedLine() { + System.out.print(buf); if (buf.length() == 0) { - if (s_flag) { - s_flag = elseFlag = false; - } + if (startFlag) startFlag = elseFlag = false; return; } - if (s_flag) { - if (continuationFlag) tabs++; + if (startFlag) { + // Indent suppressed at eg. if{ and when + // buf is close-brackets only followed by ';'. + boolean indentMore = !buf.toString().matches("[\\s\\]\\}\\)]*;") + && (arrayFlag || buf.charAt(0) != '{') + && overflowFlag; + System.out.println(indentMore + ", " + overflowFlag); + if (indentMore) tabs++; printIndentation(); - s_flag = false; - if (continuationFlag) tabs--; + startFlag = false; + if (indentMore) tabs--; } if (elseFlag) { if (lastNonSpaceChar() == '}') { @@ -192,12 +188,14 @@ private void writeIndentedComment() { while (buf.charAt(firstNonSpace) == ' ') firstNonSpace++; if (lookup_com("/**")) jdoc_flag = true; - if (s_flag) printIndentation(); - + if (startFlag) printIndentation(); + if (buf.charAt(firstNonSpace) == '/' && buf.charAt(firstNonSpace+1) == '*') { - if (s_flag && lastNonWhitespace != ';') { + if (startFlag && lastNonWhitespace != ';') { result.append(buf.substring(firstNonSpace)); - } else { result.append(buf); } + } else { + result.append(buf); + } } else { if (buf.charAt(firstNonSpace) == '*' || !jdoc_flag) { result.append(" " + buf.substring(firstNonSpace)); @@ -210,7 +208,7 @@ private void writeIndentedComment() { /** - * Makes tabs >= 0 and appends tabs*indentValue + * Makes tabs >= 0 and appends tabs*indentValue * spaces to result. */ private void printIndentation() { @@ -259,7 +257,7 @@ private void advanceToNonSpace() { * Does nothing and returns zero if EOF was set when it was called. */ private char nextChar() { - if (EOF) return 0; + if (EOF) return 0; pos++; if (pos == chars.length-1) EOF = true; if (pos >= chars.length) return 0; @@ -303,13 +301,44 @@ private boolean readForNewLine() { if (c == '\n') { // eat it nextChar(); -// lineNumber++; tabs = savedTabs; return true; } return false; } + /** + * Called at '\n' in the main loop + * @returns whether the statement (or whatever) had ended. + */ + private boolean isOverflowing() { + if (lastNonWhitespace == 0 || lastNonWhitespace == ';') { + return false; //start of file or end of statement + } + if (lastNonWhitespace != '{' && lastNonWhitespace != '}') { + return true; // evidently in a statement; + } + return arrayFlag; // there was an array, {} aren't blocks. + // If {} were blocks for an odd, valid reason... this will + // fail, but I reckon that anyone putting blocks in arrays + // is in a small minority. + } + + + /** + * @returns 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]. @@ -346,19 +375,18 @@ private void trimRight(final StringBuilder sb) { */ public String format(final String source) { final String normalizedText = source.replaceAll("\r", ""); - final String cleanText = + final String cleanText = normalizedText + (normalizedText.endsWith("\n") ? "" : "\n"); result.setLength(0); indentValue = Preferences.getInteger("editor.tabs.size"); -// lineNumber = 0; boolean forFlag = if_flg = false; - s_flag = true; + startFlag = true; int forParenthLevel = 0; conditionalLevel = parenLevel = curlyLvl = if_lev = level = 0; tabs = 0; - jdoc_flag = continuationFlag = inStatementFlag = false; + jdoc_flag = overflowFlag = inStatementFlag = false; char cc; int[] s_level = new int[10]; @@ -371,7 +399,6 @@ public String format(final String source) { s_tabs = new int[20][10]; pos = -1; chars = cleanText.toCharArray(); -// lineNumber = 1; EOF = false; // set in nextChar() when EOF @@ -384,9 +411,9 @@ public String format(final String source) { break; case ',': + inStatementFlag = true; trimRight(buf); - buf.append(c); - buf.append(' '); + buf.append(", "); advanceToNonSpace(); break; @@ -395,28 +422,20 @@ public String format(final String source) { elseFlag = lookup("else"); if (elseFlag) { gotElse(); - if ((!s_flag) || buf.length() > 0) { - buf.append(c); - } - + if (!startFlag || buf.length() > 0) buf.append(c); writeIndentedLine(); - s_flag = false; + startFlag = false; break; } - if ((!s_flag) || buf.length() > 0) { - buf.append(c); - } + if (!startFlag || buf.length() > 0) buf.append(c); break; case '\n': -// lineNumber++; - if (EOF) { - writeIndentedLine(); - break; - } + if (EOF) break; + elseFlag = lookup("else"); if (elseFlag) gotElse(); - + if (lookup_com("//")) { if (buf.charAt(buf.length() - 1) == '\n') { buf.setLength(buf.length() - 1); @@ -425,44 +444,50 @@ public String format(final String source) { writeIndentedLine(); result.append("\n"); - s_flag = true; + startFlag = true; if (elseFlag) { p_flg[level]++; tabs++; - } - continuationFlag = inStatementFlag; + } else overflowFlag = inStatementFlag; break; case '{': - inStatementFlag = false; + char prevChar = prevNonWhitespace(); + if (prevChar == '=' || prevChar == ']') arrayFlag = true; + if (arrayFlag) { + buf.append(c); + break; // No special treatment. TODO: indents. + } else inStatementFlag = false; // eg class declaration ends + elseFlag = lookup("else"); if (elseFlag) gotElse(); - 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 (s_flag && p_flg[level] != 0) { + 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)))) + 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++; - s_flag = true; + startFlag = true; if (p_flg[level] > 0) { ind[level] = 1; level++; @@ -471,8 +496,12 @@ public String format(final String source) { break; case '}': - inStatementFlag = false; - curlyLvl--; + if (arrayFlag) { + buf.append(c); + break; + } else inStatementFlag = false; + + curlyLvl--; if (curlyLvl < 0) { curlyLvl = 0; buf.append(c); @@ -491,18 +520,18 @@ public String format(final String source) { result.append("\n"); printIndentation(); result.append(c); - if (peek() == ';') { + if (peek() == ';' || arrayFlag) { + //Now does commas etc. + if (peek() == ' ') advanceToNonSpace(); result.append(nextChar()); } readForNewLine(); writeIndentedLine(); result.append('\n'); - s_flag = true; - if (curlyLvl < s_level[level]) { - if (level > 0) { - level--; - } + startFlag = true; + if (curlyLvl < s_level[level] && level > 0) { + level--; } if (ind[level] != 0) { tabs -= p_flg[level]; @@ -523,9 +552,8 @@ public String format(final String source) { buf.append(cc = nextChar()); } if (cc == '\n') { -// lineNumber++; writeIndentedLine(); - s_flag = true; + startFlag = true; } cc = nextChar(); } @@ -545,7 +573,6 @@ public String format(final String source) { advanceToNonSpace(); break; } - inStatementFlag = false; buf.append(c); writeIndentedLine(); if (p_flg[level] > 0 && ind[level] == 0) { @@ -555,7 +582,7 @@ public String format(final String source) { readForNewLine(); writeIndentedLine(); result.append("\n"); - s_flag = true; + startFlag = true; if (if_lev > 0) { if (if_flg) { if_lev--; @@ -564,6 +591,10 @@ public String format(final String source) { if_lev = 0; } } + // Array behaviour ends at the end + // of a statement. + arrayFlag = false; + inStatementFlag = false; break; case '\\': @@ -577,7 +608,7 @@ public String format(final String source) { break; case ':': - // Java 8 :: operator. + // Java 8 :: operator. if (peek() == ':') { result.append(c).append(nextChar()); break; @@ -599,9 +630,10 @@ else if (forFlag) { } buf.append(c); + inStatementFlag = false; if (!lookup("default") && !lookup("case")) { - s_flag = false; + startFlag = false; writeIndentedLine(); } else { tabs--; @@ -614,7 +646,7 @@ else if (forFlag) { readForNewLine(); writeIndentedLine(); result.append('\n'); - s_flag = true; + startFlag = true; break; case '/': @@ -638,7 +670,7 @@ else if (forFlag) { case ')': final boolean isCast = castFlags.isEmpty() ? false : castFlags.pop(); parenLevel--; - + // If we're further back than the start of a for loop, we've // left it. if (forFlag && forParenthLevel > parenLevel) forFlag = false; @@ -690,7 +722,7 @@ else if (forFlag) { } // end while not EOF if (buf.length() > 0) writeIndentedLine(); - + final String formatted = result.toString(); return formatted.equals(cleanText) ? source : formatted; } From 9a6160e9fdbd70b6291f5b0a457b4d515e16800c Mon Sep 17 00:00:00 2001 From: George Bateman Date: Sun, 28 Sep 2014 17:58:58 +0100 Subject: [PATCH 5/5] Fixed #2540, #1041, and EOF checks in autoformat This adds proper support for indents caused by continuing to a new line, allows arrays to be expressed in more compact form, prevents the final line being eaten if it isn't properly ended, and neatens code. --- app/src/processing/mode/java/AutoFormat.java | 258 ++++++------------- 1 file changed, 79 insertions(+), 179 deletions(-) diff --git a/app/src/processing/mode/java/AutoFormat.java b/app/src/processing/mode/java/AutoFormat.java index 63f6f98468..778aaba85d 100644 --- a/app/src/processing/mode/java/AutoFormat.java +++ b/app/src/processing/mode/java/AutoFormat.java @@ -56,20 +56,17 @@ public class AutoFormat implements Formatter { private boolean inStatementFlag; // in a line of code private boolean overflowFlag; // line overrunning? - private boolean arrayFlag; // in an array/after it in same statement? - private boolean if_flg; - private boolean startFlag; // No buf has yet been writen to this line. - private boolean elseFlag; + private boolean startFlag; // No buf has yet been writen to this line. + + /** -1 if not in array or 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; /** chars[pos] is where we're at. */ private int pos; - private int level; - private int[][] sp_flg; - private int[][] s_ind; - private int if_lev; /** Number of curly brackets entered and not exited, excluding arrays. */ @@ -78,9 +75,6 @@ public class AutoFormat implements Formatter { /** Number of parentheses entered and not exited. */ private int parenLevel; - private int[] ind; - private int[] p_flg; - private int[][] s_tabs; private boolean jdoc_flag; /** The number of times to indent at a given point */ @@ -89,11 +83,9 @@ public class AutoFormat implements Formatter { /** The last non-space seen by nextChar(). */ private char lastNonWhitespace = 0; - private final Stack castFlags = new Stack(); - private void handleMultiLineComment() { - final boolean saved_startFlag = startFlag; + final boolean savedStartFlag = startFlag; buf.append(nextChar()); // So /*/ isn't self-closing. for (char ch = nextChar(); !EOF; ch = nextChar()) { @@ -112,7 +104,7 @@ private void handleMultiLineComment() { } writeIndentedComment(); - startFlag = saved_startFlag; + startFlag = savedStartFlag; jdoc_flag = false; return; } @@ -134,31 +126,36 @@ private void handleSingleLineComment() { private void writeIndentedLine() { - System.out.print(buf); if (buf.length() == 0) { - if (startFlag) startFlag = elseFlag = false; + startFlag = false; return; } if (startFlag) { // Indent suppressed at eg. if{ and when // buf is close-brackets only followed by ';'. - boolean indentMore = !buf.toString().matches("[\\s\\]\\}\\)]*;") - && (arrayFlag || buf.charAt(0) != '{') + boolean indentMore = !buf.toString().matches("[\\s\\]\\}\\)]*;") && + (buf.charAt(0) != '{' | arrayLevel >= 0) && overflowFlag; - System.out.println(indentMore + ", " + overflowFlag); - if (indentMore) tabs++; + if (indentMore) { + tabs++; + if (arrayIndent > 0) tabs += arrayIndent; + } printIndentation(); startFlag = false; - if (indentMore) tabs--; - } - if (elseFlag) { - if (lastNonSpaceChar() == '}') { - trimRight(result); - result.append(' '); + if (indentMore) { + tabs--; + if (arrayIndent > 0) tabs -= arrayIndent; } - elseFlag = false; } + if (lastNonSpaceChar() == '}' && (bufStarts("else") || + bufStarts("while"))) { + result.append(' '); + } + // If we're still in a statement at \n, that's overflow. + overflowFlag = inStatementFlag; + arrayIndent = arrayLevel; result.append(buf); + buf.setLength(0); } @@ -208,7 +205,7 @@ private void writeIndentedComment() { /** - * Makes tabs >= 0 and appends tabs*indentValue + * Makes tabs >= 0 and appends tabs*indentValue * spaces to result. */ private void printIndentation() { @@ -254,7 +251,7 @@ private void advanceToNonSpace() { * 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 EOF was set when it was called. + * Does nothing and returns zero if already at EOF. */ private char nextChar() { if (EOF) return 0; @@ -268,14 +265,6 @@ private char nextChar() { } - 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; - } - - private boolean readForNewLine() { final int savedTabs = tabs; char c = peek(); @@ -307,26 +296,9 @@ private boolean readForNewLine() { return false; } - /** - * Called at '\n' in the main loop - * @returns whether the statement (or whatever) had ended. - */ - private boolean isOverflowing() { - if (lastNonWhitespace == 0 || lastNonWhitespace == ';') { - return false; //start of file or end of statement - } - if (lastNonWhitespace != '{' && lastNonWhitespace != '}') { - return true; // evidently in a statement; - } - return arrayFlag; // there was an array, {} aren't blocks. - // If {} were blocks for an odd, valid reason... this will - // fail, but I reckon that anyone putting blocks in arrays - // is in a small minority. - } - /** - * @returns last non-wsp in result+buf, or 0 on error. + * @return last non-wsp in result+buf, or 0 on error. */ private char prevNonWhitespace() { StringBuffer tot = new StringBuffer(); @@ -342,17 +314,28 @@ private char prevNonWhitespace() { /** * 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 &. + * It won't allow keyword to be directly followed by an alphanumeric, _, or &. * Will be different if keyword contains regex codes. */ - private boolean lookup(final String keyword) { + 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("^.*(? 0) buf.append(c); - writeIndentedLine(); - startFlag = false; - break; - } + // Nuke old indent. if (!startFlag || buf.length() > 0) buf.append(c); break; case '\n': if (EOF) break; - elseFlag = lookup("else"); - if (elseFlag) gotElse(); - if (lookup_com("//")) { if (buf.charAt(buf.length() - 1) == '\n') { buf.setLength(buf.length() - 1); } } - + writeIndentedLine(); + result.append("\n"); startFlag = true; - if (elseFlag) { - p_flg[level]++; - tabs++; - } else overflowFlag = inStatementFlag; break; case '{': char prevChar = prevNonWhitespace(); - if (prevChar == '=' || prevChar == ']') arrayFlag = true; - if (arrayFlag) { + 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; // No special treatment. TODO: indents. - } else inStatementFlag = false; // eg class declaration ends - - elseFlag = lookup("else"); - if (elseFlag) gotElse(); - if (s_if_lev.length == curlyLvl) { - s_if_lev = PApplet.expand(s_if_lev); - s_if_flg = PApplet.expand(s_if_flg); - } + break; // Nothing fancy. + } else inStatementFlag = false; // eg. class declaration ends - 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)))) { @@ -485,18 +435,17 @@ public String format(final String source) { readForNewLine(); writeIndentedLine(); - result.append("\n"); + result.append('\n'); tabs++; startFlag = true; - if (p_flg[level] > 0) { - ind[level] = 1; - level++; - s_level[level] = curlyLvl; - } break; case '}': - if (arrayFlag) { + 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; } else inStatementFlag = false; @@ -507,37 +456,20 @@ public String format(final String source) { buf.append(c); writeIndentedLine(); } else { - if_lev = s_if_lev[curlyLvl] - 1; - if (if_lev < 0) { - if_lev = 0; - } - if_flg = s_if_flg[curlyLvl]; trimRight(buf); writeIndentedLine(); tabs--; trimRight(result); - result.append("\n"); + result.append('\n'); printIndentation(); result.append(c); - if (peek() == ';' || arrayFlag) { - //Now does commas etc. - if (peek() == ' ') advanceToNonSpace(); - result.append(nextChar()); - } - + if (peek() == ';') result.append(nextChar()); + readForNewLine(); writeIndentedLine(); result.append('\n'); startFlag = true; - if (curlyLvl < s_level[level] && level > 0) { - level--; - } - if (ind[level] != 0) { - tabs -= p_flg[level]; - p_flg[level] = 0; - ind[level] = 0; - } } break; @@ -574,27 +506,14 @@ public String format(final String source) { break; } buf.append(c); + inStatementFlag = false; writeIndentedLine(); - if (p_flg[level] > 0 && ind[level] == 0) { - tabs -= p_flg[level]; - p_flg[level] = 0; - } readForNewLine(); writeIndentedLine(); result.append("\n"); startFlag = true; - if (if_lev > 0) { - if (if_flg) { - if_lev--; - if_flg = false; - } else { - if_lev = 0; - } - } - // Array behaviour ends at the end - // of a statement. - arrayFlag = false; - inStatementFlag = false; + // Array behaviour ends at the end of a statement. + arrayLevel = -1; break; case '\\': @@ -632,17 +551,17 @@ else if (forFlag) { buf.append(c); inStatementFlag = false; - if (!lookup("default") && !lookup("case")) { - startFlag = false; - writeIndentedLine(); - } else { + if (bufEnds("default") || bufEnds("case")) { tabs--; writeIndentedLine(); tabs++; + } else { + startFlag = false; // Label; zero indent. + writeIndentedLine(); } - if (peek() == ';') { - result.append(nextChar()); - } + + if (peek() == ';') result.append(nextChar()); + readForNewLine(); writeIndentedLine(); result.append('\n'); @@ -658,7 +577,7 @@ else if (forFlag) { result.append("\n"); } else if (next == '*') { if (buf.length() > 0) { - writeIndentedLine(); + writeIndentedLine(); // TODO } buf.append(c).append(nextChar()); handleMultiLineComment(); @@ -668,7 +587,6 @@ else if (forFlag) { break; case ')': - final boolean isCast = castFlags.isEmpty() ? false : castFlags.pop(); parenLevel--; // If we're further back than the start of a for loop, we've @@ -678,24 +596,13 @@ else if (forFlag) { if (parenLevel < 0) parenLevel = 0; buf.append(c); - writeIndentedLine(); - if (readForNewLine()) { - chars[pos--] = '\n'; - if (parenLevel == 0 && tabs > 0 && !isCast) { - p_flg[level]++; - tabs++; - ind[level] = 0; - } - } break; case '(': - castFlags.push(Pattern.matches("^.*?(?:int|color|float)\\s*$", buf)); + final boolean isFor = bufEnds("for"); + final boolean isIf = bufEnds("if"); - final boolean isFor = lookup("for"); - final boolean isIf = lookup("if"); - - if (isFor || isIf || lookup("while")) { + if (isFor || isIf || bufEnds("while")) { if (!Character.isWhitespace(buf.charAt(buf.length() - 1))) { buf.append(' '); } @@ -710,13 +617,6 @@ else if (forFlag) { 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_flg = true; } } // end switch } // end while not EOF