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.jet.lang.descriptors.DeclarationDescriptor;
024    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
025    import org.jetbrains.jet.lang.descriptors.Modality;
026    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
027    import org.jetbrains.jet.lang.psi.JetElement;
028    import org.jetbrains.k2js.translate.context.TranslationContext;
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 = new JsNameRef("defineProperty");
036        public static final JsNameRef CREATE_OBJECT = new JsNameRef("create");
037        private static final JsNameRef EMPTY_REF = new JsNameRef("");
038    
039        private static final JsNameRef VALUE = new JsNameRef("value");
040        private static final JsPropertyInitializer WRITABLE = new JsPropertyInitializer(new JsNameRef("writable"), JsLiteral.TRUE);
041        private static final JsPropertyInitializer ENUMERABLE = new JsPropertyInitializer(new JsNameRef("enumerable"), JsLiteral.TRUE);
042    
043        public static final String LENDS_JS_DOC_TAG = "lends";
044    
045        static {
046            JsNameRef globalObjectReference = new JsNameRef("Object");
047            DEFINE_PROPERTY.setQualifier(globalObjectReference);
048            CREATE_OBJECT.setQualifier(globalObjectReference);
049        }
050    
051        private JsAstUtils() {
052        }
053    
054        @NotNull
055        public static JsStatement convertToStatement(@NotNull JsNode jsNode) {
056            assert (jsNode instanceof JsExpression) || (jsNode instanceof JsStatement)
057                    : "Unexpected node of type: " + jsNode.getClass().toString();
058            if (jsNode instanceof JsExpression) {
059                return ((JsExpression) jsNode).makeStmt();
060            }
061            return (JsStatement) jsNode;
062        }
063    
064        @NotNull
065        public static JsBlock convertToBlock(@NotNull JsNode jsNode) {
066            if (jsNode instanceof JsBlock) {
067                return (JsBlock) jsNode;
068            }
069            return new JsBlock(convertToStatement(jsNode));
070        }
071    
072        @NotNull
073        public static JsExpression convertToExpression(@NotNull JsNode jsNode) {
074            assert jsNode instanceof JsExpression : "Unexpected node of type: " + jsNode.getClass().toString();
075            return (JsExpression) jsNode;
076        }
077    
078        @NotNull
079        public static JsPrefixOperation negated(@NotNull JsExpression expression) {
080            return new JsPrefixOperation(JsUnaryOperator.NOT, expression);
081        }
082    
083        @NotNull
084        public static JsBinaryOperation and(@NotNull JsExpression op1, @NotNull JsExpression op2) {
085            return new JsBinaryOperation(JsBinaryOperator.AND, op1, op2);
086        }
087    
088        @NotNull
089        public static JsBinaryOperation or(@NotNull JsExpression op1, @NotNull JsExpression op2) {
090            return new JsBinaryOperation(JsBinaryOperator.OR, op1, op2);
091        }
092    
093        public static void setQualifier(@NotNull JsExpression selector, @Nullable JsExpression receiver) {
094            assert (selector instanceof JsInvocation || selector instanceof JsNameRef);
095            if (selector instanceof JsInvocation) {
096                setQualifier(((JsInvocation) selector).getQualifier(), receiver);
097                return;
098            }
099            setQualifierForNameRef((JsNameRef) selector, receiver);
100        }
101    
102        private static void setQualifierForNameRef(@NotNull JsNameRef selector, @Nullable JsExpression receiver) {
103            JsExpression qualifier = selector.getQualifier();
104            if (qualifier == null) {
105                selector.setQualifier(receiver);
106            }
107            else {
108                setQualifier(qualifier, receiver);
109            }
110        }
111    
112        @NotNull
113        public static JsBinaryOperation equality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
114            return new JsBinaryOperation(JsBinaryOperator.REF_EQ, arg1, arg2);
115        }
116    
117        @NotNull
118        public static JsBinaryOperation inequality(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
119            return new JsBinaryOperation(JsBinaryOperator.REF_NEQ, arg1, arg2);
120        }
121    
122        @NotNull
123        public static JsBinaryOperation lessThanEq(@NotNull JsExpression arg1, @NotNull JsExpression arg2) {
124            return new JsBinaryOperation(JsBinaryOperator.LTE, arg1, arg2);
125        }
126    
127        @NotNull
128        public static JsExpression assignment(@NotNull JsExpression left, @NotNull JsExpression right) {
129            return new JsBinaryOperation(JsBinaryOperator.ASG, left, right);
130        }
131    
132        @NotNull
133        public static JsBinaryOperation sum(@NotNull JsExpression left, @NotNull JsExpression right) {
134            return new JsBinaryOperation(JsBinaryOperator.ADD, left, right);
135        }
136    
137        @NotNull
138        public static JsBinaryOperation addAssign(@NotNull JsExpression left, @NotNull JsExpression right) {
139            return new JsBinaryOperation(JsBinaryOperator.ASG_ADD, left, right);
140        }
141    
142        @NotNull
143        public static JsBinaryOperation subtract(@NotNull JsExpression left, @NotNull JsExpression right) {
144            return new JsBinaryOperation(JsBinaryOperator.SUB, 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 JsFor generateForExpression(@NotNull JsVars initExpression,
159                @NotNull JsExpression condition,
160                @NotNull JsExpression incrementExpression,
161                @NotNull JsStatement body) {
162            JsFor result = new JsFor(initExpression, condition, incrementExpression);
163            result.setBody(body);
164            return result;
165        }
166    
167        @NotNull
168        public static JsVars newVar(@NotNull JsName name, @Nullable JsExpression expr) {
169            return new JsVars(new JsVars.JsVar(name, expr));
170        }
171    
172        public static void setArguments(@NotNull JsInvocation invocation, @NotNull List<JsExpression> newArgs) {
173            List<JsExpression> arguments = invocation.getArguments();
174            assert arguments.isEmpty() : "Arguments already set.";
175            arguments.addAll(newArgs);
176        }
177    
178        public static void setArguments(@NotNull HasArguments invocation, @NotNull List<JsExpression> newArgs) {
179            List<JsExpression> arguments = invocation.getArguments();
180            assert arguments.isEmpty() : "Arguments already set.";
181            arguments.addAll(newArgs);
182        }
183    
184        public static void setArguments(@NotNull HasArguments invocation, JsExpression... arguments) {
185            setArguments(invocation, Arrays.asList(arguments));
186        }
187    
188        public static void setParameters(@NotNull JsFunction function, @NotNull List<JsParameter> newParams) {
189            List<JsParameter> parameters = function.getParameters();
190            assert parameters.isEmpty() : "Arguments already set.";
191            parameters.addAll(newParams);
192        }
193    
194        @NotNull
195        public static JsExpression newSequence(@NotNull List<JsExpression> expressions) {
196            assert !expressions.isEmpty();
197            if (expressions.size() == 1) {
198                return expressions.get(0);
199            }
200            JsExpression result = expressions.get(expressions.size() - 1);
201            for (int i = expressions.size() - 2; i >= 0; i--) {
202                result = new JsBinaryOperation(JsBinaryOperator.COMMA, expressions.get(i), result);
203            }
204            return result;
205        }
206    
207        @NotNull
208        public static JsFunction createFunctionWithEmptyBody(@NotNull JsScope parent) {
209            return new JsFunction(parent, new JsBlock());
210        }
211    
212        @NotNull
213        public static List<JsExpression> toStringLiteralList(@NotNull List<String> strings, @NotNull JsProgram program) {
214            if (strings.isEmpty()) {
215                return Collections.emptyList();
216            }
217    
218            List<JsExpression> result = new SmartList<JsExpression>();
219            for (String str : strings) {
220                result.add(program.getStringLiteral(str));
221            }
222            return result;
223        }
224    
225        @NotNull
226        public static JsInvocation definePropertyDataDescriptor(@NotNull PropertyDescriptor descriptor,
227                @NotNull JsExpression value,
228                @NotNull TranslationContext context) {
229            return defineProperty(context.getNameForDescriptor(descriptor).getIdent(), createPropertyDataDescriptor(descriptor, value),
230                                  context);
231        }
232    
233        @NotNull
234        public static JsInvocation defineProperty(
235                @NotNull String name,
236                @NotNull JsObjectLiteral value,
237                @NotNull TranslationContext context
238        ) {
239            return new JsInvocation(DEFINE_PROPERTY, JsLiteral.THIS, context.program().getStringLiteral(name), value);
240        }
241    
242        @NotNull
243        public static JsStatement defineSimpleProperty(@NotNull String name, @NotNull JsExpression value, @NotNull TranslationContext context) {
244            if (context.isEcma5()) {
245                return defineProperty(name, createDataDescriptor(value, false, false), context).makeStmt();
246            } else {
247                return assignment(new JsNameRef(name, JsLiteral.THIS), value).makeStmt();
248            }
249        }
250    
251        @NotNull
252        public static JsObjectLiteral createPropertyDataDescriptor(@NotNull FunctionDescriptor descriptor,
253                @NotNull JsExpression value) {
254            return createPropertyDataDescriptor(descriptor, descriptor.getModality().isOverridable(), value);
255        }
256    
257        @NotNull
258        public static JsObjectLiteral createDataDescriptor(@NotNull JsExpression value) {
259            return createDataDescriptor(value, false, false);
260        }
261    
262        @NotNull
263        public static JsObjectLiteral createDataDescriptor(@NotNull JsExpression value, boolean writable, boolean enumerable) {
264            JsObjectLiteral dataDescriptor = new JsObjectLiteral();
265            dataDescriptor.getPropertyInitializers().add(new JsPropertyInitializer(VALUE, value));
266            if (writable) {
267                dataDescriptor.getPropertyInitializers().add(WRITABLE);
268            }
269            if (enumerable) {
270                dataDescriptor.getPropertyInitializers().add(ENUMERABLE);
271            }
272            return dataDescriptor;
273        }
274    
275        @NotNull
276        public static JsObjectLiteral createPropertyDataDescriptor(@NotNull PropertyDescriptor descriptor, @NotNull JsExpression value) {
277            return createPropertyDataDescriptor(descriptor, descriptor.isVar() || descriptor.getModality() == Modality.OPEN, value);
278        }
279    
280        @NotNull
281        private static JsObjectLiteral createPropertyDataDescriptor(
282                @NotNull DeclarationDescriptor descriptor,
283                boolean writable,
284                @NotNull JsExpression value
285        ) {
286            return createDataDescriptor(value, writable, AnnotationsUtils.isEnumerable(descriptor));
287        }
288    
289        @NotNull
290        public static JsFunction createPackage(@NotNull List<JsStatement> to, @NotNull JsScope scope) {
291            JsFunction packageBlockFunction = createFunctionWithEmptyBody(scope);
292            to.add(new JsInvocation(EMPTY_REF, new JsInvocation(packageBlockFunction)).makeStmt());
293            return packageBlockFunction;
294        }
295    
296        @NotNull
297        public static JsObjectLiteral wrapValue(@NotNull JsExpression label, @NotNull JsExpression value) {
298            return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value)));
299        }
300    
301        @NotNull
302        public static <T extends JsNode> T source(@NotNull T jsNode, @NotNull JetElement ktElement) {
303            jsNode.setSourceInfo(ktElement);
304            return jsNode;
305        }
306    
307    }