blob: ea7f67f69e9e9b5e1e146e882bc022c34345f137 [file] [log] [blame]
package org.checkerframework.javacutil.trees;
import java.util.StringTokenizer;
import javax.annotation.processing.ProcessingEnvironment;
import com.sun.source.tree.ExpressionTree;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;
/**
* A Utility class for parsing Java expression snippets, and converting them
* to proper Javac AST nodes.
*
* This is useful for parsing {@code EnsuresNonNull*},
* and {@code KeyFor} values.
*
* Currently, it handles four tree types only:
* <ul>
* <li>Identifier tree (e.g. {@code id})</li>
* <li>Literal tree (e.g. 2, 3)</li>
* <li>Method invocation tree (e.g. {@code method(2, 3)})</li>
* <li>Member select tree (e.g. {@code Class.field}, {@code instance.method()})
* <li>Array access tree (e.g. {@code array[id]})</li>
* </ul>
*
* Notable limitation: Doesn't handle spaces, or non-method-argument
* parenthesis.
*
* It's implemented via a Recursive-Descend parser.
*/
public class TreeParser {
private static final String DELIMS = ".[](),";
private static final String SENTINAL = "";
private final TreeMaker maker;
private final Names names;
public TreeParser(ProcessingEnvironment env) {
Context context = ((JavacProcessingEnvironment)env).getContext();
maker = TreeMaker.instance(context);
names = Names.instance(context);
}
/**
* Parses the snippet in the string as an internal Javac AST expression
* node
*
* @param s the java snippet
* @return the AST corresponding to the snippet
*/
public ExpressionTree parseTree(String s) {
tokenizer = new StringTokenizer(s, DELIMS, true);
token = tokenizer.nextToken();
try {
return parseExpression();
} catch (Exception e) {
throw new ParseError(e);
} finally {
tokenizer = null;
token = null;
}
}
StringTokenizer tokenizer = null;
String token = null;
private String nextToken() {
token = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : SENTINAL;
return token;
}
JCExpression fromToken(String token) {
// Optimization
if ("true".equals(token)) {
return maker.Literal(true);
} else if ("false".equals(token)) {
return maker.Literal(false);
}
if (Character.isLetter(token.charAt(0)))
return maker.Ident(names.fromString(token));
Object value = null;
try {
value = Integer.valueOf(token);
} catch (Exception e2) { try {
value = Double.valueOf(token);
} catch (Exception ef) {}}
assert value != null;
return maker.Literal(value);
}
JCExpression parseExpression() {
JCExpression tree = fromToken(token);
while (tokenizer.hasMoreTokens()) {
String delim = nextToken();
if (".".equals(delim)) {
nextToken();
tree = maker.Select(tree,
names.fromString(token));
} else if ("(".equals(delim)) {
nextToken();
ListBuffer<JCExpression> args = new ListBuffer<>();
while (!")".equals(token)) {
JCExpression arg = parseExpression();
args.append(arg);
if (",".equals(token))
nextToken();
}
// For now, handle empty args only
assert ")".equals(token);
tree = maker.Apply(List.<JCExpression>nil(),
tree, args.toList());
} else if ("[".equals(token)) {
nextToken();
JCExpression index = parseExpression();
assert "]".equals(token);
tree = maker.Indexed(tree, index);
} else {
return tree;
}
}
return tree;
}
class ParseError extends RuntimeException {
private static final long serialVersionUID = 1887754619522101929L;
ParseError(Throwable cause) {
super(cause);
}
}
}