diff --git a/treegen/Makefile b/treegen/Makefile new file mode 100644 index 000000000..57d616e58 --- /dev/null +++ b/treegen/Makefile @@ -0,0 +1,11 @@ + +all: lib/parser.js + +lib/parser.js: src/parser.pegjs + @echo "Generating parser ..." + @mkdir -p lib/ + @if ! pegjs --output lib/parser.js src/parser.pegjs; then \ + rm -rf lib/parser.js; \ + exit 1; \ + fi + diff --git a/treegen/package-lock.json b/treegen/package-lock.json new file mode 100644 index 000000000..fb8b31374 --- /dev/null +++ b/treegen/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "treegen", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/treegen/package.json b/treegen/package.json new file mode 100644 index 000000000..e6cfdafc4 --- /dev/null +++ b/treegen/package.json @@ -0,0 +1,29 @@ +{ + "name": "treegen", + "version": "1.0.0", + "private": true, + "description": "An AST tree generator from a specification file.", + "main": "lib/index.js", + "bin": { + "treegen": "lib/cli.js" + }, + "scripts": { + "prepare": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/samvv/BoltJS.git" + }, + "keywords": [ + "abstract-syntaxt-tree", + "specification-file", + "code-generator" + ], + "author": "Sam Vervaeck", + "license": "MIT", + "bugs": { + "url": "https://github.com/samvv/BoltJS/issues" + }, + "homepage": "https://github.com/samvv/BoltJS#readme" +} diff --git a/treegen/snippets/ast-after.d.ts b/treegen/snippets/ast-after.d.ts new file mode 100644 index 000000000..e69de29bb diff --git a/treegen/snippets/ast-after.js b/treegen/snippets/ast-after.js new file mode 100644 index 000000000..e69de29bb diff --git a/src/treegen/ast.dts.template b/treegen/snippets/ast-before.d.ts similarity index 100% rename from src/treegen/ast.dts.template rename to treegen/snippets/ast-before.d.ts diff --git a/src/treegen/ast-template.js b/treegen/snippets/ast-before.js similarity index 54% rename from src/treegen/ast-template.js rename to treegen/snippets/ast-before.js index c27c41278..6c5182e1c 100644 --- a/src/treegen/ast-template.js +++ b/treegen/snippets/ast-before.js @@ -1,7 +1,5 @@ -const exported = {}; - -class NodeVisitor { +export class NodeVisitor { visit(node) { for (const child of node.preorder()) { const key = `visit${kindToString(child.kind)}`; @@ -12,9 +10,15 @@ class NodeVisitor { } } -exported.NodeVisitor = NodeVisitor; +let nextNodeId = 1; -const nodeProto = { +class SyntaxBase { + + constructor(span) { + this.id = nextNodeId++; + this.errors = []; + this.span = span; + } *getChildNodes() { for (const key of Object.keys(this)) { @@ -34,7 +38,7 @@ const nodeProto = { } } } - }, + } visit(visitors) { const stack = [this]; @@ -54,7 +58,7 @@ const nodeProto = { stack.push(childNode); } } - }, + } *preorder() { const stack = [this]; @@ -65,12 +69,12 @@ const nodeProto = { stack.push(childNode); } } - }, + } mayContainKind(kind) { // TODO return true; - }, + } getParentOfKind(kind) { let currNode = this.parentNode; @@ -81,7 +85,7 @@ const nodeProto = { currNode = currNode.parentNode; } return null; - }, + } *findAllChildrenOfKind(kind) { for (const node of this.preorder()) { @@ -96,87 +100,15 @@ const nodeProto = { } -function isSyntax(value) { +export function isSyntax(value) { return typeof value === 'object' && value !== null && value.__NODE_TYPE !== undefined; } -exported.isSyntax = isSyntax; - -let nextNodeId = 1; - -function createNode(nodeType) { - const obj = Object.create(nodeProto); - Object.defineProperty(obj, '__NODE_TYPE', { - enumerable: false, - writable: false, - configurable: true, - value: nodeType, - }); - Object.defineProperty(obj, 'kind', { - enumerable: false, - configurable: true, - get() { - return this.__NODE_TYPE.index; - } - }); - Object.defineProperty(obj, 'errors', { - enumerable: false, - configurable: true, - value: [], - }) - Object.defineProperty(obj, 'id', { - enumerable: true, - configurable: true, - value: nextNodeId++, - }) - obj.span = null; - return obj; -} - -for (const nodeName of Object.keys(NODE_TYPES)) { - - exported[`create${nodeName}`] = function (...args) { - const nodeType = NODE_TYPES[nodeName]; - const node = createNode(nodeType); - let i = 0; - const iter = nodeType.fields[Symbol.iterator](); - for (; i < args.length; i++) { - const { done, value } = iter.next(); - if (done) { - break; - } - const [fieldName, fieldType] = value; - node[fieldName] = args[i]; - } - while (true) { - const { done, value } = iter.next(); - if (done) { - break; - } - const [fieldName, fieldType] = value; - throw new Error(`No argument provided for field '${fieldName}'`); - } - if (i < args.length) { - node.span = args[i++]; - } - if (i < args.length) { - throw new Error(`Too many arguments provided to function create${nodeName}`); - } - return node; - } - -} - -exported.setParents = function setParents(node, parentNode = null) { +export function setParents(node, parentNode = null) { node.parentNode = parentNode; for (const child of node.getChildNodes()) { setParents(child, node) } } - -if (typeof module !== 'undefined') { - module.exports = exports; -} - diff --git a/src/treegen/ast.ts b/treegen/src/ast.ts similarity index 100% rename from src/treegen/ast.ts rename to treegen/src/ast.ts diff --git a/src/bin/bolt-treegen.ts b/treegen/src/cli.ts similarity index 76% rename from src/bin/bolt-treegen.ts rename to treegen/src/cli.ts index 31b7b9a01..7c5cf5dac 100755 --- a/src/bin/bolt-treegen.ts +++ b/treegen/src/cli.ts @@ -3,13 +3,13 @@ import * as path from "path" import * as fs from "fs" -import { parse, SyntaxError } from "../treegen/parser" -import { Declaration } from "../treegen/ast" -import { generateAST } from "../treegen/index" -import { getFileStem } from "../treegen/util" +import { parse, SyntaxError } from "./parser" +import { Declaration } from "./ast" +import { generateAST } from "./index" +import { getFileStem } from "./util" import minimist from "minimist" -const PACKAGE_ROOT = path.join(__dirname, '..', '..'); +//const PACKAGE_ROOT = path.join(__dirname, '..', '..'); const argv = minimist(process.argv.slice(2)); diff --git a/src/treegen/index.ts b/treegen/src/index.ts similarity index 88% rename from src/treegen/index.ts rename to treegen/src/index.ts index 8a6cb7674..9c29999be 100644 --- a/src/treegen/index.ts +++ b/treegen/src/index.ts @@ -2,13 +2,12 @@ import * as fs from "fs" import * as path from "path" -const PACKAGE_ROOT = path.resolve(__dirname, '..', '..'); +const PACKAGE_ROOT = path.resolve(__dirname, '..'); const CUSTOM_TYPES = ['Package', 'BoltValue', 'JSValue']; import { Syntax, Declaration, NodeDeclaration, TypeDeclaration, EnumDeclaration, TypeNode, NodeField } from "./ast" -import { MapLike, assert } from "../util" -import { FileWriter } from "./util" +import { MapLike, FileWriter } from "./util" export function generateAST(decls: Declaration[]) { @@ -49,45 +48,65 @@ export function generateAST(decls: Declaration[]) { // Write a JavaScript file that contains all AST definitions. - jsFile.write(`\nconst NODE_TYPES = {\n`); - jsFile.indent(); + jsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'snippets', 'ast-before.js'), 'utf8')); + + //jsFile.write(`\nconst NODE_TYPES = {\n`); + //jsFile.indent(); for (const decl of finalNodes) { - if (decl.type === 'NodeDeclaration' && isFinalNode(decl.name)) { - jsFile.write(`'${decl.name}': {\n`); - jsFile.indent(); - jsFile.write(`index: ${decl.index},\n`); - jsFile.write(`parents: [`); - for (const parentName of getParentChain(decl.name)) { - jsFile.write(`'${parentName}', `) - } - jsFile.write(`'Syntax'],\n`); - jsFile.write(`fields: new Map([\n`); - jsFile.indent(); - for (const field of getAllFields(decl)) { - jsFile.write(`['${field.name}', ${JSON.stringify(jsonify(field.typeNode))}],\n`); - } - jsFile.dedent(); - jsFile.write(']),\n'); - jsFile.dedent(); - jsFile.write('},\n'); + + jsFile.write(`class ${decl.name} extends SyntaxBase {\n\n`); + jsFile.indent(); + + jsFile.write(`static kind = ${decl.index};\n\n`); + jsFile.write(`static parents = `); + jsFile.write(JSON.stringify([...getParentChain(decl.name), 'Syntax'], undefined, 2)); + jsFile.write(';\n\n') + //jsFile.write(`static fields = new Map([\n`); + //jsFile.indent(); + //jsFile.write(JSON.stringify([...getAllFields(decl)].map(field => [field.name, jsonify(field.typeNode)]), undefined, 2)), + //jsFile.dedent(); + + jsFile.write(`constructor(\n`); + jsFile.indent(); + for (const field of getAllFields(decl)) { + jsFile.write(`${field.name},\n`); } + jsFile.write(`span = null,\n`) + jsFile.dedent(); + jsFile.write(`) {\n`); + jsFile.indent(); + jsFile.write(`super(span);\n`); + for (const field of getAllFields(decl)) { + jsFile.write(`this.${field.name} = ${field.name};\n`); + } + jsFile.write(`this.span = span\n`) + jsFile.dedent(); + jsFile.write('}\n\n') + + jsFile.dedent(); + jsFile.write('}\n'); + } + + jsFile.write(`const NODE_CLASSES = {\n`) + jsFile.indent(); + for (const node of finalNodes) { + jsFile.write(node.name + ',\n'); } jsFile.dedent(); - jsFile.write('};\n\n'); + jsFile.write('}\n\n') + //jsFile.dedent(); + //jsFile.write('};\n\n'); - jsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'src', 'treegen', 'ast-template.js'), 'utf8')); - - jsFile.write(`function kindToString (kind) {\n switch (kind) {\n`); + jsFile.write(`export function kindToString (kind) {\n switch (kind) {\n`); jsFile.indent(2); for (const leafNode of finalNodes) { jsFile.write(`case ${leafNode.index}: return '${leafNode.name}';\n`); } jsFile.dedent(2); jsFile.write(` }\n}\n\n`); - jsFile.write('exported.kindToString = kindToString;\n\n'); for (const decl of nodeDecls) { - jsFile.write(`exported.is${decl.name} = function (value) {\n`); + jsFile.write(`export function is${decl.name}(value) {\n`); jsFile.indent(); jsFile.write(`if (!isSyntax(value)) {\n return false;\n}\n`); if (isFinalNode(decl.name)) { @@ -99,11 +118,11 @@ export function generateAST(decls: Declaration[]) { jsFile.write(`}\n`); } - jsFile.write(`\nif (typeof module !== 'undefined') {\n module.exports = exported;\n}\n\n`) + jsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'snippets', 'ast-after.js'), 'utf8')); // Write corresponding TypeScript declarations - dtsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'src', 'treegen', 'ast.dts.template'), 'utf8')); + dtsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'snippets', 'ast-before.d.ts'), 'utf8')); dtsFile.write('export class NodeVisitor {\n'); dtsFile.write(' public visit(node: Syntax): void;\n'); @@ -219,6 +238,8 @@ export function generateAST(decls: Declaration[]) { dtsFile.write(`export function is${decl.name}(value: any): value is ${decl.name};\n`); } + dtsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'snippets', 'ast-after.d.ts'), 'utf8')); + return { jsFile: jsFile.currentText, dtsFile: dtsFile.currentText, diff --git a/src/treegen/parser.d.ts b/treegen/src/parser.d.ts similarity index 100% rename from src/treegen/parser.d.ts rename to treegen/src/parser.d.ts diff --git a/src/treegen/parser.pegjs b/treegen/src/parser.pegjs similarity index 100% rename from src/treegen/parser.pegjs rename to treegen/src/parser.pegjs diff --git a/src/treegen/util.ts b/treegen/src/util.ts similarity index 96% rename from src/treegen/util.ts rename to treegen/src/util.ts index fef9b9ea3..ecbd35a7b 100644 --- a/src/treegen/util.ts +++ b/treegen/src/util.ts @@ -5,6 +5,8 @@ export function getFileStem(filepath: string): string { return path.basename(filepath).split('.')[0]; } +export interface MapLike { [key: string]: T } + function isWhiteSpace(ch: string) { return /[\r\t ]/.test(ch); } diff --git a/treegen/tsconfig.json b/treegen/tsconfig.json new file mode 100644 index 000000000..d54bb3257 --- /dev/null +++ b/treegen/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "outDir": "./lib", + "strict": true, + "esModuleInterop": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +}