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