Integrated that compiler executes acme assembler by itself, to build final executable
This commit is contained in:
parent
59f056734f
commit
f4c713af7a
4 changed files with 300 additions and 20 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -40,3 +40,4 @@ c65gm
|
||||||
opencode-config/package-lock.json
|
opencode-config/package-lock.json
|
||||||
internal/preproc/lib/
|
internal/preproc/lib/
|
||||||
.bun/
|
.bun/
|
||||||
|
*.asm
|
||||||
|
|
|
||||||
11
AGENTS.md
11
AGENTS.md
|
|
@ -157,8 +157,17 @@ C65LIBPATH=/app/lib ./c65gm -in input.c65 -out output.asm
|
||||||
|
|
||||||
### Compilation
|
### Compilation
|
||||||
**IMPORTANT**: The agent cannot compile code. Provide these instructions to the user:
|
**IMPORTANT**: The agent cannot compile code. Provide these instructions to the user:
|
||||||
|
|
||||||
|
#### New Self-Contained Method (Recommended)
|
||||||
```bash
|
```bash
|
||||||
./c65gm -in input.c65 -out output.asm
|
./c65gm myprogram.c65 # Creates myprogram.prg (compile + assemble)
|
||||||
|
./c65gm build -i myprogram.c65 # Same as above
|
||||||
|
./c65gm compile -i myprogram.c65 # Creates myprogram.asm only
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Legacy Method (Still Supported)
|
||||||
|
```bash
|
||||||
|
./c65gm -in myprogram.c65 -out output.asm
|
||||||
acme -f cbm -o output.prg output.asm
|
acme -f cbm -o output.prg output.asm
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
46
README.md
46
README.md
|
|
@ -42,18 +42,46 @@ go install
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Compile a source file to ACME assembly:
|
### Quick Start (Recommended)
|
||||||
|
Compile and assemble directly to a .prg file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./c65gm -in input.c65 -out output.asm
|
./c65gm myprogram.c65 # Creates myprogram.prg
|
||||||
|
./c65gm -i myprogram.c65 -o game.prg # Creates game.prg
|
||||||
```
|
```
|
||||||
|
|
||||||
Then assemble the output with ACME:
|
### Command Reference
|
||||||
|
|
||||||
|
#### Build (compile + assemble to .prg)
|
||||||
```bash
|
```bash
|
||||||
acme -f cbm -o output.prg output.asm
|
./c65gm build -i myprogram.c65 [-o output.prg] [--keep-asm] [--no-cbm]
|
||||||
|
./c65gm myprogram.c65 # Default build to myprogram.prg
|
||||||
|
./c65gm -i myprogram.c65 # Same as above
|
||||||
|
./c65gm -in myprogram.c65 # Legacy syntax, still works
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Compile (to .asm only)
|
||||||
|
```bash
|
||||||
|
./c65gm compile -i myprogram.c65 [-o output.asm]
|
||||||
|
./c65gm myprogram.c65 -o output.asm # .asm extension triggers compile mode
|
||||||
|
./c65gm -i myprogram.c65 -out output.asm # Legacy syntax
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Help
|
||||||
|
```bash
|
||||||
|
./c65gm help
|
||||||
|
./c65gm -h
|
||||||
|
./c65gm --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
- **Self-contained**: No external build scripts needed
|
||||||
|
- **Flexible syntax**: `-i`/`-in` and `-o`/`-out` are equivalent
|
||||||
|
- **Smart defaults**: Output extension determines mode (.prg = build, .asm = compile)
|
||||||
|
- **ACME integration**: Automatically finds and runs ACME assembler with `-f cbm` by default
|
||||||
|
- **Backward compatible**: Legacy `-in`/`-out` flags still work
|
||||||
|
- **Customizable**: Use `--no-cbm` to disable CBM format, `--keep-asm` to keep intermediate files
|
||||||
|
|
||||||
## Running Tests
|
## Running Tests
|
||||||
|
|
||||||
Run all tests:
|
Run all tests:
|
||||||
|
|
@ -83,6 +111,16 @@ See the `examples/` directory for sample programs:
|
||||||
- `memlib_demo/` - Memory library usage
|
- `memlib_demo/` - Memory library usage
|
||||||
- `switch_demo/` - SWITCH/CASE statement examples
|
- `switch_demo/` - SWITCH/CASE statement examples
|
||||||
|
|
||||||
|
### Building Examples
|
||||||
|
```bash
|
||||||
|
cd examples/hires
|
||||||
|
c65gm hires.c65 # Creates hires.prg
|
||||||
|
c65gm -i hires.c65 -o demo.prg # Creates demo.prg
|
||||||
|
c65gm compile -i hires.c65 # Creates hires.asm only
|
||||||
|
```
|
||||||
|
|
||||||
|
The example directories also contain `cm.sh` scripts showing the old build method.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- `language.md` - Complete language reference
|
- `language.md` - Complete language reference
|
||||||
|
|
|
||||||
260
main.go
260
main.go
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"c65gm/internal/commands"
|
"c65gm/internal/commands"
|
||||||
|
|
@ -21,29 +23,85 @@ func main() {
|
||||||
fmt.Println("Distributed under GPL.")
|
fmt.Println("Distributed under GPL.")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
inFile := flag.String("in", "", "input source file (required)")
|
// Check if we have any arguments
|
||||||
outFile := flag.String("out", "", "output assembly file (required)")
|
if len(os.Args) < 2 {
|
||||||
flag.Parse()
|
printUsage()
|
||||||
|
|
||||||
if *inFile == "" || *outFile == "" {
|
|
||||||
_, _ = fmt.Fprintln(os.Stderr, "Error: -in and -out are required")
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := run(*inFile, *outFile); err != nil {
|
// Check for help flags
|
||||||
if _, ok := err.(preproc.HaltError); ok {
|
if os.Args[1] == "-h" || os.Args[1] == "--help" {
|
||||||
fmt.Println("Halted by #HALT directive")
|
printUsage()
|
||||||
os.Exit(2)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if first arg is a subcommand
|
||||||
|
subcommand := os.Args[1]
|
||||||
|
if subcommand == "build" || subcommand == "compile" || subcommand == "help" {
|
||||||
|
switch subcommand {
|
||||||
|
case "build":
|
||||||
|
runBuildCommand(os.Args[2:])
|
||||||
|
case "compile":
|
||||||
|
runCompileCommand(os.Args[2:])
|
||||||
|
case "help":
|
||||||
|
printUsage()
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default mode: treat as build command with implicit arguments
|
||||||
|
// Parse arguments flexibly
|
||||||
|
var inputFile, outputFile string
|
||||||
|
args := os.Args[1:]
|
||||||
|
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
arg := args[i]
|
||||||
|
|
||||||
|
if arg == "-i" || arg == "-in" {
|
||||||
|
if i+1 < len(args) {
|
||||||
|
inputFile = args[i+1]
|
||||||
|
i++ // Skip next arg
|
||||||
|
}
|
||||||
|
} else if arg == "-o" || arg == "-out" {
|
||||||
|
if i+1 < len(args) {
|
||||||
|
outputFile = args[i+1]
|
||||||
|
i++ // Skip next arg
|
||||||
|
}
|
||||||
|
} else if !strings.HasPrefix(arg, "-") && inputFile == "" {
|
||||||
|
// First non-flag argument is the input file
|
||||||
|
inputFile = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inputFile == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: No input file specified")
|
||||||
|
printUsage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Compilation successful.")
|
// Default output based on input
|
||||||
|
if outputFile == "" {
|
||||||
|
base := strings.TrimSuffix(filepath.Base(inputFile), filepath.Ext(inputFile))
|
||||||
|
outputFile = base + ".prg" // Default to build mode (.prg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine mode by output extension
|
||||||
|
if strings.HasSuffix(strings.ToLower(outputFile), ".prg") {
|
||||||
|
// Build mode (compile + assemble)
|
||||||
|
if err := build(inputFile, outputFile, false, false); err != nil {
|
||||||
|
handleError(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Build successful.")
|
||||||
|
} else {
|
||||||
|
// Compile mode (assembly only)
|
||||||
|
if err := compileOnly(inputFile, outputFile); err != nil {
|
||||||
|
handleError(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Compilation successful.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(inFile, outFile string) error {
|
func compileOnly(inFile, outFile string) error {
|
||||||
// Preprocess
|
// Preprocess
|
||||||
lines, pragma, err := preproc.PreProcess(inFile)
|
lines, pragma, err := preproc.PreProcess(inFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -119,3 +177,177 @@ func registerCommands(comp *compiler.Compiler) {
|
||||||
func writeOutput(filename string, lines []string) error {
|
func writeOutput(filename string, lines []string) error {
|
||||||
return os.WriteFile(filename, []byte(strings.Join(lines, "\n")+"\n"), 0644)
|
return os.WriteFile(filename, []byte(strings.Join(lines, "\n")+"\n"), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleError(err error) {
|
||||||
|
if _, ok := err.(preproc.HaltError); ok {
|
||||||
|
fmt.Println("Halted by #HALT directive")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printUsage() {
|
||||||
|
fmt.Println("Usage:")
|
||||||
|
fmt.Println(" c65gm build -i <input.c65> [-o <output.prg>] [--keep-asm] [--no-cbm]")
|
||||||
|
fmt.Println(" c65gm compile -i <input.c65> [-o <output.asm>]")
|
||||||
|
fmt.Println(" c65gm <input.c65> [-o <output>] (smart default mode)")
|
||||||
|
fmt.Println(" c65gm -in <input.c65> -out <output.asm> (legacy mode)")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Commands:")
|
||||||
|
fmt.Println(" build Compile and assemble to .prg file (default)")
|
||||||
|
fmt.Println(" compile Compile to .asm file only")
|
||||||
|
fmt.Println(" help Show this help")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Options:")
|
||||||
|
fmt.Println(" -i, --input Input .c65 file (required)")
|
||||||
|
fmt.Println(" -o, --output Output file (default: <input>.prg for build, <input>.asm for compile)")
|
||||||
|
fmt.Println(" --keep-asm Keep intermediate assembly file (build command only)")
|
||||||
|
fmt.Println(" --no-cbm Don't add -f cbm flag to ACME (build command only)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBuildCommand(args []string) {
|
||||||
|
buildCmd := flag.NewFlagSet("build", flag.ExitOnError)
|
||||||
|
input := buildCmd.String("i", "", "input .c65 file (required)")
|
||||||
|
output := buildCmd.String("o", "", "output .prg file (default: <input>.prg)")
|
||||||
|
keepAsm := buildCmd.Bool("keep-asm", false, "keep intermediate assembly file")
|
||||||
|
noCBM := buildCmd.Bool("no-cbm", false, "don't add -f cbm flag to ACME")
|
||||||
|
|
||||||
|
if err := buildCmd.Parse(args); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error parsing flags: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *input == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: -i flag is required")
|
||||||
|
buildCmd.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default output filename
|
||||||
|
if *output == "" {
|
||||||
|
base := strings.TrimSuffix(filepath.Base(*input), filepath.Ext(*input))
|
||||||
|
*output = base + ".prg"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure output has .prg extension
|
||||||
|
if !strings.HasSuffix(strings.ToLower(*output), ".prg") {
|
||||||
|
*output = *output + ".prg"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := build(*input, *output, *keepAsm, *noCBM); err != nil {
|
||||||
|
handleError(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Build successful.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCompileCommand(args []string) {
|
||||||
|
compileCmd := flag.NewFlagSet("compile", flag.ExitOnError)
|
||||||
|
input := compileCmd.String("i", "", "input .c65 file (required)")
|
||||||
|
output := compileCmd.String("o", "", "output .asm file (default: <input>.asm)")
|
||||||
|
|
||||||
|
if err := compileCmd.Parse(args); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error parsing flags: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *input == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: -i flag is required")
|
||||||
|
compileCmd.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default output filename
|
||||||
|
if *output == "" {
|
||||||
|
base := strings.TrimSuffix(filepath.Base(*input), filepath.Ext(*input))
|
||||||
|
*output = base + ".asm"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure output has .asm extension
|
||||||
|
if !strings.HasSuffix(strings.ToLower(*output), ".asm") {
|
||||||
|
*output = *output + ".asm"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := compileOnly(*input, *output); err != nil {
|
||||||
|
handleError(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Compilation successful.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func build(inputFile, outputFile string, keepAsm, noCBM bool) error {
|
||||||
|
// Check if ACME is available before starting compilation
|
||||||
|
if err := checkACMEAvailable(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create temporary assembly file
|
||||||
|
tempDir := os.TempDir()
|
||||||
|
asmFile := filepath.Join(tempDir, "c65gm_"+filepath.Base(inputFile)+".asm")
|
||||||
|
|
||||||
|
// Compile to assembly
|
||||||
|
if err := compileOnly(inputFile, asmFile); err != nil {
|
||||||
|
return fmt.Errorf("compilation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run ACME assembler
|
||||||
|
if err := runACME(asmFile, outputFile, noCBM); err != nil {
|
||||||
|
return fmt.Errorf("assembly failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up temporary assembly file unless requested to keep it
|
||||||
|
if !keepAsm {
|
||||||
|
os.Remove(asmFile)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Intermediate assembly file kept: %s\n", asmFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runACME(asmFile, outputFile string, noCBM bool) error {
|
||||||
|
// Check if ACME is available
|
||||||
|
acmePath, err := exec.LookPath("acme")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("acme assembler not found in PATH. Please install ACME: https://github.com/meonwax/acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build ACME command arguments
|
||||||
|
args := []string{"-o", outputFile}
|
||||||
|
if !noCBM {
|
||||||
|
args = append(args, "-f", "cbm")
|
||||||
|
}
|
||||||
|
args = append(args, asmFile)
|
||||||
|
|
||||||
|
fmt.Printf("Running ACME assembler: %s %s\n", acmePath, strings.Join(args, " "))
|
||||||
|
|
||||||
|
cmd := exec.Command(acmePath, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("acme failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Assembled to: %s\n", outputFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkACMEAvailable checks if ACME is installed and provides helpful instructions if not
|
||||||
|
func checkACMEAvailable() error {
|
||||||
|
_, err := exec.LookPath("acme")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`ACME assembler not found in PATH.
|
||||||
|
|
||||||
|
c65gm requires ACME (Cross-Assembler) to create executable files.
|
||||||
|
Please install ACME from: https://github.com/meonwax/acme
|
||||||
|
|
||||||
|
Installation options:
|
||||||
|
- Linux: Download from releases or use package manager
|
||||||
|
- macOS: brew install acme
|
||||||
|
- Windows: Download binary from releases
|
||||||
|
|
||||||
|
Or use compile mode to generate assembly only:
|
||||||
|
c65gm compile -i program.c65`)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue