This commit is contained in:
SimonFJ20 2024-07-21 21:56:42 +02:00
commit edb79a10bc
9 changed files with 303 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
parser.out.ts

20
ast Normal file
View File

@ -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)

48
ast.out.ts Normal file
View File

@ -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 });

104
ast_generator.ts Normal file
View File

@ -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 <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);

10
bootstrap.sh Executable file
View File

@ -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

6
build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
set -xe
nearleyc parser.ne -o parser.out.ts

5
generate_ast Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
deno run --allow-read ast_generator.ts $1

33
install.sh Executable file
View File

@ -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"

76
parser.ne Normal file
View File

@ -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):+