All Downloads are FREE. Search and download functionalities are using the official Maven repository.

izumi.idealingua.translator.tocsharp.CSharpTranslator.scala Maven / Gradle / Ivy

The newest version!
package izumi.idealingua.translator.tocsharp

import izumi.fundamentals.platform.strings.IzString._
import izumi.idealingua.model.common.TypeId._
import izumi.idealingua.model.common.{Builtin, DomainId, TypeId, TypePath}
import izumi.idealingua.model.il.ast.typed.DefMethod.Output.{Algebraic, Alternative, Singular, Struct, Void}
import izumi.idealingua.model.il.ast.typed.TypeDef._
import izumi.idealingua.model.il.ast.typed.{DefMethod, _}
import izumi.idealingua.model.output.Module
import izumi.idealingua.model.typespace.Typespace
import izumi.idealingua.translator.CompilerOptions._
import izumi.idealingua.translator.tocsharp.extensions.{JsonNetExtension, NUnitExtension}
import izumi.idealingua.translator.tocsharp.products.CogenProduct._
import izumi.idealingua.translator.tocsharp.products.RenderableCogenProduct
import izumi.idealingua.translator.tocsharp.types.{CSharpClass, CSharpField, CSharpType}
import izumi.idealingua.translator.{Translated, Translator}

object CSharpTranslator {
  final val defaultExtensions = Seq(
    JsonNetExtension,
    NUnitExtension,
  )
}

class CSharpTranslator(ts: Typespace, options: CSharpTranslatorOptions) extends Translator {
  protected val ctx: CSTContext = new CSTContext(ts, options.extensions)

  import ctx._

  def translate(): Translated = {
    val modules = Seq(
      typespace.domain.types.flatMap(translateDef),
      typespace.domain.services.flatMap(translateService),
      typespace.domain.buzzers.flatMap(translateBuzzer),
    ).flatten

    Translated(ts, modules)
  }

  protected def translateService(definition: Service): Seq[Module] = {
    implicit val ts: Typespace          = this.ts
    implicit val imports: CSharpImports = CSharpImports(definition, definition.id.domain.toPackage, List.empty)
    ctx.modules.toSource(definition.id.domain, ctx.modules.toModuleId(definition.id), renderService(definition))
  }

  protected def translateBuzzer(definition: Buzzer): Seq[Module] = {
    implicit val ts: Typespace          = this.ts
    implicit val imports: CSharpImports = CSharpImports(definition, definition.id.domain.toPackage, List.empty)
    ctx.modules.toSource(definition.id.domain, ctx.modules.toModuleId(definition.id), renderBuzzer(definition))
  }

  protected def translateDef(definition: TypeDef): Seq[Module] = {
    implicit val ts: Typespace          = this.ts
    implicit val imports: CSharpImports = CSharpImports(definition, definition.id.path.toPackage)

    val defns = definition match {
      case i: Alias =>
        renderAlias(i)
      case i: Enumeration =>
        renderEnumeration(i)
      case i: Identifier =>
        renderIdentifier(i)
      case i: Interface =>
        renderInterface(i)
      case d: DTO =>
        renderDto(d)
      case d: Adt =>
        renderAdt(d)
      case _ =>
        RenderableCogenProduct.empty
    }

    ctx.modules.toSource(definition.id.path.domain, ctx.modules.toModuleId(definition), defns) ++ ext.postEmitModules(ctx, definition)
  }

  protected def renderDto(i: DTO)(implicit im: CSharpImports, ts: Typespace): RenderableCogenProduct = {
    val structure = ts.structure.structure(i)
    val struct    = CSharpClass(i.id, i.id.name, structure, List.empty)

    val dto =
      s"""${im.renderUsings()}
         |${ext.preModelEmit(ctx, i)}
         |${struct.renderHeader()} {
         |${struct.render(withWrapper = false, withSlices = true, withRTTI = true).shift(4)}
         |}
         |${ext.postModelEmit(ctx, i)}
       """.stripMargin

    CompositeProduct(dto, im.renderImports(List("System", "System.Collections", "System.Collections.Generic") ++ ext.imports(ctx, i).toList))
  }

  protected def renderAlias(i: Alias)(implicit im: CSharpImports, ts: Typespace): RenderableCogenProduct = {
    val cstype = CSharpType(i.target)

    AliasProduct(
      s"""// C# does not natively support full type aliases. They usually
         |// live only within the current file scope, making it impossible
         |// to make them type aliases within another namespace.
         |//
         |// Had it been fully supported, the code would be something like:
         |// using ${i.id.name} = ${cstype.renderType(true)}
         |//
         |// For the time being, please use the target type everywhere you need.
         """.stripMargin
    )
  }

  protected def renderAdtMember(adtName: String, member: AdtMember)(implicit im: CSharpImports, ts: Typespace): String = {
    val needsFQN = im.imports.find(i => i.id == member.typeId)
    // Sometimes we import something, but don't generate an using for it because
    // it has no ambiguous name with regards to the rest. However, methods for adt will
    // have the same name making it ambiguous, so we bail out here by providing
    // an FQN
    val nonambName = if (needsFQN.isDefined && needsFQN.get.usingName == "") CSharpType(member.typeId).renderType(true) else s"_${member.typename}"
    val operators =
      s"""    public static explicit operator $nonambName(${member.typename} m) {
         |        return m.Value;
         |    }
         |
         |    public static explicit operator ${member.typename}($nonambName m) {
         |        return new ${member.typename}(m);
         |    }
       """.stripMargin

    val operatorsDummy =
      s"""    // We would normally want to have an operator, but unfortunately if it is an interface,
         |    // it will fail on "user-defined conversions to or from an interface are not allowed".
         |    // public static explicit operator $nonambName(${member.typename} m) {
         |    //     return m.Value;
         |    // }
         |    //
         |    // public static explicit operator ${member.typename}($nonambName m) {
         |    //     return new ${member.typename}(m);
         |    // }
       """.stripMargin

    //    val memberType = CSharpType(member.typeId)
    s"""public sealed class ${member.typename}: $adtName {
       |    public $nonambName Value { get; private set; }
       |    public ${member.typename}($nonambName value) {
       |        this.Value = value;
       |    }
       |
       |    public override void Visit(I${adtName}Visitor visitor) {
       |        visitor.Visit(this);
       |    }
       |
       |${if (member.typeId.isInstanceOf[InterfaceId]) operatorsDummy else operators}
       |}
     """.stripMargin
  }

  protected def renderAdtUsings(m: AdtMember)(implicit im: CSharpImports, ts: Typespace): String = {
    s"using _${m.typename} = ${CSharpType(m.typeId).renderType(true)};"
  }

  protected def renderAdtImpl(adtName: String, members: List[AdtMember], renderUsings: Boolean = true)(implicit im: CSharpImports, ts: Typespace): String = {
    val adt = Adt(AdtId(TypePath(DomainId.Undefined, Seq.empty), adtName), members, NodeMeta.empty)
    s"""${im.renderUsings()}
       |${if (renderUsings) members.map(m => renderAdtUsings(m)).mkString("\n") else ""}
       |
       |${ext.preModelEmit(ctx, adt)}
       |public abstract class $adtName {
       |    public interface I${adtName}Visitor {
       |${members.map(m => s"        void Visit(${m.typename} visitor);").mkString("\n")}
       |    }
       |
       |    public abstract void Visit(I${adtName}Visitor visitor);
       |    private $adtName() {}
       |
       |${members.map(m => renderAdtMember(adtName, m)).mkString("\n").shift(4)}
       |}
       |${ext.postModelEmit(ctx, adt)}
     """.stripMargin
  }

  protected def renderServiceMethodAlternativeOutput(name: String, at: Alternative, success: Boolean)(implicit im: CSharpImports, ts: Typespace): String = {
    if (success)
      at.success match {
        case _: Algebraic => s"${name}Success" /*ts.tools.toNegativeBranchName(alternative.failure.)*/
        case _: Struct    => s"${name}Success"
        case si: Singular => CSharpType(si.typeId).renderType(true)
        case _            => throw new Exception("Not supported alternative non singular or algebraic " + at.success.toString)
      }
    else
      at.failure match {
        case _: Algebraic => s"${name}Failure" /*ts.tools.toNegativeBranchName(alternative.failure.)*/
        case _: Struct    => s"${name}Failure"
        case si: Singular => CSharpType(si.typeId).renderType(true)
        case _            => throw new Exception("Not supported alternative non singular or algebraic " + at.failure.toString)
      }
  }

  protected def renderServiceMethodAlternativeOutputTypeId(typePath: TypePath, name: String, at: Alternative, success: Boolean): TypeId = {
    if (success)
      at.success match {
        case _: Algebraic => new AdtId(TypePath(typePath.domain, Seq(s"${name}Success")), s"${name}Success")
        case _: Struct    => new DTOId(TypePath(typePath.domain, Seq(s"${name}Success")), s"${name}Success")
        case si: Singular => si.typeId
        case _            => throw new Exception("Not supported alternative non singular or algebraic " + at.success.toString)
      }
    else
      at.failure match {
        case _: Algebraic => new AdtId(TypePath(typePath.domain, Seq(s"${name}Failure")), s"${name}Failure")
        case _: Struct    => new DTOId(TypePath(typePath.domain, Seq(s"${name}Failure")), s"${name}Failure")
        case si: Singular => si.typeId
        case _            => throw new Exception("Not supported alternative non singular or algebraic " + at.failure.toString)
      }
  }

  protected def renderAlternativeType(name: String, alternative: Alternative)(implicit im: CSharpImports, ts: Typespace): String = {
    val leftType  = renderServiceMethodAlternativeOutput(name, alternative, success = false)
    val rightType = renderServiceMethodAlternativeOutput(name, alternative, success = true)

    s"Either<$leftType, $rightType> "
  }

  protected def renderAlternativeImpl(structId: DTOId, name: String, alternative: Alternative)(implicit im: CSharpImports, ts: Typespace): String = {
    // val leftType = renderServiceMethodAlternativeOutput(name, alternative, success = false)
    // val leftTypeId = renderServiceMethodAlternativeOutputTypeId(structId.path, name, alternative, success = false)
    val left = alternative.failure match {
      case al: Algebraic => renderAdtImpl(renderServiceMethodAlternativeOutput(name, alternative, success = false), al.alternatives, renderUsings = false)
      case st: Struct    => renderServiceMethodInModel(new DTOId(structId.path, structId.name + "Failure"), st.struct)
      case _             => ""
    }

    // val rightType = renderServiceMethodAlternativeOutput(name, alternative, success = true)
    // val rightTypeId = renderServiceMethodAlternativeOutputTypeId(structId.path, name, alternative, success = true)
    val right = alternative.success match {
      case al: Algebraic => renderAdtImpl(renderServiceMethodAlternativeOutput(name, alternative, success = true), al.alternatives, renderUsings = false)
      case st: Struct    => renderServiceMethodInModel(new DTOId(structId.path, structId.name + "Success"), st.struct)
      case _             => ""
    }

    /* // This is last resort here where we would need to actually create an instance of a class
    s"""$left
       |$right
       |${ext.preModelEmit(ctx, name, alternative)}
       |type public abstract class $name: Either<$leftType, $rightType> {
       |}
       |${ext.postModelEmit(ctx, name, alternative, leftTypeId, rightTypeId)}
     """.stripMargin
     */
    s"""$left
       |$right
     """.stripMargin
  }

  protected def renderAdt(i: Adt)(implicit im: CSharpImports, ts: Typespace): RenderableCogenProduct = {

    AdtProduct(renderAdtImpl(i.id.name, i.alternatives), im.renderImports(ext.imports(ctx, i).toList))
  }

  protected def renderEnumeration(i: Enumeration)(implicit im: CSharpImports, ts: Typespace): RenderableCogenProduct = {
    val name = i.id.name
    // Alternative way using reflection for From method:
    // return ($name)Enum.Parse(typeof($name), value);
    val decl: String =
      s"""// $name Enumeration
         |public enum $name {
         |${i.members.map(_.value).map(m => s"$m${if (m == i.members.last.value) "" else ","}").mkString("\n").shift(4)}
         |}
         |
         |public static class ${name}Helpers {
         |    public static $name From(string value) {
         |        switch (value) {
         |${i.members.map(_.value).map(m => s"""case \"$m\": return $name.$m;""").mkString("\n").shift(12)}
         |            default:
         |                throw new ArgumentOutOfRangeException();
         |        }
         |    }
         |
         |    public static bool IsValid(string value) {
         |        return Enum.IsDefined(typeof($name), value);
         |    }
         |
         |    // The elements in the array are still changeable, please use with care.
         |    private static readonly $name[] all = new $name[] {
         |${i.members.map(_.value).map(m => s"$name.$m${if (m == i.members.last.value) "" else ","}").mkString("\n").shift(8)}
         |    };
         |
         |    public static $name[] GetAll() {
         |        return ${name}Helpers.all;
         |    }
         |
         |    // Extensions
         |
         |    public static string ToString(this $name e) {
         |        return Enum.GetName(typeof($name), e);
         |    }
         |}
         |
         |${ext.postModelEmit(ctx, i)}
         |""".stripMargin

    EnumProduct(
      decl,
      im.renderImports(List("System") ++ ext.imports(ctx, i)),
      "",
    )
  }

  protected def renderIdentifier(i: Identifier)(implicit im: CSharpImports, ts: Typespace): RenderableCogenProduct = {

    val fields       = ts.structure.structure(i).all.map(f => CSharpField(f.field, i.id.name))
    val fieldsSorted = fields.sortBy(_.name)
    val csClass      = CSharpClass(i.id, i.id.name, fields)
    val prefixLength = i.id.name.length + 1

    val decl =
      s"""${im.renderUsings()}
         |${ext.preModelEmit(ctx, i)}
         |${csClass.renderHeader()} {
         |    private static char[] idSplitter = new char[]{':'};
         |${csClass.render(withWrapper = false, withSlices = false, withRTTI = true).shift(4)}
         |    public override string ToString() {
         |        var suffix = ${fieldsSorted.map(f => f.tp.renderToString(f.renderMemberName(), escape = true)).mkString(" + \":\" + ")};
         |        return "${i.id.name}#" + suffix;
         |    }
         |
         |    public static ${i.id.name} From(string value) {
         |        if (value == null) {
         |            throw new ArgumentNullException("value");
         |        }
         |
         |        if (!value.StartsWith("${i.id.name}#", StringComparison.Ordinal)) {
         |            throw new ArgumentException(string.Format("Expected identifier for type ${i.id.name}, got {0}", value));
         |        }
         |
         |        var parts = value.Substring($prefixLength, value.Length - $prefixLength).Split(idSplitter, StringSplitOptions.None);
         |        if (parts.Length != ${fields.length}) {
         |            throw new ArgumentException(string.Format("Expected identifier for type ${i.id.name} with ${fields.length} parts, got {0} in string {1}", parts.Length, value));
         |        }
         |
         |        var res = new ${i.id.name}();
         |${fieldsSorted.zipWithIndex.map { case (f, index) => s"res.${f.renderMemberName()} = ${f.tp.renderFromString(s"parts[$index]", unescape = true)};" }
          .mkString("\n").shift(8)}
         |        return res;
         |    }
         |}
         |
         |${ext.postModelEmit(ctx, i)}
         """.stripMargin

    IdentifierProduct(
      decl,
      im.renderImports(List("System", "System.Collections", "System.Collections.Generic") ++ ext.imports(ctx, i)),
    )
  }

  protected def renderInterface(i: Interface)(implicit im: CSharpImports, ts: Typespace): RenderableCogenProduct = {
    val structure = typespace.structure.structure(i)
    val eid       = typespace.tools.implId(i.id)

    val parentIfaces = ts.inheritance.parentsInherited(i.id).filter(_ != i.id)
    val validFields  = structure.all.filterNot(f => parentIfaces.contains(f.defn.definedBy))
    val ifaceFields =
      validFields.map(f => (f.defn.variance.nonEmpty, CSharpField( /*if (f.defn.variance.nonEmpty) f.defn.variance.last else */ f.field, eid.name, Seq.empty)))

    val struct = CSharpClass(eid, i.id.name + eid.name, structure, List(i.id))
    val ifaceImplements =
      if (i.struct.superclasses.interfaces.isEmpty) ": IRTTI"
      else
        ": " +
        i.struct.superclasses.interfaces.map(ifc => ifc.name).mkString(", ") + ", IRTTI"
    val dto = DTO(eid, Structure(validFields.map(f => f.field), List.empty, Super(List(i.id), List.empty, List.empty)), NodeMeta.empty)

    val iface =
      s"""${im.renderUsings()}
         |${ext.preModelEmit(ctx, i)}
         |public interface ${i.id.name}$ifaceImplements {
         |${ifaceFields
          .map(f => s"${if (f._1) "// Would have been covariance, but C# doesn't support it:\n// " else ""}${f._2.renderMember(true)}").mkString("\n").shift(4)}
         |}
         |${ext.postModelEmit(ctx, i)}
       """.stripMargin

    val companion =
      s"""${ext.preModelEmit(ctx, dto)}
         |${struct.renderHeader()} {
         |${struct.render(withWrapper = false, withSlices = true, withRTTI = true, withCTORs = Some(i.id.name)).shift(4)}
         |}
         |${ext.postModelEmit(ctx, dto)}
       """.stripMargin

    InterfaceProduct(
      iface,
      companion,
      im.renderImports(List("IRT", "System", "System.Collections", "System.Collections.Generic", "System.Reflection") ++ ext.imports(ctx, i).toList),
    )
  }

  protected def isServiceMethodReturnExistent(method: DefMethod.RPCMethod): Boolean = method.signature.output match {
    case _: Void => false
    case _       => true
  }

  protected def renderRPCMethodSignature(svcOrBuzzer: String, method: DefMethod, forClient: Boolean)(implicit imports: CSharpImports, ts: Typespace): String = {
    method match {
      case m: DefMethod.RPCMethod => {
        val returnValue = if (isServiceMethodReturnExistent(method.asInstanceOf[DefMethod.RPCMethod])) s"<${renderRPCMethodOutputSignature(svcOrBuzzer, m)}>" else ""

        val callback =
          s"${if (m.signature.input.fields.isEmpty) "" else ", "}Action$returnValue onSuccess, Action onFailure, Action onAny = null, C ctx = null"
        val fields  = m.signature.input.fields.map(f => CSharpType(f.typeId).renderType(true) + " " + CSharpField.safeVarName(f.name)).mkString(", ")
        val context = s"C ctx${if (m.signature.input.fields.isEmpty) "" else ", "}"
        if (forClient) {
          s"void ${m.name.capitalize}($fields$callback)"
        } else {
          s"${renderRPCMethodOutputSignature(svcOrBuzzer, m)} ${m.name.capitalize}($context$fields)"
        }
      }
    }
  }

  protected def renderRPCMethodOutputModel(svcOrBuzzer: String, method: DefMethod.RPCMethod)(implicit imports: CSharpImports, ts: Typespace): String =
    method.signature.output match {
      case _: Struct       => s"$svcOrBuzzer.Out${method.name.capitalize}"
      case _: Algebraic    => s"$svcOrBuzzer.Out${method.name.capitalize}"
      case si: Singular    => s"${CSharpType(si.typeId).renderType(true)}"
      case _: Void         => "void"
      case at: Alternative => renderAlternativeType(s"$svcOrBuzzer.Out${method.name.capitalize}", at)
    }

  protected def renderRPCMethodOutputSignature(svcOrBuzzer: String, method: DefMethod.RPCMethod)(implicit imports: CSharpImports, ts: Typespace): String = {
    s"${renderRPCMethodOutputModel(svcOrBuzzer, method)}"
  }

  protected def renderRPCClientMethod(svcOrBuzzer: String, method: DefMethod)(implicit imports: CSharpImports, ts: Typespace): String = method match {
    case m: DefMethod.RPCMethod =>
      m.signature.output match {
        case _: Struct | _: Algebraic | _: Alternative =>
          s"""public ${renderRPCMethodSignature(svcOrBuzzer, method, forClient = true)} {
             |    ${
              if (m.signature.input.fields.isEmpty) "// No input params for this method"
              else s"var inData = new $svcOrBuzzer.In${m.name.capitalize}(${m.signature.input.fields.map(ff => CSharpField.safeVarName(ff.name)).mkString(", ")});"
            }
             |    Transport.Send<${if (m.signature.input.fields.nonEmpty) s"$svcOrBuzzer.In${m.name.capitalize}" else "object"}, ${renderRPCMethodOutputModel(
              svcOrBuzzer,
              m,
            )}>("$svcOrBuzzer", "${m.name}", ${if (m.signature.input.fields.isEmpty) "null" else "inData"},
             |        new ClientTransportCallback<${renderRPCMethodOutputModel(svcOrBuzzer, m)}>(onSuccess, onFailure, onAny), ctx);
             |}
       """.stripMargin

        case _: Singular =>
          s"""public ${renderRPCMethodSignature(svcOrBuzzer, method, forClient = true)} {
             |    ${
              if (m.signature.input.fields.isEmpty) "// No input params for this method"
              else s"var inData = new $svcOrBuzzer.In${m.name.capitalize}(${m.signature.input.fields.map(ff => CSharpField.safeVarName(ff.name)).mkString(", ")});"
            }
             |    Transport.Send<${if (m.signature.input.fields.nonEmpty) s"$svcOrBuzzer.In${m.name.capitalize}" else "object"}, ${renderRPCMethodOutputModel(
              svcOrBuzzer,
              m,
            )}>("$svcOrBuzzer", "${m.name}", ${if (m.signature.input.fields.isEmpty) "null" else "inData"},
             |        new ClientTransportCallback<${renderRPCMethodOutputModel(svcOrBuzzer, m)}>(onSuccess, onFailure, onAny), ctx);
             |}
       """.stripMargin

        case _: Void =>
          s"""public ${renderRPCMethodSignature(svcOrBuzzer, method, forClient = true)} {
             |    ${
              if (m.signature.input.fields.isEmpty) "// No input params for this method"
              else s"var inData = new $svcOrBuzzer.In${m.name.capitalize}(${m.signature.input.fields.map(ff => CSharpField.safeVarName(ff.name)).mkString(", ")});"
            }
             |    Transport.Send<${if (m.signature.input.fields.nonEmpty) s"$svcOrBuzzer.In${m.name.capitalize}" else "object"}, IRT.Void>("$svcOrBuzzer", "${m.name}", ${
              if (m.signature.input.fields.isEmpty) "null" else "inData"
            },
             |        new ClientTransportCallback(_ => onSuccess(), onFailure, onAny), ctx);
             |}
       """.stripMargin
      }
  }

  protected def renderServiceClient(i: Service)(implicit imports: CSharpImports, ts: Typespace): String = {
    val name = s"${i.id.name}Client"

    s"""public interface I$name where C: class, IClientTransportContext {
       |${i.methods.map(m => renderRPCMethodSignature(i.id.name, m, forClient = true) + ";").mkString("\n").shift(4)}
       |}
       |
       |public class ${name}Generic: I$name where C: class, IClientTransportContext {
       |    public IClientTransport Transport { get; private set; }
       |
       |    public ${name}Generic(IClientTransport t) {
       |        Transport = t;
       |    }
       |
       |    public void SetHTTPTransport(string endpoint, IJsonMarshaller marshaller, bool blocking = false, int timeout = 60) {
       |        if (blocking) {
       |            this.Transport = new SyncHttpTransportGeneric(endpoint, marshaller, timeout);
       |        } else {
       |            this.Transport = new AsyncHttpTransportGeneric(endpoint, marshaller, timeout);
       |        }
       |    }
       |${i.methods.map(me => renderRPCClientMethod(i.id.name, me)).mkString("\n").shift(4)}
       |}
       |
       |public class $name: ${name}Generic {
       |    public $name(IClientTransport t): base(t) {}
       |}
     """.stripMargin
  }

  protected def renderRPCDispatcherHandler(svcOrBuzzer: String, method: DefMethod, server: String)(implicit imports: CSharpImports, ts: Typespace): String =
    method match {
      case m: DefMethod.RPCMethod =>
        if (isServiceMethodReturnExistent(m))
          s"""case "${m.name}": {
             |    ${
              if (m.signature.input.fields.isEmpty) "// No input params for this method"
              else s"var obj = marshaller.Unmarshal<${if (m.signature.input.fields.nonEmpty) s"$svcOrBuzzer.In${m.name.capitalize}" else "object"}>(data);"
            }
             |    return marshaller.Marshal<${renderRPCMethodOutputModel(svcOrBuzzer, m)}>(\n        $server.${m.name.capitalize}(ctx${
              if (m.signature.input.fields.isEmpty) "" else ", "
            }${m.signature.input.fields.map(f => s"obj.${f.name.capitalize}").mkString(", ")})\n    );
             |}
         """.stripMargin
        else
          s"""case "${m.name}": {
             |    ${
              if (m.signature.input.fields.isEmpty) "// No input params for this method"
              else s"var obj = marshaller.Unmarshal<${if (m.signature.input.fields.nonEmpty) s"$svcOrBuzzer.In${m.name.capitalize}" else "object"}>(data);"
            }
             |    $server.${m.name.capitalize}(ctx${if (m.signature.input.fields.isEmpty) "" else ", "}${m.signature.input.fields
              .map(f => s"obj.${f.name.capitalize}").mkString(", ")});
             |    return marshaller.Marshal(null);
             |}
       """.stripMargin
    }

  protected def renderServiceDispatcher(i: Service)(implicit imports: CSharpImports, ts: Typespace): String = {
    s"""public interface I${i.id.name}Server {
       |${i.methods.map(m => renderRPCMethodSignature(i.id.name, m, forClient = false) + ";").mkString("\n").shift(4)}
       |}
       |
       |public class ${i.id.name}Dispatcher: IServiceDispatcher {
       |    private static readonly string[] methods = { ${i.methods
        .map(m => if (m.isInstanceOf[DefMethod.RPCMethod]) "\"" + m.asInstanceOf[DefMethod.RPCMethod].name + "\"" else "").mkString(", ")} };
       |    protected IMarshaller marshaller;
       |    protected I${i.id.name}Server server;
       |
       |    public ${i.id.name}Dispatcher(IMarshaller marshaller, I${i.id.name}Server server) {
       |        this.marshaller = marshaller;
       |        this.server = server;
       |    }
       |
       |    public string GetSupportedService() {
       |        return "${i.id.name}";
       |    }
       |
       |    public string[] GetSupportedMethods() {
       |        return ${i.id.name}Dispatcher.methods;
       |    }
       |
       |    public D Dispatch(C ctx, string method, D data) {
       |        switch(method) {
       |${i.methods.map(m => renderRPCDispatcherHandler(i.id.name, m, "server")).mkString("\n").shift(12)}
       |            default:
       |                throw new DispatcherException(string.Format("Method {0} is not supported by ${i.id.name}Dispatcher.", method));
       |        }
       |    }
       |}
     """.stripMargin
  }

  protected def renderRPCDummyMethod(svcOrBuzzer: String, member: DefMethod, virtual: Boolean)(implicit imports: CSharpImports, ts: Typespace): String = {
    val retValue = member match {
      case m: DefMethod.RPCMethod =>
        m.signature.output match {
          case _: Struct | _: Algebraic | _: Alternative => "return null;"
          case s: Singular                               => "return " + CSharpType(s.typeId).defaultValue + ";";
          case _: Void                                   => "// Nothing to return"
        }
      case _ => throw new Exception("Unsupported renderServiceServerDummyMethod case.")
    }
    s"""public ${if (virtual) "virtual " else ""}${renderRPCMethodSignature(svcOrBuzzer, member, forClient = false)} {
       |    $retValue
       |}
     """.stripMargin
  }

  protected def renderServiceServerBase(i: Service)(implicit imports: CSharpImports, ts: Typespace): String = {
    val name = s"${i.id.name}Server"
    s"""public abstract class $name: ${i.id.name}Dispatcher,  I${i.id.name}Server {
       |    public $name(IMarshaller marshaller): base(marshaller, null) {
       |        server = this;
       |    }
       |
       |${i.methods.map(m => renderRPCDummyMethod(i.id.name, m, virtual = true)).mkString("\n").shift(4)}
       |}
     """.stripMargin
  }

  protected def outputToAdtMember(out: DefMethod.Output): List[AdtMember] = out match {
    case si: Singular  => List(AdtMember(si.typeId, None, NodeMeta.empty))
    case al: Algebraic => al.alternatives
    case _: Struct     => List.empty
    case _             => throw new Exception("Output type to TypeId is not supported for non singular or void types. " + out)
  }

  protected def renderServiceMethodOutModel(i: Service, name: String, out: DefMethod.Output)(implicit imports: CSharpImports, ts: Typespace): String = out match {
    case st: Struct      => renderServiceMethodInModel(i, name, st.struct)
    case al: Algebraic   => renderAdtImpl(name, al.alternatives, renderUsings = false)
    case si: Singular    => s"// ${si.typeId}"
    case _: Void         => ""
    case at: Alternative => renderAlternativeImpl(DTOId(i.id, name), name, at)
  }

  protected def renderBuzzerMethodOutModel(i: Buzzer, name: String, out: DefMethod.Output)(implicit imports: CSharpImports, ts: Typespace): String = out match {
    case st: Struct      => renderBuzzerMethodInModel(i, name, st.struct)
    case al: Algebraic   => renderAdtImpl(name, al.alternatives, renderUsings = false)
    case si: Singular    => s"// ${si.typeId}"
    case _: Void         => ""
    case at: Alternative => renderAlternativeImpl(DTOId(i.id, name), name, at)
  }

  protected def renderServiceMethodInModel(i: Service, name: String, structure: SimpleStructure)(implicit imports: CSharpImports, ts: Typespace): String = {
    renderServiceMethodInModel(DTOId(i.id, name), structure)
  }

  protected def renderBuzzerMethodInModel(i: Buzzer, name: String, structure: SimpleStructure)(implicit imports: CSharpImports, ts: Typespace): String = {
    renderServiceMethodInModel(DTOId(i.id, name), structure)
  }

  protected def renderServiceMethodInModel(i: DTOId, structure: SimpleStructure)(implicit imports: CSharpImports, ts: Typespace): String = {
    val csClass = CSharpClass(i, structure)

    s"""${ext.preModelEmit(ctx, csClass.id.name, csClass)}
       |${csClass.render(withWrapper = true, withSlices = false, withRTTI = true)}
       |${ext.postModelEmit(ctx, csClass.id.name, csClass)}""".stripMargin
  }

  protected def renderServiceMethodModels(i: Service, method: DefMethod)(implicit imports: CSharpImports, ts: Typespace): String = method match {
    case m: DefMethod.RPCMethod =>
      s"""${if (m.signature.input.fields.isEmpty) "" else renderServiceMethodInModel(i, s"In${m.name.capitalize}", m.signature.input)}
         |${renderServiceMethodOutModel(i, s"Out${m.name.capitalize}", m.signature.output)}
       """.stripMargin

  }

  protected def renderServiceMethodAdtUsings(method: DefMethod)(implicit imports: CSharpImports, ts: Typespace): List[String] = method match {
    case m: DefMethod.RPCMethod =>
      m.signature.output match {
        case al: Algebraic => al.alternatives.map(adtm => renderAdtUsings(adtm))
//      case at: Alternative => (outputToAdtMember(at.failure) ++ outputToAdtMember(at.success)).map(adtm => renderAdtUsings(adtm))
        case _ => List.empty
      }
  }

  protected def renderServiceModels(i: Service)(implicit imports: CSharpImports, ts: Typespace): String = {
    i.methods.map(me => renderServiceMethodModels(i, me)).mkString("\n")
  }

  protected def renderServiceUsings(i: Service)(implicit imports: CSharpImports, ts: Typespace): String = {
    i.methods.flatMap(me => renderServiceMethodAdtUsings(me)).distinct.mkString("\n")
  }

  protected def renderService(i: Service)(implicit im: CSharpImports, ts: Typespace): RenderableCogenProduct = {
    val svc =
      s"""${renderServiceUsings(i)}
         |
         |public static class ${i.id.name} {
         |${renderServiceModels(i).shift(4)}
         |}
         |
         |// ============== Service Client ==============
         |${renderServiceClient(i)}
         |
         |// ============== Service Dispatcher ==============
         |${renderServiceDispatcher(i)}
         |
         |// ============== Service Server Base ==============
         |${renderServiceServerBase(i)}
         """.stripMargin

    val extraImports = i.methods.flatMap(
      me =>
        me match {
          case m: DefMethod.RPCMethod =>
            val sigTypes = m.signature.output match {
              case al: Algebraic   => al.alternatives.map(_.typeId)
              case si: Singular    => Seq(si.typeId)
              case st: Struct      => st.struct.fields.map(_.typeId) ++ st.struct.concepts
              case _: Void         => Seq.empty
              case at: Alternative => (outputToAdtMember(at.success) ++ outputToAdtMember(at.failure)).map(am => am.typeId)
            }

            (sigTypes.filterNot(_.isInstanceOf[Builtin]).map(typespace.apply)
            ++ Seq(Adt(AdtId(TypePath(DomainId.Undefined, Seq.empty), "FakeName"), List.empty, NodeMeta.empty)) // TODO:fake entry to trigger imports (unsafe)
            ).flatMap(defn => ext.imports(ctx, defn))

        }
    )

    ServiceProduct(
      svc,
      im.renderImports(List("IRT", "IRT.Marshaller", "IRT.Transport.Client", "System", "System.Collections", "System.Collections.Generic") ++ extraImports),
    )
  }

  protected def renderBuzzerUsings(i: Buzzer)(implicit imports: CSharpImports, ts: Typespace): String = {
    i.events.flatMap(me => renderServiceMethodAdtUsings(me)).distinct.mkString("\n")
  }

  protected def renderBuzzerModels(i: Buzzer)(implicit imports: CSharpImports, ts: Typespace): String = {
    i.events.map(me => renderBuzzerMethodModels(i, me)).mkString("\n")
  }

  protected def renderBuzzerMethodModels(i: Buzzer, method: DefMethod)(implicit imports: CSharpImports, ts: Typespace): String = method match {
    case m: DefMethod.RPCMethod =>
      s"""${if (m.signature.input.fields.isEmpty) "" else renderBuzzerMethodInModel(i, s"In${m.name.capitalize}", m.signature.input)}
         |${renderBuzzerMethodOutModel(i, s"Out${m.name.capitalize}", m.signature.output)}
       """.stripMargin
  }

  protected def renderBuzzerClient(i: Buzzer)(implicit imports: CSharpImports, ts: Typespace): String = {
    val name = s"${i.id.name}Client"

    s"""public interface I$name where C: class, IClientTransportContext {
       |${i.events.map(m => renderRPCMethodSignature(i.id.name, m, forClient = true) + ";").mkString("\n").shift(4)}
       |}
       |
       |public class ${name}Generic: I$name where C: class, IClientTransportContext {
       |    public IClientSocketTransport Transport { get; private set; }
       |
       |    public ${name}Generic(IClientSocketTransport t) {
       |        Transport = t;
       |    }
       |
       |${i.events.map(me => renderRPCClientMethod(i.id.name, me)).mkString("\n").shift(4)}
       |}
       |
       |public class $name: ${name}Generic {
       |    public $name(IClientSocketTransport t): base(t) {}
       |}
     """.stripMargin
  }

  protected def renderBuzzerDispatcher(i: Buzzer)(implicit imports: CSharpImports, ts: Typespace): String = {
    s"""public interface I${i.id.name}BuzzerHandlers {
       |${i.events.map(m => renderRPCMethodSignature(i.id.name, m, forClient = false) + ";").mkString("\n").shift(4)}
       |}
       |
       |public class ${i.id.name}Dispatcher: IServiceDispatcher {
       |    private static readonly string[] methods = { ${i.events
        .map(m => if (m.isInstanceOf[DefMethod.RPCMethod]) "\"" + m.asInstanceOf[DefMethod.RPCMethod].name + "\"" else "").mkString(", ")} };
       |    protected IMarshaller marshaller;
       |    protected I${i.id.name}BuzzerHandlers handlers;
       |
       |    public ${i.id.name}Dispatcher(IMarshaller marshaller, I${i.id.name}BuzzerHandlers handlers) {
       |        this.marshaller = marshaller;
       |        this.handlers = handlers;
       |    }
       |
       |    public string GetSupportedService() {
       |        return "${i.id.name}";
       |    }
       |
       |    public string[] GetSupportedMethods() {
       |        return ${i.id.name}Dispatcher.methods;
       |    }
       |
       |    public D Dispatch(C ctx, string method, D data) {
       |        switch(method) {
       |${i.events.map(m => renderRPCDispatcherHandler(i.id.name, m, "handlers")).mkString("\n").shift(12)}
       |            default:
       |                throw new DispatcherException(string.Format("Method {0} is not supported by ${i.id.name}Dispatcher.", method));
       |        }
       |    }
       |}
     """.stripMargin
  }

  protected def renderBuzzerHandlersDummy(i: Buzzer)(implicit imports: CSharpImports, ts: Typespace): String = {
    val name = s"${i.id.name}BuzzerHandlers"
    s"""public abstract class $name: ${i.id.name}Dispatcher,  I${i.id.name}BuzzerHandlers {
       |    public $name(IMarshaller marshaller): base(marshaller, null) {
       |        handlers = this;
       |    }
       |
       |${i.events.map(m => renderRPCDummyMethod(i.id.name, m, virtual = true)).mkString("\n").shift(4)}
       |}
     """.stripMargin
  }

  protected def renderBuzzer(i: Buzzer)(implicit im: CSharpImports, ts: Typespace): RenderableCogenProduct = {
    val svc =
      s"""${renderBuzzerUsings(i)}
         |
         |public static class ${i.id.name} {
         |${renderBuzzerModels(i).shift(4)}
         |}
         |
         |// ============== Client ==============
         |${renderBuzzerClient(i)}
         |
         |// ============== Dispatcher ==============
         |${renderBuzzerDispatcher(i)}
         |
         |// ============== Buzzer Handlers Base ==============
         |${renderBuzzerHandlersDummy(i)}
         """.stripMargin

    val extraImports = i.events.flatMap(
      me =>
        me match {
          case m: DefMethod.RPCMethod =>
            val sigTypes = m.signature.output match {
              case al: Algebraic   => al.alternatives.map(_.typeId)
              case si: Singular    => Seq(si.typeId)
              case st: Struct      => st.struct.fields.map(_.typeId) ++ st.struct.concepts
              case _: Void         => Seq.empty
              case at: Alternative => (outputToAdtMember(at.success) ++ outputToAdtMember(at.failure)).map(am => am.typeId)
            }

            (sigTypes.filterNot(_.isInstanceOf[Builtin]).map(typespace.apply)
            ++ Seq(Adt(AdtId(TypePath(DomainId.Undefined, Seq.empty), "FakeName"), List.empty, NodeMeta.empty)) // TODO:fake entry to trigger imports (unsafe)
            ).flatMap(defn => ext.imports(ctx, defn))

        }
    )

    BuzzerProduct(
      svc,
      im.renderImports(List("IRT", "IRT.Marshaller", "IRT.Transport.Client", "System", "System.Collections", "System.Collections.Generic") ++ extraImports),
    )
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy