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