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((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((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(); 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; }