diff --git a/asm/asm.go b/asm/asm.go index 74ae88f..977e759 100644 --- a/asm/asm.go +++ b/asm/asm.go @@ -5,6 +5,7 @@ import ( "io" "path/filepath" + "github.com/zellyn/go6502/asm/context" "github.com/zellyn/go6502/asm/flavors" "github.com/zellyn/go6502/asm/inst" "github.com/zellyn/go6502/asm/lines" @@ -17,13 +18,17 @@ type Assembler struct { Opener lines.Opener Insts []*inst.I Macros map[string]macros.M + Ctx *context.SimpleContext } func NewAssembler(flavor flavors.F, opener lines.Opener) *Assembler { + ctx := &context.SimpleContext{} + flavor.InitContext(ctx) return &Assembler{ Flavor: flavor, Opener: opener, Macros: make(map[string]macros.M), + Ctx: ctx, } } @@ -47,7 +52,7 @@ func (a *Assembler) Load(filename string, prefix int) error { lineSources = lineSources[1:] continue } - in, parseErr := a.Flavor.ParseInstr(line, false) + 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 continue @@ -83,17 +88,17 @@ 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) + a.Ctx.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() { + if !a.Ctx.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)) } - val, err := in.Exprs[0].Eval(a.Flavor, in.Line) + val, err := in.Exprs[0].Eval(a.Ctx, in.Line) if err != nil { return in.Errorf("cannot eval ifdef condition: %v", err) } @@ -163,14 +168,14 @@ 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, true) + in2, err := a.Flavor.ParseInstr(a.Ctx, 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 { a.Macros[m.Name] = m - a.Flavor.AddMacroName(m.Name) + a.Ctx.AddMacroName(m.Name) return nil } } @@ -178,9 +183,9 @@ func (a *Assembler) readMacro(in inst.I, ls lines.LineSource) error { // Clear out stuff that may be hanging around from the previous pass, set origin to default, etc. func (a *Assembler) initPass() { - a.Flavor.SetLastLabel("") // No last label (yet) - a.Flavor.RemoveChanged() // Remove any variables whose value ever changed. - a.Flavor.SetAddr(a.Flavor.DefaultOrigin()) + a.Ctx.SetLastLabel("") // No last label (yet) + a.Ctx.RemoveChanged() // Remove any variables whose value ever changed. + a.Ctx.SetAddr(a.Flavor.DefaultOrigin()) } // passInst performs a pass on a single instruction. It forces the @@ -188,16 +193,15 @@ 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) { - // fmt.Printf("PLUGH: in.Compute(a.Flavor, true, true) on %s\n", in) - isFinal, err = in.Compute(a.Flavor, final) + isFinal, err = in.Compute(a.Ctx, final) if err != nil { return false, err } // Update address - addr, _ := a.Flavor.GetAddr() + addr, _ := a.Ctx.GetAddr() in.Addr = addr - a.Flavor.SetAddr(addr + in.Width) + a.Ctx.SetAddr(addr + in.Width) return isFinal, nil } diff --git a/asm/context/context.go b/asm/context/context.go index ffd5ee3..72a4550 100644 --- a/asm/context/context.go +++ b/asm/context/context.go @@ -7,7 +7,8 @@ type Context interface { Get(name string) (uint16, bool) SetAddr(uint16) GetAddr() (uint16, bool) - DivZero() (uint16, error) + DivZero() *uint16 + SetDivZero(uint16) RemoveChanged() Clear() SettingOn(name string) error @@ -19,7 +20,7 @@ type Context interface { PushMacroCall(name string, number int, locals map[string]bool) PopMacroCall() bool GetMacroCall() (string, int, map[string]bool) - + SetOnOffDefaults(map[string]bool) LastLabel() string SetLastLabel(label string) } @@ -39,6 +40,7 @@ type SimpleContext struct { onOffDefaults map[string]bool macroNames map[string]bool macroCalls []macroCall + divZeroVal *uint16 } type symbolValue struct { @@ -52,10 +54,6 @@ func (sc *SimpleContext) fix() { } } -func (sc *SimpleContext) DivZero() (uint16, error) { - return 0, fmt.Errorf("Not implemented: context.SimpleContext.DivZero()") -} - func (sc *SimpleContext) Get(name string) (uint16, bool) { if name == "*" { return sc.GetAddr() @@ -185,3 +183,11 @@ func (sc *SimpleContext) LastLabel() string { func (sc *SimpleContext) SetLastLabel(l string) { sc.lastLabel = l } + +func (sc *SimpleContext) DivZero() *uint16 { + return sc.divZeroVal +} + +func (sc *SimpleContext) SetDivZero(val uint16) { + sc.divZeroVal = &val +} diff --git a/asm/expr/expression.go b/asm/expr/expression.go index 997c59b..0063d0b 100644 --- a/asm/expr/expression.go +++ b/asm/expr/expression.go @@ -174,7 +174,11 @@ func (e *E) Eval(ctx context.Context, ln *lines.Line) (uint16, error) { return 0, nil case OpDiv: if r == 0 { - return ctx.DivZero() + z := ctx.DivZero() + if z == nil { + return 0, ln.Errorf("divizion by zero") + } + return *z, nil } return l / r, nil case OpAnd: diff --git a/asm/flavors/as65/as65.go b/asm/flavors/as65/as65.go index ab0672b..d046d45 100644 --- a/asm/flavors/as65/as65.go +++ b/asm/flavors/as65/as65.go @@ -11,16 +11,14 @@ import ( // AS65 implements the AS65-compatible assembler flavor. // See http://www.kingswood-consulting.co.uk/assemblers/ -type AS65 struct { - context.SimpleContext -} +type AS65 struct{} func New() *AS65 { return &AS65{} } // Parse an entire instruction, or return an appropriate error. -func (a *AS65) ParseInstr(line lines.Line, quick bool) (inst.I, error) { +func (a *AS65) ParseInstr(ctx context.Context, line lines.Line, quick bool) (inst.I, error) { return inst.I{}, nil } @@ -47,3 +45,6 @@ func (a *AS65) LocalMacroLabels() bool { func (a *AS65) String() string { return "as65" } + +func (a *AS65) InitContext(ctx context.Context) { +} diff --git a/asm/flavors/flavors.go b/asm/flavors/flavors.go index 39631eb..7b07acc 100644 --- a/asm/flavors/flavors.go +++ b/asm/flavors/flavors.go @@ -7,10 +7,10 @@ import ( ) type F interface { - ParseInstr(Line lines.Line, quick bool) (inst.I, error) + ParseInstr(ctx context.Context, 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 + InitContext(context.Context) } diff --git a/asm/flavors/merlin/merlin.go b/asm/flavors/merlin/merlin.go index 713000b..8b02d11 100644 --- a/asm/flavors/merlin/merlin.go +++ b/asm/flavors/merlin/merlin.go @@ -1,10 +1,10 @@ package merlin import ( - "errors" "fmt" "strings" + "github.com/zellyn/go6502/asm/context" "github.com/zellyn/go6502/asm/expr" "github.com/zellyn/go6502/asm/flavors/oldschool" "github.com/zellyn/go6502/asm/inst" @@ -38,6 +38,8 @@ func New() *Merlin { m.InvCharChars = `"` m.MacroArgSep = ";" m.SuffixForWide = true + m.LocalMacroLabelsVal = true + m.DefaultOriginVal = 0x8000 m.Directives = map[string]oldschool.DirectiveInfo{ "ORG": {inst.TypeOrg, m.ParseAddress, 0}, @@ -77,16 +79,18 @@ func New() *Merlin { "!": expr.OpXor, } - m.SetOnOffDefaults(map[string]bool{ - "LST": true, // Display listing: not used - "XC": false, // Extended commands: not implemented yet - "EXP": false, // How to print macro calls - "LSTDO": false, // List conditional code? - "TR": false, // truncate listing to 3 bytes? - "CYC": false, // print cycle times? - }) + m.InitContextFunc = func(ctx context.Context) { + ctx.SetOnOffDefaults(map[string]bool{ + "LST": true, // Display listing: not used + "XC": false, // Extended commands: not implemented yet + "EXP": false, // How to print macro calls + "LSTDO": false, // List conditional code? + "TR": false, // truncate listing to 3 bytes? + "CYC": false, // print cycle times? + }) + } - m.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) { + m.SetAsciiVariation = func(ctx context.Context, in *inst.I, lp *lines.Parse) { if in.Command != "ASC" && in.Command != "DCI" { panic(fmt.Sprintf("Unimplemented/unknown ascii directive: '%s'", in.Command)) } @@ -108,11 +112,11 @@ func New() *Merlin { // the "command column" value, which caused isMacroCall to return // true, and the lp to be pointing to the following character // (probably whitespace). - m.ParseMacroCall = func(in inst.I, lp *lines.Parse) (inst.I, bool, error) { + m.ParseMacroCall = func(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, bool, error) { if in.Command == "" { return in, false, nil } - byName := m.HasMacroName(in.Command) + byName := ctx.HasMacroName(in.Command) // It's not a macro call. if in.Command != ">>>" && in.Command != "PMC" && !byName { @@ -127,7 +131,7 @@ func New() *Merlin { return in, true, in.Errorf("Expected macro name, got char '%c'", c) } in.Command = lp.Emit() - if !m.HasMacroName(in.Command) { + if !ctx.HasMacroName(in.Command) { return in, true, in.Errorf("Unknown macro: '%s'", in.Command) } if !lp.Consume(" ./,-(") { @@ -153,13 +157,13 @@ func New() *Merlin { return in, true, nil } - m.FixLabel = func(label string) (string, error) { - _, macroCount, locals := m.GetMacroCall() + m.FixLabel = func(ctx context.Context, label string) (string, error) { + _, macroCount, locals := ctx.GetMacroCall() switch { case label == "": return label, nil case label[0] == ':': - if last := m.LastLabel(); last == "" { + if last := ctx.LastLabel(); last == "" { return "", fmt.Errorf("sublabel '%s' without previous label", label) } else { return fmt.Sprintf("%s/%s", last, label), nil @@ -178,15 +182,7 @@ func New() *Merlin { return m } -func (m *Merlin) Zero() (uint16, error) { - return 0, errors.New("Division by zero.") -} - -func (m *Merlin) DefaultOrigin() uint16 { - return 0x8000 -} - -func (m *Merlin) ParseInclude(in inst.I, lp *lines.Parse) (inst.I, error) { +func (m *Merlin) ParseInclude(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(whitespace) lp.AcceptUntil(";") filename := strings.TrimSpace(lp.Emit()) @@ -204,7 +200,3 @@ func (m *Merlin) ParseInclude(in inst.I, lp *lines.Parse) (inst.I, error) { in.Final = true return in, nil } - -func (m *Merlin) LocalMacroLabels() bool { - return true -} diff --git a/asm/flavors/oldschool/oldschool.go b/asm/flavors/oldschool/oldschool.go index c945c1d..502981b 100644 --- a/asm/flavors/oldschool/oldschool.go +++ b/asm/flavors/oldschool/oldschool.go @@ -2,6 +2,7 @@ package oldschool import ( "encoding/hex" + "errors" "fmt" "regexp" "strconv" @@ -25,7 +26,7 @@ const fileChars = Letters + Digits + "." type DirectiveInfo struct { Type inst.Type - Func func(inst.I, *lines.Parse) (inst.I, error) + Func func(context.Context, inst.I, *lines.Parse) (inst.I, error) Var inst.Variant } @@ -40,32 +41,34 @@ const ( // Base implements the S-C Macro Assembler-compatible assembler flavor. // See http://www.txbobsc.com/scsc/ and http://stjarnhimlen.se/apple2/ type Base struct { - Name string - Directives map[string]DirectiveInfo - Operators map[string]expr.Operator - context.SimpleContext - LabelChars string - LabelColons Requiredness - ExplicitARegister Requiredness - HexCommas Requiredness - SpacesForComment int // this many spaces after command means it's the comment field - StringEndOptional bool // can omit closing delimeter from string args? - SuffixForWide bool // is eg. "LDA:" a force-wide on "LDA"? (Merlin) - CommentChar rune - BinaryChar rune - MsbChars string - LsbChars string - ImmediateChars string - 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 + Name string + Directives map[string]DirectiveInfo + Operators map[string]expr.Operator + LabelChars string + LabelColons Requiredness + ExplicitARegister Requiredness + HexCommas Requiredness + SpacesForComment int // this many spaces after command means it's the comment field + StringEndOptional bool // can omit closing delimeter from string args? + SuffixForWide bool // is eg. "LDA:" a force-wide on "LDA"? (Merlin) + CommentChar rune + BinaryChar rune + MsbChars string + LsbChars string + ImmediateChars string + operatorChars string + CharChars string + InvCharChars string + MacroArgSep string + ExtraCommenty func(string) bool + SetAsciiVariation func(context.Context, *inst.I, *lines.Parse) + ParseMacroCall func(context.Context, inst.I, *lines.Parse) (inst.I, bool, error) + IsNewParentLabel func(label string) bool + InitContextFunc func(context.Context) + FixLabel func(context.Context, string) (string, error) + LocalMacroLabelsVal bool + DivZeroVal *uint16 + DefaultOriginVal uint16 } func (a *Base) String() string { @@ -73,7 +76,7 @@ func (a *Base) String() string { } // Parse an entire instruction, or return an appropriate error. -func (a *Base) ParseInstr(line lines.Line, quick bool) (inst.I, error) { +func (a *Base) ParseInstr(ctx context.Context, line lines.Line, quick bool) (inst.I, error) { lp := line.Parse in := inst.I{Line: &line} @@ -125,13 +128,13 @@ func (a *Base) ParseInstr(line lines.Line, quick bool) (inst.I, error) { // If appropriate, set the last parent label. if !quick { parent := a.IsNewParentLabel(in.Label) - newL, err := a.FixLabel(in.Label) + newL, err := a.FixLabel(ctx, in.Label) if err != nil { return in, in.Errorf("%v", err) } in.Label = newL if parent { - a.SetLastLabel(in.Label) + ctx.SetLastLabel(in.Label) } } @@ -142,16 +145,12 @@ func (a *Base) ParseInstr(line lines.Line, quick bool) (inst.I, error) { in.Type = inst.TypeNone return in, nil } - return a.parseCmd(in, lp, quick) -} - -func (a *Base) DefaultOrigin() uint16 { - return 0x0800 + return a.parseCmd(ctx, in, lp, quick) } // 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, quick bool) (inst.I, error) { +func (a *Base) parseCmd(ctx context.Context, 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) @@ -170,7 +169,7 @@ func (a *Base) parseCmd(in inst.I, lp *lines.Parse, quick bool) (inst.I, error) } // Give ParseMacroCall a chance if a.ParseMacroCall != nil { - i, isMacro, err := a.ParseMacroCall(in, lp) + i, isMacro, err := a.ParseMacroCall(ctx, in, lp) if err != nil { return in, err } @@ -185,16 +184,16 @@ func (a *Base) parseCmd(in inst.I, lp *lines.Parse, quick bool) (inst.I, error) if dir.Func == nil { return in, nil } - return dir.Func(in, lp) + return dir.Func(ctx, in, lp) } - if a.HasSetting(in.Command) { - return a.parseSetting(in, lp) + if ctx.HasSetting(in.Command) { + return a.parseSetting(ctx, in, lp) } if summary, ok := opcodes.ByName[in.Command]; ok { in.Type = inst.TypeOp - return a.parseOpArgs(in, lp, summary, false) + return a.parseOpArgs(ctx, in, lp, summary, false) } // Merlin lets you say "LDA:" or "LDA@" or "LDAZ" to force non-zero-page. @@ -203,14 +202,14 @@ func (a *Base) parseCmd(in inst.I, lp *lines.Parse, quick bool) (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(ctx, 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(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { in.Type = inst.TypeSetting lp.IgnoreRun(Whitespace) if !lp.AcceptRun(Letters) { @@ -220,9 +219,9 @@ func (a *Base) parseSetting(in inst.I, lp *lines.Parse) (inst.I, error) { in.TextArg = lp.Emit() switch in.TextArg { case "ON": - a.SettingOn(in.Command) + ctx.SettingOn(in.Command) case "OFF": - a.SettingOff(in.Command) + ctx.SettingOff(in.Command) default: return in, in.Errorf("expecting ON/OFF, found '%s'", in.TextArg) } @@ -269,7 +268,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(in inst.I, lp *lines.Parse, summary opcodes.OpSummary, forceWide bool) (inst.I, error) { +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] @@ -314,7 +313,7 @@ func (a *Base) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary return in, in.Errorf("%s doesn't support any indirect modes", in.Command) } xy := '-' - expr, err := a.ParseExpression(in, lp) + expr, err := a.parseExpression(ctx, in, lp) if err != nil { return in, err } @@ -371,12 +370,12 @@ func (a *Base) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary } } - return common.DecodeOp(a, in, summary, indirect, xy, forceWide) + return common.DecodeOp(ctx, in, summary, indirect, xy, forceWide) } -func (a *Base) ParseAddress(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseAddress(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) - expr, err := a.ParseExpression(in, lp) + expr, err := a.parseExpression(ctx, in, lp) if err != nil { return in, err } @@ -387,9 +386,9 @@ func (a *Base) ParseAddress(in inst.I, lp *lines.Parse) (inst.I, error) { return in, nil } -func (a *Base) ParseAscii(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseAscii(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) - a.SetAsciiVariation(&in, lp) + a.SetAsciiVariation(ctx, &in, lp) var invert, invertLast byte switch in.Var { case inst.VarAscii: @@ -424,9 +423,9 @@ func (a *Base) ParseAscii(in inst.I, lp *lines.Parse) (inst.I, error) { return in, nil } -func (a *Base) ParseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseBlockStorage(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) - ex, err := a.ParseExpression(in, lp) + ex, err := a.parseExpression(ctx, in, lp) if err != nil { return in, err } @@ -434,10 +433,10 @@ func (a *Base) ParseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) { return in, nil } -func (a *Base) ParseData(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseData(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) for { - ex, err := a.ParseExpression(in, lp) + ex, err := a.parseExpression(ctx, in, lp) if err != nil { return in, err } @@ -449,9 +448,9 @@ func (a *Base) ParseData(in inst.I, lp *lines.Parse) (inst.I, error) { return in, nil } -func (a *Base) ParseDo(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseDo(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) - expr, err := a.ParseExpression(in, lp) + expr, err := a.parseExpression(ctx, in, lp) if err != nil { return in, err } @@ -462,12 +461,18 @@ func (a *Base) ParseDo(in inst.I, lp *lines.Parse) (inst.I, error) { return in, nil } -func (a *Base) ParseEquate(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseEquate(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) - expr, err := a.ParseExpression(in, lp) + expr, err := a.parseExpression(ctx, in, lp) if err != nil { return in, err } + + xyzzy, err := expr.Eval(ctx, in.Line) + if err != nil { + return in, err + } + _ = xyzzy in.Exprs = append(in.Exprs, expr) in.WidthKnown = true in.Width = 0 @@ -475,7 +480,7 @@ func (a *Base) ParseEquate(in inst.I, lp *lines.Parse) (inst.I, error) { return in, nil } -func (a *Base) ParseHexString(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseHexString(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.AcceptRun(Whitespace) for { lp.Ignore() @@ -500,7 +505,7 @@ func (a *Base) ParseHexString(in inst.I, lp *lines.Parse) (inst.I, error) { return in, nil } -func (a *Base) ParseInclude(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseInclude(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) if !lp.AcceptRun(fileChars) { return in, in.Errorf("Expecting filename, found '%c'", lp.Next()) @@ -513,7 +518,7 @@ func (a *Base) ParseInclude(in inst.I, lp *lines.Parse) (inst.I, error) { } // For assemblers where the macro name follows the macro directive. -func (a *Base) ParseMacroStart(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseMacroStart(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { lp.IgnoreRun(Whitespace) if !lp.AcceptRun(cmdChars) { return in, in.Errorf("Expecting valid macro name, found '%c'", lp.Next()) @@ -526,7 +531,7 @@ func (a *Base) ParseMacroStart(in inst.I, lp *lines.Parse) (inst.I, error) { } // For assemblers where the macro name is the label, followed by the directive. -func (a *Base) MarkMacroStart(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) MarkMacroStart(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { in.TextArg, in.Label = in.Label, "" in.WidthKnown = true in.Width = 0 @@ -534,18 +539,18 @@ func (a *Base) MarkMacroStart(in inst.I, lp *lines.Parse) (inst.I, error) { return in, nil } -func (a *Base) ParseNoArgDir(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseNoArgDir(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { in.WidthKnown = true in.Width = 0 in.Final = true return in, nil } -func (a *Base) ParseNotImplemented(in inst.I, lp *lines.Parse) (inst.I, error) { +func (a *Base) ParseNotImplemented(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) { return in, in.Errorf("not implemented (yet?): %s", in.Command) } -func (a *Base) ParseExpression(in inst.I, lp *lines.Parse) (*expr.E, error) { +func (a *Base) parseExpression(ctx context.Context, in inst.I, lp *lines.Parse) (*expr.E, error) { if a.operatorChars == "" { for k, _ := range a.Operators { @@ -581,14 +586,14 @@ func (a *Base) ParseExpression(in inst.I, lp *lines.Parse) (*expr.E, error) { } } - tree, err := a.ParseTerm(in, lp) + tree, err := a.ParseTerm(ctx, in, lp) if err != nil { return &expr.E{}, err } for lp.Accept(a.operatorChars) { c := lp.Emit() - right, err := a.ParseTerm(in, lp) + right, err := a.ParseTerm(ctx, in, lp) if err != nil { return &expr.E{}, err } @@ -602,7 +607,7 @@ func (a *Base) ParseExpression(in inst.I, lp *lines.Parse) (*expr.E, error) { return tree, nil } -func (a *Base) ParseTerm(in inst.I, lp *lines.Parse) (*expr.E, error) { +func (a *Base) ParseTerm(ctx context.Context, in inst.I, lp *lines.Parse) (*expr.E, error) { ex := &expr.E{} top := ex @@ -688,7 +693,7 @@ 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) + newL, err := a.FixLabel(ctx, ex.Text) if err != nil { return &expr.E{}, in.Errorf("%v", err) } @@ -715,18 +720,18 @@ func (a *Base) DefaultIsNewParentLabel(label string) bool { return label != "" && label[0] != '.' } -func (a *Base) DefaultFixLabel(label string) (string, error) { +func (a *Base) DefaultFixLabel(ctx context.Context, label string) (string, error) { switch { case label == "": return label, nil case label[0] == '.': - if last := a.LastLabel(); last == "" { + if last := ctx.LastLabel(); last == "" { return "", fmt.Errorf("sublabel '%s' without previous label", label) } else { return fmt.Sprintf("%s/%s", last, label), nil } case label[0] == ':': - _, macroCall, _ := a.GetMacroCall() + _, macroCall, _ := ctx.GetMacroCall() if macroCall == 0 { return "", fmt.Errorf("macro-local label '%s' seen outside macro", label) } else { @@ -735,3 +740,24 @@ func (a *Base) DefaultFixLabel(label string) (string, error) { } return label, nil } + +func (a *Base) LocalMacroLabels() bool { + return a.LocalMacroLabelsVal +} + +func (a *Base) Zero() (uint16, error) { + if a.DivZeroVal == nil { + return 0, errors.New("Division by zero.") + } + return *a.DivZeroVal, nil +} + +func (a *Base) DefaultOrigin() uint16 { + return a.DefaultOriginVal +} + +func (a *Base) InitContext(ctx context.Context) { + if a.InitContextFunc != nil { + a.InitContextFunc(ctx) + } +} diff --git a/asm/flavors/redbook/redbook.go b/asm/flavors/redbook/redbook.go index db259c2..0273f78 100644 --- a/asm/flavors/redbook/redbook.go +++ b/asm/flavors/redbook/redbook.go @@ -3,6 +3,7 @@ package redbook import ( "fmt" + "github.com/zellyn/go6502/asm/context" "github.com/zellyn/go6502/asm/expr" "github.com/zellyn/go6502/asm/flavors/oldschool" "github.com/zellyn/go6502/asm/inst" @@ -37,6 +38,7 @@ func newRedbook(name string) *RedBook { r.MsbChars = "/" r.ImmediateChars = "#" r.HexCommas = oldschool.ReqOptional + r.DefaultOriginVal = 0x0800 r.Directives = map[string]oldschool.DirectiveInfo{ "ORG": {inst.TypeOrg, r.ParseAddress, 0}, @@ -67,14 +69,16 @@ func newRedbook(name string) *RedBook { "=": expr.OpEq, } - r.SetOnOffDefaults(map[string]bool{ - "MSB": true, // MSB defaults to true, as per manual - "LST": true, // Display listing: not used - }) + r.InitContextFunc = func(ctx context.Context) { + ctx.SetOnOffDefaults(map[string]bool{ + "MSB": true, // MSB defaults to true, as per manual + "LST": true, // Display listing: not used + }) + } - r.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) { + r.SetAsciiVariation = func(ctx context.Context, in *inst.I, lp *lines.Parse) { if in.Command == "ASC" { - if r.Setting("MSB") { + if ctx.Setting("MSB") { in.Var = inst.VarAsciiHi } else { in.Var = inst.VarAscii @@ -93,7 +97,3 @@ func newRedbook(name string) *RedBook { return r } - -func (r *RedBook) LocalMacroLabels() bool { - return false -} diff --git a/asm/flavors/scma/scma.go b/asm/flavors/scma/scma.go index 95d38b7..2bcbd64 100644 --- a/asm/flavors/scma/scma.go +++ b/asm/flavors/scma/scma.go @@ -3,6 +3,7 @@ package scma import ( "strings" + "github.com/zellyn/go6502/asm/context" "github.com/zellyn/go6502/asm/expr" "github.com/zellyn/go6502/asm/flavors/oldschool" "github.com/zellyn/go6502/asm/inst" @@ -29,6 +30,9 @@ func New() *SCMA { a.ImmediateChars = "#" a.CharChars = "'" a.MacroArgSep = "," + a.DefaultOriginVal = 0x0800 + divZeroVal := uint16(0xffff) + a.DivZeroVal = &divZeroVal a.Directives = map[string]oldschool.DirectiveInfo{ ".IN": {inst.TypeInclude, a.ParseInclude, 0}, @@ -67,7 +71,7 @@ func New() *SCMA { return strings.HasPrefix(s, commentWhitespacePrefix) } - a.SetAsciiVariation = func(in *inst.I, lp *lines.Parse) { + a.SetAsciiVariation = func(ctx context.Context, in *inst.I, lp *lines.Parse) { // For S-C Assembler, leading "-" flips high bit invert := lp.Consume("-") invertLast := in.Command == ".AT" @@ -87,7 +91,7 @@ func New() *SCMA { // the "command column" value, which caused isMacroCall to return // true, and the lp to be pointing to the following character // (probably whitespace). - a.ParseMacroCall = func(in inst.I, lp *lines.Parse) (inst.I, bool, error) { + a.ParseMacroCall = func(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, bool, error) { if in.Command == "" || in.Command[0] != '>' { // not a macro call return in, false, nil @@ -117,11 +121,3 @@ func New() *SCMA { return a } - -func (a *SCMA) Zero() (uint16, error) { - return uint16(0xffff), nil -} - -func (a *SCMA) LocalMacroLabels() bool { - return false -} diff --git a/asm/flavors/tests/assemble_test.go b/asm/flavors/tests/assemble_test.go index 403781a..ddb5040 100644 --- a/asm/flavors/tests/assemble_test.go +++ b/asm/flavors/tests/assemble_test.go @@ -2,7 +2,6 @@ package flavors import ( "encoding/hex" - "fmt" "reflect" "strings" "testing" @@ -24,15 +23,23 @@ func h(s string) []byte { return b } +type asmFactory func() *asm.Assembler + func TestMultiline(t *testing.T) { o := lines.NewTestOpener() - ss := asm.NewAssembler(scma.New(), o) - ra := asm.NewAssembler(redbook.NewRedbookA(), o) - mm := asm.NewAssembler(merlin.New(), o) + ss := asmFactory(func() *asm.Assembler { + return asm.NewAssembler(scma.New(), o) + }) + ra := asmFactory(func() *asm.Assembler { + return asm.NewAssembler(redbook.NewRedbookA(), o) + }) + mm := asmFactory(func() *asm.Assembler { + return asm.NewAssembler(merlin.New(), o) + }) tests := []struct { - a *asm.Assembler // assembler + af asmFactory // assembler factory name string // name i []string // main file: lines ii map[string][]string // other files: lines @@ -293,42 +300,41 @@ func TestMultiline(t *testing.T) { if !tt.active { continue } - fmt.Println(tt.name) + a := tt.af() 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) + t.Fatalf(`%d("%s" - %s): test case must specify bytes or pieces`, i, tt.name, a.Flavor) } - tt.a.Reset() o.Clear() o["TESTFILE"] = strings.Join(tt.i, "\n") for k, v := range tt.ii { o[k] = strings.Join(v, "\n") } - if err := tt.a.Load("TESTFILE", 0); err != nil { - t.Fatalf(`%d("%s" - %s): tt.a.Load("TESTFILE") failed: %s`, i, tt.name, tt.a.Flavor, err) + if err := a.Load("TESTFILE", 0); err != nil { + t.Fatalf(`%d("%s" - %s): a.Load("TESTFILE") failed: %s`, i, tt.name, a.Flavor, err) } - err := tt.a.Pass2() + err := a.Pass2() if err != nil { - t.Fatalf(`%d("%s" - %s): tt.a.Pass(true) failed: %s`, i, tt.name, tt.a.Flavor, err) + t.Fatalf(`%d("%s" - %s): a.Pass(true) failed: %s`, i, tt.name, a.Flavor, err) } if tt.b != "" { - bb, err := tt.a.RawBytes() + bb, err := a.RawBytes() if err != nil { - t.Fatalf(`%d("%s" - %s): tt.a.RawBytes() failed: %s`, i, tt.name, tt.a.Flavor, err) + t.Fatalf(`%d("%s" - %s): a.RawBytes() failed: %s`, i, tt.name, a.Flavor, err) } hx := hex.EncodeToString(bb) if hx != tt.b { - t.Fatalf(`%d("%s" - %s): tt.a.RawBytes()=[%s]; want [%s]`, i, tt.name, tt.a.Flavor, hx, tt.b) + t.Fatalf(`%d("%s" - %s): a.RawBytes()=[%s]; want [%s]`, i, tt.name, a.Flavor, hx, tt.b) } } if len(tt.ps) != 0 { - m, err := tt.a.Membuf() + m, err := a.Membuf() if err != nil { - t.Fatalf(`%d("%s" - %s): tt.a.Membuf() failed: %s`, i, tt.name, tt.a.Flavor, err) + t.Fatalf(`%d("%s" - %s): a.Membuf() failed: %s`, i, tt.name, a.Flavor, err) } ps := m.Pieces() if !reflect.DeepEqual(ps, tt.ps) { - t.Fatalf(`%d("%s" - %s): tt.Membuf().Pieces() = %v; want %v`, i, tt.name, tt.a.Flavor, ps, tt.ps) + t.Fatalf(`%d("%s" - %s): tt.Membuf().Pieces() = %v; want %v`, i, tt.name, a.Flavor, ps, tt.ps) } } } diff --git a/asm/flavors/tests/simple_parse_test.go b/asm/flavors/tests/simple_parse_test.go index d3782b0..421f1a4 100644 --- a/asm/flavors/tests/simple_parse_test.go +++ b/asm/flavors/tests/simple_parse_test.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "testing" + "github.com/zellyn/go6502/asm/context" "github.com/zellyn/go6502/asm/flavors" "github.com/zellyn/go6502/asm/flavors/as65" "github.com/zellyn/go6502/asm/flavors/merlin" @@ -20,7 +21,7 @@ func TestSimpleCommonFunctions(t *testing.T) { mm := merlin.New() tests := []struct { - a flavors.F // assembler flavor + f flavors.F // assembler flavor i string // input string p string // printed instruction, expected b string // bytes, expected @@ -209,6 +210,7 @@ func TestSimpleCommonFunctions(t *testing.T) { {ss, ` .AT -DABCD`, "{data/b}", "c1c243"}, {ss, ` .AT /ABC/`, "{data/b}", "4142c3"}, {ss, `>SAM AB,$12,"A B","A, B, "" C"`, `{call SAM {"AB", "$12", "A B", "A, B, \" C"}}`, ""}, + // {ss, " LDA #3/0", "{LDA/imm (lsb (/ $0003 $0000))}", "a9ff"}, } // TODO(zellyn): Add tests for finalization of four SCMA directives: @@ -220,30 +222,32 @@ func TestSimpleCommonFunctions(t *testing.T) { // TODO(zellyn): Test AS65 and Merlin too. // Initialize to a known state for testing. - tt.a.Clear() - tt.a.SetAddr(0x2345) - tt.a.Set("A.B", 0x6789) - tt.a.Set("C.D", 0x789a) - tt.a.Set("L2", 0x6789) - tt.a.Set("L3", 0x789a) - tt.a.AddMacroName("INCW") - tt.a.AddMacroName("M1") + ctx := &context.SimpleContext{} + ctx.Clear() + ctx.SetAddr(0x2345) + ctx.Set("A.B", 0x6789) + ctx.Set("C.D", 0x789a) + ctx.Set("L2", 0x6789) + ctx.Set("L3", 0x789a) + ctx.AddMacroName("INCW") + ctx.AddMacroName("M1") + tt.f.InitContext(ctx) - inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i), false) + inst, err := tt.f.ParseInstr(ctx, lines.NewSimple(tt.i), false) if err != nil { - t.Errorf(`%d. %s.ParseInstr("%s") => error: %s`, i, tt.a, tt.i, err) + 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) } - _, err = inst.Compute(tt.a, true) + _, err = inst.Compute(ctx, true) if err != nil { - t.Errorf(`%d. %s.ParseInstr("%s"): %s.Compute(tt.a, true) => error: %s`, i, tt.a, tt.i, inst, err) + t.Errorf(`%d. %s.ParseInstr("%s"): %s.Compute(tt.f, true) => error: %s`, i, tt.f, tt.i, inst, err) continue } if inst.String() != tt.p { - t.Errorf(`%d. %s.ParseInstr("%s") = %s; want %s`, i, tt.a, tt.i, inst.String(), tt.p) + t.Errorf(`%d. %s.ParseInstr("%s") = %s; want %s`, i, tt.f, tt.i, inst.String(), tt.p) continue } @@ -251,7 +255,7 @@ func TestSimpleCommonFunctions(t *testing.T) { hx := hex.EncodeToString(inst.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.a, tt.i, hx, tt.b) + t.Errorf(`%d. %s.ParseInstr("%s").Data = [%s]; want [%s]`, i, tt.f, tt.i, hx, tt.b) continue } @@ -275,7 +279,7 @@ func TestSimpleErrors(t *testing.T) { mm := merlin.New() tests := []struct { - a flavors.F // assembler flavor + f flavors.F // assembler flavor i string // input string }{ @@ -289,13 +293,13 @@ func TestSimpleErrors(t *testing.T) { } for i, tt := range tests { - // TODO(zellyn): Test AS65 and Merlin too. - if tt.a != ss { + if tt.f != ss { continue } - inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i), false) + ctx := &context.SimpleContext{} + inst, err := tt.f.ParseInstr(ctx, lines.NewSimple(tt.i), false) if err == nil { - t.Errorf(`%d. %s.ParseInstr("%s") want err; got %s`, i, tt.a, tt.i, inst) + t.Errorf(`%d. %s.ParseInstr("%s") want err; got %s`, i, tt.f, tt.i, inst) continue } }