100 lines
2.5 KiB
Go
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
|
|
}
|