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