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