001    /*
002     * Copyright 2010-2013 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.k2js.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.k2js.translate.context.Namer;
024    import org.jetbrains.k2js.translate.context.TranslationContext;
025    
026    import java.util.Arrays;
027    import java.util.Collections;
028    import java.util.List;
029    
030    public final class JsAstUtils {
031        private static final JsNameRef DEFINE_PROPERTY = new JsNameRef("defineProperty");
032        public static final JsNameRef CREATE_OBJECT = new JsNameRef("create");
033    
034        private static final JsNameRef VALUE = new JsNameRef("value");
035        private static final JsPropertyInitializer WRITABLE = new JsPropertyInitializer(new JsNameRef("writable"), JsLiteral.TRUE);
036        private static final JsPropertyInitializer ENUMERABLE = new JsPropertyInitializer(new JsNameRef("enumerable"), JsLiteral.TRUE);
037    
038        public static final String LENDS_JS_DOC_TAG = "lends";
039    
040        static {
041            JsNameRef globalObjectReference = new JsNameRef("Object");
042            DEFINE_PROPERTY.setQualifier(globalObjectReference);
043            CREATE_OBJECT.setQualifier(globalObjectReference);
044        }
045    
046        private JsAstUtils() {
047        }
048    
049        @NotNull
050        public static JsStatement convertToStatement(@NotNull JsNode jsNode) {
051            assert (jsNode instanceof JsExpression) || (jsNode instanceof JsStatement)
052                    : "Unexpected node of type: " + jsNode.getClass().toString();
053            if (jsNode instanceof JsExpression) {
054                return ((JsExpression) jsNode).makeStmt();
055            }
056            return (JsStatement) jsNode;
057        }
058    
059        @NotNull
060        public static JsBlock convertToBlock(@NotNull JsNode jsNode) {
061            if (jsNode instanceof JsBlock) {
062                return (JsBlock) jsNode;
063            }
064            return new JsBlock(convertToStatement(jsNode));
065        }
066    
067        @NotNull
068        public static JsExpression convertToExpression(@NotNull JsNode jsNode) {
069            assert jsNode instanceof JsExpression : "Unexpected node of type: " + jsNode.getClass().toString();
070            return (JsExpression) jsNode;
071        }
072    
073        @NotNull
074        public static JsPrefixOperation negated(@NotNull JsExpression expression) {
075            return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
076        }
077    
078        @NotNull
079        public static JsBinaryOperation and(@NotNull JsExpression op1, @NotNull JsExpression op2) {
080            return new JsBinaryOperation(JsBinaryOperator.AND, op1, op2);
081        }
082    
083        @NotNull
084        public static JsBinaryOperation or(@NotNull JsExpression op1, @NotNull JsExpression op2) {
085            return new JsBinaryOperation(JsBinaryOperator.OR, op1, op2);
086        }
087    
088        public static void setQualifier(@NotNull JsExpression selector, @Nullable JsExpression receiver) {
089            assert (selector instanceof JsInvocation || selector instanceof JsNameRef);
090            if (selector instanceof JsInvocation) {
091                setQualifier(((JsInvocation) selector).getQualifier(), receiver);
092                return;
093            }
094            setQualifierForNameRef((JsNameRef) selector, receiver);
095        }
096    
097        private static void setQualifierForNameRef(@NotNull JsNameRef selector, @Nullable JsExpression receiver) {
098            JsExpression qualifier = selector.getQualifier();
099            if (qualifier == null) {
100                selector.setQualifier(receiver);
101            }
102            else {
103                setQualifier(qualifier, receiver);
104            }
105        }
106    
107        @NotNull
108        public static JsBinaryOperation equality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
109            return new JsBinaryOperation(JsBinaryOperator.REF_EQ, arg1, arg2);
110        }
111    
112        @NotNull
113        public static JsBinaryOperation inequality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
114            return new JsBinaryOperation(JsBinaryOperator.REF_NEQ, arg1, arg2);
115        }
116    
117        @NotNull
118        public static JsBinaryOperation lessThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
119            return new JsBinaryOperation(JsBinaryOperator.LTE, arg1, arg2);
120        }
121    
122        @NotNull
123        public static JsExpression assignment(@NotNull JsExpression left, @NotNull JsExpression right) {
124            return new JsBinaryOperation(JsBinaryOperator.ASG, left, right);
125        }
126    
127        @NotNull
128        public static JsBinaryOperation sum(@NotNull JsExpression left, @NotNull JsExpression right) {
129            return new JsBinaryOperation(JsBinaryOperator.ADD, left, right);
130        }
131    
132        @NotNull
133        public static JsBinaryOperation addAssign(@NotNull JsExpression left, @NotNull JsExpression right) {
134            return new JsBinaryOperation(JsBinaryOperator.ASG_ADD, left, right);
135        }
136    
137        @NotNull
138        public static JsBinaryOperation subtract(@NotNull JsExpression left, @NotNull JsExpression right) {
139            return new JsBinaryOperation(JsBinaryOperator.SUB, left, right);
140        }
141    
142        @NotNull
143        public static JsBinaryOperation mul(@NotNull JsExpression left, @NotNull JsExpression right) {
144            return new JsBinaryOperation(JsBinaryOperator.MUL, left, right);
145        }
146    
147        @NotNull
148        public static JsPrefixOperation not(@NotNull JsExpression expression) {
149            return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
150        }
151    
152        @NotNull
153        public static JsBinaryOperation typeof(@NotNull JsExpression expression, @NotNull JsStringLiteral string) {
154            return equality(new JsPrefixOperation(JsUnaryOperator.TYPEOF, expression), string);
155        }
156    
157        @NotNull
158        public static JsVars newVar(@NotNull JsName name, @Nullable JsExpression expr) {
159            return new JsVars(new JsVars.JsVar(name, expr));
160        }
161    
162        public static void setArguments(@NotNull HasArguments invocation, @NotNull List<JsExpression> newArgs) {
163            List<JsExpression> arguments = invocation.getArguments();
164            assert arguments.isEmpty() : "Arguments already set.";
165            arguments.addAll(newArgs);
166        }
167    
168        public static void setArguments(@NotNull HasArguments invocation, JsExpression... arguments) {
169            setArguments(invocation, Arrays.asList(arguments));
170        }
171    
172        public static void setParameters(@NotNull JsFunction function, @NotNull List<JsParameter> newParams) {
173            List<JsParameter> parameters = function.getParameters();
174            assert parameters.isEmpty() : "Arguments already set.";
175            parameters.addAll(newParams);
176        }
177    
178        @NotNull
179        public static JsExpression newSequence(@NotNull List<JsExpression> expressions) {
180            assert !expressions.isEmpty();
181            if (expressions.size() == 1) {
182                return expressions.get(0);
183            }
184            JsExpression result = expressions.get(expressions.size() - 1);
185            for (int i = expressions.size() - 2; i >= 0; i--) {
186                result = new JsBinaryOperation(JsBinaryOperator.COMMA, expressions.get(i), result);
187            }
188            return result;
189        }
190    
191        @NotNull
192        public static JsFunction createFunctionWithEmptyBody(@NotNull JsScope parent) {
193            return new JsFunction(parent, new JsBlock());
194        }
195    
196        @NotNull
197        public static List<JsExpression> toStringLiteralList(@NotNull List<String> strings, @NotNull JsProgram program) {
198            if (strings.isEmpty()) {
199                return Collections.emptyList();
200            }
201    
202            List<JsExpression> result = new SmartList<JsExpression>();
203            for (String str : strings) {
204                result.add(program.getStringLiteral(str));
205            }
206            return result;
207        }
208    
209        @NotNull
210        public static JsInvocation defineProperty(
211                @NotNull String name,
212                @NotNull JsObjectLiteral value,
213                @NotNull TranslationContext context
214        ) {
215            return new JsInvocation(DEFINE_PROPERTY, JsLiteral.THIS, context.program().getStringLiteral(name), value);
216        }
217    
218        @NotNull
219        public static JsStatement defineSimpleProperty(@NotNull String name, @NotNull JsExpression value) {
220            return assignment(new JsNameRef(name, JsLiteral.THIS), value).makeStmt();
221        }
222    
223        @NotNull
224        public static JsObjectLiteral createDataDescriptor(@NotNull JsExpression value, boolean writable, boolean enumerable) {
225            JsObjectLiteral dataDescriptor = new JsObjectLiteral();
226            dataDescriptor.getPropertyInitializers().add(new JsPropertyInitializer(VALUE, value));
227            if (writable) {
228                dataDescriptor.getPropertyInitializers().add(WRITABLE);
229            }
230            if (enumerable) {
231                dataDescriptor.getPropertyInitializers().add(ENUMERABLE);
232            }
233            return dataDescriptor;
234        }
235    
236        @NotNull
237        public static JsFunction createPackage(@NotNull List<JsStatement> to, @NotNull JsScope scope) {
238            JsFunction packageBlockFunction = createFunctionWithEmptyBody(scope);
239    
240            JsName kotlinObjectAsParameter = packageBlockFunction.getScope().declareName(Namer.KOTLIN_NAME);
241            packageBlockFunction.getParameters().add(new JsParameter(kotlinObjectAsParameter));
242    
243            to.add(new JsInvocation(packageBlockFunction, Namer.KOTLIN_OBJECT_REF).makeStmt());
244    
245            return packageBlockFunction;
246        }
247    
248        @NotNull
249        public static JsObjectLiteral wrapValue(@NotNull JsExpression label, @NotNull JsExpression value) {
250            return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value)));
251        }
252    
253        public static void replaceRootReference(@NotNull JsNameRef fullQualifier, @NotNull JsExpression newQualifier) {
254            JsNameRef qualifier = fullQualifier;
255            while (true) {
256                JsExpression parent = qualifier.getQualifier();
257                assert parent instanceof JsNameRef : "unexpected qualifier: " + parent + ", original: " + fullQualifier;
258                if (((JsNameRef) parent).getQualifier() == null) {
259                    assert Namer.getRootPackageName().equals(((JsNameRef) parent).getIdent());
260                    qualifier.setQualifier(newQualifier);
261                    return;
262                }
263                qualifier = (JsNameRef) parent;
264            }
265        }
266    }