179 lines
5.1 KiB
TypeScript
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
|
|
}
|