2014-03-05 01:42:51 +00:00
|
|
|
package flavors
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
2014-05-22 04:44:23 +00:00
|
|
|
"reflect"
|
2014-03-05 01:42:51 +00:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/zellyn/go6502/asm"
|
2014-06-13 00:39:48 +00:00
|
|
|
"github.com/zellyn/go6502/asm/flavors/merlin"
|
2014-06-03 15:46:49 +00:00
|
|
|
"github.com/zellyn/go6502/asm/flavors/redbook"
|
2014-03-05 01:42:51 +00:00
|
|
|
"github.com/zellyn/go6502/asm/flavors/scma"
|
|
|
|
"github.com/zellyn/go6502/asm/lines"
|
2014-05-22 04:44:23 +00:00
|
|
|
"github.com/zellyn/go6502/asm/membuf"
|
2016-04-22 10:40:00 +00:00
|
|
|
"github.com/zellyn/go6502/opcodes"
|
2014-03-05 01:42:51 +00:00
|
|
|
)
|
|
|
|
|
2014-05-22 04:44:23 +00:00
|
|
|
// h converts from hex or panics.
|
|
|
|
func h(s string) []byte {
|
|
|
|
b, err := hex.DecodeString(s)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2014-08-22 15:24:30 +00:00
|
|
|
type asmFactory func() *asm.Assembler
|
|
|
|
|
2014-03-05 01:42:51 +00:00
|
|
|
func TestMultiline(t *testing.T) {
|
|
|
|
o := lines.NewTestOpener()
|
|
|
|
|
2014-08-22 15:24:30 +00:00
|
|
|
ss := asmFactory(func() *asm.Assembler {
|
2016-05-06 01:18:29 +00:00
|
|
|
return asm.NewAssembler(scma.New(opcodes.SetSweet16), o)
|
2014-08-22 15:24:30 +00:00
|
|
|
})
|
|
|
|
ra := asmFactory(func() *asm.Assembler {
|
2016-05-06 01:18:29 +00:00
|
|
|
return asm.NewAssembler(redbook.NewRedbookA(opcodes.SetSweet16), o)
|
2014-08-22 15:24:30 +00:00
|
|
|
})
|
|
|
|
mm := asmFactory(func() *asm.Assembler {
|
2016-05-06 01:18:29 +00:00
|
|
|
return asm.NewAssembler(merlin.New(opcodes.SetSweet16), o)
|
2014-08-22 15:24:30 +00:00
|
|
|
})
|
2014-03-05 01:42:51 +00:00
|
|
|
|
|
|
|
tests := []struct {
|
2014-08-22 15:24:30 +00:00
|
|
|
af asmFactory // assembler factory
|
2014-03-05 01:42:51 +00:00
|
|
|
name string // name
|
|
|
|
i []string // main file: lines
|
|
|
|
ii map[string][]string // other files: lines
|
|
|
|
b string // bytes, expected
|
2014-05-22 04:44:23 +00:00
|
|
|
ps []membuf.Piece // output bytes expected
|
2014-03-05 01:42:51 +00:00
|
|
|
active bool
|
|
|
|
}{
|
|
|
|
// We cannot determine L2, so we go wide.
|
|
|
|
{ss, "Unknown label: wide", []string{
|
|
|
|
"L1 LDA L2-L1",
|
|
|
|
"L2 NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "ad0300ea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
|
2014-05-05 03:51:58 +00:00
|
|
|
// sc-asm sets instruction widths on the first pass
|
|
|
|
{ss, "Later label: wide", []string{
|
|
|
|
" LDA FOO",
|
|
|
|
"FOO .EQ $FF",
|
|
|
|
" NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "adff00ea", nil, true},
|
2014-05-05 03:51:58 +00:00
|
|
|
|
2014-03-05 01:42:51 +00:00
|
|
|
// Sub-labels
|
2014-06-13 00:39:48 +00:00
|
|
|
{ss, "ss:Sublabels", []string{
|
2014-03-05 01:42:51 +00:00
|
|
|
"L1 BEQ .1",
|
|
|
|
".1 NOP",
|
|
|
|
"L2 BEQ .2",
|
|
|
|
".1 NOP",
|
|
|
|
".2 NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "f000eaf001eaea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
|
2014-06-13 00:39:48 +00:00
|
|
|
// Sub-labels
|
|
|
|
{mm, "mm:Sublabels", []string{
|
|
|
|
"L1 BEQ :ONE",
|
|
|
|
":ONE NOP",
|
|
|
|
"L2 BEQ :TWO",
|
|
|
|
":ONE NOP",
|
|
|
|
":TWO NOP",
|
|
|
|
}, nil, "f000eaf001eaea", nil, true},
|
|
|
|
|
2014-03-05 01:42:51 +00:00
|
|
|
// Includes: one level deep
|
|
|
|
{ss, "Include A", []string{
|
|
|
|
" BEQ OVER",
|
|
|
|
" .IN SUBFILE1",
|
|
|
|
"OVER NOP",
|
|
|
|
}, map[string][]string{
|
|
|
|
"SUBFILE1": {
|
|
|
|
" LDA #$2a",
|
|
|
|
},
|
2014-05-22 04:44:23 +00:00
|
|
|
}, "f002a92aea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
|
|
|
|
// Ifdefs: simple at first
|
|
|
|
{ss, "Ifdef A", []string{
|
|
|
|
"L1 .EQ $17",
|
|
|
|
" .DO L1>$16",
|
|
|
|
" LDA #$01",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$02",
|
|
|
|
" .FIN",
|
|
|
|
" NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "a901ea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
|
|
|
|
// Ifdefs: else part
|
|
|
|
{ss, "Ifdef B", []string{
|
|
|
|
"L1 .EQ $16",
|
|
|
|
" .DO L1>$16",
|
|
|
|
" LDA #$01",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$02",
|
|
|
|
" .FIN",
|
|
|
|
" NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "a902ea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
|
|
|
|
// Ifdefs: multiple else, true
|
|
|
|
{ss, "Ifdef C", []string{
|
|
|
|
"L1 .EQ $17",
|
|
|
|
" .DO L1>$16",
|
|
|
|
" LDA #$01",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$02",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$03",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$04",
|
|
|
|
" .FIN",
|
|
|
|
" NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "a901a903ea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
|
|
|
|
// Ifdefs: multiple else, false
|
|
|
|
{ss, "Ifdef D", []string{
|
|
|
|
"L1 .EQ $16",
|
|
|
|
" .DO L1>$16",
|
|
|
|
" LDA #$01",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$02",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$03",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$04",
|
|
|
|
" .FIN",
|
|
|
|
" NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "a902a904ea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
|
|
|
|
// Ifdef based on org, true
|
|
|
|
{ss, "Ifdef/org A", []string{
|
|
|
|
" .OR $1000",
|
|
|
|
" LDA #$01",
|
|
|
|
" .DO *=$1002",
|
|
|
|
" LDA #$02",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$03",
|
|
|
|
" .FIN",
|
|
|
|
" NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "a901a902ea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
|
|
|
|
// Ifdef based on org, false
|
|
|
|
{ss, "Ifdef/org B", []string{
|
|
|
|
" .OR $1000",
|
|
|
|
" LDA #$01",
|
|
|
|
" .DO *=$1003",
|
|
|
|
" LDA #$02",
|
|
|
|
" .ELSE",
|
|
|
|
" LDA #$03",
|
|
|
|
" .FIN",
|
|
|
|
" NOP",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "a901a903ea", nil, true},
|
2014-05-08 00:44:03 +00:00
|
|
|
|
|
|
|
// Macro, simple
|
|
|
|
{ss, "Macro, simple", []string{
|
|
|
|
" .MA INCD MACRO NAME",
|
|
|
|
" INC ]1 CALL PARAMETER",
|
|
|
|
" BNE :1 PRIVATE LABEL",
|
2014-05-08 23:44:08 +00:00
|
|
|
" INC ]1+1",
|
2014-05-08 00:44:03 +00:00
|
|
|
":1",
|
|
|
|
" .EM END OF DEFINITION",
|
|
|
|
" >INCD PTR",
|
|
|
|
"PTR .HS 0000",
|
|
|
|
"ZPTR .EQ $42",
|
|
|
|
" >INCD ZPTR",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "ee0808d003ee09080000e642d002e643", nil, true},
|
2014-05-08 00:44:03 +00:00
|
|
|
|
|
|
|
// Macro, conditional assembly
|
|
|
|
{ss, "Macro, conditional assembly", []string{
|
|
|
|
" *--------------------------------",
|
|
|
|
" * DEMONSTRATE CONDITIONAL ASSEMBLY IN",
|
|
|
|
" *-------------------------------- MACRO",
|
|
|
|
" .MA INCD",
|
|
|
|
" .DO ]#=2",
|
|
|
|
" INC ]1,]2",
|
|
|
|
" BNE :3",
|
|
|
|
" INC ]1+1,]2",
|
|
|
|
":3",
|
|
|
|
" .ELSE",
|
|
|
|
" INC ]1",
|
|
|
|
" BNE :3",
|
|
|
|
" INC ]1+1",
|
|
|
|
":3",
|
|
|
|
" .FIN",
|
|
|
|
" .EM",
|
|
|
|
" *--------------------------------",
|
|
|
|
" >INCD $12",
|
|
|
|
" >INCD $1234",
|
|
|
|
" >INCD $12,X",
|
|
|
|
" >INCD $1234,X",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, nil, "e612d002e613ee3412d003ee3512f612d002f613fe3412d003fe3512", nil, true},
|
2014-05-08 00:44:03 +00:00
|
|
|
|
|
|
|
// Macros, nested
|
|
|
|
{ss, "Macros, nested", []string{
|
|
|
|
" .MA CALL",
|
|
|
|
" JSR ]1",
|
|
|
|
" .DO ]#>1",
|
|
|
|
" JSR ]2",
|
|
|
|
" .FIN",
|
|
|
|
" .DO ]#>2",
|
|
|
|
" JSR ]3",
|
|
|
|
" .FIN",
|
|
|
|
" .EM",
|
|
|
|
" >CALL SAM,TOM,JOE",
|
|
|
|
" >CALL SAM,TOM",
|
|
|
|
"SAM RTS",
|
|
|
|
"JOE RTS",
|
|
|
|
"TOM RTS",
|
2014-05-22 04:44:23 +00:00
|
|
|
}, 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},
|
2014-06-03 15:46:49 +00:00
|
|
|
|
|
|
|
// Check turning MSB on and off
|
2014-06-04 15:35:31 +00:00
|
|
|
{ra, "MSB toggle", []string{
|
2014-06-03 15:46:49 +00:00
|
|
|
" ASC 'AB'",
|
|
|
|
" MSB OFF",
|
|
|
|
" ASC 'AB'",
|
|
|
|
" MSB ON",
|
|
|
|
" ASC 'AB'",
|
|
|
|
}, nil, "c1c24142c1c2", nil, true},
|
2014-06-13 00:39:48 +00:00
|
|
|
|
|
|
|
// Merlin: macros, local labels
|
|
|
|
{mm, "Macros: local labels", []string{
|
|
|
|
"L1 NOP",
|
|
|
|
"M1 MAC",
|
|
|
|
" INC ]1",
|
|
|
|
" BNE L1",
|
|
|
|
"L1 NOP",
|
|
|
|
" <<<",
|
|
|
|
" >>> M1.$42",
|
|
|
|
" PMC M1($43;$44",
|
|
|
|
" M1 $44",
|
|
|
|
}, nil, "eae642d000eae643d000eae644d000ea", nil, true},
|
2014-07-31 23:33:10 +00:00
|
|
|
|
|
|
|
// Merlin: macros in other files
|
|
|
|
{mm, "macro in include", []string{
|
|
|
|
" ORG $E000",
|
|
|
|
" USE MACROS",
|
|
|
|
"PROMPT = $0033",
|
|
|
|
" INCW PROMPT",
|
|
|
|
" NOP",
|
|
|
|
}, map[string][]string{
|
|
|
|
"T.MACROS": {
|
|
|
|
"* Increment word",
|
|
|
|
"INCW MAC",
|
|
|
|
" INC ]1",
|
|
|
|
" BNE INCW_END",
|
|
|
|
" INC ]1+1",
|
|
|
|
"INCW_END EOM",
|
|
|
|
},
|
|
|
|
}, "e633d002e634ea", nil, true},
|
|
|
|
|
|
|
|
// Merlin: macros override the 3+1 zero-page rule
|
|
|
|
{mm, "macro vs zero page override", []string{
|
|
|
|
" USE MACROS",
|
|
|
|
"A1 = $0033",
|
|
|
|
"A2 = $0035",
|
|
|
|
" CMPW A1;A2",
|
|
|
|
" NOP",
|
|
|
|
}, map[string][]string{
|
|
|
|
"T.MACROS": {
|
|
|
|
"* Compare word",
|
|
|
|
"CMPW MAC",
|
|
|
|
" LDA ]1",
|
|
|
|
" CMP ]2",
|
|
|
|
" LDA ]1+1",
|
|
|
|
" SBC ]2+1",
|
|
|
|
" EOM",
|
|
|
|
},
|
|
|
|
}, "a533c535a534e536ea", nil, true},
|
2014-09-09 03:59:14 +00:00
|
|
|
|
|
|
|
// Merlin: DB with unknown addresses
|
|
|
|
{mm, "DB with unknown addresses", []string{
|
|
|
|
"ONE = $0123",
|
|
|
|
"TWO = $4567",
|
|
|
|
" DB <ONE,<$FFFF,<$FFFF,<TWO",
|
|
|
|
" DB <THREE,<FOUR",
|
|
|
|
"THREE = $89ab",
|
|
|
|
"FOUR = $cdef",
|
|
|
|
" NOP",
|
|
|
|
}, nil, "23ffff67abefea", nil, true},
|
2014-03-05 01:42:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for i, tt := range tests {
|
|
|
|
if !tt.active {
|
|
|
|
continue
|
|
|
|
}
|
2014-08-22 15:24:30 +00:00
|
|
|
a := tt.af()
|
2014-05-22 04:44:23 +00:00
|
|
|
if tt.b == "" && len(tt.ps) == 0 {
|
2014-08-22 15:24:30 +00:00
|
|
|
t.Fatalf(`%d("%s" - %s): test case must specify bytes or pieces`, i, tt.name, a.Flavor)
|
2014-05-22 04:44:23 +00:00
|
|
|
}
|
2014-03-05 01:42:51 +00:00
|
|
|
o.Clear()
|
|
|
|
o["TESTFILE"] = strings.Join(tt.i, "\n")
|
|
|
|
for k, v := range tt.ii {
|
|
|
|
o[k] = strings.Join(v, "\n")
|
|
|
|
}
|
2014-08-22 15:24:30 +00:00
|
|
|
if err := a.Load("TESTFILE", 0); err != nil {
|
|
|
|
t.Fatalf(`%d("%s" - %s): a.Load("TESTFILE") failed: %s`, i, tt.name, a.Flavor, err)
|
2014-03-05 01:42:51 +00:00
|
|
|
}
|
2014-08-22 15:24:30 +00:00
|
|
|
err := a.Pass2()
|
2014-03-05 01:42:51 +00:00
|
|
|
if err != nil {
|
2014-08-22 15:24:30 +00:00
|
|
|
t.Fatalf(`%d("%s" - %s): a.Pass(true) failed: %s`, i, tt.name, a.Flavor, err)
|
2014-03-05 01:42:51 +00:00
|
|
|
}
|
2014-05-22 04:44:23 +00:00
|
|
|
|
|
|
|
if tt.b != "" {
|
2014-08-22 15:24:30 +00:00
|
|
|
bb, err := a.RawBytes()
|
2014-05-22 04:44:23 +00:00
|
|
|
if err != nil {
|
2014-08-22 15:24:30 +00:00
|
|
|
t.Fatalf(`%d("%s" - %s): a.RawBytes() failed: %s`, i, tt.name, a.Flavor, err)
|
2014-05-22 04:44:23 +00:00
|
|
|
}
|
|
|
|
hx := hex.EncodeToString(bb)
|
|
|
|
if hx != tt.b {
|
2014-08-22 15:24:30 +00:00
|
|
|
t.Fatalf(`%d("%s" - %s): a.RawBytes()=[%s]; want [%s]`, i, tt.name, a.Flavor, hx, tt.b)
|
2014-05-22 04:44:23 +00:00
|
|
|
}
|
2014-03-05 01:42:51 +00:00
|
|
|
}
|
2014-05-22 04:44:23 +00:00
|
|
|
if len(tt.ps) != 0 {
|
2014-08-22 15:24:30 +00:00
|
|
|
m, err := a.Membuf()
|
2014-05-22 04:44:23 +00:00
|
|
|
if err != nil {
|
2014-08-22 15:24:30 +00:00
|
|
|
t.Fatalf(`%d("%s" - %s): a.Membuf() failed: %s`, i, tt.name, a.Flavor, err)
|
2014-05-22 04:44:23 +00:00
|
|
|
}
|
|
|
|
ps := m.Pieces()
|
|
|
|
if !reflect.DeepEqual(ps, tt.ps) {
|
2014-08-22 15:24:30 +00:00
|
|
|
t.Fatalf(`%d("%s" - %s): tt.Membuf().Pieces() = %v; want %v`, i, tt.name, a.Flavor, ps, tt.ps)
|
2014-05-22 04:44:23 +00:00
|
|
|
}
|
2014-03-05 01:42:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|