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.*;
023    import org.jetbrains.jet.lang.psi.*;
024    import org.jetbrains.jet.lang.resolve.BindingContext;
025    import org.jetbrains.jet.lang.resolve.BindingContextUtils;
026    import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage;
027    import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
028    import org.jetbrains.jet.lang.resolve.constants.NullValue;
029    import org.jetbrains.jet.lang.types.JetType;
030    import org.jetbrains.jet.lang.types.TypeUtils;
031    import org.jetbrains.jet.lexer.JetTokens;
032    import org.jetbrains.k2js.translate.context.TemporaryVariable;
033    import org.jetbrains.k2js.translate.context.TranslationContext;
034    import org.jetbrains.k2js.translate.declaration.ClassTranslator;
035    import org.jetbrains.k2js.translate.expression.foreach.ForTranslator;
036    import org.jetbrains.k2js.translate.general.Translation;
037    import org.jetbrains.k2js.translate.general.TranslatorVisitor;
038    import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator;
039    import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
040    import org.jetbrains.k2js.translate.reference.*;
041    import org.jetbrains.k2js.translate.utils.*;
042    import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
043    
044    import java.util.List;
045    
046    import static org.jetbrains.jet.lang.resolve.BindingContextUtils.isVarCapturedInClosure;
047    import static org.jetbrains.k2js.translate.context.Namer.getCapturedVarAccessor;
048    import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression;
049    import static org.jetbrains.k2js.translate.reference.ReferenceTranslator.translateAsFQReference;
050    import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
051    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
052    import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
053    import static org.jetbrains.k2js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration;
054    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty;
055    import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
056    
057    public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
058        @Override
059        @NotNull
060        public JsNode visitConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
061            return translateConstantExpression(expression, context).source(expression);
062        }
063    
064        @NotNull
065        private static JsNode translateConstantExpression(@NotNull JetConstantExpression expression, @NotNull TranslationContext context) {
066            CompileTimeConstant<?> compileTimeValue = context.bindingContext().get(BindingContext.COMPILE_TIME_VALUE, expression);
067    
068            assert compileTimeValue != null : message(expression, "Expression is not compile time value: " + expression.getText() + " ");
069    
070            if (compileTimeValue instanceof NullValue) {
071                return JsLiteral.NULL;
072            }
073    
074            Object value = getCompileTimeValue(context.bindingContext(), expression, compileTimeValue);
075            if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
076                return context.program().getNumberLiteral(((Number) value).intValue());
077            }
078            else if (value instanceof Number) {
079                return context.program().getNumberLiteral(((Number) value).doubleValue());
080            }
081            else if (value instanceof Boolean) {
082                return JsLiteral.getBoolean((Boolean) value);
083            }
084    
085            //TODO: test
086            if (value instanceof String) {
087                return context.program().getStringLiteral((String) value);
088            }
089            if (value instanceof Character) {
090                return context.program().getStringLiteral(value.toString());
091            }
092    
093            throw new AssertionError(message(expression, "Unsupported constant expression: " + expression.getText() + " "));
094        }
095    
096        @Override
097        @NotNull
098        public JsNode visitBlockExpression(@NotNull JetBlockExpression jetBlock, @NotNull TranslationContext context) {
099            List<JetElement> statements = jetBlock.getStatements();
100            JsBlock jsBlock = new JsBlock();
101            TranslationContext blockContext = context.innerBlock(jsBlock);
102            for (JetElement statement : statements) {
103                assert statement instanceof JetExpression : "Elements in JetBlockExpression " +
104                                                            "should be of type JetExpression";
105                JsNode jsNode = statement.accept(this, blockContext);
106                if (jsNode != null) {
107                    jsBlock.getStatements().add(convertToStatement(jsNode));
108                }
109            }
110            return jsBlock;
111        }
112    
113        @Override
114        public JsNode visitMultiDeclaration(@NotNull JetMultiDeclaration multiDeclaration, @NotNull TranslationContext context) {
115            JetExpression jetInitializer = multiDeclaration.getInitializer();
116            assert jetInitializer != null : "Initializer for multi declaration must be not null";
117            JsExpression initializer = Translation.translateAsExpression(jetInitializer, context);
118            return MultiDeclarationTranslator.translate(multiDeclaration, context.scope().declareTemporary(), initializer, context);
119        }
120    
121        @Override
122        @NotNull
123        public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression,
124                @NotNull TranslationContext context) {
125            JetExpression returned = jetReturnExpression.getReturnedExpression();
126            return new JsReturn(returned != null ? translateAsExpression(returned, context) : null).source(jetReturnExpression);
127        }
128    
129        @Override
130        @NotNull
131        public JsNode visitParenthesizedExpression(@NotNull JetParenthesizedExpression expression,
132                @NotNull TranslationContext context) {
133            JetExpression expressionInside = expression.getExpression();
134            if (expressionInside != null) {
135                JsNode translated = expressionInside.accept(this, context);
136                if (translated != null) {
137                    return translated;
138                }
139            }
140            return context.program().getEmptyStatement();
141        }
142    
143        @Override
144        @NotNull
145        public JsNode visitBinaryExpression(@NotNull JetBinaryExpression expression,
146                @NotNull TranslationContext context) {
147            return BinaryOperationTranslator.translate(expression, context);
148        }
149    
150        @Override
151        @NotNull
152        // assume it is a local variable declaration
153        public JsNode visitProperty(@NotNull JetProperty expression, @NotNull TranslationContext context) {
154            VariableDescriptor descriptor = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, expression);
155            JsExpression initializer = translateInitializerForProperty(expression, context);
156            JsName name = context.getNameForDescriptor(descriptor);
157            if (isVarCapturedInClosure(context.bindingContext(), descriptor)) {
158                JsNameRef alias = getCapturedVarAccessor(name.makeRef());
159                initializer = JsAstUtils.wrapValue(alias, initializer == null ? JsLiteral.NULL : initializer);
160            }
161    
162            return newVar(name, initializer).source(expression);
163        }
164    
165        @Override
166        @NotNull
167        public JsNode visitCallableReferenceExpression(@NotNull JetCallableReferenceExpression expression, @NotNull TranslationContext context) {
168            return CallableReferenceTranslator.INSTANCE$.translate(expression, context);
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 = BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext());
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            JetTypeReference right = expression.getRight();
351            assert right != null;
352    
353            JetType rightType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, right);
354            JetType leftType = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.EXPRESSION_TYPE, expression.getLeft());
355            if (TypeUtils.isNullableType(rightType) || !TypeUtils.isNullableType(leftType)) {
356                return jsExpression.source(expression);
357            }
358    
359            // KT-2670
360            // we actually do not care for types in js
361            return TranslationUtils.sure(jsExpression, context).source(expression);
362        }
363    
364        private static String getReferencedName(JetSimpleNameExpression expression) {
365            String name = expression.getReferencedName();
366            return name.charAt(0) == '@' ? name.substring(1) + '$' : name;
367        }
368    
369        private static String getTargetLabel(JetExpressionWithLabel expression, TranslationContext context) {
370            JetSimpleNameExpression labelElement = expression.getTargetLabel();
371            if (labelElement == null) {
372                return null;
373            }
374            else {
375                JsName name = context.scope().findName(getReferencedName(labelElement));
376                assert name != null;
377                return name.getIdent();
378            }
379        }
380    
381        @Override
382        @NotNull
383        public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
384                @NotNull TranslationContext context) {
385            return new JsBreak(getTargetLabel(expression, context)).source(expression);
386        }
387    
388        @Override
389        @NotNull
390        public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
391                @NotNull TranslationContext context) {
392            return new JsContinue(getTargetLabel(expression, context)).source(expression);
393        }
394    
395        @Override
396        @NotNull
397        public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression, @NotNull TranslationContext context) {
398            return new LiteralFunctionTranslator(context).translate(expression.getFunctionLiteral());
399        }
400    
401        @Override
402        @NotNull
403        public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
404            JsExpression alias = new LiteralFunctionTranslator(context).translate(expression);
405    
406            FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
407            JsName name = context.getNameForDescriptor(descriptor);
408    
409            return new JsVars(new JsVars.JsVar(name, alias)).source(expression);
410        }
411    
412        @Override
413        @NotNull
414        public JsNode visitThisExpression(@NotNull JetThisExpression expression, @NotNull TranslationContext context) {
415            DeclarationDescriptor thisExpression =
416                    getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
417            assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
418    
419            return context.getThisObject(getReceiverParameterForDeclaration(thisExpression)).source(expression);
420        }
421    
422        @Override
423        @NotNull
424        public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
425                @NotNull TranslationContext context) {
426            return AccessTranslationUtils.translateAsGet(expression, context);
427        }
428    
429        @Override
430        @NotNull
431        public JsNode visitSuperExpression(@NotNull JetSuperExpression expression, @NotNull TranslationContext context) {
432            DeclarationDescriptor superClassDescriptor = context.bindingContext().get(BindingContext.REFERENCE_TARGET, expression.getInstanceReference());
433            assert superClassDescriptor != null: message(expression);
434            return translateAsFQReference(superClassDescriptor, context);
435        }
436    
437        @Override
438        @NotNull
439        public JsNode visitForExpression(@NotNull JetForExpression expression,
440                @NotNull TranslationContext context) {
441            return ForTranslator.translate(expression, context).source(expression);
442        }
443    
444        @Override
445        @NotNull
446        public JsNode visitTryExpression(@NotNull JetTryExpression expression,
447                @NotNull TranslationContext context) {
448            return TryTranslator.translate(expression, context).source(expression);
449        }
450    
451        @Override
452        @NotNull
453        public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
454                @NotNull TranslationContext context) {
455            JetExpression thrownExpression = expression.getThrownExpression();
456            assert thrownExpression != null : "Thrown expression must not be null";
457            return new JsThrow(translateAsExpression(thrownExpression, context)).source(expression);
458        }
459    
460        @Override
461        @NotNull
462        public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
463                @NotNull TranslationContext context) {
464            return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
465        }
466    
467        @Override
468        @NotNull
469        public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
470                @NotNull TranslationContext context) {
471            DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), expression);
472            JsName name = context.getNameForDescriptor(descriptor);
473            JsExpression value = ClassTranslator.generateClassCreation(expression, context);
474            return newVar(name, value).source(expression);
475        }
476    }