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