import { TypeDefinition, ServiceDefinition, EnumDefinition, TypeFieldDefinition, Step, } from "../ir"; import { CompileTarget } from "../compile"; type lineAppender = (ind: number, line: string | string[]) => void; const conversion = { boolean: "bool", number: "double", string: "string", void: "void", bytes: "" }; function toCSharpType(type: string): string { return (conversion as any)[type] || type; } export class CSharpTarget extends CompileTarget<{ csharp_namespace: string }> { name: string = "c#"; get namespace() { return this.options.csharp_namespace || "JRPC"; } start(): void { if(this.options.use_messagepack == true) { throw new Error("C# has no support for MessagePack yet!"); } this.writeFile( this.namespace + ".csproj", this.getTemplate("CSharp/CSharp.csproj") ); const fixNS = (input: string) => input.replace("__NAMESPACE__", this.namespace); const copyClass = (name: string) => this.writeFile( name + ".cs", fixNS(this.getTemplate(`CSharp/${name}.cs`)) ); copyClass("JRpcClient"); copyClass("JRpcServer"); copyClass("JRpcTransport"); } generateType(definition: TypeDefinition): void { let lines: string[] = []; const a: lineAppender = (i, t) => { if (!Array.isArray(t)) { t = [t]; } t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); }; a(0, `using System.Text.Json;`); a(0, `using System.Text.Json.Serialization;`); a(0, `using System.Collections.Generic;`); a(0, ``); a(0, `namespace ${this.namespace};`); a(0, ``); a(0, `public class ${definition.name} {`); for (const field of definition.fields) { if (field.array) { a( 1, `public IList<${toCSharpType(field.type)}>? ${ field.name } { get; set; }` ); } else if (field.map) { a( 1, `public Dictionary<${toCSharpType(field.map)}, ${toCSharpType( field.type )}>? ${field.name} { get; set; }` ); } else { a( 1, `public ${toCSharpType(field.type)}? ${field.name} { get; set; }` ); } } a(0, `}`); this.writeFile(`${definition.name}.cs`, lines.join("\n")); } generateEnum(definition: EnumDefinition): void { let lines: string[] = []; const a: lineAppender = (i, t) => { if (!Array.isArray(t)) { t = [t]; } t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); }; a(0, `using System.Text.Json;`); a(0, `using System.Text.Json.Serialization;`); a(0, ``); a(0, `namespace ${this.namespace};`); a(0, ``); a(0, `public enum ${definition.name} {`); for (const field of definition.values) { a(1, `${field.name} = ${field.value},`); } a(0, `}`); this.writeFile(`${definition.name}.cs`, lines.join("\n")); } generateServiceClient(definition: ServiceDefinition) { let lines: string[] = []; const a: lineAppender = (i, t) => { if (!Array.isArray(t)) { t = [t]; } t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); }; a(0, `using System.Text.Json;`); a(0, `using System.Text.Json.Serialization;`); a(0, `using System.Text.Json.Nodes;`); a(0, `using System.Threading.Tasks;`); a(0, ``); a(0, `namespace ${this.namespace};`); a(0, ``); a(0, `public class ${definition.name}Client {`); a(0, ``); a(1, `private JRpcClient Client;`); a(0, ``); a(1, `public ${definition.name}Client(JRpcClient client) {`); a(2, `this.Client = client;`); a(1, `}`); a(0, ``); for (const fnc of definition.functions) { let params = fnc.inputs .map((inp) => { if (inp.array) { return `List<${toCSharpType(inp.type)}> ${inp.name}`; } else { return `${toCSharpType(inp.type)} ${inp.name}`; } }) .join(", "); const genParam = () => a( 2, `var param = new JsonArray(${fnc.inputs .map((e) => `JsonSerializer.SerializeToNode(${e.name})`) .join(", ")});` ); if (fnc.return) { if (fnc.return.type == "void") { a(1, `public async Task ${fnc.name}(${params}) {`); genParam(); a( 2, `await this.Client.SendRequestRaw("${definition.name}.${fnc.name}", param);` ); a(1, `}`); } else { let ret = fnc.return ? fnc.return.array ? `IList<${toCSharpType(fnc.return.type)}>` : toCSharpType(fnc.return.type) : undefined; a(1, `public async Task<${ret}?> ${fnc.name}(${params}) {`); genParam(); a( 2, `return await this.Client.SendRequest<${ret}>("${definition.name}.${fnc.name}", param);` ); a(1, `}`); } } else { //Notification a(1, `public void ${fnc.name}(${params}) {`); genParam(); a( 2, `this.Client.SendNotification("${definition.name}.${fnc.name}", param);` ); a(1, `}`); } a(1, ``); } // a(0, ``); // a(0, ``); // a(0, ``); a(0, `}`); this.writeFile(`${definition.name}Client.cs`, lines.join("\n")); } generateServiceServer(definition: ServiceDefinition) { let lines: string[] = []; const a: lineAppender = (i, t) => { if (!Array.isArray(t)) { t = [t]; } t.forEach((l) => lines.push(" ".repeat(i) + l.trim())); }; a(0, `using System.Text.Json;`); a(0, `using System.Text.Json.Serialization;`); a(0, `using System.Text.Json.Nodes;`); a(0, `using System.Threading.Tasks;`); a(0, ``); a(0, `namespace ${this.namespace};`); a(0, ``); a( 0, `public abstract class ${definition.name}Server : JRpcService {` ); a(0, ``); a(1, `public override string Name {`); a(2, `get {`); a(3, `return "${definition.name}";`); a(2, `}`); a(1, `}`); a(0, ``); a(1, `public ${definition.name}Server() {`); for (const fnc of definition.functions) { a(2, `this.RegisterFunction("${fnc.name}");`); } a(1, `}`); a(0, ``); for (const fnc of definition.functions) { let params = [ ...fnc.inputs.map((inp) => { if (inp.array) { return `List<${toCSharpType(inp.type)}> ${inp.name}`; } else { return `${toCSharpType(inp.type)} ${inp.name}`; } }), "TContext ctx", ].join(", "); if (fnc.return) { if (fnc.return.type == "void") { a(1, `public abstract Task ${fnc.name}(${params});`); } else { let ret = fnc.return ? fnc.return.array ? `IList<${toCSharpType(fnc.return.type)}>` : toCSharpType(fnc.return.type) : undefined; a(1, `public abstract Task<${ret}> ${fnc.name}(${params});`); } } else { a(1, `public abstract void ${fnc.name}(${params});`); } } a(0, ``); a( 1, `public async override Task HandleRequest(string func, JsonNode param, TContext context) {` ); a(2, `switch(func) {`); for (const fnc of definition.functions) { a(3, `case "${fnc.name}": {`); a(4, `if(param is JsonObject) {`); a( 5, `var ja = new JsonArray(${fnc.inputs .map((inp) => { return `param["${inp.name}"]`; }) .join(", ")});` ); a(5, `param = ja;`); a(4, `}`); let pref = ""; if (fnc.return) { if (fnc.return.type != "void") pref = "var result = await "; else pref = "await "; } a( 4, pref + `this.${fnc.name}(${[ ...fnc.inputs.map((inp, idx) => { let type = inp.array ? `List<${toCSharpType(inp.type)}>` : `${toCSharpType(inp.type)}`; return `param[${idx}]!.Deserialize<${type}>()`; }), "context", ].join(", ")});` ); if (fnc.return && fnc.return.type != "void") { // if(fnc.return.type == "void") { // a(3, `return null;`); // } else { // a(3, ``); // } a(4, `return JsonSerializer.SerializeToNode(result);`); // a(3, ``); } else { a(4, `return null;`); } a(3, `}`); a(0, ``); } a(3, `default:`); a(4, `throw new Exception("Invalid Method!");`); // a(0, ``); // a(0, ``); // a(0, ``); a(2, `}`); a(1, `}`); a(0, `}`); this.writeFile(`${definition.name}Server.cs`, lines.join("\n")); } generateService(definition: ServiceDefinition): void { this.generateServiceClient(definition); this.generateServiceServer(definition); } finalize(steps: Step[]): void {} }