001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.k2js.translate.expression;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
023    import org.jetbrains.jet.lang.psi.*;
024    import org.jetbrains.jet.lang.resolve.BindingContext;
025    import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
026    import org.jetbrains.jet.lang.resolve.constants.NullValue;
027    import org.jetbrains.k2js.translate.context.TemporaryVariable;
028    import org.jetbrains.k2js.translate.context.TranslationContext;
029    import org.jetbrains.k2js.translate.declaration.ClassTranslator;
030    import org.jetbrains.k2js.translate.expression.foreach.ForTranslator;
031    import org.jetbrains.k2js.translate.general.Translation;
032    import org.jetbrains.k2js.translate.general.TranslatorVisitor;
033    import org.jetbrains.k2js.translate.operation.BinaryOperationTranslator;
034    import org.jetbrains.k2js.translate.operation.UnaryOperationTranslator;
035    import org.jetbrains.k2js.translate.reference.*;
036    import org.jetbrains.k2js.translate.utils.BindingUtils;
037    import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
038    
039    import java.util.List;
040    
041    import static org.jetbrains.k2js.translate.general.Translation.translateAsExpression;
042    import static org.jetbrains.k2js.translate.utils.BindingUtils.*;
043    import static org.jetbrains.k2js.translate.utils.ErrorReportingUtils.message;
044    import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
045    import static org.jetbrains.k2js.translate.utils.PsiUtils.getObjectDeclarationName;
046    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateInitializerForProperty;
047    import static org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator.mutateLastExpression;
048    
049    public 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    }