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