1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.databinding.tool;
18
19import android.databinding.parser.BindingExpressionBaseVisitor;
20import android.databinding.parser.BindingExpressionParser;
21import android.databinding.parser.BindingExpressionParser.AndOrOpContext;
22import android.databinding.parser.BindingExpressionParser.BinaryOpContext;
23import android.databinding.parser.BindingExpressionParser.BitShiftOpContext;
24import android.databinding.parser.BindingExpressionParser.InstanceOfOpContext;
25import android.databinding.parser.BindingExpressionParser.UnaryOpContext;
26import android.databinding.tool.expr.CallbackExprModel;
27import android.databinding.tool.expr.Expr;
28import android.databinding.tool.expr.ExprModel;
29import android.databinding.tool.expr.StaticIdentifierExpr;
30import android.databinding.tool.reflection.ModelAnalyzer;
31import android.databinding.tool.reflection.ModelClass;
32import android.databinding.tool.util.Preconditions;
33
34import com.android.annotations.NonNull;
35import com.google.common.base.Objects;
36
37import org.antlr.v4.runtime.ParserRuleContext;
38import org.antlr.v4.runtime.tree.ParseTree;
39import org.antlr.v4.runtime.tree.ParseTreeListener;
40import org.antlr.v4.runtime.tree.TerminalNode;
41
42import java.util.ArrayDeque;
43import java.util.ArrayList;
44import java.util.List;
45
46class ExpressionVisitor extends BindingExpressionBaseVisitor<Expr> {
47    private ExprModel mModel;
48    private ParseTreeListener mParseTreeListener;
49    private ArrayDeque<ExprModel> mModelStack = new ArrayDeque<ExprModel>();
50    private BindingTarget mTarget;
51
52    ExpressionVisitor(ExprModel model) {
53        mModel = model;
54    }
55
56    void setParseTreeListener(ParseTreeListener parseTreeListener) {
57        mParseTreeListener = parseTreeListener;
58    }
59
60    public void setBindingTarget(BindingTarget bindingTarget) {
61        mTarget = bindingTarget;
62    }
63
64    private void onEnter(ParserRuleContext context) {
65        if (mParseTreeListener != null) {
66            mParseTreeListener.enterEveryRule(context);
67        }
68    }
69
70    private void onExit(ParserRuleContext context) {
71        if (mParseTreeListener != null) {
72            mParseTreeListener.exitEveryRule(context);
73        }
74    }
75
76    private void pushModel(ExprModel model) {
77        Preconditions.checkNotNull(mModel, "Cannot put empty model to stack");
78        Preconditions.checkNotNull(model, "Cannot set null model");
79        mModelStack.push(mModel);
80        mModel = model;
81    }
82
83    private void popModel() {
84        Preconditions.checkNotNull(mModel, "Cannot have empty mdoel stack");
85        Preconditions.check(mModelStack.size() > 0, "Cannot have empty model stack");
86        mModel = mModelStack.pop();
87    }
88
89    @Override
90    public Expr visitRootLambda(@NonNull BindingExpressionParser.RootLambdaContext ctx) {
91        try {
92            onEnter(ctx);
93            CallbackExprModel callbackModel = new CallbackExprModel(mModel);
94            ExprModel prev = mModel;
95            pushModel(callbackModel);
96            final BindingExpressionParser.LambdaExpressionContext lambdaCtx = ctx
97                    .lambdaExpression();
98            lambdaCtx.args.accept(this);
99            return prev.lambdaExpr(lambdaCtx.expression().accept(this), callbackModel);
100        } finally {
101            popModel();
102            onExit(ctx);
103        }
104    }
105
106    @Override
107    public Expr visitSingleLambdaParameter(
108            @NonNull BindingExpressionParser.SingleLambdaParameterContext ctx) {
109        try {
110            onEnter(ctx);
111            Preconditions.check(mModel instanceof CallbackExprModel, "Lambdas can only be used in"
112                    + " callbacks.");
113            // just add it to the callback model as identifier
114            ((CallbackExprModel) mModel).callbackArg(ctx.getText());
115            return null;
116        } finally {
117            onExit(ctx);
118        }
119    }
120
121    @Override
122    public Expr visitLambdaParameterList(
123            @NonNull BindingExpressionParser.LambdaParameterListContext ctx) {
124        try {
125            onEnter(ctx);
126            Preconditions.check(mModel instanceof CallbackExprModel, "Lambdas can only be used in"
127                    + " callbacks.");
128            if (ctx.params != null) {
129                for (ParseTree item : ctx.params.children) {
130                    if (Objects.equal(item.getText(), ",")) {
131                        continue;
132                    }
133                    // just add them to the callback model as identifiers
134                    ((CallbackExprModel) mModel).callbackArg(item.getText());
135                }
136            }
137            return null;
138        } finally {
139            onExit(ctx);
140        }
141    }
142
143    @Override
144    public Expr visitStringLiteral(@NonNull BindingExpressionParser.StringLiteralContext ctx) {
145        try {
146            onEnter(ctx);
147            final String javaString;
148            if (ctx.SingleQuoteString() != null) {
149                String str = ctx.SingleQuoteString().getText();
150                String contents = str.substring(1, str.length() - 1);
151                contents = contents.replace("\"", "\\\"").replace("\\`", "`");
152                javaString = '"' + contents + '"';
153            } else {
154                javaString = ctx.DoubleQuoteString().getText();
155            }
156            return mModel.symbol(javaString, String.class);
157        } finally {
158            onExit(ctx);
159        }
160    }
161
162    @Override
163    public Expr visitRootExpr(@NonNull BindingExpressionParser.RootExprContext ctx) {
164        try {
165            onEnter(ctx);
166            // TODO handle defaults
167            return mModel.bindingExpr(ctx.expression().accept(this));
168        } catch (Exception e) {
169            System.out.println("Error while parsing! " + ctx.getText());
170            e.printStackTrace();
171            throw new RuntimeException(e);
172        } finally {
173            onExit(ctx);
174        }
175    }
176
177    @Override
178    public Expr visitGrouping(@NonNull BindingExpressionParser.GroupingContext ctx) {
179        try {
180            onEnter(ctx);
181            Preconditions.check(ctx.children.size() == 3, "Grouping expression should have"
182                    + " 3 children. # of children: %d", ctx.children.size());
183            return ctx.children.get(1).accept(this);
184        } finally {
185            onExit(ctx);
186        }
187    }
188
189    @Override
190    public Expr visitDotOp(@NonNull BindingExpressionParser.DotOpContext ctx) {
191        try {
192            onEnter(ctx);
193            ModelAnalyzer analyzer = ModelAnalyzer.getInstance();
194            ModelClass modelClass = analyzer.findClass(ctx.getText(), mModel.getImports());
195            if (modelClass == null) {
196                return mModel.field(ctx.expression().accept(this),
197                        ctx.Identifier().getSymbol().getText());
198            } else {
199                String name = modelClass.toJavaCode();
200                StaticIdentifierExpr expr = mModel.staticIdentifier(name);
201                expr.setUserDefinedType(name);
202                return expr;
203            }
204        } finally {
205            onExit(ctx);
206        }
207    }
208
209    @Override
210    public Expr visitFunctionRef(@NonNull BindingExpressionParser.FunctionRefContext ctx) {
211        try {
212            onEnter(ctx);
213            return mModel.methodReference(ctx.expression().accept(this),
214                    ctx.Identifier().getSymbol().getText());
215        } finally {
216            onExit(ctx);
217        }
218    }
219
220    @Override
221    public Expr visitQuestionQuestionOp(
222            @NonNull BindingExpressionParser.QuestionQuestionOpContext ctx) {
223        try {
224            onEnter(ctx);
225            final Expr left = ctx.left.accept(this);
226            return mModel.ternary(mModel.comparison("==", left, mModel.symbol("null", Object.class)),
227                    ctx.right.accept(this), left);
228        } finally {
229            onExit(ctx);
230        }
231    }
232
233    @Override
234    public Expr visitTerminal(@NonNull TerminalNode node) {
235        try {
236            onEnter((ParserRuleContext) node.getParent());
237            final int type = node.getSymbol().getType();
238            Class classType;
239            switch (type) {
240                case BindingExpressionParser.IntegerLiteral:
241                    classType = int.class;
242                    break;
243                case BindingExpressionParser.FloatingPointLiteral:
244                    classType = float.class;
245                    break;
246                case BindingExpressionParser.BooleanLiteral:
247                    classType = boolean.class;
248                    break;
249                case BindingExpressionParser.CharacterLiteral:
250                    classType = char.class;
251                    break;
252                case BindingExpressionParser.SingleQuoteString:
253                case BindingExpressionParser.DoubleQuoteString:
254                    classType = String.class;
255                    break;
256                case BindingExpressionParser.NullLiteral:
257                    classType = Object.class;
258                    break;
259                case BindingExpressionParser.VoidLiteral:
260                    classType = void.class;
261                    break;
262                default:
263                    throw new RuntimeException("cannot create expression from terminal node " +
264                            node.toString());
265            }
266            return mModel.symbol(node.getText(), classType);
267        } finally {
268            onExit((ParserRuleContext) node.getParent());
269        }
270    }
271
272    @Override
273    public Expr visitComparisonOp(@NonNull BindingExpressionParser.ComparisonOpContext ctx) {
274        try {
275            onEnter(ctx);
276            return mModel.comparison(ctx.op.getText(), ctx.left.accept(this), ctx.right.accept(this));
277        } finally {
278            onExit(ctx);
279        }
280    }
281
282    @Override
283    public Expr visitIdentifier(@NonNull BindingExpressionParser.IdentifierContext ctx) {
284        try {
285            onEnter(ctx);
286            return mModel.identifier(ctx.getText());
287        } finally {
288            onExit(ctx);
289        }
290    }
291
292    @Override
293    public Expr visitTernaryOp(@NonNull BindingExpressionParser.TernaryOpContext ctx) {
294        try {
295            onEnter(ctx);
296            return mModel.ternary(ctx.left.accept(this), ctx.iftrue.accept(this),
297                    ctx.iffalse.accept(this));
298        } finally {
299            onExit(ctx);
300        }
301
302    }
303
304    @Override
305    public Expr visitMethodInvocation(
306            @NonNull BindingExpressionParser.MethodInvocationContext ctx) {
307        try {
308            onEnter(ctx);
309            List<Expr> args = new ArrayList<Expr>();
310            if (ctx.args != null) {
311                for (ParseTree item : ctx.args.children) {
312                    if (Objects.equal(item.getText(), ",")) {
313                        continue;
314                    }
315                    args.add(item.accept(this));
316                }
317            }
318            return mModel.methodCall(ctx.target.accept(this),
319                    ctx.Identifier().getText(), args);
320        } finally {
321            onExit(ctx);
322        }
323    }
324
325    @Override
326    public Expr visitMathOp(@NonNull BindingExpressionParser.MathOpContext ctx) {
327        try {
328            onEnter(ctx);
329            return mModel.math(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
330        } finally {
331            onExit(ctx);
332        }
333    }
334
335    @Override
336    public Expr visitAndOrOp(@NonNull AndOrOpContext ctx) {
337        try {
338            onEnter(ctx);
339            return mModel.logical(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
340        } finally {
341            onExit(ctx);
342        }
343    }
344
345    @Override
346    public Expr visitBinaryOp(@NonNull BinaryOpContext ctx) {
347        try {
348            onEnter(ctx);
349            return mModel.math(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
350        } finally {
351            onExit(ctx);
352        }
353    }
354
355    @Override
356    public Expr visitBitShiftOp(@NonNull BitShiftOpContext ctx) {
357        try {
358            onEnter(ctx);
359            return mModel.bitshift(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
360        } finally {
361            onExit(ctx);
362        }
363    }
364
365    @Override
366    public Expr visitInstanceOfOp(@NonNull InstanceOfOpContext ctx) {
367        try {
368            onEnter(ctx);
369            return mModel.instanceOfOp(ctx.expression().accept(this), ctx.type().getText());
370        } finally {
371            onExit(ctx);
372        }
373    }
374
375    @Override
376    public Expr visitUnaryOp(@NonNull UnaryOpContext ctx) {
377        try {
378            onEnter(ctx);
379            return mModel.unary(ctx.op.getText(), ctx.expression().accept(this));
380        } finally {
381            onExit(ctx);
382        }
383    }
384
385    @Override
386    public Expr visitResources(@NonNull BindingExpressionParser.ResourcesContext ctx) {
387        try {
388            onEnter(ctx);
389            final List<Expr> args = new ArrayList<Expr>();
390            if (ctx.resourceParameters() != null) {
391                for (ParseTree item : ctx.resourceParameters().expressionList().children) {
392                    if (Objects.equal(item.getText(), ",")) {
393                        continue;
394                    }
395                    args.add(item.accept(this));
396                }
397            }
398            final String resourceReference = ctx.ResourceReference().getText();
399            final int colonIndex = resourceReference.indexOf(':');
400            final int slashIndex = resourceReference.indexOf('/');
401            final String packageName = colonIndex < 0 ? null :
402                    resourceReference.substring(1, colonIndex).trim();
403            final int startIndex = Math.max(1, colonIndex + 1);
404            final String resourceType = resourceReference.substring(startIndex, slashIndex).trim();
405            final String resourceName = resourceReference.substring(slashIndex + 1).trim();
406            return mModel.resourceExpr(mTarget, packageName, resourceType, resourceName, args);
407        } finally {
408            onExit(ctx);
409        }
410    }
411
412    @Override
413    public Expr visitBracketOp(@NonNull BindingExpressionParser.BracketOpContext ctx) {
414        try {
415            onEnter(ctx);
416            return mModel.bracketExpr(visit(ctx.expression(0)), visit(ctx.expression(1)));
417        } finally {
418            onExit(ctx);
419        }
420    }
421
422    @Override
423    public Expr visitCastOp(@NonNull BindingExpressionParser.CastOpContext ctx) {
424        try {
425            onEnter(ctx);
426            return mModel.castExpr(ctx.type().getText(), visit(ctx.expression()));
427        } finally {
428            onExit(ctx);
429        }
430    }
431}
432