From 65c391cc46963526ab3392dc7a19e214a49a177d Mon Sep 17 00:00:00 2001
From: Zellyn Hunter <zellyn@gmail.com>
Date: Thu, 19 Jun 2014 08:24:54 -0700
Subject: [PATCH] merlin: simple tests passing, only macros remain

---
 asm/flavors/common/common.go           | 34 +++++++++++++++++++++-----
 asm/flavors/merlin/merlin.go           |  1 +
 asm/flavors/oldschool/oldschool.go     | 23 ++++++++++++-----
 asm/flavors/tests/simple_parse_test.go |  9 ++++---
 4 files changed, 52 insertions(+), 15 deletions(-)

diff --git a/asm/flavors/common/common.go b/asm/flavors/common/common.go
index c22abd7..d8db9b0 100644
--- a/asm/flavors/common/common.go
+++ b/asm/flavors/common/common.go
@@ -10,7 +10,7 @@ import (
 
 // DecodeOp contains the common code that decodes an Opcode, once we
 // have fully parsed it.
-func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect bool, xy rune) (inst.I, error) {
+func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect bool, xy rune, forceWide bool) (inst.I, error) {
 	i := inst.I{}
 	ex := in.Exprs[0]
 
@@ -20,7 +20,10 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 		case 'x':
 			op, ok := summary.OpForMode(opcodes.MODE_INDIRECT_X)
 			if !ok {
-				return i, fmt.Errorf("%s doesn't support indexed indirect (addr,X) mode", in.Command)
+				return i, in.Errorf("%s doesn't support indexed indirect (addr,X) mode", in.Command)
+			}
+			if forceWide {
+				return i, in.Errorf("%s (addr,X) doesn't have a wide variant", in.Command)
 			}
 			in.Op = op.Byte
 			in.WidthKnown = true
@@ -31,7 +34,10 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 		case 'y':
 			op, ok := summary.OpForMode(opcodes.MODE_INDIRECT_Y)
 			if !ok {
-				return i, fmt.Errorf("%s doesn't support indirect indexed (addr),Y mode", in.Command)
+				return i, in.Errorf("%s doesn't support indirect indexed (addr),Y mode", in.Command)
+			}
+			if forceWide {
+				return i, fmt.Errorf("%s (addr),Y doesn't have a wide variant", in.Command)
 			}
 			in.WidthKnown = true
 			in.MinWidth = 2
@@ -42,7 +48,7 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 		default:
 			op, ok := summary.OpForMode(opcodes.MODE_INDIRECT)
 			if !ok {
-				return i, fmt.Errorf("%s doesn't support indirect (addr) mode", in.Command)
+				return i, in.Errorf("%s doesn't support indirect (addr) mode", in.Command)
 			}
 			in.Op = op.Byte
 			in.WidthKnown = true
@@ -59,6 +65,10 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 		if !ok {
 			panic(fmt.Sprintf("opcode error: %s has no MODE_RELATIVE opcode", in.Command))
 		}
+		if forceWide {
+			return i, fmt.Errorf("%s doesn't have a wide variant", in.Command)
+		}
+
 		in.Op = op.Byte
 		in.WidthKnown = true
 		in.MinWidth = 2
@@ -68,7 +78,7 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 	}
 
 	// No ,X or ,Y, and width is forced to 1-byte: immediate mode.
-	if xy == '-' && ex.Width() == 1 && summary.AnyModes(opcodes.MODE_IMMEDIATE) {
+	if xy == '-' && ex.Width() == 1 && summary.AnyModes(opcodes.MODE_IMMEDIATE) && !forceWide {
 		op, ok := summary.OpForMode(opcodes.MODE_IMMEDIATE)
 		if !ok {
 			panic(fmt.Sprintf("opcode error: %s has no MODE_IMMEDIATE opcode", in.Command))
@@ -99,7 +109,7 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 	opZp, zpOk := summary.OpForMode(zp)
 
 	if !summary.AnyModes(zp | wide) {
-		return i, fmt.Errorf("%s opcode doesn't support %s or %s modes.", zpS, wideS)
+		return i, in.Errorf("%s opcode doesn't support %s or %s modes.", zpS, wideS)
 	}
 
 	if !summary.AnyModes(zp) {
@@ -117,6 +127,9 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 		if !zpOk {
 			panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, zpS))
 		}
+		if forceWide {
+			return i, fmt.Errorf("%s doesn't have a wide variant", in.Command)
+		}
 		in.Op = opZp.Byte
 		in.WidthKnown = true
 		in.MinWidth = 2
@@ -125,6 +138,15 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 		return in, nil
 	}
 
+	if forceWide {
+		in.Op = opWide.Byte
+		in.WidthKnown = true
+		in.MinWidth = 3
+		in.MaxWidth = 3
+		in.Mode = wide
+		return in, nil
+	}
+
 	// Okay, we don't know whether it's wide or narrow: store enough info for either.
 	if !zpOk {
 		panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, zpS))
diff --git a/asm/flavors/merlin/merlin.go b/asm/flavors/merlin/merlin.go
index 8e1e4e6..4c9bd9f 100644
--- a/asm/flavors/merlin/merlin.go
+++ b/asm/flavors/merlin/merlin.go
@@ -36,6 +36,7 @@ func New() *Merlin {
 	m.CharChars = "'"
 	m.InvCharChars = `"`
 	m.MacroArgSep = ";"
+	m.SuffixForWide = true
 
 	m.Directives = map[string]oldschool.DirectiveInfo{
 		"ORG":    {inst.TypeOrg, m.ParseAddress, 0},
diff --git a/asm/flavors/oldschool/oldschool.go b/asm/flavors/oldschool/oldschool.go
index 69d0b2a..ee5d53d 100644
--- a/asm/flavors/oldschool/oldschool.go
+++ b/asm/flavors/oldschool/oldschool.go
@@ -20,7 +20,7 @@ const Digits = "0123456789"
 const binarydigits = "01"
 const hexdigits = Digits + "abcdefABCDEF"
 const Whitespace = " \t"
-const cmdChars = Letters + Digits + ".>_"
+const cmdChars = Letters + Digits + ".<>_:@"
 const fileChars = Letters + Digits + "."
 
 type DirectiveInfo struct {
@@ -50,6 +50,7 @@ type Base struct {
 	HexCommas         Requiredness
 	SpacesForComment  int  // this many spaces after command means it's the comment field
 	StringEndOptional bool // can omit closing delimeter from string args?
+	SuffixForWide     bool // is eg. "LDA:" a force-wide on "LDA"? (Merlin)
 	CommentChar       rune
 	BinaryChar        rune
 	MsbChars          string
@@ -166,7 +167,17 @@ func (a *Base) ParseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
 
 	if summary, ok := opcodes.ByName[in.Command]; ok {
 		in.Type = inst.TypeOp
-		return a.ParseOpArgs(in, lp, summary)
+		return a.ParseOpArgs(in, lp, summary, false)
+	}
+
+	// Merlin lets you say "LDA:" or "LDA@" or "LDAZ" to force non-zero-page.
+	if a.SuffixForWide {
+		prefix := in.Command[:len(in.Command)-1]
+		if summary, ok := opcodes.ByName[prefix]; ok {
+			in.Command = prefix
+			in.Type = inst.TypeOp
+			return a.ParseOpArgs(in, lp, summary, true)
+		}
 	}
 
 	return inst.I{}, in.Errorf(`unknown command/instruction: "%s"`, in.Command)
@@ -229,9 +240,9 @@ func (a *Base) ParseQuoted(in inst.I, lp *lines.Parse) (string, error) {
 	return strings.Replace(s, `""`, `"`, -1), nil
 }
 
-// ParseOpArgs parses the arguments to an assembly op. We expect to be looking at the first
-// non-op character (probably whitespace)
-func (a *Base) ParseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary) (inst.I, error) {
+// ParseOpArgs parses the arguments to an assembly op. We expect to be
+// looking at the first non-op character (probably whitespace)
+func (a *Base) ParseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary, forceWide bool) (inst.I, error) {
 	i := inst.I{}
 
 	// MODE_IMPLIED: we don't really care what comes next: it's a comment.
@@ -338,7 +349,7 @@ func (a *Base) ParseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
 		}
 	}
 
-	return common.DecodeOp(a, in, summary, indirect, xy)
+	return common.DecodeOp(a, in, summary, indirect, xy, forceWide)
 }
 
 func (a *Base) ParseAddress(in inst.I, lp *lines.Parse) (inst.I, error) {
diff --git a/asm/flavors/tests/simple_parse_test.go b/asm/flavors/tests/simple_parse_test.go
index 46d69e3..de9fe4a 100644
--- a/asm/flavors/tests/simple_parse_test.go
+++ b/asm/flavors/tests/simple_parse_test.go
@@ -49,6 +49,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
 		// {aa, ` include "FILE.NAME"`, "{inc 'FILE.NAME'}", ""},
 		// {aa, ` title "Title here"`, "{-}", ""},
 		// {ss, " .TA *-1234", "{target (- * $04d2)}", ""},
+		{mm, " <<<", `{endm}`, ""},
 		{mm, " >>> M1,$42 ;$43", `{call M1 {"$42"}}`, ""},
 		{mm, " >>> M1.$42", `{call M1 {"$42"}}`, ""},
 		{mm, " >>> M1/$42;$43", `{call M1 {"$42", "$43"}}`, ""},
@@ -61,6 +62,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
 		{mm, " DFB $12,$34,$1234", "{data/b $0012,$0034,$1234}", "123434"},
 		{mm, " DFB $34,100,$81A2-$77C4,%1011,>$81A2-$77C4", "{data/b $0034,$0064,(- $81a2 $77c4),$000b,(msb (- $81a2 $77c4))}", "3464de0b09"},
 		{mm, " DSK OUTFILE", "{-}", ""},
+		{mm, " EOM", `{endm}`, ""},
 		{mm, " HEX 00,01,FF,AB", "{data/b}", "0001ffab"},
 		{mm, " HEX 0001FFAB", "{data/b}", "0001ffab"},
 		{mm, " INCW $42;$43", `{call INCW {"$42", "$43"}}`, ""},
@@ -79,9 +81,9 @@ func TestSimpleCommonFunctions(t *testing.T) {
 		{mm, " LDA $1234,X", "{LDA/absX $1234}", "bd3412"},
 		{mm, " LDA ($12),Y", "{LDA/indY $0012}", "b112"},
 		{mm, " LDA ($12,X)", "{LDA/indX $0012}", "a112"},
-		{mm, " LDA: $12", "{LDA/zp $0012}", "ad1200"},
-		{mm, " LDA@ $12", "{LDA/zp $0012}", "ad1200"},
-		{mm, " LDAX $12", "{LDA/zp $0012}", "ad1200"},
+		{mm, " LDA: $12", "{LDA/abs $0012}", "ad1200"},
+		{mm, " LDA@ $12", "{LDA/abs $0012}", "ad1200"},
+		{mm, " LDAX $12", "{LDA/abs $0012}", "ad1200"},
 		{mm, " LDX $12,Y", "{LDX/zpY $0012}", "b612"},
 		{mm, " ORG $D000", "{org $d000}", ""},
 		{mm, " PMC M1($42", `{call M1 {"$42"}}`, ""},
@@ -107,6 +109,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
 		{mm, "Label", "{- 'Label'}", ""},
 		{mm, "Label;Comment", "{- 'Label'}", ""},
 		{mm, "MacroName MAC", `{macro "MacroName"}`, ""},
+		{mm, "MacroName MAC", `{macro "MacroName"}`, ""},
 		{mm, ` ASC !ABC!`, "{data/b}", "c1c2c3"},
 		{mm, ` ASC "ABC"`, "{data/b}", "c1c2c3"},
 		{mm, ` ASC #ABC#`, "{data/b}", "c1c2c3"},