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
017package org.jetbrains.k2js.translate.operation;
018
019import com.google.dart.compiler.backend.js.ast.*;
020import org.jetbrains.annotations.NotNull;
021import org.jetbrains.annotations.Nullable;
022import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
023import org.jetbrains.jet.lang.psi.JetBinaryExpression;
024import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
025import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
026import org.jetbrains.jet.lexer.JetToken;
027import org.jetbrains.jet.lexer.JetTokens;
028import org.jetbrains.k2js.translate.context.TranslationContext;
029import org.jetbrains.k2js.translate.general.AbstractTranslator;
030import org.jetbrains.k2js.translate.intrinsic.operation.BinaryOperationIntrinsic;
031import org.jetbrains.k2js.translate.reference.CallBuilder;
032import org.jetbrains.k2js.translate.reference.CallType;
033import org.jetbrains.k2js.translate.utils.JsAstUtils;
034
035import static org.jetbrains.k2js.translate.operation.AssignmentTranslator.isAssignmentOperator;
036import static org.jetbrains.k2js.translate.operation.CompareToTranslator.isCompareToCall;
037import static org.jetbrains.k2js.translate.utils.BindingUtils.getFunctionDescriptorForOperationExpression;
038import static org.jetbrains.k2js.translate.utils.BindingUtils.getResolvedCall;
039import static org.jetbrains.k2js.translate.utils.JsAstUtils.not;
040import static org.jetbrains.k2js.translate.utils.PsiUtils.*;
041import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateLeftExpression;
042import static org.jetbrains.k2js.translate.utils.TranslationUtils.translateRightExpression;
043
044
045public 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}