Compare commits
7 Commits
zig-target
...
dart
Author | SHA1 | Date | |
---|---|---|---|
98bc2d4148 | |||
1774306b06 | |||
327d7dfac6 | |||
276fd12894 | |||
e23dd6a6fb | |||
68af1ff442 | |||
43d1477088 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -8,6 +8,10 @@ examples/CSharp/Example/obj
|
||||
examples/definition.json
|
||||
examples/Rust/Gen
|
||||
examples/Rust/Impl/target
|
||||
examples/Dart/out
|
||||
templates/CSharp/bin
|
||||
templates/CSharp/obj
|
||||
lib/
|
||||
templates/Dart/.dart_tool
|
||||
templates/Dart/.packages
|
||||
templates/Dart/pubspec.lock
|
42
examples/Dart/Test.dart
Normal file
42
examples/Dart/Test.dart
Normal file
@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import "./out/lib/example.dart";
|
||||
import "dart:convert";
|
||||
|
||||
void main() async {
|
||||
var str = StreamController<Map<String, dynamic>>();
|
||||
|
||||
var provider = ServiceProvider(str.stream);
|
||||
var sock = await Socket.connect("127.0.0.1", 8859);
|
||||
|
||||
utf8.decoder.bind(sock).transform(new LineSplitter()).listen((line) {
|
||||
str.add(jsonDecode(line));
|
||||
});
|
||||
|
||||
provider.output.stream.listen((event) {
|
||||
sock.writeln(jsonEncode(event));
|
||||
});
|
||||
|
||||
var s = new TestServiceClient(provider);
|
||||
|
||||
var r = await s.AddValuesMultipleParams(10, 15);
|
||||
print(r);
|
||||
|
||||
var r2 =
|
||||
await s.AddValuesSingleParam(AddValueRequest(value1: 10, value2: 15));
|
||||
print(r2?.value);
|
||||
|
||||
var catched = false;
|
||||
await s.ThrowingError().catchError((err) {
|
||||
catched = true;
|
||||
print("Expected error was catched: " + err.toString());
|
||||
});
|
||||
|
||||
if (!catched) {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
await sock.close();
|
||||
// exit(0);
|
||||
}
|
62
examples/Typescript/server.ts
Normal file
62
examples/Typescript/server.ts
Normal file
@ -0,0 +1,62 @@
|
||||
|
||||
import * as net from "net";
|
||||
import { Server, AddValueRequest, AddValueResponse } from "./out";
|
||||
import * as readline from 'node:readline';
|
||||
|
||||
|
||||
const server = new Server.ServiceProvider();
|
||||
|
||||
class TestService extends Server.TestService<undefined> {
|
||||
async AddValuesSingleParam(
|
||||
request: AddValueRequest,
|
||||
ctx: undefined
|
||||
): Promise<AddValueResponse> {
|
||||
return {
|
||||
value: request.value1 + request!.value2,
|
||||
};
|
||||
}
|
||||
async AddValuesMultipleParams(
|
||||
value1: number,
|
||||
value2: number,
|
||||
ctx: undefined
|
||||
): Promise<number> {
|
||||
return value1 + value2;
|
||||
}
|
||||
|
||||
async ReturningVoid(param1) {
|
||||
console.log("Calling Returning Void");
|
||||
}
|
||||
|
||||
OnEvent(param1: string, ctx: undefined): void {
|
||||
console.log("Received notification", param1);
|
||||
}
|
||||
|
||||
async FunctionWithArrayAsParamAndReturn(vals1, vals2) {
|
||||
return [...vals1, ...vals2];
|
||||
}
|
||||
|
||||
async ThrowingError(ctx: undefined): Promise<void> {
|
||||
throw new Error("Remote error!");
|
||||
}
|
||||
}
|
||||
|
||||
server.addService(new TestService());
|
||||
|
||||
net.createServer((socket) => {
|
||||
socket.on("error", console.error);
|
||||
|
||||
console.log("Connection from:", socket.remoteAddress);
|
||||
const sess = server.getSession(msg => {
|
||||
const s = JSON.stringify(msg);
|
||||
console.log("Sending:", s);
|
||||
socket.write(s + "\n");
|
||||
});
|
||||
const rl = readline.createInterface(socket);
|
||||
rl.on("line", line => {
|
||||
console.log("Receiving:", line);
|
||||
sess.onMessage(JSON.parse(line));
|
||||
})
|
||||
rl.on("error", console.error);
|
||||
}).listen(8859).on("listening", () => {
|
||||
console.log("Is listening on :8859");
|
||||
}).on("error", console.error)
|
@ -22,7 +22,7 @@ class TestService extends Server.TestService<undefined> {
|
||||
ctx: undefined
|
||||
): Promise<AddValueResponse> {
|
||||
return {
|
||||
value: request.value1 + request.value2,
|
||||
value: request.value1 + request!.value2,
|
||||
};
|
||||
}
|
||||
async AddValuesMultipleParams(
|
||||
|
@ -2,6 +2,7 @@ import "./import";
|
||||
|
||||
define csharp_namespace Example;
|
||||
define rust_crate example;
|
||||
define dart_library_name example;
|
||||
|
||||
enum TestEnum {
|
||||
VAL1,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hibas123/jrpcgen",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"main": "lib/index.js",
|
||||
"license": "MIT",
|
||||
"packageManager": "yarn@3.1.1",
|
||||
@ -24,6 +24,9 @@
|
||||
"templates/CSharp/*.csproj",
|
||||
"templates/Rust/Cargo.toml",
|
||||
"templates/Rust/src/lib.rs",
|
||||
"templates/Dart/service_client.dart",
|
||||
"templates/Dart/base.dart",
|
||||
"templates/Dart/pubspec.yaml",
|
||||
"examples/*.jrpcproj",
|
||||
"src/**",
|
||||
"tsconfig.json"
|
||||
|
@ -2,7 +2,7 @@ import type { Parsed, StatementNode } from "./parser";
|
||||
import dbg from "debug";
|
||||
const log = dbg("app");
|
||||
|
||||
const BUILTIN = ["float", "int", "string", "boolean"];
|
||||
export const BUILTIN = ["float", "int", "string", "boolean"];
|
||||
|
||||
export class IRError extends Error {
|
||||
constructor(public statement: StatementNode, message: string) {
|
||||
@ -316,7 +316,10 @@ export default function get_ir(parsed: Parsed): IR {
|
||||
builtin.push("bytes");
|
||||
}
|
||||
} else {
|
||||
throw new IRError(statement, "Invalid statement!");
|
||||
throw new IRError(
|
||||
statement,
|
||||
"Invalid statement: " + (statement as any).type
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
106
src/process.ts
106
src/process.ts
@ -2,6 +2,8 @@ import dbg from "debug";
|
||||
import * as FS from "fs";
|
||||
import Color from "chalk";
|
||||
import * as Path from "path";
|
||||
import * as Https from "https";
|
||||
import * as Http from "http";
|
||||
import tokenize, { TokenizerError } from "./tokenizer";
|
||||
import parse, { Parsed, ParserError } from "./parser";
|
||||
import get_ir, { IR, IRError } from "./ir";
|
||||
@ -13,6 +15,8 @@ import {
|
||||
import { CSharpTarget } from "./targets/csharp";
|
||||
import { RustTarget } from "./targets/rust";
|
||||
import { ZIGTarget } from "./targets/zig";
|
||||
import { DartTarget } from "./targets/dart";
|
||||
import { URL } from "url";
|
||||
|
||||
class CatchedError extends Error {}
|
||||
|
||||
@ -25,6 +29,7 @@ Targets.set("ts-node", NodeJSTypescriptTarget);
|
||||
Targets.set("c#", CSharpTarget as typeof CompileTarget);
|
||||
Targets.set("rust", RustTarget as typeof CompileTarget);
|
||||
Targets.set("zig", ZIGTarget as typeof CompileTarget);
|
||||
Targets.set("dart", DartTarget as typeof CompileTarget);
|
||||
|
||||
function indexToLineAndCol(src: string, index: number) {
|
||||
let line = 1;
|
||||
@ -41,26 +46,63 @@ function indexToLineAndCol(src: string, index: number) {
|
||||
return { line, col };
|
||||
}
|
||||
|
||||
function resolve(base: string, ...parts: string[]) {
|
||||
if (base.startsWith("http://") || base.startsWith("https://")) {
|
||||
let u = new URL(base);
|
||||
for (const part of parts) {
|
||||
u = new URL(part, u);
|
||||
}
|
||||
return u.href;
|
||||
} else {
|
||||
return Path.resolve(base, ...parts);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchFileFromURL(url: string) {
|
||||
return new Promise<string>((yes, no) => {
|
||||
const makeReq = url.startsWith("https://") ? Https.request : Http.request;
|
||||
const req = makeReq(url, (res) => {
|
||||
let chunks: Buffer[] = [];
|
||||
res.on("data", (chunk) => {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
});
|
||||
res.on("error", no);
|
||||
res.on("end", () => yes(Buffer.concat(chunks).toString("utf-8")));
|
||||
});
|
||||
|
||||
req.on("error", no);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
const fileCache = new Map<string, string>();
|
||||
function getFile(name: string) {
|
||||
|
||||
async function getFile(name: string) {
|
||||
if (fileCache.has(name)) return fileCache.get(name);
|
||||
else {
|
||||
try {
|
||||
const data = FS.readFileSync(name, "utf-8");
|
||||
fileCache.set(name, data);
|
||||
return data;
|
||||
if (name.startsWith("http://") || name.startsWith("https://")) {
|
||||
const data = await fetchFileFromURL(name);
|
||||
fileCache.set(name, data);
|
||||
return data;
|
||||
} else {
|
||||
const data = FS.readFileSync(name, "utf-8");
|
||||
fileCache.set(name, data);
|
||||
return data;
|
||||
}
|
||||
} catch (err) {
|
||||
printError(new Error(`Cannot open file ${name};`), null, 0);
|
||||
log(err);
|
||||
await printError(new Error(`Cannot open file ${name};`), null, 0);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function printError(err: Error, file: string | null, idx: number) {
|
||||
async function printError(err: Error, file: string | null, idx: number) {
|
||||
let loc = { line: 0, col: 0 };
|
||||
if (file != null) {
|
||||
const data = getFile(file);
|
||||
if (data) loc = indexToLineAndCol(data, idx);
|
||||
if (data) loc = indexToLineAndCol(await data, idx);
|
||||
}
|
||||
|
||||
console.error(`${Color.red("ERROR: at")} ${file}:${loc.line}:${loc.col}`);
|
||||
@ -84,21 +126,21 @@ type ProcessContext = {
|
||||
processedFiles: Set<string>;
|
||||
};
|
||||
|
||||
function processFile(
|
||||
async function processFile(
|
||||
ctx: ProcessContext,
|
||||
file: string,
|
||||
root = false
|
||||
): Parsed | null {
|
||||
file = Path.resolve(file);
|
||||
): Promise<Parsed | null> {
|
||||
file = resolve(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 been processed", file);
|
||||
return null;
|
||||
}
|
||||
ctx.processedFiles.add(file);
|
||||
log("Processing file %s", file);
|
||||
|
||||
const content = getFile(file);
|
||||
if (!content) throw new Error("Could not open file " + file);
|
||||
const content = await getFile(file);
|
||||
if (content == undefined) throw new Error("Could not open file " + file);
|
||||
try {
|
||||
log("Tokenizing %s", file);
|
||||
const tokens = tokenize(content);
|
||||
@ -106,27 +148,29 @@ function processFile(
|
||||
const parsed = parse(tokens, file);
|
||||
|
||||
log("Resolving imports of %s", file);
|
||||
let resolved = parsed
|
||||
.map((statement) => {
|
||||
if (statement.type == "import") {
|
||||
const base = Path.dirname(file);
|
||||
const resolved = Path.resolve(
|
||||
Path.join(base, statement.path + ".jrpc")
|
||||
);
|
||||
return processFile(ctx, resolved);
|
||||
let resolved: Parsed = [];
|
||||
for (const statement of parsed) {
|
||||
if (statement.type == "import") {
|
||||
let res: string;
|
||||
if (file.startsWith("http://") || file.startsWith("https://")) {
|
||||
res = resolve(file, statement.path + ".jrpc");
|
||||
} else {
|
||||
return statement;
|
||||
const base = Path.dirname(file);
|
||||
res = resolve(base, statement.path + ".jrpc");
|
||||
}
|
||||
})
|
||||
.filter((e) => !!e)
|
||||
.flat(1) as Parsed;
|
||||
return resolved;
|
||||
resolved.push(...((await processFile(ctx, res)) || []));
|
||||
} else {
|
||||
resolved.push(statement);
|
||||
}
|
||||
}
|
||||
|
||||
return resolved.filter((e) => !!e).flat(1) as Parsed;
|
||||
} catch (err) {
|
||||
if (err instanceof TokenizerError) {
|
||||
printError(err, file, err.index);
|
||||
await printError(err, file, err.index);
|
||||
if (!root) throw new CatchedError();
|
||||
} else if (err instanceof ParserError) {
|
||||
printError(err, file, err.token.startIdx);
|
||||
await printError(err, file, err.token.startIdx);
|
||||
if (!root) throw new CatchedError();
|
||||
} else if (root && err instanceof CatchedError) {
|
||||
return null;
|
||||
@ -136,7 +180,7 @@ function processFile(
|
||||
}
|
||||
}
|
||||
|
||||
export default function startCompile(options: CompileOptions) {
|
||||
export default async function startCompile(options: CompileOptions) {
|
||||
const ctx = {
|
||||
options,
|
||||
processedFiles: new Set(),
|
||||
@ -146,14 +190,14 @@ export default function startCompile(options: CompileOptions) {
|
||||
if (options.input.endsWith(".json")) {
|
||||
ir = JSON.parse(FS.readFileSync(options.input, "utf-8"));
|
||||
} else {
|
||||
const parsed = processFile(ctx, options.input, true);
|
||||
const parsed = await processFile(ctx, options.input, true);
|
||||
|
||||
if (!parsed) process.exit(1); // Errors should have already been emitted
|
||||
try {
|
||||
ir = get_ir(parsed);
|
||||
} catch (err) {
|
||||
if (err instanceof IRError) {
|
||||
printError(
|
||||
await printError(
|
||||
err,
|
||||
err.statement.location.file,
|
||||
err.statement.location.idx
|
||||
|
279
src/targets/dart.ts
Normal file
279
src/targets/dart.ts
Normal file
@ -0,0 +1,279 @@
|
||||
import { TypeDefinition, ServiceDefinition, EnumDefinition, Step } from "../ir";
|
||||
|
||||
import { CompileTarget } from "../compile";
|
||||
import { LineAppender, lineAppender } from "../utils";
|
||||
import chalk from "chalk";
|
||||
|
||||
const conversion = {
|
||||
boolean: "bool",
|
||||
int: "int",
|
||||
float: "double",
|
||||
string: "String",
|
||||
void: "void",
|
||||
// bytes: "Uint8List", //TODO: Check this
|
||||
};
|
||||
|
||||
function toDartType(type: string): string {
|
||||
return (conversion as any)[type] || type;
|
||||
}
|
||||
|
||||
export class DartTarget extends CompileTarget<{ dart_library_name: string }> {
|
||||
name: string = "dart";
|
||||
|
||||
start(): void {
|
||||
if (this.options.allow_bytes == true) {
|
||||
throw new Error("Dart has no support for 'bytes' yet!");
|
||||
}
|
||||
|
||||
if (!this.options.dart_library_name) {
|
||||
throw new Error("Setting dart_library_name is required for DART target!");
|
||||
}
|
||||
}
|
||||
|
||||
getImport(name: string) {
|
||||
return `import "./${name}.dart";`;
|
||||
}
|
||||
|
||||
generateImports(a: lineAppender, def: TypeDefinition | ServiceDefinition) {
|
||||
a(0, `import "./base.dart";`)
|
||||
def.depends.forEach((dep) => {
|
||||
a(0, this.getImport(dep));
|
||||
});
|
||||
}
|
||||
|
||||
getTypeParse(type: string, value: string) {
|
||||
if (conversion[type]) {
|
||||
return `${toDartType(type)}_fromJson(${value})`;
|
||||
} else {
|
||||
return `${toDartType(type)}.fromJson(${value})`;
|
||||
}
|
||||
}
|
||||
|
||||
generateType(definition: TypeDefinition): void {
|
||||
const { a, getResult } = LineAppender();
|
||||
|
||||
this.generateImports(a, definition);
|
||||
|
||||
a(0, ``);
|
||||
a(0, `class ${definition.name} {`);
|
||||
for (const field of definition.fields) {
|
||||
if (field.array) {
|
||||
a(1, `List<${toDartType(field.type)}>? ${field.name};`);
|
||||
} else if (field.map) {
|
||||
a(
|
||||
1,
|
||||
`Map<${toDartType(field.map)},${toDartType(field.type)}>? ${field.name
|
||||
};`
|
||||
);
|
||||
} else {
|
||||
a(1, `${toDartType(field.type)}? ${field.name};`);
|
||||
}
|
||||
}
|
||||
|
||||
a(0, ``);
|
||||
a(
|
||||
1,
|
||||
`${definition.name}({${definition.fields
|
||||
.map((e) => `this.${e.name}`)
|
||||
.join(", ")}});`
|
||||
);
|
||||
a(0, ``);
|
||||
a(1, `${definition.name}.fromJson(Map<String, dynamic> json) {`);
|
||||
for (const field of definition.fields) {
|
||||
a(2, `if(json.containsKey("${field.name}")) {`);
|
||||
|
||||
if (field.array) {
|
||||
a(3, `this.${field.name} = [];`);
|
||||
a(3, `(json["${field.name}"] as List<dynamic>).forEach((e) => {`);
|
||||
a(4, `this.${field.name}!.add(${this.getTypeParse(field.type, "e")})`);
|
||||
a(3, `});`);
|
||||
} else if (field.map) {
|
||||
a(3, `this.${field.name} = {};`);
|
||||
a(
|
||||
3,
|
||||
`(json["${field.name}"] as Map<${toDartType(
|
||||
field.map
|
||||
)},dynamic>).forEach((key, value) => {`
|
||||
);
|
||||
a(4, `this.${field.name}![key] = ${this.getTypeParse(field.type, "value")}`);
|
||||
a(3, `});`);
|
||||
} else {
|
||||
a(
|
||||
3,
|
||||
`this.${field.name} = ${this.getTypeParse(field.type, `json["${field.name}"]`)};`
|
||||
);
|
||||
}
|
||||
a(2, `} else {`);
|
||||
a(3, `this.${field.name} = null;`);
|
||||
a(2, `}`);
|
||||
a(0, ``);
|
||||
}
|
||||
|
||||
a(1, `}`);
|
||||
|
||||
a(1, `Map<String, dynamic> toJson() {`);
|
||||
a(2, `Map<String, dynamic> res = {};`);
|
||||
for (const field of definition.fields) {
|
||||
if (conversion[field.type]) {
|
||||
a(2, `res["${field.name}"] = this.${field.name};`);
|
||||
} else {
|
||||
if (field.array) {
|
||||
a(
|
||||
2,
|
||||
`res["${field.name}"] = this.${field.name}?.map((entry) => entry.toJson()).toList();`
|
||||
);
|
||||
} else if (field.map) {
|
||||
// dict.map((key, value) => MapEntry(key, value.toString()));
|
||||
a(
|
||||
2,
|
||||
`res["${field.name}"] = this.${field.name}?.map((key, value) => MapEntry(key, value.toJson()));`
|
||||
);
|
||||
} else {
|
||||
a(2, `res["${field.name}"] = this.${field.name};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
a(2, `return res;`);
|
||||
a(1, `}`);
|
||||
|
||||
a(0, `}`);
|
||||
|
||||
this.writeFile(`lib/src/${definition.name}.dart`, getResult());
|
||||
}
|
||||
|
||||
generateEnum(definition: EnumDefinition): void {
|
||||
const { a, getResult } = LineAppender();
|
||||
|
||||
a(0, `enum ${definition.name} {`);
|
||||
for (const entry of definition.values) {
|
||||
const isLast =
|
||||
definition.values[definition.values.length - 1] == entry;
|
||||
a(1, `${entry.name}(${entry.value})${isLast ? ";" : ","}`);
|
||||
}
|
||||
a(0, ``);
|
||||
a(1, `final int val;`);
|
||||
a(1, `const ${definition.name}(int valT) : val= valT;`);
|
||||
a(1, `static ${definition.name}? fromJson(int val) {`);
|
||||
a(2, `switch(val){`);
|
||||
for (const entry of definition.values) {
|
||||
a(3, `case ${entry.value}:`);
|
||||
a(4, `return ${definition.name}.${entry.name};`);
|
||||
}
|
||||
a(3, `default:`);
|
||||
a(4, `return null;`);
|
||||
a(2, `}`);
|
||||
a(1, `}`);
|
||||
|
||||
a(0, ``);
|
||||
|
||||
a(1, `int toJson() {`);
|
||||
a(2, `return this.val;`);
|
||||
a(1, `}`);
|
||||
|
||||
a(0, ``);
|
||||
a(0, `}`);
|
||||
|
||||
a(0, ``);
|
||||
|
||||
this.writeFile(`lib/src/${definition.name}.dart`, getResult());
|
||||
}
|
||||
|
||||
generateServiceClient(definition: ServiceDefinition): void {
|
||||
const { a, getResult } = LineAppender();
|
||||
|
||||
this.generateImports(a, definition);
|
||||
a(0, `import "./service_client.dart";`);
|
||||
a(0, ``);
|
||||
a(0, `class ${definition.name}Client extends Service {`);
|
||||
a(0, ``);
|
||||
|
||||
a(1, `${definition.name}Client(ServiceProvider provider):super(provider, "${definition.name}");`);
|
||||
|
||||
a(0, ``);
|
||||
|
||||
for (const func of definition.functions) {
|
||||
const args = func.inputs.map(inp =>
|
||||
(inp.array ? `List<${toDartType(inp.type)}>` : toDartType(inp.type)) + " " + inp.name
|
||||
).join(", ");
|
||||
|
||||
const asParams = func.inputs.map(e => e.name).join(", ");
|
||||
|
||||
if (!func.return) {
|
||||
a(1, `void ${func.name}(${args}) {`)
|
||||
a(2, `provider.sendNotification("${definition.name}.${func.name}", [${asParams}]);`);
|
||||
a(1, `}`);
|
||||
} else {
|
||||
const baseReturnType = func.return.type != "void" ? (toDartType(func.return.type) + "?") : toDartType(func.return.type);
|
||||
const returnType = func.return.array ? `List<${baseReturnType}>` : baseReturnType;
|
||||
|
||||
a(1, `Future<${returnType}> ${func.name}(${args}) async {`);
|
||||
a(2, `var res = await this.provider.sendRequest("${definition.name}.${func.name}", [${asParams}]);`);
|
||||
if (func.return.type !== "void") {
|
||||
if(func.return.array) {
|
||||
a(2, `return res.map((entry) =>${this.getTypeParse(func.return.type, "entry")}).toList();`);
|
||||
} else {
|
||||
a(2, `return ${this.getTypeParse(func.return.type, "res")};`);
|
||||
}
|
||||
}
|
||||
a(1, `}`);
|
||||
}
|
||||
a(0, ``);
|
||||
}
|
||||
a(0, `}`);
|
||||
a(0, ``);
|
||||
|
||||
this.writeFile(`lib/src/${definition.name}Client.dart`, getResult());
|
||||
}
|
||||
|
||||
generateServiceServer(definition: ServiceDefinition): void {
|
||||
console.log(
|
||||
chalk.yellow("[DART] WARNING:"),
|
||||
"DART support for services is not yet there. Service generation is currently limited to clients!"
|
||||
);
|
||||
}
|
||||
|
||||
generateService(definition: ServiceDefinition): void {
|
||||
this.generateServiceClient(definition);
|
||||
this.writeFile("lib/src/service_client.dart", this.getTemplate("Dart/service_client.dart"))
|
||||
}
|
||||
|
||||
finalize(steps: Step[]): void {
|
||||
const { a, getResult } = LineAppender();
|
||||
|
||||
a(0, `library ${this.options.dart_library_name};`)
|
||||
a(0, ``);
|
||||
|
||||
|
||||
let hasService = false;
|
||||
|
||||
|
||||
steps.forEach(([type, def]) => {
|
||||
switch (type) {
|
||||
case "type":
|
||||
a(0, `export 'src/${def.name}.dart';`);
|
||||
break;
|
||||
case "enum":
|
||||
a(0, `export 'src/${def.name}.dart';`);
|
||||
break;
|
||||
case "service":
|
||||
a(0, `export 'src/${def.name}Client.dart';`);
|
||||
hasService = true;
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
chalk.yellow("[DART] WARNING:"),
|
||||
"unimplemented step found:",
|
||||
type
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasService) {
|
||||
a(0, `export 'src/service_client.dart';`)
|
||||
}
|
||||
|
||||
this.writeFile(`lib/${this.options.dart_library_name}.dart`, getResult());
|
||||
this.writeFile(`pubspec.yaml`, this.getTemplate("Dart/pubspec.yaml").replace("__NAME__", this.options.dart_library_name));
|
||||
this.writeFile(`lib/src/base.dart`, this.getTemplate("Dart/base.dart"));
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ function toZigType(type: string): string {
|
||||
return (conversion as any)[type] || type;
|
||||
}
|
||||
|
||||
export class ZIGTarget extends CompileTarget<{ csharp_namespace: string }> {
|
||||
export class ZIGTarget extends CompileTarget<{ }> {
|
||||
name: string = "zig";
|
||||
|
||||
start(): void {
|
||||
|
30
templates/Dart/analysis_options.yaml
Normal file
30
templates/Dart/analysis_options.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
52
templates/Dart/base.dart
Normal file
52
templates/Dart/base.dart
Normal file
@ -0,0 +1,52 @@
|
||||
class JRPCError extends Error {
|
||||
String message;
|
||||
JRPCError(this.message);
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
int? int_fromJson(dynamic val) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
} else if (val is int) {
|
||||
return val;
|
||||
} else if (val is double) {
|
||||
return val.toInt();
|
||||
} else {
|
||||
throw JRPCError("Not a number!");
|
||||
}
|
||||
}
|
||||
|
||||
double? double_fromJson(dynamic val) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
} else if (val is int) {
|
||||
return val.toDouble();
|
||||
} else if (val is double) {
|
||||
return val;
|
||||
} else {
|
||||
throw JRPCError("Not a number!");
|
||||
}
|
||||
}
|
||||
|
||||
bool? bool_fromJson(dynamic val) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
} else if (val is bool) {
|
||||
return val;
|
||||
} else {
|
||||
throw JRPCError("Not a bool!");
|
||||
}
|
||||
}
|
||||
|
||||
String? String_fromJson(dynamic val) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
} else if (val is String) {
|
||||
return val;
|
||||
} else {
|
||||
return val.toString(); // TODO: Either this or error
|
||||
// throw JRPCError("Not a string!");
|
||||
}
|
||||
}
|
10
templates/Dart/pubspec.yaml
Normal file
10
templates/Dart/pubspec.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
name: __NAME__
|
||||
description: JRPC
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: '>=2.17.6 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
86
templates/Dart/service_client.dart
Normal file
86
templates/Dart/service_client.dart
Normal file
@ -0,0 +1,86 @@
|
||||
import "dart:async";
|
||||
import 'dart:math';
|
||||
|
||||
import "./base.dart";
|
||||
|
||||
abstract class Service {
|
||||
String name;
|
||||
ServiceProvider provider;
|
||||
|
||||
Service(this.provider, this.name) {
|
||||
provider._services[name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceProvider {
|
||||
final Map<String, Service> _services = {};
|
||||
final Map<String, Completer<dynamic>> _requests = {};
|
||||
|
||||
StreamController<Map<String, dynamic>> output = StreamController();
|
||||
late StreamSubscription s;
|
||||
|
||||
ServiceProvider(Stream<Map<String, dynamic>> input) {
|
||||
s = input.listen(onMessage);
|
||||
}
|
||||
|
||||
void onMessage(Map<String, dynamic> msg) {
|
||||
// print("Working on message");
|
||||
if (msg.containsKey("method")) {
|
||||
if (msg.containsKey("id")) {
|
||||
print("Message is request");
|
||||
// Request, not supported!
|
||||
return;
|
||||
} else {
|
||||
// print("Message is notification");
|
||||
// Notification
|
||||
// TODO: Implement
|
||||
}
|
||||
} else {
|
||||
// print("Message is response");
|
||||
// Response
|
||||
var req = _requests[msg["id"]];
|
||||
|
||||
if (req == null) {
|
||||
// print("Could not find related request. Ignoring");
|
||||
return; // Irrelevant response
|
||||
}
|
||||
if (msg.containsKey("error")) {
|
||||
//TODO:
|
||||
req.completeError(JRPCError(msg["error"]["message"]));
|
||||
} else {
|
||||
req.complete(msg["result"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> sendRequest(String method, dynamic params) {
|
||||
var id = nanoid(10);
|
||||
var req = {"jsonrpc": "2.0", "id": id, "method": method, "params": params};
|
||||
|
||||
var completer = Completer<dynamic>();
|
||||
|
||||
output.add(req);
|
||||
_requests[id] = completer;
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void sendNotification(String method, dynamic params) {
|
||||
var req = {"jsonrpc": "2.0", "method": method, "params": params};
|
||||
output.add(req);
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from: https://github.com/pd4d10/nanoid-dart (MIT License)
|
||||
final _random = Random.secure();
|
||||
const urlAlphabet =
|
||||
'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
|
||||
|
||||
String nanoid([int size = 21]) {
|
||||
final len = urlAlphabet.length;
|
||||
String id = '';
|
||||
while (0 < size--) {
|
||||
id += urlAlphabet[_random.nextInt(len)];
|
||||
}
|
||||
return id;
|
||||
}
|
Reference in New Issue
Block a user