First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+22
View File
@@ -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
View File
@@ -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
View File
@@ -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" };
}