Pulling out parsing of macro calls.

This commit is contained in:
Zellyn Hunter 2014-06-16 08:24:42 -07:00
parent 1754dcf7a3
commit 006f18e51d
9 changed files with 122 additions and 79 deletions

View File

@ -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]

View File

@ -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()
}

View File

@ -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" {

View File

@ -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()

View File

@ -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" {

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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", "{-}", ""},

View File

@ -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:]