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