Adding Decorators for comments
This commit is contained in:
parent
579055d8fb
commit
4e389dc472
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ node_modules/
|
|||||||
.yarn/cache
|
.yarn/cache
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
examples/out
|
examples/out
|
||||||
|
examples/definition.json
|
||||||
|
@ -26,9 +26,20 @@ type AddValueResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
service TestService {
|
service TestService {
|
||||||
|
@Description("Add two numbers")
|
||||||
|
@Param("request", "Parameter containing the two numbers")
|
||||||
AddValuesSingleParam(request: AddValueRequest): AddValueResponse;
|
AddValuesSingleParam(request: AddValueRequest): AddValueResponse;
|
||||||
|
|
||||||
|
@Description("Add two numbers")
|
||||||
|
@Param("value1", "The first value")
|
||||||
|
@Param("value2", "The second value")
|
||||||
AddValuesMultipleParams(value1: number, value2: number): number;
|
AddValuesMultipleParams(value1: number, value2: number): number;
|
||||||
|
|
||||||
|
@Description("Does literaly nothing")
|
||||||
|
@Param("param1", "Some number")
|
||||||
ReturningVoid(param1: number): void;
|
ReturningVoid(param1: number): void;
|
||||||
|
|
||||||
|
@Description("Just sends an Event with a String")
|
||||||
|
@Param("param1", "Parameter with some string for event")
|
||||||
notification OnEvent(param1: string);
|
notification OnEvent(param1: string);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@hibas123/jrpcgen",
|
"name": "@hibas123/jrpcgen",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"packageManager": "yarn@3.1.1",
|
"packageManager": "yarn@3.1.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node src/index.ts",
|
"start": "ts-node src/index.ts",
|
||||||
"test": "npm run start -- compile examples/example.jrpc -o=ts-node:examples/out && ts-node examples/test.ts",
|
"test": "npm run start -- compile examples/example.jrpc --definition=examples/definition.json -o=ts-node:examples/out && ts-node examples/test.ts",
|
||||||
"build": "esbuild src/index.ts --bundle --platform=node --target=node14 --outfile=lib/jrpc.js",
|
"build": "esbuild src/index.ts --bundle --platform=node --target=node14 --outfile=lib/jrpc.js",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
|
53
src/ir.ts
53
src/ir.ts
@ -33,10 +33,20 @@ export interface EnumDefinition {
|
|||||||
values: EnumValueDefinition[];
|
values: EnumValueDefinition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ServiceFunctionDecorators = {
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
name:string;
|
||||||
|
description: string;
|
||||||
|
}[];
|
||||||
|
returns: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServiceFunctionParamsDefinition {
|
export interface ServiceFunctionParamsDefinition {
|
||||||
name: string;
|
name: string;
|
||||||
inputs: { type: string; name: string }[];
|
inputs: { type: string; name: string }[];
|
||||||
return: string | undefined;
|
return: string | undefined;
|
||||||
|
decorators: ServiceFunctionDecorators
|
||||||
}
|
}
|
||||||
export type ServiceFunctionDefinition = ServiceFunctionParamsDefinition;
|
export type ServiceFunctionDefinition = ServiceFunctionParamsDefinition;
|
||||||
|
|
||||||
@ -208,10 +218,53 @@ export default function get_ir(parsed: Parsed): IR {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let decorators = {} as ServiceFunctionDecorators;
|
||||||
|
|
||||||
|
fnc.decorators.forEach((values, key)=>{
|
||||||
|
for(const val of values) {
|
||||||
|
switch(key) {
|
||||||
|
case "Description":
|
||||||
|
if(decorators.description)
|
||||||
|
throw new IRError(fnc, `Decorator 'Description' can only be used once!`);
|
||||||
|
if(val.length != 1)
|
||||||
|
throw new IRError(fnc, `Decorator 'Description' requires exactly one parameter!`);
|
||||||
|
decorators.description = val[0];
|
||||||
|
break;
|
||||||
|
case "Returns":
|
||||||
|
if(decorators.returns)
|
||||||
|
throw new IRError(fnc, `Decorator 'Returns' can only be used once!`);
|
||||||
|
if(val.length != 1)
|
||||||
|
throw new IRError(fnc, `Decorator 'Returns' requires exactly one parameter!`);
|
||||||
|
decorators.returns = val[0];
|
||||||
|
break;
|
||||||
|
case "Param":
|
||||||
|
if(!decorators.parameters)
|
||||||
|
decorators.parameters = [];
|
||||||
|
if(val.length != 2)
|
||||||
|
throw new IRError(fnc, `Decorator 'Param' requires exactly two parameters!`);
|
||||||
|
const [name, description] = val;
|
||||||
|
if(!fnc.inputs.find(e=>e.name == name))
|
||||||
|
throw new IRError(fnc, `Decorator 'Param' requires the first param to equal the name of a function parameter!`);
|
||||||
|
if(decorators.parameters.find(e=>e.name == name))
|
||||||
|
throw new IRError(fnc, `Decorator 'Param' has already been set for the parameter ${name}!`);
|
||||||
|
|
||||||
|
decorators.parameters.push({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IRError(fnc, `Decorator ${key} is not a valid decorator!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: fnc.name,
|
name: fnc.name,
|
||||||
inputs: fnc.inputs,
|
inputs: fnc.inputs,
|
||||||
return: fnc.return_type,
|
return: fnc.return_type,
|
||||||
|
decorators
|
||||||
} as ServiceFunctionDefinition;
|
} as ServiceFunctionDefinition;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,11 +44,15 @@ export interface IServiceFunctionInput {
|
|||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type Decorators = Map<string, string[][]>;
|
||||||
|
|
||||||
export interface ServiceFunctionStatement extends DefinitionNode {
|
export interface ServiceFunctionStatement extends DefinitionNode {
|
||||||
type: "service_function";
|
type: "service_function";
|
||||||
inputs: IServiceFunctionInput[];
|
inputs: IServiceFunctionInput[];
|
||||||
name: string;
|
name: string;
|
||||||
return_type: string | undefined; // Makes it a notification
|
return_type: string | undefined; // Makes it a notification
|
||||||
|
decorators: Decorators;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceStatement extends DefinitionNode {
|
export interface ServiceStatement extends DefinitionNode {
|
||||||
@ -116,6 +120,8 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
|||||||
return val;
|
return val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const checkTypes = (...types: string[]) => {
|
const checkTypes = (...types: string[]) => {
|
||||||
if (types.indexOf(currentToken.type) < 0) {
|
if (types.indexOf(currentToken.type) < 0) {
|
||||||
throw new ParserError(
|
throw new ParserError(
|
||||||
@ -277,8 +283,44 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseFunctionDecorator = (decorators: Decorators = new Map) => {
|
||||||
|
const idx = eatToken("@");
|
||||||
|
const [decorator] = eatText();
|
||||||
|
eatToken("(");
|
||||||
|
let args: string[] = [];
|
||||||
|
let first = true;
|
||||||
|
while(currentToken.value !== ")") {
|
||||||
|
if(first) {
|
||||||
|
first= false;
|
||||||
|
} else {
|
||||||
|
eatToken(",");
|
||||||
|
}
|
||||||
|
checkTypes("string");
|
||||||
|
// if(currentToken.type == "number") {
|
||||||
|
// let arg = Number(currentToken.value);
|
||||||
|
// if (Number.isNaN(arg)) {
|
||||||
|
// throw new ParserError(
|
||||||
|
// `Value cannot be parsed as number! ${currentToken.value}`,
|
||||||
|
// currentToken
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
args.push(currentToken.value.slice(1, -1));
|
||||||
|
eatToken();
|
||||||
|
|
||||||
|
}
|
||||||
|
eatToken(")");
|
||||||
|
|
||||||
|
let dec = decorators.get(decorator) || [];
|
||||||
|
dec.push(args);
|
||||||
|
decorators.set(decorator, dec);
|
||||||
|
|
||||||
|
return decorators;
|
||||||
|
}
|
||||||
|
|
||||||
const parseServiceFunction = (
|
const parseServiceFunction = (
|
||||||
notification?: boolean
|
decorators: Decorators,
|
||||||
|
notification?: boolean,
|
||||||
): ServiceFunctionStatement => {
|
): ServiceFunctionStatement => {
|
||||||
const [name, idx] = eatText();
|
const [name, idx] = eatText();
|
||||||
|
|
||||||
@ -318,6 +360,7 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
|||||||
},
|
},
|
||||||
inputs,
|
inputs,
|
||||||
return_type,
|
return_type,
|
||||||
|
decorators
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -327,13 +370,19 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
|||||||
eatToken("{");
|
eatToken("{");
|
||||||
let functions: ServiceFunctionStatement[] = [];
|
let functions: ServiceFunctionStatement[] = [];
|
||||||
|
|
||||||
while (currentToken.type === "text") {
|
while (currentToken.type !== "curly_close") {
|
||||||
|
let decorators:Decorators = new Map;
|
||||||
|
while(currentToken.type == "at") {
|
||||||
|
parseFunctionDecorator(decorators);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let notification = false;
|
let notification = false;
|
||||||
if (currentToken.value == "notification") {
|
if (currentToken.value == "notification") {
|
||||||
eatText();
|
eatText();
|
||||||
notification = true;
|
notification = true;
|
||||||
}
|
}
|
||||||
functions.push(parseServiceFunction(notification));
|
functions.push(parseServiceFunction(decorators, notification));
|
||||||
}
|
}
|
||||||
eatToken("}");
|
eatToken("}");
|
||||||
|
|
||||||
|
@ -4,10 +4,12 @@ import Color from "chalk";
|
|||||||
import * as Path from "path";
|
import * as Path from "path";
|
||||||
import tokenize, { TokenizerError } from "./tokenizer";
|
import tokenize, { TokenizerError } from "./tokenizer";
|
||||||
import parse, { Parsed, ParserError } from "./parser";
|
import parse, { Parsed, ParserError } from "./parser";
|
||||||
import get_ir, { IR } from "./ir";
|
import get_ir, { IR, IRError } from "./ir";
|
||||||
import compile, { CompileTarget } from "./compile";
|
import compile, { CompileTarget } from "./compile";
|
||||||
import { ESMTypescriptTarget, NodeJSTypescriptTarget } from "./targets/typescript";
|
import { ESMTypescriptTarget, NodeJSTypescriptTarget } from "./targets/typescript";
|
||||||
|
|
||||||
|
class CatchedError extends Error {}
|
||||||
|
|
||||||
const log = dbg("app");
|
const log = dbg("app");
|
||||||
|
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ type ProcessContext = {
|
|||||||
processedFiles: Set<string>;
|
processedFiles: Set<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function processFile(ctx: ProcessContext, file: string): Parsed | null {
|
function processFile(ctx: ProcessContext, file: string, root = false): Parsed | null {
|
||||||
file = Path.resolve(file);
|
file = Path.resolve(file);
|
||||||
if (ctx.processedFiles.has(file)) {
|
if (ctx.processedFiles.has(file)) {
|
||||||
log("Skipping file %s since it has already be processed", file);
|
log("Skipping file %s since it has already be processed", file);
|
||||||
@ -109,13 +111,17 @@ function processFile(ctx: ProcessContext, file: string): Parsed | null {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof TokenizerError) {
|
if (err instanceof TokenizerError) {
|
||||||
printError(err, file, err.index);
|
printError(err, file, err.index);
|
||||||
|
if(!root)
|
||||||
|
throw new CatchedError();
|
||||||
} else if (err instanceof ParserError) {
|
} else if (err instanceof ParserError) {
|
||||||
printError(err, file, err.token.startIdx);
|
printError(err, file, err.token.startIdx);
|
||||||
|
if(!root)
|
||||||
|
throw new CatchedError();
|
||||||
|
} else if(root && err instanceof CatchedError) {
|
||||||
|
return null;
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,19 +135,27 @@ export default function startCompile(options: CompileOptions) {
|
|||||||
if(options.input.endsWith(".json")) {
|
if(options.input.endsWith(".json")) {
|
||||||
ir = JSON.parse(FS.readFileSync(options.input, "utf-8"));
|
ir = JSON.parse(FS.readFileSync(options.input, "utf-8"));
|
||||||
} else {
|
} else {
|
||||||
const parsed = processFile(ctx, options.input);
|
const parsed = processFile(ctx, options.input, true);
|
||||||
// console.log(([...parsed].pop() as any).functions)
|
|
||||||
if(!parsed)
|
if(!parsed)
|
||||||
throw new Error("Error compiling: Parse output is undefined!");
|
process.exit(1); // Errors should have already been emitted
|
||||||
|
try {
|
||||||
ir = get_ir(parsed);
|
ir = get_ir(parsed);
|
||||||
|
} catch(err) {
|
||||||
|
if(err instanceof IRError) {
|
||||||
|
printError(err, err.statement.location.file, err.statement.location.idx);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!ir)
|
if(!ir)
|
||||||
throw new Error("Error compiling: Cannot get IR");
|
throw new Error("Error compiling: Cannot get IR");
|
||||||
|
|
||||||
if(options.emitDefinitions) {
|
if(options.emitDefinitions) {
|
||||||
FS.writeFileSync(options.emitDefinitions, JSON.stringify(ir));
|
FS.writeFileSync(options.emitDefinitions, JSON.stringify(ir, undefined, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.targets.length <= 0) {
|
if(options.targets.length <= 0) {
|
||||||
|
@ -3,6 +3,7 @@ export type TokenTypes =
|
|||||||
| "comment"
|
| "comment"
|
||||||
| "string"
|
| "string"
|
||||||
| "keyword"
|
| "keyword"
|
||||||
|
| "at"
|
||||||
| "colon"
|
| "colon"
|
||||||
| "semicolon"
|
| "semicolon"
|
||||||
| "comma"
|
| "comma"
|
||||||
@ -57,6 +58,7 @@ const matcher = [
|
|||||||
regexMatcher(/^".*?"/, "string"),
|
regexMatcher(/^".*?"/, "string"),
|
||||||
// regexMatcher(/(?<=^")(.*?)(?=")/, "string"),
|
// regexMatcher(/(?<=^")(.*?)(?=")/, "string"),
|
||||||
regexMatcher(/^(type|enum|import|service)\b/, "keyword"),
|
regexMatcher(/^(type|enum|import|service)\b/, "keyword"),
|
||||||
|
regexMatcher(/^\@/, "at"),
|
||||||
regexMatcher(/^\:/, "colon"),
|
regexMatcher(/^\:/, "colon"),
|
||||||
regexMatcher(/^\;/, "semicolon"),
|
regexMatcher(/^\;/, "semicolon"),
|
||||||
regexMatcher(/^\,/, "comma"),
|
regexMatcher(/^\,/, "comma"),
|
||||||
|
Loading…
Reference in New Issue
Block a user