105 lines
3.1 KiB
TypeScript
105 lines
3.1 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, Node, Param, 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.enum_);
|
||
|
break;
|
||
|
case "Node":
|
||
|
this.generateNode(statement.node);
|
||
|
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 of node.params) {
|
||
|
this.result += ` ${this.makeParam(param)};\n`;
|
||
|
}
|
||
|
this.result += "};\n";
|
||
|
const fnParams = node.params.map((param) => this.makeParam(param)).join(
|
||
|
", ",
|
||
|
);
|
||
|
const fields = [
|
||
|
...(kind ? [`kind: "${kind}"`] : []),
|
||
|
...node.params.map((param) => this.makeName(param.name)),
|
||
|
].join(", ");
|
||
|
this.result +=
|
||
|
`export const ${name} = (${fnParams}): ${name} => ({ ${fields} });\n`;
|
||
|
}
|
||
|
|
||
|
private makeParam(param: Param): 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_.name);
|
||
|
case "Optional":
|
||
|
return `${this.makeType(type_.type_)} | null`;
|
||
|
case "Multiple":
|
||
|
return `${this.makeType(type_.type_)}[]`;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private makeName(name: Name): string {
|
||
|
return name.value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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);
|