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