mirror of
https://github.com/zellyn/go6502.git
synced 2025-02-07 02:30:38 +00:00
added a2as command-line assembler
This commit is contained in:
parent
9992fd049a
commit
bcb02d5c41
119
asm/a2as/a2as.go
Normal file
119
asm/a2as/a2as.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/zellyn/go6502/asm"
|
||||||
|
"github.com/zellyn/go6502/asm/flavors"
|
||||||
|
"github.com/zellyn/go6502/asm/flavors/scma"
|
||||||
|
"github.com/zellyn/go6502/asm/ihex"
|
||||||
|
"github.com/zellyn/go6502/asm/lines"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flavorsByName map[string]flavors.F
|
||||||
|
var flavor string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flavorsByName = map[string]flavors.F{
|
||||||
|
"scma": scma.New(),
|
||||||
|
}
|
||||||
|
var names []string
|
||||||
|
for name := range flavorsByName {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
usage := fmt.Sprintf("assembler flavor: %s", strings.Join(names, ","))
|
||||||
|
flag.StringVar(&flavor, "flavor", "", usage)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var infile = flag.String("in", "", "input file")
|
||||||
|
var outfile = flag.String("out", "", "output file")
|
||||||
|
var format = flag.String("format", "binary", "output format: binary/ihex")
|
||||||
|
var fill = flag.Uint("fillbyte", 0x00, "byte value to use when filling gaps between assmebler output regions")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if *infile == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "no input file specified")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if *outfile == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "no output file specified")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if flavor == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "no flavor specified")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
f, ok := flavorsByName[flavor]
|
||||||
|
if !ok {
|
||||||
|
fmt.Fprintf(os.Stderr, "invalid flavor: '%s'\n", flavor)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if *format != "binary" && *format != "ihex" {
|
||||||
|
fmt.Fprintf(os.Stderr, "format must be binary or ihex; got '%s'\n", *format)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if *fill > 0xff {
|
||||||
|
fmt.Fprintf(os.Stderr, "fillbyte must be <= 255; got '%s'\n", *format)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var o lines.OsOpener
|
||||||
|
a := asm.NewAssembler(f, o)
|
||||||
|
|
||||||
|
if err := a.Assemble(*infile); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.Create(*outfile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := out.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
m, err := a.Membuf()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch *format {
|
||||||
|
case "binary":
|
||||||
|
p := m.Piece(byte(*fill))
|
||||||
|
n, err := out.Write(p.Data)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if n != len(p.Data) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error writing to '%s': wrote %d of %d bytes", *outfile, n, len(p.Data))
|
||||||
|
os.Exit(1)
|
||||||
|
|
||||||
|
}
|
||||||
|
case "ihex":
|
||||||
|
w := ihex.NewWriter(out)
|
||||||
|
for _, p := range m.Pieces() {
|
||||||
|
if err := w.Write(p.Addr, p.Data); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.End()
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "format must be binary or ihex; got '%s'\n", *format)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
@ -232,8 +232,9 @@ func (a *Assembler) Pass(setWidth, final bool) (isFinal bool, err error) {
|
|||||||
return isFinal, nil
|
return isFinal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawBytes returns the raw bytes, sequentially. Intended for testing,
|
// RawBytes returns the raw bytes, sequentially in the order of the
|
||||||
// the return value gives no indication of address changes.
|
// lines of the file. Intended for testing, the return value gives no
|
||||||
|
// indication of address changes.
|
||||||
func (a *Assembler) RawBytes() ([]byte, error) {
|
func (a *Assembler) RawBytes() ([]byte, error) {
|
||||||
result := []byte{}
|
result := []byte{}
|
||||||
for _, in := range a.Insts {
|
for _, in := range a.Insts {
|
||||||
|
@ -2,14 +2,25 @@ package flavors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/zellyn/go6502/asm"
|
"github.com/zellyn/go6502/asm"
|
||||||
"github.com/zellyn/go6502/asm/flavors/scma"
|
"github.com/zellyn/go6502/asm/flavors/scma"
|
||||||
"github.com/zellyn/go6502/asm/lines"
|
"github.com/zellyn/go6502/asm/lines"
|
||||||
|
"github.com/zellyn/go6502/asm/membuf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// h converts from hex or panics.
|
||||||
|
func h(s string) []byte {
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func TestMultiline(t *testing.T) {
|
func TestMultiline(t *testing.T) {
|
||||||
o := lines.NewTestOpener()
|
o := lines.NewTestOpener()
|
||||||
|
|
||||||
@ -23,20 +34,21 @@ func TestMultiline(t *testing.T) {
|
|||||||
i []string // main file: lines
|
i []string // main file: lines
|
||||||
ii map[string][]string // other files: lines
|
ii map[string][]string // other files: lines
|
||||||
b string // bytes, expected
|
b string // bytes, expected
|
||||||
|
ps []membuf.Piece // output bytes expected
|
||||||
active bool
|
active bool
|
||||||
}{
|
}{
|
||||||
// We cannot determine L2, so we go wide.
|
// We cannot determine L2, so we go wide.
|
||||||
{ss, "Unknown label: wide", []string{
|
{ss, "Unknown label: wide", []string{
|
||||||
"L1 LDA L2-L1",
|
"L1 LDA L2-L1",
|
||||||
"L2 NOP",
|
"L2 NOP",
|
||||||
}, nil, "ad0300ea", true},
|
}, nil, "ad0300ea", nil, true},
|
||||||
|
|
||||||
// sc-asm sets instruction widths on the first pass
|
// sc-asm sets instruction widths on the first pass
|
||||||
{ss, "Later label: wide", []string{
|
{ss, "Later label: wide", []string{
|
||||||
" LDA FOO",
|
" LDA FOO",
|
||||||
"FOO .EQ $FF",
|
"FOO .EQ $FF",
|
||||||
" NOP",
|
" NOP",
|
||||||
}, nil, "adff00ea", true},
|
}, nil, "adff00ea", nil, true},
|
||||||
|
|
||||||
// Sub-labels
|
// Sub-labels
|
||||||
{ss, "Sublabels", []string{
|
{ss, "Sublabels", []string{
|
||||||
@ -45,7 +57,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
"L2 BEQ .2",
|
"L2 BEQ .2",
|
||||||
".1 NOP",
|
".1 NOP",
|
||||||
".2 NOP",
|
".2 NOP",
|
||||||
}, nil, "f000eaf001eaea", true},
|
}, nil, "f000eaf001eaea", nil, true},
|
||||||
|
|
||||||
// Includes: one level deep
|
// Includes: one level deep
|
||||||
{ss, "Include A", []string{
|
{ss, "Include A", []string{
|
||||||
@ -56,7 +68,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
"SUBFILE1": {
|
"SUBFILE1": {
|
||||||
" LDA #$2a",
|
" LDA #$2a",
|
||||||
},
|
},
|
||||||
}, "f002a92aea", true},
|
}, "f002a92aea", nil, true},
|
||||||
|
|
||||||
// Ifdefs: simple at first
|
// Ifdefs: simple at first
|
||||||
{ss, "Ifdef A", []string{
|
{ss, "Ifdef A", []string{
|
||||||
@ -67,7 +79,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
" LDA #$02",
|
" LDA #$02",
|
||||||
" .FIN",
|
" .FIN",
|
||||||
" NOP",
|
" NOP",
|
||||||
}, nil, "a901ea", true},
|
}, nil, "a901ea", nil, true},
|
||||||
|
|
||||||
// Ifdefs: else part
|
// Ifdefs: else part
|
||||||
{ss, "Ifdef B", []string{
|
{ss, "Ifdef B", []string{
|
||||||
@ -78,7 +90,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
" LDA #$02",
|
" LDA #$02",
|
||||||
" .FIN",
|
" .FIN",
|
||||||
" NOP",
|
" NOP",
|
||||||
}, nil, "a902ea", true},
|
}, nil, "a902ea", nil, true},
|
||||||
|
|
||||||
// Ifdefs: multiple else, true
|
// Ifdefs: multiple else, true
|
||||||
{ss, "Ifdef C", []string{
|
{ss, "Ifdef C", []string{
|
||||||
@ -93,7 +105,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
" LDA #$04",
|
" LDA #$04",
|
||||||
" .FIN",
|
" .FIN",
|
||||||
" NOP",
|
" NOP",
|
||||||
}, nil, "a901a903ea", true},
|
}, nil, "a901a903ea", nil, true},
|
||||||
|
|
||||||
// Ifdefs: multiple else, false
|
// Ifdefs: multiple else, false
|
||||||
{ss, "Ifdef D", []string{
|
{ss, "Ifdef D", []string{
|
||||||
@ -108,7 +120,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
" LDA #$04",
|
" LDA #$04",
|
||||||
" .FIN",
|
" .FIN",
|
||||||
" NOP",
|
" NOP",
|
||||||
}, nil, "a902a904ea", true},
|
}, nil, "a902a904ea", nil, true},
|
||||||
|
|
||||||
// Ifdef based on org, true
|
// Ifdef based on org, true
|
||||||
{ss, "Ifdef/org A", []string{
|
{ss, "Ifdef/org A", []string{
|
||||||
@ -120,7 +132,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
" LDA #$03",
|
" LDA #$03",
|
||||||
" .FIN",
|
" .FIN",
|
||||||
" NOP",
|
" NOP",
|
||||||
}, nil, "a901a902ea", true},
|
}, nil, "a901a902ea", nil, true},
|
||||||
|
|
||||||
// Ifdef based on org, false
|
// Ifdef based on org, false
|
||||||
{ss, "Ifdef/org B", []string{
|
{ss, "Ifdef/org B", []string{
|
||||||
@ -132,7 +144,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
" LDA #$03",
|
" LDA #$03",
|
||||||
" .FIN",
|
" .FIN",
|
||||||
" NOP",
|
" NOP",
|
||||||
}, nil, "a901a903ea", true},
|
}, nil, "a901a903ea", nil, true},
|
||||||
|
|
||||||
// Macro, simple
|
// Macro, simple
|
||||||
{ss, "Macro, simple", []string{
|
{ss, "Macro, simple", []string{
|
||||||
@ -146,7 +158,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
"PTR .HS 0000",
|
"PTR .HS 0000",
|
||||||
"ZPTR .EQ $42",
|
"ZPTR .EQ $42",
|
||||||
" >INCD ZPTR",
|
" >INCD ZPTR",
|
||||||
}, nil, "ee0808d003ee09080000e642d002e643", true},
|
}, nil, "ee0808d003ee09080000e642d002e643", nil, true},
|
||||||
|
|
||||||
// Macro, conditional assembly
|
// Macro, conditional assembly
|
||||||
{ss, "Macro, conditional assembly", []string{
|
{ss, "Macro, conditional assembly", []string{
|
||||||
@ -171,7 +183,7 @@ func TestMultiline(t *testing.T) {
|
|||||||
" >INCD $1234",
|
" >INCD $1234",
|
||||||
" >INCD $12,X",
|
" >INCD $12,X",
|
||||||
" >INCD $1234,X",
|
" >INCD $1234,X",
|
||||||
}, nil, "e612d002e613ee3412d003ee3512f612d002f613fe3412d003fe3512", true},
|
}, nil, "e612d002e613ee3412d003ee3512f612d002f613fe3412d003fe3512", nil, true},
|
||||||
|
|
||||||
// Macros, nested
|
// Macros, nested
|
||||||
{ss, "Macros, nested", []string{
|
{ss, "Macros, nested", []string{
|
||||||
@ -189,13 +201,30 @@ func TestMultiline(t *testing.T) {
|
|||||||
"SAM RTS",
|
"SAM RTS",
|
||||||
"JOE RTS",
|
"JOE RTS",
|
||||||
"TOM RTS",
|
"TOM RTS",
|
||||||
}, nil, "200f08201108201008200f08201108606060", true},
|
}, nil, "200f08201108201008200f08201108606060", nil, true},
|
||||||
|
|
||||||
|
// Check outputs when origin is changed.
|
||||||
|
{ss, "Origin A", []string{
|
||||||
|
" .OR $1000",
|
||||||
|
" LDA #$01",
|
||||||
|
" .OR $2000",
|
||||||
|
" LDA #$02",
|
||||||
|
" .OR $1002",
|
||||||
|
" LDA #$03",
|
||||||
|
" NOP",
|
||||||
|
}, nil, "a901a902a903ea", []membuf.Piece{
|
||||||
|
{0x1000, h("a901a903ea")},
|
||||||
|
{0x2000, h("a902")},
|
||||||
|
}, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
if !tt.active {
|
if !tt.active {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if tt.b == "" && len(tt.ps) == 0 {
|
||||||
|
t.Fatalf(`%d("%s"): test case must specify bytes or pieces`, i, tt.name)
|
||||||
|
}
|
||||||
tt.a.Reset()
|
tt.a.Reset()
|
||||||
o.Clear()
|
o.Clear()
|
||||||
o["TESTFILE"] = strings.Join(tt.i, "\n")
|
o["TESTFILE"] = strings.Join(tt.i, "\n")
|
||||||
@ -221,15 +250,29 @@ func TestMultiline(t *testing.T) {
|
|||||||
t.Errorf(`%d("%s"): tt.a.Pass(true, true) couldn't finalize`, i, tt.name)
|
t.Errorf(`%d("%s"): tt.a.Pass(true, true) couldn't finalize`, i, tt.name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bb, err := tt.a.RawBytes()
|
|
||||||
if err != nil {
|
if tt.b != "" {
|
||||||
t.Errorf(`%d("%s"): tt.a.RawBytes() failed: %s`, i, tt.name, err)
|
bb, err := tt.a.RawBytes()
|
||||||
continue
|
if err != nil {
|
||||||
|
t.Errorf(`%d("%s"): tt.a.RawBytes() failed: %s`, i, tt.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hx := hex.EncodeToString(bb)
|
||||||
|
if hx != tt.b {
|
||||||
|
t.Errorf(`%d("%s"): tt.a.RawBytes()=[%s]; want [%s]`, i, tt.name, hx, tt.b)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hx := hex.EncodeToString(bb)
|
if len(tt.ps) != 0 {
|
||||||
if hx != tt.b {
|
m, err := tt.a.Membuf()
|
||||||
t.Errorf(`%d("%s"): tt.a.RawBytes()=[%s]; want [%s]`, i, tt.name, hx, tt.b)
|
if err != nil {
|
||||||
continue
|
t.Errorf(`%d("%s"): tt.a.Membuf() failed: %s`, i, tt.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ps := m.Pieces()
|
||||||
|
if !reflect.DeepEqual(ps, tt.ps) {
|
||||||
|
t.Errorf(`%d("%s"): tt.Membuf().Pieces() = %v; want %v`, i, tt.name, ps, tt.ps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ type Membuf struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Piece struct {
|
type Piece struct {
|
||||||
Addr int
|
Addr uint32
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ func (m *Membuf) Pieces() []Piece {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
p = &Piece{Addr: a}
|
p = &Piece{Addr: uint32(a)}
|
||||||
}
|
}
|
||||||
p.Data = append(p.Data, byte(d-1))
|
p.Data = append(p.Data, byte(d-1))
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ func (m *Membuf) Piece(fill byte) Piece {
|
|||||||
} else {
|
} else {
|
||||||
if !started {
|
if !started {
|
||||||
started = true
|
started = true
|
||||||
p.Addr = a
|
p.Addr = uint32(a)
|
||||||
}
|
}
|
||||||
p.Data = append(p.Data, byte(d-1))
|
p.Data = append(p.Data, byte(d-1))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user