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