2025-10-15 10:12:44 +03:00

100 lines
2.5 KiB
Go

package project
import (
"sync"
"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/parser"
"github.com/zeebo/xxh3"
)
type parseCacheKey struct {
ast.SourceFileParseOptions
scriptKind core.ScriptKind
}
func newParseCacheKey(
options ast.SourceFileParseOptions,
scriptKind core.ScriptKind,
) parseCacheKey {
return parseCacheKey{
SourceFileParseOptions: options,
scriptKind: scriptKind,
}
}
type parseCacheEntry struct {
mu sync.Mutex
sourceFile *ast.SourceFile
hash xxh3.Uint128
refCount int
}
type ParseCacheOptions struct {
// DisableDeletion prevents entries from being removed from the cache.
// Used for testing.
DisableDeletion bool
}
type ParseCache struct {
Options ParseCacheOptions
entries collections.SyncMap[parseCacheKey, *parseCacheEntry]
}
func (c *ParseCache) Acquire(
fh FileContent,
opts ast.SourceFileParseOptions,
scriptKind core.ScriptKind,
) *ast.SourceFile {
key := newParseCacheKey(opts, scriptKind)
entry, loaded := c.loadOrStoreNewLockedEntry(key)
defer entry.mu.Unlock()
if !loaded || entry.hash != fh.Hash() {
// Reparse the file if the hash has changed, or parse for the first time.
entry.sourceFile = parser.ParseSourceFile(opts, fh.Content(), scriptKind)
entry.hash = fh.Hash()
}
return entry.sourceFile
}
func (c *ParseCache) Ref(file *ast.SourceFile) {
key := newParseCacheKey(file.ParseOptions(), file.ScriptKind)
if entry, ok := c.entries.Load(key); ok {
entry.mu.Lock()
entry.refCount++
entry.mu.Unlock()
} else {
panic("parse cache entry not found")
}
}
func (c *ParseCache) Deref(file *ast.SourceFile) {
key := newParseCacheKey(file.ParseOptions(), file.ScriptKind)
if entry, ok := c.entries.Load(key); ok {
entry.mu.Lock()
entry.refCount--
remove := entry.refCount <= 0
entry.mu.Unlock()
if !c.Options.DisableDeletion && remove {
c.entries.Delete(key)
}
}
}
// loadOrStoreNewLockedEntry loads an existing entry or creates a new one. The returned
// entry's mutex is locked and its refCount is incremented (or initialized to 1 in the
// case of a new entry).
func (c *ParseCache) loadOrStoreNewLockedEntry(key parseCacheKey) (*parseCacheEntry, bool) {
entry := &parseCacheEntry{refCount: 1}
entry.mu.Lock()
existing, loaded := c.entries.LoadOrStore(key, entry)
if loaded {
existing.mu.Lock()
existing.refCount++
return existing, true
}
return entry, false
}