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