| 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); |
| } |
| } |
| } |