init
This commit is contained in:
commit
edb79a10bc
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
parser.out.ts
|
20
ast
Normal file
20
ast
Normal 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
48
ast.out.ts
Normal 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
104
ast_generator.ts
Normal 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
10
bootstrap.sh
Executable 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
6
build.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -xe
|
||||
|
||||
nearleyc parser.ne -o parser.out.ts
|
||||
|
5
generate_ast
Executable file
5
generate_ast
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
deno run --allow-read ast_generator.ts $1
|
33
install.sh
Executable file
33
install.sh
Executable 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
76
parser.ne
Normal 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):+
|
Loading…
Reference in New Issue
Block a user