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