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