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