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