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