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