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