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