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((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( (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 dslMethods }