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 return ((JsExpression) jsNode).makeStmt(); 060 } 061 return (JsStatement) jsNode; 062 } 063 064 @NotNull 065 public static JsBlock convertToBlock(@NotNull JsNode jsNode) { 066 if (jsNode instanceof JsBlock) { 067 return (JsBlock) jsNode; 068 } 069 JsBlock block = new JsBlock(); 070 block.getStatements().add(convertToStatement(jsNode)); 071 return block; 072 } 073 074 @NotNull 075 private static JsStatement deBlockIfPossible(@NotNull JsStatement statement) { 076 if (statement instanceof JsBlock && ((JsBlock)statement).getStatements().size() == 1) { 077 return ((JsBlock)statement).getStatements().get(0); 078 } 079 else { 080 return statement; 081 } 082 } 083 084 @NotNull 085 public static JsIf newJsIf( 086 @NotNull JsExpression ifExpression, 087 @NotNull JsStatement thenStatement, 088 @Nullable JsStatement elseStatement 089 ) { 090 elseStatement = elseStatement != null ? deBlockIfPossible(elseStatement) : null; 091 return new JsIf(ifExpression, deBlockIfPossible(thenStatement), elseStatement); 092 } 093 094 @NotNull 095 public static JsIf newJsIf(@NotNull JsExpression ifExpression, @NotNull JsStatement thenStatement) { 096 return newJsIf(ifExpression, thenStatement, null); 097 } 098 099 @Nullable 100 public static JsExpression extractExpressionFromStatement(@Nullable JsStatement statement) { 101 return statement instanceof JsExpressionStatement ? ((JsExpressionStatement) statement).getExpression() : null; 102 } 103 104 @NotNull 105 public static JsStatement mergeStatementInBlockIfNeeded(@NotNull JsStatement statement, @NotNull JsBlock block) { 106 if (block.isEmpty()) { 107 return statement; 108 } else { 109 if (isEmptyStatement(statement)) { 110 return deBlockIfPossible(block); 111 } 112 block.getStatements().add(statement); 113 return block; 114 } 115 } 116 117 public static boolean isEmptyStatement(@NotNull JsStatement statement) { 118 return statement instanceof JsEmpty; 119 } 120 121 public static boolean isEmptyExpression(@NotNull JsExpression expression) { 122 return expression instanceof JsEmptyExpression; 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 JsBinaryOperation and(@NotNull JsExpression op1, @NotNull JsExpression op2) { 243 return new JsBinaryOperation(JsBinaryOperator.AND, op1, op2); 244 } 245 246 @NotNull 247 public static JsBinaryOperation or(@NotNull JsExpression op1, @NotNull JsExpression op2) { 248 return new JsBinaryOperation(JsBinaryOperator.OR, op1, op2); 249 } 250 251 public static void setQualifier(@NotNull JsExpression selector, @Nullable JsExpression receiver) { 252 assert (selector instanceof JsInvocation || selector instanceof JsNameRef); 253 if (selector instanceof JsInvocation) { 254 setQualifier(((JsInvocation) selector).getQualifier(), receiver); 255 return; 256 } 257 setQualifierForNameRef((JsNameRef) selector, receiver); 258 } 259 260 private static void setQualifierForNameRef(@NotNull JsNameRef selector, @Nullable JsExpression receiver) { 261 JsExpression qualifier = selector.getQualifier(); 262 if (qualifier == null) { 263 selector.setQualifier(receiver); 264 } 265 else { 266 setQualifier(qualifier, receiver); 267 } 268 } 269 270 @NotNull 271 public static JsBinaryOperation equality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) { 272 return new JsBinaryOperation(JsBinaryOperator.REF_EQ, arg1, arg2); 273 } 274 275 @NotNull 276 public static JsBinaryOperation inequality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) { 277 return new JsBinaryOperation(JsBinaryOperator.REF_NEQ, arg1, arg2); 278 } 279 280 @NotNull 281 public static JsBinaryOperation lessThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) { 282 return new JsBinaryOperation(JsBinaryOperator.LTE, arg1, arg2); 283 } 284 285 @NotNull 286 public static JsBinaryOperation lessThan(@NotNull JsExpression arg1, @NotNull JsExpression arg2) { 287 return new JsBinaryOperation(JsBinaryOperator.LT, arg1, arg2); 288 } 289 290 @NotNull 291 public static JsBinaryOperation greaterThan(@NotNull JsExpression arg1, @NotNull JsExpression arg2) { 292 return new JsBinaryOperation(JsBinaryOperator.GT, arg1, arg2); 293 } 294 295 @NotNull 296 public static JsBinaryOperation greaterThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) { 297 return new JsBinaryOperation(JsBinaryOperator.GTE, arg1, arg2); 298 } 299 300 @NotNull 301 public static JsBinaryOperation assignment(@NotNull JsExpression left, @NotNull JsExpression right) { 302 return new JsBinaryOperation(JsBinaryOperator.ASG, left, right); 303 } 304 305 @Nullable 306 public static Pair<JsExpression, JsExpression> decomposeAssignment(@NotNull JsExpression expr) { 307 if (!(expr instanceof JsBinaryOperation)) return null; 308 309 JsBinaryOperation binary = (JsBinaryOperation) expr; 310 if (binary.getOperator() != JsBinaryOperator.ASG) return null; 311 312 return new Pair<JsExpression, JsExpression>(binary.getArg1(), binary.getArg2()); 313 } 314 315 @Nullable 316 public static Pair<JsName, JsExpression> decomposeAssignmentToVariable(@NotNull JsExpression expr) { 317 Pair<JsExpression, JsExpression> assignment = decomposeAssignment(expr); 318 if (assignment == null || !(assignment.getFirst() instanceof JsNameRef)) return null; 319 320 JsNameRef nameRef = (JsNameRef) assignment.getFirst(); 321 if (nameRef.getName() == null || nameRef.getQualifier() != null) return null; 322 323 return new Pair<JsName, JsExpression>(nameRef.getName(), assignment.getSecond()); 324 } 325 326 @NotNull 327 public static JsBinaryOperation sum(@NotNull JsExpression left, @NotNull JsExpression right) { 328 return new JsBinaryOperation(JsBinaryOperator.ADD, left, right); 329 } 330 331 @NotNull 332 public static JsBinaryOperation addAssign(@NotNull JsExpression left, @NotNull JsExpression right) { 333 return new JsBinaryOperation(JsBinaryOperator.ASG_ADD, left, right); 334 } 335 336 @NotNull 337 public static JsBinaryOperation subtract(@NotNull JsExpression left, @NotNull JsExpression right) { 338 return new JsBinaryOperation(JsBinaryOperator.SUB, left, right); 339 } 340 341 @NotNull 342 public static JsBinaryOperation mul(@NotNull JsExpression left, @NotNull JsExpression right) { 343 return new JsBinaryOperation(JsBinaryOperator.MUL, left, right); 344 } 345 346 @NotNull 347 public static JsBinaryOperation div(@NotNull JsExpression left, @NotNull JsExpression right) { 348 return new JsBinaryOperation(JsBinaryOperator.DIV, left, right); 349 } 350 351 @NotNull 352 public static JsBinaryOperation mod(@NotNull JsExpression left, @NotNull JsExpression right) { 353 return new JsBinaryOperation(JsBinaryOperator.MOD, left, right); 354 } 355 356 @NotNull 357 public static JsPrefixOperation not(@NotNull JsExpression expression) { 358 return new JsPrefixOperation(JsUnaryOperator.NOT, expression); 359 } 360 361 @NotNull 362 public static JsBinaryOperation typeOfIs(@NotNull JsExpression expression, @NotNull JsStringLiteral string) { 363 return equality(new JsPrefixOperation(JsUnaryOperator.TYPEOF, expression), string); 364 } 365 366 @NotNull 367 public static JsVars newVar(@NotNull JsName name, @Nullable JsExpression expr) { 368 return new JsVars(new JsVars.JsVar(name, expr)); 369 } 370 371 public static void setArguments(@NotNull HasArguments invocation, @NotNull List<JsExpression> newArgs) { 372 List<JsExpression> arguments = invocation.getArguments(); 373 assert arguments.isEmpty() : "Arguments already set."; 374 arguments.addAll(newArgs); 375 } 376 377 public static void setArguments(@NotNull HasArguments invocation, JsExpression... arguments) { 378 setArguments(invocation, Arrays.asList(arguments)); 379 } 380 381 public static void setParameters(@NotNull JsFunction function, @NotNull List<JsParameter> newParams) { 382 List<JsParameter> parameters = function.getParameters(); 383 assert parameters.isEmpty() : "Arguments already set."; 384 parameters.addAll(newParams); 385 } 386 387 @NotNull 388 public static JsExpression newSequence(@NotNull List<JsExpression> expressions) { 389 assert !expressions.isEmpty(); 390 if (expressions.size() == 1) { 391 return expressions.get(0); 392 } 393 JsExpression result = expressions.get(expressions.size() - 1); 394 for (int i = expressions.size() - 2; i >= 0; i--) { 395 result = new JsBinaryOperation(JsBinaryOperator.COMMA, expressions.get(i), result); 396 } 397 return result; 398 } 399 400 @NotNull 401 public static JsFunction createFunctionWithEmptyBody(@NotNull JsScope parent) { 402 return new JsFunction(parent, new JsBlock(), "<anonymous>"); 403 } 404 405 @NotNull 406 public static List<JsExpression> toStringLiteralList(@NotNull List<String> strings, @NotNull JsProgram program) { 407 if (strings.isEmpty()) { 408 return Collections.emptyList(); 409 } 410 411 List<JsExpression> result = new SmartList<JsExpression>(); 412 for (String str : strings) { 413 result.add(program.getStringLiteral(str)); 414 } 415 return result; 416 } 417 418 @NotNull 419 public static JsInvocation defineProperty( 420 @NotNull String name, 421 @NotNull JsObjectLiteral value, 422 @NotNull TranslationContext context 423 ) { 424 return new JsInvocation(DEFINE_PROPERTY, JsLiteral.THIS, context.program().getStringLiteral(name), value); 425 } 426 427 @NotNull 428 public static JsStatement defineSimpleProperty(@NotNull String name, @NotNull JsExpression value) { 429 return assignment(new JsNameRef(name, JsLiteral.THIS), value).makeStmt(); 430 } 431 432 @NotNull 433 public static JsObjectLiteral createDataDescriptor(@NotNull JsExpression value, boolean writable, boolean enumerable) { 434 JsObjectLiteral dataDescriptor = new JsObjectLiteral(); 435 dataDescriptor.getPropertyInitializers().add(new JsPropertyInitializer(VALUE, value)); 436 if (writable) { 437 dataDescriptor.getPropertyInitializers().add(WRITABLE); 438 } 439 if (enumerable) { 440 dataDescriptor.getPropertyInitializers().add(ENUMERABLE); 441 } 442 return dataDescriptor; 443 } 444 445 @NotNull 446 public static JsFunction createPackage(@NotNull List<JsStatement> to, @NotNull JsObjectScope scope) { 447 JsFunction packageBlockFunction = createFunctionWithEmptyBody(scope); 448 449 JsName kotlinObjectAsParameter = packageBlockFunction.getScope().declareNameUnsafe(Namer.KOTLIN_NAME); 450 packageBlockFunction.getParameters().add(new JsParameter(kotlinObjectAsParameter)); 451 452 to.add(new JsInvocation(packageBlockFunction, Namer.kotlinObject()).makeStmt()); 453 454 return packageBlockFunction; 455 } 456 457 @NotNull 458 public static JsObjectLiteral wrapValue(@NotNull JsExpression label, @NotNull JsExpression value) { 459 return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value))); 460 } 461 462 public static JsExpression replaceRootReference(@NotNull JsNameRef fullQualifier, @NotNull JsExpression newQualifier) { 463 if (fullQualifier.getQualifier() == null) { 464 assert Namer.getRootPackageName().equals(fullQualifier.getIdent()) : "Expected root package, but: " + fullQualifier.getIdent(); 465 return newQualifier; 466 } 467 468 fullQualifier = fullQualifier.deepCopy(); 469 JsNameRef qualifier = fullQualifier; 470 while (true) { 471 JsExpression parent = qualifier.getQualifier(); 472 assert parent instanceof JsNameRef : "unexpected qualifier: " + parent + ", original: " + fullQualifier; 473 if (((JsNameRef) parent).getQualifier() == null) { 474 assert Namer.getRootPackageName().equals(((JsNameRef) parent).getIdent()); 475 qualifier.setQualifier(newQualifier); 476 return fullQualifier; 477 } 478 qualifier = (JsNameRef) parent; 479 } 480 } 481 482 @NotNull 483 public static List<JsStatement> flattenStatement(@NotNull JsStatement statement) { 484 if (statement instanceof JsBlock) { 485 return ((JsBlock) statement).getStatements(); 486 } 487 488 return new SmartList<JsStatement>(statement); 489 } 490 491 @NotNull 492 public static JsNameRef pureFqn(@NotNull String identifier, @Nullable JsExpression qualifier) { 493 JsNameRef result = new JsNameRef(identifier, qualifier); 494 MetadataProperties.setSideEffects(result, SideEffectKind.PURE); 495 return result; 496 } 497 498 @NotNull 499 public static JsNameRef pureFqn(@NotNull JsName identifier, @Nullable JsExpression qualifier) { 500 JsNameRef result = new JsNameRef(identifier, qualifier); 501 MetadataProperties.setSideEffects(result, SideEffectKind.PURE); 502 return result; 503 } 504 505 public static boolean isUndefinedExpression(JsExpression expression) { 506 if (!(expression instanceof JsUnaryOperation)) return false; 507 508 JsUnaryOperation unary = (JsUnaryOperation) expression; 509 return unary.getOperator() == JsUnaryOperator.VOID; 510 } 511 }