402 lines
16 KiB
Go
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
|
|
}
|