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.intrinsic.functions.factories;
018
019import com.google.common.base.Predicate;
020import com.google.common.base.Predicates;
021import com.google.common.collect.ImmutableMap;
022import com.google.dart.compiler.backend.js.ast.*;
023import com.google.dart.compiler.util.AstUtil;
024import org.jetbrains.annotations.NotNull;
025import org.jetbrains.annotations.Nullable;
026import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
027import org.jetbrains.jet.lang.resolve.name.Name;
028import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
029import org.jetbrains.jet.lexer.JetToken;
030import org.jetbrains.k2js.translate.context.TemporaryVariable;
031import org.jetbrains.k2js.translate.context.TranslationContext;
032import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic;
033import org.jetbrains.k2js.translate.intrinsic.functions.patterns.DescriptorPredicate;
034import org.jetbrains.k2js.translate.intrinsic.functions.patterns.NamePredicate;
035import org.jetbrains.k2js.translate.operation.OperatorTable;
036
037import java.util.List;
038
039import static org.jetbrains.k2js.translate.intrinsic.functions.factories.NumberConversionFIF.INTEGER_NUMBER_TYPES;
040import static org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern;
041import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
042
043public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory {
044    INSTANCE;
045
046    @NotNull
047    private static final FunctionIntrinsic RANGE_TO_INTRINSIC = new FunctionIntrinsic() {
048
049        @NotNull
050        @Override
051        public JsExpression apply(@Nullable JsExpression rangeStart, @NotNull List<JsExpression> arguments,
052                @NotNull TranslationContext context) {
053            assert arguments.size() == 1 : "RangeTo must have one argument.";
054            assert rangeStart != null;
055            JsExpression rangeEnd = arguments.get(0);
056            JsNameRef expr = AstUtil.newQualifiedNameRef("Kotlin.NumberRange");
057            HasArguments numberRangeConstructorInvocation = context.isEcma5() ? new JsInvocation(expr) : new JsNew(expr);
058            //TODO: add tests and correct expression for reversed ranges.
059            setArguments(numberRangeConstructorInvocation, rangeStart, rangeEnd);
060            return (JsExpression) numberRangeConstructorInvocation;
061        }
062    };
063
064    @NotNull
065    private static final FunctionIntrinsic INTEGER_DIVISION_INTRINSIC = new FunctionIntrinsic() {
066        @NotNull
067        @Override
068        public JsExpression apply(@Nullable JsExpression receiver,
069                @NotNull List<JsExpression> arguments,
070                @NotNull TranslationContext context) {
071            assert receiver != null;
072            TemporaryVariable left = context.declareTemporary(receiver);
073            assert arguments.size() == 1;
074            TemporaryVariable right = context.declareTemporary(arguments.get(0));
075            JsBinaryOperation divRes = new JsBinaryOperation(JsBinaryOperator.DIV, left.reference(), right.reference());
076            JsBinaryOperation modRes = new JsBinaryOperation(JsBinaryOperator.MOD, left.reference(), right.reference());
077            JsBinaryOperation fractionalPart = new JsBinaryOperation(JsBinaryOperator.DIV, modRes, right.reference());
078            return AstUtil.newSequence(left.assignmentExpression(), right.assignmentExpression(), subtract(divRes, fractionalPart));
079        }
080    };
081
082    @NotNull
083    private static final NamePredicate BINARY_OPERATIONS = new NamePredicate(OperatorConventions.BINARY_OPERATION_NAMES.values());
084    private static final DescriptorPredicate INT_WITH_BIT_OPERATIONS = pattern("Int.or|and|xor|shl|shr|ushr");
085
086    private static final ImmutableMap<String, JsBinaryOperator> BINARY_BITWISE_OPERATIONS = ImmutableMap.<String, JsBinaryOperator>builder()
087            .put("or", JsBinaryOperator.BIT_OR)
088            .put("and", JsBinaryOperator.BIT_AND)
089            .put("xor", JsBinaryOperator.BIT_XOR)
090            .put("shl", JsBinaryOperator.SHL)
091            .put("shr", JsBinaryOperator.SHR)
092            .put("ushr", JsBinaryOperator.SHRU)
093            .build();
094
095    @NotNull
096    @Override
097    public Predicate<FunctionDescriptor> getPredicate() {
098        //TODO: check that it is binary operation
099        return Predicates.or(INT_WITH_BIT_OPERATIONS,
100                             pattern(NamePredicate.PRIMITIVE_NUMBERS, BINARY_OPERATIONS),
101                             pattern("Boolean.or|and|xor"),
102                             pattern("String.plus"));
103    }
104
105    @NotNull
106    @Override
107    public FunctionIntrinsic getIntrinsic(@NotNull FunctionDescriptor descriptor) {
108        if (pattern(INTEGER_NUMBER_TYPES + ".div").apply(descriptor)) {
109            return INTEGER_DIVISION_INTRINSIC;
110        }
111        if (descriptor.getName().equals(Name.identifier("rangeTo"))) {
112            return RANGE_TO_INTRINSIC;
113        }
114        if (INT_WITH_BIT_OPERATIONS.apply(descriptor)) {
115            JsBinaryOperator op = BINARY_BITWISE_OPERATIONS.get(descriptor.getName().asString());
116            if (op != null) {
117                return new PrimitiveBinaryOperationFunctionIntrinsic(op);
118            }
119        }
120        JsBinaryOperator operator = getOperator(descriptor);
121        return new PrimitiveBinaryOperationFunctionIntrinsic(operator);
122    }
123
124    @NotNull
125    private static JsBinaryOperator getOperator(@NotNull FunctionDescriptor descriptor) {
126        JetToken token = OperatorConventions.BINARY_OPERATION_NAMES.inverse().get(descriptor.getName());
127        if (token == null) {
128            token = OperatorConventions.BOOLEAN_OPERATIONS.inverse().get(descriptor.getName());
129        }
130        if (token == null) {
131            assert descriptor.getName().asString().equals("xor");
132            return JsBinaryOperator.BIT_XOR;
133        }
134        return OperatorTable.getBinaryOperator(token);
135    }
136
137    private static class PrimitiveBinaryOperationFunctionIntrinsic extends FunctionIntrinsic {
138
139        @NotNull
140        private final JsBinaryOperator operator;
141
142        private PrimitiveBinaryOperationFunctionIntrinsic(@NotNull JsBinaryOperator operator) {
143            this.operator = operator;
144        }
145
146        @NotNull
147        @Override
148        public JsExpression apply(@Nullable JsExpression receiver,
149                @NotNull List<JsExpression> arguments,
150                @NotNull TranslationContext context) {
151            assert receiver != null;
152            assert arguments.size() == 1 : "Binary operator should have a receiver and one argument";
153            return new JsBinaryOperation(operator, receiver, arguments.get(0));
154        }
155    }
156}