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.CallableDescriptor;
023    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
024    import org.jetbrains.jet.lang.psi.JetBinaryExpression;
025    import org.jetbrains.jet.lang.psi.JetExpression;
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.callTranslator.CallTranslator;
031    import org.jetbrains.k2js.translate.context.TemporaryVariable;
032    import org.jetbrains.k2js.translate.context.TranslationContext;
033    import org.jetbrains.k2js.translate.general.AbstractTranslator;
034    import org.jetbrains.k2js.translate.general.Translation;
035    import org.jetbrains.k2js.translate.intrinsic.operation.BinaryOperationIntrinsic;
036    import org.jetbrains.k2js.translate.utils.TranslationUtils;
037    import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
038    import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator;
039    
040    import static org.jetbrains.k2js.translate.operation.AssignmentTranslator.isAssignmentOperator;
041    import static org.jetbrains.k2js.translate.operation.CompareToTranslator.isCompareToCall;
042    import static org.jetbrains.k2js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
043    import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionResolvedCall;
044    import static org.jetbrains.k2js.translate.utils.JsAstUtils.convertToStatement;
045    import static org.jetbrains.k2js.translate.utils.JsAstUtils.not;
046    import static org.jetbrains.k2js.translate.utils.PsiUtils.*;
047    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateLeftExpression;
048    import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateRightExpression;
049    
050    
051    public final class BinaryOperationTranslator extends AbstractTranslator {
052    
053        @NotNull
054        public static JsExpression translate(@NotNull JetBinaryExpression expression,
055                @NotNull TranslationContext context) {
056            JsExpression jsExpression = new BinaryOperationTranslator(expression, context).translate();
057            return jsExpression.source(expression);
058        }
059    
060        @NotNull
061        /*package*/ static JsExpression translateAsOverloadedCall(@NotNull JetBinaryExpression expression,
062                @NotNull TranslationContext context) {
063            JsExpression jsExpression = (new BinaryOperationTranslator(expression, context)).translateAsOverloadedBinaryOperation();
064            return jsExpression.source(expression);
065        }
066    
067        @NotNull
068        private final JetBinaryExpression expression;
069    
070        @Nullable
071        private final CallableDescriptor operationDescriptor;
072    
073        private BinaryOperationTranslator(@NotNull JetBinaryExpression expression,
074                @NotNull TranslationContext context) {
075            super(context);
076            this.expression = expression;
077            this.operationDescriptor = getCallableDescriptorForOperationExpression(bindingContext(), expression);
078        }
079    
080        @NotNull
081        private JsExpression translate() {
082            BinaryOperationIntrinsic intrinsic = getIntrinsicForExpression();
083            if (intrinsic != null) {
084                return applyIntrinsic(intrinsic);
085            }
086            if (getOperationToken(expression).equals(JetTokens.ELVIS)) {
087                return translateElvis(expression);
088            }
089            if (isAssignmentOperator(expression)) {
090                return AssignmentTranslator.translate(expression, context());
091            }
092            if (isNotOverloadable()) {
093                return translateAsUnOverloadableBinaryOperation();
094            }
095            if (isCompareToCall(expression, context())) {
096                return CompareToTranslator.translate(expression, context());
097            }
098            assert operationDescriptor != null :
099                    "Overloadable operations must have not null descriptor";
100            return translateAsOverloadedBinaryOperation();
101        }
102    
103        @NotNull
104        private JsExpression translateElvis(JetBinaryExpression expression) {
105            JsExpression leftExpression = translateLeftExpression(context(), expression);
106    
107            JetExpression rightJetExpression = expression.getRight();
108            assert rightJetExpression != null : "Binary expression should have a right expression";
109            JsNode rightNode = Translation.translateExpression(rightJetExpression, context());
110    
111            if (rightNode instanceof JsExpression) {
112                return TranslationUtils.notNullConditional(leftExpression, (JsExpression) rightNode, context());
113            }
114    
115            TemporaryVariable result = context().declareTemporary(null);
116            AssignToExpressionMutator saveResultToTemporaryMutator = new AssignToExpressionMutator(result.reference());
117            context().addStatementToCurrentBlock(LastExpressionMutator.mutateLastExpression(leftExpression, saveResultToTemporaryMutator));
118    
119            JsExpression testExpression = TranslationUtils.isNullCheck(result.reference());
120            JsStatement thenStatement = convertToStatement(rightNode);
121            JsIf ifStatement = new JsIf(testExpression, thenStatement);
122            context().addStatementToCurrentBlock(ifStatement);
123    
124            return result.reference();
125        }
126    
127        @Nullable
128        private BinaryOperationIntrinsic getIntrinsicForExpression() {
129            return context().intrinsics().getBinaryOperationIntrinsics().getIntrinsic(expression, context());
130        }
131    
132        @NotNull
133        private JsExpression applyIntrinsic(@NotNull BinaryOperationIntrinsic intrinsic) {
134            return intrinsic.apply(expression,
135                                   translateLeftExpression(context(), expression),
136                                   translateRightExpression(context(), expression),
137                                   context());
138        }
139    
140        private boolean isNotOverloadable() {
141            return operationDescriptor == null;
142        }
143    
144        @NotNull
145        private JsExpression translateAsUnOverloadableBinaryOperation() {
146            JetToken token = getOperationToken(expression);
147            JsBinaryOperator operator = OperatorTable.getBinaryOperator(token);
148            assert OperatorConventions.NOT_OVERLOADABLE.contains(token);
149            JsExpression left = translateLeftExpression(context(), expression);
150            JsExpression right = translateRightExpression(context(), expression);
151            return new JsBinaryOperation(operator, left, right);
152        }
153    
154    
155        @NotNull
156        private JsExpression translateAsOverloadedBinaryOperation() {
157            ResolvedCall<? extends FunctionDescriptor> resolvedCall = getFunctionResolvedCall(bindingContext(), expression.getOperationReference());
158            JsExpression result = CallTranslator.instance$.translate(context(), resolvedCall, getReceiver());
159            return mayBeWrapWithNegation(result);
160        }
161    
162        @NotNull
163        private JsExpression getReceiver() {
164            if (isInOrNotInOperation(expression)) {
165                return translateRightExpression(context(), expression);
166            } else {
167                return translateLeftExpression(context(), expression);
168            }
169        }
170    
171        @NotNull
172        private JsExpression mayBeWrapWithNegation(@NotNull JsExpression result) {
173            if (isNegatedOperation(expression)) {
174                return not(result);
175            }
176            else {
177                return result;
178            }
179        }
180    }