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.TranslationUtils;
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.JsAstUtils.source;
041    import static org.jetbrains.k2js.translate.utils.PsiUtils.*;
042    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateLeftExpression;
043    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateRightExpression;
044    
045    
046    public final class BinaryOperationTranslator extends AbstractTranslator {
047    
048        @NotNull
049        public static JsExpression translate(@NotNull JetBinaryExpression expression,
050                @NotNull TranslationContext context) {
051            JsExpression jsExpression = new BinaryOperationTranslator(expression, context).translate();
052            return source(jsExpression, expression);
053        }
054    
055        @NotNull
056        /*package*/ static JsExpression translateAsOverloadedCall(@NotNull JetBinaryExpression expression,
057                @NotNull TranslationContext context) {
058            JsExpression jsExpression = (new BinaryOperationTranslator(expression, context)).translateAsOverloadedBinaryOperation();
059            return source(jsExpression, expression);
060        }
061    
062        @NotNull
063        private final JetBinaryExpression expression;
064    
065        @Nullable
066        private final FunctionDescriptor operationDescriptor;
067    
068        private BinaryOperationTranslator(@NotNull JetBinaryExpression expression,
069                @NotNull TranslationContext context) {
070            super(context);
071            this.expression = expression;
072            this.operationDescriptor =
073                    getFunctionDescriptorForOperationExpression(bindingContext(), expression);
074        }
075    
076        @NotNull
077        private JsExpression translate() {
078            BinaryOperationIntrinsic intrinsic = getIntrinsicForExpression();
079            if (intrinsic != null) {
080                return applyIntrinsic(intrinsic);
081            }
082            if (getOperationToken(expression).equals(JetTokens.ELVIS)) {
083                return TranslationUtils.notNullConditional(translateLeftExpression(context(), expression),
084                                                           translateRightExpression(context(), expression), context());
085            }
086            if (isAssignmentOperator(expression)) {
087                return AssignmentTranslator.translate(expression, context());
088            }
089            if (isNotOverloadable()) {
090                return translateAsUnOverloadableBinaryOperation();
091            }
092            if (isCompareToCall(expression, context())) {
093                return CompareToTranslator.translate(expression, context());
094            }
095            assert operationDescriptor != null :
096                    "Overloadable operations must have not null descriptor";
097            return translateAsOverloadedBinaryOperation();
098        }
099    
100        @Nullable
101        private BinaryOperationIntrinsic getIntrinsicForExpression() {
102            return context().intrinsics().getBinaryOperationIntrinsics().getIntrinsic(expression, context());
103        }
104    
105        @NotNull
106        private JsExpression applyIntrinsic(@NotNull BinaryOperationIntrinsic intrinsic) {
107            return intrinsic.apply(expression,
108                                   translateLeftExpression(context(), expression),
109                                   translateRightExpression(context(), expression),
110                                   context());
111        }
112    
113        private boolean isNotOverloadable() {
114            return operationDescriptor == null;
115        }
116    
117        @NotNull
118        private JsExpression translateAsUnOverloadableBinaryOperation() {
119            JetToken token = getOperationToken(expression);
120            JsBinaryOperator operator = OperatorTable.getBinaryOperator(token);
121            assert OperatorConventions.NOT_OVERLOADABLE.contains(token);
122            JsExpression left = translateLeftExpression(context(), expression);
123            JsExpression right = translateRightExpression(context(), expression);
124            return new JsBinaryOperation(operator, left, right);
125        }
126    
127    
128        @NotNull
129        private JsExpression translateAsOverloadedBinaryOperation() {
130            CallBuilder callBuilder = setReceiverAndArguments();
131            ResolvedCall<?> resolvedCall = getResolvedCall(bindingContext(), expression.getOperationReference());
132            JsExpression result = callBuilder.resolvedCall(resolvedCall).type(CallType.NORMAL).translate();
133            return mayBeWrapWithNegation(result);
134        }
135    
136        @NotNull
137        private CallBuilder setReceiverAndArguments() {
138            CallBuilder callBuilder = CallBuilder.build(context());
139    
140            JsExpression leftExpression = translateLeftExpression(context(), expression);
141            JsExpression rightExpression = translateRightExpression(context(), expression);
142    
143            if (isInOrNotInOperation(expression)) {
144                return callBuilder.receiver(rightExpression).args(leftExpression);
145            }
146            else {
147                return callBuilder.receiver(leftExpression).args(rightExpression);
148            }
149        }
150    
151        @NotNull
152        private JsExpression mayBeWrapWithNegation(@NotNull JsExpression result) {
153            if (isNegatedOperation(expression)) {
154                return not(result);
155            }
156            else {
157                return result;
158            }
159        }
160    }