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