2020-05-10 15:56:34 +02:00
|
|
|
|
|
|
|
import * as fs from "fs"
|
|
|
|
import * as path from "path"
|
|
|
|
|
|
|
|
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
const CUSTOM_TYPES = ['Package', 'BoltValue', 'JSValue'];
|
2020-05-25 11:29:19 +02:00
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
import { Syntax, Declaration, NodeDeclaration, TypeDeclaration, EnumDeclaration, TypeNode, NodeField } from "./ast"
|
2020-05-26 16:55:41 +02:00
|
|
|
import { MapLike, assert } from "../util"
|
2020-05-10 15:56:34 +02:00
|
|
|
import { FileWriter } from "./util"
|
|
|
|
|
|
|
|
export function generateAST(decls: Declaration[]) {
|
|
|
|
|
|
|
|
let jsFile = new FileWriter();
|
|
|
|
let dtsFile = new FileWriter();
|
|
|
|
let i;
|
|
|
|
|
|
|
|
// Sort declarations by category
|
|
|
|
|
|
|
|
const nodeDecls: NodeDeclaration[] = decls.filter(decl => decl.type === 'NodeDeclaration') as NodeDeclaration[];
|
|
|
|
const typeDecls: TypeDeclaration[] = decls.filter(decl => decl.type === 'TypeDeclaration') as TypeDeclaration[];
|
|
|
|
const enumDecls: EnumDeclaration[] = decls.filter(decl => decl.type === 'EnumDeclaration') as EnumDeclaration[];
|
|
|
|
|
2020-05-24 21:50:29 +02:00
|
|
|
const declByName: MapLike<Declaration> = Object.create(null);
|
2020-05-10 15:56:34 +02:00
|
|
|
i = 0;
|
|
|
|
for (const decl of decls) {
|
|
|
|
decl.index = i++;
|
|
|
|
declByName[decl.name] = decl;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a mapping from parent node to child node
|
|
|
|
// This makes it easy to generate union types for the intermediate nodes.
|
|
|
|
|
2020-05-24 21:50:29 +02:00
|
|
|
const childrenOf: MapLike<string[]> = Object.create(null);
|
2020-05-10 15:56:34 +02:00
|
|
|
for (const nodeDecl of nodeDecls) {
|
|
|
|
for (const parentName of nodeDecl.parents) {
|
|
|
|
if (childrenOf[parentName] === undefined) {
|
|
|
|
childrenOf[parentName] = [];
|
|
|
|
}
|
|
|
|
childrenOf[parentName].push(nodeDecl.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// After we're done mappping parents to children, we can use isLeafNode()
|
|
|
|
// to store the nodes we will be iterating most frequently on.
|
|
|
|
|
2020-05-26 20:59:44 +02:00
|
|
|
const finalNodes: NodeDeclaration[] = nodeDecls.filter(decl => isFinalNode(decl.name));
|
2020-05-10 15:56:34 +02:00
|
|
|
|
|
|
|
// Write a JavaScript file that contains all AST definitions.
|
|
|
|
|
|
|
|
jsFile.write(`\nconst NODE_TYPES = {\n`);
|
|
|
|
jsFile.indent();
|
2020-05-26 16:55:41 +02:00
|
|
|
for (const decl of finalNodes) {
|
2020-05-26 20:59:44 +02:00
|
|
|
if (decl.type === 'NodeDeclaration' && isFinalNode(decl.name)) {
|
2020-05-10 15:56:34 +02:00
|
|
|
jsFile.write(`'${decl.name}': {\n`);
|
|
|
|
jsFile.indent();
|
|
|
|
jsFile.write(`index: ${decl.index},\n`);
|
2020-05-28 14:08:49 +02:00
|
|
|
jsFile.write(`parents: [`);
|
|
|
|
for (const parentName of getParentChain(decl.name)) {
|
2020-05-29 18:44:58 +02:00
|
|
|
jsFile.write(`'${parentName}', `)
|
2020-05-28 14:08:49 +02:00
|
|
|
}
|
|
|
|
jsFile.write(`'Syntax'],\n`);
|
2020-05-10 15:56:34 +02:00
|
|
|
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.dedent();
|
|
|
|
jsFile.write('};\n\n');
|
|
|
|
|
|
|
|
jsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'src', 'treegen', 'ast-template.js'), 'utf8'));
|
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
jsFile.write(`function kindToString (kind) {\n switch (kind) {\n`);
|
2020-05-10 15:56:34 +02:00
|
|
|
jsFile.indent(2);
|
2020-05-26 16:55:41 +02:00
|
|
|
for (const leafNode of finalNodes) {
|
2020-05-10 15:56:34 +02:00
|
|
|
jsFile.write(`case ${leafNode.index}: return '${leafNode.name}';\n`);
|
|
|
|
}
|
|
|
|
jsFile.dedent(2);
|
|
|
|
jsFile.write(` }\n}\n\n`);
|
2020-05-26 16:55:41 +02:00
|
|
|
jsFile.write('exported.kindToString = kindToString;\n\n');
|
2020-05-10 15:56:34 +02:00
|
|
|
|
|
|
|
for (const decl of nodeDecls) {
|
|
|
|
jsFile.write(`exported.is${decl.name} = function (value) {\n`);
|
|
|
|
jsFile.indent();
|
|
|
|
jsFile.write(`if (!isSyntax(value)) {\n return false;\n}\n`);
|
2020-05-26 20:59:44 +02:00
|
|
|
if (isFinalNode(decl.name)) {
|
2020-05-10 15:56:34 +02:00
|
|
|
jsFile.write(` return value.kind === ${decl.index};\n`);
|
|
|
|
} else {
|
2020-05-26 16:55:41 +02:00
|
|
|
jsFile.write('return ' + [...getFinalNodes(decl.name)].map(d => `value.kind === ${getDeclarationNamed(d).index}`).join(' || ') + '\n');
|
2020-05-10 15:56:34 +02:00
|
|
|
}
|
|
|
|
jsFile.dedent();
|
|
|
|
jsFile.write(`}\n`);
|
|
|
|
}
|
|
|
|
|
|
|
|
jsFile.write(`\nif (typeof module !== 'undefined') {\n module.exports = exported;\n}\n\n`)
|
|
|
|
|
|
|
|
// Write corresponding TypeScript declarations
|
|
|
|
|
2020-05-25 15:52:11 +02:00
|
|
|
dtsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'src', 'treegen', 'ast.dts.template'), 'utf8'));
|
2020-05-25 11:29:19 +02:00
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
dtsFile.write('export class NodeVisitor {\n');
|
|
|
|
dtsFile.write(' public visit(node: Syntax): void;\n');
|
|
|
|
for (const decl of finalNodes) {
|
2020-05-26 20:59:44 +02:00
|
|
|
dtsFile.write(` protected visit${decl.name}?(node: ${decl.name}): void;\n`);
|
2020-05-26 16:55:41 +02:00
|
|
|
}
|
|
|
|
dtsFile.write('}\n\n');
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
dtsFile.write(`\nexport const enum SyntaxKind {\n`);
|
2020-05-26 16:55:41 +02:00
|
|
|
for (const decl of finalNodes) {
|
2020-05-10 15:56:34 +02:00
|
|
|
dtsFile.write(` ${decl.name} = ${decl.index},\n`);
|
|
|
|
}
|
|
|
|
dtsFile.write(`}\n\n`);
|
|
|
|
|
|
|
|
for (const decl of decls) {
|
|
|
|
if (decl.type === 'NodeDeclaration') {
|
2020-05-26 20:59:44 +02:00
|
|
|
if (isFinalNode(decl.name)) {
|
|
|
|
dtsFile.write(`export interface ${decl.name} extends SyntaxBase {\n`)
|
2020-05-10 15:56:34 +02:00
|
|
|
dtsFile.indent()
|
|
|
|
dtsFile.write(`kind: SyntaxKind.${decl.name};\n`);
|
|
|
|
for (const field of getAllFields(decl)) {
|
|
|
|
dtsFile.write(`${field.name}: ${emitTypeScriptType(field.typeNode)};\n`);
|
|
|
|
}
|
2020-05-26 16:55:41 +02:00
|
|
|
dtsFile.write(`parentNode: ${decl.name}Parent;\n`);
|
|
|
|
dtsFile.write(`getChildNodes(): IterableIterator<${decl.name}Child>\n`)
|
2020-05-10 15:56:34 +02:00
|
|
|
dtsFile.dedent();
|
|
|
|
dtsFile.write(`}\n\n`);
|
2020-05-26 16:55:41 +02:00
|
|
|
{
|
|
|
|
dtsFile.write(`export type ${decl.name}Parent\n`)
|
|
|
|
let first = true;
|
|
|
|
for (const parentName of uniq(getNodesReferencingNode(decl.name))) {
|
|
|
|
dtsFile.write((first ? '=' : '|') + ' ' + parentName + '\n');
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
dtsFile.write((first ? '=' : '|') + ' never\n\n');
|
|
|
|
}
|
|
|
|
{
|
|
|
|
dtsFile.write(`export type ${decl.name}AnyParent\n`)
|
|
|
|
let first = true;
|
|
|
|
for (const parentDecl of uniq(getNodesTransitivelyReferencingNode(decl))) {
|
|
|
|
dtsFile.write((first ? '=' : '|') + ' ' + parentDecl + '\n');
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
dtsFile.write((first ? '=' : '|') + ' never\n\n');
|
|
|
|
}
|
|
|
|
{
|
|
|
|
dtsFile.write(`export type ${decl.name}Child\n`)
|
|
|
|
let first = true;
|
|
|
|
for (const childDecl of uniq(getFinalNodes(decl.name))) {
|
|
|
|
dtsFile.write((first ? '=' : '|') + ' ' + childDecl + '\n');
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
dtsFile.write((first ? '=' : '|') + ' never\n\n');
|
|
|
|
}
|
2020-05-10 15:56:34 +02:00
|
|
|
} else {
|
|
|
|
dtsFile.write(`export type ${decl.name}\n`);
|
|
|
|
let first = true;
|
|
|
|
dtsFile.indent();
|
2020-05-26 16:55:41 +02:00
|
|
|
for (const childDecl of uniq(getFinalNodes(decl.name))) {
|
|
|
|
dtsFile.write((first ? '=' : '|') + ' ' + childDecl + '\n');
|
2020-05-10 15:56:34 +02:00
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
dtsFile.dedent();
|
|
|
|
dtsFile.write('\n\n');
|
|
|
|
}
|
|
|
|
} else if (decl.type === 'EnumDeclaration') {
|
|
|
|
dtsFile.write(`export const enum ${decl.name} {\n`);
|
|
|
|
dtsFile.indent();
|
|
|
|
for (const field of decl.fields) {
|
|
|
|
dtsFile.write(`${field.name} = ${field.value},`);
|
|
|
|
}
|
|
|
|
dtsFile.dedent();
|
|
|
|
dtsFile.write('}\n\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 14:18:20 +02:00
|
|
|
//dtsFile.write('export type ResolveSyntaxKind<K extends SyntaxKind>\n');
|
|
|
|
//{
|
|
|
|
//let i = 0;
|
|
|
|
//for (const decl of leafNodes) {
|
|
|
|
//dtsFile.write(i === 0 ? ' =' : ' |');
|
|
|
|
//dtsFile.write(` K extends SyntaxKind.${decl.name} ? ${decl.name}`);
|
|
|
|
//dtsFile.write(' :');
|
|
|
|
//dtsFile.write('\n');
|
|
|
|
//i++;
|
|
|
|
//}
|
|
|
|
//dtsFile.write(' never\n\n');
|
|
|
|
//}
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
dtsFile.write(`export type Syntax\n`);
|
|
|
|
let first = true;
|
|
|
|
dtsFile.indent();
|
2020-05-26 16:55:41 +02:00
|
|
|
for (const decl of finalNodes) {
|
2020-05-10 15:56:34 +02:00
|
|
|
dtsFile.write((first ? '=' : '|') + ' ' + decl.name + '\n');
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
dtsFile.dedent();
|
|
|
|
dtsFile.write('\n\n');
|
|
|
|
|
|
|
|
dtsFile.write('export function kindToString(kind: SyntaxKind): string;\n\n');
|
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
for (const decl of finalNodes) {
|
2020-05-10 15:56:34 +02:00
|
|
|
dtsFile.write(`export function create${decl.name}(`);
|
|
|
|
for (const field of getAllFields(decl)) {
|
|
|
|
dtsFile.write(`${field.name}: ${emitTypeScriptType(field.typeNode)}, `);
|
|
|
|
}
|
2020-05-10 18:21:44 +02:00
|
|
|
dtsFile.write(`span?: TextSpan | null): ${decl.name};\n`);
|
2020-05-10 15:56:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
dtsFile.write('\n');
|
|
|
|
|
|
|
|
for (const decl of nodeDecls) {
|
|
|
|
dtsFile.write(`export function is${decl.name}(value: any): value is ${decl.name};\n`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
jsFile: jsFile.currentText,
|
|
|
|
dtsFile: dtsFile.currentText,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Below are some useful functions
|
|
|
|
|
|
|
|
function hasDeclarationNamed(name: string): boolean {
|
|
|
|
return name in declByName;
|
|
|
|
}
|
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
function* getNodesTransitivelyReferencingNode(node: NodeDeclaration): IterableIterator<string> {
|
|
|
|
const visited = new Set();
|
|
|
|
const stack = [ node.name ];
|
|
|
|
while (stack.length > 0) {
|
|
|
|
const nodeName = stack.pop()!;
|
|
|
|
visited.add(nodeName);
|
|
|
|
for (const parentName of getNodesReferencingNode(nodeName)) {
|
|
|
|
if (!visited.has(parentName)) {
|
|
|
|
yield parentName;
|
|
|
|
stack.push(parentName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function containsDeclarationInInheritanceChain(rootName: string, name: string): boolean {
|
|
|
|
const decl = getDeclarationNamed(name);
|
|
|
|
if (decl.type === 'NodeDeclaration') {
|
|
|
|
for (const childName of getNodesDirectlyInheritingFrom(rootName)) {
|
|
|
|
if (childName === name) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (containsDeclarationInInheritanceChain(childName, name)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
throw new Error(`Checking membership of other types of declarations is not supported.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function* getNodesReferencingNode(name: string): IterableIterator<string> {
|
|
|
|
const decl = getDeclarationNamed(name) as NodeDeclaration;
|
|
|
|
for (const parentNode of finalNodes) {
|
|
|
|
inner: for (const field of getAllFields(parentNode)) {
|
|
|
|
if (typeReferencesDeclarationNamed(field.typeNode, name)) {
|
|
|
|
yield parentNode.name;
|
|
|
|
break inner;
|
|
|
|
}
|
|
|
|
for (const upperNodeName of getNodesDirectlyInheritingFrom(name)) {
|
|
|
|
if (containsDeclarationInInheritanceChain(upperNodeName, name)) {
|
|
|
|
yield parentNode.name;
|
|
|
|
break inner;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function typeReferencesDeclarationNamed(type: TypeNode, name: string): boolean {
|
|
|
|
for (const declName of getAllDeclarationsInType(type)) {
|
|
|
|
const decl = getDeclarationNamed(declName);
|
|
|
|
if (decl.type === 'NodeDeclaration') {
|
|
|
|
return containsDeclarationInInheritanceChain(decl.name, name)
|
|
|
|
} else if (decl.type === 'TypeDeclaration') {
|
|
|
|
return typeReferencesDeclarationNamed(decl.typeNode, name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function* getAllDeclarationsInType(typeNode: TypeNode): IterableIterator<string> {
|
|
|
|
if (typeNode.type === 'ReferenceTypeNode') {
|
|
|
|
if (typeNode.typeArgs === null) {
|
|
|
|
if (hasDeclarationNamed(typeNode.name)) {
|
|
|
|
yield typeNode.name;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (const arg of typeNode.typeArgs) {
|
|
|
|
yield* getAllDeclarationsInType(arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (typeNode.type === 'UnionTypeNode') {
|
|
|
|
for (const element of typeNode.elements) {
|
|
|
|
yield* getAllDeclarationsInType(element);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error(`Could not infer declarations inside ${typeNode}.`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function isDeclarationInType(typeNode: TypeNode, declName: string): boolean {
|
|
|
|
if (typeNode.type === 'ReferenceTypeNode') {
|
|
|
|
if (typeNode.typeArgs === null) {
|
|
|
|
return typeNode.name === declName;
|
|
|
|
}
|
|
|
|
return typeNode.typeArgs.some(arg => isDeclarationInType(arg, declName));
|
|
|
|
} else if (typeNode.type === 'UnionTypeNode') {
|
|
|
|
return typeNode.elements.some(el => isDeclarationInType(el, declName));
|
|
|
|
}
|
|
|
|
throw new Error(`Could not infer whether declaration ${declName} occurs in the given type node.`)
|
|
|
|
}
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
function emitTypeScriptType(typeNode: TypeNode): string {
|
|
|
|
if (typeNode.type === 'ReferenceTypeNode') {
|
|
|
|
if (hasDeclarationNamed(typeNode.name)) {
|
|
|
|
return typeNode.name;
|
|
|
|
} else if (typeNode.name === 'Option') {
|
|
|
|
return `${emitTypeScriptType(typeNode.typeArgs[0])} | null`;
|
|
|
|
} else if (typeNode.name === 'Vec') {
|
|
|
|
return `${emitTypeScriptType(typeNode.typeArgs[0])}[]`;
|
2020-05-25 11:29:19 +02:00
|
|
|
} else if (CUSTOM_TYPES.indexOf(typeNode.name) !== -1) {
|
|
|
|
return typeNode.name;
|
2020-05-10 15:56:34 +02:00
|
|
|
} else if (typeNode.name === 'String') {
|
|
|
|
return `string`;
|
|
|
|
} else if (typeNode.name === 'Int') {
|
|
|
|
return `bigint`;
|
|
|
|
} else if (typeNode.name === 'usize') {
|
|
|
|
return `number`;
|
|
|
|
} else if (typeNode.name === 'bool') {
|
|
|
|
return `boolean`;
|
|
|
|
} else {
|
|
|
|
throw new Error(`Could not emit TypeScript type for reference type node named ${typeNode.name}`);
|
|
|
|
}
|
|
|
|
} else if (typeNode.type === 'UnionTypeNode') {
|
2020-05-27 19:57:15 +02:00
|
|
|
return '(' + typeNode.elements.map(emitTypeScriptType).join(' | ') + ')';
|
2020-05-10 15:56:34 +02:00
|
|
|
}
|
|
|
|
throw new Error(`Could not emit TypeScript type for type node ${typeNode}`);
|
|
|
|
}
|
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
function getNodesDirectlyInheritingFrom(declName: string) {
|
|
|
|
const children = childrenOf[declName];
|
|
|
|
if (children === undefined) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
return children;
|
|
|
|
}
|
2020-05-10 15:56:34 +02:00
|
|
|
|
2020-05-29 18:44:58 +02:00
|
|
|
function *getParentChain(nodeName: string): IterableIterator<string> {
|
2020-05-28 14:08:49 +02:00
|
|
|
const stack = [ nodeName ];
|
|
|
|
while (stack.length > 0) {
|
|
|
|
const nodeDecl = getDeclarationNamed(stack.pop()!) as NodeDeclaration;
|
|
|
|
for (const parentName of nodeDecl.parents) {
|
|
|
|
yield parentName;
|
|
|
|
stack.push(parentName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
function* getFinalNodes(declName: string): IterableIterator<string> {
|
|
|
|
const stack = [ declName ];
|
|
|
|
while (stack.length > 0) {
|
|
|
|
const nodeName = stack.pop()!;
|
|
|
|
for (const childName of getNodesDirectlyInheritingFrom(nodeName)) {
|
|
|
|
//const childDecl = getDeclarationNamed(childName)
|
|
|
|
//if (childDecl.type !== 'NodeDeclaration') {
|
|
|
|
// throw new Error(`Node ${declName} has a child named '${childDecl.name}' that is not a node.`);
|
|
|
|
//}
|
2020-05-26 20:59:44 +02:00
|
|
|
if (isFinalNode(childName)) {
|
2020-05-26 16:55:41 +02:00
|
|
|
yield childName;
|
|
|
|
} else {
|
|
|
|
stack.push(childName);
|
2020-05-10 15:56:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
function* getAllFields(nodeDecl: NodeDeclaration): IterableIterator<NodeField> {
|
2020-05-26 20:59:44 +02:00
|
|
|
yield* nodeDecl.fields;
|
|
|
|
if (isFinalNode(nodeDecl.name)) {
|
|
|
|
for (const parentName of nodeDecl.parents) {
|
|
|
|
const parentDecl = getDeclarationNamed(parentName);
|
|
|
|
if (parentDecl.type !== 'NodeDeclaration') {
|
|
|
|
throw new Error(`Parent declaration '${parentName}' of '${nodeDecl.name}' must be a node declaration.`);
|
|
|
|
}
|
|
|
|
yield* parentDecl.fields;
|
|
|
|
}
|
2020-05-26 16:55:41 +02:00
|
|
|
} else {
|
|
|
|
for (const nodeName of getFinalNodes(nodeDecl.name)) {
|
|
|
|
yield* getAllFields(getDeclarationNamed(nodeName) as NodeDeclaration);
|
2020-05-10 15:56:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDeclarationNamed(name: string): Declaration {
|
|
|
|
const decl = declByName[name];
|
|
|
|
if (decl === undefined) {
|
|
|
|
throw new Error(`Declaration '${name}' was not found in any of the definition files.`);
|
|
|
|
}
|
|
|
|
return decl;
|
|
|
|
}
|
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
function hasChildren(name: string): boolean {
|
|
|
|
return childrenOf[name] !== undefined && childrenOf[name].length !== 0;
|
|
|
|
}
|
|
|
|
|
2020-05-26 20:59:44 +02:00
|
|
|
function isFinalNode(name: string): boolean {
|
2020-05-10 15:56:34 +02:00
|
|
|
const decl = getDeclarationNamed(name);
|
|
|
|
if (decl.type !== 'NodeDeclaration') {
|
|
|
|
return false;
|
|
|
|
}
|
2020-05-26 16:55:41 +02:00
|
|
|
return !hasChildren(name);
|
2020-05-10 15:56:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function pushAll<T>(arr: T[], els: T[]): void {
|
|
|
|
for (const el of els) {
|
|
|
|
arr.push(el);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function isNode(value: any): value is Syntax {
|
|
|
|
return typeof value === 'object' && value !== null && value.__IS_NODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
function jsonify(value: any) {
|
|
|
|
|
|
|
|
function visitNode(node: any) {
|
|
|
|
|
|
|
|
const obj: any = {};
|
|
|
|
|
|
|
|
for (const key of Object.keys(node)) {
|
|
|
|
if (key !== 'type' && key !== 'span' && key !== '__IS_NODE') {
|
|
|
|
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 (isNode(value)) {
|
|
|
|
return visitNode(value);
|
|
|
|
} else {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return visit(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
function stripSuffix(str: string, suffix: string): string {
|
|
|
|
if (!str.endsWith(suffix)) {
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
return str.substring(0, str.length-suffix.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getFileStem(filepath: string) {
|
|
|
|
return path.basename(filepath).split('.')[0];
|
|
|
|
}
|
|
|
|
|
2020-05-26 16:55:41 +02:00
|
|
|
function uniq<T>(elements: Iterable<T>): T[] {
|
|
|
|
const out: T[] = [];
|
|
|
|
const visited = new Set<T>();
|
|
|
|
for (const element of elements) {
|
|
|
|
if (visited.has(element)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
visited.add(element);
|
|
|
|
out.push(element);
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|