import { setUnion , setUnionInto , setMap , setFilter , iterableFind , iterableSome , iterableMinBy , setGroupBy , setFilterMap , iterableFirst , iterableEvery , mapMergeInto } from "collection-utils" ; import { defined , assert , panic } from "./support/Support" ; export class Namespace { readonly forbiddenNamespaces : ReadonlySet < Namespace > ; readonly additionalForbidden : ReadonlySet < Name > ; private readonly _children = new Set < Namespace > ( ) ; private readonly _members = new Set < Name > ( ) ; constructor ( _name , parent , forbiddenNamespaces , additionalForbidden ) { this . forbiddenNamespaces = new Set ( forbiddenNamespaces ) ; this . additionalForbidden = new Set ( additionalForbidden ) ; if ( parent !== undefined ) { parent . addChild ( this ) ; } } private addChild ( child ) { this . _children . add ( child ) ; } get children ( ) : ReadonlySet < Namespace > { return this . _children ; } get members ( ) : ReadonlySet < Name > { return this . _members ; } get forbiddenNameds ( ) : ReadonlySet < Name > { return setUnion ( this . additionalForbidden , ... Array . from ( this . forbiddenNamespaces ) . map ( ns => ns . members ) ) ; } add < TName extends Name > ( named ) { this . _members . add ( named ) ; return named ; } } export type NameStyle = ( rawName : string ) => string ; export class Namer { private readonly _prefixes : ReadonlySet < string > ; constructor ( readonly name , readonly nameStyle , prefixes ) { this . _prefixes = new Set ( prefixes ) ; } assignNames ( names , forbiddenNamesIterable , namesToAssignIterable ) { const forbiddenNames = new Set ( forbiddenNamesIterable ) ; const namesToAssign = Array . from ( namesToAssignIterable ) ; assert ( namesToAssign . length > 0 , "Number of names can't be less than 1" ) ; const allAssignedNames = new Map < Name , string > ( ) ; let namesToPrefix = [ ] ; for ( const name of namesToAssign ) { const proposedNames = name . proposeUnstyledNames ( names ) ; const namingFunction = name . namingFunction ; const maybeUniqueName = iterableFind ( proposedNames , proposed => ! forbiddenNames . has ( namingFunction . nameStyle ( proposed ) ) && namesToAssign . every ( n => n === name || ! n . proposeUnstyledNames ( names ) . has ( proposed ) ) ) ; if ( maybeUniqueName !== undefined ) { const styledName = namingFunction . nameStyle ( maybeUniqueName ) ; const assigned = name . nameAssignments ( forbiddenNames , styledName ) ; if ( assigned !== null ) { mapMergeInto ( allAssignedNames , assigned ) ; setUnionInto ( forbiddenNames , assigned . values ( ) ) ; continue ; } } namesToPrefix . push ( name ) ; } let prefixes = this . _prefixes . values ( ) ; let suffixNumber = 1 ; for ( const name of namesToPrefix ) { const originalName = defined ( iterableFirst ( name . proposeUnstyledNames ( names ) ) ) ; for ( ; ; ) { let nameToTry ; const { done , value : prefix } = prefixes . next ( ) ; if ( ! done ) { nameToTry = `${ prefix }_${ originalName }` ; } else { nameToTry = `${ originalName }_${ suffixNumber . toString ( ) }` ; suffixNumber ++ ; } const styledName = name . namingFunction . nameStyle ( nameToTry ) ; const assigned = name . nameAssignments ( forbiddenNames , styledName ) ; if ( assigned === null ) continue ; mapMergeInto ( allAssignedNames , assigned ) ; setUnionInto ( forbiddenNames , assigned . values ( ) ) ; break ; } } return allAssignedNames ; } } const funPrefixes = [ "Purple" , "Fluffy" , "Tentacled" , "Sticky" , "Indigo" , "Indecent" , "Hilarious" , "Ambitious" , "Cunning" , "Magenta" , "Frisky" , "Mischievous" , "Braggadocious" ] ; export function funPrefixNamer ( name , nameStyle ) { return new Namer ( name , nameStyle , funPrefixes ) ; } export abstract class Name { private readonly _associates = new Set < AssociatedName > ( ) ; constructor ( private readonly _namingFunction , readonly order ) { } addAssociate ( associate ) { this . _associates . add ( associate ) ; } abstract get dependencies ( ) : ReadonlyArray < Name > ; isFixed ( ) { return this instanceof FixedName ; } get namingFunction ( ) : Namer { return defined ( this . _namingFunction ) ; } abstract proposeUnstyledNames ( names ) ; firstProposedName ( names ) { return defined ( iterableFirst ( this . proposeUnstyledNames ( names ) ) ) ; } nameAssignments ( forbiddenNames , assignedName ) { if ( forbiddenNames . has ( assignedName ) ) return null ; const assignments = new Map < Name , string > ( [ [ this , assignedName ] ] ) ; for ( const an of this . _associates ) { const associatedAssignedName = an . getName ( assignedName ) ; if ( forbiddenNames . has ( associatedAssignedName ) ) { return null ; } assignments . set ( an , associatedAssignedName ) ; } return assignments ; } } export class FixedName extends Name { constructor ( private readonly _fixedName ) { super ( undefined , 0 ) ; } get dependencies ( ) : ReadonlyArray < Name > { return [ ] ; } addAssociate ( _ ) { return panic ( "Cannot add associates to fixed names" ) ; } get fixedName ( ) : string { return this . _fixedName ; } proposeUnstyledNames ( _ ? ) { return panic ( "Only fixedName should be called on FixedName." ) ; } } export class SimpleName extends Name { private readonly _unstyledNames : ReadonlySet < string > ; constructor ( unstyledNames , namingFunction , order ) { super ( namingFunction , order ) ; this . _unstyledNames = new Set ( unstyledNames ) ; } get dependencies ( ) : ReadonlyArray < Name > { return [ ] ; } proposeUnstyledNames ( _ ? ) { return this . _unstyledNames ; } } export class AssociatedName extends Name { constructor ( private readonly _sponsor , order , readonly getName ) { super ( undefined , order ) ; } get dependencies ( ) : ReadonlyArray < Name > { return [ this . _sponsor ] ; } proposeUnstyledNames ( _ ? ) { return panic ( "AssociatedName must be assigned via its sponsor" ) ; } } export class DependencyName extends Name { private readonly _dependencies : ReadonlySet < Name > ; constructor ( namingFunction , order , private readonly _proposeUnstyledName ) { super ( namingFunction , order ) ; const dependencies = [ ] ; _proposeUnstyledName ( n => { dependencies . push ( n ) ; return "0xDEADBEEF" ; } ) ; this . _dependencies = new Set ( dependencies ) ; } get dependencies ( ) : ReadonlyArray < Name > { return Array . from ( this . _dependencies ) ; } proposeUnstyledNames ( names ) { return new Set ( [ this . _proposeUnstyledName ( n => { assert ( this . _dependencies . has ( n ) , "DependencyName proposer is not pure" ) ; return defined ( names . get ( n ) ) ; } ) ] ) ; } } export function keywordNamespace ( name , keywords ) { const ns = new Namespace ( name , undefined , [ ] , [ ] ) ; for ( const kw of keywords ) { ns . add ( new FixedName ( kw ) ) ; } return ns ; } function allNamespacesRecursively ( namespaces ) { return setUnion ( namespaces , ... Array . from ( setMap ( namespaces , ns => allNamespacesRecursively ( ns . children ) ) ) ) ; } class NamingContext { private readonly _names : Map < Name , string > = new Map ( ) ; private readonly _namedsForName : Map < string , Set < Name > > = new Map ( ) ; readonly namespaces : ReadonlySet < Namespace > ; constructor ( rootNamespaces ) { this . namespaces = allNamespacesRecursively ( rootNamespaces ) ; } get names ( ) : ReadonlyMap < Name , string > { return this . _names ; } isReadyToBeNamed = ( named ) : boolean => { if ( this . _names . has ( named ) ) return false ; return named . dependencies . every ( ( n ) => this . _names . has ( n ) ) ; } ; areForbiddensFullyNamed ( namespace ) { return iterableEvery ( namespace . forbiddenNameds , n => this . _names . has ( n ) ) ; } isConflicting = ( namedNamespace , proposed ) : boolean => { const namedsForProposed = this . _namedsForName . get ( proposed ) ; if ( namedsForProposed === undefined ) return false ; for ( const n of namedsForProposed ) { if ( namedNamespace . members . has ( n ) || namedNamespace . forbiddenNameds . has ( n ) ) { return true ; } } return false ; } ; assign = ( named , namedNamespace , name ) : void => { assert ( ! this . names . has ( named ) , `Name "${ name }" assigned twice` ) ; assert ( ! this . isConflicting ( namedNamespace , name ) , `Assigned name "${ name }" conflicts` ) ; this . _names . set ( named , name ) ; let namedsForName = this . _namedsForName . get ( name ) ; if ( namedsForName === undefined ) { namedsForName = new Set ( ) ; this . _namedsForName . set ( name , namedsForName ) ; } namedsForName . add ( named ) ; } ; } export function assignNames ( rootNamespaces ) { const ctx = new NamingContext ( rootNamespaces ) ; for ( const ns of ctx . namespaces ) { for ( const n of ns . members ) { if ( ! n . isFixed ( ) ) continue ; ctx . assign ( n , ns , n . fixedName ) ; } } for ( ; ; ) { const unfinishedNamespaces = setFilter ( ctx . namespaces , ns => ctx . areForbiddensFullyNamed ( ns ) ) ; const readyNamespace = iterableFind ( unfinishedNamespaces , ns => iterableSome ( ns . members , ctx . isReadyToBeNamed ) ) ; if ( readyNamespace === undefined ) { return ctx . names ; } const allForbiddenNames = setUnion ( readyNamespace . members , readyNamespace . forbiddenNameds ) ; let forbiddenNames = setFilterMap ( allForbiddenNames , n => ctx . names . get ( n ) ) ; for ( ; ; ) { const allReadyNames = setFilter ( readyNamespace . members , ctx . isReadyToBeNamed ) ; const minOrderName = iterableMinBy ( allReadyNames , n => n . order ) ; if ( minOrderName === undefined ) break ; const minOrder = minOrderName . order ; const readyNames = setFilter ( allReadyNames , n => n . order === minOrder ) ; const byNamingFunction = setGroupBy ( readyNames , n => n . namingFunction ) ; for ( const [ namer , namedsForNamingFunction ] of byNamingFunction ) { const byProposed = setGroupBy ( namedsForNamingFunction , n => n . namingFunction . nameStyle ( n . firstProposedName ( ctx . names ) ) ) ; for ( const [ , nameds ] of byProposed ) { const names = namer . assignNames ( ctx . names , forbiddenNames , nameds ) ; for ( const [ name , assigned ] of names ) { ctx . assign ( name , readyNamespace , assigned ) ; } setUnionInto ( forbiddenNames , names . values ( ) ) ; } } } } }