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
017package org.jetbrains.k2js.translate.expression;
018
019import com.google.dart.compiler.backend.js.ast.*;
020import org.jetbrains.annotations.NotNull;
021import org.jetbrains.annotations.Nullable;
022import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
023import org.jetbrains.jet.lang.psi.*;
024import org.jetbrains.jet.lang.resolve.BindingContext;
025import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
026import org.jetbrains.jet.lang.resolve.constants.NullValue;
027import org.jetbrains.k2js.translate.context.TemporaryVariable;
028import org.jetbrains.k2js.translate.context.TranslationContext;
029import org.jetbrains.k2js.translate.declaration.ClassTranslator;
030import org.jetbrains.k2js.translate.expression.foreach.ForTranslator;
031import org.jetbrains.k2js.translate.general.Translation;
032import org.jetbrains.k2js.translate.general.TranslatorVisitor;
033import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator;
034import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
035import org.jetbrains.k2js.translate.reference.*;
036import org.jetbrains.k2js.translate.utils.BindingUtils;
037import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
038
039import java.util.List;
040
041import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression;
042import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
043import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
044import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
045import static org.jetbrains.k2js.translate.utils.PsiUtils.getObjectDeclarationName;
046import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty;
047import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
048
049public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
050    @Override
051    @NotNull
052    public JsNode visitConstantExpression(@NotNull JetConstantExpression expression,
053            @NotNull TranslationContext context) {
054        CompileTimeConstant<?> compileTimeValue = context.bindingContext().get(BindingContext.COMPILE_TIME_VALUE, expression);
055        assert compileTimeValue != null;
056
057        if (compileTimeValue instanceof NullValue) {
058            return JsLiteral.NULL;
059        }
060
061        Object value = compileTimeValue.getValue();
062        if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
063            return context.program().getNumberLiteral(((Number) value).intValue());
064        }
065        else if (value instanceof Number) {
066            return context.program().getNumberLiteral(((Number) value).doubleValue());
067        }
068        else if (value instanceof Boolean) {
069            return JsLiteral.getBoolean((Boolean) value);
070        }
071
072        //TODO: test
073        if (value instanceof String) {
074            return context.program().getStringLiteral((String) value);
075        }
076        if (value instanceof Character) {
077            return context.program().getStringLiteral(value.toString());
078        }
079        throw new AssertionError(message(expression, "Unsupported constant expression"));
080    }
081
082    @Override
083    @NotNull
084    public JsNode visitBlockExpression(@NotNull JetBlockExpression jetBlock, @NotNull TranslationContext context) {
085        List<JetElement> statements = jetBlock.getStatements();
086        JsBlock jsBlock = new JsBlock();
087        TranslationContext blockContext = context.innerBlock(jsBlock);
088        for (JetElement statement : statements) {
089            assert statement instanceof JetExpression : "Elements in JetBlockExpression " +
090                                                        "should be of type JetExpression";
091            JsNode jsNode = statement.accept(this, blockContext);
092            if (jsNode != null) {
093                jsBlock.getStatements().add(convertToStatement(jsNode));
094            }
095        }
096        return jsBlock;
097    }
098
099    @Override
100    @NotNull
101    public JsNode visitReturnExpression(@NotNull JetReturnExpression jetReturnExpression,
102            @NotNull TranslationContext context) {
103        JetExpression returnedExpression = jetReturnExpression.getReturnedExpression();
104        if (returnedExpression != null) {
105            JsExpression jsExpression = translateAsExpression(returnedExpression, context);
106            return new JsReturn(jsExpression);
107        }
108        return new JsReturn();
109    }
110
111    @Override
112    @NotNull
113    public JsNode visitParenthesizedExpression(@NotNull JetParenthesizedExpression expression,
114            @NotNull TranslationContext context) {
115        JetExpression expressionInside = expression.getExpression();
116        if (expressionInside != null) {
117            return expressionInside.accept(this, context);
118        }
119        return context.program().getEmptyStmt();
120    }
121
122    @Override
123    @NotNull
124    public JsNode visitBinaryExpression(@NotNull JetBinaryExpression expression,
125            @NotNull TranslationContext context) {
126        return BinaryOperationTranslator.translate(expression, context);
127    }
128
129    @Override
130    @NotNull
131    // assume it is a local variable declaration
132    public JsNode visitProperty(@NotNull JetProperty expression, @NotNull TranslationContext context) {
133        DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), expression);
134        JsName jsPropertyName = context.getNameForDescriptor(descriptor);
135        JsExpression jsInitExpression = translateInitializerForProperty(expression, context);
136        return newVar(jsPropertyName, jsInitExpression);
137    }
138
139    @Override
140    @NotNull
141    public JsNode visitCallExpression(@NotNull JetCallExpression expression,
142            @NotNull TranslationContext context) {
143        return CallExpressionTranslator.translate(expression, null, CallType.NORMAL, context);
144    }
145
146    @Override
147    @NotNull
148    public JsNode visitIfExpression(@NotNull JetIfExpression expression, @NotNull TranslationContext context) {
149        JsExpression testExpression = translateConditionExpression(expression.getCondition(), context);
150        JetExpression thenExpression = expression.getThen();
151        JetExpression elseExpression = expression.getElse();
152        assert thenExpression != null;
153        JsNode thenNode = thenExpression.accept(this, context);
154        JsNode elseNode = elseExpression == null ? null : elseExpression.accept(this, context);
155
156        boolean isKotlinStatement = BindingUtils.isStatement(context.bindingContext(), expression);
157        boolean canBeJsExpression = thenNode instanceof JsExpression && elseNode instanceof JsExpression;
158        if (!isKotlinStatement && canBeJsExpression) {
159            return new JsConditional(testExpression, convertToExpression(thenNode), convertToExpression(elseNode));
160        }
161        else {
162            JsIf ifStatement = new JsIf(testExpression, convertToStatement(thenNode), elseNode == null ? null : convertToStatement(elseNode));
163            if (isKotlinStatement) {
164                return ifStatement;
165            }
166
167            TemporaryVariable result = context.declareTemporary(null);
168            AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference());
169            context.addStatementToCurrentBlock(mutateLastExpression(ifStatement, saveResultToTemporaryMutator));
170            return result.reference();
171        }
172    }
173
174    @Override
175    @NotNull
176    public JsNode visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression,
177            @NotNull TranslationContext context) {
178        return ReferenceTranslator.translateSimpleName(expression, context);
179    }
180
181
182    @NotNull
183    private JsStatement translateNullableExpressionAsNotNullStatement(@Nullable JetExpression nullableExpression,
184            @NotNull TranslationContext context) {
185        if (nullableExpression == null) {
186            return context.program().getEmptyStmt();
187        }
188        return convertToStatement(nullableExpression.accept(this, context));
189    }
190
191    @NotNull
192    private JsExpression translateConditionExpression(@Nullable JetExpression expression,
193            @NotNull TranslationContext context) {
194        JsExpression jsCondition = translateNullableExpression(expression, context);
195        assert (jsCondition != null) : "Condition should not be empty";
196        return convertToExpression(jsCondition);
197    }
198
199    @Nullable
200    private JsExpression translateNullableExpression(@Nullable JetExpression expression,
201            @NotNull TranslationContext context) {
202        if (expression == null) {
203            return null;
204        }
205        return convertToExpression(expression.accept(this, context));
206    }
207
208    @Override
209    @NotNull
210    public JsNode visitWhileExpression(@NotNull JetWhileExpression expression, @NotNull TranslationContext context) {
211        return createWhile(new JsWhile(), expression, context);
212    }
213
214    @Override
215    @NotNull
216    public JsNode visitDoWhileExpression(@NotNull JetDoWhileExpression expression, @NotNull TranslationContext context) {
217        return createWhile(new JsDoWhile(), expression, context);
218    }
219
220    private JsNode createWhile(@NotNull JsWhile result, @NotNull JetWhileExpressionBase expression, @NotNull TranslationContext context) {
221        result.setCondition(translateConditionExpression(expression.getCondition(), context));
222        result.setBody(translateNullableExpressionAsNotNullStatement(expression.getBody(), context));
223        return result;
224    }
225
226    @Override
227    @NotNull
228    public JsNode visitStringTemplateExpression(@NotNull JetStringTemplateExpression expression,
229            @NotNull TranslationContext context) {
230        JsStringLiteral stringLiteral = resolveAsStringConstant(expression, context);
231        if (stringLiteral != null) {
232            return stringLiteral;
233        }
234        return resolveAsTemplate(expression, context);
235    }
236
237    @NotNull
238    private static JsNode resolveAsTemplate(@NotNull JetStringTemplateExpression expression,
239            @NotNull TranslationContext context) {
240        return StringTemplateTranslator.translate(expression, context);
241    }
242
243    @Nullable
244    private static JsStringLiteral resolveAsStringConstant(@NotNull JetExpression expression,
245            @NotNull TranslationContext context) {
246        Object value = getCompileTimeValue(context.bindingContext(), expression);
247        if (value == null) {
248            return null;
249        }
250        assert value instanceof String : "Compile time constant template should be a String constant.";
251        String constantString = (String) value;
252        return context.program().getStringLiteral(constantString);
253    }
254
255    @Override
256    @NotNull
257    public JsNode visitDotQualifiedExpression(@NotNull JetDotQualifiedExpression expression,
258            @NotNull TranslationContext context) {
259        return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context);
260    }
261
262    @Override
263    @NotNull
264    public JsNode visitPrefixExpression(@NotNull JetPrefixExpression expression,
265            @NotNull TranslationContext context) {
266        return UnaryOperationTranslator.translate(expression, context);
267    }
268
269    @Override
270    @NotNull
271    public JsNode visitPostfixExpression(@NotNull JetPostfixExpression expression,
272            @NotNull TranslationContext context) {
273        return UnaryOperationTranslator.translate(expression, context);
274    }
275
276    @Override
277    @NotNull
278    public JsNode visitIsExpression(@NotNull JetIsExpression expression,
279            @NotNull TranslationContext context) {
280        return Translation.patternTranslator(context).translateIsExpression(expression);
281    }
282
283    @Override
284    @NotNull
285    public JsNode visitSafeQualifiedExpression(@NotNull JetSafeQualifiedExpression expression,
286            @NotNull TranslationContext context) {
287        return QualifiedExpressionTranslator.translateQualifiedExpression(expression, context);
288    }
289
290    @Override
291    @Nullable
292    public JsNode visitWhenExpression(@NotNull JetWhenExpression expression,
293            @NotNull TranslationContext context) {
294        return Translation.translateWhenExpression(expression, context);
295    }
296
297
298    @Override
299    @NotNull
300    public JsNode visitBinaryWithTypeRHSExpression(@NotNull JetBinaryExpressionWithTypeRHS expression,
301            @NotNull TranslationContext context) {
302        // we actually do not care for types in js
303        return Translation.translateExpression(expression.getLeft(), context);
304    }
305
306    @Override
307    @NotNull
308    public JsNode visitBreakExpression(@NotNull JetBreakExpression expression,
309            @NotNull TranslationContext context) {
310        return new JsBreak();
311    }
312
313    @Override
314    @NotNull
315    public JsNode visitContinueExpression(@NotNull JetContinueExpression expression,
316            @NotNull TranslationContext context) {
317        return new JsContinue();
318    }
319
320    @Override
321    @NotNull
322    public JsNode visitFunctionLiteralExpression(@NotNull JetFunctionLiteralExpression expression,
323                                                 @NotNull TranslationContext context) {
324        return context.literalFunctionTranslator().translate(expression);
325    }
326
327    @Override
328    @NotNull
329    public JsNode visitThisExpression(@NotNull JetThisExpression expression,
330            @NotNull TranslationContext context) {
331        DeclarationDescriptor thisExpression =
332                getDescriptorForReferenceExpression(context.bindingContext(), expression.getInstanceReference());
333        assert thisExpression != null : "This expression must reference a descriptor: " + expression.getText();
334        return context.getThisObject(thisExpression);
335    }
336
337    @Override
338    @NotNull
339    public JsNode visitArrayAccessExpression(@NotNull JetArrayAccessExpression expression,
340            @NotNull TranslationContext context) {
341        return AccessTranslationUtils.translateAsGet(expression, context);
342    }
343
344    @Override
345    @NotNull
346    public JsNode visitForExpression(@NotNull JetForExpression expression,
347            @NotNull TranslationContext context) {
348        return ForTranslator.translate(expression, context);
349    }
350
351    @Override
352    @NotNull
353    public JsNode visitTryExpression(@NotNull JetTryExpression expression,
354            @NotNull TranslationContext context) {
355        return TryTranslator.translate(expression, context);
356    }
357
358    @Override
359    @NotNull
360    public JsNode visitThrowExpression(@NotNull JetThrowExpression expression,
361            @NotNull TranslationContext context) {
362        JetExpression thrownExpression = expression.getThrownExpression();
363        assert thrownExpression != null : "Thrown expression must not be null";
364        return new JsThrow(translateAsExpression(thrownExpression, context));
365    }
366
367    @Override
368    @NotNull
369    public JsNode visitObjectLiteralExpression(@NotNull JetObjectLiteralExpression expression,
370            @NotNull TranslationContext context) {
371        return ClassTranslator.generateObjectLiteral(expression, context);
372    }
373
374    @Override
375    @NotNull
376    public JsNode visitObjectDeclaration(@NotNull JetObjectDeclaration expression,
377            @NotNull TranslationContext context) {
378        JetObjectDeclarationName objectDeclarationName = getObjectDeclarationName(expression);
379        DeclarationDescriptor descriptor = getDescriptorForElement(context.bindingContext(), objectDeclarationName);
380        JsName propertyName = context.getNameForDescriptor(descriptor);
381        JsExpression value = ClassTranslator.generateClassCreation(expression, context);
382        return newVar(propertyName, value);
383    }
384
385    @Override
386    @NotNull
387    public JsNode visitNamedFunction(@NotNull JetNamedFunction function,
388            @NotNull TranslationContext context) {
389        return FunctionTranslator.newInstance(function, context).translateAsLocalFunction();
390    }
391}