295 lines
9.0 KiB
TypeScript
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
|
|
};
|
|
}
|