Adding C# Support. Badly tested currently, but kindof working
This commit is contained in:
parent
94832ef682
commit
49425cab39
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,5 +1,10 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
.yarn/cache
|
.yarn/cache
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
examples/out
|
examples/Typescript/out
|
||||||
|
examples/CSharp/Generated
|
||||||
|
examples/CSharp/Example/bin
|
||||||
|
examples/CSharp/Example/obj
|
||||||
examples/definition.json
|
examples/definition.json
|
||||||
|
templates/CSharp/bin
|
||||||
|
templates/CSharp/obj
|
||||||
|
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": ".NET Core Launch (console)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
// "preLaunchTask": "build",
|
||||||
|
"program": "${workspaceFolder}/examples/CSharp_Example/bin/Debug/net6.0/CSharp_Example.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"console": "internalConsole",
|
||||||
|
"stopAtEntry": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
14
examples/CSharp/Example/CSharp_Example.csproj
Normal file
14
examples/CSharp/Example/CSharp_Example.csproj
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Generated\Example.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
94
examples/CSharp/Example/Program.cs
Normal file
94
examples/CSharp/Example/Program.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
using Example;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
class TestSrvimpl : Example.TestServiceServer<int> {
|
||||||
|
public TestSrvimpl(): base() {}
|
||||||
|
public override async Task<Example.AddValueResponse> AddValuesSingleParam(AddValueRequest request, int ctx) {
|
||||||
|
var res = new Example.AddValueResponse();
|
||||||
|
res.value = 1;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<double> AddValuesMultipleParams(double value1,double value2,int ctx) {
|
||||||
|
return value1 + value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ReturningVoid(double param1,int ctx) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEvent(string param1,int ctx) {
|
||||||
|
Console.WriteLine($"OnEvent {param1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<IList<double>> FunctionWithArrayAsParamAndReturn(List<double> values1,List<double> values2, int ctx) {
|
||||||
|
var l = new List<double>();
|
||||||
|
l.Append(1);
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ThrowingError(int ctx) {
|
||||||
|
throw new Exception("This is a remote error :)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class CopyTransportS2 : Example.JRpcTransport {
|
||||||
|
CopyTransportS1 tr1;
|
||||||
|
public Queue<string> backlog = new Queue<string>();
|
||||||
|
|
||||||
|
public CopyTransportS2(CopyTransportS1 tr1) {
|
||||||
|
this.tr1 = tr1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Write(string data) {
|
||||||
|
Console.WriteLine("--> " + data);
|
||||||
|
this.tr1.DevSendPacket(data);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CopyTransportS1 : Example.JRpcTransport {
|
||||||
|
public Queue<string> backlog = new Queue<string>();
|
||||||
|
|
||||||
|
public CopyTransportS2 tr2;
|
||||||
|
|
||||||
|
public CopyTransportS1() {
|
||||||
|
this.tr2 = new CopyTransportS2(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Write(string data) {
|
||||||
|
Console.WriteLine("<-- " + data);
|
||||||
|
this.tr2.DevSendPacket(data);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
public static void Main() {
|
||||||
|
Program.Start().Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Start() {
|
||||||
|
var server = new Example.JRpcServer<int>();
|
||||||
|
server.AddService("TestService", new TestSrvimpl());
|
||||||
|
var transport = new CopyTransportS1();
|
||||||
|
|
||||||
|
var sess = server.GetSession(transport, 0);
|
||||||
|
var client = new Example.JRpcClient(transport.tr2);
|
||||||
|
var testService = new Example.TestServiceClient(client);
|
||||||
|
|
||||||
|
var result = await testService.AddValuesMultipleParams(1,2);
|
||||||
|
|
||||||
|
Console.WriteLine($"Add 1 + 2 = {result}");
|
||||||
|
|
||||||
|
await testService.ThrowingError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -44,6 +44,10 @@ class TestService extends Server.TestService<undefined> {
|
|||||||
async FunctionWithArrayAsParamAndReturn(vals1, vals2) {
|
async FunctionWithArrayAsParamAndReturn(vals1, vals2) {
|
||||||
return [...vals1, ...vals2];
|
return [...vals1, ...vals2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ThrowingError(ctx: undefined): Promise<void> {
|
||||||
|
throw new Error("Remote error!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server.addService(new TestService());
|
server.addService(new TestService());
|
@ -1,5 +1,7 @@
|
|||||||
import "./import";
|
import "./import";
|
||||||
|
|
||||||
|
define CSharp_Namespace Example;
|
||||||
|
|
||||||
enum TestEnum {
|
enum TestEnum {
|
||||||
VAL1,
|
VAL1,
|
||||||
VAL2,
|
VAL2,
|
||||||
@ -44,6 +46,8 @@ service TestService {
|
|||||||
@Param("param1", "Parameter with some string for event")
|
@Param("param1", "Parameter with some string for event")
|
||||||
notification OnEvent(param1: string);
|
notification OnEvent(param1: string);
|
||||||
|
|
||||||
|
ThrowingError(): void;
|
||||||
|
|
||||||
|
|
||||||
FunctionWithArrayAsParamAndReturn(values1: number[], values2: number[]): number[];
|
FunctionWithArrayAsParamAndReturn(values1: number[], values2: number[]): number[];
|
||||||
}
|
}
|
||||||
|
2870
lib/jrpc.js
2870
lib/jrpc.js
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,10 @@
|
|||||||
"packageManager": "yarn@3.1.1",
|
"packageManager": "yarn@3.1.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node src/index.ts",
|
"start": "ts-node src/index.ts",
|
||||||
"test": "npm run start -- compile examples/example.jrpc --definition=examples/definition.json -o=ts-node:examples/out && ts-node examples/test.ts",
|
"test-start": "npm run start -- compile examples/example.jrpc --definition=examples/definition.json -o=ts-node:examples/Typescript/out -o=c#:examples/CSharp/Generated",
|
||||||
|
"test-csharp": "cd examples/CSharp/Example/ && dotnet run",
|
||||||
|
"test-typescript": "ts-node examples/test.ts",
|
||||||
|
"test": "npm run test-start && npm run test-csharp && npm run test-typescript",
|
||||||
"build": "esbuild src/index.ts --bundle --platform=node --target=node14 --outfile=lib/jrpc.js",
|
"build": "esbuild src/index.ts --bundle --platform=node --target=node14 --outfile=lib/jrpc.js",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
@ -22,6 +25,7 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/debug": "^4.1.7",
|
"@types/debug": "^4.1.7",
|
||||||
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/node": "^17.0.5",
|
"@types/node": "^17.0.5",
|
||||||
"@types/prettier": "^2.4.2",
|
"@types/prettier": "^2.4.2",
|
||||||
"@types/yargs": "^17.0.8",
|
"@types/yargs": "^17.0.8",
|
||||||
@ -32,5 +36,8 @@
|
|||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"yargs": "^17.3.1"
|
"yargs": "^17.3.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fs-extra": "^10.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as FS from "fs";
|
import * as FS from "fs";
|
||||||
|
import * as FSE from "fs-extra"
|
||||||
import * as Path from "path";
|
import * as Path from "path";
|
||||||
import {
|
import {
|
||||||
EnumDefinition,
|
EnumDefinition,
|
||||||
@ -8,9 +9,9 @@ import {
|
|||||||
TypeDefinition,
|
TypeDefinition,
|
||||||
} from "./ir";
|
} from "./ir";
|
||||||
|
|
||||||
export abstract class CompileTarget {
|
export abstract class CompileTarget<T = any> {
|
||||||
abstract name: string;
|
abstract name: string;
|
||||||
constructor(private outputFolder: string) {
|
constructor(private outputFolder: string, protected options: T) {
|
||||||
if (!FS.existsSync(outputFolder)) {
|
if (!FS.existsSync(outputFolder)) {
|
||||||
FS.mkdirSync(outputFolder, {
|
FS.mkdirSync(outputFolder, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
@ -57,6 +58,14 @@ export abstract class CompileTarget {
|
|||||||
|
|
||||||
return res.join("\n");
|
return res.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected loadTemplateFolder(name:string) {
|
||||||
|
let root = Path.join(__dirname, "../templates/", name);
|
||||||
|
|
||||||
|
FSE.copySync(root, this.outputFolder, {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function compile(ir: IR, target: CompileTarget) {
|
export default function compile(ir: IR, target: CompileTarget) {
|
||||||
@ -64,12 +73,12 @@ export default function compile(ir: IR, target: CompileTarget) {
|
|||||||
|
|
||||||
// setState("Building for target: " + target.name);
|
// setState("Building for target: " + target.name);
|
||||||
target.start();
|
target.start();
|
||||||
ir.forEach((step) => {
|
ir.steps.forEach((step) => {
|
||||||
const [type, def] = step;
|
const [type, def] = step;
|
||||||
if (type == "type") target.generateType(def as TypeDefinition);
|
if (type == "type") target.generateType(def as TypeDefinition);
|
||||||
else if (type == "enum") target.generateEnum(def as EnumDefinition);
|
else if (type == "enum") target.generateEnum(def as EnumDefinition);
|
||||||
else if (type == "service")
|
else if (type == "service")
|
||||||
target.generateService(def as ServiceDefinition);
|
target.generateService(def as ServiceDefinition);
|
||||||
});
|
});
|
||||||
if (target.finalize) target.finalize(ir);
|
if (target.finalize) target.finalize(ir.steps);
|
||||||
}
|
}
|
||||||
|
13
src/ir.ts
13
src/ir.ts
@ -61,7 +61,10 @@ export type Step = [
|
|||||||
TypeDefinition | EnumDefinition | ServiceDefinition
|
TypeDefinition | EnumDefinition | ServiceDefinition
|
||||||
];
|
];
|
||||||
|
|
||||||
export type IR = Step[];
|
export type IR = {
|
||||||
|
options: { [key:string]: string},
|
||||||
|
steps: Step[]
|
||||||
|
};
|
||||||
|
|
||||||
export default function get_ir(parsed: Parsed): IR {
|
export default function get_ir(parsed: Parsed): IR {
|
||||||
log("Generatie IR from parse output");
|
log("Generatie IR from parse output");
|
||||||
@ -72,6 +75,7 @@ export default function get_ir(parsed: Parsed): IR {
|
|||||||
// Verifiy and generating steps
|
// Verifiy and generating steps
|
||||||
|
|
||||||
let steps: Step[] = [];
|
let steps: Step[] = [];
|
||||||
|
let options = {} as any;
|
||||||
|
|
||||||
parsed.forEach((statement) => {
|
parsed.forEach((statement) => {
|
||||||
log("Working on statement of type %s", statement.type);
|
log("Working on statement of type %s", statement.type);
|
||||||
@ -276,10 +280,15 @@ export default function get_ir(parsed: Parsed): IR {
|
|||||||
functions,
|
functions,
|
||||||
} as ServiceDefinition,
|
} as ServiceDefinition,
|
||||||
]);
|
]);
|
||||||
|
} else if(statement.type == "define") {
|
||||||
|
options[statement.key] = statement.value;
|
||||||
} else {
|
} else {
|
||||||
throw new IRError(statement, "Invalid statement!");
|
throw new IRError(statement, "Invalid statement!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return steps;
|
return {
|
||||||
|
options,
|
||||||
|
steps
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -67,11 +67,19 @@ export interface ServiceStatement extends DefinitionNode {
|
|||||||
functions: ServiceFunctionStatement[];
|
functions: ServiceFunctionStatement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DefineStatement extends DefinitionNode {
|
||||||
|
type: "define";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type RootStatementNode =
|
export type RootStatementNode =
|
||||||
| ImportStatement
|
| ImportStatement
|
||||||
| TypeStatement
|
| TypeStatement
|
||||||
| ServiceStatement
|
| ServiceStatement
|
||||||
| EnumStatement;
|
| EnumStatement
|
||||||
|
| DefineStatement;
|
||||||
|
|
||||||
export type StatementNode =
|
export type StatementNode =
|
||||||
| RootStatementNode
|
| RootStatementNode
|
||||||
| TypeFieldStatement
|
| TypeFieldStatement
|
||||||
@ -414,6 +422,22 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseDefine = (): DefineStatement => {
|
||||||
|
const idx = eatToken("define");
|
||||||
|
|
||||||
|
let [key] = eatText()
|
||||||
|
let [value] = eatText()
|
||||||
|
|
||||||
|
eatToken(";");
|
||||||
|
|
||||||
|
return {
|
||||||
|
type :"define",
|
||||||
|
location: {file, idx},
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const parseStatement = () => {
|
const parseStatement = () => {
|
||||||
if (currentToken.type === "keyword") {
|
if (currentToken.type === "keyword") {
|
||||||
switch (currentToken.value) {
|
switch (currentToken.value) {
|
||||||
@ -427,6 +451,8 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
|||||||
return parseEnumStatement();
|
return parseEnumStatement();
|
||||||
case "service":
|
case "service":
|
||||||
return parseServiceStatement();
|
return parseServiceStatement();
|
||||||
|
case "define":
|
||||||
|
return parseDefine();
|
||||||
default:
|
default:
|
||||||
throw new ParserError(
|
throw new ParserError(
|
||||||
`Unknown keyword ${currentToken.value}`,
|
`Unknown keyword ${currentToken.value}`,
|
||||||
|
@ -6,18 +6,21 @@ import tokenize, { TokenizerError } from "./tokenizer";
|
|||||||
import parse, { Parsed, ParserError } from "./parser";
|
import parse, { Parsed, ParserError } from "./parser";
|
||||||
import get_ir, { IR, IRError } from "./ir";
|
import get_ir, { IR, IRError } from "./ir";
|
||||||
import compile, { CompileTarget } from "./compile";
|
import compile, { CompileTarget } from "./compile";
|
||||||
import { ESMTypescriptTarget, NodeJSTypescriptTarget } from "./targets/typescript";
|
import {
|
||||||
|
ESMTypescriptTarget,
|
||||||
|
NodeJSTypescriptTarget,
|
||||||
|
} from "./targets/typescript";
|
||||||
|
import { CSharpTarget } from "./targets/csharp";
|
||||||
|
|
||||||
class CatchedError extends Error {}
|
class CatchedError extends Error {}
|
||||||
|
|
||||||
const log = dbg("app");
|
const log = dbg("app");
|
||||||
|
|
||||||
|
const Targets = new Map<string, typeof CompileTarget>();
|
||||||
|
|
||||||
const Targets = new Map<string,typeof CompileTarget>();
|
Targets.set("ts-esm", ESMTypescriptTarget);
|
||||||
|
Targets.set("ts-node", NodeJSTypescriptTarget);
|
||||||
Targets.set("ts-esm", ESMTypescriptTarget)
|
Targets.set("c#", CSharpTarget as typeof CompileTarget);
|
||||||
Targets.set("ts-node", NodeJSTypescriptTarget)
|
|
||||||
|
|
||||||
|
|
||||||
function indexToLineAndCol(src: string, index: number) {
|
function indexToLineAndCol(src: string, index: number) {
|
||||||
let line = 1;
|
let line = 1;
|
||||||
@ -77,7 +80,11 @@ type ProcessContext = {
|
|||||||
processedFiles: Set<string>;
|
processedFiles: Set<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function processFile(ctx: ProcessContext, file: string, root = false): Parsed | null {
|
function processFile(
|
||||||
|
ctx: ProcessContext,
|
||||||
|
file: string,
|
||||||
|
root = false
|
||||||
|
): Parsed | null {
|
||||||
file = Path.resolve(file);
|
file = Path.resolve(file);
|
||||||
if (ctx.processedFiles.has(file)) {
|
if (ctx.processedFiles.has(file)) {
|
||||||
log("Skipping file %s since it has already be processed", file);
|
log("Skipping file %s since it has already be processed", file);
|
||||||
@ -99,7 +106,9 @@ function processFile(ctx: ProcessContext, file: string, root = false): Parsed |
|
|||||||
.map((statement) => {
|
.map((statement) => {
|
||||||
if (statement.type == "import") {
|
if (statement.type == "import") {
|
||||||
const base = Path.dirname(file);
|
const base = Path.dirname(file);
|
||||||
const resolved = Path.resolve(Path.join(base, statement.path + ".jrpc"));
|
const resolved = Path.resolve(
|
||||||
|
Path.join(base, statement.path + ".jrpc")
|
||||||
|
);
|
||||||
return processFile(ctx, resolved);
|
return processFile(ctx, resolved);
|
||||||
} else {
|
} else {
|
||||||
return statement;
|
return statement;
|
||||||
@ -111,16 +120,14 @@ function processFile(ctx: ProcessContext, file: string, root = false): Parsed |
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof TokenizerError) {
|
if (err instanceof TokenizerError) {
|
||||||
printError(err, file, err.index);
|
printError(err, file, err.index);
|
||||||
if(!root)
|
if (!root) throw new CatchedError();
|
||||||
throw new CatchedError();
|
|
||||||
} else if (err instanceof ParserError) {
|
} else if (err instanceof ParserError) {
|
||||||
printError(err, file, err.token.startIdx);
|
printError(err, file, err.token.startIdx);
|
||||||
if(!root)
|
if (!root) throw new CatchedError();
|
||||||
throw new CatchedError();
|
} else if (root && err instanceof CatchedError) {
|
||||||
} else if(root && err instanceof CatchedError) {
|
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,18 +139,21 @@ export default function startCompile(options: CompileOptions) {
|
|||||||
} as ProcessContext;
|
} as ProcessContext;
|
||||||
|
|
||||||
let ir: IR | undefined = undefined;
|
let ir: IR | undefined = undefined;
|
||||||
if(options.input.endsWith(".json")) {
|
if (options.input.endsWith(".json")) {
|
||||||
ir = JSON.parse(FS.readFileSync(options.input, "utf-8"));
|
ir = JSON.parse(FS.readFileSync(options.input, "utf-8"));
|
||||||
} else {
|
} else {
|
||||||
const parsed = processFile(ctx, options.input, true);
|
const parsed = processFile(ctx, options.input, true);
|
||||||
|
|
||||||
if(!parsed)
|
if (!parsed) process.exit(1); // Errors should have already been emitted
|
||||||
process.exit(1); // Errors should have already been emitted
|
|
||||||
try {
|
try {
|
||||||
ir = get_ir(parsed);
|
ir = get_ir(parsed);
|
||||||
} catch(err) {
|
} catch (err) {
|
||||||
if(err instanceof IRError) {
|
if (err instanceof IRError) {
|
||||||
printError(err, err.statement.location.file, err.statement.location.idx);
|
printError(
|
||||||
|
err,
|
||||||
|
err.statement.location.file,
|
||||||
|
err.statement.location.idx
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
@ -151,23 +161,25 @@ export default function startCompile(options: CompileOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!ir)
|
if (!ir) throw new Error("Error compiling: Cannot get IR");
|
||||||
throw new Error("Error compiling: Cannot get IR");
|
|
||||||
|
|
||||||
if(options.emitDefinitions) {
|
if (options.emitDefinitions) {
|
||||||
FS.writeFileSync(options.emitDefinitions, JSON.stringify(ir, undefined, 3));
|
FS.writeFileSync(
|
||||||
|
options.emitDefinitions,
|
||||||
|
JSON.stringify(ir, undefined, 3)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.targets.length <= 0) {
|
if (options.targets.length <= 0) {
|
||||||
console.log(Color.yellow("WARNING:"), "No targets selected!");
|
console.log(Color.yellow("WARNING:"), "No targets selected!");
|
||||||
}
|
}
|
||||||
|
|
||||||
options.targets.forEach(target => {
|
options.targets.forEach((target) => {
|
||||||
const tg = Targets.get(target.type) as any
|
const tg = Targets.get(target.type) as any;
|
||||||
if(!tg) {
|
if (!tg) {
|
||||||
console.log(Color.red("ERROR:"), "Target not supported!");
|
console.log(Color.red("ERROR:"), "Target not supported!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
compile(ir, new tg(target.output));
|
compile(ir, new tg(target.output, ir.options));
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
328
src/targets/csharp.ts
Normal file
328
src/targets/csharp.ts
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
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",
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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<TContext> : JRpcService<TContext> {`
|
||||||
|
);
|
||||||
|
|
||||||
|
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<JsonNode?> 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 {}
|
||||||
|
}
|
@ -57,7 +57,7 @@ const matcher = [
|
|||||||
regexMatcher(/^#.+/, "comment"),
|
regexMatcher(/^#.+/, "comment"),
|
||||||
regexMatcher(/^".*?"/, "string"),
|
regexMatcher(/^".*?"/, "string"),
|
||||||
// regexMatcher(/(?<=^")(.*?)(?=")/, "string"),
|
// regexMatcher(/(?<=^")(.*?)(?=")/, "string"),
|
||||||
regexMatcher(/^(type|enum|import|service)\b/, "keyword"),
|
regexMatcher(/^(type|enum|import|service|define)\b/, "keyword"),
|
||||||
regexMatcher(/^\@/, "at"),
|
regexMatcher(/^\@/, "at"),
|
||||||
regexMatcher(/^\:/, "colon"),
|
regexMatcher(/^\:/, "colon"),
|
||||||
regexMatcher(/^\;/, "semicolon"),
|
regexMatcher(/^\;/, "semicolon"),
|
||||||
|
9
templates/CSharp/CSharp.csproj
Normal file
9
templates/CSharp/CSharp.csproj
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
130
templates/CSharp/JRpcClient.cs
Normal file
130
templates/CSharp/JRpcClient.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace __NAMESPACE__;
|
||||||
|
|
||||||
|
public class JRpcClient
|
||||||
|
{
|
||||||
|
private JRpcTransport Transport;
|
||||||
|
private IDictionary<string, TaskCompletionSource<JsonNode?>> Requests;
|
||||||
|
|
||||||
|
public JRpcClient(JRpcTransport transport)
|
||||||
|
{
|
||||||
|
this.Transport = transport;
|
||||||
|
this.Requests = new Dictionary<string, TaskCompletionSource<JsonNode?>>();
|
||||||
|
|
||||||
|
this.Transport.OnPacket += this.HandlePacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePacket(string packet)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parsed = JsonNode.Parse(packet);
|
||||||
|
if (parsed == null || (string?)parsed["jsonrpc"] != "2.0")
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (parsed["method"] != null)
|
||||||
|
{ // Request or Notification
|
||||||
|
if (parsed["id"] != null) // Requests are not supported on the Client
|
||||||
|
return;
|
||||||
|
//TODO: implement Notifications
|
||||||
|
}
|
||||||
|
else if (parsed["id"] != null && parsed["method"] == null)
|
||||||
|
{
|
||||||
|
// Response
|
||||||
|
//TODO: Somehow remove this warning, since it has no meaning in this context.
|
||||||
|
// ID has to be something, that was checked before
|
||||||
|
var id = (string)parsed["id"]!;
|
||||||
|
|
||||||
|
var task = this.Requests[id];
|
||||||
|
if (task == null)
|
||||||
|
return; //This Request was not from here
|
||||||
|
|
||||||
|
if (parsed["error"] != null)
|
||||||
|
{
|
||||||
|
var err = parsed["error"].Deserialize<JRpcError>();
|
||||||
|
if (err == null)
|
||||||
|
{
|
||||||
|
task.SetException(new JRpcException("Internal Server Error"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
task.SetException(err.ToException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
task.SetResult(parsed["result"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ignoring invalid packet!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
//TODO: Maybe log exception, but don't break!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<JsonNode?> SendRequestRaw(string method, JsonArray param)
|
||||||
|
{
|
||||||
|
var id = Guid.NewGuid().ToString();
|
||||||
|
var request = new JsonObject
|
||||||
|
{
|
||||||
|
["jsonrpc"] = "2.0",
|
||||||
|
["id"] = id,
|
||||||
|
["method"] = method,
|
||||||
|
["params"] = param
|
||||||
|
};
|
||||||
|
|
||||||
|
var task = new TaskCompletionSource<JsonNode?>();
|
||||||
|
this.Requests.Add(id, task);
|
||||||
|
await this.Transport.Write(request.ToJsonString());
|
||||||
|
|
||||||
|
return await task.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TResult?> SendRequest<TResult>(string method, JsonArray param)
|
||||||
|
{
|
||||||
|
var result = await this.SendRequestRaw(method, param);
|
||||||
|
return result.Deserialize<TResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void SendNotification(string method, JsonArray param)
|
||||||
|
{
|
||||||
|
var not = new JsonObject
|
||||||
|
{
|
||||||
|
["jsonrpc"] = "2.0",
|
||||||
|
["method"] = method,
|
||||||
|
["params"] = param,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.Transport.Write(not.ToJsonString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class JRpcError
|
||||||
|
{
|
||||||
|
public int code { get; set; }
|
||||||
|
public string? message { get; set; }
|
||||||
|
public JsonNode? data { get; set; }
|
||||||
|
|
||||||
|
public JRpcException ToException()
|
||||||
|
{
|
||||||
|
return new JRpcException(this.message!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JRpcException : Exception
|
||||||
|
{
|
||||||
|
public JRpcException(string message) : base(message) { }
|
||||||
|
}
|
196
templates/CSharp/JRpcServer.cs
Normal file
196
templates/CSharp/JRpcServer.cs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace __NAMESPACE__;
|
||||||
|
|
||||||
|
public class JRpcServer<TContext>
|
||||||
|
{
|
||||||
|
public IDictionary<string, JRpcService<TContext>> Services;
|
||||||
|
|
||||||
|
public JRpcServer()
|
||||||
|
{
|
||||||
|
this.Services = new Dictionary<string, JRpcService<TContext>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddService(string name, JRpcService<TContext> service)
|
||||||
|
{
|
||||||
|
this.Services.Add(name, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JRpcServerSession<TContext> GetSession(JRpcTransport transport, TContext context)
|
||||||
|
{
|
||||||
|
return new JRpcServerSession<TContext>(this, transport, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JRpcServerSession<TContext>
|
||||||
|
{
|
||||||
|
JRpcServer<TContext> Server;
|
||||||
|
JRpcTransport Transport;
|
||||||
|
TContext Context;
|
||||||
|
|
||||||
|
public JRpcServerSession(JRpcServer<TContext> server, JRpcTransport transport, TContext context)
|
||||||
|
{
|
||||||
|
this.Server = server;
|
||||||
|
this.Transport = transport;
|
||||||
|
this.Context = context;
|
||||||
|
|
||||||
|
this.Transport.OnPacket += this.HandlePacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePacket(string packet)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parsed = JsonNode.Parse(packet);
|
||||||
|
if (parsed == null || (string?)parsed["jsonrpc"] != "2.0")
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (parsed["method"] != null) // Request or Notification
|
||||||
|
{
|
||||||
|
var id = (string?)parsed["id"];
|
||||||
|
var splitted = ((string)parsed["method"]!).Split(".", 2);
|
||||||
|
var serviceName = splitted[0];
|
||||||
|
var functionName = splitted[1];
|
||||||
|
|
||||||
|
if (serviceName == null || functionName == null)
|
||||||
|
{
|
||||||
|
if (id != null)
|
||||||
|
{
|
||||||
|
var response = new JsonObject
|
||||||
|
{
|
||||||
|
["jsonrpc"] = "2.0",
|
||||||
|
["id"] = id,
|
||||||
|
["error"] = new JsonObject
|
||||||
|
{
|
||||||
|
["code"] = -32700,
|
||||||
|
["message"] = "Method not found!",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_ = this.Transport.Write(response.ToJsonString()!);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var service = this.Server.Services[serviceName];
|
||||||
|
if (service == null)
|
||||||
|
{
|
||||||
|
if (id != null)
|
||||||
|
{
|
||||||
|
var response = new JsonObject
|
||||||
|
{
|
||||||
|
["jsonrpc"] = "2.0",
|
||||||
|
["id"] = id,
|
||||||
|
["error"] = new JsonObject
|
||||||
|
{
|
||||||
|
["code"] = -32700,
|
||||||
|
["message"] = "Method not found!",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_ = this.Transport.Write(response.ToJsonString()!);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!service.Functions.Contains(functionName))
|
||||||
|
{
|
||||||
|
if (id != null)
|
||||||
|
{
|
||||||
|
var response = new JsonObject
|
||||||
|
{
|
||||||
|
["jsonrpc"] = "2.0",
|
||||||
|
["id"] = id,
|
||||||
|
["error"] = new JsonObject
|
||||||
|
{
|
||||||
|
["code"] = -32700,
|
||||||
|
["message"] = "Method not found!",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_ = this.Transport.Write(response.ToJsonString()!);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(parsed["params"] is JsonArray || parsed["params"] is JsonObject))
|
||||||
|
{
|
||||||
|
if (id != null)
|
||||||
|
{
|
||||||
|
var response = new JsonObject
|
||||||
|
{
|
||||||
|
["jsonrpc"] = "2.0",
|
||||||
|
["id"] = id,
|
||||||
|
["error"] = new JsonObject
|
||||||
|
{
|
||||||
|
["code"] = -32602,
|
||||||
|
["message"] = "Invalid Parameters!",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_ = this.Transport.Write(response.ToJsonString()!);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = service.HandleRequest(functionName, parsed["params"]!, this.Context).ContinueWith(result =>
|
||||||
|
{
|
||||||
|
if (id != null)
|
||||||
|
{
|
||||||
|
var response = new JsonObject
|
||||||
|
{
|
||||||
|
["jsonrpc"] = "2.0",
|
||||||
|
["id"] = id,
|
||||||
|
["error"] = result.IsFaulted ? new JsonObject
|
||||||
|
{
|
||||||
|
["code"] = -32603,
|
||||||
|
["message"] = result.Exception!.InnerException!.Message
|
||||||
|
} : null,
|
||||||
|
["result"] = !result.IsFaulted ? result.Result : null,
|
||||||
|
};
|
||||||
|
_ = this.Transport.Write(response.ToJsonString()!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: implement Notifications
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ignoring everyting else. Don't care!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
//TODO: Maybe log exception, but don't break!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async void SendNotification(string method, JsonArray param)
|
||||||
|
{
|
||||||
|
var not = new JsonObject
|
||||||
|
{
|
||||||
|
["jsonrpc"] = "2.0",
|
||||||
|
["method"] = method,
|
||||||
|
["params"] = param,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.Transport.Write(not.ToJsonString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class JRpcService<TContext>
|
||||||
|
{
|
||||||
|
public HashSet<string> Functions = new HashSet<string>();
|
||||||
|
|
||||||
|
protected void RegisterFunction(string name)
|
||||||
|
{
|
||||||
|
this.Functions.Add(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task<JsonNode?> HandleRequest(string function, JsonNode param, TContext context);
|
||||||
|
// public abstract Task HandleNotification(string notification, JsonNode param);
|
||||||
|
}
|
14
templates/CSharp/JRpcTransport.cs
Normal file
14
templates/CSharp/JRpcTransport.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace __NAMESPACE__;
|
||||||
|
|
||||||
|
public delegate void NotifyPacket(string data);
|
||||||
|
|
||||||
|
public abstract class JRpcTransport {
|
||||||
|
public event NotifyPacket OnPacket;
|
||||||
|
public abstract Task Write(string data);
|
||||||
|
|
||||||
|
public void DevSendPacket(string data) {
|
||||||
|
this.OnPacket.Invoke(data);
|
||||||
|
}
|
||||||
|
}
|
@ -9,10 +9,10 @@ export const Logging = {
|
|||||||
|
|
||||||
export enum ErrorCodes {
|
export enum ErrorCodes {
|
||||||
ParseError = -32700,
|
ParseError = -32700,
|
||||||
InvalidRequest = -32700,
|
InvalidRequest = -32600,
|
||||||
MethodNotFound = -32700,
|
MethodNotFound = -32601,
|
||||||
InvalidParams = -32700,
|
InvalidParams = -32602,
|
||||||
InternalError = -32700,
|
InternalError = -32603,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestObject {
|
export interface RequestObject {
|
||||||
|
56
yarn.lock
56
yarn.lock
@ -26,12 +26,14 @@ __metadata:
|
|||||||
resolution: "@hibas123/jrpcgen@workspace:."
|
resolution: "@hibas123/jrpcgen@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/debug": ^4.1.7
|
"@types/debug": ^4.1.7
|
||||||
|
"@types/fs-extra": ^9.0.13
|
||||||
"@types/node": ^17.0.5
|
"@types/node": ^17.0.5
|
||||||
"@types/prettier": ^2.4.2
|
"@types/prettier": ^2.4.2
|
||||||
"@types/yargs": ^17.0.8
|
"@types/yargs": ^17.0.8
|
||||||
chalk: 4
|
chalk: 4
|
||||||
debug: ^4.3.3
|
debug: ^4.3.3
|
||||||
esbuild: ^0.14.10
|
esbuild: ^0.14.10
|
||||||
|
fs-extra: ^10.0.0
|
||||||
prettier: ^2.5.1
|
prettier: ^2.5.1
|
||||||
ts-node: ^10.4.0
|
ts-node: ^10.4.0
|
||||||
typescript: ^4.5.4
|
typescript: ^4.5.4
|
||||||
@ -78,6 +80,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/fs-extra@npm:^9.0.13":
|
||||||
|
version: 9.0.13
|
||||||
|
resolution: "@types/fs-extra@npm:9.0.13"
|
||||||
|
dependencies:
|
||||||
|
"@types/node": "*"
|
||||||
|
checksum: add79e212acd5ac76b97b9045834e03a7996aef60a814185e0459088fd290519a3c1620865d588fa36c4498bf614210d2a703af5cf80aa1dbc125db78f6edac3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/ms@npm:*":
|
"@types/ms@npm:*":
|
||||||
version: 0.7.31
|
version: 0.7.31
|
||||||
resolution: "@types/ms@npm:0.7.31"
|
resolution: "@types/ms@npm:0.7.31"
|
||||||
@ -85,6 +96,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/node@npm:*":
|
||||||
|
version: 17.0.8
|
||||||
|
resolution: "@types/node@npm:17.0.8"
|
||||||
|
checksum: f4cadeb9e602027520abc88c77142697e33cf6ac98bb02f8b595a398603cbd33df1f94d01c055c9f13cde0c8eaafc5e396ca72645458d42b4318b845bc7f1d0f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/node@npm:^17.0.5":
|
"@types/node@npm:^17.0.5":
|
||||||
version: 17.0.5
|
version: 17.0.5
|
||||||
resolution: "@types/node@npm:17.0.5"
|
resolution: "@types/node@npm:17.0.5"
|
||||||
@ -422,6 +440,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fs-extra@npm:^10.0.0":
|
||||||
|
version: 10.0.0
|
||||||
|
resolution: "fs-extra@npm:10.0.0"
|
||||||
|
dependencies:
|
||||||
|
graceful-fs: ^4.2.0
|
||||||
|
jsonfile: ^6.0.1
|
||||||
|
universalify: ^2.0.0
|
||||||
|
checksum: 5285a3d8f34b917cf2b66af8c231a40c1623626e9d701a20051d3337be16c6d7cac94441c8b3732d47a92a2a027886ca93c69b6a4ae6aee3c89650d2a8880c0a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"get-caller-file@npm:^2.0.5":
|
"get-caller-file@npm:^2.0.5":
|
||||||
version: 2.0.5
|
version: 2.0.5
|
||||||
resolution: "get-caller-file@npm:2.0.5"
|
resolution: "get-caller-file@npm:2.0.5"
|
||||||
@ -429,6 +458,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0":
|
||||||
|
version: 4.2.9
|
||||||
|
resolution: "graceful-fs@npm:4.2.9"
|
||||||
|
checksum: 68ea4e07ff2c041ada184f9278b830375f8e0b75154e3f080af6b70f66172fabb4108d19b3863a96b53fc068a310b9b6493d86d1291acc5f3861eb4b79d26ad6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"has-flag@npm:^4.0.0":
|
"has-flag@npm:^4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "has-flag@npm:4.0.0"
|
resolution: "has-flag@npm:4.0.0"
|
||||||
@ -443,6 +479,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"jsonfile@npm:^6.0.1":
|
||||||
|
version: 6.1.0
|
||||||
|
resolution: "jsonfile@npm:6.1.0"
|
||||||
|
dependencies:
|
||||||
|
graceful-fs: ^4.1.6
|
||||||
|
universalify: ^2.0.0
|
||||||
|
dependenciesMeta:
|
||||||
|
graceful-fs:
|
||||||
|
optional: true
|
||||||
|
checksum: 7af3b8e1ac8fe7f1eccc6263c6ca14e1966fcbc74b618d3c78a0a2075579487547b94f72b7a1114e844a1e15bb00d440e5d1720bfc4612d790a6f285d5ea8354
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"make-error@npm:^1.1.1":
|
"make-error@npm:^1.1.1":
|
||||||
version: 1.3.6
|
version: 1.3.6
|
||||||
resolution: "make-error@npm:1.3.6"
|
resolution: "make-error@npm:1.3.6"
|
||||||
@ -558,6 +607,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"universalify@npm:^2.0.0":
|
||||||
|
version: 2.0.0
|
||||||
|
resolution: "universalify@npm:2.0.0"
|
||||||
|
checksum: 2406a4edf4a8830aa6813278bab1f953a8e40f2f63a37873ffa9a3bc8f9745d06cc8e88f3572cb899b7e509013f7f6fcc3e37e8a6d914167a5381d8440518c44
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"wrap-ansi@npm:^7.0.0":
|
"wrap-ansi@npm:^7.0.0":
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
resolution: "wrap-ansi@npm:7.0.0"
|
resolution: "wrap-ansi@npm:7.0.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user