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