package printer import ( "fmt" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug" ) // Flags enum to track count of temp variables and a few dedicated names type tempFlags int const ( tempFlagsAuto tempFlags = 0x00000000 // No preferred name tempFlagsCountMask tempFlags = 0x0FFFFFFF // Temp variable counter tempFlags_i tempFlags = 0x10000000 // Use/preference flag for '_i' ) type NameGenerator struct { Context *EmitContext IsFileLevelUniqueNameInCurrentFile func(string, bool) bool // callback for Printer.isFileLevelUniqueNameInCurrentFile GetTextOfNode func(*ast.Node) string // callback for Printer.getTextOfNode nodeIdToGeneratedName map[ast.NodeId]string // Map of generated names for specific nodes nodeIdToGeneratedPrivateName map[ast.NodeId]string // Map of generated private names for specific nodes autoGeneratedIdToGeneratedName map[AutoGenerateId]string // Map of generated names for temp and loop variables nameGenerationScope *nameGenerationScope privateNameGenerationScope *nameGenerationScope generatedNames collections.Set[string] // NOTE: Used to match Strada, but should be moved to nameGenerationScope after port is complete. } type nameGenerationScope struct { next *nameGenerationScope // The next nameGenerationScope in the stack tempFlags tempFlags // TempFlags for the current name generation scope. formattedNameTempFlags map[string]tempFlags // TempFlags for the current name generation scope. reservedNames collections.Set[string] // Names reserved in nested name generation scopes. // generatedNames collections.Set[string] // NOTE: generated names should be scoped after Strada port is complete. } func (g *NameGenerator) PushScope(reuseTempVariableScope bool) { g.privateNameGenerationScope = &nameGenerationScope{next: g.privateNameGenerationScope} if !reuseTempVariableScope { g.nameGenerationScope = &nameGenerationScope{next: g.nameGenerationScope} } } func (g *NameGenerator) PopScope(reuseTempVariableScope bool) { if g.privateNameGenerationScope != nil { g.privateNameGenerationScope = g.privateNameGenerationScope.next } if !reuseTempVariableScope && g.nameGenerationScope != nil { g.nameGenerationScope = g.nameGenerationScope.next } } func (g *NameGenerator) getScope(privateName bool) **nameGenerationScope { return core.IfElse(privateName, &g.privateNameGenerationScope, &g.nameGenerationScope) } func (g *NameGenerator) getTempFlags(privateName bool) tempFlags { scope := g.getScope(privateName) if *scope != nil { return (*scope).tempFlags } return tempFlagsAuto } func (g *NameGenerator) setTempFlags(privateName bool, flags tempFlags) { scope := g.getScope(privateName) if *scope == nil { *scope = &nameGenerationScope{} } (*scope).tempFlags = flags } // Gets the TempFlags to use in the current nameGenerationScope for the given key func (g *NameGenerator) getTempFlagsForFormattedName(privateName bool, formattedNameKey string) tempFlags { scope := g.getScope(privateName) if *scope != nil { if flags, ok := (*scope).formattedNameTempFlags[formattedNameKey]; ok { return flags } } return tempFlagsAuto } // Sets the TempFlags to use in the current nameGenerationScope for the given key func (g *NameGenerator) setTempFlagsForFormattedName(privateName bool, formattedNameKey string, flags tempFlags) { scope := g.getScope(privateName) if *scope == nil { *scope = &nameGenerationScope{} } if (*scope).formattedNameTempFlags == nil { (*scope).formattedNameTempFlags = make(map[string]tempFlags) } (*scope).formattedNameTempFlags[formattedNameKey] = flags } func (g *NameGenerator) reserveName(name string, privateName bool, scoped bool, temp bool) { scope := g.getScope(privateName) if *scope == nil { *scope = &nameGenerationScope{} } if privateName || scoped { (*scope).reservedNames.Add(name) } else if !temp { g.generatedNames.Add(name) // NOTE: Matches Strada, but is incorrect. // (*scope).generatedNames.Add(name) // TODO: generated names should be scoped after Strada port is complete. } } // Generate the text for a generated identifier or private identifier func (g *NameGenerator) GenerateName(name *ast.MemberName) string { if g.Context != nil { if autoGenerate, ok := g.Context.autoGenerate[name]; ok { if autoGenerate.Flags.IsNode() { // Node names generate unique names based on their original node // and are cached based on that node's id. return g.generateNameForNodeCached(g.Context.GetNodeForGeneratedName(name), ast.IsPrivateIdentifier(name), autoGenerate.Flags, autoGenerate.Prefix, autoGenerate.Suffix) } else { // Auto, Loop, and Unique names are cached based on their unique autoGenerateId. if autoGeneratedName, ok := g.autoGeneratedIdToGeneratedName[autoGenerate.Id]; ok { return autoGeneratedName } if g.autoGeneratedIdToGeneratedName == nil { g.autoGeneratedIdToGeneratedName = make(map[AutoGenerateId]string) } autoGeneratedName := g.makeName(name) g.autoGeneratedIdToGeneratedName[autoGenerate.Id] = autoGeneratedName return autoGeneratedName } } } return g.GetTextOfNode(name) } func (g *NameGenerator) generateNameForNodeCached(node *ast.Node, privateName bool, flags GeneratedIdentifierFlags, prefix string, suffix string) string { nodeId := ast.GetNodeId(node) cache := core.IfElse(privateName, &g.nodeIdToGeneratedPrivateName, &g.nodeIdToGeneratedName) if *cache == nil { *cache = make(map[ast.NodeId]string) } if name, ok := (*cache)[nodeId]; ok { return name } name := g.generateNameForNode(node, privateName, flags, prefix, suffix) (*cache)[nodeId] = name return name } func (g *NameGenerator) generateNameForNode(node *ast.Node, privateName bool, flags GeneratedIdentifierFlags, prefix string, suffix string) string { switch node.Kind { case ast.KindIdentifier, ast.KindPrivateIdentifier: return g.makeUniqueName(g.GetTextOfNode(node), nil /*checkFn*/, flags.IsOptimistic(), flags.IsReservedInNestedScopes(), privateName, prefix, suffix) case ast.KindModuleDeclaration, ast.KindEnumDeclaration: if privateName || len(prefix) > 0 || len(suffix) > 0 { panic("Generated name for a module or enum cannot be private and may have neither a prefix nor suffix") } return g.generateNameForModuleOrEnum(node) case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportDeclaration: if privateName || len(prefix) > 0 || len(suffix) > 0 { panic("Generated name for an import or export cannot be private and may have neither a prefix nor suffix") } return g.generateNameForImportOrExportDeclaration(node) case ast.KindFunctionDeclaration, ast.KindClassDeclaration: if privateName || len(prefix) > 0 || len(suffix) > 0 { panic("Generated name for a class or function declaration cannot be private and may have neither a prefix nor suffix") } name := node.Name() if name != nil && !(g.Context == nil && g.Context.HasAutoGenerateInfo(name)) { return g.generateNameForNode(name, false /*privateName*/, flags, "" /*prefix*/, "" /*suffix*/) } return g.generateNameForExportDefault() case ast.KindExportAssignment: if privateName || len(prefix) > 0 || len(suffix) > 0 { panic("Generated name for an export assignment cannot be private and may have neither a prefix nor suffix") } return g.generateNameForExportDefault() case ast.KindClassExpression: if privateName || len(prefix) > 0 || len(suffix) > 0 { panic("Generated name for a class expression cannot be private and may have neither a prefix nor suffix") } return g.generateNameForClassExpression() case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor: return g.generateNameForMethodOrAccessor(node, privateName, prefix, suffix) case ast.KindComputedPropertyName: return g.makeTempVariableName(tempFlagsAuto, true /*reservedInNestedScopes*/, privateName, prefix, suffix) default: return g.makeTempVariableName(tempFlagsAuto, false /*reservedInNestedScopes*/, privateName, prefix, suffix) } } func (g *NameGenerator) generateNameForModuleOrEnum(node *ast.Node /* ModuleDeclaration | EnumDeclaration */) string { name := g.GetTextOfNode(node.Name()) // Use module/enum name itself if it is unique, otherwise make a unique variation if isUniqueLocalName(name, node) { return name } else { return g.makeUniqueName(name, nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/) } } func (g *NameGenerator) generateNameForImportOrExportDeclaration(node *ast.Node /* ImportDeclaration | ExportDeclaration */) string { expr := ast.GetExternalModuleName(node) baseName := "module" if ast.IsStringLiteral(expr) { baseName = makeIdentifierFromModuleName(expr.Text()) } return g.makeUniqueName(baseName, nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/) } func (g *NameGenerator) generateNameForExportDefault() string { return g.makeUniqueName("default", nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/) } func (g *NameGenerator) generateNameForClassExpression() string { return g.makeUniqueName("class", nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/) } func (g *NameGenerator) generateNameForMethodOrAccessor(node *ast.Node /* MethodDeclaration | AccessorDeclaration */, privateName bool, prefix string, suffix string) string { if ast.IsIdentifier(node.Name()) { return g.generateNameForNodeCached(node.Name(), privateName, GeneratedIdentifierFlagsNone, prefix, suffix) } return g.makeTempVariableName(tempFlagsAuto, false /*reservedInNestedScopes*/, privateName, prefix, suffix) } func (g *NameGenerator) makeName(name *ast.Node) string { if g.Context != nil { if autoGenerate, ok := g.Context.autoGenerate[name]; ok { switch autoGenerate.Flags.Kind() { case GeneratedIdentifierFlagsAuto: return g.makeTempVariableName(tempFlagsAuto, autoGenerate.Flags.IsReservedInNestedScopes(), ast.IsPrivateIdentifier(name), autoGenerate.Prefix, autoGenerate.Suffix) case GeneratedIdentifierFlagsLoop: debug.AssertNode(name, ast.IsIdentifier) return g.makeTempVariableName(tempFlags_i, autoGenerate.Flags.IsReservedInNestedScopes(), false /*privateName*/, autoGenerate.Prefix, autoGenerate.Suffix) case GeneratedIdentifierFlagsUnique: return g.makeUniqueName( name.Text(), core.IfElse(autoGenerate.Flags.IsFileLevel(), g.IsFileLevelUniqueNameInCurrentFile, nil), autoGenerate.Flags.IsOptimistic(), autoGenerate.Flags.IsReservedInNestedScopes(), ast.IsPrivateIdentifier(name), autoGenerate.Prefix, autoGenerate.Suffix, ) } } } return g.GetTextOfNode(name) } // Return the next available name in the pattern _a ... _z, _0, _1, ... // TempFlags._i may be used to express a preference for that dedicated name. // Note that names generated by makeTempVariableName and makeUniqueName will never conflict. func (g *NameGenerator) makeTempVariableName(flags tempFlags, reservedInNestedScopes bool, privateName bool, prefix string, suffix string) string { var tempFlags tempFlags var key string simple := len(prefix) == 0 && len(suffix) == 0 if simple { tempFlags = g.getTempFlags(privateName) } else { // Generate a key to use to acquire a TempFlags counter based on the fixed portions of the generated name. key = FormatGeneratedName(privateName, prefix, "" /*base*/, suffix) if privateName { key = ensureLeadingHash(key) } tempFlags = g.getTempFlagsForFormattedName(privateName, key) } if flags != 0 && tempFlags&flags == 0 { fullName := FormatGeneratedName(privateName, prefix, "_i", suffix) if g.isUniqueName(fullName, privateName) { tempFlags |= flags g.reserveName(fullName, privateName, reservedInNestedScopes, true /*temp*/) if simple { g.setTempFlags(privateName, tempFlags) } else { g.setTempFlagsForFormattedName(privateName, key, tempFlags) } return fullName } } for { count := tempFlags & tempFlagsCountMask tempFlags++ // Skip over 'i' and 'n' if count != 8 && count != 13 { var name string if count < 26 { name = fmt.Sprintf("_%c", 'a'+byte(count)) } else { name = fmt.Sprintf("_%d", count-26) } fullName := FormatGeneratedName(privateName, prefix, name, suffix) if g.isUniqueName(fullName, privateName) { g.reserveName(fullName, privateName, reservedInNestedScopes, true /*temp*/) if simple { g.setTempFlags(privateName, tempFlags) } else { g.setTempFlagsForFormattedName(privateName, key, tempFlags) } return fullName } } } } // Generate a name that is unique within the current file and doesn't conflict with any names // in global scope. The name is formed by adding an '_n' suffix to the specified base name, // where n is a positive integer. Note that names generated by makeTempVariableName and // makeUniqueName are guaranteed to never conflict. // If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1' func (g *NameGenerator) makeUniqueName(baseName string, checkFn func(name string, privateName bool) bool, optimistic bool, scoped bool, privateName bool, prefix string, suffix string) string { baseName = removeLeadingHash(baseName) if optimistic { fullName := FormatGeneratedName(privateName, prefix, baseName, suffix) if g.checkUniqueName(fullName, privateName, checkFn) { g.reserveName(fullName, privateName, scoped, false /*temp*/) return fullName } } // Find the first unique 'name_n', where n is a positive integer if len(baseName) > 0 && baseName[len(baseName)-1] != '_' { baseName += "_" } i := 1 for { fullName := FormatGeneratedName(privateName, prefix, fmt.Sprintf("%s%d", baseName, i), suffix) if g.checkUniqueName(fullName, privateName, checkFn) { g.reserveName(fullName, privateName, scoped, false /*temp*/) return fullName } i++ } } func (g *NameGenerator) checkUniqueName(name string, privateName bool, checkFn func(name string, privateName bool) bool) bool { if checkFn != nil { return checkFn(name, privateName) } else { return g.isUniqueName(name, privateName) } } func nextContainer(node *ast.Node) *ast.Node { data := node.LocalsContainerData() if data != nil { return data.NextContainer } return nil } func isUniqueLocalName(name string, container *ast.Node) bool { node := container for node != nil && ast.IsNodeDescendantOf(node, container) && node.LocalsContainerData() != nil { locals := node.Locals() if locals != nil { // We conservatively include alias symbols to cover cases where they're emitted as locals if local, ok := locals[name]; ok && local.Flags&(ast.SymbolFlagsValue|ast.SymbolFlagsExportValue|ast.SymbolFlagsAlias) != 0 { return false } } node = nextContainer(node) } return true } func (g *NameGenerator) isUniqueName(name string, privateName bool) bool { return (g.IsFileLevelUniqueNameInCurrentFile == nil || g.IsFileLevelUniqueNameInCurrentFile(name, privateName)) && !g.isReservedName(name, privateName) } func (g *NameGenerator) isReservedName(name string, privateName bool) bool { scope := g.getScope(privateName) // NOTE: The following matches Strada, but is incorrect. if g.generatedNames.Has(name) { return true } // TODO: generated names should be scoped after Strada port is complete. ////if *scope != nil { //// if (*scope).generatedNames.Has(name) { //// return true //// } ////} for *scope != nil { if (*scope).reservedNames.Has(name) { return true } scope = &(*scope).next } return false }