commit edb79a10bc6a30fba5eab2a8f5406f8598bd61e9 Author: SimonFJ20 Date: Sun Jul 21 21:56:42 2024 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c20209 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +parser.out.ts diff --git a/ast b/ast new file mode 100644 index 0000000..c99b4cc --- /dev/null +++ b/ast @@ -0,0 +1,20 @@ + +Statement { + Enum(enum_: Enum) + Node(node: Node) +} + +Enum(name: Name, nodes: Node[]) + +Node(name: Name, params: Param[]) + +Param(name: Name, type_: Type) + +Type { + Name(name: Name) + Optional(type_: Type) + Multiple(type_: Type) +} + +Name(value: string) + diff --git a/ast.out.ts b/ast.out.ts new file mode 100644 index 0000000..99ab9da --- /dev/null +++ b/ast.out.ts @@ -0,0 +1,48 @@ +// Generated file by ast_generator +export type EnumStatement = { + kind: "Enum"; + enum_: Enum; +}; +export const EnumStatement = (enum_: Enum): EnumStatement => ({ kind: "Enum", enum_ }); +export type NodeStatement = { + kind: "Node"; + node: Node; +}; +export const NodeStatement = (node: Node): NodeStatement => ({ kind: "Node", node }); +export type Statement = EnumStatement | NodeStatement; +export type Enum = { + name: Name; + nodes: Node[]; +}; +export const Enum = (name: Name, nodes: Node[]): Enum => ({ name, nodes }); +export type Node = { + name: Name; + params: Param[]; +}; +export const Node = (name: Name, params: Param[]): Node => ({ name, params }); +export type Param = { + name: Name; + type_: Type; +}; +export const Param = (name: Name, type_: Type): Param => ({ name, type_ }); +export type NameType = { + kind: "Name"; + name: Name; +}; +export const NameType = (name: Name): NameType => ({ kind: "Name", name }); +export type OptionalType = { + kind: "Optional"; + type_: Type; +}; +export const OptionalType = (type_: Type): OptionalType => ({ kind: "Optional", type_ }); +export type MultipleType = { + kind: "Multiple"; + type_: Type; +}; +export const MultipleType = (type_: Type): MultipleType => ({ kind: "Multiple", type_ }); +export type Type = NameType | OptionalType | MultipleType; +export type Name = { + value: string; +}; +export const Name = (value: string): Name => ({ value }); + diff --git a/ast_generator.ts b/ast_generator.ts new file mode 100644 index 0000000..c429e4b --- /dev/null +++ b/ast_generator.ts @@ -0,0 +1,104 @@ +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 "); + 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); diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..802a315 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -xe + +./generate_ast ast > new.ast.out.ts + +mv new.ast.out.ts ast.out.ts + +./build.sh + diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..55cd6a4 --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -xe + +nearleyc parser.ne -o parser.out.ts + diff --git a/generate_ast b/generate_ast new file mode 100755 index 0000000..e03900d --- /dev/null +++ b/generate_ast @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +deno run --allow-read ast_generator.ts $1 diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..bc68ea4 --- /dev/null +++ b/install.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -xe + +DEFAULT_PREFIX=/usr/local +INSTALL_PREFIX="${PREFIX:-$DEFAULT_PREFIX}" + +if [[ $1 == "uninstall" ]]; then + echo "Uninstalling at ${INSTALL_PREFIX}..." + sudo rm -r "${INSTALL_PREFIX}/share/ast_generator" + sudo rm "${INSTALL_PREFIX}/bin/generate_ast" + echo "Uninstalled" + exit 0 +fi + +echo "Installing at ${INSTALL_PREFIX}..." + +./build.sh + +sudo mkdir -p "${INSTALL_PREFIX}/share/ast_generator" + +sudo cp ast_generator.ts "${INSTALL_PREFIX}/share/ast_generator/" +sudo cp ast.out.ts "${INSTALL_PREFIX}/share/ast_generator/" +sudo cp parser.out.ts "${INSTALL_PREFIX}/share/ast_generator/" + +echo "#!/bin/bash" >> generate_ast.temp +echo "deno run --allow-read ${INSTALL_PREFIX}/share/ast_generator/ast_generator.ts \$1" >> generate_ast.temp +chmod +x generate_ast.temp + +sudo mv generate_ast.temp "${INSTALL_PREFIX}/bin/generate_ast" + +echo "Installed" + diff --git a/parser.ne b/parser.ne new file mode 100644 index 0000000..a9bb7f5 --- /dev/null +++ b/parser.ne @@ -0,0 +1,76 @@ + +@preprocessor typescript + +@{% +import * as moo from "https://deno.land/x/moo@0.5.1-deno.2/mod.ts"; +import * as ast from "./ast.out.ts"; + +const lexer: any = moo.compile({ + + newline: { match: /[\n]+/, lineBreaks: true }, + whitespace: /[ \t]+/, + + singleLineComment: /\/\/.*?$/, + multiLineComment: { match: /\*[^*]*\*+(?:[^/*][^*]*\*+)*/, lineBreaks: true }, + + name: { + match: /[a-zA-Z0-9_]+/, + type: moo.keywords({ + keyword: [], + }), + }, + + lparen: "(", + rparen: ")", + lbrace: "{", + rbrace: "}", + lbracket: "[", + rbracket: "]", + colon: ":", + comma: ",", + questionmark: "?", +}); +%} + +@lexer lexer + +file -> elements {% id %} + +elements -> _ (element elementTail _):? {% v => v[1] ? [v[1][0], ...v[1][1]] : [] %} + +elementTail -> (nl__ element):* {% v => v[0].map(w => w[1]) %} + +element -> enum {% v => ast.EnumStatement(v[0]) %} + | node {% v => ast.NodeStatement(v[0]) %} + +enum -> name _ "{" nodes "}" {% v => ast.Enum(v[0], v[3]) %} + +nodes -> _ (node nodeTail _):? {% v => v[1] ? [v[1][0], ...v[1][1]] : [] %} + +nodeTail -> (nl__ node):* {% v => v[0].map(w => w[1]) %} + +node -> name _ "(" params ")" {% v => ast.Node(v[0], v[3]) %} + +params -> _ (param paramTail (_ ","):? _):? {% v => v[1] ? [v[1][0], ...v[1][1]] : [] %} + +paramTail -> ("," _ param):* {% v => v[0].map(w => w[2]) %} + +param -> name _ ":" _ type {% v => ast.Param(v[0], v[4]) %} + +type -> optional {% id %} + | multiple {% id %} + | name {% v => ast.NameType(v[0]) %} + +optional -> type "?" {% v => ast.OptionalType(v[0]) %} + +multiple -> type "[" "]" {% v => ast.MultipleType(v[0]) %} + +name -> %name {% v => ast.Name(v[0]) %} + +_ -> __:? +__ -> (%whitespace|%newline|%singeLineComment|%multiLineComment):+ + +nl__ -> sl_ (%newline sl_):+ + +sl_ -> sl__:? +sl__ -> (%whitespace|%singleLineComment|%multiLineComment):+