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