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