This document covers the embeddable pkg/retroasm API.
For most users, the retroasm CLI is the primary interface. The library API is mainly useful when you are:
- generating assembly code programmatically
- integrating assembly into a compiler or build pipeline
- assembling source text inside another Go tool
The public library API is designed around a reusable assembler interface with pluggable architectures. At the moment, the implemented production path matches the current CLI target:
- system: NES
- CPU: 6502
- text formats:
asm6,ca65,nesasm
The core entry points are:
AssembleTextfor source text inputAssembleASTfor AST-first workflows
go get github.com/retroenv/retroasmRequirements:
- Go 1.24 or later
Create an assembler instance and register the architecture adapter you want to assemble for. The example below uses the current 6502 implementation:
package main
import (
"github.com/retroenv/retroasm/pkg/arch/m6502"
"github.com/retroenv/retroasm/pkg/retroasm"
"github.com/retroenv/retrogolib/arch"
)
func newAssembler() (retroasm.Assembler, error) {
assembler := retroasm.New()
m6502Arch := m6502.New()
adapter := retroasm.NewArchitectureAdapter(string(arch.M6502), m6502Arch, m6502Arch)
if err := assembler.RegisterArchitecture(string(arch.M6502), adapter); err != nil {
return nil, err
}
return assembler, nil
}Use AssembleText when you already have assembly source as text.
The example below shows the current 6502/NES-oriented path:
package main
import (
"context"
"fmt"
"strings"
"github.com/retroenv/retroasm/pkg/arch/m6502"
"github.com/retroenv/retroasm/pkg/retroasm"
"github.com/retroenv/retrogolib/arch"
)
func main() {
assembler := retroasm.New()
m6502Arch := m6502.New()
adapter := retroasm.NewArchitectureAdapter(string(arch.M6502), m6502Arch, m6502Arch)
if err := assembler.RegisterArchitecture(string(arch.M6502), adapter); err != nil {
panic(err)
}
output, err := assembler.AssembleText(context.Background(), &retroasm.TextInput{
Source: strings.NewReader(".segment \"CODE\"\nLDA #$01\nSTA $0200\n"),
SourceName: "example.asm",
Format: retroasm.FormatCa65,
})
if err != nil {
panic(err)
}
fmt.Printf("% X\n", output.Binary)
}Notes:
Sourceis required.SourceNameis used in diagnostics and symbol metadata.Formatshould be one ofretroasm.FormatAsm6,retroasm.FormatCa65, orretroasm.FormatNesasm.- If
ConfigFileis empty, retroasm uses its built-in default ca65-style memory configuration for the current implementation.
If your source depends on a custom memory map, pass a config file path:
output, err := assembler.AssembleText(context.Background(), &retroasm.TextInput{
Source: strings.NewReader(source),
SourceName: "game.asm",
Format: retroasm.FormatCa65,
ConfigFile: "memory.cfg",
})Use AssembleAST when another part of your program already produces assembly nodes directly.
The example below again uses the current 6502 path:
package main
import (
"context"
"fmt"
"github.com/retroenv/retroasm/pkg/arch/m6502"
"github.com/retroenv/retroasm/pkg/parser/ast"
"github.com/retroenv/retroasm/pkg/retroasm"
"github.com/retroenv/retrogolib/arch"
cpu "github.com/retroenv/retrogolib/arch/cpu/m6502"
)
func main() {
assembler := retroasm.New()
m6502Arch := m6502.New()
adapter := retroasm.NewArchitectureAdapter(string(arch.M6502), m6502Arch, m6502Arch)
if err := assembler.RegisterArchitecture(string(arch.M6502), adapter); err != nil {
panic(err)
}
program := []ast.Node{
ast.NewInstruction("LDA", int(cpu.ImmediateAddressing), ast.NewNumber(1), nil),
ast.NewInstruction("STA", int(cpu.AbsoluteAddressing), ast.NewNumber(0x0200), nil),
}
output, err := assembler.AssembleAST(context.Background(), &retroasm.ASTInput{
AST: program,
SourceName: "generated.asm",
BaseAddr: 0x8000,
})
if err != nil {
panic(err)
}
fmt.Printf("% X\n", output.Binary)
}Notes:
- If the AST does not begin with a segment node, retroasm prepends
CODEautomatically. BaseAddroverrides the built-in default start address for AST assembly.- See examples/ast-first for a larger AST-first example.
Both AssembleText and AssembleAST return *retroasm.AssemblyOutput.
Relevant fields:
Binary: assembled machine codeAST: AST nodes returned by the assembly flowSymbols: symbol metadata keyed by symbol nameSegments: segment output metadataDiagnostics: warnings or informational diagnostics
Example:
output, err := assembler.AssembleText(ctx, input)
if err != nil {
return err
}
fmt.Printf("bytes: %d\n", len(output.Binary))
for name, symbol := range output.Symbols {
fmt.Printf("%s = $%X\n", name, symbol.Value)
}The package also exposes a configuration builder:
config := retroasm.NewConfigurationBuilder().
SetMemoryLayout(retroasm.MemoryLayout{
AddressSize: 16,
Endianness: retroasm.LittleEndian,
}).
SetSymbol("RESET_VECTOR", 0xFFFC).
AddSegment(retroasm.SegmentConfig{
Name: "CODE",
StartAddr: 0x8000,
Size: 0x8000,
Type: retroasm.SegmentTypeCode,
}).
Build()At the moment, the active implementation primarily consumes:
TextInput.ConfigFilefor text-based custom memory layoutASTInput.BaseAddrfor AST-based base address controlASTInput.SymbolsandTextInput.Symbolsfor symbol metadata passed into the output
So while SetConfiguration exists on the public interface, callers should currently treat it as a broader API surface than the main configuration mechanism used by the implemented target path today.
The current library surface is intentionally narrow:
- only the current NES/6502 path is implemented as a production target
- text assembly still depends on the existing assembler pipeline and ca65-style configuration model
- library consumers should expect the AST-first path to be the more specialized integration mode