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.openapi.util.Pair;
021    import com.intellij.util.SmartList;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.annotations.Nullable;
024    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
025    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
026    import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
027    import org.jetbrains.jet.lang.descriptors.PropertyGetterDescriptor;
028    import org.jetbrains.jet.lang.psi.*;
029    import org.jetbrains.k2js.translate.context.TemporaryConstVariable;
030    import org.jetbrains.k2js.translate.context.TranslationContext;
031    import org.jetbrains.k2js.translate.general.Translation;
032    
033    import java.util.ArrayList;
034    import java.util.Collections;
035    import java.util.List;
036    
037    import static com.google.dart.compiler.backend.js.ast.JsBinaryOperator.*;
038    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptorForOperationExpression;
039    import static org.jetbrains.k2js.translate.utils.JsAstUtils.assignment;
040    import static org.jetbrains.k2js.translate.utils.JsAstUtils.createDataDescriptor;
041    
042    public final class TranslationUtils {
043        private TranslationUtils() {
044        }
045    
046        @NotNull
047        public static JsPropertyInitializer translateFunctionAsEcma5PropertyDescriptor(@NotNull JsFunction function,
048                @NotNull FunctionDescriptor descriptor,
049                @NotNull TranslationContext context) {
050            if (JsDescriptorUtils.isExtension(descriptor)) {
051                return translateExtensionFunctionAsEcma5DataDescriptor(function, descriptor, context);
052            }
053            else {
054                JsStringLiteral getOrSet = context.program().getStringLiteral(descriptor instanceof PropertyGetterDescriptor ? "get" : "set");
055                return new JsPropertyInitializer(getOrSet, function);
056            }
057        }
058    
059        @NotNull
060        public static JsFunction simpleReturnFunction(@NotNull JsScope functionScope, @NotNull JsExpression returnExpression) {
061            return new JsFunction(functionScope, new JsBlock(new JsReturn(returnExpression)));
062        }
063    
064        @NotNull
065        private static JsPropertyInitializer translateExtensionFunctionAsEcma5DataDescriptor(@NotNull JsFunction function,
066                @NotNull FunctionDescriptor descriptor, @NotNull TranslationContext context) {
067            JsObjectLiteral meta = createDataDescriptor(function, descriptor.getModality().isOverridable());
068            return new JsPropertyInitializer(context.getNameForDescriptor(descriptor).makeRef(), meta);
069        }
070    
071        @NotNull
072        public static JsExpression translateExclForBinaryEqualLikeExpr(@NotNull JsBinaryOperation baseBinaryExpression) {
073            return new JsBinaryOperation(notOperator(baseBinaryExpression.getOperator()), baseBinaryExpression.getArg1(), baseBinaryExpression.getArg2());
074        }
075    
076        public static boolean isEqualLikeOperator(@NotNull JsBinaryOperator operator) {
077            return notOperator(operator) != null;
078        }
079    
080        @Nullable
081        private static JsBinaryOperator notOperator(@NotNull JsBinaryOperator operator) {
082            switch (operator) {
083                case REF_EQ:
084                    return REF_NEQ;
085                case REF_NEQ:
086                    return REF_EQ;
087                case EQ:
088                    return NEQ;
089                case NEQ:
090                    return EQ;
091                default:
092                    return null;
093            }
094        }
095    
096        @NotNull
097        public static JsBinaryOperation isNullCheck(@NotNull JsExpression expressionToCheck) {
098            return nullCheck(expressionToCheck, false);
099        }
100    
101        @NotNull
102        public static JsBinaryOperation isNotNullCheck(@NotNull JsExpression expressionToCheck) {
103            return nullCheck(expressionToCheck, true);
104        }
105    
106        @NotNull
107        public static JsBinaryOperation nullCheck(@NotNull JsExpression expressionToCheck, boolean isNegated) {
108            JsBinaryOperator operator = isNegated ? JsBinaryOperator.NEQ : JsBinaryOperator.EQ;
109            return new JsBinaryOperation(operator, expressionToCheck, JsLiteral.NULL);
110        }
111    
112        @NotNull
113        public static JsConditional notNullConditional(
114                @NotNull JsExpression expression,
115                @NotNull JsExpression elseExpression,
116                @NotNull TranslationContext context
117        ) {
118            JsExpression testExpression;
119            JsExpression thenExpression;
120            if (isCacheNeeded(expression)) {
121                TemporaryConstVariable tempVar = context.getOrDeclareTemporaryConstVariable(expression);
122                testExpression = isNotNullCheck(tempVar.value());
123                thenExpression = tempVar.value();
124            }
125            else {
126                testExpression = isNotNullCheck(expression);
127                thenExpression = expression;
128            }
129    
130            return new JsConditional(testExpression, thenExpression, elseExpression);
131        }
132    
133        @NotNull
134        public static List<JsExpression> translateArgumentList(@NotNull TranslationContext context,
135                @NotNull List<? extends ValueArgument> jetArguments) {
136            if (jetArguments.isEmpty()) {
137                return Collections.emptyList();
138            }
139    
140            List<JsExpression> jsArguments = new SmartList<JsExpression>();
141            for (ValueArgument argument : jetArguments) {
142                jsArguments.add(translateArgument(context, argument));
143            }
144            return jsArguments;
145        }
146    
147        @NotNull
148        private static JsExpression translateArgument(@NotNull TranslationContext context, @NotNull ValueArgument argument) {
149            JetExpression jetExpression = argument.getArgumentExpression();
150            assert jetExpression != null : "Argument with no expression";
151            return Translation.translateAsExpression(jetExpression, context);
152        }
153    
154        @NotNull
155        public static JsNameRef backingFieldReference(@NotNull TranslationContext context,
156                @NotNull PropertyDescriptor descriptor) {
157            JsName backingFieldName = context.getNameForDescriptor(descriptor);
158            return new JsNameRef(backingFieldName, JsLiteral.THIS);
159        }
160    
161        @NotNull
162        public static JsExpression assignmentToBackingField(@NotNull TranslationContext context,
163                @NotNull PropertyDescriptor descriptor,
164                @NotNull JsExpression assignTo) {
165            JsNameRef backingFieldReference = backingFieldReference(context, descriptor);
166            return assignment(backingFieldReference, assignTo);
167        }
168    
169        @Nullable
170        public static JsExpression translateInitializerForProperty(@NotNull JetProperty declaration,
171                @NotNull TranslationContext context) {
172            JsExpression jsInitExpression = null;
173            JetExpression initializer = declaration.getInitializer();
174            if (initializer != null) {
175                jsInitExpression = Translation.translateAsExpression(initializer, context);
176            }
177            return jsInitExpression;
178        }
179    
180        @NotNull
181        public static JsNameRef getQualifiedReference(@NotNull TranslationContext context, @NotNull DeclarationDescriptor descriptor) {
182            return new JsNameRef(context.getNameForDescriptor(descriptor), context.getQualifierForDescriptor(descriptor));
183        }
184    
185        @NotNull
186        public static List<JsExpression> translateExpressionList(@NotNull TranslationContext context,
187                @NotNull List<JetExpression> expressions) {
188            List<JsExpression> result = new ArrayList<JsExpression>();
189            for (JetExpression expression : expressions) {
190                result.add(Translation.translateAsExpression(expression, context));
191            }
192            return result;
193        }
194    
195        @NotNull
196        public static JsExpression translateBaseExpression(@NotNull TranslationContext context,
197                @NotNull JetUnaryExpression expression) {
198            JetExpression baseExpression = PsiUtils.getBaseExpression(expression);
199            return Translation.translateAsExpression(baseExpression, context);
200        }
201    
202        @NotNull
203        public static JsExpression translateLeftExpression(@NotNull TranslationContext context,
204                @NotNull JetBinaryExpression expression) {
205            JetExpression left = expression.getLeft();
206            assert left != null : "Binary expression should have a left expression: " + expression.getText();
207            return Translation.translateAsExpression(left, context);
208        }
209    
210        @NotNull
211        public static JsExpression translateRightExpression(@NotNull TranslationContext context,
212                @NotNull JetBinaryExpression expression) {
213            JetExpression rightExpression = expression.getRight();
214            assert rightExpression != null : "Binary expression should have a right expression";
215            return Translation.translateAsExpression(rightExpression, context);
216        }
217    
218        public static boolean hasCorrespondingFunctionIntrinsic(@NotNull TranslationContext context,
219                @NotNull JetOperationExpression expression) {
220            FunctionDescriptor operationDescriptor = getFunctionDescriptorForOperationExpression(context.bindingContext(), expression);
221    
222            if (operationDescriptor == null) return true;
223            if (context.intrinsics().getFunctionIntrinsics().getIntrinsic(operationDescriptor).exists()) return true;
224    
225            return false;
226        }
227    
228        @NotNull
229        public static List<JsExpression> generateInvocationArguments(@NotNull JsExpression receiver, @NotNull List<JsExpression> arguments) {
230            if (arguments.isEmpty()) {
231                return Collections.singletonList(receiver);
232            }
233    
234            List<JsExpression> argumentList = new ArrayList<JsExpression>(1 + arguments.size());
235            argumentList.add(receiver);
236            argumentList.addAll(arguments);
237            return argumentList;
238        }
239    
240        public static boolean isCacheNeeded(@NotNull JsExpression expression) {
241            return !(expression instanceof JsLiteral) &&
242                   (!(expression instanceof JsNameRef) || ((JsNameRef) expression).getQualifier() != null);
243        }
244    
245        @NotNull
246        public static Pair<JsVars.JsVar, JsExpression> createTemporaryIfNeed(
247                @NotNull JsExpression expression,
248                @NotNull TranslationContext context
249        ) {
250            // don't create temp variable for simple expression
251            if (isCacheNeeded(expression)) {
252                return context.dynamicContext().createTemporary(expression);
253            }
254            else {
255                return Pair.create(null, expression);
256            }
257        }
258    
259        @NotNull
260        public static JsConditional sure(@NotNull JsExpression expression, @NotNull TranslationContext context) {
261            JsInvocation throwNPE = new JsInvocation(context.namer().throwNPEFunctionRef());
262            JsConditional ensureNotNull = notNullConditional(expression, throwNPE, context);
263    
264            JsExpression thenExpression = ensureNotNull.getThenExpression();
265            if (thenExpression instanceof JsNameRef) {
266                // associate (cache) ensureNotNull expression to new TemporaryConstVariable with same name.
267                context.associateExpressionToLazyValue(ensureNotNull,
268                                                       new TemporaryConstVariable(((JsNameRef) thenExpression).getName(), ensureNotNull));
269            }
270    
271            return ensureNotNull;
272        }
273    }