blob: e91cbed119932548e01d9c77524fef0ae1c6b772 [file] [log] [blame]
package org.checkerframework.javacutil.trees;
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;
import java.util.StringTokenizer;
import javax.annotation.processing.ProcessingEnvironment;
import org.checkerframework.javacutil.Pair;
/**
* A utility class for parsing Java expression snippets, and converting them to proper Javac AST
* nodes.
*
* <p>This is useful for parsing {@code EnsuresNonNull*}, and {@code KeyFor} values.
*
* <p>Currently, it handles four tree types only:
*
* <ul>
* <li>Identifier tree (e.g. {@code id})
* <li>Literal tree (e.g. 2, 3)
* <li>Method invocation tree (e.g. {@code method(2, 3)})
* <li>Member select tree (e.g. {@code Class.field}, {@code instance.method()})
* <li>Array access tree (e.g. {@code array[id]})
* </ul>
*
* Notable limitation: Doesn't handle spaces, or non-method-argument parenthesis.
*
* <p>It's implemented via a Recursive-Descend parser.
*/
public class TreeParser {
/** Valid delimiters. */
private static final String DELIMS = ".[](),";
/** A sentinel value. */
private static final String SENTINEL = "";
/** The TreeMaker instance. */
private final TreeMaker maker;
/** The names instance. */
private final Names names;
/** Create a TreeParser. */
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) {
StringTokenizer tokenizer = new StringTokenizer(s, DELIMS, true);
String token = tokenizer.nextToken();
try {
return parseExpression(tokenizer, token).first;
} catch (Exception e) {
throw new ParseError(e);
} finally {
tokenizer = null;
token = null;
}
}
/** The next token from the tokenizer, or the {@code SENTINEL} if none is available. */
private String nextToken(StringTokenizer tokenizer) {
return tokenizer.hasMoreTokens() ? tokenizer.nextToken() : SENTINEL;
}
/** The parsed expression tree for the given token. */
private 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;
try {
value = Integer.valueOf(token);
} catch (Exception e2) {
try {
value = Double.valueOf(token);
} catch (Exception ef) {
throw new Error("Can't parse as integer or double: " + token);
}
}
return maker.Literal(value);
}
/**
* Parse an expression.
*
* @param tokenizer the tokenizer
* @param token the first token
* @return a pair of a parsed expression and the next token
*/
private Pair<JCExpression, String> parseExpression(StringTokenizer tokenizer, String token) {
JCExpression tree = fromToken(token);
while (tokenizer.hasMoreTokens()) {
String delim = nextToken(tokenizer);
token = delim;
if (".".equals(delim)) {
token = nextToken(tokenizer);
tree = maker.Select(tree, names.fromString(token));
} else if ("(".equals(delim)) {
token = nextToken(tokenizer);
ListBuffer<JCExpression> args = new ListBuffer<>();
while (!")".equals(token)) {
Pair<JCExpression, String> p = parseExpression(tokenizer, token);
JCExpression arg = p.first;
token = p.second;
args.append(arg);
if (",".equals(token)) {
token = nextToken(tokenizer);
}
}
// For now, handle empty args only
assert ")".equals(token) : "Unexpected token: " + token;
tree = maker.Apply(List.nil(), tree, args.toList());
} else if ("[".equals(token)) {
token = nextToken(tokenizer);
Pair<JCExpression, String> p = parseExpression(tokenizer, token);
JCExpression index = p.first;
token = p.second;
assert "]".equals(token) : "Unexpected token: " + token;
tree = maker.Indexed(tree, index);
} else {
return Pair.of(tree, token);
}
assert tokenizer != null : "@AssumeAssertion(nullness): side effects";
}
return Pair.of(tree, token);
}
/** An internal error. */
private static class ParseError extends RuntimeException {
/** The serial version UID. */
private static final long serialVersionUID = 1887754619522101929L;
/** Create a ParseError. */
ParseError(Throwable cause) {
super(cause);
}
}
}