LarpixClient/node_modules/chevrotain/src/parse/grammar/gast/gast.ts
2026-05-03 17:09:55 +02:00

179 lines
5.1 KiB
TypeScript

import { contains, every, has, some } from "../../../utils/utils"
import {
AbstractProduction,
Alternation,
Alternative,
NonTerminal,
Option,
Repetition,
RepetitionMandatory,
RepetitionMandatoryWithSeparator,
RepetitionWithSeparator,
Rule,
Terminal
} from "./gast_public"
import { GAstVisitor } from "./gast_visitor_public"
import { IProduction, IProductionWithOccurrence } from "../../../../api"
export function isSequenceProd(prod: IProduction): boolean {
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 {
let 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 some((<Alternation>prod).definition, (subProd: IProduction) => {
return isOptionalProd(subProd, alreadyVisited)
})
} else if (prod instanceof NonTerminal && contains(alreadyVisited, prod)) {
// avoiding stack overflow due to infinite recursion
return false
} else if (prod instanceof AbstractProduction) {
if (prod instanceof NonTerminal) {
alreadyVisited.push(prod)
}
return every(
(<AbstractProduction>prod).definition,
(subProd: IProduction) => {
return isOptionalProd(subProd, alreadyVisited)
}
)
} else {
return false
}
}
export function isBranchingProd(prod: IProduction): boolean {
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"
} else {
throw Error("non exhaustive match")
}
}
export class DslMethodsCollectorVisitor extends GAstVisitor {
// A minus is never valid in an identifier name
public separator = "-"
public dslMethods = {
option: [],
alternation: [],
repetition: [],
repetitionWithSeparator: [],
repetitionMandatory: [],
repetitionMandatoryWithSeparator: []
}
reset() {
this.dslMethods = {
option: [],
alternation: [],
repetition: [],
repetitionWithSeparator: [],
repetitionMandatory: [],
repetitionMandatoryWithSeparator: []
}
}
public visitTerminal(terminal: Terminal): void {
const key = terminal.terminalType.name + this.separator + "Terminal"
if (!has(this.dslMethods, key)) {
this.dslMethods[key] = []
}
this.dslMethods[key].push(terminal)
}
public visitNonTerminal(subrule: NonTerminal): void {
const key = subrule.nonTerminalName + this.separator + "Terminal"
if (!has(this.dslMethods, key)) {
this.dslMethods[key] = []
}
this.dslMethods[key].push(subrule)
}
public visitOption(option: Option): void {
this.dslMethods.option.push(option)
}
public visitRepetitionWithSeparator(manySep: RepetitionWithSeparator): void {
this.dslMethods.repetitionWithSeparator.push(manySep)
}
public visitRepetitionMandatory(atLeastOne: RepetitionMandatory): void {
this.dslMethods.repetitionMandatory.push(atLeastOne)
}
public visitRepetitionMandatoryWithSeparator(
atLeastOneSep: RepetitionMandatoryWithSeparator
): void {
this.dslMethods.repetitionMandatoryWithSeparator.push(atLeastOneSep)
}
public visitRepetition(many: Repetition): void {
this.dslMethods.repetition.push(many)
}
public visitAlternation(or: Alternation): void {
this.dslMethods.alternation.push(or)
}
}
const collectorVisitor = new DslMethodsCollectorVisitor()
export function collectMethods(
rule: Rule
): {
option: Option[]
alternation: Alternation[]
repetition: Repetition[]
repetitionWithSeparator: RepetitionWithSeparator[]
repetitionMandatory: RepetitionMandatory[]
repetitionMandatoryWithSeparator: RepetitionMandatoryWithSeparator
} {
collectorVisitor.reset()
rule.accept(collectorVisitor)
const dslMethods = collectorVisitor.dslMethods
// avoid uncleaned references
collectorVisitor.reset()
return <any>dslMethods
}