First Commit
Yes, that is what I do at 31.12.2021 at 22:39 local time...
This commit is contained in:
		
							
								
								
									
										11
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| root=true | ||||
| [*] | ||||
| charset = utf-8 | ||||
| end_of_line = lf | ||||
| indent_size = 3 | ||||
| indent_style = space | ||||
| insert_final_newline = true | ||||
| [*.yml] | ||||
| indent_size = 2 | ||||
| [*.md] | ||||
| indent_size = 2 | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| node_modules/ | ||||
| .yarn/cache | ||||
| .yarn/install-state.gz | ||||
| examples/out | ||||
							
								
								
									
										363
									
								
								.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										768
									
								
								.yarn/releases/yarn-3.1.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										768
									
								
								.yarn/releases/yarn-3.1.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										7
									
								
								.yarnrc.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.yarnrc.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| nodeLinker: node-modules | ||||
|  | ||||
| plugins: | ||||
|   - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs | ||||
|     spec: "@yarnpkg/plugin-interactive-tools" | ||||
|  | ||||
| yarnPath: .yarn/releases/yarn-3.1.1.cjs | ||||
							
								
								
									
										33
									
								
								examples/example.binc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								examples/example.binc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| import "./import.binc"; | ||||
|  | ||||
| enum TestEnum { | ||||
|    VAL1, | ||||
|    VAL2, | ||||
|    VAL10 = 10, | ||||
|    VAL11, | ||||
|    VAL12 | ||||
| } | ||||
|  | ||||
| type Test { | ||||
|    atom: TestAtom; | ||||
|    array: TestAtom[]; | ||||
|    enumValue: TestEnum; | ||||
|    map: {number, TestAtom}; | ||||
| } | ||||
|  | ||||
|  | ||||
| type AddValueRequest { | ||||
|    value1: number; | ||||
|    value2: number; | ||||
| } | ||||
|  | ||||
| type AddValueResponse { | ||||
|    value: number; | ||||
| } | ||||
|  | ||||
| service TestService { | ||||
|    AddValuesSingleParam(request: AddValueRequest): AddValueResponse; | ||||
|    AddValuesMultipleParams(value1: number, value2: number): number; | ||||
|  | ||||
|    notification OnEvent(param1: string); | ||||
| } | ||||
							
								
								
									
										5
									
								
								examples/import.binc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								examples/import.binc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| type TestAtom { | ||||
|    val_number: number; | ||||
|    val_boolean: boolean; | ||||
|    val_string: string; | ||||
| } | ||||
							
								
								
									
										54
									
								
								examples/test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								examples/test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| import { AddValueRequest, AddValueResponse, Logging } from "./out/index" | ||||
|  | ||||
|  | ||||
| // Logging.verbose = false; | ||||
|  | ||||
| import * as Client from "./out/index_client" | ||||
| import * as Server from "./out/index_server" | ||||
|  | ||||
| const client = new Client.ServiceProvider(msg=>{ | ||||
|    session.onMessage(msg); | ||||
| }) | ||||
|  | ||||
| const server = new Server.ServiceProvider() | ||||
|  | ||||
| const session =    server.getSession((msg) => { | ||||
|    client.onPacket(msg); | ||||
| }) | ||||
|  | ||||
| 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; | ||||
|    } | ||||
|  | ||||
|    OnEvent(param1: string, ctx: undefined): void { | ||||
|       console.log("Received notification", param1); | ||||
|    } | ||||
|  | ||||
| } | ||||
|  | ||||
| server.addService(new TestService()) | ||||
|  | ||||
| const test = new Client.TestService(client); | ||||
|  | ||||
| async function run() { | ||||
|    console.log("Testing AddValuesSingleParam") | ||||
|    console.log(await test.AddValuesSingleParam({ | ||||
|       value1: 1, | ||||
|       value2: 2 | ||||
|    })); | ||||
|  | ||||
|    console.log("Testing AddValuesMultipleParams") | ||||
|    console.log(await test.AddValuesMultipleParams(1,2)); | ||||
|  | ||||
|    console.log("Testing Notification") | ||||
|    test.OnEvent("Hi, this is an event"); | ||||
| } | ||||
|  | ||||
| run(); | ||||
|  | ||||
							
								
								
									
										24
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| { | ||||
|    "name": "@hibas123/SimpleRPC", | ||||
|    "version": "1.0.0", | ||||
|    "main": "lib/index.js", | ||||
|    "license": "MIT", | ||||
|    "packageManager": "yarn@3.1.1", | ||||
|    "scripts": { | ||||
|       "start": "ts-node src/index.ts", | ||||
|       "test": "npm run start -- compile examples/example.binc -o=ts-node:examples/out && ts-node examples/test.ts" | ||||
|    }, | ||||
|    "devDependencies": { | ||||
|       "@types/debug": "^4.1.7", | ||||
|       "@types/node": "^17.0.5", | ||||
|       "@types/yargs": "^17.0.8", | ||||
|       "ts-node": "^10.4.0", | ||||
|       "typescript": "^4.5.4" | ||||
|    }, | ||||
|    "dependencies": { | ||||
|       "chalk": "4", | ||||
|       "debug": "^4.3.3", | ||||
|       "prettier": "^2.5.1", | ||||
|       "yargs": "^17.3.1" | ||||
|    } | ||||
| } | ||||
							
								
								
									
										74
									
								
								src/compile.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/compile.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| import * as FS from "fs"; | ||||
| import * as Path from "path"; | ||||
| import { | ||||
|    EnumDefinition, | ||||
|    IR, | ||||
|    ServiceDefinition, | ||||
|    Step, | ||||
|    TypeDefinition, | ||||
| } from "./ir"; | ||||
|  | ||||
| export abstract class CompileTarget { | ||||
|    abstract name: string; | ||||
|    constructor(private outputFolder: string) { | ||||
|       if (!FS.existsSync(outputFolder)) { | ||||
|          FS.mkdirSync(outputFolder, { | ||||
|             recursive: true, | ||||
|          }); | ||||
|       } | ||||
|    } | ||||
|  | ||||
|    abstract start(): void; | ||||
|  | ||||
|    abstract generateType(definition: TypeDefinition): void; | ||||
|  | ||||
|    abstract generateEnum(definition: EnumDefinition): void; | ||||
|  | ||||
|    abstract generateService(definition: ServiceDefinition): void; | ||||
|  | ||||
|    abstract finalize(steps: Step[]): void; | ||||
|  | ||||
|    protected writeFile(name: string, content: string | Promise<string>) { | ||||
|       if (content instanceof Promise) { | ||||
|          content.then((res) => | ||||
|             FS.writeFileSync(Path.join(this.outputFolder, name), res) | ||||
|          ); | ||||
|       } else { | ||||
|          FS.writeFileSync(Path.join(this.outputFolder, name), content); | ||||
|       } | ||||
|    } | ||||
|  | ||||
|    protected getTemplate(name: string): string { | ||||
|       let path = Path.join(__dirname, "../templates/" + name); | ||||
|       let file = FS.readFileSync(path, "utf-8"); | ||||
|  | ||||
|       const splitted = file.split("\n"); | ||||
|       let res = []; | ||||
|       let ignore = false; | ||||
|       for (const line of splitted) { | ||||
|          if (ignore) { | ||||
|             ignore = false; | ||||
|          } else if (line.trim().startsWith("//@template-ignore")) { | ||||
|             ignore = true; | ||||
|          } else { | ||||
|             res.push(line); | ||||
|          } | ||||
|       } | ||||
|  | ||||
|       return res.join("\n"); | ||||
|    } | ||||
| } | ||||
|  | ||||
| export default function compile(ir: IR, target: CompileTarget) { | ||||
|    // Types are verified. They are now ready to be compiled to targets | ||||
|  | ||||
|    // setState("Building for target: " + target.name); | ||||
|    ir.forEach((step) => { | ||||
|       const [type, def] = step; | ||||
|       if (type == "type") target.generateType(def as TypeDefinition); | ||||
|       else if (type == "enum") target.generateEnum(def as EnumDefinition); | ||||
|       else if (type == "service") | ||||
|          target.generateService(def as ServiceDefinition); | ||||
|    }); | ||||
|    if (target.finalize) target.finalize(ir); | ||||
| } | ||||
							
								
								
									
										62
									
								
								src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| import yargs from "yargs"; | ||||
| import { hideBin } from "yargs/helpers"; | ||||
| import startCompile, { Target } from "./process"; | ||||
|  | ||||
| import dbg from "debug"; | ||||
| const log = dbg("app"); | ||||
|  | ||||
| dbg.disable(); | ||||
|  | ||||
| yargs(hideBin(process.argv)) | ||||
|    .version("1.0.0") | ||||
|    .command( | ||||
|       "compile <input>", | ||||
|       "Compile source", | ||||
|       (yargs) => { | ||||
|          return yargs | ||||
|             .positional("input", { | ||||
|                describe: "Input file", | ||||
|                type: "string", | ||||
|                demandOption: true, | ||||
|             }) | ||||
|             .option("definition", { | ||||
|                type: "string", | ||||
|                describe: "Emit definition json at specified location", | ||||
|             }) | ||||
|             .option("output", { | ||||
|                type: "string", | ||||
|                describe: "Output lang and location 'ts:out/' 'c:/test'", | ||||
|                alias: "o", | ||||
|                coerce: (arg: string | string[] | undefined) => { | ||||
|                   if (!arg) return []; | ||||
|                   if (!Array.isArray(arg)) arg = [arg]; | ||||
|                   return arg.map((input) => { | ||||
|                      const [type, output] = input.split(":", 2); | ||||
|                      return { | ||||
|                         type, | ||||
|                         output, | ||||
|                      } as Target; | ||||
|                   }); | ||||
|                }, | ||||
|                array: true, | ||||
|             }); | ||||
|       }, | ||||
|       (argv) => { | ||||
|          if (argv.verbose) {dbg.enable("app");} | ||||
|          log("Received compile command with args", argv); | ||||
|  | ||||
|          startCompile({ | ||||
|             input: argv.input, | ||||
|             targets: argv.output as any, | ||||
|             emitDefinitions: argv.definition | ||||
|          }) | ||||
|       } | ||||
|    ) | ||||
|    .option("verbose", { | ||||
|       alias: "v", | ||||
|       type: "boolean", | ||||
|       describe: "Adds additional outputs", | ||||
|    }) | ||||
|    .strictCommands() | ||||
|    .demandCommand() | ||||
|    .parse(); | ||||
							
								
								
									
										232
									
								
								src/ir.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								src/ir.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,232 @@ | ||||
| import type { Parsed, StatementNode } from "./parser"; | ||||
| import dbg from "debug"; | ||||
| const log = dbg("app"); | ||||
|  | ||||
| const builtin = ["number", "string", "boolean"]; | ||||
|  | ||||
| export class IRError extends Error { | ||||
|    constructor(public statement: StatementNode, message: string) { | ||||
|       super("Error Compiling: " + message); | ||||
|    } | ||||
| } | ||||
|  | ||||
| export interface TypeFieldDefinition { | ||||
|    name: string; | ||||
|    type: string; | ||||
|    array: boolean; | ||||
|    map?: string; | ||||
| } | ||||
|  | ||||
| export interface TypeDefinition { | ||||
|    name: string; | ||||
|    depends: string[]; | ||||
|    fields: TypeFieldDefinition[]; | ||||
| } | ||||
|  | ||||
| export interface EnumValueDefinition { | ||||
|    name: string; | ||||
|    value: number; | ||||
| } | ||||
|  | ||||
| export interface EnumDefinition { | ||||
|    name: string; | ||||
|    values: EnumValueDefinition[]; | ||||
| } | ||||
|  | ||||
| export interface ServiceFunctionParamsDefinition { | ||||
|    name: string; | ||||
|    inputs: { type: string; name: string }[]; | ||||
|    return: string | undefined; | ||||
| } | ||||
| export type ServiceFunctionDefinition = ServiceFunctionParamsDefinition; | ||||
|  | ||||
| export interface ServiceDefinition { | ||||
|    name: string; | ||||
|    depends: string[]; | ||||
|    functions: ServiceFunctionDefinition[]; | ||||
| } | ||||
|  | ||||
| export type Step = [ | ||||
|    "type" | "enum" | "service", | ||||
|    TypeDefinition | EnumDefinition | ServiceDefinition | ||||
| ]; | ||||
|  | ||||
| export type IR = Step[]; | ||||
|  | ||||
| export default function get_ir(parsed: Parsed): IR { | ||||
|    log("Generatie IR from parse output"); | ||||
|    let defined: string[] = []; | ||||
|    let types: string[] = []; | ||||
|    let enums: string[] = []; | ||||
|  | ||||
|    // Verifiy and generating steps | ||||
|  | ||||
|    let steps: Step[] = []; | ||||
|  | ||||
|    parsed.forEach((statement) => { | ||||
|       log("Working on statement of type %s", statement.type); | ||||
|       if (statement.type == "import") | ||||
|          throw new IRError( | ||||
|             statement, | ||||
|             "Import statements are invalid at this step!" | ||||
|          ); | ||||
|  | ||||
|       if (statement.type === "type") { | ||||
|          if (defined.indexOf(statement.name) >= 0) { | ||||
|             throw new IRError( | ||||
|                statement, | ||||
|                `Type ${statement.name} already defined!` | ||||
|             ); | ||||
|          } | ||||
|  | ||||
|          let depends: string[] = []; | ||||
|          const fields = statement.fields.map<TypeFieldDefinition>((field) => { | ||||
|             if (field.type !== "type_field") { | ||||
|                throw new IRError(field, "Invalid statement!"); | ||||
|             } | ||||
|  | ||||
|             if (defined.indexOf(field.fieldtype) < 0) { | ||||
|                if (builtin.indexOf(field.fieldtype) < 0) { | ||||
|                   throw new IRError( | ||||
|                      field, | ||||
|                      `Type ${field.fieldtype} is not defined!` | ||||
|                   ); | ||||
|                } | ||||
|             } else { | ||||
|                if (depends.indexOf(field.fieldtype) < 0) | ||||
|                   depends.push(field.fieldtype); | ||||
|             } | ||||
|  | ||||
|             if ( | ||||
|                field.map && | ||||
|                field.map !== "number" && | ||||
|                field.map !== "string" | ||||
|             ) { | ||||
|                throw new IRError( | ||||
|                   field, | ||||
|                   `Type ${field.map} is not valid as map key!` | ||||
|                ); | ||||
|             } | ||||
|  | ||||
|             return { | ||||
|                name: field.name, | ||||
|                type: field.fieldtype, | ||||
|                array: field.array, | ||||
|                map: field.map, | ||||
|             }; | ||||
|          }); | ||||
|          steps.push([ | ||||
|             statement.type, | ||||
|             { | ||||
|                name: statement.name, | ||||
|                depends, | ||||
|                fields, | ||||
|             }, | ||||
|          ]); | ||||
|          defined.push(statement.name); | ||||
|          types.push(statement.name); | ||||
|       } else if (statement.type === "enum") { | ||||
|          if (defined.indexOf(statement.name) >= 0) { | ||||
|             throw new IRError( | ||||
|                statement, | ||||
|                `Type ${statement.name} already defined!` | ||||
|             ); | ||||
|          } | ||||
|  | ||||
|          let last = -1; | ||||
|          let values = statement.values.map<EnumValueDefinition>((valueS) => { | ||||
|             let value = last + 1; | ||||
|             if (valueS.value) { | ||||
|                if (valueS.value <= last) { | ||||
|                   throw new IRError( | ||||
|                      statement, | ||||
|                      "Enum value must be larger than the previous one!" | ||||
|                   ); | ||||
|                } else { | ||||
|                   value = valueS.value; | ||||
|                } | ||||
|             } | ||||
|  | ||||
|             last = value; | ||||
|  | ||||
|             return { | ||||
|                name: valueS.name, | ||||
|                value, | ||||
|             }; | ||||
|          }); | ||||
|          steps.push([ | ||||
|             "enum", | ||||
|             { | ||||
|                name: statement.name, | ||||
|                values, | ||||
|             } as EnumDefinition, | ||||
|          ]); | ||||
|          defined.push(statement.name); | ||||
|          enums.push(statement.name); | ||||
|       } else if (statement.type === "service") { | ||||
|          if (defined.indexOf(statement.name) >= 0) { | ||||
|             throw new IRError( | ||||
|                statement, | ||||
|                `Type ${statement.name} already defined!` | ||||
|             ); | ||||
|          } | ||||
|  | ||||
|          let depends: string[] = []; | ||||
|          let alreadyFoundFunctions = new Set<string>(); | ||||
|          let functions = statement.functions.map((fnc) => { | ||||
|             if (alreadyFoundFunctions.has(fnc.name)) | ||||
|                throw new IRError( | ||||
|                   fnc, | ||||
|                   `Function with name ${fnc.name} already defined!` | ||||
|                ); | ||||
|             alreadyFoundFunctions.add(fnc.name); | ||||
|             if (fnc.return_type) { | ||||
|                if (defined.indexOf(fnc.return_type) >= 0) { | ||||
|                   if (!depends.some((a) => a === fnc.return_type)) | ||||
|                      depends.push(fnc.return_type); | ||||
|                } else { | ||||
|                   if (builtin.indexOf(fnc.return_type) < 0) { | ||||
|                      throw new IRError( | ||||
|                         fnc, | ||||
|                         `Type ${fnc.return_type} is not defined` | ||||
|                      ); | ||||
|                   } | ||||
|                } | ||||
|             } | ||||
|  | ||||
|             for (const input of fnc.inputs) { | ||||
|                if (defined.indexOf(input.type) >= 0) { | ||||
|                   if (!depends.some((a) => a === input.type)) | ||||
|                      depends.push(input.type); | ||||
|                } else { | ||||
|                   if (builtin.indexOf(input.type) < 0) { | ||||
|                      throw new IRError( | ||||
|                         fnc, | ||||
|                         `Type ${input.type} is not defined` | ||||
|                      ); | ||||
|                   } | ||||
|                } | ||||
|             } | ||||
|  | ||||
|             return { | ||||
|                name: fnc.name, | ||||
|                inputs: fnc.inputs, | ||||
|                return: fnc.return_type, | ||||
|             } as ServiceFunctionDefinition; | ||||
|          }); | ||||
|  | ||||
|          steps.push([ | ||||
|             "service", | ||||
|             { | ||||
|                name: statement.name, | ||||
|                depends, | ||||
|                functions, | ||||
|             } as ServiceDefinition, | ||||
|          ]); | ||||
|       } else { | ||||
|          throw new IRError(statement, "Invalid statement!"); | ||||
|       } | ||||
|    }); | ||||
|  | ||||
|    return steps; | ||||
| } | ||||
							
								
								
									
										381
									
								
								src/parser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								src/parser.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,381 @@ | ||||
| import type { Token } from "./tokenizer"; | ||||
|  | ||||
| export interface DefinitionNode { | ||||
|    type: string; | ||||
|    location: { | ||||
|       file: string; | ||||
|       idx: number; | ||||
|    }; | ||||
| } | ||||
|  | ||||
| export interface ImportStatement extends DefinitionNode { | ||||
|    type: "import"; | ||||
|    path: string; | ||||
| } | ||||
|  | ||||
| export interface TypeFieldStatement extends DefinitionNode { | ||||
|    type: "type_field"; | ||||
|    name: string; | ||||
|    fieldtype: string; | ||||
|    array: boolean; | ||||
|    map?: string; | ||||
| } | ||||
|  | ||||
| export interface EnumValueStatement extends DefinitionNode { | ||||
|    type: "enum_value"; | ||||
|    name: string; | ||||
|    value?: number; | ||||
| } | ||||
|  | ||||
| export interface EnumStatement extends DefinitionNode { | ||||
|    type: "enum"; | ||||
|    name: string; | ||||
|    values: EnumValueStatement[]; | ||||
| } | ||||
|  | ||||
| export interface TypeStatement extends DefinitionNode { | ||||
|    type: "type"; | ||||
|    name: string; | ||||
|    fields: TypeFieldStatement[]; | ||||
| } | ||||
|  | ||||
| export interface IServiceFunctionInput { | ||||
|    name: string; | ||||
|    type: string; | ||||
| } | ||||
|  | ||||
| export interface ServiceFunctionStatement extends DefinitionNode { | ||||
|    type: "service_function"; | ||||
|    inputs: IServiceFunctionInput[]; | ||||
|    name: string; | ||||
|    return_type: string | undefined; // Makes it a notification | ||||
| } | ||||
|  | ||||
| export interface ServiceStatement extends DefinitionNode { | ||||
|    type: "service"; | ||||
|    name: string; | ||||
|    functions: ServiceFunctionStatement[]; | ||||
| } | ||||
|  | ||||
| export type RootStatementNode = | ||||
|    | ImportStatement | ||||
|    | TypeStatement | ||||
|    | ServiceStatement | ||||
|    | EnumStatement; | ||||
| export type StatementNode = | ||||
|    | RootStatementNode | ||||
|    | TypeFieldStatement | ||||
|    | ServiceFunctionStatement | ||||
|    | EnumValueStatement; | ||||
|  | ||||
| export type Parsed = RootStatementNode[]; | ||||
|  | ||||
| export class ParserError extends Error { | ||||
|    token: Token; | ||||
|    constructor(message: string, token: Token) { | ||||
|       super(message); | ||||
|       this.token = token; | ||||
|    } | ||||
| } | ||||
|  | ||||
| export default function parse(tokens: Token[], file: string): Parsed { | ||||
|    const tokenIterator = tokens[Symbol.iterator](); | ||||
|    let currentToken: Token = tokenIterator.next().value; | ||||
|    let nextToken: Token = tokenIterator.next().value; | ||||
|  | ||||
|    const eatToken = (value?: string) => { | ||||
|       if (value && value !== currentToken.value) { | ||||
|          throw new ParserError( | ||||
|             `Unexpected token value, expected '${value}', received '${currentToken.value}'`, | ||||
|             currentToken | ||||
|          ); | ||||
|       } | ||||
|       let idx = currentToken.startIdx; | ||||
|       currentToken = nextToken; | ||||
|       nextToken = tokenIterator.next().value; | ||||
|       return idx; | ||||
|    }; | ||||
|  | ||||
|    const eatText = (): [string, number] => { | ||||
|       checkTypes("text"); | ||||
|       let val = currentToken.value; | ||||
|       let idx = currentToken.startIdx; | ||||
|       eatToken(); | ||||
|       return [val, idx]; | ||||
|    }; | ||||
|    const eatNumber = (): number => { | ||||
|       checkTypes("number"); | ||||
|       let val = Number(currentToken.value); | ||||
|       if (Number.isNaN(val)) { | ||||
|          throw new ParserError( | ||||
|             `Value cannot be parsed as number! ${currentToken.value}`, | ||||
|             currentToken | ||||
|          ); | ||||
|       } | ||||
|       eatToken(); | ||||
|       return val; | ||||
|    }; | ||||
|  | ||||
|    const checkTypes = (...types: string[]) => { | ||||
|       if (types.indexOf(currentToken.type) < 0) { | ||||
|          throw new ParserError( | ||||
|             `Unexpected token value, expected ${types.join(" | ")}, received '${ | ||||
|                currentToken.value | ||||
|             }'`, | ||||
|             currentToken | ||||
|          ); | ||||
|       } | ||||
|    }; | ||||
|  | ||||
|    // const parseUnionField = (): UnionFieldStatement => { | ||||
|    //    let idx = currentToken.startIdx; | ||||
|    //    let name = currentToken.value; | ||||
|    //    eatToken(); | ||||
|    //    eatToken(":"); | ||||
|    //    let [type] = eatText(); | ||||
|    //    eatToken("="); | ||||
|    //    let label = eatNumber(); | ||||
|    //    eatToken(";"); | ||||
|  | ||||
|    //    return { | ||||
|    //       type: "union_field", | ||||
|    //       name, | ||||
|    //       label, | ||||
|    //       fieldtype: type, | ||||
|    //       location: { file, idx }, | ||||
|    //    }; | ||||
|    // }; | ||||
|  | ||||
|    const parseTypeField = (): TypeFieldStatement => { | ||||
|       const idx = currentToken.startIdx; | ||||
|       let name = currentToken.value; | ||||
|       eatToken(); | ||||
|       eatToken(":"); | ||||
|  | ||||
|       let array = false; | ||||
|       let type: string; | ||||
|       let mapKey: string | undefined = undefined; | ||||
|  | ||||
|       if (currentToken.type === "curly_open") { | ||||
|          eatToken("{"); | ||||
|          [mapKey] = eatText(); | ||||
|          eatToken(","); | ||||
|          [type] = eatText(); | ||||
|          eatToken("}"); | ||||
|       } else { | ||||
|          [type] = eatText(); | ||||
|          if (currentToken.type === "array") { | ||||
|             array = true; | ||||
|             eatToken("[]"); | ||||
|          } | ||||
|       } | ||||
|  | ||||
|       eatToken(";"); | ||||
|       return { | ||||
|          type: "type_field", | ||||
|          name, | ||||
|          fieldtype: type, | ||||
|          array, | ||||
|          map: mapKey, | ||||
|          location: { file, idx }, | ||||
|       }; | ||||
|    }; | ||||
|  | ||||
|    const parseTypeStatement = (): TypeStatement => { | ||||
|       const idx = eatToken("type"); | ||||
|       let [name] = eatText(); | ||||
|       eatToken("{"); | ||||
|       let fields: TypeFieldStatement[] = []; | ||||
|       while (currentToken.type === "text" || currentToken.type === "keyword") { | ||||
|          //Keywords can also be field names | ||||
|          fields.push(parseTypeField()); | ||||
|       } | ||||
|  | ||||
|       eatToken("}"); | ||||
|  | ||||
|       return { | ||||
|          type: "type", | ||||
|          name, | ||||
|          fields, | ||||
|          location: { file, idx }, | ||||
|       }; | ||||
|    }; | ||||
|  | ||||
|    // const parseUnionStatement = (): UnionStatement => { | ||||
|    //    const idx = eatToken("union"); | ||||
|    //    let [name] = eatText(); | ||||
|    //    eatToken("{"); | ||||
|    //    let fields: UnionFieldStatement[] = []; | ||||
|    //    while (currentToken.type === "text") { | ||||
|    //       fields.push(parseUnionField()); | ||||
|    //    } | ||||
|  | ||||
|    //    eatToken("}"); | ||||
|  | ||||
|    //    return { | ||||
|    //       type: "union", | ||||
|    //       name, | ||||
|    //       fields, | ||||
|    //       location: { file, idx }, | ||||
|    //    }; | ||||
|    // }; | ||||
|  | ||||
|    const parseImportStatement = (): ImportStatement => { | ||||
|       const idx = eatToken("import"); | ||||
|       checkTypes("text", "string"); | ||||
|       let path = currentToken.value; | ||||
|       if (currentToken.type === "string") { | ||||
|          path = path.substring(1, path.length - 1); | ||||
|       } | ||||
|  | ||||
|       eatToken(); | ||||
|       eatToken(";"); | ||||
|       return { | ||||
|          type: "import", | ||||
|          path, | ||||
|          location: { file, idx }, | ||||
|       }; | ||||
|    }; | ||||
|  | ||||
|    const parseEnumValue = (): EnumValueStatement => { | ||||
|       let [name, idx] = eatText(); | ||||
|       let value = undefined; | ||||
|       if (currentToken.type === "equals") { | ||||
|          eatToken("="); | ||||
|          value = eatNumber(); | ||||
|       } | ||||
|       return { | ||||
|          type: "enum_value", | ||||
|          name, | ||||
|          value, | ||||
|          location: { file, idx }, | ||||
|       }; | ||||
|    }; | ||||
|  | ||||
|    const parseEnumStatement = (): EnumStatement => { | ||||
|       let idx = eatToken("enum"); | ||||
|       let [name] = eatText(); | ||||
|       eatToken("{"); | ||||
|       let values: EnumValueStatement[] = []; | ||||
|       let next = currentToken.type === "text"; | ||||
|       while (next) { | ||||
|          values.push(parseEnumValue()); | ||||
|          if (currentToken.type === "comma") { | ||||
|             eatToken(","); | ||||
|             next = true; | ||||
|          } else { | ||||
|             next = false; | ||||
|          } | ||||
|       } | ||||
|       eatToken("}"); | ||||
|  | ||||
|       return { | ||||
|          type: "enum", | ||||
|          name: name, | ||||
|          values: values, | ||||
|          location: { file, idx }, | ||||
|       }; | ||||
|    }; | ||||
|  | ||||
|    const parseServiceFunction = ( | ||||
|       notification?: boolean | ||||
|    ): ServiceFunctionStatement => { | ||||
|       const [name, idx] = eatText(); | ||||
|  | ||||
|       eatToken("("); | ||||
|  | ||||
|       let input_streaming: string | undefined = undefined; | ||||
|       let inputs = []; | ||||
|  | ||||
|       if (currentToken.value !== ")") { | ||||
|          while (true) { | ||||
|             const [name] = eatText(); | ||||
|             eatToken(":"); | ||||
|             const [type] = eatText(); | ||||
|             inputs.push({ name, type }); | ||||
|             if (currentToken.value !== ",") break; | ||||
|             eatToken(","); | ||||
|          } | ||||
|       } | ||||
|  | ||||
|       eatToken(")"); | ||||
|  | ||||
|       let return_type = undefined; | ||||
|       if (!notification) { | ||||
|          eatToken(":"); | ||||
|  | ||||
|          return_type = eatText()[0]; | ||||
|       } | ||||
|  | ||||
|       eatToken(";"); | ||||
|  | ||||
|       return { | ||||
|          type: "service_function", | ||||
|          name, | ||||
|          location: { | ||||
|             file, | ||||
|             idx, | ||||
|          }, | ||||
|          inputs, | ||||
|          return_type, | ||||
|       }; | ||||
|    }; | ||||
|  | ||||
|    const parseServiceStatement = (): ServiceStatement => { | ||||
|       let idx = eatToken("service"); | ||||
|       let [name] = eatText(); | ||||
|       eatToken("{"); | ||||
|       let functions: ServiceFunctionStatement[] = []; | ||||
|  | ||||
|       while (currentToken.type === "text") { | ||||
|          let notification = false; | ||||
|          if (currentToken.value == "notification") { | ||||
|             eatText(); | ||||
|             notification = true; | ||||
|          } | ||||
|          functions.push(parseServiceFunction(notification)); | ||||
|       } | ||||
|       eatToken("}"); | ||||
|  | ||||
|       return { | ||||
|          type: "service", | ||||
|          name: name, | ||||
|          functions, | ||||
|          location: { file, idx }, | ||||
|       }; | ||||
|    }; | ||||
|  | ||||
|    const parseStatement = () => { | ||||
|       if (currentToken.type === "keyword") { | ||||
|          switch (currentToken.value) { | ||||
|             case "type": | ||||
|                return parseTypeStatement(); | ||||
|             // case "union": | ||||
|             //    return parseUnionStatement(); | ||||
|             case "import": | ||||
|                return parseImportStatement(); | ||||
|             case "enum": | ||||
|                return parseEnumStatement(); | ||||
|             case "service": | ||||
|                return parseServiceStatement(); | ||||
|             default: | ||||
|                throw new ParserError( | ||||
|                   `Unknown keyword ${currentToken.value}`, | ||||
|                   currentToken | ||||
|                ); | ||||
|          } | ||||
|       } else { | ||||
|          throw new ParserError( | ||||
|             `Invalid statement! ${currentToken.value}`, | ||||
|             currentToken | ||||
|          ); | ||||
|       } | ||||
|    }; | ||||
|  | ||||
|    const nodes: RootStatementNode[] = []; | ||||
|    while (currentToken) { | ||||
|       nodes.push(parseStatement()); | ||||
|    } | ||||
|  | ||||
|    return nodes; | ||||
| } | ||||
							
								
								
									
										154
									
								
								src/process.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								src/process.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,154 @@ | ||||
| import dbg from "debug"; | ||||
| import * as FS from "fs"; | ||||
| import Color from "chalk"; | ||||
| import * as Path from "path"; | ||||
| import tokenize, { TokenizerError } from "./tokenizer"; | ||||
| import parse, { Parsed, ParserError } from "./parser"; | ||||
| import get_ir, { IR } from "./ir"; | ||||
| import compile, { CompileTarget } from "./compile"; | ||||
| import { ESMTypescriptTarget, NodeJSTypescriptTarget } from "./targets/typescript"; | ||||
|  | ||||
| const log = dbg("app"); | ||||
|  | ||||
|  | ||||
| const Targets = new Map<string,typeof CompileTarget>(); | ||||
|  | ||||
| Targets.set("ts-esm", ESMTypescriptTarget) | ||||
| Targets.set("ts-node", NodeJSTypescriptTarget) | ||||
|  | ||||
|  | ||||
| function indexToLineAndCol(src: string, index: number) { | ||||
|    let line = 1; | ||||
|    let col = 1; | ||||
|    for (let i = 0; i < index; i++) { | ||||
|       if (src.charAt(i) === "\n") { | ||||
|          line++; | ||||
|          col = 1; | ||||
|       } else { | ||||
|          col++; | ||||
|       } | ||||
|    } | ||||
|  | ||||
|    return { line, col }; | ||||
| } | ||||
|  | ||||
| const fileCache = new Map<string, string>(); | ||||
| 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; | ||||
|       } catch (err) { | ||||
|          printError(new Error(`Cannot open file ${name};`), null, 0); | ||||
|       } | ||||
|    } | ||||
|    return undefined; | ||||
| } | ||||
|  | ||||
| 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); | ||||
|    } | ||||
|  | ||||
|    console.error(`${Color.red("ERROR: at")} ${file}:${loc.line}:${loc.col}`); | ||||
|    console.error("   ", err.message); | ||||
|    log(err.stack); | ||||
| } | ||||
|  | ||||
| export type Target = { | ||||
|    type: string; | ||||
|    output: string; | ||||
| }; | ||||
|  | ||||
| export type CompileOptions = { | ||||
|    input: string; | ||||
|    targets: Target[]; | ||||
|    emitDefinitions?: string; | ||||
| }; | ||||
|  | ||||
| type ProcessContext = { | ||||
|    options: CompileOptions; | ||||
|    processedFiles: Set<string>; | ||||
| }; | ||||
|  | ||||
| function processFile(ctx: ProcessContext, file: string): Parsed | null { | ||||
|    file = Path.resolve(file); | ||||
|    if (ctx.processedFiles.has(file)) { | ||||
|       log("Skipping file %s since it has already be processed", file); | ||||
|       return null; | ||||
|    } | ||||
|    log("Processing file %s", file); | ||||
|  | ||||
|    const content = getFile(file); | ||||
|    if (!content) throw new Error("Could not open file " + file); | ||||
|    try { | ||||
|       log("Tokenizing %s", file); | ||||
|       const tokens = tokenize(content); | ||||
|       log("Parsing %s", file); | ||||
|       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)); | ||||
|                return processFile(ctx, resolved); | ||||
|             } else { | ||||
|                return statement; | ||||
|             } | ||||
|          }) | ||||
|          .filter((e) => !!e) | ||||
|          .flat(1) as Parsed; | ||||
|       return resolved; | ||||
|    } catch (err) { | ||||
|       if (err instanceof TokenizerError) { | ||||
|          printError(err, file, err.index); | ||||
|       } else if (err instanceof ParserError) { | ||||
|          printError(err, file, err.token.startIdx); | ||||
|       } else { | ||||
|          throw err; | ||||
|       } | ||||
|  | ||||
|       return null; | ||||
|    } | ||||
| } | ||||
|  | ||||
| export default function startCompile(options: CompileOptions) { | ||||
|    const ctx = { | ||||
|       options, | ||||
|       processedFiles: new Set(), | ||||
|    } as ProcessContext; | ||||
|  | ||||
|    let ir: IR | undefined = undefined; | ||||
|    if(options.input.endsWith(".json")) { | ||||
|       ir = JSON.parse(FS.readFileSync(options.input, "utf-8")); | ||||
|    } else { | ||||
|       const parsed = processFile(ctx, options.input); | ||||
|       // console.log(([...parsed].pop() as any).functions) | ||||
|       if(!parsed) | ||||
|          throw new Error("Error compiling: Parse output is undefined!"); | ||||
|        | ||||
|       ir = get_ir(parsed); | ||||
|    } | ||||
|  | ||||
|    if(!ir) | ||||
|       throw new Error("Error compiling: Cannot get IR"); | ||||
|  | ||||
|    if(options.emitDefinitions) { | ||||
|       FS.writeFileSync(options.emitDefinitions, JSON.stringify(ir)); | ||||
|    } | ||||
|  | ||||
|    options.targets.forEach(target => { | ||||
|       const tg = Targets.get(target.type) as any | ||||
|       if(!tg) { | ||||
|          console.log(Color.red("ERROR:"), "Target not supported!"); | ||||
|          return; | ||||
|       } | ||||
|       compile(ir, new tg(target.output)); //TODO: implement | ||||
|    }) | ||||
| } | ||||
							
								
								
									
										426
									
								
								src/targets/typescript.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										426
									
								
								src/targets/typescript.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,426 @@ | ||||
| import { | ||||
|    TypeDefinition, | ||||
|    ServiceDefinition, | ||||
|    EnumDefinition, | ||||
|    TypeFieldDefinition, | ||||
|    Step, | ||||
| } from "../ir"; | ||||
|  | ||||
| import { CompileTarget, } from "../compile"; | ||||
|  | ||||
| type lineAppender = (ind: number, line: string | string[]) => void; | ||||
|  | ||||
| const conversion = { | ||||
|    boolean: "boolean", | ||||
|    number: "number", | ||||
|    string: "string", | ||||
| }; | ||||
|  | ||||
| function toJSType(type: string): string { | ||||
|    return (conversion as any)[type] || type; | ||||
| } | ||||
|  | ||||
| export class TypescriptTarget extends CompileTarget { | ||||
|    name = "Typescript"; | ||||
|  | ||||
|    flavour: "esm" | "node" = "node"; | ||||
|  | ||||
|    start() {} | ||||
|  | ||||
|    private generateImport(imports: string, path: string) { | ||||
|       return `import ${imports} from "${ | ||||
|          path + (this.flavour === "esm" ? ".ts" : "") | ||||
|       }";\n`; | ||||
|    } | ||||
|  | ||||
|    private generateImports( | ||||
|       a: lineAppender, | ||||
|       def: TypeDefinition | ServiceDefinition | ||||
|    ) { | ||||
|       a( | ||||
|          0, | ||||
|          def.depends.map((dep) => | ||||
|             this.generateImport( | ||||
|                `${dep}, { verify_${dep} }`, | ||||
|                "./" + dep | ||||
|             ) | ||||
|          ) | ||||
|       ); | ||||
|    } | ||||
|  | ||||
|    private getFileName(typename: string) { | ||||
|       return typename + ".ts"; | ||||
|    } | ||||
|  | ||||
|    private writeFormattedFile(file: string, code: string) { | ||||
|       this.writeFile(file, code); | ||||
|       //TODO: Add Prettier back | ||||
|       // const formatted = format(code, { | ||||
|       //    parser: "typescript", | ||||
|       //    tabWidth: 3, | ||||
|       // }); | ||||
|  | ||||
|       // this.writeFile(file, formatted); | ||||
|    } | ||||
|  | ||||
|    generateType(def: TypeDefinition) { | ||||
|       let lines: string[] = []; | ||||
|       const a: lineAppender = (i, t) => { | ||||
|          if (!Array.isArray(t)) { | ||||
|             t = [t]; | ||||
|          } | ||||
|          t.forEach((l) => lines.push("   ".repeat(i) + l.trim())); | ||||
|       }; | ||||
|  | ||||
|       this.generateImports(a, def); | ||||
|       a(0, `export default class ${def.name} {`); | ||||
|       a( | ||||
|          1, | ||||
|          def.fields.map((field) => { | ||||
|             let type = ""; | ||||
|             if (field.array) { | ||||
|                type = toJSType(field.type) + "[]"; | ||||
|             } else if (field.map) { | ||||
|                type = `Map<${toJSType(field.map)}, ${toJSType(field.type)}>`; | ||||
|             } else { | ||||
|                type = toJSType(field.type); | ||||
|             } | ||||
|             return `${field.name}?: ${type}; `; | ||||
|          }) | ||||
|       ); | ||||
|  | ||||
|       a(0, ``); | ||||
|       a(1, `constructor(init?:Partial<${def.name}>){`); | ||||
|       a(2, `if(init){`) | ||||
|       def.fields.forEach(field=>{ | ||||
|          a(3, `if(init["${field.name}"])`) | ||||
|          a(4, `this.${field.name} = init["${field.name}"];`) | ||||
|       }) | ||||
|       a(2, `}`); | ||||
|       a(1, `}`); | ||||
|  | ||||
|       a(0, ``); | ||||
|  | ||||
|  | ||||
|       a(0, ``); | ||||
|  | ||||
|       a(1, `static verify(data: ${def.name}){`); | ||||
|       a(2, `return verify_${def.name}(data);`); | ||||
|       a(1, `}`) | ||||
|       a(0, "}"); | ||||
|  | ||||
|       a(0, `export function verify_${def.name}(data: ${def.name}): boolean {`); | ||||
|       { | ||||
|          def.fields.forEach((field) => { | ||||
|             a( | ||||
|                1, | ||||
|                `if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {` | ||||
|             ); | ||||
|  | ||||
|             const ap: lineAppender = (i, l) => a(i + 2, l); | ||||
|             const verifyType = ( )=>{}; | ||||
|             a(2, "// TODO: Implement") | ||||
|             //TODO: Build verification | ||||
|             // if (field.array) { | ||||
|             //    a(2, `if(!Array.isArray(data["${field.name}"])) return false`); | ||||
|             //    a(2, `if(!(data["${field.name}"].some(e=>))) return false`) | ||||
|             //    serializeArray(field, `data["${field.name}"]`, ap); | ||||
|             // } else if (field.map) { | ||||
|             //    serializeMap(field, `data["${field.name}"]`, ap); | ||||
|             // } else { | ||||
|             //    serializeType(field, `data["${field.name}"]`, true, ap); | ||||
|             // } | ||||
|             a(1, "}"); | ||||
|             a(0, ``); | ||||
|          }); | ||||
|          a(1, `return true`); | ||||
|       } | ||||
|       a(0, `}`); | ||||
|  | ||||
|       this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); | ||||
|    } | ||||
|  | ||||
|    generateEnum(def: EnumDefinition) { | ||||
|       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, `enum ${def.name} {`); | ||||
|       for (const value of def.values) { | ||||
|          a(1, `${value.name}=${value.value},`); | ||||
|       } | ||||
|       a(0, `}`); | ||||
|  | ||||
|       a(0, `export default ${def.name}`); | ||||
|  | ||||
|       a( | ||||
|          0, | ||||
|          `export function verify_${def.name} (data: ${def.name}): boolean {` | ||||
|       ); | ||||
|       a(1, `return ${def.name}[data] != undefined`); | ||||
|       a(0, "}"); | ||||
|  | ||||
|  | ||||
|       this.writeFormattedFile(this.getFileName(def.name), lines.join("\n")); | ||||
|    } | ||||
|  | ||||
|    generateServiceClient(def: ServiceDefinition) { | ||||
|       let lines: string[] = []; | ||||
|       const a: lineAppender = (i, t) => { | ||||
|          if (!Array.isArray(t)) { | ||||
|             t = [t]; | ||||
|          } | ||||
|          t.forEach((l) => lines.push("   ".repeat(i) + l.trim())); | ||||
|       }; | ||||
|  | ||||
|       this.generateImports(a, def); | ||||
|  | ||||
|       a(0, `export type {`); | ||||
|       def.depends.forEach((dep) => { | ||||
|          a(1, `${dep},`); | ||||
|       }); | ||||
|       a(0, `}`); | ||||
|  | ||||
|       this.writeFile( | ||||
|          "service_client.ts", | ||||
|          this.generateImport( | ||||
|             "{ RequestObject, ResponseObject, ErrorCodes, Logging }", | ||||
|             "./service_base" | ||||
|          ) + | ||||
|          "\n\n" + | ||||
|          this.getTemplate("ts_service_client.ts") | ||||
|       ); | ||||
|  | ||||
|       a( | ||||
|          0, | ||||
|          this.generateImport( | ||||
|             "{ Service, ServiceProvider, getRandomID }", | ||||
|             "./service_client" | ||||
|          ) | ||||
|       ); | ||||
|  | ||||
|       a(0, `export class ${def.name} extends Service {`); | ||||
|       a(1, `constructor(provider: ServiceProvider){`); | ||||
|       a(2, `super(provider, "${def.name}");`); | ||||
|       a(1, `}`); | ||||
|  | ||||
|       for(const fnc of def.functions) { | ||||
|          const params = fnc.inputs.map(e=>`${e.name}: ${toJSType(e.type)}`).join(","); | ||||
|          //TODO: Prio 1 : Verify response! | ||||
|          //TODO: Prio 2 : Add optional parameters to this and the declaration file | ||||
|          //TODO: Prio 3 : Maybe verify params? But the server will to this regardless so... Maybe not?` | ||||
|          if(!fnc.return) { | ||||
|             a(1, `${fnc.name}(${params}): void {`); | ||||
|             a(2, `this._provider.sendMessage({`); | ||||
|             a(3, `jsonrpc: "2.0",`); | ||||
|             a(3, `method: "${def.name}.${fnc.name}",`); | ||||
|             a(3, `params: [...arguments]`); | ||||
|             a(2, `});`); | ||||
|             a(1, `}`); | ||||
|          } else { | ||||
|             const retType = fnc.return ? toJSType(fnc.return) : "void"; | ||||
|             a(1, `${fnc.name}(${params}): Promise<${retType}> {`); | ||||
|             a(2, `return new Promise<${retType}>((ok, err) => {`) | ||||
|             a(3, `this._provider.sendMessage({`); | ||||
|             a(4, `jsonrpc: "2.0",`); | ||||
|                a(4, `id: getRandomID(16),`); | ||||
|             a(4, `method: "${def.name}.${fnc.name}",`); | ||||
|             a(4, `params: [...arguments]`); | ||||
|                a(3, `}, {`); | ||||
|                a(4, `ok, err`); | ||||
|             a(3, `});`); | ||||
|             a(2, `});`); | ||||
|             a(1, `}`); | ||||
|          } | ||||
|          a(0, ``); | ||||
|       } | ||||
|  | ||||
|       a(0, `}`); | ||||
|  | ||||
|       this.writeFormattedFile( | ||||
|          this.getFileName(def.name + "_client"), | ||||
|          lines.join("\n") | ||||
|       ); | ||||
|    } | ||||
|  | ||||
|    generateServiceServer(def: ServiceDefinition) { | ||||
|       let lines: string[] = []; | ||||
|       const a: lineAppender = (i, t) => { | ||||
|          if (!Array.isArray(t)) { | ||||
|             t = [t]; | ||||
|          } | ||||
|          t.forEach((l) => lines.push("   ".repeat(i) + l.trim())); | ||||
|       }; | ||||
|  | ||||
|       this.writeFile( | ||||
|          "service_server.ts", | ||||
|          this.generateImport( | ||||
|             "{ RequestObject, ResponseObject, ErrorCodes, Logging }", | ||||
|             "./service_base" | ||||
|          ) + | ||||
|          "\n\n" + | ||||
|          this.getTemplate("ts_service_server.ts") | ||||
|       ); | ||||
|  | ||||
|       this.generateImports(a, def); | ||||
|  | ||||
|       a(0, `export type {`); | ||||
|       def.depends.forEach((dep) => { | ||||
|          a(1, `${dep},`); | ||||
|       }); | ||||
|       a(0, `}`); | ||||
|  | ||||
|       a( | ||||
|          0, | ||||
|          this.generateImport( | ||||
|             "{ Service }", | ||||
|             "./service_server" | ||||
|          ) | ||||
|       ); | ||||
|  | ||||
|  | ||||
|       a(0, `export abstract class ${def.name}<T> extends Service<T> {`); | ||||
|       a(1, `public name = "${def.name}";`); | ||||
|       a(1, `constructor(){`); | ||||
|       a(2, `super();`); | ||||
|       for(const fnc of def.functions) { | ||||
|          a(2, `this.functions.add("${fnc.name}")`); | ||||
|       } | ||||
|       a(1, `}`) | ||||
|       a(0, ``); | ||||
|        | ||||
|       for(const fnc of def.functions) { | ||||
|          const params = [...fnc.inputs.map(e=>`${e.name}: ${toJSType(e.type)}`), `ctx: T`].join(", "); | ||||
|          const retVal = fnc.return ? `Promise<${toJSType(fnc.return)}>` : `void`; | ||||
|          a(1, `abstract ${fnc.name}(${params}): ${retVal};`) | ||||
|  | ||||
|          // a(0, ``); | ||||
|  | ||||
|          a(1, `_${fnc.name}(params: any[] | any, ctx: T): ${retVal} {`); | ||||
|          a(2, `let p: any[] = [];`); | ||||
|          a(2, `if(Array.isArray(params)){`); | ||||
|          //TODO: Verify params! | ||||
|          a(3, `p = params;`); | ||||
|          a(2, `} else {`); | ||||
|          for(const param of fnc.inputs) { | ||||
|             a(3, `p.push(params["${param.name}"])`); | ||||
|          } | ||||
|          a(2, `}`); | ||||
|          a(2, `p.push(ctx);`) //TODO: Either this or [...p, ctx] but idk | ||||
|          a(2, `return this.${fnc.name}.call(this, ...p);`); | ||||
|          a(1, `}`) | ||||
|          a(0, ``); | ||||
|       } | ||||
|  | ||||
|       a(0, `}`) | ||||
|  | ||||
|       this.writeFormattedFile( | ||||
|          this.getFileName(def.name + "_server"), | ||||
|          lines.join("\n") | ||||
|       ); | ||||
|    } | ||||
|  | ||||
|    generateService(def: ServiceDefinition) { | ||||
|       this.writeFile("service_base.ts", this.getTemplate("ts_service_base.ts")); | ||||
|       this.generateServiceClient(def); | ||||
|       this.generateServiceServer(def); | ||||
|    } | ||||
|  | ||||
|    finalize(steps: Step[]) { | ||||
|       let linesClient: string[] = []; | ||||
|       let linesServer: string[] = []; | ||||
|  | ||||
|       const ac: lineAppender = (i, t) => { | ||||
|          if (!Array.isArray(t)) { | ||||
|             t = [t]; | ||||
|          } | ||||
|          t.forEach((l) => linesClient.push("   ".repeat(i) + l.trim())); | ||||
|       }; | ||||
|  | ||||
|       const as: lineAppender = (i, t) => { | ||||
|          if (!Array.isArray(t)) { | ||||
|             t = [t]; | ||||
|          } | ||||
|          t.forEach((l) => linesServer.push("   ".repeat(i) + l.trim())); | ||||
|       }; | ||||
|  | ||||
|       let lines:string[] = []; | ||||
|       const a: lineAppender = (i, t) => { | ||||
|          if (!Array.isArray(t)) { | ||||
|             t = [t]; | ||||
|          } | ||||
|          t.forEach((l) => lines.push("   ".repeat(i) + l.trim())); | ||||
|       }; | ||||
|  | ||||
|  | ||||
|       let hasService = false; | ||||
|       steps.forEach(([type, def]) => { | ||||
|          switch(type) { | ||||
|             case "type": | ||||
|                a(0, this.generateImport(`${def.name}, { verify_${def.name} }`, "./" + def.name)) | ||||
|                 | ||||
|                a(0, `export { verify_${def.name} }`) | ||||
|                a(0, `export type { ${def.name} }`); | ||||
|                a(0,``); | ||||
|                break; | ||||
|             case "enum": | ||||
|                a(0, this.generateImport(`${def.name}, { verify_${def.name} }`, "./" + def.name)) | ||||
|                a(0, `export { ${def.name}, verify_${def.name} }`) | ||||
|                a(0,``); | ||||
|                break; | ||||
|  | ||||
|             case "service": | ||||
|                let ext = this.flavour == "esm" ? ".ts" : ""; | ||||
|                if(!hasService) { | ||||
|                   hasService = true; | ||||
|                   ac(0, `export * from "./service_client${ext}"`); | ||||
|                   ac(0,``); | ||||
|  | ||||
|                   as(0, `export * from "./service_server${ext}"`); | ||||
|                   as(0,``); | ||||
|  | ||||
|                   a(0, `export * as Client from "./index_client${ext}"`); | ||||
|                   a(0, `export * as Server from "./index_server${ext}"`); | ||||
|                   a(0, `export { Logging } from "./service_base${ext}"`); | ||||
|                   a(0,``) | ||||
|                   //TODO: Export service globals | ||||
|                } | ||||
|  | ||||
|                ac(0, `export { ${def.name} } from "./${def.name}_client${ext}"`); | ||||
|                as(0, `export { ${def.name} } from "./${def.name}_server${ext}"`); | ||||
|                ac(0,``); | ||||
|                as(0,``); | ||||
|                break; | ||||
|          } | ||||
|       }) | ||||
|  | ||||
|       this.writeFormattedFile( | ||||
|          this.getFileName("index"), | ||||
|          lines.join("\n") | ||||
|       ); | ||||
|  | ||||
|       this.writeFormattedFile( | ||||
|          this.getFileName("index_client"), | ||||
|          linesClient.join("\n") | ||||
|       ); | ||||
|  | ||||
|       this.writeFormattedFile( | ||||
|          this.getFileName("index_server"), | ||||
|          linesServer.join("\n") | ||||
|       ); | ||||
|    } | ||||
| } | ||||
|  | ||||
| export class ESMTypescriptTarget extends TypescriptTarget { | ||||
|    name = "ts-esm"; | ||||
|    flavour: "esm" = "esm"; | ||||
| } | ||||
|  | ||||
| export class NodeJSTypescriptTarget extends TypescriptTarget { | ||||
|    name = "ts-node"; | ||||
|    flavour: "node" = "node"; | ||||
| } | ||||
							
								
								
									
										93
									
								
								src/tokenizer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/tokenizer.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| export type TokenTypes = | ||||
|    | "space" | ||||
|    | "comment" | ||||
|    | "string" | ||||
|    | "keyword" | ||||
|    | "colon" | ||||
|    | "semicolon" | ||||
|    | "comma" | ||||
|    | "equals" | ||||
|    | "curly_open" | ||||
|    | "curly_close" | ||||
|    | "bracket_open" | ||||
|    | "bracket_close" | ||||
|    | "array" | ||||
|    | "questionmark" | ||||
|    | "number" | ||||
|    | "text"; | ||||
|  | ||||
| export type Token = { | ||||
|    type: TokenTypes; | ||||
|    value: string; | ||||
|    startIdx: number; | ||||
|    endIdx: number; | ||||
| }; | ||||
|  | ||||
| type Matcher = (input: string, index: number) => undefined | Token; | ||||
|  | ||||
| export class TokenizerError extends Error { | ||||
|    index: number; | ||||
|    constructor(message: string, index: number) { | ||||
|       super(message); | ||||
|       this.index = index; | ||||
|    } | ||||
| } | ||||
|  | ||||
| function regexMatcher(regex: string | RegExp, type: TokenTypes): Matcher { | ||||
|    if (typeof regex === "string") regex = new RegExp(regex); | ||||
|  | ||||
|    return (input: string, index: number) => { | ||||
|       let matches = input.substring(index).match(regex as RegExp); | ||||
|       if (!matches || matches.length <= 0) return undefined; | ||||
|  | ||||
|       return { | ||||
|          type, | ||||
|          value: matches[0], | ||||
|          startIdx: index, | ||||
|          endIdx: index + matches[0].length, | ||||
|       } as Token; | ||||
|    }; | ||||
| } | ||||
|  | ||||
| const matcher = [ | ||||
|    regexMatcher(/^\s+/, "space"), | ||||
|    regexMatcher(/^(\/\*)(.|\s)*?(\*\/)/g, "comment"), | ||||
|    regexMatcher(/^\/\/.+/, "comment"), | ||||
|    regexMatcher(/^#.+/, "comment"), | ||||
|    regexMatcher(/^".*?"/, "string"), | ||||
|    // regexMatcher(/(?<=^")(.*?)(?=")/, "string"), | ||||
|    regexMatcher(/^(type|enum|import|service)\b/, "keyword"), | ||||
|    regexMatcher(/^\:/, "colon"), | ||||
|    regexMatcher(/^\;/, "semicolon"), | ||||
|    regexMatcher(/^\,/, "comma"), | ||||
|    regexMatcher(/^\=/, "equals"), | ||||
|    regexMatcher(/^{/, "curly_open"), | ||||
|    regexMatcher(/^}/, "curly_close"), | ||||
|    regexMatcher(/^\(/, "bracket_open"), | ||||
|    regexMatcher(/^\)/, "bracket_close"), | ||||
|    regexMatcher(/^\[\]/, "array"), | ||||
|    regexMatcher(/^\?/, "questionmark"), | ||||
|    regexMatcher(/^[\.0-9]+/, "number"), | ||||
|    regexMatcher(/^[a-zA-Z_]([a-zA-Z0-9_]?)+/, "text"), | ||||
| ]; | ||||
|  | ||||
| export default function tokenize(input: string) { | ||||
|    let index = 0; | ||||
|    let tokens: Token[] = []; | ||||
|    while (index < input.length) { | ||||
|       const matches = matcher.map((m) => m(input, index)).filter((e) => !!e); | ||||
|       let match = matches[0]; | ||||
|       if (match) { | ||||
|          if (match.type !== "space" && match.type !== "comment") { | ||||
|             tokens.push(match); | ||||
|          } | ||||
|          index += match.value.length; | ||||
|       } else { | ||||
|          throw new TokenizerError( | ||||
|             `Unexpected token '${input.substring(index, index + 1)}'`, | ||||
|             index | ||||
|          ); | ||||
|       } | ||||
|    } | ||||
|    return tokens; | ||||
| } | ||||
							
								
								
									
										30
									
								
								templates/ts_service_base.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								templates/ts_service_base.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| export const Logging  = { | ||||
|    verbose: false, | ||||
|    log(...args: any[]) { | ||||
|       if(Logging.verbose) { | ||||
|          console.log(...args) | ||||
|       } | ||||
|    } | ||||
| } | ||||
|  | ||||
| export enum ErrorCodes { | ||||
|    ParseError=-32700, | ||||
|    InvalidRequest=-32700, | ||||
|    MethodNotFound=-32700, | ||||
|    InvalidParams=-32700, | ||||
|    InternalError=-32700, | ||||
| } | ||||
|  | ||||
| export interface RequestObject { | ||||
|    jsonrpc: "2.0"; | ||||
|    method: string; | ||||
|    params?: any[] | { [key: string]: any }; | ||||
|    id?: string | null; | ||||
| } | ||||
|  | ||||
| export interface ResponseObject { | ||||
|    jsonrpc: "2.0"; | ||||
|    result?: any; | ||||
|    error?: { code: ErrorCodes, message:string, data?: any} | ||||
|    id: string; | ||||
| } | ||||
							
								
								
									
										93
									
								
								templates/ts_service_client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								templates/ts_service_client.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| //@template-ignore | ||||
| import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base"; | ||||
|  | ||||
|  | ||||
| export type IMessageCallback = (data: any) => void; | ||||
|  | ||||
| export type ResponseListener = { | ||||
|    ok: (response:any)=>void; | ||||
|    err: (error: Error)=>void; | ||||
| } | ||||
|  | ||||
| export class Service { | ||||
|    public _name: string = null as any; | ||||
|  | ||||
|    constructor(protected _provider: ServiceProvider, name: string) { | ||||
|       this._name = name; | ||||
|       this._provider.services.set(name, this); | ||||
|    } | ||||
| } | ||||
|  | ||||
| export class ServiceProvider { | ||||
|    services = new Map<string, Service>(); | ||||
|    requests = new Map<string, ResponseListener>(); | ||||
|  | ||||
|    constructor(private sendPacket: IMessageCallback) {} | ||||
|  | ||||
|    onPacket(msg: RequestObject | ResponseObject) { | ||||
|       Logging.log("CLIENT: Received message:", msg); | ||||
|       if("method" in msg) { | ||||
|          if(msg.id){  | ||||
|             Logging.log("CLIENT: Determined type is Request"); | ||||
|             // Request, which are not supported by client, so ignore | ||||
|             return; | ||||
|          } else { | ||||
|             Logging.log("CLIENT: Determined type is Notification"); | ||||
|             //Notification. Send to Notification handler | ||||
|             //TODO: Implement | ||||
|          } | ||||
|       } else { | ||||
|          Logging.log("CLIENT: Determined type is Response"); | ||||
|          // Response | ||||
|          let resListener = this.requests.get(msg.id); | ||||
|          if(!resListener) return; // Ignore wrong responses | ||||
|          if(msg.error) { | ||||
|             resListener.err(new Error(msg.error.message)); | ||||
|          } else { | ||||
|             resListener.ok(msg.result); | ||||
|          } | ||||
|       } | ||||
|           | ||||
|    } | ||||
|  | ||||
|    sendMessage(msg: RequestObject, res?: ResponseListener) { | ||||
|       Logging.log("CLIENT: Sending Messgage", msg); | ||||
|       if(msg.id) { | ||||
|          this.requests.set(msg.id, res); | ||||
|       } | ||||
|       this.sendPacket(msg) | ||||
|    } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| declare var require: any; | ||||
| export const getRandomBytes = ( | ||||
|    typeof self !== "undefined" && (self.crypto || (self as any).msCrypto) | ||||
|       ? function () { | ||||
|            // Browsers | ||||
|            var crypto = self.crypto || (self as any).msCrypto; | ||||
|            var QUOTA = 65536; | ||||
|            return function (n: number) { | ||||
|               var a = new Uint8Array(n); | ||||
|               for (var i = 0; i < n; i += QUOTA) { | ||||
|                  crypto.getRandomValues( | ||||
|                     a.subarray(i, i + Math.min(n - i, QUOTA)) | ||||
|                  ); | ||||
|               } | ||||
|               return a; | ||||
|            }; | ||||
|         } | ||||
|       : function () { | ||||
|            // Node | ||||
|            return require("crypto").randomBytes; | ||||
|         } | ||||
| )() as (cnt: number) => Uint8Array; | ||||
|  | ||||
| export const getRandomID = (length: number) => { | ||||
|    return btoa(String.fromCharCode.apply(null, getRandomBytes(length) as any)); | ||||
| }; | ||||
							
								
								
									
										120
									
								
								templates/ts_service_server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								templates/ts_service_server.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | ||||
| //@template-ignore | ||||
| import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base"; | ||||
|  | ||||
|  | ||||
| export class Service<T> { | ||||
|    public name: string = null as any; | ||||
|    public functions = new Set<string>(); | ||||
|  | ||||
|    constructor() {} | ||||
| } | ||||
|  | ||||
| type ISendMessageCB = (data: any, catchedErr?: Error) => void; | ||||
|  | ||||
| export class ServiceProvider<T = any> { | ||||
|    services = new Map<string, Service<T>>(); | ||||
|    addService(service: Service<T>) { | ||||
|       this.services.set(service.name, service); | ||||
|       Logging.log("SERVER: Adding Service to provider:", service.name); | ||||
|       Logging.log("SERVER: Service provides:", [...service.functions.keys()]) | ||||
|    } | ||||
|  | ||||
|    getSession(send: ISendMessageCB, ctx?: Partial<T>): Session<T> { | ||||
|       return new Session(this, send, ctx); | ||||
|    } | ||||
| } | ||||
|  | ||||
| class Session<T> { | ||||
|    ctx: Partial<T>; | ||||
|  | ||||
|    constructor( | ||||
|       private provider: ServiceProvider, | ||||
|       private _send: ISendMessageCB, | ||||
|       ctx?: Partial<T> | ||||
|    ) { | ||||
|       this.ctx = ctx || {}; | ||||
|    } | ||||
|  | ||||
|    send(data: any, catchedErr?:Error) { | ||||
|       Logging.log("SERVER: Sending Message", data) | ||||
|       this._send(data, catchedErr); | ||||
|    } | ||||
|  | ||||
|    async onMessage(data: RequestObject) { | ||||
|       Logging.log("SERVER: Received Message", data); | ||||
|       try { | ||||
|          if (!data.method) { | ||||
|             if (data.id) { | ||||
|                this.send({ | ||||
|                   jsonrpc: "2.0", | ||||
|                   id: data.id, | ||||
|                   error: { | ||||
|                      code: ErrorCodes.InvalidRequest, | ||||
|                      message: "No method defined!", | ||||
|                   }, | ||||
|                } as ResponseObject); | ||||
|             } | ||||
|             return; | ||||
|          } | ||||
|  | ||||
|          const [srvName, fncName] = data.method.split("."); | ||||
|          Logging.log("SERVER: Message for", srvName, fncName); | ||||
|  | ||||
|          const service = this.provider.services.get(srvName); | ||||
|          if (!service) { | ||||
|             Logging.log("SERVER: Did not find Service"); | ||||
|             if (data.id) { | ||||
|                this.send({ | ||||
|                   jsonrpc: "2.0", | ||||
|                   id: data.id, | ||||
|                   error: { | ||||
|                      code: ErrorCodes.MethodNotFound, | ||||
|                      message: "Service not found!", | ||||
|                   }, | ||||
|                } as ResponseObject); | ||||
|             } | ||||
|             return; | ||||
|          } | ||||
|  | ||||
|          const fnc = service.functions.has(fncName); | ||||
|          if (!fnc) { | ||||
|             Logging.log("SERVER: Did not find Function"); | ||||
|             if (data.id) { | ||||
|                this.send({ | ||||
|                   jsonrpc: "2.0", | ||||
|                   id: data.id, | ||||
|                   error: { | ||||
|                      code: ErrorCodes.MethodNotFound, | ||||
|                      message: "Function not found!", | ||||
|                   }, | ||||
|                } as ResponseObject); | ||||
|             } | ||||
|             return; | ||||
|          } | ||||
|  | ||||
|          let result = await (service as any)["_" + fncName](data.params, this.ctx); | ||||
|          if(data.id) { //Request | ||||
|             this.send({ | ||||
|                jsonrpc: "2.0", | ||||
|                id: data.id, | ||||
|                result: result, | ||||
|             } as ResponseObject); | ||||
|          } //else Notification and response is ignored | ||||
|       } catch (err) { | ||||
|          if (data.id) { | ||||
|             this.send( | ||||
|                { | ||||
|                   jsonrpc: "2.0", | ||||
|                   id: data.id, | ||||
|                   error: { | ||||
|                      code: ErrorCodes.InternalError, | ||||
|                      message: err.message, | ||||
|                   }, | ||||
|                } as ResponseObject, | ||||
|                err | ||||
|             ); | ||||
|          } | ||||
|          //TODO: Think about else case | ||||
|       } | ||||
|    } | ||||
| } | ||||
							
								
								
									
										14
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| { | ||||
|    "compilerOptions": { | ||||
|       "target": "ESNext", | ||||
|       "module": "CommonJS", | ||||
|       "moduleResolution": "node", | ||||
|       "outDir": "examples/out", | ||||
|       "esModuleInterop": true, | ||||
|       "forceConsistentCasingInFileNames": true, | ||||
|       "skipLibCheck": true | ||||
|    }, | ||||
|    "include": [ | ||||
|       "src/" | ||||
|    ] | ||||
| } | ||||
							
								
								
									
										404
									
								
								yarn.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								yarn.lock
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,404 @@ | ||||
| # This file is generated by running "yarn install" inside your project. | ||||
| # Manual changes might be lost - proceed with caution! | ||||
|  | ||||
| __metadata: | ||||
|   version: 5 | ||||
|   cacheKey: 8 | ||||
|  | ||||
| "@cspotcode/source-map-consumer@npm:0.8.0": | ||||
|   version: 0.8.0 | ||||
|   resolution: "@cspotcode/source-map-consumer@npm:0.8.0" | ||||
|   checksum: c0c16ca3d2f58898f1bd74c4f41a189dbcc202e642e60e489cbcc2e52419c4e89bdead02c886a12fb13ea37798ede9e562b2321df997ebc210ae9bd881561b4e | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@cspotcode/source-map-support@npm:0.7.0": | ||||
|   version: 0.7.0 | ||||
|   resolution: "@cspotcode/source-map-support@npm:0.7.0" | ||||
|   dependencies: | ||||
|     "@cspotcode/source-map-consumer": 0.8.0 | ||||
|   checksum: 9faddda7757cd778b5fd6812137b2cc265810043680d6399acc20441668fafcdc874053be9dccd0d9110087287bfad27eb3bf342f72bceca9aa9059f5d0c4be8 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@hibas123/SimpleRPC@workspace:.": | ||||
|   version: 0.0.0-use.local | ||||
|   resolution: "@hibas123/SimpleRPC@workspace:." | ||||
|   dependencies: | ||||
|     "@types/debug": ^4.1.7 | ||||
|     "@types/node": ^17.0.5 | ||||
|     "@types/yargs": ^17.0.8 | ||||
|     chalk: 4 | ||||
|     debug: ^4.3.3 | ||||
|     prettier: ^2.5.1 | ||||
|     ts-node: ^10.4.0 | ||||
|     typescript: ^4.5.4 | ||||
|     yargs: ^17.3.1 | ||||
|   languageName: unknown | ||||
|   linkType: soft | ||||
|  | ||||
| "@tsconfig/node10@npm:^1.0.7": | ||||
|   version: 1.0.8 | ||||
|   resolution: "@tsconfig/node10@npm:1.0.8" | ||||
|   checksum: b8d5fffbc6b17ef64ef74f7fdbccee02a809a063ade785c3648dae59406bc207f70ea2c4296f92749b33019fa36a5ae716e42e49cc7f1bbf0fd147be0d6b970a | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@tsconfig/node12@npm:^1.0.7": | ||||
|   version: 1.0.9 | ||||
|   resolution: "@tsconfig/node12@npm:1.0.9" | ||||
|   checksum: a01b2400ab3582b86b589c6d31dcd0c0656f333adecde85d6d7d4086adb059808b82692380bb169546d189bf771ae21d02544a75b57bd6da4a5dd95f8567bec9 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@tsconfig/node14@npm:^1.0.0": | ||||
|   version: 1.0.1 | ||||
|   resolution: "@tsconfig/node14@npm:1.0.1" | ||||
|   checksum: 976345e896c0f059867f94f8d0f6ddb8b1844fb62bf36b727de8a9a68f024857e5db97ed51d3325e23e0616a5e48c034ff51a8d595b3fe7e955f3587540489be | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@tsconfig/node16@npm:^1.0.2": | ||||
|   version: 1.0.2 | ||||
|   resolution: "@tsconfig/node16@npm:1.0.2" | ||||
|   checksum: ca94d3639714672bbfd55f03521d3f56bb6a25479bd425da81faf21f13e1e9d15f40f97377dedbbf477a5841c5b0c8f4cd1b391f33553d750b9202c54c2c07aa | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@types/debug@npm:^4.1.7": | ||||
|   version: 4.1.7 | ||||
|   resolution: "@types/debug@npm:4.1.7" | ||||
|   dependencies: | ||||
|     "@types/ms": "*" | ||||
|   checksum: 0a7b89d8ed72526858f0b61c6fd81f477853e8c4415bb97f48b1b5545248d2ae389931680b94b393b993a7cfe893537a200647d93defe6d87159b96812305adc | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@types/ms@npm:*": | ||||
|   version: 0.7.31 | ||||
|   resolution: "@types/ms@npm:0.7.31" | ||||
|   checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@types/node@npm:^17.0.5": | ||||
|   version: 17.0.5 | ||||
|   resolution: "@types/node@npm:17.0.5" | ||||
|   checksum: 105535e78722515c26cfdc1b0cbf1b19f55fe53b814e2e90d8b1e653bc63136d4760c7efc102eca111c6d124a291e37d60d761d569a3f4afb3fba05bad5d9ade | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@types/yargs-parser@npm:*": | ||||
|   version: 20.2.1 | ||||
|   resolution: "@types/yargs-parser@npm:20.2.1" | ||||
|   checksum: 1d039e64494a7a61ddd278349a3dc60b19f99ff0517425696e796f794e4252452b9d62178e69755ad03f439f9dc0c8c3d7b3a1201b3a24e134bac1a09fa11eaa | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "@types/yargs@npm:^17.0.8": | ||||
|   version: 17.0.8 | ||||
|   resolution: "@types/yargs@npm:17.0.8" | ||||
|   dependencies: | ||||
|     "@types/yargs-parser": "*" | ||||
|   checksum: 63d06700ffbed745f00d7994eb92416649c8a3ead22f26446979d383f3af52fa9400bb185268f3a44a2348749098ffe33a8185ca676b77bc3206c63b8b73fd01 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "acorn-walk@npm:^8.1.1": | ||||
|   version: 8.2.0 | ||||
|   resolution: "acorn-walk@npm:8.2.0" | ||||
|   checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "acorn@npm:^8.4.1": | ||||
|   version: 8.7.0 | ||||
|   resolution: "acorn@npm:8.7.0" | ||||
|   bin: | ||||
|     acorn: bin/acorn | ||||
|   checksum: e0f79409d68923fbf1aa6d4166f3eedc47955320d25c89a20cc822e6ba7c48c5963d5bc657bc242d68f7a4ac9faf96eef033e8f73656da6c640d4219935fdfd0 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "ansi-regex@npm:^5.0.1": | ||||
|   version: 5.0.1 | ||||
|   resolution: "ansi-regex@npm:5.0.1" | ||||
|   checksum: 2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": | ||||
|   version: 4.3.0 | ||||
|   resolution: "ansi-styles@npm:4.3.0" | ||||
|   dependencies: | ||||
|     color-convert: ^2.0.1 | ||||
|   checksum: 513b44c3b2105dd14cc42a19271e80f386466c4be574bccf60b627432f9198571ebf4ab1e4c3ba17347658f4ee1711c163d574248c0c1cdc2d5917a0ad582ec4 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "arg@npm:^4.1.0": | ||||
|   version: 4.1.3 | ||||
|   resolution: "arg@npm:4.1.3" | ||||
|   checksum: 544af8dd3f60546d3e4aff084d451b96961d2267d668670199692f8d054f0415d86fc5497d0e641e91546f0aa920e7c29e5250e99fc89f5552a34b5d93b77f43 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "chalk@npm:4": | ||||
|   version: 4.1.2 | ||||
|   resolution: "chalk@npm:4.1.2" | ||||
|   dependencies: | ||||
|     ansi-styles: ^4.1.0 | ||||
|     supports-color: ^7.1.0 | ||||
|   checksum: fe75c9d5c76a7a98d45495b91b2172fa3b7a09e0cc9370e5c8feb1c567b85c4288e2b3fded7cfdd7359ac28d6b3844feb8b82b8686842e93d23c827c417e83fc | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "cliui@npm:^7.0.2": | ||||
|   version: 7.0.4 | ||||
|   resolution: "cliui@npm:7.0.4" | ||||
|   dependencies: | ||||
|     string-width: ^4.2.0 | ||||
|     strip-ansi: ^6.0.0 | ||||
|     wrap-ansi: ^7.0.0 | ||||
|   checksum: ce2e8f578a4813806788ac399b9e866297740eecd4ad1823c27fd344d78b22c5f8597d548adbcc46f0573e43e21e751f39446c5a5e804a12aace402b7a315d7f | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "color-convert@npm:^2.0.1": | ||||
|   version: 2.0.1 | ||||
|   resolution: "color-convert@npm:2.0.1" | ||||
|   dependencies: | ||||
|     color-name: ~1.1.4 | ||||
|   checksum: 79e6bdb9fd479a205c71d89574fccfb22bd9053bd98c6c4d870d65c132e5e904e6034978e55b43d69fcaa7433af2016ee203ce76eeba9cfa554b373e7f7db336 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "color-name@npm:~1.1.4": | ||||
|   version: 1.1.4 | ||||
|   resolution: "color-name@npm:1.1.4" | ||||
|   checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "create-require@npm:^1.1.0": | ||||
|   version: 1.1.1 | ||||
|   resolution: "create-require@npm:1.1.1" | ||||
|   checksum: a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "debug@npm:^4.3.3": | ||||
|   version: 4.3.3 | ||||
|   resolution: "debug@npm:4.3.3" | ||||
|   dependencies: | ||||
|     ms: 2.1.2 | ||||
|   peerDependenciesMeta: | ||||
|     supports-color: | ||||
|       optional: true | ||||
|   checksum: 14472d56fe4a94dbcfaa6dbed2dd3849f1d72ba78104a1a328047bb564643ca49df0224c3a17fa63533fd11dd3d4c8636cd861191232a2c6735af00cc2d4de16 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "diff@npm:^4.0.1": | ||||
|   version: 4.0.2 | ||||
|   resolution: "diff@npm:4.0.2" | ||||
|   checksum: f2c09b0ce4e6b301c221addd83bf3f454c0bc00caa3dd837cf6c127d6edf7223aa2bbe3b688feea110b7f262adbfc845b757c44c8a9f8c0c5b15d8fa9ce9d20d | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "emoji-regex@npm:^8.0.0": | ||||
|   version: 8.0.0 | ||||
|   resolution: "emoji-regex@npm:8.0.0" | ||||
|   checksum: d4c5c39d5a9868b5fa152f00cada8a936868fd3367f33f71be515ecee4c803132d11b31a6222b2571b1e5f7e13890156a94880345594d0ce7e3c9895f560f192 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "escalade@npm:^3.1.1": | ||||
|   version: 3.1.1 | ||||
|   resolution: "escalade@npm:3.1.1" | ||||
|   checksum: a3e2a99f07acb74b3ad4989c48ca0c3140f69f923e56d0cba0526240ee470b91010f9d39001f2a4a313841d237ede70a729e92125191ba5d21e74b106800b133 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "get-caller-file@npm:^2.0.5": | ||||
|   version: 2.0.5 | ||||
|   resolution: "get-caller-file@npm:2.0.5" | ||||
|   checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "has-flag@npm:^4.0.0": | ||||
|   version: 4.0.0 | ||||
|   resolution: "has-flag@npm:4.0.0" | ||||
|   checksum: 261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "is-fullwidth-code-point@npm:^3.0.0": | ||||
|   version: 3.0.0 | ||||
|   resolution: "is-fullwidth-code-point@npm:3.0.0" | ||||
|   checksum: 44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "make-error@npm:^1.1.1": | ||||
|   version: 1.3.6 | ||||
|   resolution: "make-error@npm:1.3.6" | ||||
|   checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "ms@npm:2.1.2": | ||||
|   version: 2.1.2 | ||||
|   resolution: "ms@npm:2.1.2" | ||||
|   checksum: 673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "prettier@npm:^2.5.1": | ||||
|   version: 2.5.1 | ||||
|   resolution: "prettier@npm:2.5.1" | ||||
|   bin: | ||||
|     prettier: bin-prettier.js | ||||
|   checksum: 21b9408476ea1c544b0e45d51ceb94a84789ff92095abb710942d780c862d0daebdb29972d47f6b4d0f7ebbfb0ffbf56cc2cfa3e3e9d1cca54864af185b15b66 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "require-directory@npm:^2.1.1": | ||||
|   version: 2.1.1 | ||||
|   resolution: "require-directory@npm:2.1.1" | ||||
|   checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": | ||||
|   version: 4.2.3 | ||||
|   resolution: "string-width@npm:4.2.3" | ||||
|   dependencies: | ||||
|     emoji-regex: ^8.0.0 | ||||
|     is-fullwidth-code-point: ^3.0.0 | ||||
|     strip-ansi: ^6.0.1 | ||||
|   checksum: e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": | ||||
|   version: 6.0.1 | ||||
|   resolution: "strip-ansi@npm:6.0.1" | ||||
|   dependencies: | ||||
|     ansi-regex: ^5.0.1 | ||||
|   checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "supports-color@npm:^7.1.0": | ||||
|   version: 7.2.0 | ||||
|   resolution: "supports-color@npm:7.2.0" | ||||
|   dependencies: | ||||
|     has-flag: ^4.0.0 | ||||
|   checksum: 3dda818de06ebbe5b9653e07842d9479f3555ebc77e9a0280caf5a14fb877ffee9ed57007c3b78f5a6324b8dbeec648d9e97a24e2ed9fdb81ddc69ea07100f4a | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "ts-node@npm:^10.4.0": | ||||
|   version: 10.4.0 | ||||
|   resolution: "ts-node@npm:10.4.0" | ||||
|   dependencies: | ||||
|     "@cspotcode/source-map-support": 0.7.0 | ||||
|     "@tsconfig/node10": ^1.0.7 | ||||
|     "@tsconfig/node12": ^1.0.7 | ||||
|     "@tsconfig/node14": ^1.0.0 | ||||
|     "@tsconfig/node16": ^1.0.2 | ||||
|     acorn: ^8.4.1 | ||||
|     acorn-walk: ^8.1.1 | ||||
|     arg: ^4.1.0 | ||||
|     create-require: ^1.1.0 | ||||
|     diff: ^4.0.1 | ||||
|     make-error: ^1.1.1 | ||||
|     yn: 3.1.1 | ||||
|   peerDependencies: | ||||
|     "@swc/core": ">=1.2.50" | ||||
|     "@swc/wasm": ">=1.2.50" | ||||
|     "@types/node": "*" | ||||
|     typescript: ">=2.7" | ||||
|   peerDependenciesMeta: | ||||
|     "@swc/core": | ||||
|       optional: true | ||||
|     "@swc/wasm": | ||||
|       optional: true | ||||
|   bin: | ||||
|     ts-node: dist/bin.js | ||||
|     ts-node-cwd: dist/bin-cwd.js | ||||
|     ts-node-script: dist/bin-script.js | ||||
|     ts-node-transpile-only: dist/bin-transpile.js | ||||
|     ts-script: dist/bin-script-deprecated.js | ||||
|   checksum: 3933ac0a937d33c45e04a6750fcdd3e765eb2897d1da1307cd97ac52af093bcfb632ec0453a75000a65c8b5b7bdb32b1077050a186dcc556e62657cb592e6d49 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "typescript@npm:^4.5.4": | ||||
|   version: 4.5.4 | ||||
|   resolution: "typescript@npm:4.5.4" | ||||
|   bin: | ||||
|     tsc: bin/tsc | ||||
|     tsserver: bin/tsserver | ||||
|   checksum: 59f3243f9cd6fe3161e6150ff6bf795fc843b4234a655dbd938a310515e0d61afd1ac942799e7415e4334255e41c2c49b7dd5d9fd38a17acd25a6779ca7e0961 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "typescript@patch:typescript@^4.5.4#~builtin<compat/typescript>": | ||||
|   version: 4.5.4 | ||||
|   resolution: "typescript@patch:typescript@npm%3A4.5.4#~builtin<compat/typescript>::version=4.5.4&hash=493e53" | ||||
|   bin: | ||||
|     tsc: bin/tsc | ||||
|     tsserver: bin/tsserver | ||||
|   checksum: 2e488dde7d2c4a2fa2e79cf2470600f8ce81bc0563c276b72df8ff412d74456ae532ba824650ae936ce207440c79720ddcfaa25e3cb4477572b8534fa4e34d49 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "wrap-ansi@npm:^7.0.0": | ||||
|   version: 7.0.0 | ||||
|   resolution: "wrap-ansi@npm:7.0.0" | ||||
|   dependencies: | ||||
|     ansi-styles: ^4.0.0 | ||||
|     string-width: ^4.1.0 | ||||
|     strip-ansi: ^6.0.0 | ||||
|   checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "y18n@npm:^5.0.5": | ||||
|   version: 5.0.8 | ||||
|   resolution: "y18n@npm:5.0.8" | ||||
|   checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "yargs-parser@npm:^21.0.0": | ||||
|   version: 21.0.0 | ||||
|   resolution: "yargs-parser@npm:21.0.0" | ||||
|   checksum: 1e205fca1cb7a36a1585e2b94a64e641c12741b53627d338e12747f4dca3c3610cdd9bb235040621120548dd74c3ef03a8168d52a1eabfedccbe4a62462b6731 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "yargs@npm:^17.3.1": | ||||
|   version: 17.3.1 | ||||
|   resolution: "yargs@npm:17.3.1" | ||||
|   dependencies: | ||||
|     cliui: ^7.0.2 | ||||
|     escalade: ^3.1.1 | ||||
|     get-caller-file: ^2.0.5 | ||||
|     require-directory: ^2.1.1 | ||||
|     string-width: ^4.2.3 | ||||
|     y18n: ^5.0.5 | ||||
|     yargs-parser: ^21.0.0 | ||||
|   checksum: 64fc2e32c56739f1d14d2d24acd17a6944c3c8e3e3558f09fc1953ac112e868cc16013d282886b9d5be22187f8b9ed4f60741a6b1011f595ce2718805a656852 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
|  | ||||
| "yn@npm:3.1.1": | ||||
|   version: 3.1.1 | ||||
|   resolution: "yn@npm:3.1.1" | ||||
|   checksum: 2c487b0e149e746ef48cda9f8bad10fc83693cd69d7f9dcd8be4214e985de33a29c9e24f3c0d6bcf2288427040a8947406ab27f7af67ee9456e6b84854f02dd6 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
		Reference in New Issue
	
	Block a user
	 K35
					K35