001    /*
002     * Copyright 2010-2015 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.kotlin.js.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.JsBinaryOperation;
023    import com.google.dart.compiler.backend.js.ast.JsBinaryOperator;
024    import com.google.dart.compiler.backend.js.ast.JsExpression;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.annotations.Nullable;
027    import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
028    import org.jetbrains.kotlin.js.patterns.DescriptorPredicate;
029    import org.jetbrains.kotlin.js.patterns.NamePredicate;
030    import org.jetbrains.kotlin.js.translate.context.TranslationContext;
031    import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic;
032    import org.jetbrains.kotlin.js.translate.operation.OperatorTable;
033    import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
034    import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils;
035    import org.jetbrains.kotlin.lexer.KtToken;
036    import org.jetbrains.kotlin.name.Name;
037    import org.jetbrains.kotlin.types.expressions.OperatorConventions;
038    import org.jetbrains.kotlin.util.OperatorNameConventions;
039    
040    import java.util.List;
041    
042    import static org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern;
043    
044    public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory {
045        INSTANCE;
046    
047        private static abstract class BinaryOperationIntrinsicBase extends FunctionIntrinsic {
048            @NotNull
049            public abstract JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context);
050    
051            @NotNull
052            @Override
053            public JsExpression apply(
054                    @Nullable JsExpression receiver,
055                    @NotNull List<JsExpression> arguments,
056                    @NotNull TranslationContext context) {
057                assert receiver != null;
058                assert arguments.size() == 1;
059                return doApply(receiver, arguments.get(0), context);
060            }
061        }
062    
063        @NotNull
064        private static final BinaryOperationIntrinsicBase RANGE_TO_INTRINSIC = new BinaryOperationIntrinsicBase() {
065            @NotNull
066            @Override
067            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
068                //TODO: add tests and correct expression for reversed ranges.
069                return JsAstUtils.numberRangeTo(left, right);
070            }
071        };
072    
073        @NotNull
074        private static final BinaryOperationIntrinsicBase CHAR_RANGE_TO_INTRINSIC = new BinaryOperationIntrinsicBase() {
075            @NotNull
076            @Override
077            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
078                //TODO: add tests and correct expression for reversed ranges.
079                return JsAstUtils.charRangeTo(left, right);
080            }
081        };
082    
083        @NotNull
084        private static final BinaryOperationIntrinsicBase INTEGER_DIVISION_INTRINSIC = new BinaryOperationIntrinsicBase() {
085            @NotNull
086            @Override
087            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
088                JsBinaryOperation div = new JsBinaryOperation(JsBinaryOperator.DIV, left, right);
089                return JsAstUtils.toInt32(div);
090            }
091        };
092    
093        @NotNull
094        private static final BinaryOperationIntrinsicBase BUILTINS_COMPARE_TO_INTRINSIC = new BinaryOperationIntrinsicBase() {
095            @NotNull
096            @Override
097            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
098                return JsAstUtils.compareTo(left, right);
099            }
100        };
101    
102        @NotNull
103        private static final BinaryOperationIntrinsicBase PRIMITIVE_NUMBER_COMPARE_TO_INTRINSIC = new BinaryOperationIntrinsicBase() {
104            @NotNull
105            @Override
106            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
107                return JsAstUtils.primitiveCompareTo(left, right);
108            }
109        };
110    
111        @NotNull
112        private static final NamePredicate BINARY_OPERATIONS = new NamePredicate(OperatorConventions.BINARY_OPERATION_NAMES.values());
113        private static final DescriptorPredicate PRIMITIVE_NUMBERS_BINARY_OPERATIONS =
114                pattern(NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS, BINARY_OPERATIONS);
115    
116        private static final DescriptorPredicate PRIMITIVE_NUMBERS_COMPARE_TO_OPERATIONS =
117                pattern(NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS, "compareTo");
118        private static final DescriptorPredicate INT_WITH_BIT_OPERATIONS = pattern("Int.or|and|xor|shl|shr|ushr");
119        private static final DescriptorPredicate BOOLEAN_OPERATIONS = pattern("Boolean.or|and|xor");
120        private static final DescriptorPredicate STRING_PLUS = pattern("String.plus");
121    
122        private static final ImmutableMap<String, JsBinaryOperator> BINARY_BITWISE_OPERATIONS = ImmutableMap.<String, JsBinaryOperator>builder()
123                .put("or", JsBinaryOperator.BIT_OR)
124                .put("and", JsBinaryOperator.BIT_AND)
125                .put("xor", JsBinaryOperator.BIT_XOR)
126                .put("shl", JsBinaryOperator.SHL)
127                .put("shr", JsBinaryOperator.SHR)
128                .put("ushr", JsBinaryOperator.SHRU)
129                .build();
130    
131        private static final Predicate<FunctionDescriptor> PREDICATE = Predicates.or(PRIMITIVE_NUMBERS_BINARY_OPERATIONS, BOOLEAN_OPERATIONS,
132                                                                                     STRING_PLUS, INT_WITH_BIT_OPERATIONS,
133                                                                                     PRIMITIVE_NUMBERS_COMPARE_TO_OPERATIONS);
134    
135        @Nullable
136        @Override
137        public FunctionIntrinsic getIntrinsic(@NotNull FunctionDescriptor descriptor) {
138            if (pattern("Char.rangeTo(Char)").apply(descriptor)) {
139                return CHAR_RANGE_TO_INTRINSIC;
140            }
141    
142            if (PRIMITIVE_NUMBERS_COMPARE_TO_OPERATIONS.apply(descriptor)) {
143                return PRIMITIVE_NUMBER_COMPARE_TO_INTRINSIC;
144            }
145    
146    
147            if (JsDescriptorUtils.isBuiltin(descriptor) && descriptor.getName().equals(OperatorNameConventions.COMPARE_TO)) {
148                return BUILTINS_COMPARE_TO_INTRINSIC;
149            }
150    
151            if (!PREDICATE.apply(descriptor)) {
152                return null;
153            }
154    
155    
156            if (pattern("Int|Short|Byte.div(Int|Short|Byte)").apply(descriptor)) {
157                return INTEGER_DIVISION_INTRINSIC;
158            }
159            if (descriptor.getName().equals(Name.identifier("rangeTo"))) {
160                return RANGE_TO_INTRINSIC;
161            }
162            if (INT_WITH_BIT_OPERATIONS.apply(descriptor)) {
163                JsBinaryOperator op = BINARY_BITWISE_OPERATIONS.get(descriptor.getName().asString());
164                if (op != null) {
165                    return new PrimitiveBinaryOperationFunctionIntrinsic(op);
166                }
167            }
168            JsBinaryOperator operator = getOperator(descriptor);
169            BinaryOperationIntrinsicBase result = new PrimitiveBinaryOperationFunctionIntrinsic(operator);
170    
171            if (pattern("Char.plus|minus(Int)").apply(descriptor)) {
172                return new CharAndIntBinaryOperationFunctionIntrinsic(result);
173            }
174            if (pattern("Char.minus(Char)").apply(descriptor)) {
175                return new CharAndCharBinaryOperationFunctionIntrinsic(result);
176            }
177            return result;
178        }
179    
180        @NotNull
181        private static JsBinaryOperator getOperator(@NotNull FunctionDescriptor descriptor) {
182            KtToken token = OperatorConventions.BINARY_OPERATION_NAMES.inverse().get(descriptor.getName());
183            if (token == null) {
184                token = OperatorConventions.BOOLEAN_OPERATIONS.inverse().get(descriptor.getName());
185            }
186            if (token == null) {
187                assert descriptor.getName().asString().equals("xor");
188                return JsBinaryOperator.BIT_XOR;
189            }
190            return OperatorTable.getBinaryOperator(token);
191        }
192    
193        private static class PrimitiveBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase {
194    
195            @NotNull
196            private final JsBinaryOperator operator;
197    
198            private PrimitiveBinaryOperationFunctionIntrinsic(@NotNull JsBinaryOperator operator) {
199                this.operator = operator;
200            }
201    
202            @NotNull
203            @Override
204            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
205                return new JsBinaryOperation(operator, left, right);
206            }
207        }
208    
209        private static class CharAndIntBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase {
210    
211            @NotNull
212            private final BinaryOperationIntrinsicBase functionIntrinsic;
213    
214            private CharAndIntBinaryOperationFunctionIntrinsic(@NotNull BinaryOperationIntrinsicBase functionIntrinsic) {
215                this.functionIntrinsic = functionIntrinsic;
216            }
217    
218            @NotNull
219            @Override
220            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
221                return JsAstUtils.toChar(functionIntrinsic.doApply(JsAstUtils.charToInt(left), right, context));
222            }
223        }
224    
225        private static class CharAndCharBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase {
226    
227            @NotNull
228            private final BinaryOperationIntrinsicBase functionIntrinsic;
229    
230            private CharAndCharBinaryOperationFunctionIntrinsic(@NotNull BinaryOperationIntrinsicBase functionIntrinsic) {
231                this.functionIntrinsic = functionIntrinsic;
232            }
233    
234            @NotNull
235            @Override
236            public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) {
237                return functionIntrinsic.doApply(JsAstUtils.charToInt(left), JsAstUtils.charToInt(right), context);
238            }
239        }
240    }