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