aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/TreeParser.java
blob: 9ea545e55cc0bb5006727cc4501e4c55a075947a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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;

/**
 * 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 {
    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;
    }

    private static class ParseError extends RuntimeException {
        private static final long serialVersionUID = 1887754619522101929L;

        ParseError(Throwable cause) {
            super(cause);
        }
    }
}