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