mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
416 lines
9.6 KiB
416 lines
9.6 KiB
import { assign, forEach, isRegExp, isString, map, pickBy } from "lodash-es";
|
|
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 isString(obj.LABEL) && 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);
|
|
forEach(this.definition, (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([]);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
|
|
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);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class Alternative extends AbstractProduction {
|
|
public ignoreAmbiguities: boolean = false;
|
|
|
|
constructor(options: {
|
|
definition: IProduction[];
|
|
ignoreAmbiguities?: boolean;
|
|
}) {
|
|
super(options.definition);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
}
|
|
|
|
export class Terminal implements IProductionWithOccurrence {
|
|
public terminalType!: TokenType;
|
|
public label?: string;
|
|
public idx: number = 1;
|
|
|
|
constructor(options: {
|
|
terminalType: TokenType;
|
|
label?: string;
|
|
idx?: number;
|
|
}) {
|
|
assign(
|
|
this,
|
|
pickBy(options, (v) => v !== undefined),
|
|
);
|
|
}
|
|
|
|
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 map(topRules, serializeProduction);
|
|
}
|
|
|
|
export function serializeProduction(node: IProduction): ISerializedGast {
|
|
function convertDefinition(definition: IProduction[]): ISerializedGast[] {
|
|
return map(definition, serializeProduction);
|
|
}
|
|
/* istanbul ignore else */
|
|
if (node instanceof NonTerminal) {
|
|
const serializedNonTerminal: ISerializedNonTerminal = {
|
|
type: "NonTerminal",
|
|
name: node.nonTerminalName,
|
|
idx: node.idx,
|
|
};
|
|
|
|
if (isString(node.label)) {
|
|
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 (isString(node.label)) {
|
|
serializedTerminal.terminalLabel = node.label;
|
|
}
|
|
|
|
const pattern = node.terminalType.PATTERN;
|
|
if (node.terminalType.PATTERN) {
|
|
serializedTerminal.pattern = isRegExp(pattern)
|
|
? (<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");
|
|
}
|
|
}
|
|
|