/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.intrinsic.functions.factories;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.patterns.DescriptorPredicate;
import org.jetbrains.kotlin.js.patterns.NamePredicate;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsic;
import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntrinsicWithReceiverComputed;
import org.jetbrains.kotlin.js.translate.operation.OperatorTable;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils;
import org.jetbrains.kotlin.lexer.KtToken;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import java.util.List;
import java.util.function.Predicate;
import static org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern;
public enum PrimitiveUnaryOperationFIF implements FunctionIntrinsicFactory {
INSTANCE;
private static final NamePredicate UNARY_OPERATIONS = new NamePredicate(OperatorNameConventions.UNARY_OPERATION_NAMES);
@NotNull
private static final DescriptorPredicate UNARY_OPERATION_FOR_PRIMITIVE_NUMBER =
pattern(NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS, UNARY_OPERATIONS);
@NotNull
private static final Predicate<FunctionDescriptor> PRIMITIVE_UNARY_OPERATION_NAMES =
UNARY_OPERATION_FOR_PRIMITIVE_NUMBER.or(pattern("Boolean.not")).or(pattern("Int|Short|Byte.inv"));
@NotNull
private static final DescriptorPredicate NO_PARAMETERS = descriptor -> !JsDescriptorUtils.hasParameters(descriptor);
@NotNull
private static final Predicate<FunctionDescriptor> PATTERN = PRIMITIVE_UNARY_OPERATION_NAMES.and(NO_PARAMETERS);
private static final DescriptorPredicate INC_OPERATION_FOR_INT = pattern("Int.inc");
private static final DescriptorPredicate DEC_OPERATION_FOR_INT = pattern("Int.dec");
private static final DescriptorPredicate INC_OPERATION_FOR_BYTE = pattern("Byte.inc");
private static final DescriptorPredicate DEC_OPERATION_FOR_BYTE = pattern("Byte.dec");
private static final DescriptorPredicate INC_OPERATION_FOR_SHORT = pattern("Short.inc");
private static final DescriptorPredicate DEC_OPERATION_FOR_SHORT = pattern("Short.dec");
@NotNull
private static final DescriptorPredicate INC_OPERATION_FOR_PRIMITIVE_NUMBER = pattern("Float|Double.inc()");
@NotNull
private static final DescriptorPredicate DEC_OPERATION_FOR_PRIMITIVE_NUMBER = pattern("Float|Double.dec()");
private static class IntOverflowIntrinsic extends FunctionIntrinsicWithReceiverComputed {
private final FunctionIntrinsicWithReceiverComputed underlyingIntrinsic;
public IntOverflowIntrinsic(FunctionIntrinsicWithReceiverComputed underlyingIntrinsic) {
this.underlyingIntrinsic = underlyingIntrinsic;
}
@NotNull
@Override
public JsExpression apply(
@Nullable JsExpression receiver,
@NotNull List<? extends JsExpression> arguments,
@NotNull TranslationContext context
) {
return JsAstUtils.toInt32(underlyingIntrinsic.apply(receiver, arguments, context));
}
}
private static class ShortOverflowIntrinsic extends FunctionIntrinsicWithReceiverComputed {
private final FunctionIntrinsicWithReceiverComputed underlyingIntrinsic;
public ShortOverflowIntrinsic(FunctionIntrinsicWithReceiverComputed underlyingIntrinsic) {
this.underlyingIntrinsic = underlyingIntrinsic;
}
@NotNull
@Override
public JsExpression apply(
@Nullable JsExpression receiver,
@NotNull List<? extends JsExpression> arguments,
@NotNull TranslationContext context
) {
return JsAstUtils.toShort(underlyingIntrinsic.apply(receiver, arguments, context));
}
}
private static class ByteOverflowIntrinsic extends FunctionIntrinsicWithReceiverComputed {
private final FunctionIntrinsicWithReceiverComputed underlyingIntrinsic;
public ByteOverflowIntrinsic(FunctionIntrinsicWithReceiverComputed underlyingIntrinsic) {
this.underlyingIntrinsic = underlyingIntrinsic;
}
@NotNull
@Override
public JsExpression apply(
@Nullable JsExpression receiver,
@NotNull List<? extends JsExpression> arguments,
@NotNull TranslationContext context
) {
return JsAstUtils.toByte(underlyingIntrinsic.apply(receiver, arguments, context));
}
}
@NotNull
private static final FunctionIntrinsicWithReceiverComputed NUMBER_INC_INTRINSIC = new FunctionIntrinsicWithReceiverComputed() {
@NotNull
@Override
public JsExpression apply(
@Nullable JsExpression receiver,
@NotNull List<? extends JsExpression> arguments,
@NotNull TranslationContext context
) {
assert receiver != null;
assert arguments.size() == 0;
return new JsBinaryOperation(JsBinaryOperator.ADD, receiver, context.program().getNumberLiteral(1));
}
};
@NotNull
private static final FunctionIntrinsicWithReceiverComputed NUMBER_DEC_INTRINSIC = new FunctionIntrinsicWithReceiverComputed() {
@NotNull
@Override
public JsExpression apply(
@Nullable JsExpression receiver,
@NotNull List<? extends JsExpression> arguments,
@NotNull TranslationContext context
) {
assert receiver != null;
assert arguments.size() == 0;
return new JsBinaryOperation(JsBinaryOperator.SUB, receiver, context.program().getNumberLiteral(1));
}
};
private static abstract class UnaryOperationInstrinsicBase extends FunctionIntrinsicWithReceiverComputed {
@NotNull
public abstract JsExpression doApply(@NotNull JsExpression receiver, @NotNull TranslationContext context);
@NotNull
@Override
public JsExpression apply(
@Nullable JsExpression receiver,
@NotNull List<? extends JsExpression> arguments,
@NotNull TranslationContext context
) {
assert receiver != null;
assert arguments.size() == 0;
return doApply(receiver, context);
}
}
@NotNull
private static final FunctionIntrinsic CHAR_PLUS = new UnaryOperationInstrinsicBase() {
@NotNull
@Override
public JsExpression doApply(
@NotNull JsExpression receiver, @NotNull TranslationContext context
) {
return JsAstUtils.charToInt(receiver);
}
};
@NotNull
private static final FunctionIntrinsic CHAR_MINUS = new UnaryOperationInstrinsicBase() {
@NotNull
@Override
public JsExpression doApply(
@NotNull JsExpression receiver, @NotNull TranslationContext context
) {
return new JsPrefixOperation(JsUnaryOperator.NEG, JsAstUtils.charToInt(receiver));
}
};
@NotNull
private static final FunctionIntrinsic CHAR_INC = new UnaryOperationInstrinsicBase() {
@NotNull
@Override
public JsExpression doApply(
@NotNull JsExpression receiver, @NotNull TranslationContext context
) {
return JsAstUtils.invokeKotlinFunction("charInc", receiver);
}
};
@NotNull
private static final FunctionIntrinsic CHAR_DEC = new UnaryOperationInstrinsicBase() {
@NotNull
@Override
public JsExpression doApply(
@NotNull JsExpression receiver, @NotNull TranslationContext context
) {
return JsAstUtils.invokeKotlinFunction("charDec", receiver);
}
};
@Nullable
@Override
public FunctionIntrinsic getIntrinsic(@NotNull FunctionDescriptor descriptor) {
if (!PATTERN.test(descriptor)) {
return null;
}
if (pattern("Char.unaryPlus()").test(descriptor)) {
return CHAR_PLUS;
}
if (pattern("Char.unaryMinus()").test(descriptor)) {
return CHAR_MINUS;
}
if (pattern("Char.plus()").test(descriptor)) {
return CHAR_PLUS;
}
if (pattern("Char.minus()").test(descriptor)) {
return CHAR_MINUS;
}
if (pattern("Char.inc()").test(descriptor)) {
return CHAR_INC;
}
if (pattern("Char.dec()").test(descriptor)) {
return CHAR_DEC;
}
if (INC_OPERATION_FOR_INT.test(descriptor)) {
return new IntOverflowIntrinsic(NUMBER_INC_INTRINSIC);
}
if (DEC_OPERATION_FOR_INT.test(descriptor)) {
return new IntOverflowIntrinsic(NUMBER_DEC_INTRINSIC);
}
if (INC_OPERATION_FOR_SHORT.test(descriptor)) {
return new ShortOverflowIntrinsic(NUMBER_INC_INTRINSIC);
}
if (DEC_OPERATION_FOR_SHORT.test(descriptor)) {
return new ShortOverflowIntrinsic(NUMBER_DEC_INTRINSIC);
}
if (INC_OPERATION_FOR_BYTE.test(descriptor)) {
return new ByteOverflowIntrinsic(NUMBER_INC_INTRINSIC);
}
if (DEC_OPERATION_FOR_BYTE.test(descriptor)) {
return new ByteOverflowIntrinsic(NUMBER_DEC_INTRINSIC);
}
if (INC_OPERATION_FOR_PRIMITIVE_NUMBER.test(descriptor)) {
return NUMBER_INC_INTRINSIC;
}
if (DEC_OPERATION_FOR_PRIMITIVE_NUMBER.test(descriptor)) {
return NUMBER_DEC_INTRINSIC;
}
Name name = descriptor.getName();
JsUnaryOperator jsOperator;
if ("inv".equals(name.asString())) {
jsOperator = JsUnaryOperator.BIT_NOT;
}
else {
KtToken jetToken = OperatorConventions.UNARY_OPERATION_NAMES.inverse().get(name);
jsOperator = OperatorTable.getUnaryOperator(jetToken);
}
JsUnaryOperator finalJsOperator = jsOperator;
return new FunctionIntrinsicWithReceiverComputed() {
@NotNull
@Override
public JsExpression apply(@Nullable JsExpression receiver,
@NotNull List<? extends JsExpression> arguments,
@NotNull TranslationContext context) {
assert receiver != null;
assert arguments.size() == 0 : "Unary operator should not have arguments.";
//NOTE: cannot use this for increment/decrement
return new JsPrefixOperation(finalJsOperator, receiver);
}
};
}
}