mirror of
https://github.com/zellyn/go6502.git
synced 2025-04-14 17:41:01 +00:00
Can now assemble autostart ROM
This commit is contained in:
parent
9a7f1c2cca
commit
0a28127420
@ -33,6 +33,7 @@ func init() {
|
||||
|
||||
var infile = flag.String("in", "", "input file")
|
||||
var outfile = flag.String("out", "", "output file")
|
||||
var listfile = flag.String("listing", "", "listing 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")
|
||||
|
||||
@ -118,4 +119,26 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "format must be binary or ihex; got '%s'\n", *format)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *listfile != "" {
|
||||
list, err := os.Create(*listfile)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func() {
|
||||
err := list.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
err = a.GenerateListing(list, 3)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error while generating %s: %s", *listfile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
43
asm/asm.go
43
asm/asm.go
@ -2,6 +2,7 @@ package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zellyn/go6502/asm/flavors"
|
||||
@ -266,3 +267,45 @@ func (a *Assembler) Membuf() (*membuf.Membuf, error) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (a *Assembler) GenerateListing(w io.Writer, width int) error {
|
||||
for _, in := range a.Insts {
|
||||
if !in.Final {
|
||||
return in.Errorf("cannot finalize value: %s", in)
|
||||
}
|
||||
if !in.AddrKnown {
|
||||
return in.Errorf("address unknown: %s", in)
|
||||
}
|
||||
|
||||
for i := 0; i < len(in.Data) || i < width; i++ {
|
||||
if i%width == 0 {
|
||||
s := fmt.Sprintf("%04x:", int(in.Addr)+i)
|
||||
if i > 0 {
|
||||
s = "\n" + s
|
||||
}
|
||||
if _, err := fmt.Fprint(w, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s := " "
|
||||
if i < len(in.Data) {
|
||||
s = fmt.Sprintf(" %02x", in.Data[i])
|
||||
}
|
||||
if _, err := fmt.Fprint(w, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i == width-1 {
|
||||
if _, err := fmt.Fprint(w, " "+in.Line.Text()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if _, err := fmt.Fprint(w, "\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ type SimpleContext struct {
|
||||
addr int32
|
||||
lastLabel string
|
||||
clearMesg string // Saved message describing why Addr was cleared.
|
||||
highbit byte // OR-mask for ASCII high bit
|
||||
}
|
||||
|
||||
type symbolValue struct {
|
||||
@ -91,4 +92,5 @@ func (sc *SimpleContext) RemoveChanged() {
|
||||
|
||||
func (sc *SimpleContext) Clear() {
|
||||
sc.symbols = make(map[string]symbolValue)
|
||||
sc.highbit = 0x00
|
||||
}
|
||||
|
@ -24,12 +24,10 @@ const macroNameChars = Letters + Digits + "._"
|
||||
const fileChars = Letters + Digits + "."
|
||||
const operatorChars = "+-*/<>="
|
||||
|
||||
// 40 spaces = comment column
|
||||
const comment_whitespace_prefix = " "
|
||||
|
||||
type DirectiveInfo struct {
|
||||
Type inst.Type
|
||||
Func func(inst.I, *lines.Parse) (inst.I, error)
|
||||
Var int
|
||||
}
|
||||
|
||||
type Requiredness int
|
||||
@ -47,9 +45,13 @@ type Base struct {
|
||||
Operators map[string]expr.Operator
|
||||
context.SimpleContext
|
||||
context.LabelerBase
|
||||
LabelChars string
|
||||
LabelColons Requiredness
|
||||
ExplicitARegister Requiredness
|
||||
LabelChars string
|
||||
LabelColons Requiredness
|
||||
ExplicitARegister Requiredness
|
||||
ExtraCommenty func(string) bool
|
||||
TwoSpacesIsComment bool // two spaces after command means comment field?
|
||||
StringEndOptional bool // can omit closing delimeter from string args?
|
||||
SetAsciiVariation func(*inst.I, *lines.Parse)
|
||||
}
|
||||
|
||||
// Parse an entire instruction, or return an appropriate error.
|
||||
@ -73,8 +75,8 @@ func (a *Base) ParseInstr(line lines.Line) (inst.I, error) {
|
||||
in.DeclaredLine = uint16(i)
|
||||
}
|
||||
|
||||
// Comment by virtue of long whitespace prefix
|
||||
if strings.HasPrefix(lp.Rest(), comment_whitespace_prefix) {
|
||||
// Flavor considers this line extra commenty for some reason?
|
||||
if a.ExtraCommenty != nil && a.ExtraCommenty(lp.Rest()) {
|
||||
in.Type = inst.TypeNone
|
||||
return in, nil
|
||||
}
|
||||
@ -89,6 +91,16 @@ func (a *Base) ParseInstr(line lines.Line) (inst.I, error) {
|
||||
// See if we have a label at the start
|
||||
if lp.AcceptRun(a.LabelChars) {
|
||||
in.Label = lp.Emit()
|
||||
|
||||
// Some need colons after labels, some allow them.
|
||||
switch a.LabelColons {
|
||||
case ReqRequired:
|
||||
if !lp.Consume(":") {
|
||||
return inst.I{}, line.Errorf("label '%s' must end in colon", in.Label)
|
||||
}
|
||||
case ReqOptional:
|
||||
lp.Consume(":")
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore whitespace at the start or after the label.
|
||||
@ -122,6 +134,7 @@ func (a *Base) ParseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
in.Command = lp.Emit()
|
||||
if dir, ok := a.Directives[in.Command]; ok {
|
||||
in.Type = dir.Type
|
||||
in.Var = dir.Var
|
||||
if dir.Func == nil {
|
||||
return in, nil
|
||||
}
|
||||
@ -217,7 +230,11 @@ func (a *Base) ParseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
|
||||
|
||||
// Nothing else on the line? Must be MODE_A
|
||||
lp.Consume(whitespace)
|
||||
if lp.Consume(whitespace) || lp.Peek() == lines.Eol {
|
||||
if !a.TwoSpacesIsComment {
|
||||
lp.IgnoreRun(whitespace)
|
||||
}
|
||||
if (a.TwoSpacesIsComment && lp.Consume(whitespace)) || lp.Peek() == lines.Eol || lp.Peek() == ';' {
|
||||
// Nothing left on line except comments.
|
||||
if !summary.AnyModes(opcodes.MODE_A) {
|
||||
return i, in.Errorf("%s with no arguments", in.Command)
|
||||
}
|
||||
@ -243,6 +260,29 @@ func (a *Base) ParseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
if !indirect && (expr.Text == "a" || expr.Text == "A") {
|
||||
if !summary.AnyModes(opcodes.MODE_A) {
|
||||
return i, in.Errorf("%s doesn't support A mode", in.Command)
|
||||
}
|
||||
switch a.ExplicitARegister {
|
||||
case ReqDisallowed:
|
||||
return i, in.Errorf("Assembler flavor doesn't support A mode", in.Command)
|
||||
case ReqOptional, ReqRequired:
|
||||
op, ok := summary.OpForMode(opcodes.MODE_A)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s doesn't support accumulator mode", in.Command))
|
||||
}
|
||||
in.Data = []byte{op.Byte}
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 1
|
||||
in.MaxWidth = 1
|
||||
in.Final = true
|
||||
in.Mode = opcodes.MODE_A
|
||||
in.Exprs = nil
|
||||
return in, nil
|
||||
|
||||
}
|
||||
}
|
||||
in.Exprs = append(in.Exprs, expr)
|
||||
comma := lp.Consume(",")
|
||||
if comma {
|
||||
@ -293,12 +333,19 @@ func (a *Base) ParseAddress(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
|
||||
func (a *Base) ParseAscii(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
a.SetAsciiVariation(&in, lp)
|
||||
var invert, invertLast byte
|
||||
if lp.Consume("-") {
|
||||
switch in.Var {
|
||||
case inst.DataAscii:
|
||||
case inst.DataAsciiHi:
|
||||
invert = 0x80
|
||||
}
|
||||
if in.Command == ".AT" {
|
||||
case inst.DataAsciiFlip:
|
||||
invertLast = 0x80
|
||||
case inst.DataAsciiHiFlip:
|
||||
invert = 0x80
|
||||
invertLast = 0x80
|
||||
default:
|
||||
panic(fmt.Sprintf("ParseAscii with weird Variation: %d", in.Var))
|
||||
}
|
||||
delim := lp.Next()
|
||||
if delim == lines.Eol || strings.IndexRune(whitespace, delim) >= 0 {
|
||||
@ -307,7 +354,7 @@ func (a *Base) ParseAscii(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.Ignore()
|
||||
lp.AcceptUntil(string(delim))
|
||||
delim2 := lp.Next()
|
||||
if delim != delim2 {
|
||||
if delim != delim2 && !(delim2 == lines.Eol && a.StringEndOptional) {
|
||||
return inst.I{}, in.Errorf("%s: expected closing delimeter '%s'; got '%s'", in.Command, delim, delim2)
|
||||
}
|
||||
lp.Backup()
|
||||
|
@ -1,14 +1,18 @@
|
||||
package redbook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zellyn/go6502/asm/expr"
|
||||
"github.com/zellyn/go6502/asm/flavors/oldschool"
|
||||
"github.com/zellyn/go6502/asm/inst"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
)
|
||||
|
||||
// RedBook implements a Redbook-listing-compatible-ish assembler flavor.
|
||||
type RedBook struct {
|
||||
oldschool.Base
|
||||
asciiHi bool
|
||||
}
|
||||
|
||||
func New() *RedBook {
|
||||
@ -17,35 +21,35 @@ func New() *RedBook {
|
||||
a.LabelChars = oldschool.Letters + oldschool.Digits + "."
|
||||
a.LabelColons = oldschool.ReqOptional
|
||||
a.ExplicitARegister = oldschool.ReqRequired
|
||||
a.StringEndOptional = true
|
||||
|
||||
a.Directives = map[string]oldschool.DirectiveInfo{
|
||||
".IN": {inst.TypeInclude, a.ParseInclude},
|
||||
"ORG": {inst.TypeOrg, a.ParseAddress},
|
||||
"OBJ": {inst.TypeNone, nil},
|
||||
".TF": {inst.TypeNone, nil},
|
||||
".EN": {inst.TypeEnd, a.ParseNoArgDir},
|
||||
"EQU": {inst.TypeEqu, a.ParseEquate},
|
||||
".DA": {inst.TypeData, a.ParseData},
|
||||
"DFB": {inst.TypeDataBytes, a.ParseData},
|
||||
"DW": {inst.TypeDataWords, a.ParseData},
|
||||
".HS": {inst.TypeData, a.ParseHexString},
|
||||
".AS": {inst.TypeData, a.ParseAscii},
|
||||
".AT": {inst.TypeData, a.ParseAscii},
|
||||
".BS": {inst.TypeBlock, a.ParseBlockStorage},
|
||||
".LIST": {inst.TypeNone, nil},
|
||||
".PG": {inst.TypeNone, nil},
|
||||
".DO": {inst.TypeIfdef, a.ParseDo},
|
||||
".ELSE": {inst.TypeIfdefElse, a.ParseNoArgDir},
|
||||
".FIN": {inst.TypeIfdefEnd, a.ParseNoArgDir},
|
||||
".MA": {inst.TypeMacroStart, a.ParseMacroStart},
|
||||
".EM": {inst.TypeMacroEnd, a.ParseNoArgDir},
|
||||
".US": {inst.TypeNone, a.ParseNotImplemented},
|
||||
"PAGE": {inst.TypeNone, nil}, // New page
|
||||
"LST": {inst.TypeNone, nil}, // Listing on/off
|
||||
"SBTL": {inst.TypeNone, nil}, // Subtitle
|
||||
"SKP": {inst.TypeNone, nil}, // Skip lines
|
||||
"REP": {inst.TypeNone, nil}, // Repeat character
|
||||
"CHR": {inst.TypeNone, nil}, // Set repeated character
|
||||
".IN": {inst.TypeInclude, a.ParseInclude, 0},
|
||||
"ORG": {inst.TypeOrg, a.ParseAddress, 0},
|
||||
"OBJ": {inst.TypeNone, nil, 0},
|
||||
".TF": {inst.TypeNone, nil, 0},
|
||||
".EN": {inst.TypeEnd, a.ParseNoArgDir, 0},
|
||||
"EQU": {inst.TypeEqu, a.ParseEquate, 0},
|
||||
"DFB": {inst.TypeData, a.ParseData, inst.DataBytes},
|
||||
"DW": {inst.TypeData, a.ParseData, inst.DataWordsLe},
|
||||
"DDB": {inst.TypeData, a.ParseData, inst.DataWordsBe},
|
||||
"ASC": {inst.TypeData, a.ParseAscii, inst.DataAscii},
|
||||
"DCI": {inst.TypeData, a.ParseAscii, inst.DataAsciiFlip},
|
||||
".BS": {inst.TypeBlock, a.ParseBlockStorage, 0},
|
||||
".LIST": {inst.TypeNone, nil, 0},
|
||||
".PG": {inst.TypeNone, nil, 0},
|
||||
".DO": {inst.TypeIfdef, a.ParseDo, 0},
|
||||
".ELSE": {inst.TypeIfdefElse, a.ParseNoArgDir, 0},
|
||||
".FIN": {inst.TypeIfdefEnd, a.ParseNoArgDir, 0},
|
||||
".MA": {inst.TypeMacroStart, a.ParseMacroStart, 0},
|
||||
".EM": {inst.TypeMacroEnd, a.ParseNoArgDir, 0},
|
||||
".US": {inst.TypeNone, a.ParseNotImplemented, 0},
|
||||
"PAGE": {inst.TypeNone, nil, 0}, // New page
|
||||
"LST": {inst.TypeNone, nil, 0}, // Listing on/off
|
||||
"SBTL": {inst.TypeNone, nil, 0}, // Subtitle
|
||||
"SKP": {inst.TypeNone, nil, 0}, // Skip lines
|
||||
"REP": {inst.TypeNone, nil, 0}, // Repeat character
|
||||
"CHR": {inst.TypeNone, nil, 0}, // Set repeated character
|
||||
}
|
||||
a.Operators = map[string]expr.Operator{
|
||||
"*": expr.OpMul,
|
||||
@ -57,5 +61,21 @@ func New() *RedBook {
|
||||
"=": expr.OpEq,
|
||||
}
|
||||
|
||||
a.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) {
|
||||
if in.Command == "ASC" {
|
||||
if a.asciiHi {
|
||||
in.Var = inst.DataAsciiHi
|
||||
} else {
|
||||
in.Var = inst.DataAscii
|
||||
}
|
||||
return
|
||||
}
|
||||
if in.Command == "DCI" {
|
||||
in.Var = inst.DataAsciiFlip
|
||||
} else {
|
||||
panic(fmt.Sprintf("Unknown ascii directive: '%s'", in.Command))
|
||||
}
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
package scma
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zellyn/go6502/asm/expr"
|
||||
"github.com/zellyn/go6502/asm/flavors/oldschool"
|
||||
"github.com/zellyn/go6502/asm/inst"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
)
|
||||
|
||||
// 40 spaces = comment column
|
||||
const commentWhitespacePrefix = " "
|
||||
|
||||
// SCMA implements the S-C Macro Assembler-compatible assembler flavor.
|
||||
// See http://www.txbobsc.com/scsc/ and http://stjarnhimlen.se/apple2/
|
||||
type SCMA struct {
|
||||
@ -17,28 +23,29 @@ func New() *SCMA {
|
||||
a.LabelChars = oldschool.Letters + oldschool.Digits + ".:"
|
||||
a.LabelColons = oldschool.ReqDisallowed
|
||||
a.ExplicitARegister = oldschool.ReqDisallowed
|
||||
a.TwoSpacesIsComment = true
|
||||
|
||||
a.Directives = map[string]oldschool.DirectiveInfo{
|
||||
".IN": {inst.TypeInclude, a.ParseInclude},
|
||||
".OR": {inst.TypeOrg, a.ParseAddress},
|
||||
".TA": {inst.TypeTarget, a.ParseNotImplemented},
|
||||
".TF": {inst.TypeNone, nil},
|
||||
".EN": {inst.TypeEnd, a.ParseNoArgDir},
|
||||
".EQ": {inst.TypeEqu, a.ParseEquate},
|
||||
".DA": {inst.TypeData, a.ParseData},
|
||||
".HS": {inst.TypeData, a.ParseHexString},
|
||||
".AS": {inst.TypeData, a.ParseAscii},
|
||||
".AT": {inst.TypeData, a.ParseAscii},
|
||||
".BS": {inst.TypeBlock, a.ParseBlockStorage},
|
||||
".TI": {inst.TypeNone, nil},
|
||||
".LIST": {inst.TypeNone, nil},
|
||||
".PG": {inst.TypeNone, nil},
|
||||
".DO": {inst.TypeIfdef, a.ParseDo},
|
||||
".ELSE": {inst.TypeIfdefElse, a.ParseNoArgDir},
|
||||
".FIN": {inst.TypeIfdefEnd, a.ParseNoArgDir},
|
||||
".MA": {inst.TypeMacroStart, a.ParseMacroStart},
|
||||
".EM": {inst.TypeMacroEnd, a.ParseNoArgDir},
|
||||
".US": {inst.TypeNone, a.ParseNotImplemented},
|
||||
".IN": {inst.TypeInclude, a.ParseInclude, 0},
|
||||
".OR": {inst.TypeOrg, a.ParseAddress, 0},
|
||||
".TA": {inst.TypeTarget, a.ParseNotImplemented, 0},
|
||||
".TF": {inst.TypeNone, nil, 0},
|
||||
".EN": {inst.TypeEnd, a.ParseNoArgDir, 0},
|
||||
".EQ": {inst.TypeEqu, a.ParseEquate, 0},
|
||||
".DA": {inst.TypeData, a.ParseData, inst.DataMixed},
|
||||
".HS": {inst.TypeData, a.ParseHexString, inst.DataBytes},
|
||||
".AS": {inst.TypeData, a.ParseAscii, inst.DataBytes},
|
||||
".AT": {inst.TypeData, a.ParseAscii, inst.DataBytes},
|
||||
".BS": {inst.TypeBlock, a.ParseBlockStorage, 0},
|
||||
".TI": {inst.TypeNone, nil, 0},
|
||||
".LIST": {inst.TypeNone, nil, 0},
|
||||
".PG": {inst.TypeNone, nil, 0},
|
||||
".DO": {inst.TypeIfdef, a.ParseDo, 0},
|
||||
".ELSE": {inst.TypeIfdefElse, a.ParseNoArgDir, 0},
|
||||
".FIN": {inst.TypeIfdefEnd, a.ParseNoArgDir, 0},
|
||||
".MA": {inst.TypeMacroStart, a.ParseMacroStart, 0},
|
||||
".EM": {inst.TypeMacroEnd, a.ParseNoArgDir, 0},
|
||||
".US": {inst.TypeNone, a.ParseNotImplemented, 0},
|
||||
}
|
||||
a.Operators = map[string]expr.Operator{
|
||||
"*": expr.OpMul,
|
||||
@ -50,6 +57,27 @@ func New() *SCMA {
|
||||
"=": expr.OpEq,
|
||||
}
|
||||
|
||||
a.ExtraCommenty = func(s string) bool {
|
||||
//Comment by virtue of long whitespace prefix
|
||||
return strings.HasPrefix(s, commentWhitespacePrefix)
|
||||
}
|
||||
|
||||
a.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) {
|
||||
// For S-C Assembler, leading "-" flips high bit
|
||||
invert := lp.Consume("-")
|
||||
invertLast := in.Command == ".AT"
|
||||
switch {
|
||||
case !invert && !invertLast:
|
||||
in.Var = inst.DataAscii
|
||||
case !invert && invertLast:
|
||||
in.Var = inst.DataAsciiFlip
|
||||
case invert && !invertLast:
|
||||
in.Var = inst.DataAsciiHi
|
||||
case invert && invertLast:
|
||||
in.Var = inst.DataAsciiHiFlip
|
||||
}
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
|
@ -51,14 +51,20 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
{mm, " ORG $D000", "{org $d000}", ""},
|
||||
// {ss, " .TA *-1234", "{target (- * $04d2)}", ""},
|
||||
{ss, " .DA $1234", "{data $1234}", "3412"},
|
||||
{aa, " dw $1234", "{data $1234}", "3412"},
|
||||
{mm, " DW $1234", "{data $1234}", "3412"},
|
||||
{aa, " dw $1234", "{data/wle $1234}", "3412"},
|
||||
{mm, " DW $1234", "{data/wle $1234}", "3412"},
|
||||
{ss, " .DA/$1234,#$1234,$1234", "{data (msb $1234),(lsb $1234),$1234}", "12343412"},
|
||||
{rb, " DFB $12", "{data/b $0012}", "12"},
|
||||
{rb, " DFB $12,$34,$1234", "{data/b $0012,$0034,$1234}", "123434"},
|
||||
{rb, " DW $12,$34,$1234", "{data/wle $0012,$0034,$1234}", "120034003412"},
|
||||
{rb, " DDB $12,$34,$1234", "{data/wbe $0012,$0034,$1234}", "001200341234"},
|
||||
{ss, " ROL", "{ROL/a}", "2a"},
|
||||
{aa, " rol a", "{ROL/a}", "2a"},
|
||||
{rb, " ROL A", "{ROL/a}", "2a"},
|
||||
{rb, " ROL A", "{ROL/a}", "2a"}, // two spaces is no big deal
|
||||
{mm, " ROL", "{ROL/a}", "2a"},
|
||||
{ss, " ROL Comment after two spaces", "{ROL/a}", "2a"},
|
||||
{ss, " ROL X", "{ROL/a}", "2a"}, // two spaces = comment
|
||||
{ss, " ROL $1234", "{ROL/abs $1234}", "2e3412"},
|
||||
{aa, " rol $1234", "{ROL/abs $1234}", "2e3412"},
|
||||
{mm, " ROL $1234", "{ROL/abs $1234}", "2e3412"},
|
||||
@ -107,17 +113,22 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
{ss, " LDA ($12,X)", "{LDA/indX $0012}", "a112"},
|
||||
{aa, " lda ($12,x)", "{LDA/indX $0012}", "a112"},
|
||||
{mm, " LDA ($12,X)", "{LDA/indX $0012}", "a112"},
|
||||
{ss, ` .AS "ABC"`, "{data}", "414243"},
|
||||
{ss, ` .AT "ABC"`, "{data}", "4142c3"},
|
||||
{ss, ` .AS /ABC/`, "{data}", "414243"},
|
||||
{ss, ` .AT /ABC/`, "{data}", "4142c3"},
|
||||
{ss, ` .AS -"ABC"`, "{data}", "c1c2c3"},
|
||||
{ss, ` .AT -"ABC"`, "{data}", "c1c243"},
|
||||
{ss, ` .AS -dABCd`, "{data}", "c1c2c3"},
|
||||
{ss, ` .AT -dABCd`, "{data}", "c1c243"},
|
||||
{ss, " .HS 0001ffAb", "{data}", "0001ffab"},
|
||||
{ss, ` .AS "ABC"`, "{data/b}", "414243"},
|
||||
{ss, ` .AT "ABC"`, "{data/b}", "4142c3"},
|
||||
{ss, ` .AS /ABC/`, "{data/b}", "414243"},
|
||||
{ss, ` .AT /ABC/`, "{data/b}", "4142c3"},
|
||||
{ss, ` .AS -"ABC"`, "{data/b}", "c1c2c3"},
|
||||
{ss, ` .AT -"ABC"`, "{data/b}", "c1c243"},
|
||||
{ss, ` .AS -dABCd`, "{data/b}", "c1c2c3"},
|
||||
{ss, ` .AT -dABCd`, "{data/b}", "c1c243"},
|
||||
{rb, ` ASC "ABC"`, "{data/b}", "414243"},
|
||||
{rb, ` ASC $ABC$ ;comment`, "{data/b}", "414243"},
|
||||
{rb, ` ASC $ABC`, "{data/b}", "414243"},
|
||||
{rb, ` DCI "ABC"`, "{data/b}", "4142c3"},
|
||||
{rb, ` ASC -ABC-`, "{data/b}", "414243"},
|
||||
{ss, " .HS 0001ffAb", "{data/b}", "0001ffab"},
|
||||
{ss, "A.B .EQ *-C.D", "{= 'A.B' (- * C.D)}", ""},
|
||||
{ss, " .BS $8", "{block $0008}", ""},
|
||||
{ss, " .BS $8", "{block $0008}", "xxxxxxxxxxxxxxxx"},
|
||||
{ss, " .DO A<$3", "{if (< A $0003)}", ""},
|
||||
{ss, " .ELSE", "{else}", ""},
|
||||
{ss, " .FIN", "{endif}", ""},
|
||||
@ -168,8 +179,25 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
|
||||
if tt.b != "?" {
|
||||
hx := hex.EncodeToString(inst.Data)
|
||||
if hx != tt.b {
|
||||
t.Fatalf(`%d. %T.ParseInstr("%s").Data = [%s]; want [%s]`, i, tt.a, tt.i, hx, tt.b)
|
||||
// xxxxxx sets the width, but doesn't expect actual data
|
||||
if hx != tt.b && (len(tt.b) == 0 || tt.b[0] != 'x') {
|
||||
t.Errorf(`%d. %T.ParseInstr("%s").Data = [%s]; want [%s]`, i, tt.a, tt.i, hx, tt.b)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check length
|
||||
w := uint16(len(tt.b) / 2)
|
||||
if !inst.WidthKnown {
|
||||
t.Errorf(`%d. %s.WidthKnown is false`, i, inst)
|
||||
continue
|
||||
}
|
||||
if inst.MinWidth != inst.MaxWidth {
|
||||
t.Errorf(`%d. %s: MinWidth(%d) != MaxWidth(%d)`, i, inst, inst.MinWidth, inst.MaxWidth)
|
||||
continue
|
||||
}
|
||||
if inst.MinWidth != w {
|
||||
t.Errorf(`%d. %s.MinWidth=%d; want %d`, i, inst, inst.MinWidth, w)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,7 @@ const (
|
||||
TypeIfdefElse // Ifdef else block
|
||||
TypeIfdefEnd // Ifdef end
|
||||
TypeInclude // Include a file
|
||||
TypeData // Data: hex, ascii, etc., etc.
|
||||
TypeDataBytes // Data: expressions, but forced to one byte per
|
||||
TypeDataWords // Data: expressions, but forced to one word per
|
||||
TypeData // Data
|
||||
TypeBlock // Block storage
|
||||
TypeOrg // Where to store assembled code
|
||||
TypeTarget // Target address to use for jumps, labels, etc.
|
||||
@ -37,6 +35,18 @@ const (
|
||||
TypeEnd // End assembly
|
||||
)
|
||||
|
||||
// Variants for "TypeData" instructions.
|
||||
const (
|
||||
DataBytes = iota // Data: expressions, but forced to one byte per
|
||||
DataMixed // Bytes or words (LE), depending on individual expression widths
|
||||
DataWordsLe // Data: expressions, but forced to one word per, little-endian
|
||||
DataWordsBe // Data: expressions, but forced to one word per, big-endian
|
||||
DataAscii // Data: from ASCII strings, high bit clear
|
||||
DataAsciiHi // Data: from ASCII strings, high bit set
|
||||
DataAsciiFlip // Data: from ASCII strings, high bit clear, except last char
|
||||
DataAsciiHiFlip // Data: from ASCII strings, high bit set, except last char
|
||||
)
|
||||
|
||||
type I struct {
|
||||
Type Type // Type of instruction
|
||||
Label string // Text of label part
|
||||
@ -54,10 +64,11 @@ type I struct {
|
||||
ZeroMode opcodes.AddressingMode // Possible ZP-option mode
|
||||
ZeroOp byte // Possible ZP-option Opcode
|
||||
Value uint16 // For Equates, the value
|
||||
DeclaredLine uint16
|
||||
Line *lines.Line
|
||||
Addr uint16
|
||||
AddrKnown bool
|
||||
DeclaredLine uint16 // Line number listed in file
|
||||
Line *lines.Line // Line object for this line
|
||||
Addr uint16 // Current memory address
|
||||
AddrKnown bool // Whether the current memory address is known
|
||||
Var int // Variant of instruction type
|
||||
}
|
||||
|
||||
func (i I) TypeString() string {
|
||||
@ -81,7 +92,20 @@ func (i I) TypeString() string {
|
||||
case TypeInclude:
|
||||
return "inc"
|
||||
case TypeData:
|
||||
return "data"
|
||||
switch i.Var {
|
||||
case DataMixed:
|
||||
return "data"
|
||||
case DataBytes:
|
||||
return "data/b"
|
||||
case DataWordsLe:
|
||||
return "data/wle"
|
||||
case DataWordsBe:
|
||||
return "data/wbe"
|
||||
case DataAscii, DataAsciiHi, DataAsciiFlip, DataAsciiHiFlip:
|
||||
return "data/b"
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown data variant: %d", i.Var))
|
||||
}
|
||||
case TypeBlock:
|
||||
return "block"
|
||||
case TypeOrg:
|
||||
@ -243,7 +267,15 @@ func (i *I) computeData(c context.Context, setWidth bool, final bool) (bool, err
|
||||
data := []byte{}
|
||||
var width uint16
|
||||
for _, e := range i.Exprs {
|
||||
w := e.Width()
|
||||
var w uint16
|
||||
switch i.Var {
|
||||
case DataMixed:
|
||||
w = e.Width()
|
||||
case DataBytes:
|
||||
w = 1
|
||||
case DataWordsLe, DataWordsBe:
|
||||
w = 2
|
||||
}
|
||||
width += w
|
||||
val, labelMissing, err := e.CheckedEval(c, i.Line)
|
||||
if err != nil && !labelMissing {
|
||||
@ -255,11 +287,22 @@ func (i *I) computeData(c context.Context, setWidth bool, final bool) (bool, err
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
switch w {
|
||||
case 1:
|
||||
switch i.Var {
|
||||
case DataMixed:
|
||||
switch w {
|
||||
case 1:
|
||||
data = append(data, byte(val))
|
||||
case 2:
|
||||
data = append(data, byte(val), byte(val>>8))
|
||||
}
|
||||
case DataBytes:
|
||||
data = append(data, byte(val))
|
||||
case 2:
|
||||
case DataWordsLe:
|
||||
data = append(data, byte(val), byte(val>>8))
|
||||
case DataWordsBe:
|
||||
data = append(data, byte(val>>8), byte(val))
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown data variant handed to computeData: %d", i.Var))
|
||||
}
|
||||
}
|
||||
i.MinWidth = width
|
||||
@ -390,10 +433,10 @@ func (i *I) computeOp(c context.Context, setWidth bool, final bool) (bool, error
|
||||
// Found both current and target addresses
|
||||
offset := int32(val) - (int32(curr) + 2)
|
||||
if offset > 127 {
|
||||
return false, i.Errorf("%s cannot jump forward %d (max 127)", i.Command, offset)
|
||||
return false, i.Errorf("%s cannot jump forward %d (max 127) from $%04x to $%04x", i.Command, offset, curr+2, val)
|
||||
}
|
||||
if offset < -128 {
|
||||
return false, i.Errorf("%s cannot jump back %d (max -128)", i.Command, offset)
|
||||
return false, i.Errorf("%s cannot jump back %d (max -128) from $%04x to $%04x", i.Command, offset, curr+2, val)
|
||||
}
|
||||
val = uint16(offset)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user