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