diff --git a/asm/asm.go b/asm/asm.go index 977e759..8fbcac3 100644 --- a/asm/asm.go +++ b/asm/asm.go @@ -32,6 +32,11 @@ func NewAssembler(flavor flavors.F, opener lines.Opener) *Assembler { } } +type ifdef struct { + active bool + in inst.I +} + // Load loads a new assembler file, deleting any previous data. func (a *Assembler) Load(filename string, prefix int) error { a.initPass() @@ -41,7 +46,7 @@ func (a *Assembler) Load(filename string, prefix int) error { return err } lineSources := []lines.LineSource{ls} - ifdefs := []bool{} + ifdefs := []ifdef{} macroCall := 0 for len(lineSources) > 0 { line, done, err := lineSources[0].Next() @@ -52,14 +57,18 @@ func (a *Assembler) Load(filename string, prefix int) error { lineSources = lineSources[1:] continue } - in, parseErr := a.Flavor.ParseInstr(a.Ctx, line, false) - if len(ifdefs) > 0 && !ifdefs[0] && in.Type != inst.TypeIfdefElse && in.Type != inst.TypeIfdefEnd { - // we're in an inactive ifdef branch + inactive := len(ifdefs) > 0 && !ifdefs[0].active + mode := flavors.ParseModeNormal + if inactive { + mode = flavors.ParseModeInactive + } + in, parseErr := a.Flavor.ParseInstr(a.Ctx, line, mode) + if inactive && in.Type != inst.TypeIfdefElse && in.Type != inst.TypeIfdefEnd { + // we're still in an inactive ifdef branch continue } - - if err != nil { - return err + if parseErr != nil { + return parseErr } if _, err := a.passInst(&in, false); err != nil { @@ -102,13 +111,13 @@ func (a *Assembler) Load(filename string, prefix int) error { if err != nil { return in.Errorf("cannot eval ifdef condition: %v", err) } - ifdefs = append([]bool{val != 0}, ifdefs...) + ifdefs = append([]ifdef{{val != 0, in}}, ifdefs...) case inst.TypeIfdefElse: if len(ifdefs) == 0 { return in.Errorf("ifdef else branch encountered outside ifdef: %s", line) } - ifdefs[0] = !ifdefs[0] + ifdefs[0].active = !ifdefs[0].active case inst.TypeIfdefEnd: if len(ifdefs) == 0 { return in.Errorf("ifdef end encountered outside ifdef: %s", line) @@ -132,6 +141,9 @@ func (a *Assembler) Load(filename string, prefix int) error { } a.Insts = append(a.Insts, &in) } + if len(ifdefs) > 0 { + return ifdefs[0].in.Errorf("Ifdef not closed before end of file") + } return nil } @@ -168,7 +180,7 @@ 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(a.Ctx, line, true) + in2, err := a.Flavor.ParseInstr(a.Ctx, line, flavors.ParseModeMacroSave) if a.Flavor.LocalMacroLabels() && in2.Label != "" { m.Locals[in2.Label] = true } @@ -193,13 +205,17 @@ func (a *Assembler) initPass() { // arguments. If final is true, and the instruction cannot be // finalized, it returns an error. func (a *Assembler) passInst(in *inst.I, final bool) (isFinal bool, err error) { + if in.Type == inst.TypeOrg { + a.Ctx.SetAddr(in.Addr) + return true, nil + } isFinal, err = in.Compute(a.Ctx, final) if err != nil { return false, err } // Update address - addr, _ := a.Ctx.GetAddr() + addr := a.Ctx.GetAddr() in.Addr = addr a.Ctx.SetAddr(addr + in.Width) diff --git a/asm/asm.org b/asm/asm.org index 0e295bd..2eb9490 100644 --- a/asm/asm.org +++ b/asm/asm.org @@ -133,8 +133,8 @@ http://twimgs.com/informationweek/byte/archive/Apple-II-Description/The-Apple-II It's a bit tricky: 1. Parse instruction. - 1. Set the org/target immediately, updating current address. - 2. Set the current label. + 1. Set the current label to the current address, unless it's an equate + 2. Set the org/target immediately, updating current address. 3. Force equates to evaluate immediately. 4. Evaluate instructions. diff --git a/asm/context/context.go b/asm/context/context.go index 72a4550..57a1379 100644 --- a/asm/context/context.go +++ b/asm/context/context.go @@ -6,7 +6,7 @@ type Context interface { Set(name string, value uint16) Get(name string) (uint16, bool) SetAddr(uint16) - GetAddr() (uint16, bool) + GetAddr() uint16 DivZero() *uint16 SetDivZero(uint16) RemoveChanged() @@ -33,7 +33,7 @@ type macroCall struct { type SimpleContext struct { symbols map[string]symbolValue - addr int32 + addr uint16 lastLabel string highbit byte // OR-mask for ASCII high bit onOff map[string]bool @@ -56,7 +56,7 @@ func (sc *SimpleContext) fix() { func (sc *SimpleContext) Get(name string) (uint16, bool) { if name == "*" { - return sc.GetAddr() + return sc.GetAddr(), true } sc.fix() s, found := sc.symbols[name] @@ -64,14 +64,11 @@ func (sc *SimpleContext) Get(name string) (uint16, bool) { } func (sc *SimpleContext) SetAddr(addr uint16) { - sc.addr = int32(addr) + sc.addr = addr } -func (sc *SimpleContext) GetAddr() (uint16, bool) { - if sc.addr == -1 { - return 0, false - } - return uint16(sc.addr), true +func (sc *SimpleContext) GetAddr() uint16 { + return sc.addr } func (sc *SimpleContext) Set(name string, value uint16) { diff --git a/asm/flavors/as65/as65.go b/asm/flavors/as65/as65.go index d046d45..9c77a7c 100644 --- a/asm/flavors/as65/as65.go +++ b/asm/flavors/as65/as65.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/zellyn/go6502/asm/context" + "github.com/zellyn/go6502/asm/flavors" "github.com/zellyn/go6502/asm/inst" "github.com/zellyn/go6502/asm/lines" ) @@ -18,7 +19,7 @@ func New() *AS65 { } // Parse an entire instruction, or return an appropriate error. -func (a *AS65) ParseInstr(ctx context.Context, line lines.Line, quick bool) (inst.I, error) { +func (a *AS65) ParseInstr(ctx context.Context, line lines.Line, mode flavors.ParseMode) (inst.I, error) { return inst.I{}, nil } diff --git a/asm/flavors/common/common.go b/asm/flavors/common/common.go index ea58ee2..a870561 100644 --- a/asm/flavors/common/common.go +++ b/asm/flavors/common/common.go @@ -170,11 +170,7 @@ func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect } 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) - } - // Found both current and target addresses + curr := c.GetAddr() 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) diff --git a/asm/flavors/flavors.go b/asm/flavors/flavors.go index 7b07acc..adedd3c 100644 --- a/asm/flavors/flavors.go +++ b/asm/flavors/flavors.go @@ -6,8 +6,16 @@ import ( "github.com/zellyn/go6502/asm/lines" ) +type ParseMode int + +const ( + ParseModeNormal ParseMode = iota + ParseModeMacroSave + ParseModeInactive +) + type F interface { - ParseInstr(ctx context.Context, Line lines.Line, quick bool) (inst.I, error) + ParseInstr(ctx context.Context, Line lines.Line, mode ParseMode) (inst.I, error) DefaultOrigin() uint16 ReplaceMacroArgs(line string, args []string, kwargs map[string]string) (string, error) LocalMacroLabels() bool diff --git a/asm/flavors/merlin/merlin.go b/asm/flavors/merlin/merlin.go index 8b02d11..5dc0c12 100644 --- a/asm/flavors/merlin/merlin.go +++ b/asm/flavors/merlin/merlin.go @@ -42,7 +42,7 @@ func New() *Merlin { m.DefaultOriginVal = 0x8000 m.Directives = map[string]oldschool.DirectiveInfo{ - "ORG": {inst.TypeOrg, m.ParseAddress, 0}, + "ORG": {inst.TypeOrg, m.ParseOrg, 0}, "OBJ": {inst.TypeNone, nil, 0}, "ENDASM": {inst.TypeEnd, m.ParseNoArgDir, 0}, "=": {inst.TypeEqu, m.ParseEquate, inst.VarEquNormal}, @@ -66,6 +66,11 @@ func New() *Merlin { "PUT": {inst.TypeInclude, m.ParseInclude, 0}, "USE": {inst.TypeInclude, m.ParseInclude, 0}, } + + m.EquateDirectives = map[string]bool{ + "=": true, + } + m.Operators = map[string]expr.Operator{ "*": expr.OpMul, "/": expr.OpDiv, diff --git a/asm/flavors/oldschool/oldschool.go b/asm/flavors/oldschool/oldschool.go index 502981b..155055e 100644 --- a/asm/flavors/oldschool/oldschool.go +++ b/asm/flavors/oldschool/oldschool.go @@ -10,6 +10,7 @@ import ( "github.com/zellyn/go6502/asm/context" "github.com/zellyn/go6502/asm/expr" + "github.com/zellyn/go6502/asm/flavors" "github.com/zellyn/go6502/asm/flavors/common" "github.com/zellyn/go6502/asm/inst" "github.com/zellyn/go6502/asm/lines" @@ -44,6 +45,7 @@ type Base struct { Name string Directives map[string]DirectiveInfo Operators map[string]expr.Operator + EquateDirectives map[string]bool LabelChars string LabelColons Requiredness ExplicitARegister Requiredness @@ -76,7 +78,7 @@ func (a *Base) String() string { } // Parse an entire instruction, or return an appropriate error. -func (a *Base) ParseInstr(ctx context.Context, line lines.Line, quick bool) (inst.I, error) { +func (a *Base) ParseInstr(ctx context.Context, line lines.Line, mode flavors.ParseMode) (inst.I, error) { lp := line.Parse in := inst.I{Line: &line} @@ -124,49 +126,87 @@ func (a *Base) ParseInstr(ctx context.Context, line lines.Line, quick bool) (ins } } - // 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(ctx, in.Label) - if err != nil { - return in, in.Errorf("%v", err) - } - in.Label = newL - if parent { - ctx.SetLastLabel(in.Label) - } - } - // Ignore whitespace at the start or after the label. lp.IgnoreRun(Whitespace) if lp.Peek() == lines.Eol || lp.Peek() == a.CommentChar { in.Type = inst.TypeNone + if mode == flavors.ParseModeNormal { + if err := a.handleLabel(ctx, in); err != nil { + return in, err + } + } return in, nil } - return a.parseCmd(ctx, in, lp, quick) + return a.parseCmd(ctx, in, lp, mode) +} + +func (a *Base) handleLabel(ctx context.Context, in inst.I) error { + if in.Label == "" { + return nil + } + addr := ctx.GetAddr() + + // Munge for macros, relative labels, etc. If appropriate, + // set the last parent label. + parent := a.IsNewParentLabel(in.Label) + newL, err := a.FixLabel(ctx, in.Label) + if err != nil { + return in.Errorf("%v", err) + } + in.Label = newL + if parent { + ctx.SetLastLabel(in.Label) + } + + lval, lok := ctx.Get(in.Label) + if lok && addr != lval { + return in.Errorf("Trying to set label '%s' to $%04x, but it already has value $%04x", in.Label, addr, lval) + } + ctx.Set(in.Label, addr) + return nil } // parseCmd parses the "command" part of an instruction: we expect to be // looking at a non-whitespace character. -func (a *Base) parseCmd(ctx context.Context, in inst.I, lp *lines.Parse, quick bool) (inst.I, error) { +func (a *Base) parseCmd(ctx context.Context, in inst.I, lp *lines.Parse, mode flavors.ParseMode) (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 { + if mode == flavors.ParseModeMacroSave { // 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 } - + return in, nil } + + if mode == flavors.ParseModeInactive { + // all we care about are endif and else. + if dir, ok := a.Directives[in.Command]; ok { + if dir.Type == inst.TypeIfdefElse || dir.Type == inst.TypeIfdefEnd { + in.Type = dir.Type + // It's weird, but handle labels on else/endif lines. + if err := a.handleLabel(ctx, in); err != nil { + return in, err + } + } + } + return in, nil + } + + // We don't need to handle labels if it's an equate. + if !a.EquateDirectives[in.Command] { + if err := a.handleLabel(ctx, in); err != nil { + return in, err + } + } + // Give ParseMacroCall a chance if a.ParseMacroCall != nil { i, isMacro, err := a.ParseMacroCall(ctx, in, lp) @@ -269,6 +309,7 @@ func (a *Base) parseQuoted(in inst.I, lp *lines.Parse) (string, 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(ctx context.Context, 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] @@ -373,16 +414,21 @@ func (a *Base) parseOpArgs(ctx context.Context, in inst.I, lp *lines.Parse, summ return common.DecodeOp(ctx, in, summary, indirect, xy, forceWide) } -func (a *Base) ParseAddress(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseOrg(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) expr, err := a.parseExpression(ctx, in, lp) if err != nil { return in, err } in.Exprs = append(in.Exprs, expr) + val, err := expr.Eval(ctx, in.Line) + if err != nil { + return in, err + } in.WidthKnown = true in.Width = 0 in.Final = true + in.Addr = val return in, nil } diff --git a/asm/flavors/redbook/redbook.go b/asm/flavors/redbook/redbook.go index 0273f78..959a22e 100644 --- a/asm/flavors/redbook/redbook.go +++ b/asm/flavors/redbook/redbook.go @@ -41,7 +41,7 @@ func newRedbook(name string) *RedBook { r.DefaultOriginVal = 0x0800 r.Directives = map[string]oldschool.DirectiveInfo{ - "ORG": {inst.TypeOrg, r.ParseAddress, 0}, + "ORG": {inst.TypeOrg, r.ParseOrg, 0}, "OBJ": {inst.TypeNone, nil, 0}, "ENDASM": {inst.TypeEnd, r.ParseNoArgDir, 0}, "EQU": {inst.TypeEqu, r.ParseEquate, inst.VarEquNormal}, @@ -59,6 +59,12 @@ func newRedbook(name string) *RedBook { "REP": {inst.TypeNone, nil, 0}, // Repeat character "CHR": {inst.TypeNone, nil, 0}, // Set repeated character } + + r.EquateDirectives = map[string]bool{ + "EQU": true, + "EPZ": true, + } + r.Operators = map[string]expr.Operator{ "*": expr.OpMul, "/": expr.OpDiv, diff --git a/asm/flavors/scma/scma.go b/asm/flavors/scma/scma.go index 2bcbd64..26d9f5f 100644 --- a/asm/flavors/scma/scma.go +++ b/asm/flavors/scma/scma.go @@ -36,7 +36,7 @@ func New() *SCMA { a.Directives = map[string]oldschool.DirectiveInfo{ ".IN": {inst.TypeInclude, a.ParseInclude, 0}, - ".OR": {inst.TypeOrg, a.ParseAddress, 0}, + ".OR": {inst.TypeOrg, a.ParseOrg, 0}, ".TA": {inst.TypeTarget, a.ParseNotImplemented, 0}, ".TF": {inst.TypeNone, nil, 0}, ".EN": {inst.TypeEnd, a.ParseNoArgDir, 0}, @@ -56,6 +56,11 @@ func New() *SCMA { ".EM": {inst.TypeMacroEnd, a.ParseNoArgDir, 0}, ".US": {inst.TypeNone, a.ParseNotImplemented, 0}, } + + a.EquateDirectives = map[string]bool{ + ".EQ": true, + } + a.Operators = map[string]expr.Operator{ "*": expr.OpMul, "/": expr.OpDiv, diff --git a/asm/flavors/tests/simple_parse_test.go b/asm/flavors/tests/simple_parse_test.go index 421f1a4..3c1197c 100644 --- a/asm/flavors/tests/simple_parse_test.go +++ b/asm/flavors/tests/simple_parse_test.go @@ -10,6 +10,7 @@ import ( "github.com/zellyn/go6502/asm/flavors/merlin" "github.com/zellyn/go6502/asm/flavors/redbook" "github.com/zellyn/go6502/asm/flavors/scma" + "github.com/zellyn/go6502/asm/inst" "github.com/zellyn/go6502/asm/lines" ) @@ -233,26 +234,29 @@ func TestSimpleCommonFunctions(t *testing.T) { ctx.AddMacroName("M1") tt.f.InitContext(ctx) - inst, err := tt.f.ParseInstr(ctx, lines.NewSimple(tt.i), false) + in, err := tt.f.ParseInstr(ctx, lines.NewSimple(tt.i), flavors.ParseModeNormal) if err != nil { t.Errorf(`%d. %s.ParseInstr("%s") => error: %s`, i, tt.f, tt.i, err) continue } - if inst.Line.Parse == nil { - t.Errorf("Got empty inst.Line.Parse on input '%s'", tt.i) + if in.Line.Parse == nil { + t.Errorf("Got empty in.Line.Parse on input '%s'", tt.i) } - _, err = inst.Compute(ctx, true) - if err != nil { - t.Errorf(`%d. %s.ParseInstr("%s"): %s.Compute(tt.f, true) => error: %s`, i, tt.f, tt.i, inst, err) - continue + if in.Type != inst.TypeOrg { + _, err = in.Compute(ctx, true) + + if err != nil { + t.Errorf(`%d. %s.ParseInstr("%s"): %s.Compute(tt.f, true) => error: %s`, i, tt.f, tt.i, in, err) + continue + } } - if inst.String() != tt.p { - t.Errorf(`%d. %s.ParseInstr("%s") = %s; want %s`, i, tt.f, tt.i, inst.String(), tt.p) + if in.String() != tt.p { + t.Errorf(`%d. %s.ParseInstr("%s") = %s; want %s`, i, tt.f, tt.i, in.String(), tt.p) continue } if tt.b != "?" { - hx := hex.EncodeToString(inst.Data) + hx := hex.EncodeToString(in.Data) // xxxxxx sets the width, but doesn't expect actual data if hx != tt.b && (len(tt.b) == 0 || tt.b[0] != 'x') { t.Errorf(`%d. %s.ParseInstr("%s").Data = [%s]; want [%s]`, i, tt.f, tt.i, hx, tt.b) @@ -261,12 +265,12 @@ func TestSimpleCommonFunctions(t *testing.T) { // Check length w := uint16(len(tt.b) / 2) - if !inst.WidthKnown { - t.Errorf(`%d. %s.WidthKnown is false`, i, inst) + if !in.WidthKnown { + t.Errorf(`%d. %s.WidthKnown is false`, i, in) continue } - if inst.Width != w { - t.Errorf(`%d. %s.Width=%d; want %d`, i, inst, inst.Width, w) + if in.Width != w { + t.Errorf(`%d. %s.Width=%d; want %d`, i, in, in.Width, w) continue } } @@ -297,9 +301,9 @@ func TestSimpleErrors(t *testing.T) { continue } ctx := &context.SimpleContext{} - inst, err := tt.f.ParseInstr(ctx, lines.NewSimple(tt.i), false) + in, err := tt.f.ParseInstr(ctx, lines.NewSimple(tt.i), flavors.ParseModeNormal) if err == nil { - t.Errorf(`%d. %s.ParseInstr("%s") want err; got %s`, i, tt.f, tt.i, inst) + t.Errorf(`%d. %s.ParseInstr("%s") want err; got %s`, i, tt.f, tt.i, in) continue } } diff --git a/asm/inst/instruction.go b/asm/inst/instruction.go index 0f2581c..7f8f5ad 100644 --- a/asm/inst/instruction.go +++ b/asm/inst/instruction.go @@ -192,11 +192,13 @@ func (i I) String() string { // Compute attempts to finalize the instruction. func (i *I) Compute(c context.Context, final bool) (bool, error) { - if i.Type == TypeEqu || i.Type == TypeTarget || i.Type == TypeOrg { - return i.computeMustKnow(c, final) + // xyzzy - remove + if i.Type == TypeOrg { + panic("Compute called with TypeOrg") + return true, nil } - if err := i.computeLabel(c, final); err != nil { - return false, err + if i.Type == TypeEqu || i.Type == TypeTarget { + return i.computeMustKnow(c, final) } if i.Final { return true, nil @@ -218,29 +220,6 @@ func (i *I) Compute(c context.Context, final bool) (bool, error) { return true, nil } -// computeLabel attempts to compute label values. -func (i *I) computeLabel(c context.Context, final bool) error { - if i.Label == "" { - return nil - } - - if i.Type == TypeEqu { - panic("computeLabel should not be called for equates") - } - - addr, aok := c.GetAddr() - if !aok { - return nil - } - - lval, lok := c.Get(i.Label) - if lok && addr != lval { - return i.Errorf("Trying to set label '%s' to $%04x, but it already has value $%04x", i.Label, addr, lval) - } - c.Set(i.Label, addr) - return nil -} - func (i *I) computeData(c context.Context, final bool) (bool, error) { if len(i.Data) > 0 { i.WidthKnown = true @@ -335,9 +314,6 @@ func (i *I) computeMustKnow(c context.Context, final bool) (bool, error) { // Don't handle labels. return true, nil } - if err := i.computeLabel(c, final); err != nil { - return false, err - } return true, nil } @@ -401,14 +377,7 @@ func (i *I) computeOp(c context.Context, final bool) (bool, error) { // It's a branch if i.Mode == opcodes.MODE_RELATIVE { - curr, ok := c.GetAddr() - if !ok { - if final { - return false, i.Errorf("cannot determine current address for '%s'", i.Command) - } - return false, nil - } - // Found both current and target addresses + curr := c.GetAddr() offset := int32(val) - (int32(curr) + 2) if offset > 127 { return false, i.Errorf("%s cannot jump forward %d (max 127) from $%04x to $%04x", i.Command, offset, curr+2, val)