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