treegen: Make parser build actual treegen-AST
This commit is contained in:
parent
852a9d11a6
commit
4dc5a8e135
2 changed files with 122 additions and 10 deletions
74
treegen/src/bin/treegen.ts
Executable file
74
treegen/src/bin/treegen.ts
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import * as path from "path"
|
||||||
|
import * as fs from "fs"
|
||||||
|
|
||||||
|
import { parse, SyntaxError } from "../parser"
|
||||||
|
import { Declaration } from "../ast"
|
||||||
|
|
||||||
|
for (const filename of process.argv.slice(2)) {
|
||||||
|
const contents = fs.readFileSync(filename, 'utf8');
|
||||||
|
let decls: Declaration[];
|
||||||
|
try {
|
||||||
|
decls = parse(contents);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof SyntaxError) {
|
||||||
|
console.error(`${filename}:${e.location.start.line}:${e.location.start.column}: ${e.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.error(jsonify(decls));
|
||||||
|
const generated = generateAST(decls);
|
||||||
|
fs.writeFileSync(getFileStem(filename).toLowerCase(), generated, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FastStringMap<T> { [key: string]: T }
|
||||||
|
|
||||||
|
function generateAST(decls: Declaration[]) {
|
||||||
|
|
||||||
|
const declByName: FastStringMap<Declaration> = Object.create(null);
|
||||||
|
for (const decl of decls) {
|
||||||
|
declByName[decl.name] = decl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsonify(value: any) {
|
||||||
|
|
||||||
|
function visitNode(node: any) {
|
||||||
|
|
||||||
|
const obj: any = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(node)) {
|
||||||
|
if (key !== 'type' && key !== 'span') {
|
||||||
|
const value = node[key];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
obj[key] = value.map(visit);
|
||||||
|
} else {
|
||||||
|
obj[key] = visit(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function visit(value: any) {
|
||||||
|
if (value.__IS_NODE) {
|
||||||
|
return visitNode(value);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return visit(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileStem(filepath: string) {
|
||||||
|
return path.basename(filepath).split('.')[0];
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,26 @@
|
||||||
|
{
|
||||||
|
|
||||||
|
function liftArray(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value === null || value === undefined ? [] : [value]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNode(type, props) {
|
||||||
|
return {
|
||||||
|
__IS_NODE: true,
|
||||||
|
type,
|
||||||
|
span: location(),
|
||||||
|
...props,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
File
|
File
|
||||||
= __ (@Declaration __)*
|
= __ @(@Declaration __)*
|
||||||
|
|
||||||
Declaration
|
Declaration
|
||||||
= NodeDeclaration
|
= NodeDeclaration
|
||||||
|
@ -8,34 +28,52 @@ Declaration
|
||||||
/ TypeDeclaration
|
/ TypeDeclaration
|
||||||
|
|
||||||
NodeDeclaration
|
NodeDeclaration
|
||||||
= NodeToken __ name:Identifier parents:(__ '>' __ ExtendsList)? fields:(__ '{' __ (@NodeField __)* '}')? EOS
|
= NodeToken __ name:Identifier parents:(__ '>' __ ExtendsList)? fields:(__ '{' __ (@NodeField __)* '}')? EOS {
|
||||||
|
return createNode('NodeDeclaration', { name, parents: liftArray(parents), fields: liftArray(fields) });
|
||||||
|
}
|
||||||
|
|
||||||
ExtendsList
|
ExtendsList
|
||||||
= head:Identifier tail:(__ ',' __ @Identifier)*
|
= head:Identifier tail:(__ ',' __ @Identifier)* {
|
||||||
|
return [head, ...tail];
|
||||||
|
}
|
||||||
|
|
||||||
NodeField
|
NodeField
|
||||||
= name:Identifier __ ':' __ type:TypeNode EOD
|
= name:Identifier __ ':' __ typeNode:TypeNode EOD {
|
||||||
|
return createNode('NodeField', { name, typeNode });
|
||||||
|
}
|
||||||
|
|
||||||
TypeNode
|
TypeNode
|
||||||
= UnionTypeNode
|
= UnionTypeNode
|
||||||
|
|
||||||
UnionTypeNode
|
UnionTypeNode
|
||||||
= head:ReferenceTypeNode tail:(__ '|' __ @ReferenceTypeNode)*
|
= head:ReferenceTypeNode tail:(__ '|' __ @ReferenceTypeNode)* {
|
||||||
|
return [head, ...tail];
|
||||||
|
}
|
||||||
|
|
||||||
ReferenceTypeNode
|
ReferenceTypeNode
|
||||||
= name:Identifier genericArgs:(__ '<' __ @TypeNodeList __ '>')?
|
= name:Identifier genericArgs:(__ '<' __ @TypeNodeList __ '>')? {
|
||||||
|
return createNode('ReferenceTypeNode', { name, genericArgs });
|
||||||
|
}
|
||||||
|
|
||||||
TypeNodeList
|
TypeNodeList
|
||||||
= head:TypeNode tail:(__ ',' __ @TypeNode)*
|
= parsed:(TypeNode (__ ',' __ @TypeNode)*)? {
|
||||||
|
return parsed !== null ? [parsed[0], ...parsed[1]] : [];
|
||||||
|
}
|
||||||
|
|
||||||
EnumDeclaration
|
EnumDeclaration
|
||||||
= EnumToken __ name:Identifier __ '{' __ (@EnumField __)* '}' EOS
|
= EnumToken __ name:Identifier __ '{' __ fields:(@EnumField __)* '}' EOS {
|
||||||
|
return createNode('EnumDeclaration', { name, fields });
|
||||||
|
}
|
||||||
|
|
||||||
EnumField
|
EnumField
|
||||||
= name:Identifier value:(__ '=' __ @Integer)? EOD
|
= name:Identifier value:(__ '=' __ @Integer)? EOD {
|
||||||
|
return createNode('EnumField', { name, value });
|
||||||
|
}
|
||||||
|
|
||||||
TypeDeclaration
|
TypeDeclaration
|
||||||
= TypeToken __ name:Identifier __ '=' __ type:TypeNode EOS
|
= TypeToken __ name:Identifier __ '=' __ typeNode:TypeNode EOS {
|
||||||
|
return createNode('TypeDeclaration', { name, typeNode });
|
||||||
|
}
|
||||||
|
|
||||||
Identifier "an identifier"
|
Identifier "an identifier"
|
||||||
= $(IdentifierStart IdentifierPart*)
|
= $(IdentifierStart IdentifierPart*)
|
||||||
|
|
Loading…
Reference in a new issue