001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.k2js.translate.expression;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
023    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
024    import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
025    import org.jetbrains.jet.lang.psi.*;
026    import org.jetbrains.jet.lang.resolve.BindingContext;
027    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
028    import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage;
029    import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
030    import org.jetbrains.jet.lang.resolve.constants.NullValue;
031    import org.jetbrains.jet.lang.types.JetType;
032    import org.jetbrains.jet.lang.types.TypeUtils;
033    import org.jetbrains.jet.lexer.JetTokens;
034    import org.jetbrains.k2js.translate.context.TemporaryVariable;
035    import org.jetbrains.k2js.translate.context.TranslationContext;
036    import org.jetbrains.k2js.translate.declaration.ClassTranslator;
037    import org.jetbrains.k2js.translate.expression.loopTranslator.LoopTranslatorPackage;
038    import org.jetbrains.k2js.translate.general.Translation;
039    import org.jetbrains.k2js.translate.general.TranslatorVisitor;
040    import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator;
041    import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
042    import org.jetbrains.k2js.translate.reference.*;
043    import org.jetbrains.k2js.translate.utils.JsAstUtils;
044    import org.jetbrains.k2js.translate.utils.TranslationUtils;
045    
046    import java.util.List;
047    
048    import static org.jetbrains.jet.lang.resolve.BindingContextUtils.isVarCapturedInClosure;
049    import static org.jetbrains.k2js.translate.context.Namer.getCapturedVarAccessor;
050    import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression;
051    import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference;
052    import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
053    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
054    import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement;
055    import static org.jetbrains.k2js.translate.utils.JsAstUtils.newVar;
056    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration;
057    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty;
058    
059    public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
060        @Override
061        @NotNull
062        public JsNode visitConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
063            return translateConstantExpression(expression, context).source(expression);
064        }
065    
066        @NotNull
067        private static JsNode translateConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
068            CompileTimeConstant<?> compileTimeValue = context.bindingContext().get(BindingContext.COMPILE_TIME_VALUE, expression);
069    
070            assert compileTimeValue != null : message(expression, "Expression is not compile time value: " + expression.getText() + " ");
071    
072            if (compileTimeValue instanceof NullValue) {
073                return JsLiteral.NULL;
074            }
075    
076            Object value = getCompileTimeValue(context.bindingContext(), expression, compileTimeValue);
077            if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
078                return context.program().getNumberLiteral(((Number) value).intValue());
079            }
080            else if (value instanceof Long) {
081                return JsAstUtils.newLong((Long) value, context);
082            }
083            else if (value instanceof Number) {
084                return context.program().getNumberLiteral(((Number) value).doubleValue());
085            }
086            else if (value instanceof Boolean) {
087                return JsLiteral.getBoolean((Boolean) value);
088            }
089    
090            //TODO: test
091            if (value instanceof String) {
092                return context.program().getStringLiteral((String) value);
093            }
094            if (value instanceof Character) {
095                return context.program().getStringLiteral(value.toString());
096            }
097    
098            throw new AssertionError(message(expression, "Unsupported constant expression: " + expression.getText() + " "));
099        }
100    
101        @Override
102        @NotNull
103        public JsNode visitBlockExpression(@NotNull JetBlockExpression jetBlock, @NotNull TranslationContext context) {
104            List<JetElement> statements = jetBlock.getStatements();
105            JsBlock jsBlock = new JsBlock();
106            for (JetElement statement : statements) {
107                assert statement instanceof JetExpression : "Elements in JetBlockExpression " +
108                                                            "should be of type JetExpression";
109                JsNode jsNode = Translation.translateExpression((JetExpression)statement, context, jsBlock);
110                JsStatement jsStatement = convertToStatement(jsNode);
111                if (!JsAstUtils.isEmptyStatement(jsStatement)) {
112                    jsBlock.getStatements().add(jsStatement);
113                }
114            }
115            return jsBlock;
116        }
117    
118        @Override
119        public JsNode visitMultiDeclaration(@NotNull JetMultiDeclaration multiDeclaration, @NotNull TranslationContext context) {
120            JetExpression jetInitializer = multiDeclaration.getInitializer();
121            assert jetInitializer != null : "Initializer for multi declaration must be not null";
122            JsExpression initializer = Translation.translateAsExpression(jetInitializer, context);
123            return MultiDeclarationTranslator.translate(multiDeclaration, context.scope().declareTemporary(), initializer, context);
124        }
125    
126        @Override
127        @NotNull
128        public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression,
129                @NotNull TranslationContext context) {
130            JetExpression returned = jetReturnExpression.getReturnedExpression();
131            if (returned == null) {
132                return new JsReturn(null).source(jetReturnExpression);
133            }
134            JsExpression jsReturnExpression = translateAsExpression(returned, context);
135            if (JsAstUtils.isEmptyExpression(jsReturnExpression)) {
136                return context.getEmptyExpression();
137            }
138            return new JsReturn(jsReturnExpression).source(jetReturnExpression);
139        }
140    
141        @Override
142        @NotNull
143        public JsNode visitParenthesizedExpression(@NotNull JetParenthesizedExpression expression,
144                @NotNull TranslationContext context) {
145            JetExpression expressionInside = expression.getExpression();
146            if (expressionInside != null) {
147                return Translation.translateExpression(expressionInside, context);
148            }
149            return context.getEmptyStatement();
150        }
151    
152        @Override
153        @NotNull
154        public JsNode visitBinaryExpression(@NotNull JetBinaryExpression expression,
155                @NotNull TranslationContext context) {
156            return BinaryOperationTranslator.translate(expression, context);
157        }
158    
159        @Override
160        @NotNull
161        // assume it is a local variable declaration
162        public JsNode visitProperty(@NotNull JetProperty expression, @NotNull TranslationContext context) {
163            VariableDescriptor descriptor = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, expression);
164            JsExpression initializer = translateInitializerForProperty(expression, context);
165            if (initializer != null && JsAstUtils.isEmptyExpression(initializer)) {
166                return context.getEmptyExpression();
167            }
168    
169            JsName name = context.getNameForDescriptor(descriptor);
170            if (isVarCapturedInClosure(context.bindingContext(), descriptor)) {
171                JsNameRef alias = getCapturedVarAccessor(name.makeRef());
172                initializer = JsAstUtils.wrapValue(alias, initializer == null ? JsLiteral.NULL : initializer);
173            }
174    
175            return newVar(name, initializer).source(expression);
176        }
177    
178        @Override
179        @NotNull
180        public JsNode visitCallableReferenceExpression(@NotNull JetCallableReferenceExpression expression, @NotNull TranslationContext context) {
181            return CallableReferenceTranslator.INSTANCE$.translate(expression, context);
182        }
183    
184        @Override
185        @NotNull
186        public JsNode visitCallExpression(@NotNull JetCallExpression expression,
187                @NotNull TranslationContext context) {
188            if (InlinedCallExpressionTranslator.shouldBeInlined(expression, context) &&
189                BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext())) {
190                TemporaryVariable temporaryVariable = context.declareTemporary(null);
191                JsExpression callResult = CallExpressionTranslator.translate(expression, null, context).source(expression);
192                context.addStatementToCurrentBlock(JsAstUtils.assignment(temporaryVariable.reference(), callResult).makeStmt());
193                return temporaryVariable.reference();
194            } else {
195                return CallExpressionTranslator.translate(expression, null, context).source(expression);
196            }
197        }
198    
199        @Override
200        @NotNull
201        public JsNode visitIfExpression(@NotNull JetIfExpression expression, @NotNull TranslationContext context) {
202            assert expression.getCondition() != null : "condition should not ne null: " + expression.getText();
203            JsExpression testExpression = Translation.translateAsExpression(expression.getCondition(), context);
204            if (JsAstUtils.isEmptyExpression(testExpression)) {
205                return testExpression;
206            }
207    
208            boolean isKotlinExpression = BindingContextUtilPackage.isUsedAsExpression(expression, context.bindingContext());
209    
210            JetExpression thenExpression = expression.getThen();
211            assert thenExpression != null : "then expression should not be null: " + expression.getText();
212            JetExpression elseExpression = expression.getElse();
213    
214            JsStatement thenStatement = Translation.translateAsStatementAndMergeInBlockIfNeeded(thenExpression, context);
215            JsStatement elseStatement = (elseExpression != null) ? Translation.translateAsStatementAndMergeInBlockIfNeeded(elseExpression,
216                                                                                                                           context) : null;
217    
218            if (isKotlinExpression) {
219                JsExpression jsThenExpression = JsAstUtils.extractExpressionFromStatement(thenStatement);
220                JsExpression jsElseExpression = JsAstUtils.extractExpressionFromStatement(elseStatement);
221                boolean canBeJsExpression = jsThenExpression != null && jsElseExpression != null;
222                if (canBeJsExpression) {
223                    return new JsConditional(testExpression, jsThenExpression, jsElseExpression).source(expression);
224                }
225            }
226            JsIf ifStatement = new JsIf(testExpression, thenStatement, elseStatement);
227            return ifStatement.source(expression);
228        }
229    
230        @Override
231        @NotNull
232        public JsExpression visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression,
233                @NotNull TranslationContext context) {
234            return ReferenceTranslator.translateSimpleNameWithQualifier(expression, null, context).source(expression);
235        }
236    
237        @Override
238        @NotNull
239        public JsNode visitWhileExpression(@NotNull JetWhileExpression expression, @NotNull TranslationContext context) {
240            return LoopTranslatorPackage.createWhile(false, expression, context);
241        }
242    
243        @Override
244        @NotNull
245        public JsNode visitDoWhileExpression(@NotNull JetDoWhileExpression expression, @NotNull TranslationContext context) {
246            return LoopTranslatorPackage.createWhile(true, expression, context);
247        }
248    
249        @Override
250        @NotNull
251        public JsNode visitStringTemplateExpression(@NotNull JetStringTemplateExpression expression,
252                @NotNull TranslationContext context) {
253            JsStringLiteral stringLiteral = resolveAsStringConstant(expression, context);
254            if (stringLiteral != null) {
255                return stringLiteral;
256            }
257            return resolveAsTemplate(expression, context).source(expression);
258        }
259    
260        @NotNull
261        private static JsNode resolveAsTemplate(@NotNull JetStringTemplateExpression expression,
262                @NotNull TranslationContext context) {
263            return StringTemplateTranslator.translate(expression, context);
264        }
265    
266        @Nullable
267        private static JsStringLiteral resolveAsStringConstant(@NotNull JetExpression expression,
268                @NotNull TranslationContext context) {
269            Object value = getCompileTimeValue(context.bindingContext(), expression);
270            if (value == null) {
271                return null;
272            }
273            assert value instanceof String : "Compile time constant template should be a String constant.";
274            String constantString = (String) value;
275            return context.program().getStringLiteral(constantString);
276        }
277    
278        @Override
279        @NotNull
280        public JsNode visitDotQualifiedExpression(@NotNull JetDotQualifiedExpression expression,
281                @NotNull TranslationContext context) {
282            return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context);
283        }
284    
285        @Override
286        public JsNode visitLabeledExpression(
287                @NotNull JetLabeledExpression expression, TranslationContext context
288        ) {
289            JetExpression baseExpression = expression.getBaseExpression();
290            assert baseExpression != null;
291            JsName name = context.scope().declareName(getReferencedName(expression.getTargetLabel()));
292            JsStatement baseStatement = Translation.translateAsStatement(baseExpression, context);
293            return new JsLabel(name, baseStatement).source(expression);
294        }
295    
296        @Override
297        @NotNull
298        public JsNode visitPrefixExpression(
299                @NotNull JetPrefixExpression expression,
300                @NotNull TranslationContext context
301        ) {
302            return UnaryOperationTranslator.translate(expression, context).source(expression);
303        }
304    
305        @Override
306        @NotNull
307        public JsNode visitPostfixExpression(@NotNull JetPostfixExpression expression,
308                @NotNull TranslationContext context) {
309            return UnaryOperationTranslator.translate(expression, context).source(expression);
310        }
311    
312        @Override
313        @NotNull
314        public JsNode visitIsExpression(@NotNull JetIsExpression expression,
315                @NotNull TranslationContext context) {
316            return Translation.patternTranslator(context).translateIsExpression(expression);
317        }
318    
319        @Override
320        @NotNull
321        public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
322                @NotNull TranslationContext context) {
323            return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context).source(expression);
324        }
325    
326        @Override
327        @Nullable
328        public JsNode visitWhenExpression(@NotNull JetWhenExpression expression,
329                @NotNull TranslationContext context) {
330            return WhenTranslator.translate(expression, context);
331        }
332    
333        @Override
334        @NotNull
335        public JsNode visitBinaryWithTypeRHSExpression(@NotNull JetBinaryExpressionWithTypeRHS expression,
336                @NotNull TranslationContext context) {
337            JsExpression jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
338    
339            if (expression.getOperationReference().getReferencedNameElementType() != JetTokens.AS_KEYWORD)
340                return jsExpression.source(expression);
341    
342            JetTypeReference right = expression.getRight();
343            assert right != null;
344    
345            JetType rightType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, right);
346            JetType leftType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.EXPRESSION_TYPE, expression.getLeft());
347            if (TypeUtils.isNullableType(rightType) || !TypeUtils.isNullableType(leftType)) {
348                return jsExpression.source(expression);
349            }
350    
351            // KT-2670
352            // we actually do not care for types in js
353            return TranslationUtils.sure(jsExpression, context).source(expression);
354        }
355    
356        private static String getReferencedName(JetSimpleNameExpression expression) {
357            return expression.getReferencedName()
358                    .replaceAll("^@", "")
359                    .replaceAll("(?:^`(.*)`$)", "$1");
360        }
361    
362        private static String getTargetLabel(JetExpressionWithLabel expression, TranslationContext context) {
363            JetSimpleNameExpression labelElement = expression.getTargetLabel();
364            if (labelElement == null) {
365                return null;
366            }
367            else {
368                JsName name = context.scope().findName(getReferencedName(labelElement));
369                assert name != null;
370                return name.getIdent();
371            }
372        }
373    
374        @Override
375        @NotNull
376        public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
377                @NotNull TranslationContext context) {
378            return new JsBreak(getTargetLabel(expression, context)).source(expression);
379        }
380    
381        @Override
382        @NotNull
383        public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
384                @NotNull TranslationContext context) {
385            return new JsContinue(getTargetLabel(expression, context)).source(expression);
386        }
387    
388        @Override
389        @NotNull
390        public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression, @NotNull TranslationContext context) {
391            return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral());
392        }
393    
394        @Override
395        @NotNull
396        public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
397            JsExpression alias = new LiteralFunctionTranslator(context).translate(expression);
398    
399            FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
400            JsName name = context.getNameForDescriptor(descriptor);
401    
402            return new JsVars(new JsVars.JsVar(name, alias)).source(expression);
403        }
404    
405        @Override
406        @NotNull
407        public JsNode visitThisExpression(@NotNull JetThisExpression expression, @NotNull TranslationContext context) {
408            DeclarationDescriptor thisExpression =
409                    getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
410            assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
411    
412            return context.getThisObject(getReceiverParameterForDeclaration(thisExpression)).source(expression);
413        }
414    
415        @Override
416        @NotNull
417        public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
418                @NotNull TranslationContext context) {
419            return AccessTranslationUtils.translateAsGet(expression, context);
420        }
421    
422        @Override
423        @NotNull
424        public JsNode visitSuperExpression(@NotNull JetSuperExpression expression, @NotNull TranslationContext context) {
425            DeclarationDescriptor superClassDescriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, expression.getInstanceReference());
426            assert superClassDescriptor != null: message(expression);
427            return translateAsFQReference(superClassDescriptor, context);
428        }
429    
430        @Override
431        @NotNull
432        public JsNode visitForExpression(@NotNull JetForExpression expression,
433                @NotNull TranslationContext context) {
434            return LoopTranslatorPackage.translateForExpression(expression, context).source(expression);
435        }
436    
437        @Override
438        @NotNull
439        public JsNode visitTryExpression(@NotNull JetTryExpression expression,
440                @NotNull TranslationContext context) {
441            return TryTranslator.translate(expression, context).source(expression);
442        }
443    
444        @Override
445        @NotNull
446        public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
447                @NotNull TranslationContext context) {
448            JetExpression thrownExpression = expression.getThrownExpression();
449            assert thrownExpression != null : "Thrown expression must not be null";
450            return new JsThrow(translateAsExpression(thrownExpression, context)).source(expression);
451        }
452    
453        @Override
454        @NotNull
455        public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
456                @NotNull TranslationContext context) {
457            return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
458        }
459    
460        @Override
461        @NotNull
462        public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
463                @NotNull TranslationContext context) {
464            DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), expression);
465            JsName name = context.getNameForDescriptor(descriptor);
466            JsExpression value = ClassTranslator.generateClassCreation(expression, context);
467            return newVar(name, value).source(expression);
468        }
469    }