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