c65gm/internal/preproc/hybridfilereader.go

74 lines
No EOL
2.3 KiB
Go

package preproc
import (
"fmt"
"os"
"strings"
)
// HybridFileReader implements FileReader with support for both embedded
// standard library (when C65LIBPATH is not set) and disk-based includes.
type HybridFileReader struct {
diskReader *DiskFileReader // For relative includes & C65LIBPATH overrides
libPath string // From C65LIBPATH env var
usedEmbedded bool // Track if we've used embedded library
embeddedCache map[string][]string // Cache for embedded files
}
// NewHybridFileReader creates a new HybridFileReader.
func NewHybridFileReader() *HybridFileReader {
return &HybridFileReader{
diskReader: NewDiskFileReader(),
libPath: os.Getenv("C65LIBPATH"),
embeddedCache: make(map[string][]string),
}
}
// ReadLines implements the FileReader interface.
func (h *HybridFileReader) ReadLines(includeSpec string, currentDir string) ([]string, string, error) {
// Check if this is an angle-bracket include
if strings.HasPrefix(includeSpec, "<") && strings.HasSuffix(includeSpec, ">") {
// If C65LIBPATH is set, delegate to DiskFileReader
if h.libPath != "" {
return h.diskReader.ReadLines(includeSpec, currentDir)
}
// Otherwise, use embedded library
if !h.usedEmbedded {
h.usedEmbedded = true
// Note: Library source message is printed by NewDefaultFileReader factory
}
return h.readEmbeddedLibrary(includeSpec)
}
// Relative includes always go to disk
return h.diskReader.ReadLines(includeSpec, currentDir)
}
// readEmbeddedLibrary reads a file from the embedded standard library.
func (h *HybridFileReader) readEmbeddedLibrary(spec string) ([]string, string, error) {
// Convert angle-bracket spec to embedded path
embeddedPath, err := embeddedLibPath(spec)
if err != nil {
return nil, "", err
}
// Check cache
if lines, ok := h.embeddedCache[embeddedPath]; ok {
// Return cached lines with virtual path for error messages
return lines, "<embedded>/" + strings.TrimPrefix(embeddedPath, "lib/"), nil
}
// Read from embedded filesystem
lines, err := readEmbeddedFile(embeddedPath)
if err != nil {
return nil, "", fmt.Errorf("embedded library include not found: %s", spec)
}
// Cache the result
h.embeddedCache[embeddedPath] = lines
// Return with virtual path for error messages
return lines, "<embedded>/" + strings.TrimPrefix(embeddedPath, "lib/"), nil
}