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