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 JsBinaryOperation greaterThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
294            return new JsBinaryOperation(JsBinaryOperator.GTE, arg1, arg2);
295        }
296    
297        @NotNull
298        public static JsExpression assignment(@NotNull JsExpression left, @NotNull JsExpression right) {
299            return new JsBinaryOperation(JsBinaryOperator.ASG, left, right);
300        }
301    
302        @NotNull
303        public static JsBinaryOperation sum(@NotNull JsExpression left, @NotNull JsExpression right) {
304            return new JsBinaryOperation(JsBinaryOperator.ADD, left, right);
305        }
306    
307        @NotNull
308        public static JsBinaryOperation addAssign(@NotNull JsExpression left, @NotNull JsExpression right) {
309            return new JsBinaryOperation(JsBinaryOperator.ASG_ADD, left, right);
310        }
311    
312        @NotNull
313        public static JsBinaryOperation subtract(@NotNull JsExpression left, @NotNull JsExpression right) {
314            return new JsBinaryOperation(JsBinaryOperator.SUB, left, right);
315        }
316    
317        @NotNull
318        public static JsBinaryOperation mul(@NotNull JsExpression left, @NotNull JsExpression right) {
319            return new JsBinaryOperation(JsBinaryOperator.MUL, left, right);
320        }
321    
322        @NotNull
323        public static JsBinaryOperation div(@NotNull JsExpression left, @NotNull JsExpression right) {
324            return new JsBinaryOperation(JsBinaryOperator.DIV, left, right);
325        }
326    
327        @NotNull
328        public static JsBinaryOperation mod(@NotNull JsExpression left, @NotNull JsExpression right) {
329            return new JsBinaryOperation(JsBinaryOperator.MOD, left, right);
330        }
331    
332        @NotNull
333        public static JsPrefixOperation not(@NotNull JsExpression expression) {
334            return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
335        }
336    
337        @NotNull
338        public static JsBinaryOperation typeOfIs(@NotNull JsExpression expression, @NotNull JsStringLiteral string) {
339            return equality(new JsPrefixOperation(JsUnaryOperator.TYPEOF, expression), string);
340        }
341    
342        @NotNull
343        public static JsVars newVar(@NotNull JsName name, @Nullable JsExpression expr) {
344            return new JsVars(new JsVars.JsVar(name, expr));
345        }
346    
347        public static void setArguments(@NotNull HasArguments invocation, @NotNull List<JsExpression> newArgs) {
348            List<JsExpression> arguments = invocation.getArguments();
349            assert arguments.isEmpty() : "Arguments already set.";
350            arguments.addAll(newArgs);
351        }
352    
353        public static void setArguments(@NotNull HasArguments invocation, JsExpression... arguments) {
354            setArguments(invocation, Arrays.asList(arguments));
355        }
356    
357        public static void setParameters(@NotNull JsFunction function, @NotNull List<JsParameter> newParams) {
358            List<JsParameter> parameters = function.getParameters();
359            assert parameters.isEmpty() : "Arguments already set.";
360            parameters.addAll(newParams);
361        }
362    
363        @NotNull
364        public static JsExpression newSequence(@NotNull List<JsExpression> expressions) {
365            assert !expressions.isEmpty();
366            if (expressions.size() == 1) {
367                return expressions.get(0);
368            }
369            JsExpression result = expressions.get(expressions.size() - 1);
370            for (int i = expressions.size() - 2; i >= 0; i--) {
371                result = new JsBinaryOperation(JsBinaryOperator.COMMA, expressions.get(i), result);
372            }
373            return result;
374        }
375    
376        @NotNull
377        public static JsFunction createFunctionWithEmptyBody(@NotNull JsScope parent) {
378            return new JsFunction(parent, new JsBlock(), "<anonymous>");
379        }
380    
381        @NotNull
382        public static List<JsExpression> toStringLiteralList(@NotNull List<String> strings, @NotNull JsProgram program) {
383            if (strings.isEmpty()) {
384                return Collections.emptyList();
385            }
386    
387            List<JsExpression> result = new SmartList<JsExpression>();
388            for (String str : strings) {
389                result.add(program.getStringLiteral(str));
390            }
391            return result;
392        }
393    
394        @NotNull
395        public static JsInvocation defineProperty(
396                @NotNull String name,
397                @NotNull JsObjectLiteral value,
398                @NotNull TranslationContext context
399        ) {
400            return new JsInvocation(DEFINE_PROPERTY, JsLiteral.THIS, context.program().getStringLiteral(name), value);
401        }
402    
403        @NotNull
404        public static JsStatement defineSimpleProperty(@NotNull String name, @NotNull JsExpression value) {
405            return assignment(new JsNameRef(name, JsLiteral.THIS), value).makeStmt();
406        }
407    
408        @NotNull
409        public static JsObjectLiteral createDataDescriptor(@NotNull JsExpression value, boolean writable, boolean enumerable) {
410            JsObjectLiteral dataDescriptor = new JsObjectLiteral();
411            dataDescriptor.getPropertyInitializers().add(new JsPropertyInitializer(VALUE, value));
412            if (writable) {
413                dataDescriptor.getPropertyInitializers().add(WRITABLE);
414            }
415            if (enumerable) {
416                dataDescriptor.getPropertyInitializers().add(ENUMERABLE);
417            }
418            return dataDescriptor;
419        }
420    
421        @NotNull
422        public static JsFunction createPackage(@NotNull List<JsStatement> to, @NotNull JsObjectScope scope) {
423            JsFunction packageBlockFunction = createFunctionWithEmptyBody(scope);
424    
425            JsName kotlinObjectAsParameter = packageBlockFunction.getScope().declareNameUnsafe(Namer.KOTLIN_NAME);
426            packageBlockFunction.getParameters().add(new JsParameter(kotlinObjectAsParameter));
427    
428            to.add(new JsInvocation(packageBlockFunction, Namer.KOTLIN_OBJECT_REF).makeStmt());
429    
430            return packageBlockFunction;
431        }
432    
433        @NotNull
434        public static JsObjectLiteral wrapValue(@NotNull JsExpression label, @NotNull JsExpression value) {
435            return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value)));
436        }
437    
438        public static JsExpression replaceRootReference(@NotNull JsNameRef fullQualifier, @NotNull JsExpression newQualifier) {
439            if (fullQualifier.getQualifier() == null) {
440                assert Namer.getRootPackageName().equals(fullQualifier.getIdent()) : "Expected root package, but: " + fullQualifier.getIdent();
441                return newQualifier;
442            }
443    
444            fullQualifier = fullQualifier.deepCopy();
445            JsNameRef qualifier = fullQualifier;
446            while (true) {
447                JsExpression parent = qualifier.getQualifier();
448                assert parent instanceof JsNameRef : "unexpected qualifier: " + parent + ", original: " + fullQualifier;
449                if (((JsNameRef) parent).getQualifier() == null) {
450                    assert Namer.getRootPackageName().equals(((JsNameRef) parent).getIdent());
451                    qualifier.setQualifier(newQualifier);
452                    return fullQualifier;
453                }
454                qualifier = (JsNameRef) parent;
455            }
456        }
457    
458        @NotNull
459        public static List<JsStatement> flattenStatement(@NotNull JsStatement statement) {
460            if (statement instanceof JsBlock) {
461                return ((JsBlock) statement).getStatements();
462            }
463    
464            return new SmartList<JsStatement>(statement);
465        }
466    }