1
0
mirror of https://github.com/zellyn/go6502.git synced 2024-06-06 20:29:34 +00:00

Moving more stuff to first pass.

This commit is contained in:
Zellyn Hunter 2014-08-29 16:34:52 -07:00
parent cab917364e
commit c07a3732a6
12 changed files with 160 additions and 107 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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