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, "/" + 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, "/" + strings.TrimPrefix(embeddedPath, "lib/"), nil }