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.operation;
018    
019    import com.google.dart.compiler.backend.js.ast.*;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
023    import org.jetbrains.jet.lang.psi.JetBinaryExpression;
024    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
025    import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
026    import org.jetbrains.jet.lexer.JetToken;
027    import org.jetbrains.jet.lexer.JetTokens;
028    import org.jetbrains.k2js.translate.context.TranslationContext;
029    import org.jetbrains.k2js.translate.general.AbstractTranslator;
030    import org.jetbrains.k2js.translate.intrinsic.operation.BinaryOperationIntrinsic;
031    import org.jetbrains.k2js.translate.reference.CallBuilder;
032    import org.jetbrains.k2js.translate.reference.CallType;
033    import org.jetbrains.k2js.translate.utils.JsAstUtils;
034    
035    import static org.jetbrains.k2js.translate.operation.AssignmentTranslator.isAssignmentOperator;
036    import static org.jetbrains.k2js.translate.operation.CompareToTranslator.isCompareToCall;
037    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptorForOperationExpression;
038    import static org.jetbrains.k2js.translate.utils.BindingUtils.getResolvedCall;
039    import static org.jetbrains.k2js.translate.utils.JsAstUtils.not;
040    import static org.jetbrains.k2js.translate.utils.PsiUtils.*;
041    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateLeftExpression;
042    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateRightExpression;
043    
044    
045    public final class BinaryOperationTranslator extends AbstractTranslator {
046    
047        @NotNull
048        public static JsExpression translate(@NotNull JetBinaryExpression expression,
049                @NotNull TranslationContext context) {
050            return (new BinaryOperationTranslator(expression, context).translate());
051        }
052    
053        @NotNull
054        /*package*/ static JsExpression translateAsOverloadedCall(@NotNull JetBinaryExpression expression,
055                @NotNull TranslationContext context) {
056            return (new BinaryOperationTranslator(expression, context)).translateAsOverloadedBinaryOperation();
057        }
058    
059        @NotNull
060        private final JetBinaryExpression expression;
061    
062        @Nullable
063        private final FunctionDescriptor operationDescriptor;
064    
065        private BinaryOperationTranslator(@NotNull JetBinaryExpression expression,
066                @NotNull TranslationContext context) {
067            super(context);
068            this.expression = expression;
069            this.operationDescriptor =
070                    getFunctionDescriptorForOperationExpression(bindingContext(), expression);
071        }
072    
073        @NotNull
074        private JsExpression translate() {
075            BinaryOperationIntrinsic intrinsic = getIntrinsicForExpression();
076            if (intrinsic != null) {
077                return applyIntrinsic(intrinsic);
078            }
079            if (isElvisOperator(expression)) {
080                return translateAsElvisOperator(expression);
081            }
082            if (isAssignmentOperator(expression)) {
083                return AssignmentTranslator.translate(expression, context());
084            }
085            if (isNotOverloadable()) {
086                return translateAsUnOverloadableBinaryOperation();
087            }
088            if (isCompareToCall(expression, context())) {
089                return CompareToTranslator.translate(expression, context());
090            }
091            assert operationDescriptor != null :
092                    "Overloadable operations must have not null descriptor";
093            return translateAsOverloadedBinaryOperation();
094        }
095    
096        @Nullable
097        private BinaryOperationIntrinsic getIntrinsicForExpression() {
098            return context().intrinsics().getBinaryOperationIntrinsics().getIntrinsic(expression, context());
099        }
100    
101        @NotNull
102        private JsExpression applyIntrinsic(@NotNull BinaryOperationIntrinsic intrinsic) {
103            return intrinsic.apply(expression,
104                                   translateLeftExpression(context(), expression),
105                                   translateRightExpression(context(), expression),
106                                   context());
107        }
108    
109        private static boolean isElvisOperator(@NotNull JetBinaryExpression expression) {
110            return getOperationToken(expression).equals(JetTokens.ELVIS);
111        }
112    
113        //TODO: use some generic mechanism
114        @NotNull
115        private JsExpression translateAsElvisOperator(@NotNull JetBinaryExpression expression) {
116            JsExpression translatedLeft = translateLeftExpression(context(), expression);
117            JsExpression translatedRight = translateRightExpression(context(), expression);
118            JsBinaryOperation leftIsNotNull = JsAstUtils.inequality(translatedLeft, JsLiteral.NULL);
119            return new JsConditional(leftIsNotNull, translatedLeft, translatedRight);
120        }
121    
122        private boolean isNotOverloadable() {
123            return operationDescriptor == null;
124        }
125    
126        @NotNull
127        private JsExpression translateAsUnOverloadableBinaryOperation() {
128            JetToken token = getOperationToken(expression);
129            JsBinaryOperator operator = OperatorTable.getBinaryOperator(token);
130            assert OperatorConventions.NOT_OVERLOADABLE.contains(token);
131            JsExpression left = translateLeftExpression(context(), expression);
132            JsExpression right = translateRightExpression(context(), expression);
133            return new JsBinaryOperation(operator, left, right);
134        }
135    
136    
137        @NotNull
138        private JsExpression translateAsOverloadedBinaryOperation() {
139            CallBuilder callBuilder = setReceiverAndArguments();
140            ResolvedCall<?> resolvedCall = getResolvedCall(bindingContext(), expression.getOperationReference());
141            JsExpression result = callBuilder.resolvedCall(resolvedCall).type(CallType.NORMAL).translate();
142            return mayBeWrapWithNegation(result);
143        }
144    
145        @NotNull
146        private CallBuilder setReceiverAndArguments() {
147            CallBuilder callBuilder = CallBuilder.build(context());
148    
149            JsExpression leftExpression = translateLeftExpression(context(), expression);
150            JsExpression rightExpression = translateRightExpression(context(), expression);
151    
152            if (isInOrNotInOperation(expression)) {
153                return callBuilder.receiver(rightExpression).args(leftExpression);
154            }
155            else {
156                return callBuilder.receiver(leftExpression).args(rightExpression);
157            }
158        }
159    
160        @NotNull
161        private JsExpression mayBeWrapWithNegation(@NotNull JsExpression result) {
162            if (isNegatedOperation(expression)) {
163                return not(result);
164            }
165            else {
166                return result;
167            }
168        }
169    }