This is an automated email from the ASF dual-hosted git repository.
joshtynjala pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git
The following commit(s) were added to refs/heads/develop by this push:
new aa4a088 formatter: MXML basics
aa4a088 is described below
commit aa4a0883ec1131fc2b86d0fdc28cc742dfde53e2
Author: Josh Tynjala <[email protected]>
AuthorDate: Thu Oct 28 09:47:55 2021 -0700
formatter: MXML basics
---
.../org/apache/royale/formatter/FORMATTER.java | 369 +++++++++++++++++----
1 file changed, 304 insertions(+), 65 deletions(-)
diff --git a/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
b/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
index a55af2f..31d30cd 100644
--- a/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
+++ b/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
@@ -33,6 +33,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.royale.compiler.clients.problems.CompilerProblemCategorizer;
import org.apache.royale.compiler.clients.problems.ProblemFormatter;
import org.apache.royale.compiler.clients.problems.ProblemPrinter;
@@ -49,9 +50,13 @@ import
org.apache.royale.compiler.internal.parsing.as.MetadataToken;
import org.apache.royale.compiler.internal.parsing.as.MetadataTokenTypes;
import org.apache.royale.compiler.internal.parsing.as.RepairingTokenBuffer;
import org.apache.royale.compiler.internal.parsing.as.StreamingASTokenizer;
+import org.apache.royale.compiler.internal.parsing.mxml.MXMLToken;
+import org.apache.royale.compiler.internal.parsing.mxml.MXMLTokenizer;
import org.apache.royale.compiler.internal.tree.as.FileNode;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.parsing.IASToken;
+import org.apache.royale.compiler.parsing.IMXMLToken;
+import org.apache.royale.compiler.parsing.MXMLTokenTypes;
import org.apache.royale.compiler.problems.CompilerProblemSeverity;
import org.apache.royale.compiler.problems.ConfigurationProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
@@ -138,7 +143,7 @@ public class FORMATTER {
builder.append(sysInScanner.next());
}
} finally {
- sysInScanner.close();
+
IOUtils.closeQuietly(sysInScanner);
}
String filePath =
FilenameNormalization.normalize("stdin.as");
String fileText = builder.toString();
@@ -375,36 +380,6 @@ public class FORMATTER {
}
}
- private String formatMXMLTextInternal(String filePath, String text,
Collection<ICompilerProblem> problems) {
- StringBuilder builder = new StringBuilder();
- Pattern scriptPattern = Pattern.compile(
- "[\t
]*<((?:mx|fx):(?:Script|Metadata))>\\s*(?:<!\\[CDATA\\[)?(?:.|\\n)*?(?:\\]\\]>)?\\s*<\\/(?:mx|fx):(?:Script|Metadata)>");
- Matcher scriptMatcher = scriptPattern.matcher(text);
- if (problems == null) {
- // we need to know if there were problems because it
means that we
- // need to return the original, unformatted text
- problems = new ArrayList<ICompilerProblem>();
- }
- int lastIndex = 0;
- while (scriptMatcher.find()) {
- int start = scriptMatcher.start();
- int end = scriptMatcher.end();
- String scriptText = scriptMatcher.group().trim();
-
- builder.append(text.substring(lastIndex, start));
- String formattedText =
formatMXMLScriptElement(scriptText, problems);
- if (!ignoreProblems && hasErrors(problems)) {
- return text;
- }
- builder.append(formattedText);
- lastIndex = end;
- }
- if (lastIndex < text.length()) {
- builder.append(text.substring(lastIndex));
- }
- return builder.toString();
- }
-
private String formatMXMLScriptElement(String text,
Collection<ICompilerProblem> problems) {
String indent = "\t";
if (insertSpaces) {
@@ -469,7 +444,7 @@ public class FORMATTER {
}
private String formatAS3TextInternal(String filePath, String text,
Collection<ICompilerProblem> problems) {
- if(problems == null) {
+ if (problems == null) {
problems = new ArrayList<ICompilerProblem>();
}
@@ -483,18 +458,15 @@ public class FORMATTER {
tokenizer.setFollowIncludes(false);
streamingTokens = tokenizer.getTokens(textReader);
} finally {
- try {
- textReader.close();
- tokenizer.close();
- } catch (IOException e) {
- }
+ IOUtils.closeQuietly(textReader);
+ IOUtils.closeQuietly(tokenizer);
}
if (tokenizer.hasTokenizationProblems()) {
problems.addAll(tokenizer.getTokenizationProblems());
}
- if(!ignoreProblems && hasErrors(problems)) {
+ if (!ignoreProblems && hasErrors(problems)) {
return text;
}
@@ -532,7 +504,7 @@ public class FORMATTER {
problems.addAll(parser.getSyntaxProblems());
}
- if(!ignoreProblems && hasErrors(problems)) {
+ if (!ignoreProblems && hasErrors(problems)) {
return text;
}
@@ -557,11 +529,25 @@ public class FORMATTER {
// there may be some comments left that didn't appear before any
// of the repaired tokens, so add them all at the end
repairedTokensList.addAll(comments);
- IASToken[] repairedTokens = repairedTokensList.toArray(new
IASToken[0]);
- IASToken prevToken = null;
+ List<IASToken> tokens =
insertExtraAS3Tokens(repairedTokensList, text);
+ try {
+ return parseTokens(filePath, tokens, node);
+ } catch (Exception e) {
+ if (problems != null) {
+ System.err.println(e);
+ e.printStackTrace(System.err);
+ problems.add(new UnexpectedExceptionProblem(e));
+ }
+ return text;
+ }
+
+ }
+
+ private List<IASToken> insertExtraAS3Tokens(List<IASToken>
originalTokens, String text) {
ArrayList<IASToken> tokens = new ArrayList<IASToken>();
- for (IASToken token : repairedTokens) {
+ IASToken prevToken = null;
+ for (IASToken token : originalTokens) {
if (prevToken != null) {
boolean skipSemicolon = token.getType() ==
ASTokenTypes.TOKEN_SEMICOLON && token.isImplicit()
@@ -597,17 +583,41 @@ public class FORMATTER {
tokens.add(extraToken);
}
}
- try {
- return parseTokens(filePath, tokens, node);
- } catch (Exception e) {
- if (problems != null) {
- System.err.println(e);
- e.printStackTrace(System.err);
- problems.add(new UnexpectedExceptionProblem(e));
+ return tokens;
+ }
+
+ private List<IMXMLToken> insertExtraMXMLTokens(IMXMLToken[]
originalTokens, String text) {
+ ArrayList<IMXMLToken> tokens = new ArrayList<IMXMLToken>();
+ IMXMLToken prevToken = null;
+ for (IMXMLToken token : originalTokens) {
+ if (prevToken != null) {
+ int start = prevToken.getEnd();
+ int end = token.getStart();
+ if (end > start) {
+ String tokenText =
text.substring(start, end);
+ MXMLToken extraToken = new
MXMLToken(TOKEN_TYPE_EXTRA, start, end, prevToken.getLine(),
+ prevToken.getColumn() +
end - start, tokenText);
+ extraToken.setEndLine(token.getLine());
+
extraToken.setEndLine(token.getColumn());
+ tokens.add(extraToken);
+ }
}
- return text;
+ tokens.add(token);
+ prevToken = token;
}
-
+ if (prevToken != null) {
+ int start = prevToken.getEnd();
+ int end = text.length();
+ if (end > start) {
+ String tokenText = text.substring(start, end);
+ MXMLToken extraToken = new
MXMLToken(TOKEN_TYPE_EXTRA, start, end, prevToken.getLine(),
+ prevToken.getColumn() + end -
start, tokenText);
+ extraToken.setEndLine(prevToken.getLine());
+ extraToken.setEndLine(prevToken.getColumn());
+ tokens.add(extraToken);
+ }
+ }
+ return tokens;
}
private IASToken getNextTokenSkipExtra(List<IASToken> tokens, int
startIndex) {
@@ -765,8 +775,8 @@ public class FORMATTER {
BlockStackItem
stackItem = blockStack.get(blockStack.size() - 1);
if
(stackItem.token.getType() == ASTokenTypes.TOKEN_KEYWORD_SWITCH) {
SwitchBlockStackItem switchStackItem = (SwitchBlockStackItem) stackItem;
- if
(switchStackItem.clauseCount > 0 && (prevToken == null
-
|| prevToken.getType() != ASTokenTypes.TOKEN_BLOCK_CLOSE)) {
+ if
(switchStackItem.clauseCount > 0
+
&& (prevToken == null || prevToken.getType() !=
ASTokenTypes.TOKEN_BLOCK_CLOSE)) {
indent
= decreaseIndent(indent);
}
}
@@ -929,8 +939,7 @@ public class FORMATTER {
switch (token.getType()) {
case ASTokenTypes.TOKEN_BLOCK_OPEN: {
if (blockOpenPending) {
- boolean oneLineBlock =
nextToken != null
- &&
nextToken.getType() == ASTokenTypes.TOKEN_BLOCK_CLOSE;
+ boolean oneLineBlock =
nextToken != null && nextToken.getType() == ASTokenTypes.TOKEN_BLOCK_CLOSE;
if (placeOpenBraceOnNewLine &&
(!collapseEmptyBlocks || !oneLineBlock)) {
indent =
increaseIndent(indent);
}
@@ -957,9 +966,8 @@ public class FORMATTER {
} else {
switch (token.getType()) {
case ASTokenTypes.TOKEN_SEMICOLON: {
- if (inControlFlowStatement &&
!blockStack.isEmpty()
- &&
blockStack.get(blockStack.size() - 1).token
-
.getType() == ASTokenTypes.TOKEN_KEYWORD_FOR) {
+ if (inControlFlowStatement &&
!blockStack.isEmpty() && blockStack.get(blockStack.size() - 1).token
+ .getType() ==
ASTokenTypes.TOKEN_KEYWORD_FOR) {
if
(insertSpaceAfterSemicolonInForStatements) {
requiredSpace =
true;
}
@@ -980,9 +988,8 @@ public class FORMATTER {
}
}
if (!inControlFlowStatement) {
- if (nextToken != null
- &&
(nextToken.getType() == ASTokenTypes.HIDDEN_TOKEN_SINGLE_LINE_COMMENT
-
|| nextToken.getType() ==
ASTokenTypes.HIDDEN_TOKEN_MULTI_LINE_COMMENT)) {
+ if (nextToken != null
&& (nextToken.getType() == ASTokenTypes.HIDDEN_TOKEN_SINGLE_LINE_COMMENT
+ ||
nextToken.getType() == ASTokenTypes.HIDDEN_TOKEN_MULTI_LINE_COMMENT)) {
requiredSpace =
true;
} else {
numRequiredNewLines = Math.max(numRequiredNewLines, 1);
@@ -1108,8 +1115,7 @@ public class FORMATTER {
break;
}
case ASTokenTypes.TOKEN_KEYWORD_ELSE: {
- if (nextTokenNotComment != null
- &&
nextTokenNotComment.getType() == ASTokenTypes.TOKEN_KEYWORD_IF) {
+ if (nextTokenNotComment != null
&& nextTokenNotComment.getType() == ASTokenTypes.TOKEN_KEYWORD_IF) {
requiredSpace = true;
} else {
blockStack.add(new
BlockStackItem(token));
@@ -1590,9 +1596,9 @@ public class FORMATTER {
private boolean hasErrors(Collection<ICompilerProblem> problems) {
CompilerProblemCategorizer categorizer = new
CompilerProblemCategorizer(null);
- for(ICompilerProblem problem : problems) {
+ for (ICompilerProblem problem : problems) {
CompilerProblemSeverity severity =
categorizer.getProblemSeverity(problem);
- if(CompilerProblemSeverity.ERROR.equals(severity)) {
+ if (CompilerProblemSeverity.ERROR.equals(severity)) {
return true;
}
}
@@ -1621,4 +1627,237 @@ public class FORMATTER {
public int clauseCount = 0;
}
+
+ private String formatMXMLTextInternal(String filePath, String text,
Collection<ICompilerProblem> problems) {
+ StringReader textReader = new StringReader(text);
+ MXMLTokenizer mxmlTokenizer = new MXMLTokenizer();
+ IMXMLToken[] originalTokens = null;
+ try {
+ originalTokens = mxmlTokenizer.getTokens(textReader);
+ }
+ finally {
+ IOUtils.closeQuietly(textReader);
+ IOUtils.closeQuietly(mxmlTokenizer);
+ }
+
+ if (mxmlTokenizer.hasTokenizationProblems()) {
+ return text;
+ }
+
+ Pattern scriptStartPattern =
Pattern.compile("<((?:mx|fx):(Script|Metadata))");
+
+ List<IMXMLToken> tokens = insertExtraMXMLTokens(originalTokens,
text);
+
+ int indent = 0;
+ int numRequiredNewLines = 0;
+ boolean requiredSpace = false;
+ boolean inOpenTag = false;
+ boolean inCloseTag = false;
+ boolean indentedAttributes = false;
+ IMXMLToken prevToken = null;
+ IMXMLToken prevTokenOrExtra = null;
+ IMXMLToken token = null;
+ IMXMLToken nextToken = null;
+ List<ElementStackItem> elementStack = new
ArrayList<ElementStackItem>();
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < tokens.size(); i++) {
+ token = tokens.get(i);
+ nextToken = null;
+ if (i < (tokens.size() - 1)) {
+ nextToken = tokens.get(i + 1);
+ }
+ if (token.getType() == TOKEN_TYPE_EXTRA) {
+ if (i == (tokens.size() - 1)) {
+ // if the last token is whitespace,
include new lines
+ numRequiredNewLines = Math.max(0,
countNewLinesInExtra(token));
+ appendNewLines(builder,
numRequiredNewLines);
+ break;
+ }
+ numRequiredNewLines =
Math.max(numRequiredNewLines, countNewLinesInExtra(token));
+ prevTokenOrExtra = token;
+ continue;
+ } else if (token.getType() ==
MXMLTokenTypes.TOKEN_WHITESPACE) {
+ numRequiredNewLines =
Math.max(numRequiredNewLines, countNewLinesInExtra(token));
+ if (i == (tokens.size() - 1)) {
+ // if the last token is whitespace,
append new lines
+ appendNewLines(builder,
numRequiredNewLines);
+ }
+ continue;
+ } else if (token.getType() ==
MXMLTokenTypes.TOKEN_OPEN_TAG_START
+ &&
scriptStartPattern.matcher(token.getText()).matches()) {
+
+ if (prevToken != null && numRequiredNewLines >
0) {
+ appendNewLines(builder,
numRequiredNewLines);
+ }
+ StringBuilder scriptBuilder = new
StringBuilder();
+ scriptBuilder.append(token.getText());
+ boolean inScriptCloseTag = false;
+ while (i < (tokens.size() - 1)) {
+ i++;
+ token = tokens.get(i);
+ scriptBuilder.append(token.getText());
+ if (token.getType() ==
MXMLTokenTypes.TOKEN_CLOSE_TAG_START) {
+ inScriptCloseTag = true;
+ } else if (inScriptCloseTag &&
token.getType() == MXMLTokenTypes.TOKEN_TAG_END) {
+ break;
+ }
+ }
+ if (problems == null) {
+ // we need to know if there were
problems because it means that we
+ // need to return the original,
unformatted text
+ problems = new
ArrayList<ICompilerProblem>();
+ }
+
builder.append(formatMXMLScriptElement(scriptBuilder.toString(), problems));
+ if (hasErrors(problems)) {
+ return text;
+ }
+ prevToken = token;
+ prevTokenOrExtra = token;
+ requiredSpace = false;
+ numRequiredNewLines = 1;
+ continue;
+ }
+
+ // characters that must appear before the token
+ switch (token.getType()) {
+ case MXMLTokenTypes.TOKEN_OPEN_TAG_START: {
+ if (prevToken == null ||
prevToken.getType() != MXMLTokenTypes.TOKEN_TEXT) {
+ numRequiredNewLines =
Math.max(numRequiredNewLines, 1);
+ }
+ inOpenTag = true;
+ elementStack.add(new
ElementStackItem(token.getText().substring(1)));
+ break;
+ }
+ case MXMLTokenTypes.TOKEN_CLOSE_TAG_START: {
+ if (prevToken == null ||
prevToken.getType() != MXMLTokenTypes.TOKEN_TEXT) {
+ numRequiredNewLines =
Math.max(numRequiredNewLines, 1);
+ }
+ indent = decreaseIndent(indent);
+ inCloseTag = true;
+ break;
+ }
+ case MXMLTokenTypes.TOKEN_NAME: {
+ requiredSpace = true;
+ break;
+ }
+ case MXMLTokenTypes.TOKEN_TAG_END: {
+ break;
+ }
+ case MXMLTokenTypes.TOKEN_EMPTY_TAG_END: {
+ break;
+ }
+ }
+
+ if (prevToken != null) {
+ if (numRequiredNewLines > 0) {
+ if (inOpenTag && token.getType() !=
MXMLTokenTypes.TOKEN_OPEN_TAG_START && !indentedAttributes) {
+ indentedAttributes = true;
+ indent = increaseIndent(indent);
+ }
+ appendNewLines(builder,
numRequiredNewLines);
+ appendIndent(builder, indent);
+ } else if (requiredSpace) {
+ builder.append(' ');
+ }
+ }
+
+ // include the token's own text
+ builder.append(token.getText());
+
+ // characters that must appear after the token
+ requiredSpace = false;
+ numRequiredNewLines = 0;
+
+ switch (token.getType()) {
+ case
MXMLTokenTypes.TOKEN_PROCESSING_INSTRUCTION: {
+ numRequiredNewLines =
Math.max(numRequiredNewLines, 1);
+ break;
+ }
+ case MXMLTokenTypes.TOKEN_CLOSE_TAG_START: {
+ if (nextToken != null &&
nextToken.getType() != MXMLTokenTypes.TOKEN_TAG_END
+ && nextToken.getType()
!= MXMLTokenTypes.TOKEN_EMPTY_TAG_END) {
+ requiredSpace = true;
+ }
+ if (elementStack.isEmpty()) {
+ // something is very wrong!
+ return text;
+ }
+ String elementName =
token.getText().substring(2);
+ ElementStackItem elementItem =
elementStack.remove(elementStack.size() - 1);
+ if
(!elementName.equals(elementItem.elementName)) {
+ // there's a unclosed tag with
a different name somewhere
+ return text;
+ }
+ break;
+ }
+ case MXMLTokenTypes.TOKEN_OPEN_TAG_START: {
+ if (nextToken != null &&
nextToken.getType() != MXMLTokenTypes.TOKEN_TAG_END
+ && nextToken.getType()
!= MXMLTokenTypes.TOKEN_EMPTY_TAG_END) {
+ requiredSpace = true;
+ }
+ break;
+ }
+ case MXMLTokenTypes.TOKEN_TAG_END: {
+ if (!inOpenTag || nextToken == null ||
nextToken.getType() != MXMLTokenTypes.TOKEN_TEXT) {
+ numRequiredNewLines =
Math.max(numRequiredNewLines, 1);
+ }
+ if (inOpenTag) {
+ indent = increaseIndent(indent);
+ }
+ inOpenTag = false;
+ if (indentedAttributes) {
+ indentedAttributes = false;
+ indent = decreaseIndent(indent);
+ }
+ inCloseTag = false;
+ break;
+ }
+ case MXMLTokenTypes.TOKEN_EMPTY_TAG_END: {
+ if (!inOpenTag || nextToken == null ||
nextToken.getType() != MXMLTokenTypes.TOKEN_TEXT) {
+ numRequiredNewLines =
Math.max(numRequiredNewLines, 1);
+ }
+ if (inOpenTag) {
+
elementStack.remove(elementStack.size() - 1);
+ }
+ inOpenTag = false;
+ // no need to change nested indent
after this tag
+ // however, we may need to remove
attribute indent
+ if (indentedAttributes) {
+ indentedAttributes = false;
+ indent = decreaseIndent(indent);
+ }
+ // we shouldn't find an empty close
tag, but clear flag anyway
+ inCloseTag = false;
+ break;
+ }
+ }
+
+ prevToken = token;
+ prevTokenOrExtra = token;
+ }
+
+ return builder.toString();
+ }
+
+ private int countNewLinesInExtra(IMXMLToken token) {
+ if (token == null
+ || (token.getType() !=
MXMLTokenTypes.TOKEN_WHITESPACE && token.getType() != TOKEN_TYPE_EXTRA)) {
+ return 0;
+ }
+ int numNewLinesInWhitespace = 0;
+ String whitespace = token.getText();
+ int index = -1;
+ while ((index = whitespace.indexOf('\n', index + 1)) != -1) {
+ numNewLinesInWhitespace++;
+ }
+ return numNewLinesInWhitespace;
+ }
+
+ private static class ElementStackItem {
+ public ElementStackItem(String elementName) {
+ this.elementName = elementName;
+ }
+
+ public String elementName;
+ }
}
\ No newline at end of file