/*
* ASTUtils.java
*
* Copyright (c) 2006-2007 David Holroyd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.co.badgersinfoil.metaas.impl;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import org.antlr.runtime.ANTLRReaderStream;
import org.antlr.runtime.MismatchedTokenException;
import org.antlr.runtime.NoViableAltException;
import org.antlr.runtime.RecognitionException;
import org.asdt.core.internal.antlr.AS3Lexer;
import org.asdt.core.internal.antlr.AS3Parser;
import uk.co.badgersinfoil.metaas.ActionScriptFactory;
import uk.co.badgersinfoil.metaas.SyntaxException;
import uk.co.badgersinfoil.metaas.dom.Expression;
import uk.co.badgersinfoil.metaas.impl.antlr.BasicListUpdateDelegate;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListToken;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTokenSource;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTokenStream;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTree;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTreeAdaptor;
import uk.co.badgersinfoil.metaas.impl.antlr.TreeTokenListUpdateDelegate;
/**
* A collection of helper methods for dealing with AST nodes.
*/
public class ASTUtils {
public static final LinkedListTreeAdaptor TREE_ADAPTOR = new LinkedListTreeAdaptor();
static {
TREE_ADAPTOR.setFactory(new LinkedListTreeAdaptor.Factory() {
private BasicListUpdateDelegate basicDelegate = new BasicListUpdateDelegate();
private ParentheticListUpdateDelegate blockDelegate = new ParentheticListUpdateDelegate(AS3Parser.LCURLY, AS3Parser.RCURLY);
private ParentheticListUpdateDelegate metadataTagDelegate = new ParentheticListUpdateDelegate(AS3Parser.LBRACK, AS3Parser.RBRACK);
private ParentheticListUpdateDelegate paramsDelegate = new ParentheticListUpdateDelegate(AS3Parser.LPAREN, AS3Parser.RPAREN);
public TreeTokenListUpdateDelegate create(LinkedListTree payload) {
if (payload.isNil()) {
return basicDelegate;
}
switch (payload.getType()) {
case AS3Parser.BLOCK:
case AS3Parser.TYPE_BLOCK:
case AS3Parser.OBJECT_LITERAL:
return blockDelegate;
case AS3Parser.ANNOTATION:
case AS3Parser.ARRAY_LITERAL:
return metadataTagDelegate;
case AS3Parser.PARAMS:
case AS3Parser.ANNOTATION_PARAMS:
case AS3Parser.ARGUMENTS:
case AS3Parser.ENCPS_EXPR:
case AS3Parser.CONDITION:
return paramsDelegate;
default:
return basicDelegate;
}
}
});
}
/**
* Stringifies the given IDENTIFIER node.
*/
public static String identText(LinkedListTree ast) {
if (ast.getType() != AS3Parser.IDENTIFIER) {
throw new IllegalArgumentException("expected IDENTIFIER, but token was a " + tokenName(ast));
}
return stringifyIdentAux((LinkedListTree)ast.getChild(0));
}
private static String stringifyIdentAux(LinkedListTree ast) {
StringBuffer result = new StringBuffer();
if (ast.getType()==AS3Parser.DBL_COLON) {
result.append(ast.getFirstChild());
result.append("::");
result.append(ast.getLastChild());
} else if (ast.getType()==AS3Parser.PROPERTY_OR_IDENTIFIER
|| ast.getType()==AS3Parser.DOT)
{
result.append(stringifyIdentAux(ast.getFirstChild()));
result.append(".");
result.append(stringifyIdentAux(ast.getLastChild()));
} else {
result.append(ast.getText());
}
return result.toString();
}
public static String qualifiedIdentText(LinkedListTree ast) {
if (ast.getType()==AS3Parser.DBL_COLON) {
return ast.getFirstChild() + "::" + ast.getLastChild();
}
return ast.getText();
}
public static String identStarText(LinkedListTree ast) {
if (ast.getType() != AS3Parser.IDENTIFIER_STAR) {
throw new IllegalArgumentException("expected IDENTIFIER_STAR, but token was a " + tokenName(ast));
}
StringBuffer result = new StringBuffer();
for (int i=0; i<ast.getChildCount(); i++) {
LinkedListTree part = (LinkedListTree)ast.getChild(i);
if (result.length() > 0) {
result.append(".");
}
result.append(part.getText());
}
return result.toString();
}
/**
* Stringifies the type name from the given TYPE_SPEC node.
*/
public static String typeSpecText(LinkedListTree ast) {
if (ast.getType() != AS3Parser.TYPE_SPEC) {
throw new IllegalArgumentException("expected TYPE_SPEC, but token was a " + tokenName(ast));
}
LinkedListTree type = ast.getFirstChild();
if (type.getType() == AS3Parser.IDENTIFIER) {
return identText(type);
}
// handle e.g. "void",
return type.getText();
}
/**
* Helper for constructing error messages
*/
public static String tokenName(LinkedListTree ast) {
return tokenName(ast.getType());
}
/**
* Helper for constructing error messages
*/
public static String tokenName(int type) {
if (type == -1) {
return "<EOF>";
}
return AS3Parser.tokenNames[type];
}
/**
* Returns the first child of the given AST node which has the given
* type, or null, if no such node exists.
*/
public static LinkedListTree findChildByType(LinkedListTree ast, int type) {
return (LinkedListTree)ast.getFirstChildWithType(type);
}
/**
* Returns an ActionScript3 parser which will recognise input from the
* given string.
*/
public static AS3Parser parse(String text) {
return parse(new StringReader(text));
}
/**
* Returns an ActionScript3 parser which will recognise input from the
* given reader.
*/
public static AS3Parser parse(Reader in) {
ANTLRReaderStream cs;
try {
cs = new ANTLRReaderStream(in);
} catch (IOException e) {
throw new SyntaxException(e);
}
AS3Lexer lexer = new AS3Lexer(cs);
LinkedListTokenSource linker = new LinkedListTokenSource(lexer);
LinkedListTokenStream tokenStream = new LinkedListTokenStream(linker);
AS3Parser parser = new AS3Parser(tokenStream);
parser.setInput(lexer, cs);
parser.setTreeAdaptor(TREE_ADAPTOR);
return parser;
}
public static SyntaxException buildSyntaxException(String statement,
AS3Parser parser,
RecognitionException e)
{
String msg;
if (e instanceof NoViableAltException) {
NoViableAltException ex = (NoViableAltException)e;
msg = "Unexpected token "+tokenName(parser, ex.getUnexpectedType());
if (statement != null) {
msg += " in "+ActionScriptFactory.str(statement);
}
} else if (e instanceof MismatchedTokenException) {
MismatchedTokenException ex = (MismatchedTokenException)e;
msg = "Unexpected token "+tokenName(parser, ex.getUnexpectedType())+" (expecting "+tokenName(parser, ex.expecting)+")";
if (statement != null) {
msg += " in "+ActionScriptFactory.str(statement);
}
} else {
if (statement == null) {
msg = "";
} else {
msg = "Problem parsing "+ActionScriptFactory.str(statement);
}
}
msg += " at line " + e.line;
return new SyntaxException(msg, e);
}
private static String tokenName(AS3Parser parser, int type) {
if (type == -1) {
return "<end-of-file>";
}
return parser.getTokenNames()[type];
}
/**
* Constructs a new AST of the given type, initialized to contain
* text matching the token's name (use for non-literals only).
* @deprecated
*/
public static LinkedListTree newAST(int type) {
return newImaginaryAST(type);
}
/**
* Constructs a new AST of the given type, initialized to contain
* text matching the token's name (use for non-literals only).
*/
public static LinkedListTree newImaginaryAST(int type) {
return (LinkedListTree)TREE_ADAPTOR.create(type, tokenName(type));
}
public static LinkedListTree newPlaceholderAST(int type) {
LinkedListTree node = newImaginaryAST(type);
LinkedListToken placeholder = TokenBuilder.newPlaceholder(node);
return node;
}
/**
* Constructs a new AST of the given type, initialized to contain the
* given text.
*/
public static LinkedListTree newAST(int type, String text) {
LinkedListToken tok = TokenBuilder.newToken(type, text);
LinkedListTree ast = (LinkedListTree)TREE_ADAPTOR.create(tok);
return ast;
}
public static LinkedListTree newAST(LinkedListToken tok) {
return (LinkedListTree)TREE_ADAPTOR.create(tok);
}
public static LinkedListTree newParentheticAST(int type,
int startType,
String startText,
int endType,
String endText) {
LinkedListTree ast = newImaginaryAST(type);
LinkedListToken start = TokenBuilder.newToken(startType, startText);
ast.setStartToken(start);
LinkedListToken stop = TokenBuilder.newToken(endType, endText);
ast.setStopToken(stop);
start.setNext(stop);
ast.setInitialInsertionAfter(start);
return ast;
}
public static void increaseIndent(LinkedListTree node, String indent) {
LinkedListToken newStart = increaseIndentAt(node.getStartToken(), indent);
node.setStartToken(newStart);
increaseIndentAfterFirstLine(node, indent);
}
public static void increaseIndentAfterFirstLine(LinkedListTree node, String indent) {
for (LinkedListToken tok=node.getStartToken() ; tok!=node.getStopToken(); tok=tok.getNext()) {
switch (tok.getType()) {
case AS3Parser.NL:
tok = increaseIndentAt(tok.getNext(), indent);
break;
case AS3Parser.ML_COMMENT:
DocCommentUtils.increaseCommentIndent(tok, indent);
break;
}
}
}
private static LinkedListToken increaseIndentAt(LinkedListToken tok, String indentStr) {
if (tok.getType() == AS3Parser.WS) {
tok.setText(indentStr + tok.getText());
return tok;
}
LinkedListToken indent = TokenBuilder.newWhiteSpace(indentStr);
tok.beforeInsert(indent);
return indent;
}
/**
* Inspects the whitespace preceeding the given tree node to try and
* determine its level of indentation. Scans backwards from the
* startToken and returns the contents of the first whitespace token
* following a newline token, or the empty string if no such pattern
* is found.
*/
public static String findIndent(LinkedListTree node) {
LinkedListToken tok = node.getStartToken();
if (tok == null) {
return findIndent(node.getParent());
}
// the start-token of this AST node is actually whitespace, so
// scan forward until we hit a non-WS token,
while (tok.getType()==AS3Parser.NL || tok.getType()==AS3Parser.WS) {
if (tok.getNext() == null) {
break;
}
tok = tok.getNext();
}
// search backwards though the tokens, looking for the start of
// the line,
for (; tok.getPrev()!=null; tok=tok.getPrev()) {
if (tok.getType() == AS3Parser.NL) {
break;
}
}
if (tok.getType() == AS3Parser.WS) {
return tok.getText();
}
if (tok.getType()!=AS3Parser.NL) {
return "";
}
LinkedListToken startOfLine = tok.getNext();
if (startOfLine.getType() == AS3Parser.WS) {
return startOfLine.getText();
}
return "";
}
public static void addChildWithIndentation(LinkedListTree ast, LinkedListTree stmt) {
LinkedListTree last = ast.getLastChild();
String indent;
if (last == null) {
indent = "\t" + ASTUtils.findIndent(ast);
} else {
indent = ASTUtils.findIndent(last);
}
ASTUtils.increaseIndent(stmt, indent);
stmt.addToken(0, TokenBuilder.newNewline());
ast.addChildWithTokens(stmt);
}
public static void addChildWithIndentation(LinkedListTree ast, int index, LinkedListTree stmt) {
LinkedListTree last = (LinkedListTree)ast.getChild(index);
String indent;
if (last == null) {
indent = "\t" + ASTUtils.findIndent(ast);
} else {
indent = ASTUtils.findIndent(last);
}
ASTUtils.increaseIndent(stmt, indent);
stmt.addToken(0, TokenBuilder.newNewline());
ast.addChildWithTokens(index, stmt);
}
public static String decodeStringLiteral(String str) {
StringBuffer result = new StringBuffer();
char[] s = str.toCharArray();
int end = s.length - 1;
if (s[0] != '"' && s[0] != '\'') {
throw new SyntaxException("Invalid delimiter at position 0: "+s[0]);
}
char delimiter = s[0];
for (int i=1; i<end; i++) {
char c = s[i];
switch (c) {
case '\\':
c = s[++i];
switch (c) {
case 'n':
result.append('\n');
break;
case 't':
result.append('\t');
break;
case '\\':
result.append('\\');
break;
default:
result.append(c);
}
break;
default:
result.append(c);
}
}
if (s[end] != delimiter) {
throw new SyntaxException("End delimiter doesn't match "+delimiter+" at position "+end);
}
return result.toString();
}
public static LinkedListToken newStringLiteral(String constant) {
return new LinkedListToken(AS3Parser.STRING_LITERAL, ActionScriptFactory.str(constant));
}
/**
* Deletes any whitespace tokens following (but not including) the given
* token up to a comma token, and then deletes the comma token too.
*
* Used when removing elements from comma-seperated lists.
*/
public static void removeTrailingWhitespaceAndComma(LinkedListToken stopToken) {
for (LinkedListToken tok = stopToken.getNext(); tok!=null; tok=tok.getNext()) {
if (tok.getChannel() == AS3Parser.HIDDEN) {
// TODO: this might be incorrect (but never called?) see code in removePreceeding...
tok.delete();
} else if (tok.getType() == AS3Parser.COMMA) {
tok.delete();
break;
} else {
throw new SyntaxException("Unexpected token: "+tok);
}
}
}
public static void removePreceedingWhitespaceAndComma(LinkedListToken startToken) {
for (LinkedListToken tok = startToken.getPrev(); tok!=null; tok=tok.getPrev()) {
if (tok.getChannel() == AS3Parser.HIDDEN) {
LinkedListToken del = tok;
tok = tok.getNext();
del.delete();
continue;
} else if (tok.getType() == AS3Parser.COMMA) {
tok.delete();
break;
} else {
throw new SyntaxException("Unexpected token: "+tok);
}
}
}
public static void assertAS3TokenTypeIs(int expected, int actual) {
if (expected != actual) {
throw new SyntaxException("Expected "+tokenName(expected)+", got "+tokenName(actual));
}
}
public static void assertAS3TokenTypeIs(String msg, int expected, int actual) {
if (expected != actual) {
throw new SyntaxException(msg+" Expected "+tokenName(expected)+", got "+tokenName(actual));
}
}
public static String stringifyNode(LinkedListTree ast) {
StringBuffer result = new StringBuffer();
for (LinkedListToken tok=ast.getStartToken(); tok!=null&&tok.getType()!=-1; tok=tok.getNext()) {
result.append(tok.getText());
if (tok == ast.getStopToken()) {
break;
}
}
return result.toString();
}
public static void deleteAllChildren(LinkedListTree ast) {
while (ast.getChildCount() > 0) {
ast.deleteChild(0);
}
}
public static LinkedListTree ast(Expression expr) {
return ((ASTExpression)expr).getAST();
}
}