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