kittenipc/kitcom/internal/tsgo/printer/namegenerator.go
2025-10-15 10:12:44 +03:00

402 lines
16 KiB
Go

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
}