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.intrinsic.functions.factories;
018    
019    import com.google.common.base.Predicate;
020    import com.google.common.base.Predicates;
021    import com.google.common.collect.ImmutableMap;
022    import com.google.dart.compiler.backend.js.ast.*;
023    import com.google.dart.compiler.util.AstUtil;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
027    import org.jetbrains.jet.lang.resolve.name.Name;
028    import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
029    import org.jetbrains.jet.lexer.JetToken;
030    import org.jetbrains.k2js.translate.context.TemporaryVariable;
031    import org.jetbrains.k2js.translate.context.TranslationContext;
032    import org.jetbrains.k2js.translate.intrinsic.functions.basic.FunctionIntrinsic;
033    import org.jetbrains.k2js.translate.intrinsic.functions.patterns.DescriptorPredicate;
034    import org.jetbrains.k2js.translate.intrinsic.functions.patterns.NamePredicate;
035    import org.jetbrains.k2js.translate.operation.OperatorTable;
036    
037    import java.util.List;
038    
039    import static org.jetbrains.k2js.translate.intrinsic.functions.factories.NumberConversionFIF.INTEGER_NUMBER_TYPES;
040    import static org.jetbrains.k2js.translate.intrinsic.functions.patterns.PatternBuilder.pattern;
041    import static org.jetbrains.k2js.translate.utils.JsAstUtils.*;
042    
043    public 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    }