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 private static JsExpression rangeTo(@NotNull String rangeClassName, @NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) {
173 JsNameRef expr = new JsNameRef(rangeClassName, Namer.KOTLIN_NAME);
174 JsNew numberRangeConstructorInvocation = new JsNew(expr);
175 setArguments(numberRangeConstructorInvocation, rangeStart, rangeEnd);
176 return numberRangeConstructorInvocation;
177 }
178
179 @NotNull
180 public static JsExpression numberRangeTo(@NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) {
181 return rangeTo(Namer.NUMBER_RANGE, rangeStart, rangeEnd);
182 }
183
184 @NotNull
185 public static JsExpression charRangeTo(@NotNull JsExpression rangeStart, @NotNull JsExpression rangeEnd) {
186 return rangeTo(Namer.CHAR_RANGE, rangeStart, rangeEnd);
187 }
188
189 public static JsExpression newLong(long value, @NotNull TranslationContext context) {
190 if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
191 int low = (int) value;
192 int high = (int) (value >> 32);
193 List<JsExpression> args = new SmartList<JsExpression>();
194 args.add(context.program().getNumberLiteral(low));
195 args.add(context.program().getNumberLiteral(high));
196 return new JsNew(Namer.KOTLIN_LONG_NAME_REF, args);
197 }
198 else {
199 if (value == 0) {
200 return new JsNameRef(Namer.LONG_ZERO, Namer.KOTLIN_LONG_NAME_REF);
201 }
202 else if (value == 1) {
203 return new JsNameRef(Namer.LONG_ONE, Namer.KOTLIN_LONG_NAME_REF);
204 }
205 else if (value == -1) {
206 return new JsNameRef(Namer.LONG_NEG_ONE, Namer.KOTLIN_LONG_NAME_REF);
207 }
208 return longFromInt(context.program().getNumberLiteral((int) value));
209 }
210 }
211
212 @NotNull
213 public static JsExpression longFromInt(@NotNull JsExpression expression) {
214 return invokeMethod(Namer.KOTLIN_LONG_NAME_REF, Namer.LONG_FROM_INT, expression);
215 }
216
217 @NotNull
218 public static JsExpression longFromNumber(@NotNull JsExpression expression) {
219 return invokeMethod(Namer.KOTLIN_LONG_NAME_REF, Namer.LONG_FROM_NUMBER, expression);
220 }
221
222 @NotNull
223 public static JsExpression equalsForObject(@NotNull JsExpression left, @NotNull JsExpression right) {
224 return invokeMethod(left, Namer.EQUALS_METHOD_NAME, right);
225 }
226
227 @NotNull
228 public static JsExpression compareForObject(@NotNull JsExpression left, @NotNull JsExpression right) {
229 return invokeMethod(left, Namer.COMPARE_TO_METHOD_NAME, right);
230 }
231
232 @NotNull
233 public static JsPrefixOperation negated(@NotNull JsExpression expression) {
234 return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
235 }
236
237 @NotNull
238 public static JsBinaryOperation and(@NotNull JsExpression op1, @NotNull JsExpression op2) {
239 return new JsBinaryOperation(JsBinaryOperator.AND, op1, op2);
240 }
241
242 @NotNull
243 public static JsBinaryOperation or(@NotNull JsExpression op1, @NotNull JsExpression op2) {
244 return new JsBinaryOperation(JsBinaryOperator.OR, op1, op2);
245 }
246
247 public static void setQualifier(@NotNull JsExpression selector, @Nullable JsExpression receiver) {
248 assert (selector instanceof JsInvocation || selector instanceof JsNameRef);
249 if (selector instanceof JsInvocation) {
250 setQualifier(((JsInvocation) selector).getQualifier(), receiver);
251 return;
252 }
253 setQualifierForNameRef((JsNameRef) selector, receiver);
254 }
255
256 private static void setQualifierForNameRef(@NotNull JsNameRef selector, @Nullable JsExpression receiver) {
257 JsExpression qualifier = selector.getQualifier();
258 if (qualifier == null) {
259 selector.setQualifier(receiver);
260 }
261 else {
262 setQualifier(qualifier, receiver);
263 }
264 }
265
266 @NotNull
267 public static JsBinaryOperation equality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
268 return new JsBinaryOperation(JsBinaryOperator.REF_EQ, arg1, arg2);
269 }
270
271 @NotNull
272 public static JsBinaryOperation inequality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
273 return new JsBinaryOperation(JsBinaryOperator.REF_NEQ, arg1, arg2);
274 }
275
276 @NotNull
277 public static JsBinaryOperation lessThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
278 return new JsBinaryOperation(JsBinaryOperator.LTE, arg1, arg2);
279 }
280
281 @NotNull
282 public static JsBinaryOperation lessThan(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
283 return new JsBinaryOperation(JsBinaryOperator.LT, arg1, arg2);
284 }
285
286 @NotNull
287 public static JsBinaryOperation greaterThan(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
288 return new JsBinaryOperation(JsBinaryOperator.GT, arg1, arg2);
289 }
290
291 @NotNull
292 public static JsExpression assignment(@NotNull JsExpression left, @NotNull JsExpression right) {
293 return new JsBinaryOperation(JsBinaryOperator.ASG, left, right);
294 }
295
296 @NotNull
297 public static JsBinaryOperation sum(@NotNull JsExpression left, @NotNull JsExpression right) {
298 return new JsBinaryOperation(JsBinaryOperator.ADD, left, right);
299 }
300
301 @NotNull
302 public static JsBinaryOperation addAssign(@NotNull JsExpression left, @NotNull JsExpression right) {
303 return new JsBinaryOperation(JsBinaryOperator.ASG_ADD, left, right);
304 }
305
306 @NotNull
307 public static JsBinaryOperation subtract(@NotNull JsExpression left, @NotNull JsExpression right) {
308 return new JsBinaryOperation(JsBinaryOperator.SUB, left, right);
309 }
310
311 @NotNull
312 public static JsBinaryOperation mul(@NotNull JsExpression left, @NotNull JsExpression right) {
313 return new JsBinaryOperation(JsBinaryOperator.MUL, left, right);
314 }
315
316 @NotNull
317 public static JsBinaryOperation div(@NotNull JsExpression left, @NotNull JsExpression right) {
318 return new JsBinaryOperation(JsBinaryOperator.DIV, left, right);
319 }
320
321 @NotNull
322 public static JsBinaryOperation mod(@NotNull JsExpression left, @NotNull JsExpression right) {
323 return new JsBinaryOperation(JsBinaryOperator.MOD, left, right);
324 }
325
326 @NotNull
327 public static JsPrefixOperation not(@NotNull JsExpression expression) {
328 return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
329 }
330
331 @NotNull
332 public static JsBinaryOperation typeof(@NotNull JsExpression expression, @NotNull JsStringLiteral string) {
333 return equality(new JsPrefixOperation(JsUnaryOperator.TYPEOF, expression), string);
334 }
335
336 @NotNull
337 public static JsVars newVar(@NotNull JsName name, @Nullable JsExpression expr) {
338 return new JsVars(new JsVars.JsVar(name, expr));
339 }
340
341 public static void setArguments(@NotNull HasArguments invocation, @NotNull List<JsExpression> newArgs) {
342 List<JsExpression> arguments = invocation.getArguments();
343 assert arguments.isEmpty() : "Arguments already set.";
344 arguments.addAll(newArgs);
345 }
346
347 public static void setArguments(@NotNull HasArguments invocation, JsExpression... arguments) {
348 setArguments(invocation, Arrays.asList(arguments));
349 }
350
351 public static void setParameters(@NotNull JsFunction function, @NotNull List<JsParameter> newParams) {
352 List<JsParameter> parameters = function.getParameters();
353 assert parameters.isEmpty() : "Arguments already set.";
354 parameters.addAll(newParams);
355 }
356
357 @NotNull
358 public static JsExpression newSequence(@NotNull List<JsExpression> expressions) {
359 assert !expressions.isEmpty();
360 if (expressions.size() == 1) {
361 return expressions.get(0);
362 }
363 JsExpression result = expressions.get(expressions.size() - 1);
364 for (int i = expressions.size() - 2; i >= 0; i--) {
365 result = new JsBinaryOperation(JsBinaryOperator.COMMA, expressions.get(i), result);
366 }
367 return result;
368 }
369
370 @NotNull
371 public static JsFunction createFunctionWithEmptyBody(@NotNull JsScope parent) {
372 return new JsFunction(parent, new JsBlock(), "<anonymous>");
373 }
374
375 @NotNull
376 public static List<JsExpression> toStringLiteralList(@NotNull List<String> strings, @NotNull JsProgram program) {
377 if (strings.isEmpty()) {
378 return Collections.emptyList();
379 }
380
381 List<JsExpression> result = new SmartList<JsExpression>();
382 for (String str : strings) {
383 result.add(program.getStringLiteral(str));
384 }
385 return result;
386 }
387
388 @NotNull
389 public static JsInvocation defineProperty(
390 @NotNull String name,
391 @NotNull JsObjectLiteral value,
392 @NotNull TranslationContext context
393 ) {
394 return new JsInvocation(DEFINE_PROPERTY, JsLiteral.THIS, context.program().getStringLiteral(name), value);
395 }
396
397 @NotNull
398 public static JsStatement defineSimpleProperty(@NotNull String name, @NotNull JsExpression value) {
399 return assignment(new JsNameRef(name, JsLiteral.THIS), value).makeStmt();
400 }
401
402 @NotNull
403 public static JsObjectLiteral createDataDescriptor(@NotNull JsExpression value, boolean writable, boolean enumerable) {
404 JsObjectLiteral dataDescriptor = new JsObjectLiteral();
405 dataDescriptor.getPropertyInitializers().add(new JsPropertyInitializer(VALUE, value));
406 if (writable) {
407 dataDescriptor.getPropertyInitializers().add(WRITABLE);
408 }
409 if (enumerable) {
410 dataDescriptor.getPropertyInitializers().add(ENUMERABLE);
411 }
412 return dataDescriptor;
413 }
414
415 @NotNull
416 public static JsFunction createPackage(@NotNull List<JsStatement> to, @NotNull JsObjectScope scope) {
417 JsFunction packageBlockFunction = createFunctionWithEmptyBody(scope);
418
419 JsName kotlinObjectAsParameter = packageBlockFunction.getScope().declareNameUnsafe(Namer.KOTLIN_NAME);
420 packageBlockFunction.getParameters().add(new JsParameter(kotlinObjectAsParameter));
421
422 to.add(new JsInvocation(packageBlockFunction, Namer.KOTLIN_OBJECT_REF).makeStmt());
423
424 return packageBlockFunction;
425 }
426
427 @NotNull
428 public static JsObjectLiteral wrapValue(@NotNull JsExpression label, @NotNull JsExpression value) {
429 return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value)));
430 }
431
432 public static JsExpression replaceRootReference(@NotNull JsNameRef fullQualifier, @NotNull JsExpression newQualifier) {
433 if (fullQualifier.getQualifier() == null) {
434 assert Namer.getRootPackageName().equals(fullQualifier.getIdent()) : "Expected root package, but: " + fullQualifier.getIdent();
435 return newQualifier;
436 }
437
438 fullQualifier = fullQualifier.deepCopy();
439 JsNameRef qualifier = fullQualifier;
440 while (true) {
441 JsExpression parent = qualifier.getQualifier();
442 assert parent instanceof JsNameRef : "unexpected qualifier: " + parent + ", original: " + fullQualifier;
443 if (((JsNameRef) parent).getQualifier() == null) {
444 assert Namer.getRootPackageName().equals(((JsNameRef) parent).getIdent());
445 qualifier.setQualifier(newQualifier);
446 return fullQualifier;
447 }
448 qualifier = (JsNameRef) parent;
449 }
450 }
451
452 @NotNull
453 public static List<JsStatement> flattenStatement(@NotNull JsStatement statement) {
454 if (statement instanceof JsBlock) {
455 return ((JsBlock) statement).getStatements();
456 }
457
458 return new SmartList<JsStatement>(statement);
459 }
460 }