ast_generator/ast_generator.ts
2024-07-21 23:35:34 +02:00

130 lines
4.0 KiB
TypeScript

import {
Grammar,
Parser,
} from "https://deno.land/x/nearley@2.19.7-deno/mod.ts";
import compiledParserGrammar from "./parser.out.ts";
import { Enum, Name, NamedParam, Node, 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 "Enum":
this.generateEnum(statement[0]);
break;
case "Node":
this.generateNode(statement[0]);
break;
}
}
return this.result;
}
private generateEnum(enum_: Enum) {
const enumName = this.makeName(enum_.name);
const nodeNames: string[] = [];
for (const node of enum_.nodes) {
const kind = this.makeName(node.name);
const name = `${kind}${enumName}`;
this.generateNodeBody(node, name, kind);
nodeNames.push(name);
}
this.result += `export type ${enumName} = ${nodeNames.join(" | ")};\n`;
}
private generateNode(node: Node) {
this.generateNodeBody(node);
}
private generateNodeBody(
node: Node,
name = this.makeName(node.name),
kind?: string,
) {
this.result += `export type ${name} = {\n`;
if (kind) {
this.result += ` kind: "${kind}";\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":
this.result += ` [${index}]: ${
this.makeType(param[0])
};\n`;
}
}
this.result += "};\n";
const fnParams = node.params
.map((param, index) => {
switch (param.kind) {
case "Named":
return this.makeNamedParam(param);
case "Unnamed":
return `v${index}: ${this.makeType(param[0])}`;
}
})
.join(", ");
const fields = [
...(kind ? [`kind: "${kind}"`] : []),
...node.params.map((param, index) => {
switch (param.kind) {
case "Named":
return this.makeName(param.name);
case "Unnamed":
return `[${index}]: v${index}`;
}
}),
].join(", ");
this.result +=
`export const ${name} = (${fnParams}): ${name} => ({ ${fields} });\n`;
}
private makeNamedParam(param: NamedParam): 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 <file>");
throw new Error("not enough args");
}
if (["-h", "--help"].includes(Deno.args[0])) {
console.log("generate_ast <file>");
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);