Separated context out of Flavor

This commit is contained in:
Zellyn Hunter 2014-08-22 08:24:30 -07:00
parent 457446772a
commit c7308bf05c
11 changed files with 226 additions and 187 deletions

View File

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

View File

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

View File

@ -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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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