import type { ParseNamesFromList } from '../parser-builder/builderTypes.js'; import type { CheckOverlap } from '../utils.js'; import type { GeneratorFromRules, GenRuleMap, GenRulesToObject } from './builderTypes.js'; import { DynamicGenerator } from './dynamicGenerator.js'; import type { GeneratorRule } from './generatorTypes.js'; /** * Converts a list of ruledefs to a record mapping a name to the corresponding ruledef. */ function listToRuleDefMap(rules: T): GenRulesToObject { const newRules: Record = {}; for (const rule of rules) { newRules[rule.name] = rule; } return >newRules; } export class GeneratorBuilder> { /** * Create a GeneratorBuilder from some initial grammar rules or an existing GeneratorBuilder. * If a GeneratorBuilder is provided, a new copy will be created. */ public static create>( args: GeneratorBuilder ): GeneratorBuilder; public static create< Rules extends readonly GeneratorRule[] = readonly GeneratorRule[], Context = Rules[0] extends GeneratorRule ? context : never, Names extends string = ParseNamesFromList, RuleDefs extends GenRuleMap = GenRulesToObject, >(rules: Rules): GeneratorBuilder; public static create< Rules extends readonly GeneratorRule[] = readonly GeneratorRule[], Context = Rules[0] extends GeneratorRule ? context : never, Names extends string = ParseNamesFromList, RuleDefs extends GenRuleMap = GenRulesToObject, >( start: Rules | GeneratorBuilder, ): GeneratorBuilder { if (Array.isArray(start)) { return > new GeneratorBuilder(listToRuleDefMap(start)); } return new GeneratorBuilder({ ...(>start).rules }); } private rules: RuleDefs; private constructor(startRules: RuleDefs) { this.rules = startRules; } public widenContext(): GeneratorBuilder< NewContext, Names, {[Key in keyof RuleDefs]: Key extends Names ? (RuleDefs[Key] extends GeneratorRule ? GeneratorRule : never) : never } > { return this; } public typePatch(): GeneratorBuilder : ( // Only one - infer arg yourself Patch[Key] extends [ any ] ? RuleDefs[Key] extends GeneratorRule ? GeneratorRule : never : never )) : RuleDefs[Key] extends GeneratorRule ? RuleDefs[Key] : never }> { return this; } /** * Change the implementation of an existing generator rule. */ public patchRule(patch: GeneratorRule): GeneratorBuilder : (RuleDefs[Key] extends GeneratorRule ? RuleDefs[Key] : never) } > { const self = : (RuleDefs[Key] extends GeneratorRule ? RuleDefs[Key] : never) }>> this; self.rules[patch.name] = patch; return self; } /** * Add a rule to the grammar. If the rule already exists, but the implementation differs, an error will be thrown. */ public addRuleRedundant(rule: GeneratorRule): GeneratorBuilder ? RuleDefs[K] : never) : (K extends U ? GeneratorRule : never) }> { const self = ? RuleDefs[K] : never) : (K extends U ? GeneratorRule : never) }>> this; const rules = >> self.rules; if (rules[rule.name] !== undefined && rules[rule.name] !== rule) { throw new Error(`Rule ${rule.name} already exists in the GeneratorBuilder`); } rules[rule.name] = rule; return self; } /** * Add a rule to the grammar. Will raise a typescript error if the rule already exists in the grammar. */ public addRule( rule: CheckOverlap>, ): GeneratorBuilder ? RuleDefs[K] : never) : (K extends U ? GeneratorRule : never) }> { return this.addRuleRedundant(rule); } public addMany[]>( ...rules: CheckOverlap, Names, U> ): GeneratorBuilder< Context, Names | ParseNamesFromList, {[K in Names | ParseNamesFromList]: K extends keyof GenRulesToObject ? ( GenRulesToObject[K] extends GeneratorRule ? GenRulesToObject[K] : never ) : ( K extends Names ? (RuleDefs[K] extends GeneratorRule ? RuleDefs[K] : never) : never ) } > { this.rules = { ...this.rules, ...listToRuleDefMap(rules) }; return this; } /** * Delete a grammar rule by its name. */ public deleteRule(ruleName: U): GeneratorBuilder, {[K in Exclude]: RuleDefs[K] extends GeneratorRule ? RuleDefs[K] : never }> { delete this.rules[ruleName]; return , {[K in Exclude]: RuleDefs[K] extends GeneratorRule ? RuleDefs[K] : never }>> this; } public getRule(ruleName: U): RuleDefs[U] extends GeneratorRule ? GeneratorRule : never { return this.rules[ruleName]; } /** * Merge this grammar GeneratorBuilder with another. * It is best to merge the bigger grammar with the smaller one. * If the two builders both have a grammar rule with the same name, * no error will be thrown case they map to the same ruledef object. * If they map to a different object, an error will be thrown. * To fix this problem, the overridingRules array should contain a rule with the same conflicting name, * this rule implementation will be used. */ public merge< OtherNames extends string, OtherRules extends GenRuleMap, OW extends readonly GeneratorRule[], >( GeneratorBuilder: GeneratorBuilder, overridingRules: OW, ): GeneratorBuilder< Context, Names | OtherNames | ParseNamesFromList, {[K in Names | OtherNames | ParseNamesFromList]: K extends keyof GenRulesToObject ? ( GenRulesToObject[K] extends GeneratorRule ? GenRulesToObject[K] : never ) : ( K extends Names ? (RuleDefs[K] extends GeneratorRule ? RuleDefs[K] : never) : K extends OtherNames ? (OtherRules[K] extends GeneratorRule ? OtherRules[K] : never) : never ) } > { // Assume the other grammar is bigger than yours. So start from that one and add this one const otherRules: Record> = { ...GeneratorBuilder.rules }; const myRules: Record> = this.rules; for (const rule of Object.values(myRules)) { if (otherRules[rule.name] === undefined) { otherRules[rule.name] = rule; } else { const existingRule = otherRules[rule.name]; // If same rule, no issue, move on. Else if (existingRule !== rule) { const override = overridingRules.find(x => x.name === rule.name); // If override specified, take override, else, inform user that there is a conflict if (override) { otherRules[rule.name] = override; } else { throw new Error(`Rule with name "${rule.name}" already exists in the GeneratorBuilder, specify an override to resolve conflict`); } } } } this.rules = otherRules; return this; } public build(): GeneratorFromRules { return > new DynamicGenerator(this.rules); } }