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.bindingContextUtil.BindingContextUtilPackage;
027    import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
028    import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
029    import org.jetbrains.jet.lexer.JetToken;
030    import org.jetbrains.jet.lexer.JetTokens;
031    import org.jetbrains.k2js.translate.callTranslator.CallTranslator;
032    import org.jetbrains.k2js.translate.context.TemporaryVariable;
033    import org.jetbrains.k2js.translate.context.TranslationContext;
034    import org.jetbrains.k2js.translate.general.AbstractTranslator;
035    import org.jetbrains.k2js.translate.general.Translation;
036    import org.jetbrains.k2js.translate.intrinsic.operation.BinaryOperationIntrinsic;
037    import org.jetbrains.k2js.translate.utils.JsAstUtils;
038    import org.jetbrains.k2js.translate.utils.TranslationUtils;
039    
040    import static org.jetbrains.jet.lang.resolve.calls.callUtil.CallUtilPackage.getFunctionResolvedCallWithAssert;
041    import static org.jetbrains.k2js.translate.operation.AssignmentTranslator.isAssignmentOperator;
042    import static org.jetbrains.k2js.translate.operation.CompareToTranslator.isCompareToCall;
043    import static org.jetbrains.k2js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
044    import static org.jetbrains.k2js.translate.utils.JsAstUtils.not;
045    import static org.jetbrains.k2js.translate.utils.PsiUtils.*;
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        @NotNull
067        private final JetExpression leftJetExpression;
068    
069        @NotNull
070        private final JetExpression rightJetExpression;
071    
072        @Nullable
073        private final CallableDescriptor operationDescriptor;
074    
075        private BinaryOperationTranslator(@NotNull JetBinaryExpression expression,
076                @NotNull TranslationContext context) {
077            super(context);
078            this.expression = expression;
079    
080            assert expression.getLeft() != null : "Binary expression should have a left expression: " + expression.getText();
081            leftJetExpression = expression.getLeft();
082    
083            assert expression.getRight() != null : "Binary expression should have a right expression: " + expression.getText();
084            rightJetExpression = expression.getRight();
085    
086            this.operationDescriptor = getCallableDescriptorForOperationExpression(bindingContext(), expression);
087        }
088    
089        @NotNull
090        private JsExpression translate() {
091            BinaryOperationIntrinsic intrinsic = getIntrinsicForExpression();
092            if (intrinsic.exists()) {
093                return applyIntrinsic(intrinsic);
094            }
095            if (getOperationToken(expression).equals(JetTokens.ELVIS)) {
096                return translateElvis();
097            }
098            if (isAssignmentOperator(expression)) {
099                return AssignmentTranslator.translate(expression, context());
100            }
101            if (isNotOverloadable()) {
102                return translateAsUnOverloadableBinaryOperation();
103            }
104            if (isCompareToCall(expression, context())) {
105                return CompareToTranslator.translate(expression, context());
106            }
107            assert operationDescriptor != null :
108                    "Overloadable operations must have not null descriptor";
109            return translateAsOverloadedBinaryOperation();
110        }
111    
112        @NotNull
113        private JsExpression translateElvis() {
114            JsExpression leftExpression = Translation.translateAsExpression(leftJetExpression, context());
115            if (JsAstUtils.isEmptyExpression(leftExpression)) {
116                return leftExpression;
117            }
118    
119            JsBlock rightBlock = new JsBlock();
120            JsExpression rightExpression = Translation.translateAsExpression(rightJetExpression, context(), rightBlock);
121    
122            if (rightBlock.isEmpty()) {
123                return TranslationUtils.notNullConditional(leftExpression, rightExpression, context());
124            }
125    
126            JsExpression result;
127            JsIf ifStatement;
128            if (BindingContextUtilPackage.isUsedAsExpression(expression, context().bindingContext())) {
129                if (TranslationUtils.isCacheNeeded(leftExpression)) {
130                    TemporaryVariable resultVar = context().declareTemporary(leftExpression);
131                    result = resultVar.reference();
132                    context().addStatementToCurrentBlock(resultVar.assignmentExpression().makeStmt());
133                }
134                else {
135                    result = leftExpression;
136                }
137                JsExpression testExpression = TranslationUtils.isNullCheck(result);
138                if (!JsAstUtils.isEmptyExpression(rightExpression)) {
139                    rightBlock.getStatements().add(JsAstUtils.assignment(result, rightExpression).makeStmt());
140                }
141                ifStatement = JsAstUtils.newJsIf(testExpression, rightBlock);
142            }
143            else {
144                result = context().getEmptyExpression();
145                JsExpression testExpression = TranslationUtils.isNullCheck(leftExpression);
146                ifStatement = JsAstUtils.newJsIf(testExpression, rightBlock);
147            }
148            context().addStatementToCurrentBlock(ifStatement);
149            return result;
150        }
151    
152        @NotNull
153        private BinaryOperationIntrinsic getIntrinsicForExpression() {
154            return context().intrinsics().getBinaryOperationIntrinsics().getIntrinsic(expression, context());
155        }
156    
157        @NotNull
158        private JsExpression applyIntrinsic(@NotNull BinaryOperationIntrinsic intrinsic) {
159            JsExpression leftExpression = Translation.translateAsExpression(leftJetExpression, context());
160            if (JsAstUtils.isEmptyExpression(leftExpression)) {
161                return leftExpression;
162            }
163    
164            JsBlock rightBlock = new JsBlock();
165            JsExpression rightExpression = Translation.translateAsExpression(rightJetExpression, context(), rightBlock);
166    
167            if (rightBlock.isEmpty()) {
168                return intrinsic.apply(expression, leftExpression, rightExpression, context());
169            }
170    
171            if (JsAstUtils.isEmptyExpression(rightExpression)) {
172                if (TranslationUtils.isCacheNeeded(leftExpression)) {
173                    context().addStatementToCurrentBlock(leftExpression.makeStmt());
174                }
175                context().addStatementsToCurrentBlockFrom(rightBlock);
176                return context().getEmptyExpression();
177            }
178    
179            if (TranslationUtils.isCacheNeeded(leftExpression)) {
180                TemporaryVariable temporaryVariable = context().declareTemporary(null);
181                context().addStatementToCurrentBlock(JsAstUtils.assignment(temporaryVariable.reference(), leftExpression).makeStmt());
182                leftExpression = temporaryVariable.reference();
183            }
184            context().addStatementsToCurrentBlockFrom(rightBlock);
185    
186            return intrinsic.apply(expression, leftExpression, rightExpression, context());
187        }
188    
189        private boolean isNotOverloadable() {
190            return operationDescriptor == null;
191        }
192    
193        @NotNull
194        private JsExpression translateAsUnOverloadableBinaryOperation() {
195            JetToken token = getOperationToken(expression);
196            assert OperatorConventions.NOT_OVERLOADABLE.contains(token);
197            JsBinaryOperator operator = OperatorTable.getBinaryOperator(token);
198            JsExpression leftExpression = Translation.translateAsExpression(leftJetExpression, context());
199            if (JsAstUtils.isEmptyExpression(leftExpression)) {
200                return leftExpression;
201            }
202    
203            JsBlock rightBlock = new JsBlock();
204            JsExpression rightExpression = Translation.translateAsExpression(rightJetExpression, context(), rightBlock);
205    
206            if (rightBlock.isEmpty()) {
207                return new JsBinaryOperation(operator, leftExpression, rightExpression);
208            }
209    
210            assert token.equals(JetTokens.ANDAND) || token.equals(JetTokens.OROR) : "Unsupported binary operation: " + expression.getText();
211            boolean isOror = token.equals(JetTokens.OROR);
212            JsExpression literalResult = isOror ? JsLiteral.TRUE : JsLiteral.FALSE;
213            leftExpression = isOror ? not(leftExpression) : leftExpression;
214    
215            JsIf ifStatement;
216            JsExpression result;
217            if (BindingContextUtilPackage.isUsedAsExpression(expression, context().bindingContext())) {
218                if (!JsAstUtils.isEmptyExpression(rightExpression)) {
219                    if (rightExpression instanceof JsNameRef) {
220                        result = rightExpression; // Reuse tmp variable
221                    } else {
222                        TemporaryVariable resultVar = context().declareTemporary(rightExpression);
223                        result = resultVar.reference();
224                        rightBlock.getStatements().add(resultVar.assignmentExpression().makeStmt());
225                    }
226                    JsStatement assignmentStatement = JsAstUtils.assignment(result, literalResult).makeStmt();
227                    ifStatement = JsAstUtils.newJsIf(leftExpression, rightBlock, assignmentStatement);
228                }
229                else {
230                    ifStatement = JsAstUtils.newJsIf(leftExpression, rightBlock);
231                    result = literalResult;
232                }
233            }
234            else {
235                ifStatement = JsAstUtils.newJsIf(leftExpression, rightBlock);
236                result = context().getEmptyExpression();
237            }
238            context().addStatementToCurrentBlock(ifStatement);
239            return result;
240        }
241    
242    
243        @NotNull
244        private JsExpression translateAsOverloadedBinaryOperation() {
245            ResolvedCall<? extends FunctionDescriptor> resolvedCall = getFunctionResolvedCallWithAssert(expression, bindingContext());
246            JsExpression result = CallTranslator.INSTANCE$.translate(context(), resolvedCall, getReceiver());
247            return mayBeWrapWithNegation(result);
248        }
249    
250        @NotNull
251        private JsExpression getReceiver() {
252            if (isInOrNotInOperation(expression)) {
253                return Translation.translateAsExpression(rightJetExpression, context());
254            } else {
255                return Translation.translateAsExpression(leftJetExpression, context());
256            }
257        }
258    
259        @NotNull
260        private JsExpression mayBeWrapWithNegation(@NotNull JsExpression result) {
261            if (isNegatedOperation(expression)) {
262                return not(result);
263            }
264            else {
265                return result;
266            }
267        }
268    }