diff --git a/asm/asm.go b/asm/asm.go
index fac4d7a..b1479f2 100644
--- a/asm/asm.go
+++ b/asm/asm.go
@@ -13,11 +13,10 @@ import (
 )
 
 type Assembler struct {
-	Flavor    flavors.F
-	Opener    lines.Opener
-	Insts     []*inst.I
-	LastLabel string
-	Macros    map[string]macros.M
+	Flavor flavors.F
+	Opener lines.Opener
+	Insts  []*inst.I
+	Macros map[string]macros.M
 }
 
 func NewAssembler(flavor flavors.F, opener lines.Opener) *Assembler {
@@ -44,14 +43,11 @@ func (a *Assembler) Load(filename string, prefix int) error {
 		if err != nil {
 			return err
 		}
-		// if line.Parse != nil {
-		// 	fmt.Fprintf("PLUGH: %s\n", line.Text())
-		// }
 		if done {
 			lineSources = lineSources[1:]
 			continue
 		}
-		in, parseErr := a.Flavor.ParseInstr(line)
+		in, parseErr := a.Flavor.ParseInstr(line, false)
 		if len(ifdefs) > 0 && !ifdefs[0] && in.Type != inst.TypeIfdefElse && in.Type != inst.TypeIfdefEnd {
 			// we're in an inactive ifdef branch
 			continue
@@ -61,9 +57,9 @@ func (a *Assembler) Load(filename string, prefix int) error {
 			return err
 		}
 
-		if err := in.FixLabels(a.Flavor); err != nil {
-			return err
-		}
+		// if err := in.FixLabels(a.Flavor); err != nil {
+		// 	return err
+		// }
 
 		if _, err := a.passInst(&in, false); err != nil {
 			return err
@@ -76,7 +72,6 @@ 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
 			}
@@ -92,6 +87,12 @@ func (a *Assembler) Load(filename string, prefix int) error {
 				return in.Errorf(`error calling macro "%s": %v`, m.Name, err)
 			}
 			lineSources = append([]lines.LineSource{subLs}, lineSources...)
+			a.Flavor.PushMacroCall(m.Name, macroCall, m.Locals)
+		case inst.TypeMacroEnd:
+			// If we reached here, it's in a macro call, not a definition.
+			if !a.Flavor.PopMacroCall() {
+				return in.Errorf("unexpected end of macro")
+			}
 		case inst.TypeIfdef:
 			if len(in.Exprs) == 0 {
 				panic(fmt.Sprintf("Ifdef got parsed with no expression: %s", line))
@@ -166,17 +167,16 @@ func (a *Assembler) readMacro(in inst.I, ls lines.LineSource) error {
 		if done {
 			return in.Errorf("end of file while reading macro %s", m.Name)
 		}
-		in2, err := a.Flavor.ParseInstr(line)
+		in2, err := a.Flavor.ParseInstr(line, true)
 		if a.Flavor.LocalMacroLabels() && in2.Label != "" {
 			m.Locals[in2.Label] = true
 		}
+		m.Lines = append(m.Lines, line.Parse.Text())
 		if err == nil && in2.Type == inst.TypeMacroEnd {
-			m.Lines = append(m.Lines, line.Parse.Text())
 			a.Macros[m.Name] = m
 			a.Flavor.AddMacroName(m.Name)
 			return nil
 		}
-		m.Lines = append(m.Lines, line.Parse.Text())
 	}
 }
 
@@ -246,7 +246,6 @@ func (a *Assembler) RawBytes() ([]byte, error) {
 
 func (a *Assembler) Reset() {
 	a.Insts = nil
-	a.LastLabel = ""
 }
 
 func (a *Assembler) Membuf() (*membuf.Membuf, error) {
diff --git a/asm/asm.org b/asm/asm.org
index 2174b06..3e97325 100644
--- a/asm/asm.org
+++ b/asm/asm.org
@@ -83,3 +83,45 @@ SOURCE   FILE #01 =>FOO.TXT
 * Sweet 16
 http://amigan.1emu.net/kolsen/programming/sweet16.html
 http://twimgs.com/informationweek/byte/archive/Apple-II-Description/The-Apple-II-by-Stephen-Wozniak.pdf
+
+| R0  | Accumulator                                     |
+| R12 | Subroutine stack pointer                        |
+| R13 | Comparison results                              |
+| R14 | Status (prior result register << 8 + carry bit) |
+| R15 | PC                                              |
+
+| 00 | Return to 6502 mode    | RTN      | 1 |                                         |
+| 01 | Branch always          | BR       | 2 |                                         |
+| 02 | Branch if no carry     | BNC      | 2 |                                         |
+| 03 | Branch if carry        | BC       | 2 |                                         |
+| 04 | Branch if plus         | BP       | 2 |                                         |
+| 05 | Branch if minus        | BM       | 2 |                                         |
+| 06 | Branch if zero         | BZ       | 2 |                                         |
+| 07 | Branch if nonzero      | BNZ      | 2 |                                         |
+| 08 | Branch if minus 1      | BM1      | 2 |                                         |
+| 09 | Branch if not minus 1  | BNM1     | 2 |                                         |
+| 0A | Break                  | BK       | 1 | BRK                                     |
+| 0B | Return from subroutine | RS       | 1 | PC <- [----R12]                         |
+| 0C | Branch to subroutine   | BS       | 2 | [R12++++] <- PC(R15); PC(R15) += offset |
+| 0D | -                      |          |   |                                         |
+| 0E | -                      |          |   |                                         |
+| 0F | -                      |          |   |                                         |
+| 1n | Set                    | SET R7   | 3 | Rn <- constant                          |
+| 2n | Load                   | LD R7    | 1 | R0 <- Rn                                |
+| 3n | Store                  | ST R7    | 1 | Rn <- R0                                |
+| 4n | Load indirect          | LD @R7   | 1 | R0 <- byte[Rn++]                        |
+| 5n | Store indirect         | ST @R7   | 1 | byte[Rn++] <- R0                        |
+| 6n | Load double indirect   | LDD @R7  | 1 | R0 <- word[Rn++++]                      |
+| 7n | Store double indirect  | STD @R3  | 1 | word[Rn++++] <- R0                      |
+| 8n | Pop indirect           | POP @R3  | 1 | R0 <- byte[--Rn]                        |
+| 9n | Store pop indirect     | STP @R3  | 1 | byte[--Rn] <- R0                        |
+| An | Add                    | ADD R3   | 1 | R0 <- R0 + Rn                           |
+| Bn | Sub                    | SUB R3   | 1 | R0 <- R0 - Rn                           |
+| Cn | Pop double indirect    | POPD @R3 | 1 | R0 <- word[----Rn]                      |
+| Dn | Compare                | CPR R3   | 1 | R13 <- A0 - Rn                          |
+| En | Increment              | INR R3   | 1 | Rn++                                    |
+| Fn | Decrement              | DCR R3   | 1 | Rn--                                    |
+
+* Horrible error messages
+** redbooka
+       CPY   $#0 COMMENT TEXT
diff --git a/asm/context/context.go b/asm/context/context.go
index 4f99cbc..ffd5ee3 100644
--- a/asm/context/context.go
+++ b/asm/context/context.go
@@ -6,10 +6,8 @@ type Context interface {
 	Set(name string, value uint16)
 	Get(name string) (uint16, bool)
 	SetAddr(uint16)
-	ClearAddr(message string)
-	ClearMesg() string
 	GetAddr() (uint16, bool)
-	Zero() (uint16, error) // type ZeroFunc
+	DivZero() (uint16, error)
 	RemoveChanged()
 	Clear()
 	SettingOn(name string) error
@@ -18,17 +16,29 @@ type Context interface {
 	HasSetting(name string) bool
 	AddMacroName(name string)
 	HasMacroName(name string) bool
+	PushMacroCall(name string, number int, locals map[string]bool)
+	PopMacroCall() bool
+	GetMacroCall() (string, int, map[string]bool)
+
+	LastLabel() string
+	SetLastLabel(label string)
+}
+
+type macroCall struct {
+	name   string
+	number int
+	locals map[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
+	highbit       byte // OR-mask for ASCII high bit
 	onOff         map[string]bool
 	onOffDefaults map[string]bool
 	macroNames    map[string]bool
+	macroCalls    []macroCall
 }
 
 type symbolValue struct {
@@ -42,8 +52,8 @@ func (sc *SimpleContext) fix() {
 	}
 }
 
-func (sc *SimpleContext) Zero() (uint16, error) {
-	return 0, fmt.Errorf("Not implemented: context.SimpleContext.Zero()")
+func (sc *SimpleContext) DivZero() (uint16, error) {
+	return 0, fmt.Errorf("Not implemented: context.SimpleContext.DivZero()")
 }
 
 func (sc *SimpleContext) Get(name string) (uint16, bool) {
@@ -55,19 +65,10 @@ func (sc *SimpleContext) Get(name string) (uint16, bool) {
 	return s.v, found
 }
 
-func (sc *SimpleContext) ClearAddr(message string) {
-	sc.addr = -1
-	sc.clearMesg = message
-}
-
 func (sc *SimpleContext) SetAddr(addr uint16) {
 	sc.addr = int32(addr)
 }
 
-func (sc *SimpleContext) ClearMesg() string {
-	return sc.clearMesg
-}
-
 func (sc *SimpleContext) GetAddr() (uint16, bool) {
 	if sc.addr == -1 {
 		return 0, false
@@ -156,3 +157,31 @@ func (sc *SimpleContext) SetOnOffDefaults(defaults map[string]bool) {
 	sc.onOffDefaults = defaults
 	sc.resetOnOff()
 }
+
+func (sc *SimpleContext) PushMacroCall(name string, number int, locals map[string]bool) {
+	sc.macroCalls = append(sc.macroCalls, macroCall{name, number, locals})
+}
+
+func (sc *SimpleContext) PopMacroCall() bool {
+	if len(sc.macroCalls) == 0 {
+		return false
+	}
+	sc.macroCalls = sc.macroCalls[0 : len(sc.macroCalls)-1]
+	return true
+}
+
+func (sc *SimpleContext) GetMacroCall() (string, int, map[string]bool) {
+	if len(sc.macroCalls) == 0 {
+		return "", 0, nil
+	}
+	mc := sc.macroCalls[len(sc.macroCalls)-1]
+	return mc.name, mc.number, mc.locals
+}
+
+func (sc *SimpleContext) LastLabel() string {
+	return sc.lastLabel
+}
+
+func (sc *SimpleContext) SetLastLabel(l string) {
+	sc.lastLabel = l
+}
diff --git a/asm/context/labels.go b/asm/context/labels.go
deleted file mode 100644
index d52b0d1..0000000
--- a/asm/context/labels.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package context
-
-/* Labeler is an interface that the flavors implement to handle label correction. */
-type Labeler interface {
-	LastLabel() string
-	SetLastLabel(label string)
-	FixLabel(label string, macroCall int, locals map[string]bool) (string, error)
-	IsNewParentLabel(label string) bool
-}
-
-type LabelerBase struct {
-	lastLabel string
-}
-
-func (lb *LabelerBase) LastLabel() string {
-	return lb.lastLabel
-}
-
-func (lb *LabelerBase) SetLastLabel(l string) {
-	lb.lastLabel = l
-}
diff --git a/asm/expr/expression.go b/asm/expr/expression.go
index 96aac17..997c59b 100644
--- a/asm/expr/expression.go
+++ b/asm/expr/expression.go
@@ -174,7 +174,7 @@ func (e *E) Eval(ctx context.Context, ln *lines.Line) (uint16, error) {
 			return 0, nil
 		case OpDiv:
 			if r == 0 {
-				return ctx.Zero()
+				return ctx.DivZero()
 			}
 			return l / r, nil
 		case OpAnd:
@@ -200,23 +200,3 @@ func (e *E) CheckedEval(ctx context.Context, ln *lines.Line) (val uint16, labelM
 	}
 	return val, false, err
 }
-
-// FixLabels attempts to turn .1 into LAST_LABEL.1, etc.
-func (e *E) FixLabels(labeler context.Labeler, macroCall int, locals map[string]bool, ln *lines.Line) error {
-	newL, err := labeler.FixLabel(e.Text, macroCall, locals)
-	if err != nil {
-		return ln.Errorf("%v", err)
-	}
-	e.Text = newL
-
-	if e.Left != nil {
-		if err := e.Left.FixLabels(labeler, macroCall, locals, ln); err != nil {
-			return err
-		}
-	}
-	if e.Right != nil {
-		return e.Right.FixLabels(labeler, macroCall, locals, ln)
-	}
-
-	return nil
-}
diff --git a/asm/flavors/as65/as65.go b/asm/flavors/as65/as65.go
index 3e07b13..ab0672b 100644
--- a/asm/flavors/as65/as65.go
+++ b/asm/flavors/as65/as65.go
@@ -13,7 +13,6 @@ import (
 
 type AS65 struct {
 	context.SimpleContext
-	context.LabelerBase
 }
 
 func New() *AS65 {
@@ -21,7 +20,7 @@ func New() *AS65 {
 }
 
 // Parse an entire instruction, or return an appropriate error.
-func (a *AS65) ParseInstr(line lines.Line) (inst.I, error) {
+func (a *AS65) ParseInstr(line lines.Line, quick bool) (inst.I, error) {
 	return inst.I{}, nil
 }
 
@@ -41,10 +40,6 @@ func (a *AS65) IsNewParentLabel(label string) bool {
 	return label != "" && label[0] != '.'
 }
 
-func (a *AS65) FixLabel(label string, macroCall int, locals map[string]bool) (string, error) {
-	panic("AS65.FixLabel not implemented yet.")
-}
-
 func (a *AS65) LocalMacroLabels() bool {
 	return false
 }
diff --git a/asm/flavors/common/common.go b/asm/flavors/common/common.go
index 43cb945..286cafd 100644
--- a/asm/flavors/common/common.go
+++ b/asm/flavors/common/common.go
@@ -8,10 +8,14 @@ import (
 	"github.com/zellyn/go6502/opcodes"
 )
 
+const xyzzy = false
+
 // 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, forceWide bool) (inst.I, error) {
 	ex := in.Exprs[0]
+	val, err := ex.Eval(c, in.Line)
+	valKnown := err == nil
 
 	// At this point we've parsed the Opcode. Let's see if it makes sense.
 	if indirect {
@@ -69,6 +73,17 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 		in.WidthKnown = true
 		in.Width = 2
 		in.Mode = opcodes.MODE_RELATIVE
+		in.Mode = inst.VarRelative
+		if valKnown && xyzzy {
+			b, err := RelativeAddr(c, in, val)
+			if err != nil {
+				return in, err
+			}
+			fmt.Printf("b=$%02x\n", b)
+			in.Data = []byte{in.Op, b}
+			in.Final = true
+		}
+
 		return in, nil
 	}
 
@@ -82,6 +97,8 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 		in.WidthKnown = true
 		in.Width = 2
 		in.Mode = opcodes.MODE_IMMEDIATE
+		in.Var = inst.VarBytes
+		//xyzzy()
 		return in, nil
 	}
 
@@ -152,3 +169,20 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect
 	in.Width = 2
 	return in, nil
 }
+
+func RelativeAddr(c context.Context, in inst.I, val uint16) (byte, error) {
+	curr, ok := c.GetAddr()
+	if !ok {
+		return 0, in.Errorf("cannot determine current address for '%s'", in.Command)
+	}
+	fmt.Printf("RelativeAddr: curr=%04x, val=%04x\n", curr, val)
+	// Found both current and target addresses
+	offset := int32(val) - (int32(curr) + 2)
+	if offset > 127 {
+		return 0, in.Errorf("%s cannot jump forward %d (max 127) from $%04x to $%04x", in.Command, offset, curr+2, val)
+	}
+	if offset < -128 {
+		return 0, in.Errorf("%s cannot jump back %d (max -128) from $%04x to $%04x", in.Command, offset, curr+2, val)
+	}
+	return byte(offset), nil
+}
diff --git a/asm/flavors/flavors.go b/asm/flavors/flavors.go
index 2ce6c3f..39631eb 100644
--- a/asm/flavors/flavors.go
+++ b/asm/flavors/flavors.go
@@ -7,11 +7,10 @@ import (
 )
 
 type F interface {
-	ParseInstr(Line lines.Line) (inst.I, error)
+	ParseInstr(Line lines.Line, quick bool) (inst.I, error)
 	DefaultOrigin() uint16
 	ReplaceMacroArgs(line string, args []string, kwargs map[string]string) (string, error)
 	LocalMacroLabels() bool
 	String() string
 	context.Context
-	context.Labeler
 }
diff --git a/asm/flavors/merlin/merlin.go b/asm/flavors/merlin/merlin.go
index a2b1c89..713000b 100644
--- a/asm/flavors/merlin/merlin.go
+++ b/asm/flavors/merlin/merlin.go
@@ -43,14 +43,14 @@ func New() *Merlin {
 		"ORG":    {inst.TypeOrg, m.ParseAddress, 0},
 		"OBJ":    {inst.TypeNone, nil, 0},
 		"ENDASM": {inst.TypeEnd, m.ParseNoArgDir, 0},
-		"=":      {inst.TypeEqu, m.ParseEquate, inst.EquNormal},
-		"HEX":    {inst.TypeData, m.ParseHexString, inst.DataBytes},
-		"DFB":    {inst.TypeData, m.ParseData, inst.DataBytes},
-		"DB":     {inst.TypeData, m.ParseData, inst.DataBytes},
-		"DA":     {inst.TypeData, m.ParseData, inst.DataWordsLe},
-		"DDB":    {inst.TypeData, m.ParseData, inst.DataWordsBe},
-		"ASC":    {inst.TypeData, m.ParseAscii, inst.DataAscii},
-		"DCI":    {inst.TypeData, m.ParseAscii, inst.DataAsciiFlip},
+		"=":      {inst.TypeEqu, m.ParseEquate, inst.VarEquNormal},
+		"HEX":    {inst.TypeData, m.ParseHexString, inst.VarBytes},
+		"DFB":    {inst.TypeData, m.ParseData, inst.VarBytes},
+		"DB":     {inst.TypeData, m.ParseData, inst.VarBytes},
+		"DA":     {inst.TypeData, m.ParseData, inst.VarWordsLe},
+		"DDB":    {inst.TypeData, m.ParseData, inst.VarWordsBe},
+		"ASC":    {inst.TypeData, m.ParseAscii, inst.VarAscii},
+		"DCI":    {inst.TypeData, m.ParseAscii, inst.VarAsciiFlip},
 		".DO":    {inst.TypeIfdef, m.ParseDo, 0},
 		".ELSE":  {inst.TypeIfdefElse, m.ParseNoArgDir, 0},
 		".FIN":   {inst.TypeIfdefEnd, m.ParseNoArgDir, 0},
@@ -94,13 +94,13 @@ func New() *Merlin {
 		invertLast := in.Command == "DCI"
 		switch {
 		case !invert && !invertLast:
-			in.Var = inst.DataAscii
+			in.Var = inst.VarAscii
 		case !invert && invertLast:
-			in.Var = inst.DataAsciiFlip
+			in.Var = inst.VarAsciiFlip
 		case invert && !invertLast:
-			in.Var = inst.DataAsciiHi
+			in.Var = inst.VarAsciiHi
 		case invert && invertLast:
-			in.Var = inst.DataAsciiHiFlip
+			in.Var = inst.VarAsciiHiFlip
 		}
 	}
 
@@ -153,6 +153,28 @@ func New() *Merlin {
 		return in, true, nil
 	}
 
+	m.FixLabel = func(label string) (string, error) {
+		_, macroCount, locals := m.GetMacroCall()
+		switch {
+		case label == "":
+			return label, nil
+		case label[0] == ':':
+			if last := m.LastLabel(); last == "" {
+				return "", fmt.Errorf("sublabel '%s' without previous label", label)
+			} else {
+				return fmt.Sprintf("%s/%s", last, label), nil
+			}
+		case locals[label]:
+			return fmt.Sprintf("%s{%d}", label, macroCount), nil
+
+		}
+		return label, nil
+	}
+
+	m.IsNewParentLabel = func(label string) bool {
+		return label != "" && label[0] != ':'
+	}
+
 	return m
 }
 
@@ -183,27 +205,6 @@ func (m *Merlin) ParseInclude(in inst.I, lp *lines.Parse) (inst.I, error) {
 	return in, nil
 }
 
-func (m *Merlin) IsNewParentLabel(label string) bool {
-	return label != "" && label[0] != ':'
-}
-
-func (m *Merlin) FixLabel(label string, macroCount int, locals map[string]bool) (string, error) {
-	switch {
-	case label == "":
-		return label, nil
-	case label[0] == ':':
-		if last := m.LastLabel(); last == "" {
-			return "", fmt.Errorf("sublabel '%s' without previous label", label)
-		} else {
-			return fmt.Sprintf("%s/%s", last, label), nil
-		}
-	case locals[label]:
-		return fmt.Sprintf("%s{%d}", label, macroCount), nil
-
-	}
-	return label, nil
-}
-
 func (m *Merlin) LocalMacroLabels() bool {
 	return true
 }
diff --git a/asm/flavors/oldschool/oldschool.go b/asm/flavors/oldschool/oldschool.go
index 3b8812b..f4360b6 100644
--- a/asm/flavors/oldschool/oldschool.go
+++ b/asm/flavors/oldschool/oldschool.go
@@ -44,7 +44,6 @@ type Base struct {
 	Directives map[string]DirectiveInfo
 	Operators  map[string]expr.Operator
 	context.SimpleContext
-	context.LabelerBase
 	LabelChars        string
 	LabelColons       Requiredness
 	ExplicitARegister Requiredness
@@ -57,13 +56,16 @@ type Base struct {
 	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)
 	operatorChars     string
 	CharChars         string
 	InvCharChars      string
 	MacroArgSep       string
+	LocalMacroLabels  bool
+	ExtraCommenty     func(string) bool
+	SetAsciiVariation func(*inst.I, *lines.Parse)
+	ParseMacroCall    func(inst.I, *lines.Parse) (inst.I, bool, error)
+	FixLabel          func(label string) (string, error)
+	IsNewParentLabel  func(label string) bool
 }
 
 func (a *Base) String() string {
@@ -71,7 +73,7 @@ func (a *Base) String() string {
 }
 
 // Parse an entire instruction, or return an appropriate error.
-func (a *Base) ParseInstr(line lines.Line) (inst.I, error) {
+func (a *Base) ParseInstr(line lines.Line, quick bool) (inst.I, error) {
 	lp := line.Parse
 	in := inst.I{Line: &line}
 
@@ -119,6 +121,20 @@ func (a *Base) ParseInstr(line lines.Line) (inst.I, error) {
 		}
 	}
 
+	// Handle the label: munge for macros, relative labels, etc.
+	// If appropriate, set the last parent label.
+	if !quick {
+		parent := a.IsNewParentLabel(in.Label)
+		newL, err := a.FixLabel(in.Label)
+		if err != nil {
+			return in, in.Errorf("%v", err)
+		}
+		in.Label = newL
+		if parent {
+			a.SetLastLabel(in.Label)
+		}
+	}
+
 	// Ignore whitespace at the start or after the label.
 	lp.IgnoreRun(Whitespace)
 
@@ -126,22 +142,32 @@ func (a *Base) ParseInstr(line lines.Line) (inst.I, error) {
 		in.Type = inst.TypeNone
 		return in, nil
 	}
-	return a.ParseCmd(in, lp)
+	return a.parseCmd(in, lp, quick)
 }
 
 func (a *Base) DefaultOrigin() uint16 {
 	return 0x0800
 }
 
-// ParseCmd parses the "command" part of an instruction: we expect to be
+// 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) {
+func (a *Base) parseCmd(in inst.I, lp *lines.Parse, quick bool) (inst.I, error) {
 	if !lp.AcceptRun(cmdChars) && !(a.Directives["="].Func != nil && lp.Accept("=")) {
 		c := lp.Next()
 		return in, in.Errorf("expecting instruction, found '%c' (%d)", c, c)
 	}
 	in.Command = lp.Emit()
 
+	if quick {
+		// all we care about is labels (already covered) and end-of-macro.
+		if dir, ok := a.Directives[in.Command]; ok {
+			if dir.Type == inst.TypeMacroEnd {
+				in.Type = inst.TypeMacroEnd
+			}
+			return in, nil
+		}
+
+	}
 	// Give ParseMacroCall a chance
 	if a.ParseMacroCall != nil {
 		i, isMacro, err := a.ParseMacroCall(in, lp)
@@ -163,12 +189,12 @@ func (a *Base) ParseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
 	}
 
 	if a.HasSetting(in.Command) {
-		return a.ParseSetting(in, lp)
+		return a.parseSetting(in, lp)
 	}
 
 	if summary, ok := opcodes.ByName[in.Command]; ok {
 		in.Type = inst.TypeOp
-		return a.ParseOpArgs(in, lp, summary, false)
+		return a.parseOpArgs(in, lp, summary, false)
 	}
 
 	// Merlin lets you say "LDA:" or "LDA@" or "LDAZ" to force non-zero-page.
@@ -177,14 +203,14 @@ func (a *Base) ParseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
 		if summary, ok := opcodes.ByName[prefix]; ok {
 			in.Command = prefix
 			in.Type = inst.TypeOp
-			return a.ParseOpArgs(in, lp, summary, true)
+			return a.parseOpArgs(in, lp, summary, true)
 		}
 	}
 
 	return in, in.Errorf(`unknown command/instruction: "%s"`, in.Command)
 }
 
-func (a *Base) ParseSetting(in inst.I, lp *lines.Parse) (inst.I, error) {
+func (a *Base) parseSetting(in inst.I, lp *lines.Parse) (inst.I, error) {
 	in.Type = inst.TypeSetting
 	lp.IgnoreRun(Whitespace)
 	if !lp.AcceptRun(Letters) {
@@ -208,17 +234,17 @@ func (a *Base) ParseSetting(in inst.I, lp *lines.Parse) (inst.I, error) {
 // 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)
+		return a.parseQuoted(in, lp)
 	}
 	lp.AcceptUntil(Whitespace + a.MacroArgSep)
 	return lp.Emit(), nil
 }
 
-// ParseQuoted parses a single quoted string macro argument. We expect
+// parseQuoted parses a single quoted string macro argument. We expect
 // to be looking at the first quote.
-func (a *Base) ParseQuoted(in inst.I, lp *lines.Parse) (string, error) {
+func (a *Base) parseQuoted(in inst.I, lp *lines.Parse) (string, error) {
 	if !lp.Consume(`"`) {
-		panic(fmt.Sprintf("ParseQuoted called not looking at a quote"))
+		panic(fmt.Sprintf("parseQuoted called not looking at a quote"))
 	}
 	for {
 		lp.AcceptUntil(`"`)
@@ -241,9 +267,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
+// 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) {
+func (a *Base) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary, forceWide bool) (inst.I, error) {
 	// MODE_IMPLIED: we don't really care what comes next: it's a comment.
 	if summary.Modes == opcodes.MODE_IMPLIED {
 		op := summary.Ops[0]
@@ -366,12 +392,12 @@ func (a *Base) ParseAscii(in inst.I, lp *lines.Parse) (inst.I, error) {
 	a.SetAsciiVariation(&in, lp)
 	var invert, invertLast byte
 	switch in.Var {
-	case inst.DataAscii:
-	case inst.DataAsciiHi:
+	case inst.VarAscii:
+	case inst.VarAsciiHi:
 		invert = 0x80
-	case inst.DataAsciiFlip:
+	case inst.VarAsciiFlip:
 		invertLast = 0x80
-	case inst.DataAsciiHiFlip:
+	case inst.VarAsciiHiFlip:
 		invert = 0x80
 		invertLast = 0x80
 	default:
@@ -662,6 +688,11 @@ func (a *Base) ParseTerm(in inst.I, lp *lines.Parse) (*expr.E, error) {
 
 	ex.Op = expr.OpLeaf
 	ex.Text = lp.Emit()
+	newL, err := a.FixLabel(ex.Text)
+	if err != nil {
+		return &expr.E{}, in.Errorf("%v", err)
+	}
+	ex.Text = newL
 	return top, nil
 }
 
@@ -680,11 +711,11 @@ func (a *Base) ReplaceMacroArgs(line string, args []string, kwargs map[string]st
 	return line, err
 }
 
-func (a *Base) IsNewParentLabel(label string) bool {
+func (a *Base) DefaultIsNewParentLabel(label string) bool {
 	return label != "" && label[0] != '.'
 }
 
-func (a *Base) FixLabel(label string, macroCall int, locals map[string]bool) (string, error) {
+func (a *Base) DefaultFixLabel(label string) (string, error) {
 	switch {
 	case label == "":
 		return label, nil
@@ -695,6 +726,7 @@ func (a *Base) FixLabel(label string, macroCall int, locals map[string]bool) (st
 			return fmt.Sprintf("%s/%s", last, label), nil
 		}
 	case label[0] == ':':
+		_, macroCall, _ := a.GetMacroCall()
 		if macroCall == 0 {
 			return "", fmt.Errorf("macro-local label '%s' seen outside macro", label)
 		} else {
diff --git a/asm/flavors/redbook/redbook.go b/asm/flavors/redbook/redbook.go
index b2c3330..db259c2 100644
--- a/asm/flavors/redbook/redbook.go
+++ b/asm/flavors/redbook/redbook.go
@@ -42,14 +42,14 @@ func newRedbook(name string) *RedBook {
 		"ORG":    {inst.TypeOrg, r.ParseAddress, 0},
 		"OBJ":    {inst.TypeNone, nil, 0},
 		"ENDASM": {inst.TypeEnd, r.ParseNoArgDir, 0},
-		"EQU":    {inst.TypeEqu, r.ParseEquate, inst.EquNormal},
-		"EPZ":    {inst.TypeEqu, r.ParseEquate, inst.EquPageZero},
-		"DFB":    {inst.TypeData, r.ParseData, inst.DataBytes},
-		"DW":     {inst.TypeData, r.ParseData, inst.DataWordsLe},
-		"DDB":    {inst.TypeData, r.ParseData, inst.DataWordsBe},
-		"ASC":    {inst.TypeData, r.ParseAscii, inst.DataAscii},
-		"DCI":    {inst.TypeData, r.ParseAscii, inst.DataAsciiFlip},
-		"HEX":    {inst.TypeData, r.ParseHexString, inst.DataBytes},
+		"EQU":    {inst.TypeEqu, r.ParseEquate, inst.VarEquNormal},
+		"EPZ":    {inst.TypeEqu, r.ParseEquate, inst.VarEquPageZero},
+		"DFB":    {inst.TypeData, r.ParseData, inst.VarBytes},
+		"DW":     {inst.TypeData, r.ParseData, inst.VarWordsLe},
+		"DDB":    {inst.TypeData, r.ParseData, inst.VarWordsBe},
+		"ASC":    {inst.TypeData, r.ParseAscii, inst.VarAscii},
+		"DCI":    {inst.TypeData, r.ParseAscii, inst.VarAsciiFlip},
+		"HEX":    {inst.TypeData, r.ParseHexString, inst.VarBytes},
 		"PAGE":   {inst.TypeNone, nil, 0}, // New page
 		"TITLE":  {inst.TypeNone, nil, 0}, // Title
 		"SBTL":   {inst.TypeNone, nil, 0}, // Subtitle
@@ -75,19 +75,22 @@ func newRedbook(name string) *RedBook {
 	r.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) {
 		if in.Command == "ASC" {
 			if r.Setting("MSB") {
-				in.Var = inst.DataAsciiHi
+				in.Var = inst.VarAsciiHi
 			} else {
-				in.Var = inst.DataAscii
+				in.Var = inst.VarAscii
 			}
 			return
 		}
 		if in.Command == "DCI" {
-			in.Var = inst.DataAsciiFlip
+			in.Var = inst.VarAsciiFlip
 		} else {
 			panic(fmt.Sprintf("Unknown ascii directive: '%s'", in.Command))
 		}
 	}
 
+	r.FixLabel = r.DefaultFixLabel
+	r.IsNewParentLabel = r.DefaultIsNewParentLabel
+
 	return r
 }
 
diff --git a/asm/flavors/scma/scma.go b/asm/flavors/scma/scma.go
index f1ddee4..95d38b7 100644
--- a/asm/flavors/scma/scma.go
+++ b/asm/flavors/scma/scma.go
@@ -37,10 +37,10 @@ func New() *SCMA {
 		".TF":   {inst.TypeNone, nil, 0},
 		".EN":   {inst.TypeEnd, a.ParseNoArgDir, 0},
 		".EQ":   {inst.TypeEqu, a.ParseEquate, 0},
-		".DA":   {inst.TypeData, a.ParseData, inst.DataMixed},
-		".HS":   {inst.TypeData, a.ParseHexString, inst.DataBytes},
-		".AS":   {inst.TypeData, a.ParseAscii, inst.DataBytes},
-		".AT":   {inst.TypeData, a.ParseAscii, inst.DataBytes},
+		".DA":   {inst.TypeData, a.ParseData, inst.VarMixed},
+		".HS":   {inst.TypeData, a.ParseHexString, inst.VarBytes},
+		".AS":   {inst.TypeData, a.ParseAscii, inst.VarBytes},
+		".AT":   {inst.TypeData, a.ParseAscii, inst.VarBytes},
 		".BS":   {inst.TypeBlock, a.ParseBlockStorage, 0},
 		".TI":   {inst.TypeNone, nil, 0},
 		".LIST": {inst.TypeNone, nil, 0},
@@ -73,13 +73,13 @@ func New() *SCMA {
 		invertLast := in.Command == ".AT"
 		switch {
 		case !invert && !invertLast:
-			in.Var = inst.DataAscii
+			in.Var = inst.VarAscii
 		case !invert && invertLast:
-			in.Var = inst.DataAsciiFlip
+			in.Var = inst.VarAsciiFlip
 		case invert && !invertLast:
-			in.Var = inst.DataAsciiHi
+			in.Var = inst.VarAsciiHi
 		case invert && invertLast:
-			in.Var = inst.DataAsciiHiFlip
+			in.Var = inst.VarAsciiHiFlip
 		}
 	}
 
@@ -112,6 +112,9 @@ func New() *SCMA {
 		return in, true, nil
 	}
 
+	a.FixLabel = a.DefaultFixLabel
+	a.IsNewParentLabel = a.DefaultIsNewParentLabel
+
 	return a
 }
 
diff --git a/asm/flavors/tests/assemble_test.go b/asm/flavors/tests/assemble_test.go
index bb2edd6..83e9c01 100644
--- a/asm/flavors/tests/assemble_test.go
+++ b/asm/flavors/tests/assemble_test.go
@@ -2,6 +2,7 @@ package flavors
 
 import (
 	"encoding/hex"
+	"fmt"
 	"reflect"
 	"strings"
 	"testing"
@@ -292,6 +293,7 @@ func TestMultiline(t *testing.T) {
 		if !tt.active {
 			continue
 		}
+		fmt.Println(tt.name)
 		if tt.b == "" && len(tt.ps) == 0 {
 			t.Fatalf(`%d("%s" - %s): test case must specify bytes or pieces`, i, tt.name, tt.a.Flavor)
 		}
@@ -302,41 +304,34 @@ func TestMultiline(t *testing.T) {
 			o[k] = strings.Join(v, "\n")
 		}
 		if err := tt.a.Load("TESTFILE", 0); err != nil {
-			t.Errorf(`%d("%s" - %s): tt.a.Load("TESTFILE") failed: %s`, i, tt.name, tt.a.Flavor, err)
-			continue
+			t.Fatalf(`%d("%s" - %s): tt.a.Load("TESTFILE") failed: %s`, i, tt.name, tt.a.Flavor, err)
 		}
 		isFinal, err := tt.a.Pass(true)
 		if err != nil {
-			t.Errorf(`%d("%s" - %s): tt.a.Pass(true) failed: %s`, i, tt.name, tt.a.Flavor, err)
-			continue
+			t.Fatalf(`%d("%s" - %s): tt.a.Pass(true) failed: %s`, i, tt.name, tt.a.Flavor, err)
 		}
 		if !isFinal {
-			t.Errorf(`%d("%s" - %s): tt.a.Pass(true) couldn't finalize`, i, tt.name, tt.a.Flavor)
-			continue
+			t.Fatalf(`%d("%s" - %s): tt.a.Pass(true) couldn't finalize`, i, tt.name, tt.a.Flavor)
 		}
 
 		if tt.b != "" {
 			bb, err := tt.a.RawBytes()
 			if err != nil {
-				t.Errorf(`%d("%s" - %s): tt.a.RawBytes() failed: %s`, i, tt.name, tt.a.Flavor, err)
-				continue
+				t.Fatalf(`%d("%s" - %s): tt.a.RawBytes() failed: %s`, i, tt.name, tt.a.Flavor, err)
 			}
 			hx := hex.EncodeToString(bb)
 			if hx != tt.b {
-				t.Errorf(`%d("%s" - %s): tt.a.RawBytes()=[%s]; want [%s]`, i, tt.name, tt.a.Flavor, hx, tt.b)
-				continue
+				t.Fatalf(`%d("%s" - %s): tt.a.RawBytes()=[%s]; want [%s]`, i, tt.name, tt.a.Flavor, hx, tt.b)
 			}
 		}
 		if len(tt.ps) != 0 {
 			m, err := tt.a.Membuf()
 			if err != nil {
-				t.Errorf(`%d("%s" - %s): tt.a.Membuf() failed: %s`, i, tt.name, tt.a.Flavor, err)
-				continue
+				t.Fatalf(`%d("%s" - %s): tt.a.Membuf() failed: %s`, i, tt.name, tt.a.Flavor, err)
 			}
 			ps := m.Pieces()
 			if !reflect.DeepEqual(ps, tt.ps) {
-				t.Errorf(`%d("%s" - %s): tt.Membuf().Pieces() = %v; want %v`, i, tt.name, tt.a.Flavor, ps, tt.ps)
-				continue
+				t.Fatalf(`%d("%s" - %s): tt.Membuf().Pieces() = %v; want %v`, i, tt.name, tt.a.Flavor, ps, tt.ps)
 			}
 		}
 	}
diff --git a/asm/flavors/tests/simple_parse_test.go b/asm/flavors/tests/simple_parse_test.go
index f2736c5..d3782b0 100644
--- a/asm/flavors/tests/simple_parse_test.go
+++ b/asm/flavors/tests/simple_parse_test.go
@@ -229,7 +229,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
 		tt.a.AddMacroName("INCW")
 		tt.a.AddMacroName("M1")
 
-		inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i))
+		inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i), false)
 		if err != nil {
 			t.Errorf(`%d. %s.ParseInstr("%s") => error: %s`, i, tt.a, tt.i, err)
 			continue
@@ -293,7 +293,7 @@ func TestSimpleErrors(t *testing.T) {
 		if tt.a != ss {
 			continue
 		}
-		inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i))
+		inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i), false)
 		if err == nil {
 			t.Errorf(`%d. %s.ParseInstr("%s") want err; got %s`, i, tt.a, tt.i, inst)
 			continue
diff --git a/asm/inst/instruction.go b/asm/inst/instruction.go
index af826ff..3b9a00e 100644
--- a/asm/inst/instruction.go
+++ b/asm/inst/instruction.go
@@ -36,22 +36,20 @@ const (
 	TypeSetting    // An on/off setting toggle
 )
 
-// Variants for "TypeData" instructions.
+// Variants for instructions. These tell the instruction how to
+// interpret the raw data that comes in on the first or second pass.
 const (
-	DataBytes       = iota // Data: expressions, but forced to one byte per
-	DataMixed              // Bytes or words (LE), depending on individual expression widths
-	DataWordsLe            // Data: expressions, but forced to one word per, little-endian
-	DataWordsBe            // Data: expressions, but forced to one word per, big-endian
-	DataAscii              // Data: from ASCII strings, high bit clear
-	DataAsciiFlip          // Data: from ASCII strings, high bit clear, except last char
-	DataAsciiHi            // Data: from ASCII strings, high bit set
-	DataAsciiHiFlip        // Data: from ASCII strings, high bit set, except last char
-)
-
-// Variants for "TypeEqu" instructions.
-const (
-	EquNormal = iota
-	EquPageZero
+	VarBytes       = iota // Data: expressions, but forced to one byte per
+	VarMixed              // Bytes or words (LE), depending on individual expression widths
+	VarWordsLe            // Data: expressions, but forced to one word per, little-endian
+	VarWordsBe            // Data: expressions, but forced to one word per, big-endian
+	VarAscii              // Data: from ASCII strings, high bit clear
+	VarAsciiFlip          // Data: from ASCII strings, high bit clear, except last char
+	VarAsciiHi            // Data: from ASCII strings, high bit set
+	VarAsciiHiFlip        // Data: from ASCII strings, high bit set, except last char
+	VarRelative           // For branches: a one-byte relative address
+	VarEquNormal          // Equ: a normal equate
+	VarEquPageZero        // Equ: a page-zero equate
 )
 
 type I struct {
@@ -98,15 +96,15 @@ func (i I) TypeString() string {
 		return "inc"
 	case TypeData:
 		switch i.Var {
-		case DataMixed:
+		case VarMixed:
 			return "data"
-		case DataBytes:
+		case VarBytes:
 			return "data/b"
-		case DataWordsLe:
+		case VarWordsLe:
 			return "data/wle"
-		case DataWordsBe:
+		case VarWordsBe:
 			return "data/wbe"
-		case DataAscii, DataAsciiHi, DataAsciiFlip, DataAsciiHiFlip:
+		case VarAscii, VarAsciiHi, VarAsciiFlip, VarAsciiHiFlip:
 			return "data/b"
 		default:
 			panic(fmt.Sprintf("unknown data variant: %d", i.Var))
@@ -217,29 +215,6 @@ func (i *I) Compute(c context.Context, final bool) (bool, error) {
 	return true, nil
 }
 
-// FixLabels attempts to turn .1 into LAST_LABEL.1, etc.
-func (i *I) FixLabels(labeler context.Labeler) error {
-	macroCall := i.Line.GetMacroCall()
-	macroLocals := i.Line.GetMacroLocals()
-	parent := labeler.IsNewParentLabel(i.Label)
-	newL, err := labeler.FixLabel(i.Label, macroCall, macroLocals)
-	if err != nil {
-		return i.Errorf("%v", err)
-	}
-	i.Label = newL
-	if parent {
-		labeler.SetLastLabel(i.Label)
-	}
-
-	for _, e := range i.Exprs {
-		if err := e.FixLabels(labeler, macroCall, macroLocals, i.Line); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
 // computeLabel attempts to compute equates and label values.
 func (i *I) computeLabel(c context.Context, final bool) error {
 	if i.Label == "" {
@@ -277,11 +252,11 @@ func (i *I) computeData(c context.Context, final bool) (bool, error) {
 	for _, e := range i.Exprs {
 		var w uint16
 		switch i.Var {
-		case DataMixed:
+		case VarMixed:
 			w = e.Width()
-		case DataBytes:
+		case VarBytes:
 			w = 1
-		case DataWordsLe, DataWordsBe:
+		case VarWordsLe, VarWordsBe:
 			w = 2
 		}
 		width += w
@@ -296,18 +271,18 @@ func (i *I) computeData(c context.Context, final bool) (bool, error) {
 			}
 		}
 		switch i.Var {
-		case DataMixed:
+		case VarMixed:
 			switch w {
 			case 1:
 				data = append(data, byte(val))
 			case 2:
 				data = append(data, byte(val), byte(val>>8))
 			}
-		case DataBytes:
+		case VarBytes:
 			data = append(data, byte(val))
-		case DataWordsLe:
+		case VarWordsLe:
 			data = append(data, byte(val), byte(val>>8))
-		case DataWordsBe:
+		case VarWordsBe:
 			data = append(data, byte(val>>8), byte(val))
 		default:
 			panic(fmt.Sprintf("Unknown data variant handed to computeData: %d", i.Var))
diff --git a/asm/inst/instruction_test.go b/asm/inst/instruction_test.go
index 79f4fc6..58b0284 100644
--- a/asm/inst/instruction_test.go
+++ b/asm/inst/instruction_test.go
@@ -13,7 +13,7 @@ func TestComputeLabel(t *testing.T) {
 		Label: "L1",
 	}
 	c := &context.SimpleContext{}
-	i.computeLabel(c, false, false)
+	i.computeLabel(c, false)
 }
 
 func TestWidthDoesNotChange(t *testing.T) {
@@ -27,8 +27,7 @@ func TestWidthDoesNotChange(t *testing.T) {
 				Right: &expr.E{Op: expr.OpLeaf, Text: "L2"},
 			},
 		},
-		MinWidth: 0x2,
-		MaxWidth: 0x3,
+		Width:    0x2,
 		Final:    false,
 		Op:       0xad,
 		Mode:     0x2,
@@ -38,15 +37,7 @@ func TestWidthDoesNotChange(t *testing.T) {
 	}
 	c := &context.SimpleContext{}
 	c.Set("L1", 0x102)
-	final, err := i.Compute(c, false, false)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if final {
-		t.Fatal("First pass shouldn't be able to finalize expression with unknown width")
-	}
-
-	final, err = i.Compute(c, true, false)
+	final, err := i.Compute(c, false)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -56,16 +47,13 @@ func TestWidthDoesNotChange(t *testing.T) {
 	if !i.WidthKnown {
 		t.Fatal("Second pass should have set width.")
 	}
-	if i.MinWidth != i.MaxWidth {
-		t.Fatalf("i.WidthKnown, but i.MinWidth(%d) != i.MaxWidth(%d)", i.MinWidth, i.MaxWidth)
-	}
-	if i.MinWidth != 3 {
-		t.Fatalf("i.MinWidth should be 3; got %d", i.MinWidth)
+	if i.Width != 3 {
+		t.Fatalf("i.Width should be 3; got %d", i.Width)
 	}
 
 	c.Set("L2", 0x101)
 
-	final, err = i.Compute(c, true, true)
+	final, err = i.Compute(c, true)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -75,10 +63,7 @@ func TestWidthDoesNotChange(t *testing.T) {
 	if !i.WidthKnown {
 		t.Fatal("Third pass should left width unchanged.")
 	}
-	if i.MinWidth != i.MaxWidth {
-		t.Fatalf("i.WidthKnown, but i.MinWidth(%d) != i.MaxWidth(%d)", i.MinWidth, i.MaxWidth)
-	}
-	if i.MinWidth != 3 {
-		t.Fatalf("i.MinWidth should still be 3; got %d", i.MinWidth)
+	if i.Width != 3 {
+		t.Fatalf("i.Width should still be 3; got %d", i.Width)
 	}
 }