2024-07-21 20:56:42 +01:00
|
|
|
import {
|
|
|
|
Grammar,
|
|
|
|
Parser,
|
|
|
|
} from "https://deno.land/x/nearley@2.19.7-deno/mod.ts";
|
|
|
|
|
|
|
|
import compiledParserGrammar from "./parser.out.ts";
|
2024-07-21 22:35:34 +01:00
|
|
|
import { Enum, Name, NamedParam, Node, Statement, Type } from "./ast.out.ts";
|
2024-07-21 20:56:42 +01:00
|
|
|
|
|
|
|
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":
|
2024-07-21 22:35:34 +01:00
|
|
|
this.generateEnum(statement[0]);
|
2024-07-21 20:56:42 +01:00
|
|
|
break;
|
|
|
|
case "Node":
|
2024-07-21 22:35:34 +01:00
|
|
|
this.generateNode(statement[0]);
|
2024-07-21 20:56:42 +01:00
|
|
|
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`;
|
|
|
|
}
|
2024-07-21 22:35:34 +01:00
|
|
|
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`;
|
|
|
|
}
|
2024-07-21 20:56:42 +01:00
|
|
|
}
|
|
|
|
this.result += "};\n";
|
2024-07-21 22:35:34 +01:00
|
|
|
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(", ");
|
2024-07-21 20:56:42 +01:00
|
|
|
const fields = [
|
|
|
|
...(kind ? [`kind: "${kind}"`] : []),
|
2024-07-21 22:35:34 +01:00
|
|
|
...node.params.map((param, index) => {
|
|
|
|
switch (param.kind) {
|
|
|
|
case "Named":
|
|
|
|
return this.makeName(param.name);
|
|
|
|
case "Unnamed":
|
|
|
|
return `[${index}]: v${index}`;
|
|
|
|
}
|
|
|
|
}),
|
2024-07-21 20:56:42 +01:00
|
|
|
].join(", ");
|
|
|
|
this.result +=
|
|
|
|
`export const ${name} = (${fnParams}): ${name} => ({ ${fields} });\n`;
|
|
|
|
}
|
|
|
|
|
2024-07-21 22:35:34 +01:00
|
|
|
private makeNamedParam(param: NamedParam): string {
|
2024-07-21 20:56:42 +01:00
|
|
|
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":
|
2024-07-21 22:35:34 +01:00
|
|
|
return this.makeName(type_[0]);
|
2024-07-21 20:56:42 +01:00
|
|
|
case "Optional":
|
2024-07-21 22:35:34 +01:00
|
|
|
return `${this.makeType(type_[0])} | null`;
|
2024-07-21 20:56:42 +01:00
|
|
|
case "Multiple":
|
2024-07-21 22:35:34 +01:00
|
|
|
return `${this.makeType(type_[0])}[]`;
|
2024-07-21 20:56:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private makeName(name: Name): string {
|
2024-07-21 22:35:34 +01:00
|
|
|
return name[0];
|
2024-07-21 20:56:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|