JsonRPC/src/ir.ts

295 lines
9.0 KiB
TypeScript

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 building IR: " + 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 type ServiceFunctionDecorators = {
description: string;
parameters: {
name:string;
description: string;
}[];
returns: string;
}
export interface ServiceFunctionParamsDefinition {
name: string;
inputs: { type: string; name: string, array: boolean }[];
return: { type: string, array: boolean } | undefined;
decorators: ServiceFunctionDecorators
}
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 = {
options: { [key:string]: string},
steps: 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[] = [];
let options = {} as any;
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.type) >= 0) {
if (!depends.some((a) => a === fnc.return_type.type))
depends.push(fnc.return_type.type);
} else {
if (fnc.return_type.type !== "void" && builtin.indexOf(fnc.return_type.type) < 0) {
throw new IRError(
fnc,
`Type ${fnc.return_type.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`
);
}
}
}
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;
});
steps.push([
"service",
{
name: statement.name,
depends,
functions,
} as ServiceDefinition,
]);
} else if(statement.type == "define") {
options[statement.key] = statement.value;
} else {
throw new IRError(statement, "Invalid statement!");
}
});
return {
options,
steps
};
}