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/install-state.gz
|
||||
examples/out
|
||||
examples/definition.json
|
||||
|
@ -26,9 +26,20 @@ type AddValueResponse {
|
||||
}
|
||||
|
||||
service TestService {
|
||||
@Description("Add two numbers")
|
||||
@Param("request", "Parameter containing the two numbers")
|
||||
AddValuesSingleParam(request: AddValueRequest): AddValueResponse;
|
||||
|
||||
@Description("Add two numbers")
|
||||
@Param("value1", "The first value")
|
||||
@Param("value2", "The second value")
|
||||
AddValuesMultipleParams(value1: number, value2: number): number;
|
||||
|
||||
@Description("Does literaly nothing")
|
||||
@Param("param1", "Some number")
|
||||
ReturningVoid(param1: number): void;
|
||||
|
||||
@Description("Just sends an Event with a String")
|
||||
@Param("param1", "Parameter with some string for event")
|
||||
notification OnEvent(param1: string);
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@hibas123/jrpcgen",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"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.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",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
|
53
src/ir.ts
53
src/ir.ts
@ -33,10 +33,20 @@ export interface EnumDefinition {
|
||||
values: EnumValueDefinition[];
|
||||
}
|
||||
|
||||
export type ServiceFunctionDecorators = {
|
||||
description: string;
|
||||
parameters: {
|
||||
name:string;
|
||||
description: string;
|
||||
}[];
|
||||
returns: string;
|
||||
}
|
||||
|
||||
export interface ServiceFunctionParamsDefinition {
|
||||
name: string;
|
||||
inputs: { type: string; name: string }[];
|
||||
return: string | undefined;
|
||||
decorators: ServiceFunctionDecorators
|
||||
}
|
||||
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 {
|
||||
name: fnc.name,
|
||||
inputs: fnc.inputs,
|
||||
return: fnc.return_type,
|
||||
decorators
|
||||
} as ServiceFunctionDefinition;
|
||||
});
|
||||
|
||||
|
@ -44,11 +44,15 @@ export interface IServiceFunctionInput {
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
||||
export type Decorators = Map<string, string[][]>;
|
||||
|
||||
export interface ServiceFunctionStatement extends DefinitionNode {
|
||||
type: "service_function";
|
||||
inputs: IServiceFunctionInput[];
|
||||
name: string;
|
||||
return_type: string | undefined; // Makes it a notification
|
||||
decorators: Decorators;
|
||||
}
|
||||
|
||||
export interface ServiceStatement extends DefinitionNode {
|
||||
@ -116,6 +120,8 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
||||
return val;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const checkTypes = (...types: string[]) => {
|
||||
if (types.indexOf(currentToken.type) < 0) {
|
||||
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 = (
|
||||
notification?: boolean
|
||||
decorators: Decorators,
|
||||
notification?: boolean,
|
||||
): ServiceFunctionStatement => {
|
||||
const [name, idx] = eatText();
|
||||
|
||||
@ -318,6 +360,7 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
||||
},
|
||||
inputs,
|
||||
return_type,
|
||||
decorators
|
||||
};
|
||||
};
|
||||
|
||||
@ -327,13 +370,19 @@ export default function parse(tokens: Token[], file: string): Parsed {
|
||||
eatToken("{");
|
||||
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;
|
||||
if (currentToken.value == "notification") {
|
||||
eatText();
|
||||
notification = true;
|
||||
}
|
||||
functions.push(parseServiceFunction(notification));
|
||||
functions.push(parseServiceFunction(decorators, notification));
|
||||
}
|
||||
eatToken("}");
|
||||
|
||||
|
@ -4,10 +4,12 @@ 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 get_ir, { IR, IRError } from "./ir";
|
||||
import compile, { CompileTarget } from "./compile";
|
||||
import { ESMTypescriptTarget, NodeJSTypescriptTarget } from "./targets/typescript";
|
||||
|
||||
class CatchedError extends Error {}
|
||||
|
||||
const log = dbg("app");
|
||||
|
||||
|
||||
@ -75,7 +77,7 @@ type ProcessContext = {
|
||||
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);
|
||||
if (ctx.processedFiles.has(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) {
|
||||
if (err instanceof TokenizerError) {
|
||||
printError(err, file, err.index);
|
||||
if(!root)
|
||||
throw new CatchedError();
|
||||
} else if (err instanceof ParserError) {
|
||||
printError(err, file, err.token.startIdx);
|
||||
if(!root)
|
||||
throw new CatchedError();
|
||||
} else if(root && err instanceof CatchedError) {
|
||||
return null;
|
||||
} else {
|
||||
throw err;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,19 +135,27 @@ export default function startCompile(options: CompileOptions) {
|
||||
if(options.input.endsWith(".json")) {
|
||||
ir = JSON.parse(FS.readFileSync(options.input, "utf-8"));
|
||||
} else {
|
||||
const parsed = processFile(ctx, options.input);
|
||||
// console.log(([...parsed].pop() as any).functions)
|
||||
const parsed = processFile(ctx, options.input, true);
|
||||
|
||||
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);
|
||||
} catch(err) {
|
||||
if(err instanceof IRError) {
|
||||
printError(err, err.statement.location.file, err.statement.location.idx);
|
||||
process.exit(1);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!ir)
|
||||
throw new Error("Error compiling: Cannot get IR");
|
||||
|
||||
if(options.emitDefinitions) {
|
||||
FS.writeFileSync(options.emitDefinitions, JSON.stringify(ir));
|
||||
FS.writeFileSync(options.emitDefinitions, JSON.stringify(ir, undefined, 3));
|
||||
}
|
||||
|
||||
if(options.targets.length <= 0) {
|
||||
|
@ -3,6 +3,7 @@ export type TokenTypes =
|
||||
| "comment"
|
||||
| "string"
|
||||
| "keyword"
|
||||
| "at"
|
||||
| "colon"
|
||||
| "semicolon"
|
||||
| "comma"
|
||||
@ -57,6 +58,7 @@ const matcher = [
|
||||
regexMatcher(/^".*?"/, "string"),
|
||||
// regexMatcher(/(?<=^")(.*?)(?=")/, "string"),
|
||||
regexMatcher(/^(type|enum|import|service)\b/, "keyword"),
|
||||
regexMatcher(/^\@/, "at"),
|
||||
regexMatcher(/^\:/, "colon"),
|
||||
regexMatcher(/^\;/, "semicolon"),
|
||||
regexMatcher(/^\,/, "comma"),
|
||||
|
Loading…
Reference in New Issue
Block a user