import { Grammar, Parser, } from "https://deno.land/x/nearley@2.19.7-deno/mod.ts"; import compiledParserGrammar from "./parser.out.ts"; import { CodeBlock, Enum, Name, Node, Param, Param_Named, Statement, Type, } from "./ast.out.ts"; class TypescriptGenerator { private result = ""; public generate(ast: Statement[]): string { this.result += "// Generated file by ast_generator\n"; for (const statement of ast) { switch (statement.kind) { case "CodeBlock": this.generateCodeBlock(statement[0]); break; case "Enum": this.generateEnum(statement[0]); break; case "Node": this.generateNode(statement[0]); break; } this.result += "\n"; } return this.result; } private generateCodeBlock(block: CodeBlock) { this.result += `${block[0]}\n`; } private generateEnum(enum_: Enum) { if (this.isShallowEnum(enum_)) { return this.generateShallowEnumBody(enum_); } else { return this.generateEnumBody(enum_); } } private isShallowEnum(enum_: Enum): boolean { return !enum_.nodes.some((node) => node.params.length > 0); } private generateShallowEnumBody(enum_: Enum) { const name = this.makeName(enum_.name); const fields = enum_.nodes.map((node) => this.makeName(node.name)).map( (name) => `${name}: "${name}"`, ).join(", "); this.result += `export const ${name} = { ${fields} } as const;\n`; this.result += `export type ${name} = keyof typeof ${name};\n`; } private generateEnumBody(enum_: Enum) { const enumName = this.makeName(enum_.name); const nodeNames: [string, string][] = []; for (const node of enum_.nodes) { const name = this.makeName(node.name); const fullName = `${enumName}_${name}`; nodeNames.push([name, fullName]); this.result += `export type ${fullName} = {\n`; this.result += ` type: "${enumName}",\n`; this.result += ` kind: "${name}",\n`; this.result += node.params.map((p, i) => this.makeParamField(p, i)) .map((field) => ` ${field},\n`).join(""); this.result += `};\n`; const fnParams = this.makeFnParams(node.params); const fields = this.makeFnFields(node.params); this.result += `export const ${fullName}` + ` = (${fnParams}): ${fullName}` + ` => ({ type: "${enumName}", kind: "${name}", ${fields} });\n`; } const typeList = nodeNames .map(([_, fullName]) => fullName) .join(" | "); this.result += `export type ${enumName} = ${typeList};\n`; const enumFieldFns = nodeNames .map(([name, fullName]) => `${name}: ${fullName}`) .join(", "); this.result += `export const ${enumName} = { ${enumFieldFns} } as const;\n`; } private makeParamField(param: Param, index: number): string { switch (param.kind) { case "Named": return this.makeNamedParam(param); case "Unnamed": return `[${index}]: ${this.makeType(param[0])}`; } } private generateNode( node: Node, ) { const name = this.makeName(node.name); this.result += `export type ${name} = {\n`; this.result += ` type: "${name}";\n`; for ( const [param, index] of node.params .map((v, i) => [v, i] as const) ) { switch (param.kind) { case "Named": this.result += ` ${this.makeNamedParam(param)};\n`; break; case "Unnamed": { const type_ = this.makeType(param[0]); this.result += ` [${index}]: ${type_};\n`; break; } } } this.result += "};\n"; const fnParams = this.makeFnParams(node.params); const fields = this.makeFnFields(node.params); this.result += `export const ${name}` + ` = (${fnParams}): ${name}` + ` => ({ type: "${name}", ${fields} });\n`; } private makeFnParams(params: Param[]): string { return params .map((param, index) => { switch (param.kind) { case "Named": return this.makeNamedParam(param); case "Unnamed": return `v${index}: ${this.makeType(param[0])}`; } }) .join(", "); } private makeFnFields(params: Param[]): string { return params.map((param, index) => { switch (param.kind) { case "Named": return this.makeName(param.name); case "Unnamed": return `[${index}]: v${index}`; } }) .join(", "); } private makeNamedParam(param: Param_Named): string { const name = this.makeName(param.name); const type_ = this.makeType(param.type_); return `${name}: ${type_}`; } private makeType(type_: Type): string { switch (type_.kind) { case "Name": return this.makeName(type_[0]); case "Optional": return `${this.makeType(type_[0])} | null`; case "Multiple": return `${this.makeType(type_[0])}[]`; } } private makeName(name: Name): string { return name[0]; } } const parser = new Parser(Grammar.fromCompiled(compiledParserGrammar)); if (Deno.args.length < 1) { console.log("generate_ast "); throw new Error("not enough args"); } if (["-h", "--help"].includes(Deno.args[0])) { console.log("generate_ast "); Deno.exit(0); } const text = await Deno.readTextFile(Deno.args[0]); parser.feed(text); const ast = parser.results[0]; // console.log(JSON.stringify(ast, null, "│ ")); const ts = new TypescriptGenerator().generate(ast); console.log(ts);