mirror of
https://github.com/zellyn/go6502.git
synced 2025-01-01 06:32:50 +00:00
monitor.asm assembles now
This commit is contained in:
parent
0a28127420
commit
255fa86640
@ -36,6 +36,7 @@ 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")
|
||||
var prefix = flag.Int("prefix", -1, "length of prefix to skip past addresses and bytes, -1 to guess")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
@ -69,7 +70,17 @@ func main() {
|
||||
var o lines.OsOpener
|
||||
a := asm.NewAssembler(f, o)
|
||||
|
||||
if err := a.Assemble(*infile); err != nil {
|
||||
p := *prefix
|
||||
if p < 0 {
|
||||
var err error
|
||||
p, err = lines.GuessFilePrefixSize(*infile, o)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error trying to determine prefix length for file '%s'", *infile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.AssembleWithPrefix(*infile, p); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
17
asm/asm.go
17
asm/asm.go
@ -29,10 +29,10 @@ func NewAssembler(flavor flavors.F, opener lines.Opener) *Assembler {
|
||||
}
|
||||
|
||||
// Load loads a new assembler file, deleting any previous data.
|
||||
func (a *Assembler) Load(filename string) error {
|
||||
func (a *Assembler) Load(filename string, prefix int) error {
|
||||
a.initPass()
|
||||
context := lines.Context{Filename: filename}
|
||||
ls, err := lines.NewFileLineSource(filename, context, a.Opener)
|
||||
ls, err := lines.NewFileLineSource(filename, context, a.Opener, prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -48,7 +48,7 @@ func (a *Assembler) Load(filename string) error {
|
||||
lineSources = lineSources[1:]
|
||||
continue
|
||||
}
|
||||
in, err := a.Flavor.ParseInstr(line)
|
||||
in, parseErr := a.Flavor.ParseInstr(line)
|
||||
if len(ifdefs) > 0 && !ifdefs[0] && in.Type != inst.TypeIfdefElse && in.Type != inst.TypeIfdefEnd {
|
||||
// we're in an inactive ifdef branch
|
||||
continue
|
||||
@ -63,6 +63,9 @@ func (a *Assembler) Load(filename string) error {
|
||||
|
||||
switch in.Type {
|
||||
case inst.TypeUnknown:
|
||||
if parseErr != nil {
|
||||
return parseErr
|
||||
}
|
||||
return line.Errorf("unknown instruction: %s", line.Parse.Text())
|
||||
case inst.TypeMacroStart:
|
||||
if err := a.readMacro(in, lineSources[0]); err != nil {
|
||||
@ -104,7 +107,7 @@ func (a *Assembler) Load(filename string) error {
|
||||
case inst.TypeInclude:
|
||||
filename = filepath.Join(filepath.Dir(in.Line.Context.Filename), in.TextArg)
|
||||
subContext := lines.Context{Filename: filename, Parent: in.Line}
|
||||
subLs, err := lines.NewFileLineSource(filename, subContext, a.Opener)
|
||||
subLs, err := lines.NewFileLineSource(filename, subContext, a.Opener, prefix)
|
||||
if err != nil {
|
||||
return in.Errorf("error including file: %v", err)
|
||||
}
|
||||
@ -123,8 +126,12 @@ func (a *Assembler) Load(filename string) error {
|
||||
}
|
||||
|
||||
func (a *Assembler) Assemble(filename string) error {
|
||||
return a.AssembleWithPrefix(filename, 0)
|
||||
}
|
||||
|
||||
func (a *Assembler) AssembleWithPrefix(filename string, prefix int) error {
|
||||
a.Reset()
|
||||
if err := a.Load(filename); err != nil {
|
||||
if err := a.Load(filename, prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
11
asm/asm.org
11
asm/asm.org
@ -68,3 +68,14 @@ http://mirrors.apple2.org.za/ftp.apple.asimov.net/images/programming/assembler/E
|
||||
** How to run
|
||||
Boot from the disk
|
||||
type "- edasm.system"
|
||||
|
||||
** Differences from manual
|
||||
The manual claims that "MSB ON" is the default.
|
||||
|
||||
:ASM FOO.TXT
|
||||
SOURCE FILE #01 =>FOO.TXT
|
||||
0000:41 42 43 1 ASC "ABC"
|
||||
0003: 2 MSB OFF
|
||||
0003:41 42 43 3 ASC "ABC"
|
||||
0006: 4 MSB ON
|
||||
0006:C1 C2 C3 5 ASC "ABC"
|
||||
|
@ -13,6 +13,10 @@ type Context interface {
|
||||
RemoveChanged()
|
||||
AddrKnown() bool
|
||||
Clear()
|
||||
SettingOn(name string) error
|
||||
SettingOff(name string) error
|
||||
Setting(name string) bool
|
||||
HasSetting(name string) bool
|
||||
}
|
||||
|
||||
type SimpleContext struct {
|
||||
@ -21,6 +25,7 @@ type SimpleContext struct {
|
||||
lastLabel string
|
||||
clearMesg string // Saved message describing why Addr was cleared.
|
||||
highbit byte // OR-mask for ASCII high bit
|
||||
OnOff map[string]bool
|
||||
}
|
||||
|
||||
type symbolValue struct {
|
||||
@ -94,3 +99,36 @@ func (sc *SimpleContext) Clear() {
|
||||
sc.symbols = make(map[string]symbolValue)
|
||||
sc.highbit = 0x00
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) SettingOn(name string) error {
|
||||
if !sc.HasSetting(name) {
|
||||
return fmt.Errorf("no settable variable named '%s'", name)
|
||||
}
|
||||
if sc.OnOff == nil {
|
||||
sc.OnOff = map[string]bool{name: true}
|
||||
} else {
|
||||
sc.OnOff[name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) SettingOff(name string) error {
|
||||
if !sc.HasSetting(name) {
|
||||
return fmt.Errorf("no settable variable named '%s'", name)
|
||||
}
|
||||
if sc.OnOff == nil {
|
||||
sc.OnOff = map[string]bool{name: false}
|
||||
} else {
|
||||
sc.OnOff[name] = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) Setting(name string) bool {
|
||||
return sc.OnOff[name]
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) HasSetting(name string) bool {
|
||||
_, ok := sc.OnOff[name]
|
||||
return ok
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ func (a *Base) ParseInstr(line lines.Line) (inst.I, error) {
|
||||
|
||||
// Empty line or comment
|
||||
trimmed := strings.TrimSpace(lp.Rest())
|
||||
if trimmed == "" || trimmed[0] == '*' {
|
||||
if trimmed == "" || trimmed[0] == '*' || trimmed[0] == ';' {
|
||||
in.Type = inst.TypeNone
|
||||
return in, nil
|
||||
}
|
||||
@ -141,6 +141,10 @@ func (a *Base) ParseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
return dir.Func(in, lp)
|
||||
}
|
||||
|
||||
if a.HasSetting(in.Command) {
|
||||
return a.ParseSetting(in, lp)
|
||||
}
|
||||
|
||||
if summary, ok := opcodes.ByName[in.Command]; ok {
|
||||
in.Type = inst.TypeOp
|
||||
return a.ParseOpArgs(in, lp, summary)
|
||||
@ -148,6 +152,26 @@ func (a *Base) ParseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
return inst.I{}, in.Errorf(`unknown command/instruction: "%s"`, in.Command)
|
||||
}
|
||||
|
||||
func (a *Base) ParseSetting(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
in.Type = inst.TypeSetting
|
||||
lp.IgnoreRun(whitespace)
|
||||
if !lp.AcceptRun(Letters) {
|
||||
c := lp.Next()
|
||||
return inst.I{}, in.Errorf("expecting ON/OFF, found '%s'", c)
|
||||
}
|
||||
in.TextArg = lp.Emit()
|
||||
switch in.TextArg {
|
||||
case "ON":
|
||||
a.SettingOn(in.Command)
|
||||
case "OFF":
|
||||
a.SettingOff(in.Command)
|
||||
default:
|
||||
return inst.I{}, in.Errorf("expecting ON/OFF, found '%s'", in.TextArg)
|
||||
}
|
||||
return in, nil
|
||||
|
||||
}
|
||||
|
||||
// ParseMacroCall parses a macro call. We expect to be looking at a the
|
||||
// first character of the macro name.
|
||||
func (a *Base) ParseMacroCall(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
// RedBook implements a Redbook-listing-compatible-ish assembler flavor.
|
||||
type RedBook struct {
|
||||
oldschool.Base
|
||||
asciiHi bool
|
||||
}
|
||||
|
||||
func New() *RedBook {
|
||||
@ -45,7 +44,6 @@ func New() *RedBook {
|
||||
".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
|
||||
@ -61,9 +59,14 @@ func New() *RedBook {
|
||||
"=": expr.OpEq,
|
||||
}
|
||||
|
||||
a.OnOff = map[string]bool{
|
||||
"MSB": true, // MSB defaults to true, as per manual
|
||||
"LST": true, // Display listing: not used
|
||||
}
|
||||
|
||||
a.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) {
|
||||
if in.Command == "ASC" {
|
||||
if a.asciiHi {
|
||||
if a.Setting("MSB") {
|
||||
in.Var = inst.DataAsciiHi
|
||||
} else {
|
||||
in.Var = inst.DataAscii
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/zellyn/go6502/asm"
|
||||
"github.com/zellyn/go6502/asm/flavors/redbook"
|
||||
"github.com/zellyn/go6502/asm/flavors/scma"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
"github.com/zellyn/go6502/asm/membuf"
|
||||
@ -25,6 +26,7 @@ func TestMultiline(t *testing.T) {
|
||||
o := lines.NewTestOpener()
|
||||
|
||||
ss := asm.NewAssembler(scma.New(), o)
|
||||
rb := asm.NewAssembler(redbook.New(), o)
|
||||
// aa := asm.NewAssembler(as65.New(), o)
|
||||
// mm := asm.NewAssembler(merlin.New(), o)
|
||||
|
||||
@ -216,6 +218,15 @@ func TestMultiline(t *testing.T) {
|
||||
{0x1000, h("a901a903ea")},
|
||||
{0x2000, h("a902")},
|
||||
}, true},
|
||||
|
||||
// Check turning MSB on and off
|
||||
{rb, "MSB toggle", []string{
|
||||
" ASC 'AB'",
|
||||
" MSB OFF",
|
||||
" ASC 'AB'",
|
||||
" MSB ON",
|
||||
" ASC 'AB'",
|
||||
}, nil, "c1c24142c1c2", nil, true},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
@ -231,7 +242,7 @@ func TestMultiline(t *testing.T) {
|
||||
for k, v := range tt.ii {
|
||||
o[k] = strings.Join(v, "\n")
|
||||
}
|
||||
if err := tt.a.Load("TESTFILE"); err != nil {
|
||||
if err := tt.a.Load("TESTFILE", 0); err != nil {
|
||||
t.Errorf(`%d("%s"): tt.a.Load("TESTFILE") failed: %s`, i, tt.name, err)
|
||||
continue
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
}{
|
||||
{ss, "* Comment", "{-}", ""},
|
||||
{rb, "* Comment", "{-}", ""},
|
||||
{rb, " ; Comment", "{-}", ""},
|
||||
{aa, "; Comment", "{-}", ""},
|
||||
{mm, "* Comment", "{-}", ""},
|
||||
{ss, " far-out-comment", "{-}", ""},
|
||||
@ -121,11 +122,11 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
{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, ` ASC "ABC"`, "{data/b}", "c1c2c3"},
|
||||
{rb, ` ASC $ABC$ ;comment`, "{data/b}", "c1c2c3"},
|
||||
{rb, ` ASC $ABC`, "{data/b}", "c1c2c3"},
|
||||
{rb, ` DCI "ABC"`, "{data/b}", "4142c3"},
|
||||
{rb, ` ASC -ABC-`, "{data/b}", "414243"},
|
||||
{rb, ` ASC -ABC-`, "{data/b}", "c1c2c3"},
|
||||
{ss, " .HS 0001ffAb", "{data/b}", "0001ffab"},
|
||||
{ss, "A.B .EQ *-C.D", "{= 'A.B' (- * C.D)}", ""},
|
||||
{ss, " .BS $8", "{block $0008}", "xxxxxxxxxxxxxxxx"},
|
||||
@ -140,6 +141,10 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
{ss, " LDX #']+$80", "{LDX/imm (lsb (+ $005d $0080))}", "a2dd"},
|
||||
|
||||
{ss, " CMP #';'+1", "{CMP/imm (lsb (+ $003b $0001))}", "c93c"},
|
||||
{rb, " LST ON", "{set LST ON}", ""},
|
||||
{rb, " LST OFF", "{set LST OFF}", ""},
|
||||
{rb, " MSB ON", "{set MSB ON}", ""},
|
||||
{rb, " MSB OFF", "{set MSB OFF}", ""},
|
||||
}
|
||||
|
||||
// TODO(zellyn): Add tests for finalization of four SCMA directives:
|
||||
|
@ -33,6 +33,7 @@ const (
|
||||
TypeEqu // Equate
|
||||
TypeOp // An actual asm opcode
|
||||
TypeEnd // End assembly
|
||||
TypeSetting // An on/off setting toggle
|
||||
)
|
||||
|
||||
// Variants for "TypeData" instructions.
|
||||
@ -118,6 +119,8 @@ func (i I) TypeString() string {
|
||||
return "="
|
||||
case TypeEnd:
|
||||
return "end"
|
||||
case TypeSetting:
|
||||
return "set"
|
||||
case TypeOp:
|
||||
modeStr := "?"
|
||||
switch i.Mode {
|
||||
@ -158,6 +161,8 @@ func (i I) String() string {
|
||||
switch i.Type {
|
||||
case TypeInclude:
|
||||
return fmt.Sprintf("{inc '%s'}", i.TextArg)
|
||||
case TypeSetting:
|
||||
return fmt.Sprintf("{set %s %s}", i.Command, i.TextArg)
|
||||
}
|
||||
s := "{" + i.TypeString()
|
||||
if i.Label != "" {
|
||||
|
@ -19,7 +19,10 @@ func (o OsOpener) Open(filename string) (io.ReadCloser, error) {
|
||||
return os.Open(filename)
|
||||
}
|
||||
|
||||
func NewFileLineSource(filename string, context Context, opener Opener) (LineSource, error) {
|
||||
func NewFileLineSource(filename string, context Context, opener Opener, prefix int) (LineSource, error) {
|
||||
if prefix < 0 {
|
||||
return nil, fmt.Errorf("NewFileLineSource: want prefix >= 0; got %d", prefix)
|
||||
}
|
||||
var ls []string
|
||||
file, err := opener.Open(filename)
|
||||
if err != nil {
|
||||
@ -35,7 +38,7 @@ func NewFileLineSource(filename string, context Context, opener Opener) (LineSou
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSimpleLineSource(context, ls), nil
|
||||
return NewSimpleLineSource(context, ls, prefix), nil
|
||||
}
|
||||
|
||||
type TestOpener map[string]string
|
||||
@ -58,3 +61,57 @@ func (o TestOpener) Clear() {
|
||||
func NewTestOpener() TestOpener {
|
||||
return make(TestOpener)
|
||||
}
|
||||
|
||||
var whitespace = " \t\f\r\n"
|
||||
var prefixChars = "abcdefABCDEF0123456789:"
|
||||
|
||||
func linePrefix(line string) int {
|
||||
start := 0
|
||||
for i, c := range line {
|
||||
switch {
|
||||
case strings.IndexRune(whitespace, c) >= 0:
|
||||
start = i + 1
|
||||
case strings.IndexRune(prefixChars, c) < 0:
|
||||
return start
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// GuessFilePrefixSize takes an assembly listing with address, bytes,
|
||||
// and possibly line numbers, and tries to guess the size of the
|
||||
// prefix that will skip past all that, to the label column.
|
||||
func GuessFilePrefixSize(filename string, opener Opener) (prefix int, err error) {
|
||||
counts := make(map[int]int)
|
||||
max := 0
|
||||
lines := 0
|
||||
rc, err := opener.Open(filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
err = rc.Close()
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(rc)
|
||||
for scanner.Scan() {
|
||||
lines++
|
||||
s := scanner.Text()
|
||||
prefix := linePrefix(s)
|
||||
if prefix >= 0 {
|
||||
counts[prefix] = counts[prefix] + 1
|
||||
if prefix > max {
|
||||
max = prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
min := max
|
||||
for p, _ := range counts {
|
||||
if p < min {
|
||||
min = p
|
||||
}
|
||||
}
|
||||
|
||||
return min, nil
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ type SimpleLineSource struct {
|
||||
lines []string
|
||||
size int
|
||||
curr int
|
||||
prefix int
|
||||
}
|
||||
|
||||
func (sls *SimpleLineSource) Next() (line Line, done bool, err error) {
|
||||
@ -88,16 +89,22 @@ func (sls *SimpleLineSource) Next() (line Line, done bool, err error) {
|
||||
return Line{}, true, nil
|
||||
}
|
||||
sls.curr++
|
||||
return NewLine(sls.lines[sls.curr-1], sls.curr, &sls.context), false, nil
|
||||
l := NewLine(sls.lines[sls.curr-1], sls.curr, &sls.context)
|
||||
for i := 0; i < sls.prefix; i++ {
|
||||
l.Parse.Next()
|
||||
l.Parse.Ignore()
|
||||
}
|
||||
return l, false, nil
|
||||
}
|
||||
|
||||
func (sls SimpleLineSource) Context() Context {
|
||||
return sls.context
|
||||
}
|
||||
func NewSimpleLineSource(context Context, ls []string) LineSource {
|
||||
func NewSimpleLineSource(context Context, ls []string, prefix int) LineSource {
|
||||
return &SimpleLineSource{
|
||||
context: context,
|
||||
lines: ls,
|
||||
size: len(ls),
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
@ -23,5 +23,5 @@ func (m M) LineSource(flavor flavors.F, in inst.I, macroCall int) (lines.LineSou
|
||||
}
|
||||
ls = append(ls, subbed)
|
||||
}
|
||||
return lines.NewSimpleLineSource(context, ls), nil
|
||||
return lines.NewSimpleLineSource(context, ls, 0), nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user