Added c65cm stdlib as embedded into the c65gm exe so it's completely self contained, for easy distribution.
This commit is contained in:
parent
5e71adff2b
commit
59f056734f
9 changed files with 194 additions and 6 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -38,3 +38,5 @@ c65gm
|
||||||
.env
|
.env
|
||||||
.local/
|
.local/
|
||||||
opencode-config/package-lock.json
|
opencode-config/package-lock.json
|
||||||
|
internal/preproc/lib/
|
||||||
|
.bun/
|
||||||
|
|
|
||||||
|
|
@ -90,11 +90,13 @@ LABEL lib_mylib_skip
|
||||||
## Development Guidelines
|
## Development Guidelines
|
||||||
|
|
||||||
### Environment Constraints
|
### Environment Constraints
|
||||||
**IMPORTANT**: The agent runs in a Docker container without access to compilers, testing tools, or external build systems. All compilation and testing must be performed by the user. The agent can only:
|
**CRITICAL - NO GO TOOLS AVAILABLE**: The agent runs in a Docker container without access to compilers, testing tools, or external build systems. The container does NOT have Go installed (`go` command not found). All compilation and testing must be performed by the user. The agent can only:
|
||||||
1. Read and analyze source code
|
1. Read and analyze source code
|
||||||
2. Make code changes
|
2. Make code changes
|
||||||
3. Provide instructions for the user to compile and test
|
3. Provide instructions for the user to compile and test
|
||||||
|
|
||||||
|
**NEVER attempt to run `go build`, `go test`, or any Go commands** - they will fail with "go: not found".
|
||||||
|
|
||||||
### File Access Restrictions
|
### File Access Restrictions
|
||||||
**CRITICAL**: The agent must only access normal project files within the current working directory. The agent must NEVER:
|
**CRITICAL**: The agent must only access normal project files within the current working directory. The agent must NEVER:
|
||||||
1. Look at files outside the project directory (e.g., `/tmp/`, `/etc/`, `/home/`, etc.)
|
1. Look at files outside the project directory (e.g., `/tmp/`, `/etc/`, `/home/`, etc.)
|
||||||
|
|
@ -117,7 +119,7 @@ All file operations must be restricted to the project's source code and document
|
||||||
4. Consider adding examples in `examples/` directory
|
4. Consider adding examples in `examples/` directory
|
||||||
|
|
||||||
### Testing and Rebuilding
|
### Testing and Rebuilding
|
||||||
**IMPORTANT**: The agent cannot run tests or compile code. Provide these instructions to the user:
|
**CRITICAL**: The agent cannot run tests or compile code. The container has no Go installation. Provide these instructions to the user:
|
||||||
|
|
||||||
#### Testing:
|
#### Testing:
|
||||||
- Run all tests: `go test ./...`
|
- Run all tests: `go test ./...`
|
||||||
|
|
|
||||||
13
build_c65gm.sh
Executable file
13
build_c65gm.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Copy lib directory to internal/preproc for embedding
|
||||||
|
echo "Copying lib directory for embedding..."
|
||||||
|
rm -rf internal/preproc/lib
|
||||||
|
cp -r lib internal/preproc/
|
||||||
|
|
||||||
|
# Build the binary
|
||||||
|
echo "Building c65gm..."
|
||||||
|
go build -o c65gm
|
||||||
|
|
||||||
|
echo "Build complete: c65gm"
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
# Define filename as variable
|
# Define filename as variable
|
||||||
PROGNAME="shift_demo"
|
PROGNAME="shift_demo"
|
||||||
# Only set C65LIBPATH if not already defined
|
# Only set C65LIBPATH if not already defined
|
||||||
if [ -z "$C65LIBPATH" ]; then
|
#if [ -z "$C65LIBPATH" ]; then
|
||||||
export C65LIBPATH=$(readlink -f "../../lib")
|
# export C65LIBPATH=$(readlink -f "../../lib")
|
||||||
fi
|
#fi
|
||||||
# Compile - use absolute path to c65gm
|
# Compile - use absolute path to c65gm
|
||||||
c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s
|
c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
|
|
|
||||||
38
internal/preproc/embeddedlib.go
Normal file
38
internal/preproc/embeddedlib.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package preproc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed lib
|
||||||
|
var embeddedLib embed.FS
|
||||||
|
|
||||||
|
// embeddedLibPath returns the path within the embedded filesystem for a library include.
|
||||||
|
// For example, "<c64start.c65>" becomes "lib/c64start.c65"
|
||||||
|
func embeddedLibPath(spec string) (string, error) {
|
||||||
|
if !strings.HasPrefix(spec, "<") || !strings.HasSuffix(spec, ">") {
|
||||||
|
return "", fmt.Errorf("not an angle-bracket include: %s", spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
base := strings.TrimSpace(spec[1 : len(spec)-1])
|
||||||
|
return "lib/" + base, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEmbeddedFile reads a file from the embedded filesystem.
|
||||||
|
func readEmbeddedFile(path string) ([]string, error) {
|
||||||
|
data, err := fs.ReadFile(embeddedLib, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split into lines, preserving empty lines
|
||||||
|
content := string(data)
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
|
||||||
|
// If the file ends with newline, the last element will be empty
|
||||||
|
// This matches the behavior of strings.Split for disk files
|
||||||
|
return lines, nil
|
||||||
|
}
|
||||||
|
|
@ -93,3 +93,16 @@ func (m *MockFileReader) ReadLines(includeSpec string, _ string) ([]string, stri
|
||||||
}
|
}
|
||||||
return lines, includeSpec, nil
|
return lines, includeSpec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDefaultFileReader creates the appropriate FileReader based on C65LIBPATH environment variable.
|
||||||
|
// If C65LIBPATH is set, returns a DiskFileReader for external library access.
|
||||||
|
// If C65LIBPATH is not set, returns a HybridFileReader that uses embedded standard library.
|
||||||
|
func NewDefaultFileReader() FileReader {
|
||||||
|
libPath := os.Getenv("C65LIBPATH")
|
||||||
|
if libPath != "" {
|
||||||
|
fmt.Printf("Using external library from: %s\n", libPath)
|
||||||
|
return NewDiskFileReader()
|
||||||
|
}
|
||||||
|
fmt.Println("Using embedded standard library")
|
||||||
|
return NewHybridFileReader()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package preproc
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -67,6 +68,51 @@ func TestDiskFileReader_ReadLines_LibraryWithoutEnv(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHybridFileReader_ReadLines_LibraryIncludes(t *testing.T) {
|
||||||
|
origPath := os.Getenv("C65LIBPATH")
|
||||||
|
defer func() { _ = os.Setenv("C65LIBPATH", origPath) }()
|
||||||
|
|
||||||
|
// Test 1: With C65LIBPATH set, should use disk
|
||||||
|
libPath, _ := filepath.Abs("filereader_mocks/lib")
|
||||||
|
_ = os.Setenv("C65LIBPATH", libPath)
|
||||||
|
|
||||||
|
reader1 := NewHybridFileReader()
|
||||||
|
lines1, path1, err1 := reader1.ReadLines("<test_lib.c65>", "")
|
||||||
|
if err1 != nil {
|
||||||
|
t.Errorf("HybridFileReader with C65LIBPATH should work: %v", err1)
|
||||||
|
}
|
||||||
|
if len(lines1) == 0 {
|
||||||
|
t.Error("expected lines from disk library")
|
||||||
|
}
|
||||||
|
if !strings.Contains(path1, "filereader_mocks") {
|
||||||
|
t.Errorf("expected disk path, got: %s", path1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Without C65LIBPATH, should use embedded (will fail in test since no embedded mock)
|
||||||
|
_ = os.Setenv("C65LIBPATH", "")
|
||||||
|
reader2 := NewHybridFileReader()
|
||||||
|
_, _, err2 := reader2.ReadLines("<test_lib.c65>", "")
|
||||||
|
if err2 == nil {
|
||||||
|
// This is expected to fail in unit tests since we can't mock embedded FS easily
|
||||||
|
// In real usage, embedded library would be used
|
||||||
|
t.Log("Note: HybridFileReader without C65LIBPATH would use embedded library")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHybridFileReader_ReadLines_RelativeIncludes(t *testing.T) {
|
||||||
|
reader := NewHybridFileReader()
|
||||||
|
|
||||||
|
appDir, _ := filepath.Abs("filereader_mocks/app")
|
||||||
|
|
||||||
|
lines, _, err := reader.ReadLines("test_app.c65", appDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("HybridFileReader should handle relative includes: %v", err)
|
||||||
|
}
|
||||||
|
if len(lines) == 0 {
|
||||||
|
t.Error("expected lines from relative include")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDiskFileReader_ReadLines_RelativeIncludes(t *testing.T) {
|
func TestDiskFileReader_ReadLines_RelativeIncludes(t *testing.T) {
|
||||||
reader := NewDiskFileReader()
|
reader := NewDiskFileReader()
|
||||||
|
|
||||||
|
|
|
||||||
74
internal/preproc/hybridfilereader.go
Normal file
74
internal/preproc/hybridfilereader.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ func PreProcess(rootFilename string, reader ...FileReader) ([]Line, *Pragma, err
|
||||||
if len(reader) > 0 && reader[0] != nil {
|
if len(reader) > 0 && reader[0] != nil {
|
||||||
r = reader[0]
|
r = reader[0]
|
||||||
} else {
|
} else {
|
||||||
r = NewDiskFileReader()
|
r = NewDefaultFileReader()
|
||||||
}
|
}
|
||||||
pp := newPreproc(r)
|
pp := newPreproc(r)
|
||||||
lines, err := pp.run(rootFilename)
|
lines, err := pp.run(rootFilename)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue