+22
@@ -0,0 +1,22 @@
|
||||
import { GenerateDtsOptions, Rule } from "@chevrotain/types";
|
||||
import { buildModel } from "./model.js";
|
||||
import { genDts } from "./generate.js";
|
||||
|
||||
const defaultOptions: Required<GenerateDtsOptions> = {
|
||||
includeVisitorInterface: true,
|
||||
visitorInterfaceName: "ICstNodeVisitor",
|
||||
};
|
||||
|
||||
export function generateCstDts(
|
||||
productions: Record<string, Rule>,
|
||||
options?: GenerateDtsOptions,
|
||||
): string {
|
||||
const effectiveOptions = {
|
||||
...defaultOptions,
|
||||
...options,
|
||||
};
|
||||
|
||||
const model = buildModel(productions);
|
||||
|
||||
return genDts(model, effectiveOptions);
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import { GenerateDtsOptions } from "@chevrotain/types";
|
||||
import {
|
||||
CstNodeTypeDefinition,
|
||||
PropertyArrayType,
|
||||
PropertyTypeDefinition,
|
||||
RuleArrayType,
|
||||
TokenArrayType,
|
||||
} from "./model.js";
|
||||
|
||||
export function genDts(
|
||||
model: CstNodeTypeDefinition[],
|
||||
options: Required<GenerateDtsOptions>,
|
||||
): string {
|
||||
let contentParts: string[] = [];
|
||||
|
||||
contentParts = contentParts.concat(
|
||||
`import type { CstNode, ICstVisitor, IToken } from "chevrotain";`,
|
||||
);
|
||||
|
||||
contentParts = contentParts.concat(
|
||||
model.flatMap((node) => genCstNodeTypes(node)),
|
||||
);
|
||||
|
||||
if (options.includeVisitorInterface) {
|
||||
contentParts = contentParts.concat(
|
||||
genVisitor(options.visitorInterfaceName, model),
|
||||
);
|
||||
}
|
||||
|
||||
return contentParts.join("\n\n") + "\n";
|
||||
}
|
||||
|
||||
function genCstNodeTypes(node: CstNodeTypeDefinition) {
|
||||
const nodeCstInterface = genNodeInterface(node);
|
||||
const nodeChildrenInterface = genNodeChildrenType(node);
|
||||
|
||||
return [nodeCstInterface, nodeChildrenInterface];
|
||||
}
|
||||
|
||||
function genNodeInterface(node: CstNodeTypeDefinition) {
|
||||
const nodeInterfaceName = getNodeInterfaceName(node.name);
|
||||
const childrenTypeName = getNodeChildrenTypeName(node.name);
|
||||
|
||||
return `export interface ${nodeInterfaceName} extends CstNode {
|
||||
name: "${node.name}";
|
||||
children: ${childrenTypeName};
|
||||
}`;
|
||||
}
|
||||
|
||||
function genNodeChildrenType(node: CstNodeTypeDefinition) {
|
||||
const typeName = getNodeChildrenTypeName(node.name);
|
||||
|
||||
return `export type ${typeName} = {
|
||||
${node.properties.map((property) => genChildProperty(property)).join("\n ")}
|
||||
};`;
|
||||
}
|
||||
|
||||
function genChildProperty(prop: PropertyTypeDefinition) {
|
||||
const typeName = buildTypeString(prop.type);
|
||||
return `${prop.name}${prop.optional ? "?" : ""}: ${typeName}[];`;
|
||||
}
|
||||
|
||||
function genVisitor(name: string, nodes: CstNodeTypeDefinition[]) {
|
||||
return `export interface ${name}<IN, OUT> extends ICstVisitor<IN, OUT> {
|
||||
${nodes.map((node) => genVisitorFunction(node)).join("\n ")}
|
||||
}`;
|
||||
}
|
||||
|
||||
function genVisitorFunction(node: CstNodeTypeDefinition) {
|
||||
const childrenTypeName = getNodeChildrenTypeName(node.name);
|
||||
return `${node.name}(children: ${childrenTypeName}, param?: IN): OUT;`;
|
||||
}
|
||||
|
||||
function buildTypeString(type: PropertyArrayType) {
|
||||
if (Array.isArray(type)) {
|
||||
const typeNames = [...new Set(type.map((t) => getTypeString(t)))];
|
||||
const typeString = typeNames.join(" | ");
|
||||
return "(" + typeString + ")";
|
||||
} else {
|
||||
return getTypeString(type);
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeString(type: TokenArrayType | RuleArrayType) {
|
||||
if (type.kind === "token") {
|
||||
return "IToken";
|
||||
}
|
||||
return getNodeInterfaceName(type.name);
|
||||
}
|
||||
|
||||
function getNodeInterfaceName(ruleName: string) {
|
||||
return ruleName.charAt(0).toUpperCase() + ruleName.slice(1) + "CstNode";
|
||||
}
|
||||
|
||||
function getNodeChildrenTypeName(ruleName: string) {
|
||||
return ruleName.charAt(0).toUpperCase() + ruleName.slice(1) + "CstChildren";
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
import type {
|
||||
Alternation,
|
||||
Alternative,
|
||||
IProduction,
|
||||
Option,
|
||||
Repetition,
|
||||
RepetitionMandatory,
|
||||
RepetitionMandatoryWithSeparator,
|
||||
RepetitionWithSeparator,
|
||||
Rule,
|
||||
Terminal,
|
||||
TokenType,
|
||||
} from "@chevrotain/types";
|
||||
import { GAstVisitor, NonTerminal } from "@chevrotain/gast";
|
||||
|
||||
export function buildModel(
|
||||
productions: Record<string, Rule>,
|
||||
): CstNodeTypeDefinition[] {
|
||||
const generator = new CstNodeDefinitionGenerator();
|
||||
const allRules = Object.values(productions);
|
||||
return allRules.map((rule) => generator.visitRule(rule));
|
||||
}
|
||||
|
||||
export type CstNodeTypeDefinition = {
|
||||
name: string;
|
||||
properties: PropertyTypeDefinition[];
|
||||
};
|
||||
|
||||
export type PropertyTypeDefinition = {
|
||||
name: string;
|
||||
type: PropertyArrayType;
|
||||
optional: boolean;
|
||||
};
|
||||
|
||||
export type PropertyArrayType =
|
||||
| TokenArrayType
|
||||
| RuleArrayType
|
||||
| (TokenArrayType | RuleArrayType)[];
|
||||
|
||||
export type TokenArrayType = { kind: "token" };
|
||||
export type RuleArrayType = {
|
||||
kind: "rule";
|
||||
name: string;
|
||||
};
|
||||
|
||||
class CstNodeDefinitionGenerator extends GAstVisitor {
|
||||
visitRule(node: Rule): CstNodeTypeDefinition {
|
||||
const rawElements = this.visitEach(node.definition);
|
||||
|
||||
const grouped = Object.groupBy(rawElements, (el) => el.propertyName);
|
||||
const properties = Object.entries(grouped).map(([propertyName, group]) => {
|
||||
const allNullable = !group!.some((el) => !el.canBeNull);
|
||||
|
||||
// In an alternation with a label a property name can have
|
||||
// multiple types.
|
||||
let propertyType: PropertyArrayType = group![0].type;
|
||||
if (group!.length > 1) {
|
||||
propertyType = group!.map((g) => g.type);
|
||||
}
|
||||
|
||||
return {
|
||||
name: propertyName,
|
||||
type: propertyType,
|
||||
optional: allNullable,
|
||||
} as PropertyTypeDefinition;
|
||||
});
|
||||
|
||||
return {
|
||||
name: node.name,
|
||||
properties: properties,
|
||||
};
|
||||
}
|
||||
|
||||
visitAlternative(node: Alternative) {
|
||||
return this.visitEachAndOverrideWith(node.definition, { canBeNull: true });
|
||||
}
|
||||
|
||||
visitOption(node: Option) {
|
||||
return this.visitEachAndOverrideWith(node.definition, { canBeNull: true });
|
||||
}
|
||||
|
||||
visitRepetition(node: Repetition) {
|
||||
return this.visitEachAndOverrideWith(node.definition, { canBeNull: true });
|
||||
}
|
||||
|
||||
visitRepetitionMandatory(node: RepetitionMandatory) {
|
||||
return this.visitEach(node.definition);
|
||||
}
|
||||
|
||||
visitRepetitionMandatoryWithSeparator(
|
||||
node: RepetitionMandatoryWithSeparator,
|
||||
) {
|
||||
return this.visitEach(node.definition).concat({
|
||||
propertyName: node.separator.name,
|
||||
canBeNull: true,
|
||||
type: getType(node.separator),
|
||||
});
|
||||
}
|
||||
|
||||
visitRepetitionWithSeparator(node: RepetitionWithSeparator) {
|
||||
return this.visitEachAndOverrideWith(node.definition, {
|
||||
canBeNull: true,
|
||||
}).concat({
|
||||
propertyName: node.separator.name,
|
||||
canBeNull: true,
|
||||
type: getType(node.separator),
|
||||
});
|
||||
}
|
||||
|
||||
visitAlternation(node: Alternation) {
|
||||
return this.visitEachAndOverrideWith(node.definition, { canBeNull: true });
|
||||
}
|
||||
|
||||
visitTerminal(node: Terminal): PropertyTupleElement[] {
|
||||
return [
|
||||
{
|
||||
propertyName: node.label || node.terminalType.name,
|
||||
canBeNull: false,
|
||||
type: getType(node),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
visitNonTerminal(node: NonTerminal): PropertyTupleElement[] {
|
||||
return [
|
||||
{
|
||||
propertyName: node.label || node.nonTerminalName,
|
||||
canBeNull: false,
|
||||
type: getType(node),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private visitEachAndOverrideWith(
|
||||
definition: IProduction[],
|
||||
override: Partial<PropertyTupleElement>,
|
||||
) {
|
||||
return this.visitEach(definition).map(
|
||||
(definition) => ({ ...definition, ...override }) as PropertyTupleElement,
|
||||
);
|
||||
}
|
||||
|
||||
private visitEach(definition: IProduction[]) {
|
||||
return definition.flatMap(
|
||||
(definition) => this.visit(definition) as PropertyTupleElement[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type PropertyTupleElement = {
|
||||
propertyName: string;
|
||||
canBeNull: boolean;
|
||||
type: TokenArrayType | RuleArrayType;
|
||||
};
|
||||
|
||||
function getType(
|
||||
production: Terminal | NonTerminal | TokenType,
|
||||
): TokenArrayType | RuleArrayType {
|
||||
if (production instanceof NonTerminal) {
|
||||
return {
|
||||
kind: "rule",
|
||||
name: production.referencedRule.name,
|
||||
};
|
||||
}
|
||||
|
||||
return { kind: "token" };
|
||||
}
|
||||
Reference in New Issue
Block a user