From 006f18e51df09218e990decd11c55d51f482f89f Mon Sep 17 00:00:00 2001 From: Zellyn Hunter Date: Mon, 16 Jun 2014 08:24:42 -0700 Subject: [PATCH] Pulling out parsing of macro calls. --- asm/asm.go | 2 +- asm/context/context.go | 57 +++++++++++++----- asm/flavors/merlin/merlin.go | 4 +- asm/flavors/oldschool/oldschool.go | 82 +++++++++++--------------- asm/flavors/redbook/redbook.go | 4 +- asm/flavors/scma/scma.go | 29 +++++++++ asm/flavors/tests/assemble_test.go | 19 +++--- asm/flavors/tests/simple_parse_test.go | 2 +- asm/inst/instruction.go | 2 +- 9 files changed, 122 insertions(+), 79 deletions(-) diff --git a/asm/asm.go b/asm/asm.go index 68a4620..ac07e16 100644 --- a/asm/asm.go +++ b/asm/asm.go @@ -68,11 +68,11 @@ func (a *Assembler) Load(filename string, prefix int) error { } return line.Errorf("unknown instruction: %s", line.Parse.Text()) case inst.TypeMacroStart: + if err := a.readMacro(in, lineSources[0]); err != nil { return err } continue // no need to append - return in.Errorf("macro start not (yet) implemented: %s", line) case inst.TypeMacroCall: macroCall++ m, ok := a.Macros[in.Command] diff --git a/asm/context/context.go b/asm/context/context.go index fdd7eea..a4a8f4d 100644 --- a/asm/context/context.go +++ b/asm/context/context.go @@ -17,15 +17,19 @@ type Context interface { SettingOff(name string) error Setting(name string) bool HasSetting(name string) bool + AddMacroName(name string) + HasMacroName(name string) bool } type SimpleContext struct { - symbols map[string]symbolValue - addr int32 - lastLabel string - clearMesg string // Saved message describing why Addr was cleared. - highbit byte // OR-mask for ASCII high bit - OnOff map[string]bool + symbols map[string]symbolValue + addr int32 + lastLabel string + clearMesg string // Saved message describing why Addr was cleared. + highbit byte // OR-mask for ASCII high bit + onOff map[string]bool + onOffDefaults map[string]bool + macroNames map[string]bool } type symbolValue struct { @@ -98,16 +102,18 @@ func (sc *SimpleContext) RemoveChanged() { func (sc *SimpleContext) Clear() { sc.symbols = make(map[string]symbolValue) sc.highbit = 0x00 + sc.macroNames = nil + sc.resetOnOff() } 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} + if sc.onOff == nil { + sc.onOff = map[string]bool{name: true} } else { - sc.OnOff[name] = true + sc.onOff[name] = true } return nil } @@ -116,19 +122,42 @@ 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} + if sc.onOff == nil { + sc.onOff = map[string]bool{name: false} } else { - sc.OnOff[name] = false + sc.onOff[name] = false } return nil } func (sc *SimpleContext) Setting(name string) bool { - return sc.OnOff[name] + return sc.onOff[name] } func (sc *SimpleContext) HasSetting(name string) bool { - _, ok := sc.OnOff[name] + _, ok := sc.onOff[name] return ok } + +func (sc *SimpleContext) AddMacroName(name string) { + if sc.macroNames == nil { + sc.macroNames = make(map[string]bool) + } + sc.macroNames[name] = true +} + +func (sc *SimpleContext) HasMacroName(name string) bool { + return sc.macroNames[name] +} + +func (sc *SimpleContext) resetOnOff() { + sc.onOff = make(map[string]bool) + for k, v := range sc.onOffDefaults { + sc.onOff[k] = v + } +} + +func (sc *SimpleContext) SetOnOffDefaults(defaults map[string]bool) { + sc.onOffDefaults = defaults + sc.resetOnOff() +} diff --git a/asm/flavors/merlin/merlin.go b/asm/flavors/merlin/merlin.go index e6a409a..2487748 100644 --- a/asm/flavors/merlin/merlin.go +++ b/asm/flavors/merlin/merlin.go @@ -67,14 +67,14 @@ func New() *Merlin { "=": expr.OpEq, } - m.OnOff = map[string]bool{ + m.SetOnOffDefaults(map[string]bool{ "LST": true, // Display listing: not used "XC": false, // Extended commands: not implemented yet "EXP": false, // How to print macro calls "LSTDO": false, // List conditional code? "TR": false, // truncate listing to 3 bytes? "CYC": false, // print cycle times? - } + }) m.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) { if in.Command != "ASC" && in.Command != "DCI" { diff --git a/asm/flavors/oldschool/oldschool.go b/asm/flavors/oldschool/oldschool.go index 2db1747..7d27d5e 100644 --- a/asm/flavors/oldschool/oldschool.go +++ b/asm/flavors/oldschool/oldschool.go @@ -19,9 +19,8 @@ const Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" const Digits = "0123456789" const binarydigits = "01" const hexdigits = Digits + "abcdefABCDEF" -const whitespace = " \t" -const cmdChars = Letters + "." -const macroNameChars = Letters + Digits + "._" +const Whitespace = " \t" +const cmdChars = Letters + Digits + ".>_" const fileChars = Letters + Digits + "." const operatorChars = "+-*/<>=" @@ -50,15 +49,16 @@ type Base struct { LabelColons Requiredness ExplicitARegister Requiredness HexCommas Requiredness - ExtraCommenty func(string) bool SpacesForComment int // this many spaces after command means it's the comment field StringEndOptional bool // can omit closing delimeter from string args? - SetAsciiVariation func(*inst.I, *lines.Parse) CommentChar rune BinaryChar rune MsbChars string LsbChars string ImmediateChars string + ExtraCommenty func(string) bool + SetAsciiVariation func(*inst.I, *lines.Parse) + ParseMacroCall func(inst.I, *lines.Parse) (inst.I, bool, error) } // Parse an entire instruction, or return an appropriate error. @@ -111,7 +111,7 @@ func (a *Base) ParseInstr(line lines.Line) (inst.I, error) { } // Ignore whitespace at the start or after the label. - lp.IgnoreRun(whitespace) + lp.IgnoreRun(Whitespace) if lp.Peek() == lines.Eol || lp.Peek() == a.CommentChar { in.Type = inst.TypeNone @@ -131,14 +131,23 @@ func (a *Base) SetWidthsOnFirstPass() bool { // ParseCmd parses the "command" part of an instruction: we expect to be // looking at a non-whitespace character. func (a *Base) ParseCmd(in inst.I, lp *lines.Parse) (inst.I, error) { - if lp.Consume(">") { - return a.ParseMacroCall(in, lp) - } if !lp.AcceptRun(cmdChars) && !(a.Directives["="].Func != nil && lp.Accept("=")) { c := lp.Next() return inst.I{}, in.Errorf("expecting instruction, found '%c' (%d)", c, c) } in.Command = lp.Emit() + + // Give ParseMacroCall a chance + if a.ParseMacroCall != nil { + i, isMacro, err := a.ParseMacroCall(in, lp) + if err != nil { + return inst.I{}, err + } + if isMacro { + return i, nil + } + } + if dir, ok := a.Directives[in.Command]; ok { in.Type = dir.Type in.Var = dir.Var @@ -156,12 +165,13 @@ func (a *Base) ParseCmd(in inst.I, lp *lines.Parse) (inst.I, error) { in.Type = inst.TypeOp return a.ParseOpArgs(in, lp, summary) } + 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) + lp.IgnoreRun(Whitespace) if !lp.AcceptRun(Letters) { c := lp.Next() return inst.I{}, in.Errorf("expecting ON/OFF, found '%s'", c) @@ -179,39 +189,13 @@ func (a *Base) ParseSetting(in inst.I, lp *lines.Parse) (inst.I, error) { } -// 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) { - in.Type = inst.TypeMacroCall - if !lp.AcceptRun(macroNameChars) { - c := lp.Next() - return inst.I{}, in.Errorf("expecting macro name, found '%c' (%d)", c, c) - } - in.Command = lp.Emit() - - lp.Consume(whitespace) - - for { - s, err := a.ParseMacroArg(in, lp) - if err != nil { - return inst.I{}, err - } - in.MacroArgs = append(in.MacroArgs, s) - if !lp.Consume(",") { - break - } - } - - return in, nil -} - // ParseMacroArg parses a single macro argument. We expect to be looking at the first // character of a macro argument. func (a *Base) ParseMacroArg(in inst.I, lp *lines.Parse) (string, error) { if lp.Peek() == '"' { return a.ParseQuoted(in, lp) } - lp.AcceptUntil(whitespace + ",") + lp.AcceptUntil(Whitespace + ",") return lp.Emit(), nil } @@ -260,7 +244,7 @@ func (a *Base) ParseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary } // Nothing else on the line? Must be MODE_A - lp.AcceptRun(whitespace) + lp.AcceptRun(Whitespace) ws := lp.Emit() atEnd := false if a.SpacesForComment != 0 && len(ws) >= a.SpacesForComment { @@ -355,7 +339,7 @@ func (a *Base) ParseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary } func (a *Base) ParseAddress(in inst.I, lp *lines.Parse) (inst.I, error) { - lp.IgnoreRun(whitespace) + lp.IgnoreRun(Whitespace) expr, err := a.ParseExpression(in, lp) if err != nil { return inst.I{}, err @@ -369,7 +353,7 @@ 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) + lp.IgnoreRun(Whitespace) a.SetAsciiVariation(&in, lp) var invert, invertLast byte switch in.Var { @@ -385,7 +369,7 @@ func (a *Base) ParseAscii(in inst.I, lp *lines.Parse) (inst.I, error) { panic(fmt.Sprintf("ParseAscii with weird Variation: %d", in.Var)) } delim := lp.Next() - if delim == lines.Eol || strings.IndexRune(whitespace, delim) >= 0 { + if delim == lines.Eol || strings.IndexRune(Whitespace, delim) >= 0 { return inst.I{}, in.Errorf("%s expects delimeter, found '%s'", in.Command, delim) } lp.Ignore() @@ -406,7 +390,7 @@ func (a *Base) ParseAscii(in inst.I, lp *lines.Parse) (inst.I, error) { } func (a *Base) ParseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) { - lp.IgnoreRun(whitespace) + lp.IgnoreRun(Whitespace) ex, err := a.ParseExpression(in, lp) if err != nil { return inst.I{}, err @@ -416,7 +400,7 @@ func (a *Base) ParseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) { } func (a *Base) ParseData(in inst.I, lp *lines.Parse) (inst.I, error) { - lp.IgnoreRun(whitespace) + lp.IgnoreRun(Whitespace) for { ex, err := a.ParseExpression(in, lp) if err != nil { @@ -431,7 +415,7 @@ func (a *Base) ParseData(in inst.I, lp *lines.Parse) (inst.I, error) { } func (a *Base) ParseDo(in inst.I, lp *lines.Parse) (inst.I, error) { - lp.IgnoreRun(whitespace) + lp.IgnoreRun(Whitespace) expr, err := a.ParseExpression(in, lp) if err != nil { return inst.I{}, err @@ -445,7 +429,7 @@ func (a *Base) ParseDo(in inst.I, lp *lines.Parse) (inst.I, error) { } func (a *Base) ParseEquate(in inst.I, lp *lines.Parse) (inst.I, error) { - lp.IgnoreRun(whitespace) + lp.IgnoreRun(Whitespace) expr, err := a.ParseExpression(in, lp) if err != nil { return inst.I{}, err @@ -459,7 +443,7 @@ func (a *Base) ParseEquate(in inst.I, lp *lines.Parse) (inst.I, error) { } func (a *Base) ParseHexString(in inst.I, lp *lines.Parse) (inst.I, error) { - lp.AcceptRun(whitespace) + lp.AcceptRun(Whitespace) for { lp.Ignore() if !lp.AcceptRun(hexdigits) { @@ -484,7 +468,7 @@ func (a *Base) ParseHexString(in inst.I, lp *lines.Parse) (inst.I, error) { } func (a *Base) ParseInclude(in inst.I, lp *lines.Parse) (inst.I, error) { - lp.IgnoreRun(whitespace) + lp.IgnoreRun(Whitespace) if !lp.AcceptRun(fileChars) { return inst.I{}, in.Errorf("Expecting filename, found '%c'", lp.Next()) } @@ -497,8 +481,8 @@ func (a *Base) ParseInclude(in inst.I, lp *lines.Parse) (inst.I, error) { } func (a *Base) ParseMacroStart(in inst.I, lp *lines.Parse) (inst.I, error) { - lp.IgnoreRun(whitespace) - if !lp.AcceptRun(macroNameChars) { + lp.IgnoreRun(Whitespace) + if !lp.AcceptRun(cmdChars) { return inst.I{}, in.Errorf("Expecting valid macro name, found '%c'", lp.Next()) } in.TextArg = lp.Emit() diff --git a/asm/flavors/redbook/redbook.go b/asm/flavors/redbook/redbook.go index 6a0f35c..d392f35 100644 --- a/asm/flavors/redbook/redbook.go +++ b/asm/flavors/redbook/redbook.go @@ -71,10 +71,10 @@ func newRedbook() *RedBook { "=": expr.OpEq, } - r.OnOff = map[string]bool{ + r.SetOnOffDefaults(map[string]bool{ "MSB": true, // MSB defaults to true, as per manual "LST": true, // Display listing: not used - } + }) r.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) { if in.Command == "ASC" { diff --git a/asm/flavors/scma/scma.go b/asm/flavors/scma/scma.go index 43e18f3..95d2dae 100644 --- a/asm/flavors/scma/scma.go +++ b/asm/flavors/scma/scma.go @@ -80,6 +80,35 @@ func New() *SCMA { } } + // ParseMacroCall parses a macro call. We expect in.Command to hold + // the "command column" value, which caused isMacroCall to return + // true, and the lp to be pointing to the following character + // (probably whitespace). + a.ParseMacroCall = func(in inst.I, lp *lines.Parse) (inst.I, bool, error) { + if in.Command == "" || in.Command[0] != '>' { + // not a macro call + return in, false, nil + } + + in.Type = inst.TypeMacroCall + in.Command = in.Command[1:] + + lp.Consume(oldschool.Whitespace) + + for { + s, err := a.ParseMacroArg(in, lp) + if err != nil { + return inst.I{}, true, err + } + in.MacroArgs = append(in.MacroArgs, s) + if !lp.Consume(",") { + break + } + } + + return in, true, nil + } + return a } diff --git a/asm/flavors/tests/assemble_test.go b/asm/flavors/tests/assemble_test.go index ec69bfc..1e528a1 100644 --- a/asm/flavors/tests/assemble_test.go +++ b/asm/flavors/tests/assemble_test.go @@ -256,7 +256,7 @@ func TestMultiline(t *testing.T) { continue } if tt.b == "" && len(tt.ps) == 0 { - t.Fatalf(`%d("%s"): test case must specify bytes or pieces`, i, tt.name) + t.Fatalf(`%d("%s" - %T): test case must specify bytes or pieces`, i, tt.name, tt.a.Flavor) } tt.a.Reset() o.Clear() @@ -265,46 +265,47 @@ func TestMultiline(t *testing.T) { o[k] = strings.Join(v, "\n") } if err := tt.a.Load("TESTFILE", 0); err != nil { - t.Errorf(`%d("%s"): tt.a.Load("TESTFILE") failed: %s`, i, tt.name, err) + t.Errorf(`%d("%s" - %T): tt.a.Load("TESTFILE") failed: %s`, i, tt.name, tt.a.Flavor, err) continue } if !tt.a.Flavor.SetWidthsOnFirstPass() { if _, err := tt.a.Pass(true, false); err != nil { - t.Errorf(`%d("%s"): tt.a.Pass(true, false) failed: %s`, i, tt.name, err) + t.Errorf(`%d("%s" - %T): tt.a.Pass(true, false) failed: %s`, i, tt.name, tt.a.Flavor, err) continue } } isFinal, err := tt.a.Pass(true, true) if err != nil { - t.Errorf(`%d("%s"): tt.a.Pass(true, true) failed: %s`, i, tt.name, err) + t.Errorf(`%d("%s" - %T): tt.a.Pass(true, true) failed: %s`, i, tt.name, tt.a.Flavor, err) continue } if !isFinal { - t.Errorf(`%d("%s"): tt.a.Pass(true, true) couldn't finalize`, i, tt.name) + t.Errorf(`%d("%s" - %T): tt.a.Pass(true, true) couldn't finalize`, i, tt.name, tt.a.Flavor) continue } if tt.b != "" { bb, err := tt.a.RawBytes() if err != nil { - t.Errorf(`%d("%s"): tt.a.RawBytes() failed: %s`, i, tt.name, err) + t.Errorf(`%d("%s" - %T): tt.a.RawBytes() failed: %s`, i, tt.name, tt.a.Flavor, 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) + t.Errorf(`%d("%s" - %T): tt.a.RawBytes()=[%s]; want [%s]`, i, tt.name, tt.a.Flavor, hx, tt.b) continue } } if len(tt.ps) != 0 { m, err := tt.a.Membuf() if err != nil { - t.Errorf(`%d("%s"): tt.a.Membuf() failed: %s`, i, tt.name, err) + t.Errorf(`%d("%s" - %T): tt.a.Membuf() failed: %s`, i, tt.name, tt.a.Flavor, 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) + t.Errorf(`%d("%s" - %T): tt.Membuf().Pieces() = %v; want %v`, i, tt.name, tt.a.Flavor, ps, tt.ps) + continue } } } diff --git a/asm/flavors/tests/simple_parse_test.go b/asm/flavors/tests/simple_parse_test.go index c509ee7..05c2c1b 100644 --- a/asm/flavors/tests/simple_parse_test.go +++ b/asm/flavors/tests/simple_parse_test.go @@ -169,7 +169,7 @@ func TestSimpleCommonFunctions(t *testing.T) { {ss, " .HS 0001FFAB", "{data/b}", "0001ffab"}, {ss, " .IN FILE.NAME", "{inc 'FILE.NAME'}", ""}, {ss, " .IN S.DEFS", "{inc 'S.DEFS'}", ""}, - {ss, " .MA MacroName", "{macro 'MacroName'}", ""}, + {ss, " .MA MacroName", `{macro "MacroName"}`, ""}, {ss, " .OR $D000", "{org $d000}", ""}, {ss, " .TF OUT.BIN", "{-}", ""}, {ss, " .TI 76,Title here", "{-}", ""}, diff --git a/asm/inst/instruction.go b/asm/inst/instruction.go index 8201374..5c4db16 100644 --- a/asm/inst/instruction.go +++ b/asm/inst/instruction.go @@ -175,7 +175,7 @@ func (i I) String() string { s += fmt.Sprintf(" '%s'", i.Label) } if i.TextArg != "" { - s += fmt.Sprintf(" '%s'", i.TextArg) + s += fmt.Sprintf(` "%s"`, i.TextArg) } if len(i.MacroArgs) > 0 { ma := fmt.Sprintf("%#v", i.MacroArgs)[8:]