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.JetNodeTypes;
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.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.BindingUtils;
042    import org.jetbrains.k2js.translate.utils.JsAstUtils;
043    import org.jetbrains.k2js.translate.utils.TranslationUtils;
044    import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
045    
046    import java.util.List;
047    
048    import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression;
049    import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
050    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
051    import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
052    import static org.jetbrains.k2js.translate.utils.PsiUtils.getObjectDeclarationName;
053    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty;
054    import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
055    
056    public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
057        @Override
058        @NotNull
059        public JsNode visitConstantExpression(@NotNull JetConstantExpression expression,
060                @NotNull TranslationContext context) {
061            CompileTimeConstant<?> compileTimeValue = context.bindingContext().get(BindingContext.COMPILE_TIME_VALUE, expression);
062    
063            // TODO: workaround for default parameters translation. Will be fixed later.
064            // public fun parseInt(s: String, radix:Int = 10): Int = js.noImpl
065            if (compileTimeValue == null) {
066                if (expression.getNode().getElementType() == JetNodeTypes.BOOLEAN_CONSTANT) {
067                    return JsLiteral.getBoolean(Boolean.valueOf(expression.getText()));
068                }
069                else if (expression.getNode().getElementType() == JetNodeTypes.INTEGER_CONSTANT) {
070                    return context.program().getNumberLiteral(Integer.parseInt(expression.getText()));
071                }
072            }
073    
074            assert compileTimeValue != null;
075    
076            if (compileTimeValue instanceof NullValue) {
077                return JsLiteral.NULL;
078            }
079    
080            Object value = compileTimeValue.getValue();
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            throw new AssertionError(message(expression, "Unsupported constant expression"));
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            TranslationContext blockContext = context.innerBlock(jsBlock);
107            for (JetElement statement : statements) {
108                assert statement instanceof JetExpression : "Elements in JetBlockExpression " +
109                                                            "should be of type JetExpression";
110                JsNode jsNode = statement.accept(this, blockContext);
111                if (jsNode != null) {
112                    jsBlock.getStatements().add(convertToStatement(jsNode));
113                }
114            }
115            return source(jsBlock, jetBlock);
116        }
117    
118        @Override
119        @NotNull
120        public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression,
121                @NotNull TranslationContext context) {
122            JetExpression returnedExpression = jetReturnExpression.getReturnedExpression();
123            return source(new JsReturn(returnedExpression != null ? translateAsExpression(returnedExpression, context) : null), jetReturnExpression);
124        }
125    
126        @Override
127        @NotNull
128        public JsNode visitParenthesizedExpression(@NotNull JetParenthesizedExpression expression,
129                @NotNull TranslationContext context) {
130            JetExpression expressionInside = expression.getExpression();
131            if (expressionInside != null) {
132                return expressionInside.accept(this, context);
133            }
134            return context.program().getEmptyStmt();
135        }
136    
137        @Override
138        @NotNull
139        public JsNode visitBinaryExpression(@NotNull JetBinaryExpression expression,
140                @NotNull TranslationContext context) {
141            return BinaryOperationTranslator.translate(expression, context);
142        }
143    
144        @Override
145        @NotNull
146        // assume it is a local variable declaration
147        public JsNode visitProperty(@NotNull JetProperty expression, @NotNull TranslationContext context) {
148            VariableDescriptor descriptor = BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.VARIABLE, expression);
149            JsExpression initializer = translateInitializerForProperty(expression, context);
150            JsName name = context.getNameForDescriptor(descriptor);
151            if (descriptor.isVar() && context.bindingContext().get(BindingContext.CAPTURED_IN_CLOSURE, descriptor) != null) {
152                // well, wrap it
153                JsNameRef alias = new JsNameRef("v", new JsNameRef(name));
154                initializer = JsAstUtils.wrapValue(alias, initializer == null ? JsLiteral.NULL : initializer);
155                context.aliasingContext().registerAlias(descriptor, alias);
156            }
157    
158            return source(newVar(name, initializer), expression);
159        }
160    
161        @Override
162        @NotNull
163        public JsNode visitCallExpression(@NotNull JetCallExpression expression,
164                @NotNull TranslationContext context) {
165            return source(CallExpressionTranslator.translate(expression, null, CallType.NORMAL, context), expression);
166        }
167    
168        @Override
169        @NotNull
170        public JsNode visitIfExpression(@NotNull JetIfExpression expression, @NotNull TranslationContext context) {
171            JsExpression testExpression = translateConditionExpression(expression.getCondition(), context);
172            JetExpression thenExpression = expression.getThen();
173            JetExpression elseExpression = expression.getElse();
174            assert thenExpression != null;
175            JsNode thenNode = thenExpression.accept(this, context);
176            JsNode elseNode = elseExpression == null ? null : elseExpression.accept(this, context);
177    
178            boolean isKotlinStatement = BindingUtils.isStatement(context.bindingContext(), expression);
179            boolean canBeJsExpression = thenNode instanceof JsExpression && elseNode instanceof JsExpression;
180            if (!isKotlinStatement && canBeJsExpression) {
181                return source(new JsConditional(testExpression, convertToExpression(thenNode), convertToExpression(elseNode)), expression);
182            }
183            else {
184                JsIf ifStatement = new JsIf(testExpression, convertToStatement(thenNode), elseNode == null ? null : convertToStatement(elseNode));
185                source(ifStatement, expression);
186                if (isKotlinStatement) {
187                    return ifStatement;
188                }
189    
190                TemporaryVariable result = context.declareTemporary(null);
191                AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference());
192                context.addStatementToCurrentBlock(mutateLastExpression(ifStatement, saveResultToTemporaryMutator));
193                return result.reference();
194            }
195        }
196    
197        @Override
198        @NotNull
199        public JsExpression visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression,
200                @NotNull TranslationContext context) {
201            return ReferenceTranslator.translateSimpleName(expression, context);
202        }
203    
204        @NotNull
205        private JsStatement translateNullableExpressionAsNotNullStatement(@Nullable JetExpression nullableExpression,
206                @NotNull TranslationContext context) {
207            if (nullableExpression == null) {
208                return context.program().getEmptyStmt();
209            }
210            return convertToStatement(nullableExpression.accept(this, context));
211        }
212    
213        @NotNull
214        private JsExpression translateConditionExpression(@Nullable JetExpression expression,
215                @NotNull TranslationContext context) {
216            JsExpression jsCondition = translateNullableExpression(expression, context);
217            assert (jsCondition != null) : "Condition should not be empty";
218            return convertToExpression(jsCondition);
219        }
220    
221        @Nullable
222        private JsExpression translateNullableExpression(@Nullable JetExpression expression,
223                @NotNull TranslationContext context) {
224            if (expression == null) {
225                return null;
226            }
227            return convertToExpression(expression.accept(this, context));
228        }
229    
230        @Override
231        @NotNull
232        public JsNode visitWhileExpression(@NotNull JetWhileExpression expression, @NotNull TranslationContext context) {
233            return createWhile(new JsWhile(), expression, context);
234        }
235    
236        @Override
237        @NotNull
238        public JsNode visitDoWhileExpression(@NotNull JetDoWhileExpression expression, @NotNull TranslationContext context) {
239            return createWhile(new JsDoWhile(), expression, context);
240        }
241    
242        private JsNode createWhile(@NotNull JsWhile result, @NotNull JetWhileExpressionBase expression, @NotNull TranslationContext context) {
243            result.setCondition(translateConditionExpression(expression.getCondition(), context));
244            result.setBody(translateNullableExpressionAsNotNullStatement(expression.getBody(), context));
245            return source(result, expression);
246        }
247    
248        @Override
249        @NotNull
250        public JsNode visitStringTemplateExpression(@NotNull JetStringTemplateExpression expression,
251                @NotNull TranslationContext context) {
252            JsStringLiteral stringLiteral = resolveAsStringConstant(expression, context);
253            if (stringLiteral != null) {
254                return stringLiteral;
255            }
256            return source(resolveAsTemplate(expression, context), expression);
257        }
258    
259        @NotNull
260        private static JsNode resolveAsTemplate(@NotNull JetStringTemplateExpression expression,
261                @NotNull TranslationContext context) {
262            return StringTemplateTranslator.translate(expression, context);
263        }
264    
265        @Nullable
266        private static JsStringLiteral resolveAsStringConstant(@NotNull JetExpression expression,
267                @NotNull TranslationContext context) {
268            Object value = getCompileTimeValue(context.bindingContext(), expression);
269            if (value == null) {
270                return null;
271            }
272            assert value instanceof String : "Compile time constant template should be a String constant.";
273            String constantString = (String) value;
274            return context.program().getStringLiteral(constantString);
275        }
276    
277        @Override
278        @NotNull
279        public JsNode visitDotQualifiedExpression(@NotNull JetDotQualifiedExpression expression,
280                @NotNull TranslationContext context) {
281            return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context);
282        }
283    
284        @Override
285        @NotNull
286        public JsNode visitPrefixExpression(@NotNull JetPrefixExpression expression,
287                @NotNull TranslationContext context) {
288            return UnaryOperationTranslator.translate(expression, context);
289        }
290    
291        @Override
292        @NotNull
293        public JsNode visitPostfixExpression(@NotNull JetPostfixExpression expression,
294                @NotNull TranslationContext context) {
295            return UnaryOperationTranslator.translate(expression, context);
296        }
297    
298        @Override
299        @NotNull
300        public JsNode visitIsExpression(@NotNull JetIsExpression expression,
301                @NotNull TranslationContext context) {
302            return Translation.patternTranslator(context).translateIsExpression(expression);
303        }
304    
305        @Override
306        @NotNull
307        public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
308                @NotNull TranslationContext context) {
309            return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context);
310        }
311    
312        @Override
313        @Nullable
314        public JsNode visitWhenExpression(@NotNull JetWhenExpression expression,
315                @NotNull TranslationContext context) {
316            return WhenTranslator.translate(expression, context);
317        }
318    
319        @Override
320        @NotNull
321        public JsNode visitBinaryWithTypeRHSExpression(@NotNull JetBinaryExpressionWithTypeRHS expression,
322                @NotNull TranslationContext context) {
323            JsExpression jsExpression = Translation.translateAsExpression(expression.getLeft(), context);
324    
325            if (expression.getOperationReference().getReferencedNameElementType() != JetTokens.AS_KEYWORD)
326                return jsExpression;
327    
328            JetTypeReference type = expression.getRight();
329            assert type != null;
330            if (BindingContextUtils.getNotNull(context.bindingContext(), BindingContext.TYPE, type).isNullable())
331                return jsExpression;
332    
333            // KT-2670
334            // we actually do not care for types in js
335            return TranslationUtils.sure(jsExpression, context);
336        }
337    
338        @Override
339        @NotNull
340        public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
341                @NotNull TranslationContext context) {
342            return new JsBreak();
343        }
344    
345        @Override
346        @NotNull
347        public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
348                @NotNull TranslationContext context) {
349            return new JsContinue();
350        }
351    
352        @Override
353        @NotNull
354        public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression, @NotNull TranslationContext context) {
355            FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression.getFunctionLiteral());
356            return context.literalFunctionTranslator().translate(expression.getFunctionLiteral(), descriptor, context);
357        }
358    
359        @Override
360        @NotNull
361        public JsNode visitNamedFunction(@NotNull JetNamedFunction expression, @NotNull TranslationContext context) {
362            FunctionDescriptor descriptor = getFunctionDescriptor(context.bindingContext(), expression);
363            JsExpression alias = context.literalFunctionTranslator().translate(expression, descriptor, context);
364            JsName name = context.scope().declareFreshName(descriptor.getName().asString());
365            context.aliasingContext().registerAlias(descriptor, name.makeRef());
366            return source(new JsVars(new JsVars.JsVar(name, alias)), expression);
367        }
368    
369        @Override
370        @NotNull
371        public JsNode visitThisExpression(@NotNull JetThisExpression expression, @NotNull TranslationContext context) {
372            DeclarationDescriptor thisExpression =
373                    getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
374            assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
375            return context.getThisObject(thisExpression);
376        }
377    
378        @Override
379        @NotNull
380        public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
381                @NotNull TranslationContext context) {
382            return AccessTranslationUtils.translateAsGet(expression, context);
383        }
384    
385        @Override
386        @NotNull
387        public JsNode visitForExpression(@NotNull JetForExpression expression,
388                @NotNull TranslationContext context) {
389            return ForTranslator.translate(expression, context);
390        }
391    
392        @Override
393        @NotNull
394        public JsNode visitTryExpression(@NotNull JetTryExpression expression,
395                @NotNull TranslationContext context) {
396            return TryTranslator.translate(expression, context);
397        }
398    
399        @Override
400        @NotNull
401        public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
402                @NotNull TranslationContext context) {
403            JetExpression thrownExpression = expression.getThrownExpression();
404            assert thrownExpression != null : "Thrown expression must not be null";
405            return source(new JsThrow(translateAsExpression(thrownExpression, context)), expression);
406        }
407    
408        @Override
409        @NotNull
410        public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
411                @NotNull TranslationContext context) {
412            return ClassTranslator.generateObjectLiteral(expression.getObjectDeclaration(), context);
413        }
414    
415        @Override
416        @NotNull
417        public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
418                @NotNull TranslationContext context) {
419            JetObjectDeclarationName objectDeclarationName = getObjectDeclarationName(expression);
420            DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), objectDeclarationName);
421            JsName propertyName = context.getNameForDescriptor(descriptor);
422            JsExpression value = ClassTranslator.generateClassCreation(expression, context);
423            return source(newVar(propertyName, value), expression);
424        }
425    }