From 255fa86640b186b45219a58fdaa17c3d17348906 Mon Sep 17 00:00:00 2001 From: Zellyn Hunter Date: Tue, 3 Jun 2014 08:46:49 -0700 Subject: [PATCH] monitor.asm assembles now --- asm/a2as/a2as.go | 13 +++++- asm/asm.go | 17 ++++--- asm/asm.org | 11 +++++ asm/context/context.go | 38 ++++++++++++++++ asm/flavors/oldschool/oldschool.go | 26 ++++++++++- asm/flavors/redbook/redbook.go | 9 ++-- asm/flavors/tests/assemble_test.go | 13 +++++- asm/flavors/tests/simple_parse_test.go | 13 ++++-- asm/inst/instruction.go | 5 +++ asm/lines/files.go | 61 +++++++++++++++++++++++++- asm/lines/lines.go | 11 ++++- asm/macros/macros.go | 2 +- 12 files changed, 199 insertions(+), 20 deletions(-) diff --git a/asm/a2as/a2as.go b/asm/a2as/a2as.go index ed62f54..9f036e1 100644 --- a/asm/a2as/a2as.go +++ b/asm/a2as/a2as.go @@ -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) } diff --git a/asm/asm.go b/asm/asm.go index 0ad40c6..68a4620 100644 --- a/asm/asm.go +++ b/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 } diff --git a/asm/asm.org b/asm/asm.org index 4e23bbc..27883d9 100644 --- a/asm/asm.org +++ b/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" diff --git a/asm/context/context.go b/asm/context/context.go index dbf8e5b..fdd7eea 100644 --- a/asm/context/context.go +++ b/asm/context/context.go @@ -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 +} diff --git a/asm/flavors/oldschool/oldschool.go b/asm/flavors/oldschool/oldschool.go index dfcec8f..ee80f07 100644 --- a/asm/flavors/oldschool/oldschool.go +++ b/asm/flavors/oldschool/oldschool.go @@ -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) { diff --git a/asm/flavors/redbook/redbook.go b/asm/flavors/redbook/redbook.go index f0064de..a1f10be 100644 --- a/asm/flavors/redbook/redbook.go +++ b/asm/flavors/redbook/redbook.go @@ -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 diff --git a/asm/flavors/tests/assemble_test.go b/asm/flavors/tests/assemble_test.go index 94a6ded..0bd62c4 100644 --- a/asm/flavors/tests/assemble_test.go +++ b/asm/flavors/tests/assemble_test.go @@ -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 } diff --git a/asm/flavors/tests/simple_parse_test.go b/asm/flavors/tests/simple_parse_test.go index 58882f0..4dbf752 100644 --- a/asm/flavors/tests/simple_parse_test.go +++ b/asm/flavors/tests/simple_parse_test.go @@ -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: diff --git a/asm/inst/instruction.go b/asm/inst/instruction.go index 33c9361..3f3456b 100644 --- a/asm/inst/instruction.go +++ b/asm/inst/instruction.go @@ -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 != "" { diff --git a/asm/lines/files.go b/asm/lines/files.go index 9e13d86..5613105 100644 --- a/asm/lines/files.go +++ b/asm/lines/files.go @@ -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 +} diff --git a/asm/lines/lines.go b/asm/lines/lines.go index b019140..9a3e87c 100644 --- a/asm/lines/lines.go +++ b/asm/lines/lines.go @@ -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, } } diff --git a/asm/macros/macros.go b/asm/macros/macros.go index fd99005..f36a443 100644 --- a/asm/macros/macros.go +++ b/asm/macros/macros.go @@ -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 }