First Commit
Yes, that is what I do at 31.12.2021 at 22:39 local time...
This commit is contained in:
commit
d5cfdcadc1
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
|
Loading…
Reference in New Issue
Block a user