package org.zwobble.couscous.backends.csharp.primitives; import com.google.common.collect.ImmutableMap; import org.zwobble.couscous.ast.ExpressionNode; import org.zwobble.couscous.ast.MethodCallNode; import org.zwobble.couscous.ast.Operator; import org.zwobble.couscous.ast.Receiver; import org.zwobble.couscous.ast.visitors.NodeTransformer; import org.zwobble.couscous.types.ScalarType; import org.zwobble.couscous.types.Types; import org.zwobble.couscous.values.InternalCouscousValue; import java.util.Map; import java.util.Optional; import static org.zwobble.couscous.ast.FieldAccessNode.fieldAccess; import static org.zwobble.couscous.ast.MethodCallNode.methodCall; import static org.zwobble.couscous.ast.MethodCallNode.staticMethodCall; import static org.zwobble.couscous.ast.OperationNode.operation; import static org.zwobble.couscous.ast.Operations.boxInt; import static org.zwobble.couscous.ast.Operations.integerSubtract; import static org.zwobble.couscous.types.Types.erasure; import static org.zwobble.couscous.util.Casts.tryCast; import static org.zwobble.couscous.util.ExtraLists.list; public class CsharpPrimitiveMethods { public static final NodeTransformer TRANSFORMER = NodeTransformer.builder() .transformExpression(CsharpPrimitiveMethods::transformExpression) .build(); private static Optional transformExpression(ExpressionNode expression) { return tryCast(MethodCallNode.class, expression) .flatMap(CsharpPrimitiveMethods::transformMethodCall); } private static Optional transformMethodCall(MethodCallNode call) { return call.getReceiver().accept(new Receiver.Mapper>() { @Override public Optional visit(ExpressionNode receiver) { return CsharpPrimitiveMethods.getPrimitiveMethod(erasure(receiver.getType()), call.getMethodName()) .map(method -> new PrimitiveInstanceMethodCall(method, receiver, call.getArguments())); } @Override public Optional visit(ScalarType receiver) { return CsharpPrimitiveMethods.getPrimitiveStaticMethod(receiver, call.getMethodName()) .map(method -> new PrimitiveStaticMethodCall(method, call.getArguments())); } }); } private static final Map BOOLEAN_METHODS; static { ImmutableMap.Builder methods = ImmutableMap.builder(); BOOLEAN_METHODS = methods.build(); } private static final Map BOXED_INT_METHODS; static { ImmutableMap.Builder methods = ImmutableMap.builder(); methods.put("toString", new PrimitiveInstanceMethod( (receiver, arguments) -> methodCall( receiver, "ToString", list(), Types.STRING ), Types.STRING )); BOXED_INT_METHODS = methods.build(); } private static final Map STRING_METHODS; static { ImmutableMap.Builder methods = ImmutableMap.builder(); methods.put("length", new PrimitiveInstanceMethod( (receiver, arguments) -> fieldAccess(receiver, "Length", Types.INT), Types.INT )); methods.put("substring", new PrimitiveInstanceMethod( (receiver, arguments) -> { ExpressionNode startIndex = arguments.get(0); if (arguments.size() == 1) { return methodCall( receiver, "Substring", list(startIndex), Types.STRING ); } else { ExpressionNode endIndex = arguments.get(1); ExpressionNode length = integerSubtract(endIndex, startIndex); // TODO: handle length going beyond end-of-string return methodCall( receiver, "Substring", list(startIndex, length), Types.STRING ); } }, Types.STRING )); methods.put("toLowerCase", new PrimitiveInstanceMethod( (receiver, arguments) -> methodCall( receiver, "ToLower", list(), Types.STRING ), Types.STRING )); methods.put("equals", new PrimitiveInstanceMethod( (receiver, arguments) -> methodCall( receiver, "Equals", arguments, Types.STRING ), Types.STRING )); STRING_METHODS = methods.build(); } private static final Map> METHODS = ImmutableMap.>builder() .put(Types.BOOLEAN, BOOLEAN_METHODS) .put(Types.STRING, STRING_METHODS) .put(Types.BOXED_INT, BOXED_INT_METHODS) .build(); private static final Map STATIC_INT_METHODS = ImmutableMap.builder() .put("toString", new PrimitiveStaticMethod( arguments -> methodCall( arguments.get(0), "ToString", list(), Types.STRING ), Types.STRING )) .put("parseInt", new PrimitiveStaticMethod( arguments -> { if (arguments.size() == 1 || arguments.size() == 2) { return staticMethodCall( ScalarType.topLevel("System.Convert"), "ToInt32", arguments, ScalarType.topLevel("int") ); } else { throw new IllegalArgumentException(); } }, Types.INT )) .put("valueOf", new PrimitiveStaticMethod( arguments -> boxInt(arguments.get(0)), Types.BOXED_INT )) .build(); private static final Map STATIC_STRING_METHODS = ImmutableMap.builder() .put("join", new PrimitiveStaticMethod( arguments -> staticMethodCall( ScalarType.topLevel("java.lang.String"), "join", arguments, Types.STRING ), Types.STRING )) .build(); private static final Map STATIC_INTERNAL_METHODS = ImmutableMap.builder() .put("same", new PrimitiveStaticMethod( arguments -> operation(Operator.EQUALS, arguments, Types.BOOLEAN), Types.BOOLEAN )) .build(); private static final Map> STATIC_METHODS = ImmutableMap.>builder() .put(Types.BOXED_INT, STATIC_INT_METHODS) .put(Types.STRING, STATIC_STRING_METHODS) .put(InternalCouscousValue.REF, STATIC_INTERNAL_METHODS) .build(); private static Optional getPrimitiveMethod(ScalarType type, String methodName) { return Optional.ofNullable(METHODS.get(type)) .flatMap(methodsForType -> Optional.ofNullable(methodsForType.get(methodName))); } private static Optional getPrimitiveStaticMethod(ScalarType type, String methodName) { return Optional.ofNullable(STATIC_METHODS.get(type)) .flatMap(methodsForType -> Optional.ofNullable(methodsForType.get(methodName))); } }