001 /*
002 * Copyright 2010-2015 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.kotlin.js.translate.utils;
018
019 import com.google.dart.compiler.backend.js.ast.*;
020 import com.google.dart.compiler.backend.js.ast.metadata.MetadataProperties;
021 import com.google.dart.compiler.backend.js.ast.metadata.SideEffectKind;
022 import com.intellij.util.SmartList;
023 import kotlin.Pair;
024 import org.jetbrains.annotations.NotNull;
025 import org.jetbrains.annotations.Nullable;
026 import org.jetbrains.kotlin.js.translate.context.Namer;
027 import org.jetbrains.kotlin.js.translate.context.TranslationContext;
028 import org.jetbrains.kotlin.types.expressions.OperatorConventions;
029 import org.jetbrains.kotlin.util.OperatorNameConventions;
030
031 import java.util.Arrays;
032 import java.util.Collections;
033 import java.util.List;
034
035 public final class JsAstUtils {
036 private static final JsNameRef DEFINE_PROPERTY = pureFqn("defineProperty", null);
037 public static final JsNameRef CREATE_OBJECT = pureFqn("create", null);
038
039 private static final JsNameRef VALUE = new JsNameRef("value");
040 private static final JsPropertyInitializer WRITABLE = new JsPropertyInitializer(pureFqn("writable", null), JsLiteral.TRUE);
041 private static final JsPropertyInitializer ENUMERABLE = new JsPropertyInitializer(pureFqn("enumerable", null), JsLiteral.TRUE);
042
043 public static final String LENDS_JS_DOC_TAG = "lends";
044
045 static {
046 JsNameRef globalObjectReference = new JsNameRef("Object");
047 DEFINE_PROPERTY.setQualifier(globalObjectReference);
048 CREATE_OBJECT.setQualifier(globalObjectReference);
049 }
050
051 private JsAstUtils() {
052 }
053
054 @NotNull
055 public static JsStatement convertToStatement(@NotNull JsNode jsNode) {
056 assert (jsNode instanceof JsExpression) || (jsNode instanceof JsStatement)
057 : "Unexpected node of type: " + jsNode.getClass().toString();
058 if (jsNode instanceof JsExpression) {
059 JsExpressionStatement statement = new JsExpressionStatement((JsExpression) jsNode);
060 if (jsNode instanceof JsNullLiteral) {
061 MetadataProperties.setSynthetic(statement, true);
062 }
063 return statement;
064 }
065 return (JsStatement) jsNode;
066 }
067
068 @NotNull
069 public static JsBlock convertToBlock(@NotNull JsNode jsNode) {
070 if (jsNode instanceof JsBlock) {
071 return (JsBlock) jsNode;
072 }
073 JsBlock block = new JsBlock();
074 block.getStatements().add(convertToStatement(jsNode));
075 return block;
076 }
077
078 @NotNull
079 private static JsStatement deBlockIfPossible(@NotNull JsStatement statement) {
080 if (statement instanceof JsBlock && ((JsBlock)statement).getStatements().size() == 1) {
081 return ((JsBlock)statement).getStatements().get(0);
082 }
083 else {
084 return statement;
085 }
086 }
087
088 @NotNull
089 public static JsIf newJsIf(
090 @NotNull JsExpression ifExpression,
091 @NotNull JsStatement thenStatement,
092 @Nullable JsStatement elseStatement
093 ) {
094 elseStatement = elseStatement != null ? deBlockIfPossible(elseStatement) : null;
095 return new JsIf(ifExpression, deBlockIfPossible(thenStatement), elseStatement);
096 }
097
098 @NotNull
099 public static JsIf newJsIf(@NotNull JsExpression ifExpression, @NotNull JsStatement thenStatement) {
100 return newJsIf(ifExpression, thenStatement, null);
101 }
102
103 @Nullable
104 public static JsExpression extractExpressionFromStatement(@Nullable JsStatement statement) {
105 return statement instanceof JsExpressionStatement ? ((JsExpressionStatement) statement).getExpression() : null;
106 }
107
108 @NotNull
109 public static JsStatement mergeStatementInBlockIfNeeded(@NotNull JsStatement statement, @NotNull JsBlock block) {
110 if (block.isEmpty()) {
111 return statement;
112 } else {
113 if (isEmptyStatement(statement)) {
114 return deBlockIfPossible(block);
115 }
116 block.getStatements().add(statement);
117 return block;
118 }
119 }
120
121 public static boolean isEmptyStatement(@NotNull JsStatement statement) {
122 return statement instanceof JsEmpty;
123 }
124
125 @NotNull
126 public static JsInvocation invokeKotlinFunction(@NotNull String name, @NotNull JsExpression... argument) {
127 return invokeMethod(Namer.kotlinObject(), name, argument);
128 }
129
130 @NotNull
131 public static JsInvocation invokeMethod(@NotNull JsExpression thisObject, @NotNull String name, @NotNull JsExpression... arguments) {
132 return new JsInvocation(pureFqn(name, thisObject), arguments);
133 }
134
135 @NotNull
136 public static JsExpression toInt32(@NotNull JsExpression expression) {
137 return new JsBinaryOperation(JsBinaryOperator.BIT_OR, expression, JsNumberLiteral.ZERO);
138 }
139
140 @NotNull
141 public static JsExpression charToInt(@NotNull JsExpression expression) {
142 return invokeMethod(expression, "charCodeAt", JsNumberLiteral.ZERO);
143 }
144
145 @NotNull
146 public static JsExpression toShort(@NotNull JsExpression expression) {
147 return invokeKotlinFunction(OperatorConventions.SHORT.getIdentifier(), expression);
148 }
149
150 @NotNull
151 public static JsExpression toByte(@NotNull JsExpression expression) {
152 return invokeKotlinFunction(OperatorConventions.BYTE.getIdentifier(), expression);
153 }
154
155 @NotNull
156 public static JsExpression toLong(@NotNull JsExpression expression) {
157 return invokeKotlinFunction(OperatorConventions.LONG.getIdentifier(), expression);
158 }
159
160 @NotNull
161 public static JsExpression toChar(@NotNull JsExpression expression) {
162 return invokeKotlinFunction(OperatorConventions.CHAR.getIdentifier(), expression);
163 }
164
165 @NotNull
166 public static JsExpression compareTo(@NotNull JsExpression left, @NotNull JsExpression right) {
167 return invokeKotlinFunction(OperatorNameConventions.COMPARE_TO.getIdentifier(), left, right);
168 }
169
170 @NotNull
171 public static JsExpression primitiveCompareTo(@NotNull JsExpression left, @NotNull JsExpression right) {
172 return invokeKotlinFunction(Namer.PRIMITIVE_COMPARE_TO, left, right);
173 }
174
175 @NotNull
176 private static JsExpression rangeTo(@NotNull String rangeClassName, @NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) {
177 JsNameRef expr = pureFqn(rangeClassName, Namer.kotlinObject());
178 JsNew numberRangeConstructorInvocation = new JsNew(expr);
179 setArguments(numberRangeConstructorInvocation, rangeStart, rangeEnd);
180 return numberRangeConstructorInvocation;
181 }
182
183 @NotNull
184 public static JsExpression numberRangeTo(@NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) {
185 return rangeTo(Namer.NUMBER_RANGE, rangeStart, rangeEnd);
186 }
187
188 @NotNull
189 public static JsExpression charRangeTo(@NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) {
190 return rangeTo(Namer.CHAR_RANGE, rangeStart, rangeEnd);
191 }
192
193 public static JsExpression newLong(long value, @NotNull TranslationContext context) {
194 if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
195 int low = (int) value;
196 int high = (int) (value >> 32);
197 List<JsExpression> args = new SmartList<JsExpression>();
198 args.add(context.program().getNumberLiteral(low));
199 args.add(context.program().getNumberLiteral(high));
200 return new JsNew(Namer.kotlinLong(), args);
201 }
202 else {
203 if (value == 0) {
204 return new JsNameRef(Namer.LONG_ZERO, Namer.kotlinLong());
205 }
206 else if (value == 1) {
207 return new JsNameRef(Namer.LONG_ONE, Namer.kotlinLong());
208 }
209 else if (value == -1) {
210 return new JsNameRef(Namer.LONG_NEG_ONE, Namer.kotlinLong());
211 }
212 return longFromInt(context.program().getNumberLiteral((int) value));
213 }
214 }
215
216 @NotNull
217 public static JsExpression longFromInt(@NotNull JsExpression expression) {
218 return invokeMethod(Namer.kotlinLong(), Namer.LONG_FROM_INT, expression);
219 }
220
221 @NotNull
222 public static JsExpression longFromNumber(@NotNull JsExpression expression) {
223 return invokeMethod(Namer.kotlinLong(), Namer.LONG_FROM_NUMBER, expression);
224 }
225
226 @NotNull
227 public static JsExpression equalsForObject(@NotNull JsExpression left, @NotNull JsExpression right) {
228 return invokeMethod(left, Namer.EQUALS_METHOD_NAME, right);
229 }
230
231 @NotNull
232 public static JsExpression compareForObject(@NotNull JsExpression left, @NotNull JsExpression right) {
233 return invokeMethod(left, Namer.COMPARE_TO_METHOD_NAME, right);
234 }
235
236 @NotNull
237 public static JsPrefixOperation negated(@NotNull JsExpression expression) {
238 return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
239 }
240
241 @NotNull
242 public static JsExpression negatedOptimized(@NotNull JsExpression expression) {
243 if (expression instanceof JsUnaryOperation) {
244 JsUnaryOperation unary = (JsUnaryOperation) expression;
245 if (unary.getOperator() == JsUnaryOperator.NOT) return unary.getArg();
246 }
247 else if (expression instanceof JsBinaryOperation) {
248 JsBinaryOperation binary = (JsBinaryOperation) expression;
249 switch (binary.getOperator()) {
250 case AND:
251 return or(negatedOptimized(binary.getArg1()), negatedOptimized(binary.getArg2()));
252 case OR:
253 return and(negatedOptimized(binary.getArg1()), negatedOptimized(binary.getArg2()));
254 case EQ:
255 return new JsBinaryOperation(JsBinaryOperator.NEQ, binary.getArg1(), binary.getArg2());
256 case NEQ:
257 return new JsBinaryOperation(JsBinaryOperator.EQ, binary.getArg1(), binary.getArg2());
258 case REF_EQ:
259 return inequality(binary.getArg1(), binary.getArg2());
260 case REF_NEQ:
261 return equality(binary.getArg1(), binary.getArg2());
262 case LT:
263 return greaterThanEq(binary.getArg1(), binary.getArg2());
264 case LTE:
265 return greaterThan(binary.getArg1(), binary.getArg2());
266 case GT:
267 return lessThanEq(binary.getArg1(), binary.getArg2());
268 case GTE:
269 return lessThan(binary.getArg1(), binary.getArg2());
270 default:
271 break;
272 }
273 }
274
275 return negated(expression);
276 }
277
278 @NotNull
279 public static JsBinaryOperation and(@NotNull JsExpression op1, @NotNull JsExpression op2) {
280 return new JsBinaryOperation(JsBinaryOperator.AND, op1, op2);
281 }
282
283 @NotNull
284 public static JsBinaryOperation or(@NotNull JsExpression op1, @NotNull JsExpression op2) {
285 return new JsBinaryOperation(JsBinaryOperator.OR, op1, op2);
286 }
287
288 public static void setQualifier(@NotNull JsExpression selector, @Nullable JsExpression receiver) {
289 assert (selector instanceof JsInvocation || selector instanceof JsNameRef);
290 if (selector instanceof JsInvocation) {
291 setQualifier(((JsInvocation) selector).getQualifier(), receiver);
292 return;
293 }
294 setQualifierForNameRef((JsNameRef) selector, receiver);
295 }
296
297 private static void setQualifierForNameRef(@NotNull JsNameRef selector, @Nullable JsExpression receiver) {
298 JsExpression qualifier = selector.getQualifier();
299 if (qualifier == null) {
300 selector.setQualifier(receiver);
301 }
302 else {
303 setQualifier(qualifier, receiver);
304 }
305 }
306
307 @NotNull
308 public static JsBinaryOperation equality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
309 return new JsBinaryOperation(JsBinaryOperator.REF_EQ, arg1, arg2);
310 }
311
312 @NotNull
313 public static JsBinaryOperation inequality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
314 return new JsBinaryOperation(JsBinaryOperator.REF_NEQ, arg1, arg2);
315 }
316
317 @NotNull
318 public static JsBinaryOperation lessThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
319 return new JsBinaryOperation(JsBinaryOperator.LTE, arg1, arg2);
320 }
321
322 @NotNull
323 public static JsBinaryOperation lessThan(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
324 return new JsBinaryOperation(JsBinaryOperator.LT, arg1, arg2);
325 }
326
327 @NotNull
328 public static JsBinaryOperation greaterThan(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
329 return new JsBinaryOperation(JsBinaryOperator.GT, arg1, arg2);
330 }
331
332 @NotNull
333 public static JsBinaryOperation greaterThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
334 return new JsBinaryOperation(JsBinaryOperator.GTE, arg1, arg2);
335 }
336
337 @NotNull
338 public static JsBinaryOperation assignment(@NotNull JsExpression left, @NotNull JsExpression right) {
339 return new JsBinaryOperation(JsBinaryOperator.ASG, left, right);
340 }
341
342 public static JsStatement asSyntheticStatement(@NotNull JsExpression expression) {
343 JsExpressionStatement statement = new JsExpressionStatement(expression);
344 MetadataProperties.setSynthetic(statement, true);
345 return statement;
346 }
347
348 @Nullable
349 public static Pair<JsExpression, JsExpression> decomposeAssignment(@NotNull JsExpression expr) {
350 if (!(expr instanceof JsBinaryOperation)) return null;
351
352 JsBinaryOperation binary = (JsBinaryOperation) expr;
353 if (binary.getOperator() != JsBinaryOperator.ASG) return null;
354
355 return new Pair<JsExpression, JsExpression>(binary.getArg1(), binary.getArg2());
356 }
357
358 @Nullable
359 public static Pair<JsName, JsExpression> decomposeAssignmentToVariable(@NotNull JsExpression expr) {
360 Pair<JsExpression, JsExpression> assignment = decomposeAssignment(expr);
361 if (assignment == null || !(assignment.getFirst() instanceof JsNameRef)) return null;
362
363 JsNameRef nameRef = (JsNameRef) assignment.getFirst();
364 if (nameRef.getName() == null || nameRef.getQualifier() != null) return null;
365
366 return new Pair<JsName, JsExpression>(nameRef.getName(), assignment.getSecond());
367 }
368
369 @NotNull
370 public static JsBinaryOperation sum(@NotNull JsExpression left, @NotNull JsExpression right) {
371 return new JsBinaryOperation(JsBinaryOperator.ADD, left, right);
372 }
373
374 @NotNull
375 public static JsBinaryOperation addAssign(@NotNull JsExpression left, @NotNull JsExpression right) {
376 return new JsBinaryOperation(JsBinaryOperator.ASG_ADD, left, right);
377 }
378
379 @NotNull
380 public static JsBinaryOperation subtract(@NotNull JsExpression left, @NotNull JsExpression right) {
381 return new JsBinaryOperation(JsBinaryOperator.SUB, left, right);
382 }
383
384 @NotNull
385 public static JsBinaryOperation mul(@NotNull JsExpression left, @NotNull JsExpression right) {
386 return new JsBinaryOperation(JsBinaryOperator.MUL, left, right);
387 }
388
389 @NotNull
390 public static JsBinaryOperation div(@NotNull JsExpression left, @NotNull JsExpression right) {
391 return new JsBinaryOperation(JsBinaryOperator.DIV, left, right);
392 }
393
394 @NotNull
395 public static JsBinaryOperation mod(@NotNull JsExpression left, @NotNull JsExpression right) {
396 return new JsBinaryOperation(JsBinaryOperator.MOD, left, right);
397 }
398
399 @NotNull
400 public static JsPrefixOperation not(@NotNull JsExpression expression) {
401 return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
402 }
403
404 @NotNull
405 public static JsBinaryOperation typeOfIs(@NotNull JsExpression expression, @NotNull JsStringLiteral string) {
406 return equality(new JsPrefixOperation(JsUnaryOperator.TYPEOF, expression), string);
407 }
408
409 @NotNull
410 public static JsVars newVar(@NotNull JsName name, @Nullable JsExpression expr) {
411 return new JsVars(new JsVars.JsVar(name, expr));
412 }
413
414 public static void setArguments(@NotNull HasArguments invocation, @NotNull List<JsExpression> newArgs) {
415 List<JsExpression> arguments = invocation.getArguments();
416 assert arguments.isEmpty() : "Arguments already set.";
417 arguments.addAll(newArgs);
418 }
419
420 public static void setArguments(@NotNull HasArguments invocation, JsExpression... arguments) {
421 setArguments(invocation, Arrays.asList(arguments));
422 }
423
424 public static void setParameters(@NotNull JsFunction function, @NotNull List<JsParameter> newParams) {
425 List<JsParameter> parameters = function.getParameters();
426 assert parameters.isEmpty() : "Arguments already set.";
427 parameters.addAll(newParams);
428 }
429
430 @NotNull
431 public static JsExpression newSequence(@NotNull List<JsExpression> expressions) {
432 assert !expressions.isEmpty();
433 if (expressions.size() == 1) {
434 return expressions.get(0);
435 }
436 JsExpression result = expressions.get(expressions.size() - 1);
437 for (int i = expressions.size() - 2; i >= 0; i--) {
438 result = new JsBinaryOperation(JsBinaryOperator.COMMA, expressions.get(i), result);
439 }
440 return result;
441 }
442
443 @NotNull
444 public static JsFunction createFunctionWithEmptyBody(@NotNull JsScope parent) {
445 return new JsFunction(parent, new JsBlock(), "<anonymous>");
446 }
447
448 @NotNull
449 public static List<JsExpression> toStringLiteralList(@NotNull List<String> strings, @NotNull JsProgram program) {
450 if (strings.isEmpty()) {
451 return Collections.emptyList();
452 }
453
454 List<JsExpression> result = new SmartList<JsExpression>();
455 for (String str : strings) {
456 result.add(program.getStringLiteral(str));
457 }
458 return result;
459 }
460
461 @NotNull
462 public static JsInvocation defineProperty(
463 @NotNull String name,
464 @NotNull JsObjectLiteral value,
465 @NotNull TranslationContext context
466 ) {
467 return new JsInvocation(DEFINE_PROPERTY, JsLiteral.THIS, context.program().getStringLiteral(name), value);
468 }
469
470 @NotNull
471 public static JsStatement defineSimpleProperty(@NotNull String name, @NotNull JsExpression value) {
472 return assignment(new JsNameRef(name, JsLiteral.THIS), value).makeStmt();
473 }
474
475 @NotNull
476 public static JsObjectLiteral createDataDescriptor(@NotNull JsExpression value, boolean writable, boolean enumerable) {
477 JsObjectLiteral dataDescriptor = new JsObjectLiteral();
478 dataDescriptor.getPropertyInitializers().add(new JsPropertyInitializer(VALUE, value));
479 if (writable) {
480 dataDescriptor.getPropertyInitializers().add(WRITABLE);
481 }
482 if (enumerable) {
483 dataDescriptor.getPropertyInitializers().add(ENUMERABLE);
484 }
485 return dataDescriptor;
486 }
487
488 @NotNull
489 public static JsObjectLiteral wrapValue(@NotNull JsExpression label, @NotNull JsExpression value) {
490 return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value)));
491 }
492
493 public static JsExpression replaceRootReference(@NotNull JsNameRef fullQualifier, @NotNull JsExpression newQualifier) {
494 if (fullQualifier.getQualifier() == null) {
495 assert Namer.getRootPackageName().equals(fullQualifier.getIdent()) : "Expected root package, but: " + fullQualifier.getIdent();
496 return newQualifier;
497 }
498
499 fullQualifier = fullQualifier.deepCopy();
500 JsNameRef qualifier = fullQualifier;
501 while (true) {
502 JsExpression parent = qualifier.getQualifier();
503 assert parent instanceof JsNameRef : "unexpected qualifier: " + parent + ", original: " + fullQualifier;
504 if (((JsNameRef) parent).getQualifier() == null) {
505 assert Namer.getRootPackageName().equals(((JsNameRef) parent).getIdent());
506 qualifier.setQualifier(newQualifier);
507 return fullQualifier;
508 }
509 qualifier = (JsNameRef) parent;
510 }
511 }
512
513 @NotNull
514 public static List<JsStatement> flattenStatement(@NotNull JsStatement statement) {
515 if (statement instanceof JsBlock) {
516 return ((JsBlock) statement).getStatements();
517 }
518
519 return new SmartList<JsStatement>(statement);
520 }
521
522 @NotNull
523 public static JsNameRef pureFqn(@NotNull String identifier, @Nullable JsExpression qualifier) {
524 JsNameRef result = new JsNameRef(identifier, qualifier);
525 MetadataProperties.setSideEffects(result, SideEffectKind.PURE);
526 return result;
527 }
528
529 @NotNull
530 public static JsNameRef pureFqn(@NotNull JsName identifier, @Nullable JsExpression qualifier) {
531 JsNameRef result = new JsNameRef(identifier, qualifier);
532 MetadataProperties.setSideEffects(result, SideEffectKind.PURE);
533 return result;
534 }
535
536 @NotNull
537 public static JsInvocation invokeBind(@NotNull JsExpression receiver, @NotNull JsExpression method) {
538 return invokeMethod(method, "bind", receiver);
539 }
540
541 public static boolean isUndefinedExpression(JsExpression expression) {
542 if (!(expression instanceof JsUnaryOperation)) return false;
543
544 JsUnaryOperation unary = (JsUnaryOperation) expression;
545 return unary.getOperator() == JsUnaryOperator.VOID;
546 }
547 }