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
+23
View File
@@ -0,0 +1,23 @@
export {
Rule,
Terminal,
NonTerminal,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
Alternation,
Alternative,
serializeGrammar,
serializeProduction,
} from "./model.js";
export { GAstVisitor } from "./visitor.js";
export {
getProductionDslName,
isOptionalProd,
isBranchingProd,
isSequenceProd,
} from "./helpers.js";
+96
View File
@@ -0,0 +1,96 @@
import {
AbstractProduction,
Alternation,
Alternative,
NonTerminal,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
Rule,
Terminal,
} from "./model.js";
import type { IProduction, IProductionWithOccurrence } from "@chevrotain/types";
export function isSequenceProd(
prod: IProduction,
): prod is { definition: IProduction[] } & IProduction {
return (
prod instanceof Alternative ||
prod instanceof Option ||
prod instanceof Repetition ||
prod instanceof RepetitionMandatory ||
prod instanceof RepetitionMandatoryWithSeparator ||
prod instanceof RepetitionWithSeparator ||
prod instanceof Terminal ||
prod instanceof Rule
);
}
export function isOptionalProd(
prod: IProduction,
alreadyVisited: NonTerminal[] = [],
): boolean {
const isDirectlyOptional =
prod instanceof Option ||
prod instanceof Repetition ||
prod instanceof RepetitionWithSeparator;
if (isDirectlyOptional) {
return true;
}
// note that this can cause infinite loop if one optional empty TOP production has a cyclic dependency with another
// empty optional top rule
// may be indirectly optional ((A?B?C?) | (D?E?F?))
if (prod instanceof Alternation) {
// for OR its enough for just one of the alternatives to be optional
return (<Alternation>prod).definition.some((subProd: IProduction) => {
return isOptionalProd(subProd, alreadyVisited);
});
} else if (prod instanceof NonTerminal && alreadyVisited.includes(prod)) {
// avoiding stack overflow due to infinite recursion
return false;
} else if (prod instanceof AbstractProduction) {
if (prod instanceof NonTerminal) {
alreadyVisited.push(prod);
}
return (<AbstractProduction>prod).definition.every(
(subProd: IProduction) => {
return isOptionalProd(subProd, alreadyVisited);
},
);
} else {
return false;
}
}
export function isBranchingProd(
prod: IProduction,
): prod is { definition: IProduction[] } & IProduction {
return prod instanceof Alternation;
}
export function getProductionDslName(prod: IProductionWithOccurrence): string {
/* istanbul ignore else */
if (prod instanceof NonTerminal) {
return "SUBRULE";
} else if (prod instanceof Option) {
return "OPTION";
} else if (prod instanceof Alternation) {
return "OR";
} else if (prod instanceof RepetitionMandatory) {
return "AT_LEAST_ONE";
} else if (prod instanceof RepetitionMandatoryWithSeparator) {
return "AT_LEAST_ONE_SEP";
} else if (prod instanceof RepetitionWithSeparator) {
return "MANY_SEP";
} else if (prod instanceof Repetition) {
return "MANY";
} else if (prod instanceof Terminal) {
return "CONSUME";
/* c8 ignore next 3 */
} else {
throw Error("non exhaustive match");
}
}
+390
View File
@@ -0,0 +1,390 @@
import type {
IGASTVisitor,
IProduction,
IProductionWithOccurrence,
ISerializedGast,
TokenType,
} from "@chevrotain/types";
// TODO: duplicated code to avoid extracting another sub-package -- how to avoid?
function tokenLabel(tokType: TokenType): string {
if (hasTokenLabel(tokType)) {
return tokType.LABEL;
} else {
return tokType.name;
}
}
// TODO: duplicated code to avoid extracting another sub-package -- how to avoid?
function hasTokenLabel(
obj: TokenType,
): obj is TokenType & Pick<Required<TokenType>, "LABEL"> {
return typeof obj.LABEL === "string" && obj.LABEL !== "";
}
export abstract class AbstractProduction<
T extends IProduction = IProduction,
> implements IProduction {
public get definition(): T[] {
return this._definition;
}
public set definition(value: T[]) {
this._definition = value;
}
constructor(protected _definition: T[]) {}
accept(visitor: IGASTVisitor): void {
visitor.visit(this);
this.definition.forEach((prod) => {
prod.accept(visitor);
});
}
}
export class NonTerminal
extends AbstractProduction
implements IProductionWithOccurrence
{
public nonTerminalName!: string;
public label?: string;
public referencedRule!: Rule;
public idx: number = 1;
constructor(options: {
nonTerminalName: string;
label?: string;
referencedRule?: Rule;
idx?: number;
}) {
super([]);
Object.assign(this, pickOnlyDefined(options));
}
set definition(definition: IProduction[]) {
// immutable
}
get definition(): IProduction[] {
if (this.referencedRule !== undefined) {
return this.referencedRule.definition;
}
return [];
}
accept(visitor: IGASTVisitor): void {
visitor.visit(this);
// don't visit children of a reference, we will get cyclic infinite loops if we do so
}
}
export class Rule extends AbstractProduction {
public name!: string;
public orgText: string = "";
constructor(options: {
name: string;
definition: IProduction[];
orgText?: string;
}) {
super(options.definition);
Object.assign(this, pickOnlyDefined(options));
}
}
export class Alternative extends AbstractProduction {
public ignoreAmbiguities: boolean = false;
constructor(options: {
definition: IProduction[];
ignoreAmbiguities?: boolean;
}) {
super(options.definition);
Object.assign(this, pickOnlyDefined(options));
}
}
export class Option
extends AbstractProduction
implements IProductionWithOccurrence
{
public idx: number = 1;
public maxLookahead?: number;
constructor(options: {
definition: IProduction[];
idx?: number;
maxLookahead?: number;
}) {
super(options.definition);
Object.assign(this, pickOnlyDefined(options));
}
}
export class RepetitionMandatory
extends AbstractProduction
implements IProductionWithOccurrence
{
public idx: number = 1;
public maxLookahead?: number;
constructor(options: {
definition: IProduction[];
idx?: number;
maxLookahead?: number;
}) {
super(options.definition);
Object.assign(this, pickOnlyDefined(options));
}
}
export class RepetitionMandatoryWithSeparator
extends AbstractProduction
implements IProductionWithOccurrence
{
public separator!: TokenType;
public idx: number = 1;
public maxLookahead?: number;
constructor(options: {
definition: IProduction[];
separator: TokenType;
idx?: number;
}) {
super(options.definition);
Object.assign(this, pickOnlyDefined(options));
}
}
export class Repetition
extends AbstractProduction
implements IProductionWithOccurrence
{
public separator!: TokenType;
public idx: number = 1;
public maxLookahead?: number;
constructor(options: {
definition: IProduction[];
idx?: number;
maxLookahead?: number;
}) {
super(options.definition);
Object.assign(this, pickOnlyDefined(options));
}
}
export class RepetitionWithSeparator
extends AbstractProduction
implements IProductionWithOccurrence
{
public separator!: TokenType;
public idx: number = 1;
public maxLookahead?: number;
constructor(options: {
definition: IProduction[];
separator: TokenType;
idx?: number;
}) {
super(options.definition);
Object.assign(this, pickOnlyDefined(options));
}
}
export class Alternation
extends AbstractProduction<Alternative>
implements IProductionWithOccurrence
{
public idx: number = 1;
public ignoreAmbiguities: boolean = false;
public hasPredicates: boolean = false;
public maxLookahead?: number;
public get definition(): Alternative[] {
return this._definition;
}
public set definition(value: Alternative[]) {
this._definition = value;
}
constructor(options: {
definition: Alternative[];
idx?: number;
ignoreAmbiguities?: boolean;
hasPredicates?: boolean;
maxLookahead?: number;
}) {
super(options.definition);
Object.assign(this, pickOnlyDefined(options));
}
}
export class Terminal implements IProductionWithOccurrence {
public terminalType!: TokenType;
public label?: string;
public idx: number = 1;
constructor(options: {
terminalType: TokenType;
label?: string;
idx?: number;
}) {
Object.assign(this, pickOnlyDefined(options));
}
accept(visitor: IGASTVisitor): void {
visitor.visit(this);
}
}
export interface ISerializedBasic extends ISerializedGast {
type:
| "Alternative"
| "Option"
| "RepetitionMandatory"
| "Repetition"
| "Alternation";
idx?: number;
}
export interface ISerializedGastRule extends ISerializedGast {
type: "Rule";
name: string;
orgText: string;
}
export interface ISerializedNonTerminal extends ISerializedGast {
type: "NonTerminal";
name: string;
label?: string;
idx: number;
}
export interface ISerializedTerminal extends ISerializedGast {
type: "Terminal";
name: string;
terminalLabel?: string;
label?: string;
pattern?: string;
idx: number;
}
export interface ISerializedTerminalWithSeparator extends ISerializedGast {
type: "RepetitionMandatoryWithSeparator" | "RepetitionWithSeparator";
idx: number;
separator: ISerializedTerminal;
}
export type ISerializedGastAny =
| ISerializedBasic
| ISerializedGastRule
| ISerializedNonTerminal
| ISerializedTerminal
| ISerializedTerminalWithSeparator;
export function serializeGrammar(topRules: Rule[]): ISerializedGast[] {
return topRules.map(serializeProduction);
}
export function serializeProduction(node: IProduction): ISerializedGast {
function convertDefinition(definition: IProduction[]): ISerializedGast[] {
return definition.map(serializeProduction);
}
/* istanbul ignore else */
if (node instanceof NonTerminal) {
const serializedNonTerminal: ISerializedNonTerminal = {
type: "NonTerminal",
name: node.nonTerminalName,
idx: node.idx,
};
if (typeof node.label === "string") {
serializedNonTerminal.label = node.label;
}
return serializedNonTerminal;
} else if (node instanceof Alternative) {
return <ISerializedBasic>{
type: "Alternative",
definition: convertDefinition(node.definition),
};
} else if (node instanceof Option) {
return <ISerializedBasic>{
type: "Option",
idx: node.idx,
definition: convertDefinition(node.definition),
};
} else if (node instanceof RepetitionMandatory) {
return <ISerializedBasic>{
type: "RepetitionMandatory",
idx: node.idx,
definition: convertDefinition(node.definition),
};
} else if (node instanceof RepetitionMandatoryWithSeparator) {
return <ISerializedTerminalWithSeparator>{
type: "RepetitionMandatoryWithSeparator",
idx: node.idx,
separator: <ISerializedTerminal>(
serializeProduction(new Terminal({ terminalType: node.separator }))
),
definition: convertDefinition(node.definition),
};
} else if (node instanceof RepetitionWithSeparator) {
return <ISerializedTerminalWithSeparator>{
type: "RepetitionWithSeparator",
idx: node.idx,
separator: <ISerializedTerminal>(
serializeProduction(new Terminal({ terminalType: node.separator }))
),
definition: convertDefinition(node.definition),
};
} else if (node instanceof Repetition) {
return <ISerializedBasic>{
type: "Repetition",
idx: node.idx,
definition: convertDefinition(node.definition),
};
} else if (node instanceof Alternation) {
return <ISerializedBasic>{
type: "Alternation",
idx: node.idx,
definition: convertDefinition(node.definition),
};
} else if (node instanceof Terminal) {
const serializedTerminal = <ISerializedTerminal>{
type: "Terminal",
name: node.terminalType.name,
label: tokenLabel(node.terminalType),
idx: node.idx,
};
if (typeof node.label === "string") {
serializedTerminal.terminalLabel = node.label;
}
const pattern = node.terminalType.PATTERN;
if (node.terminalType.PATTERN) {
serializedTerminal.pattern =
pattern instanceof RegExp ? (<any>pattern).source : pattern;
}
return serializedTerminal;
} else if (node instanceof Rule) {
return <ISerializedGastRule>{
type: "Rule",
name: node.name,
orgText: node.orgText,
definition: convertDefinition(node.definition),
};
/* c8 ignore next 3 */
} else {
throw Error("non exhaustive match");
}
}
function pickOnlyDefined(obj: Object): any {
return Object.fromEntries(
Object.entries(obj).filter(([, v]) => v !== undefined),
);
}
+76
View File
@@ -0,0 +1,76 @@
import {
Alternation,
Alternative,
NonTerminal,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
Rule,
Terminal,
} from "./model.js";
import type { IProduction } from "@chevrotain/types";
export abstract class GAstVisitor {
public visit(node: IProduction): any {
const nodeAny: any = node;
switch (nodeAny.constructor) {
case NonTerminal:
return this.visitNonTerminal(nodeAny);
case Alternative:
return this.visitAlternative(nodeAny);
case Option:
return this.visitOption(nodeAny);
case RepetitionMandatory:
return this.visitRepetitionMandatory(nodeAny);
case RepetitionMandatoryWithSeparator:
return this.visitRepetitionMandatoryWithSeparator(nodeAny);
case RepetitionWithSeparator:
return this.visitRepetitionWithSeparator(nodeAny);
case Repetition:
return this.visitRepetition(nodeAny);
case Alternation:
return this.visitAlternation(nodeAny);
case Terminal:
return this.visitTerminal(nodeAny);
case Rule:
return this.visitRule(nodeAny);
/* c8 ignore next 2 */
default:
throw Error("non exhaustive match");
}
}
/* c8 ignore next */
public visitNonTerminal(node: NonTerminal): any {}
/* c8 ignore next */
public visitAlternative(node: Alternative): any {}
/* c8 ignore next */
public visitOption(node: Option): any {}
/* c8 ignore next */
public visitRepetition(node: Repetition): any {}
/* c8 ignore next */
public visitRepetitionMandatory(node: RepetitionMandatory): any {}
/* c8 ignore next 3 */
public visitRepetitionMandatoryWithSeparator(
node: RepetitionMandatoryWithSeparator,
): any {}
/* c8 ignore next */
public visitRepetitionWithSeparator(node: RepetitionWithSeparator): any {}
/* c8 ignore next */
public visitAlternation(node: Alternation): any {}
/* c8 ignore next */
public visitTerminal(node: Terminal): any {}
/* c8 ignore next */
public visitRule(node: Rule): any {}
}