mirror of
https://github.com/zellyn/go6502.git
synced 2025-04-14 17:41:01 +00:00
Working on SCMA-compatible assembler.
This commit is contained in:
parent
d9fa2336d3
commit
42bde82f34
190
asm/asm.go
Normal file
190
asm/asm.go
Normal file
@ -0,0 +1,190 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zellyn/go6502/asm/context"
|
||||
"github.com/zellyn/go6502/asm/inst"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
)
|
||||
|
||||
type Flavor interface {
|
||||
ParseInstr(Line lines.Line) (inst.I, error)
|
||||
DefaultOrigin() (uint16, error)
|
||||
context.Context
|
||||
}
|
||||
|
||||
type Assembler struct {
|
||||
Flavor Flavor
|
||||
Opener lines.Opener
|
||||
Insts []*inst.I
|
||||
LastLabel string
|
||||
}
|
||||
|
||||
func NewAssembler(flavor Flavor, opener lines.Opener) *Assembler {
|
||||
return &Assembler{Flavor: flavor, Opener: opener}
|
||||
}
|
||||
|
||||
// Load loads a new assembler file, deleting any previous data.
|
||||
func (a *Assembler) Load(filename string) error {
|
||||
context := lines.Context{Filename: filename}
|
||||
ls, err := lines.NewFileLineSource(filename, context, a.Opener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lineSources := []lines.LineSource{ls}
|
||||
ifdefs := []bool{}
|
||||
for len(lineSources) > 0 {
|
||||
line, done, err := lineSources[0].Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if done {
|
||||
lineSources = lineSources[1:]
|
||||
continue
|
||||
}
|
||||
in, err := a.Flavor.ParseInstr(line)
|
||||
if len(ifdefs) > 0 && !ifdefs[0] && in.Type != inst.TypeIfdefElse && in.Type != inst.TypeIfdefEnd {
|
||||
// we're in an inactive ifdef branch
|
||||
continue
|
||||
}
|
||||
if in.Label != "" && in.Label[0] != '.' {
|
||||
a.Flavor.SetLastLabel(in.Label)
|
||||
}
|
||||
in.FixLabels(a.Flavor.GetLastLabel())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := a.passInst(&in, false, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch in.Type {
|
||||
case inst.TypeUnknown:
|
||||
return fmt.Errorf("Unknown instruction: %s", line)
|
||||
case inst.TypeMacroStart:
|
||||
return fmt.Errorf("Macro start not (yet) implemented: %s", line)
|
||||
case inst.TypeMacroCall:
|
||||
return fmt.Errorf("Macro call not (yet) implemented: %s", line)
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot eval ifdef condition: %v", err)
|
||||
}
|
||||
ifdefs = append([]bool{val != 0}, ifdefs...)
|
||||
|
||||
case inst.TypeIfdefElse:
|
||||
if len(ifdefs) == 0 {
|
||||
return fmt.Errorf("Ifdef else branch encountered outside ifdef: %s", line)
|
||||
}
|
||||
ifdefs[0] = !ifdefs[0]
|
||||
case inst.TypeIfdefEnd:
|
||||
if len(ifdefs) == 0 {
|
||||
return fmt.Errorf("Ifdef end encountered outside ifdef: %s", line)
|
||||
}
|
||||
ifdefs = ifdefs[1:]
|
||||
case inst.TypeInclude:
|
||||
subContext := lines.Context{Filename: in.TextArg, Parent: in.Line}
|
||||
subLs, err := lines.NewFileLineSource(in.TextArg, subContext, a.Opener)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error including file: %v", err)
|
||||
}
|
||||
lineSources = append([]lines.LineSource{subLs}, lineSources...)
|
||||
continue // no need to append
|
||||
case inst.TypeTarget:
|
||||
return fmt.Errorf("Target not (yet) implemented: %s", line)
|
||||
case inst.TypeSegment:
|
||||
return fmt.Errorf("Segment not (yet) implemented: %s", line)
|
||||
case inst.TypeEnd:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
a.Insts = append(a.Insts, &in)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
if org, err := a.Flavor.DefaultOrigin(); err == nil {
|
||||
a.Flavor.SetAddr(org)
|
||||
} else {
|
||||
a.Flavor.ClearAddr()
|
||||
}
|
||||
}
|
||||
|
||||
// passInst performs a pass on a single instruction. Depending on
|
||||
// whether the instruction width can be determined, it updates or
|
||||
// clears the current address. If setWidth is true, it forces the
|
||||
// instruction to decide its final width. If final is true, and the
|
||||
// instruction cannot be finalized, it returns an error.
|
||||
func (a *Assembler) passInst(in *inst.I, setWidth, 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, setWidth, final)
|
||||
fmt.Printf("PLUGH: isFinal=%v, in.Final=%v, in.WidthKnown=%v, in.MinWidth=%v\n", isFinal, in.Final, in.WidthKnown, in.MinWidth)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if in.WidthKnown && in.MinWidth != in.MaxWidth {
|
||||
panic(fmt.Sprintf("inst.I %s: WidthKnown=true, but MinWidth=%d, MaxWidth=%d", in, in.MinWidth, in.MaxWidth))
|
||||
}
|
||||
|
||||
if in.WidthKnown && a.Flavor.AddrKnown() {
|
||||
addr, _ := a.Flavor.GetAddr()
|
||||
a.Flavor.SetAddr(addr + in.MinWidth)
|
||||
} else {
|
||||
a.Flavor.ClearAddr()
|
||||
}
|
||||
|
||||
return isFinal, nil
|
||||
}
|
||||
|
||||
// Pass performs an assembly pass. If setWidth is true, it causes all
|
||||
// instructions to set their final width. If final is true, it returns
|
||||
// an error for any instruction that cannot be finalized.
|
||||
func (a *Assembler) Pass(setWidth, final bool) (isFinal bool, err error) {
|
||||
fmt.Printf("PLUGH: Pass(%v, %v): %d instructions\n", setWidth, final, len(a.Insts))
|
||||
setWidth = setWidth || final // final ⊢ setWidth
|
||||
|
||||
a.initPass()
|
||||
|
||||
isFinal = true
|
||||
for _, in := range a.Insts {
|
||||
instFinal, err := a.passInst(in, setWidth, final)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if final && !instFinal {
|
||||
return false, fmt.Errorf("Cannot finalize instruction: %s", in)
|
||||
}
|
||||
fmt.Printf("PLUGH: instFinal=%v, in.Final=%v, in.WidthKnown=%v, in.MinWidth=%v\n", instFinal, in.Final, in.WidthKnown, in.MinWidth)
|
||||
isFinal = isFinal && instFinal
|
||||
}
|
||||
|
||||
return isFinal, nil
|
||||
}
|
||||
|
||||
// RawBytes returns the raw bytes, sequentially. Intended for testing,
|
||||
// the return value gives no indication of address changes.
|
||||
func (a *Assembler) RawBytes() ([]byte, error) {
|
||||
result := []byte{}
|
||||
for _, in := range a.Insts {
|
||||
if !in.Final {
|
||||
return []byte{}, fmt.Errorf("cannot finalize value: %s", in)
|
||||
}
|
||||
result = append(result, in.Data...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *Assembler) Reset() {
|
||||
a.Insts = nil
|
||||
a.LastLabel = ""
|
||||
}
|
63
asm/asm.org
Normal file
63
asm/asm.org
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
|
||||
| Concept | SCSC | AS65 | Merlin |
|
||||
|-------------------------------+------------------+------------------+--------|
|
||||
| origin | .OR | org | |
|
||||
| | | | |
|
||||
| target address | .TA | | |
|
||||
| | | | |
|
||||
| include | .IN | | |
|
||||
| | | | |
|
||||
| comment | * | ; | |
|
||||
| | >1 space | | |
|
||||
| | | | |
|
||||
| label | at left | | |
|
||||
| | | | |
|
||||
| equ | .EQ | = | |
|
||||
| | | equ | |
|
||||
| | | | |
|
||||
| binary | | %00000001 | |
|
||||
| | | | |
|
||||
| macro | .MA macro_name | macro_name macro | |
|
||||
| | .EM | endm | |
|
||||
| | | | |
|
||||
| if | .DO | if foo = 1 | |
|
||||
| | .ELSE | endif | |
|
||||
| | [.ELSE] (toggle) | | |
|
||||
| | .FIN | | |
|
||||
| data | | | |
|
||||
| - word (LE) | .DA expr | | |
|
||||
| - byte (LSB) | .DA #expr | | |
|
||||
| - byte (MSB) | .DA /expr | | |
|
||||
| | | | |
|
||||
| ascii string | | | |
|
||||
| - normal use | .AS "foo" | | |
|
||||
| - arb. delimeter | .AS dfood | | |
|
||||
| - high bit set | .AS -"foo" | | |
|
||||
| - last high bit opposite | .AT "foo" | | |
|
||||
| | | | |
|
||||
| hex string | .HS | | |
|
||||
| | | | |
|
||||
| reserve space (block storage) | .BS <bytes> | | |
|
||||
| | | | |
|
||||
|
||||
Things to ignore
|
||||
| Concept | SCSC | AS65 |
|
||||
|---------+------------------------+------|
|
||||
| title | .TI <lines/page>,title | |
|
||||
| file | .TF filename | |
|
||||
|
||||
|
||||
|
||||
* AS65 notes
|
||||
https://github.com/Klaus2m5/6502_65C02_functional_tests/blob/master/6502_functional_test.a65
|
||||
|
||||
|
||||
* SCSC notes
|
||||
|
||||
.DO -- Conditional Assembly 5-9
|
||||
.ELSE -- Conditional Assembly 5-9
|
||||
.FIN -- Conditional Assembly 5-9
|
||||
.MA -- Macro Definition 5-11
|
||||
.EM -- End of Macro 5-11
|
||||
|
97
asm/context/context.go
Normal file
97
asm/context/context.go
Normal file
@ -0,0 +1,97 @@
|
||||
package context
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Context interface {
|
||||
Set(name string, value uint16)
|
||||
Get(name string) (uint16, bool)
|
||||
SetAddr(uint16)
|
||||
ClearAddr()
|
||||
GetAddr() (uint16, bool)
|
||||
Zero() (uint16, error) // type ZeroFunc
|
||||
RemoveChanged()
|
||||
AddrKnown() bool
|
||||
GetLastLabel() string
|
||||
SetLastLabel(string)
|
||||
Clear()
|
||||
}
|
||||
|
||||
type SimpleContext struct {
|
||||
symbols map[string]symbolValue
|
||||
addr int32
|
||||
lastLabel string
|
||||
}
|
||||
|
||||
type symbolValue struct {
|
||||
v uint16
|
||||
changed bool // Did the value ever change?
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) fix() {
|
||||
if sc.symbols == nil {
|
||||
sc.symbols = make(map[string]symbolValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) Zero() (uint16, error) {
|
||||
return 0, fmt.Errorf("Not implemented: context.SimpleContext.Zero()")
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) Get(name string) (uint16, bool) {
|
||||
if name == "*" {
|
||||
return sc.GetAddr()
|
||||
}
|
||||
sc.fix()
|
||||
s, found := sc.symbols[name]
|
||||
return s.v, found
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) ClearAddr() {
|
||||
sc.addr = -1
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) SetAddr(addr uint16) {
|
||||
sc.addr = int32(addr)
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) GetAddr() (uint16, bool) {
|
||||
if sc.addr == -1 {
|
||||
return 0, false
|
||||
}
|
||||
return uint16(sc.addr), true
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) AddrKnown() bool {
|
||||
return sc.addr != -1
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) Set(name string, value uint16) {
|
||||
sc.fix()
|
||||
s, found := sc.symbols[name]
|
||||
if found && s.v != value {
|
||||
s.changed = true
|
||||
}
|
||||
s.v = value
|
||||
sc.symbols[name] = s
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) RemoveChanged() {
|
||||
sc.fix()
|
||||
for n, s := range sc.symbols {
|
||||
if s.changed {
|
||||
delete(sc.symbols, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) GetLastLabel() string {
|
||||
return sc.lastLabel
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) SetLastLabel(l string) {
|
||||
sc.lastLabel = l
|
||||
}
|
||||
|
||||
func (sc *SimpleContext) Clear() {
|
||||
sc.symbols = make(map[string]symbolValue)
|
||||
}
|
13
asm/doc.go
Normal file
13
asm/doc.go
Normal file
@ -0,0 +1,13 @@
|
||||
/*
|
||||
Package asm provides routines for assembling 6502 code. It currently
|
||||
emulates the S-C Macro Assembler. The goal is to support (at least)
|
||||
as65 and Merlin assembly files too.
|
||||
|
||||
Once those three (two ancient, one modern) are complete, adding
|
||||
additional flavors should be straightforward.
|
||||
|
||||
TODO(zellyn): make errors return line and character position.
|
||||
TODO(zellyn): scma requires .EQ and .BS to have known values. Is this universal?
|
||||
|
||||
*/
|
||||
package asm
|
210
asm/expr/expression.go
Normal file
210
asm/expr/expression.go
Normal file
@ -0,0 +1,210 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/zellyn/go6502/asm/context"
|
||||
)
|
||||
|
||||
type UnknownLabelError struct {
|
||||
Label string
|
||||
}
|
||||
|
||||
func (e UnknownLabelError) Error() string {
|
||||
return fmt.Sprintf(`unknown label: "%s"`, e.Label)
|
||||
}
|
||||
|
||||
type Operator int
|
||||
|
||||
const (
|
||||
OpUnknown Operator = iota
|
||||
OpLeaf // No op - this is a leaf node
|
||||
OpPlus // add
|
||||
OpMinus // subtract / negate (with Right==nil)
|
||||
OpMul // multiply
|
||||
OpDiv // divide
|
||||
OpLsb // Low byte
|
||||
OpMsb // High byte
|
||||
OpLt // Less than
|
||||
OpGt // Greater than
|
||||
OpEq // Equal to
|
||||
OpByte // A single byte of storage
|
||||
)
|
||||
|
||||
var OpStrings = map[Operator]string{
|
||||
OpPlus: "+",
|
||||
OpMinus: "-",
|
||||
OpMul: "*",
|
||||
OpDiv: "/",
|
||||
OpLsb: "lsb",
|
||||
OpMsb: "msb",
|
||||
OpByte: "byte",
|
||||
OpLt: "<",
|
||||
OpGt: ">",
|
||||
OpEq: "=",
|
||||
}
|
||||
|
||||
type E struct {
|
||||
Left *E
|
||||
Right *E
|
||||
Op Operator
|
||||
Text string
|
||||
Val uint16
|
||||
}
|
||||
|
||||
func (e E) String() string {
|
||||
switch e.Op {
|
||||
case OpLeaf:
|
||||
if e.Text != "" {
|
||||
return e.Text
|
||||
}
|
||||
return fmt.Sprintf("$%04x", e.Val)
|
||||
|
||||
case OpPlus, OpMinus, OpMul, OpDiv, OpLsb, OpMsb, OpByte, OpLt, OpGt, OpEq:
|
||||
if e.Right != nil {
|
||||
return fmt.Sprintf("(%s %s %s)", OpStrings[e.Op], *e.Left, *e.Right)
|
||||
}
|
||||
return fmt.Sprintf("(%s %s)", OpStrings[e.Op], *e.Left)
|
||||
case OpUnknown:
|
||||
return "?"
|
||||
default:
|
||||
panic(fmt.Sprintf("Unprintable op type: %v", e.Op))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *E) Equal(o *E) bool {
|
||||
if e == nil || o == nil {
|
||||
return e == nil && o == nil
|
||||
}
|
||||
if e.Op != o.Op {
|
||||
return false
|
||||
}
|
||||
if e.Text != o.Text {
|
||||
return false
|
||||
}
|
||||
if e.Val != o.Val {
|
||||
return false
|
||||
}
|
||||
return e.Left.Equal(o.Left) && e.Right.Equal(o.Right)
|
||||
}
|
||||
|
||||
// Width returns the width in bytes of an expression. It'll be two for anything except
|
||||
// expressions that start with Lsb or Msb operators.
|
||||
func (e *E) Width() uint16 {
|
||||
switch e.Op {
|
||||
case OpLsb, OpMsb, OpByte:
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
func (e *E) Eval(ctx context.Context) (uint16, error) {
|
||||
if e == nil {
|
||||
return 0, errors.New("cannot Eval() nil expression")
|
||||
}
|
||||
switch e.Op {
|
||||
case OpLeaf:
|
||||
if e.Text == "" {
|
||||
return e.Val, nil
|
||||
}
|
||||
if val, ok := ctx.Get(e.Text); ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, UnknownLabelError{Label: e.Text}
|
||||
case OpMinus:
|
||||
l, err := e.Left.Eval(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if e.Right == nil {
|
||||
return -l, nil
|
||||
}
|
||||
r, err := e.Right.Eval(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return l - r, nil
|
||||
case OpMsb, OpLsb:
|
||||
l, err := e.Left.Eval(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if e.Op == OpMsb {
|
||||
return l >> 8, nil
|
||||
}
|
||||
return l & 0xff, nil
|
||||
case OpByte:
|
||||
return e.Val, nil
|
||||
case OpPlus, OpMul, OpDiv, OpLt, OpGt, OpEq:
|
||||
l, err := e.Left.Eval(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r, err := e.Right.Eval(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch e.Op {
|
||||
case OpPlus:
|
||||
return l + r, nil
|
||||
case OpMul:
|
||||
return l * r, nil
|
||||
case OpLt:
|
||||
if l < r {
|
||||
return 1, nil
|
||||
}
|
||||
return 0, nil
|
||||
case OpGt:
|
||||
if l > r {
|
||||
return 1, nil
|
||||
}
|
||||
return 0, nil
|
||||
case OpEq:
|
||||
if l == r {
|
||||
return 1, nil
|
||||
}
|
||||
return 0, nil
|
||||
case OpDiv:
|
||||
if r == 0 {
|
||||
return ctx.Zero()
|
||||
}
|
||||
return l / r, nil
|
||||
}
|
||||
panic(fmt.Sprintf("bad code - missing switch case: %d", e.Op))
|
||||
}
|
||||
panic(fmt.Sprintf("unknown operator type: %d", e.Op))
|
||||
}
|
||||
|
||||
// CheckedEval calls Eval, but also turns UnknownLabelErrors into labelMissing booleans.
|
||||
func (e *E) CheckedEval(ctx context.Context) (val uint16, labelMissing bool, err error) {
|
||||
val, err = e.Eval(ctx)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
return val, false, nil
|
||||
case UnknownLabelError:
|
||||
return val, true, err
|
||||
}
|
||||
return val, false, err
|
||||
}
|
||||
|
||||
// FixLabels attempts to turn .1 into LAST_LABEL.1
|
||||
func (e *E) FixLabels(last string) error {
|
||||
if e.Text != "" && e.Text[0] == '.' {
|
||||
if last == "" {
|
||||
return fmt.Errorf("Reference to sub-label '%s' before full label.", e.Text)
|
||||
}
|
||||
e.Text = last + e.Text
|
||||
}
|
||||
|
||||
if e.Left != nil {
|
||||
if err := e.Left.FixLabels(last); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if e.Right != nil {
|
||||
return e.Right.FixLabels(last)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
50
asm/expr/expression_test.go
Normal file
50
asm/expr/expression_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpressionString(t *testing.T) {
|
||||
tests := []struct {
|
||||
expr E
|
||||
want string
|
||||
}{
|
||||
{
|
||||
E{},
|
||||
"?",
|
||||
},
|
||||
{
|
||||
E{Op: OpLeaf, Text: "*"},
|
||||
"*",
|
||||
},
|
||||
{
|
||||
E{
|
||||
Op: OpPlus,
|
||||
Left: &E{Op: OpLeaf, Text: "Label"},
|
||||
Right: &E{Op: OpLeaf, Val: 42},
|
||||
},
|
||||
"(+ Label $002a)",
|
||||
},
|
||||
{
|
||||
E{
|
||||
Op: OpMinus,
|
||||
Left: &E{Op: OpLeaf, Text: "Label"},
|
||||
Right: &E{Op: OpLeaf, Val: 42},
|
||||
},
|
||||
"(- Label $002a)",
|
||||
},
|
||||
{
|
||||
E{
|
||||
Op: OpMinus,
|
||||
Left: &E{Op: OpLeaf, Val: 42},
|
||||
},
|
||||
"(- $002a)",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
got := tt.expr.String()
|
||||
if got != tt.want {
|
||||
t.Errorf(`%d: want String(expr)="%s"; got "%s"`, i, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
33
asm/flavors/as65/as65.go
Normal file
33
asm/flavors/as65/as65.go
Normal file
@ -0,0 +1,33 @@
|
||||
package as65
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/zellyn/go6502/asm/context"
|
||||
"github.com/zellyn/go6502/asm/inst"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
)
|
||||
|
||||
// AS65 implements the AS65-compatible assembler flavor.
|
||||
// See http://www.kingswood-consulting.co.uk/assemblers/
|
||||
|
||||
type AS65 struct {
|
||||
context.SimpleContext
|
||||
}
|
||||
|
||||
func New() *AS65 {
|
||||
return &AS65{}
|
||||
}
|
||||
|
||||
// Parse an entire instruction, or return an appropriate error.
|
||||
func (a *AS65) ParseInstr(line lines.Line) (inst.I, error) {
|
||||
return inst.I{}, nil
|
||||
}
|
||||
|
||||
func (a *AS65) Zero() (uint16, error) {
|
||||
return 0, errors.New("Division by zero.")
|
||||
}
|
||||
|
||||
func (a *AS65) DefaultOrigin() (uint16, error) {
|
||||
return 0, nil
|
||||
}
|
199
asm/flavors/assemble_test.go
Normal file
199
asm/flavors/assemble_test.go
Normal file
@ -0,0 +1,199 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/zellyn/go6502/asm"
|
||||
"github.com/zellyn/go6502/asm/flavors/scma"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
)
|
||||
|
||||
func TestMultiline(t *testing.T) {
|
||||
o := lines.NewTestOpener()
|
||||
|
||||
ss := asm.NewAssembler(scma.New(), o)
|
||||
// aa := asm.NewAssembler(as65.New(), o)
|
||||
// mm := asm.NewAssembler(merlin.New(), o)
|
||||
|
||||
tests := []struct {
|
||||
a *asm.Assembler // assembler
|
||||
name string // name
|
||||
i []string // main file: lines
|
||||
ii map[string][]string // other files: lines
|
||||
b string // bytes, expected
|
||||
active bool
|
||||
}{
|
||||
// We cannot determine L2, so we go wide.
|
||||
{ss, "Unknown label: wide", []string{
|
||||
"L1 LDA L2-L1",
|
||||
"L2 NOP",
|
||||
}, nil, "ad0300ea", false},
|
||||
|
||||
// Sub-labels
|
||||
{ss, "Sublabels", []string{
|
||||
"L1 BEQ .1",
|
||||
".1 NOP",
|
||||
"L2 BEQ .2",
|
||||
".1 NOP",
|
||||
".2 NOP",
|
||||
}, nil, "f000eaf001eaea", false},
|
||||
|
||||
// Includes: one level deep
|
||||
{ss, "Include A", []string{
|
||||
" BEQ OVER",
|
||||
" .IN SUBFILE1",
|
||||
"OVER NOP",
|
||||
}, map[string][]string{
|
||||
"SUBFILE1": {
|
||||
" LDA #$2a",
|
||||
},
|
||||
}, "f002a92aea", false},
|
||||
|
||||
// Ifdefs: simple at first
|
||||
{ss, "Ifdef A", []string{
|
||||
"L1 .EQ $17",
|
||||
" .DO L1>$16",
|
||||
" LDA #$01",
|
||||
" .ELSE",
|
||||
" LDA #$02",
|
||||
" .FIN",
|
||||
" NOP",
|
||||
}, nil, "a901ea", true},
|
||||
|
||||
// Ifdefs: else part
|
||||
{ss, "Ifdef B", []string{
|
||||
"L1 .EQ $16",
|
||||
" .DO L1>$16",
|
||||
" LDA #$01",
|
||||
" .ELSE",
|
||||
" LDA #$02",
|
||||
" .FIN",
|
||||
" NOP",
|
||||
}, nil, "a902ea", true},
|
||||
|
||||
// Ifdefs: multiple else, true
|
||||
{ss, "Ifdef C", []string{
|
||||
"L1 .EQ $17",
|
||||
" .DO L1>$16",
|
||||
" LDA #$01",
|
||||
" .ELSE",
|
||||
" LDA #$02",
|
||||
" .ELSE",
|
||||
" LDA #$03",
|
||||
" .ELSE",
|
||||
" LDA #$04",
|
||||
" .FIN",
|
||||
" NOP",
|
||||
}, nil, "a901a903ea", true},
|
||||
|
||||
// Ifdefs: multiple else, false
|
||||
{ss, "Ifdef D", []string{
|
||||
"L1 .EQ $16",
|
||||
" .DO L1>$16",
|
||||
" LDA #$01",
|
||||
" .ELSE",
|
||||
" LDA #$02",
|
||||
" .ELSE",
|
||||
" LDA #$03",
|
||||
" .ELSE",
|
||||
" LDA #$04",
|
||||
" .FIN",
|
||||
" NOP",
|
||||
}, nil, "a902a904ea", true},
|
||||
|
||||
// Ifdef based on org, true
|
||||
{ss, "Ifdef/org A", []string{
|
||||
" .OR $1000",
|
||||
" LDA #$01",
|
||||
" .DO *=$1002",
|
||||
" LDA #$02",
|
||||
" .ELSE",
|
||||
" LDA #$03",
|
||||
" .FIN",
|
||||
" NOP",
|
||||
}, nil, "a901a902ea", true},
|
||||
|
||||
// Ifdef based on org, false
|
||||
{ss, "Ifdef/org B", []string{
|
||||
" .OR $1000",
|
||||
" LDA #$01",
|
||||
" .DO *=$1003",
|
||||
" LDA #$02",
|
||||
" .ELSE",
|
||||
" LDA #$03",
|
||||
" .FIN",
|
||||
" NOP",
|
||||
}, nil, "a901a903ea", true},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
if !tt.active {
|
||||
continue
|
||||
}
|
||||
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"); err != nil {
|
||||
t.Errorf(`%d("%s"): tt.a.Load("TESTFILE") failed: %s`, i, tt.name, err)
|
||||
continue
|
||||
}
|
||||
if _, err := tt.a.Pass(true, false); err != nil {
|
||||
t.Errorf(`%d("%s"): tt.a.Pass(true, false) failed: %s`, i, tt.name, err)
|
||||
continue
|
||||
}
|
||||
isFinal, err := tt.a.Pass(true, true)
|
||||
if err != nil {
|
||||
t.Errorf(`%d("%s"): tt.a.Pass(true, true) failed: %s`, i, tt.name, err)
|
||||
continue
|
||||
}
|
||||
if !isFinal {
|
||||
t.Errorf(`%d("%s"): tt.a.Pass(true, true) couldn't finalize`, i, tt.name)
|
||||
continue
|
||||
}
|
||||
bb, err := tt.a.RawBytes()
|
||||
if err != nil {
|
||||
t.Errorf(`%d("%s"): tt.a.RawBytes() failed: %s`, i, tt.name, err)
|
||||
continue
|
||||
}
|
||||
hx := hex.EncodeToString(bb)
|
||||
if hx != tt.b {
|
||||
t.Errorf(`%d("%s"): tt.a.RawBytes()=[%s]; want [%s]`, i, tt.name, hx, tt.b)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplesoftBasic(t *testing.T) {
|
||||
if err := os.Chdir("../../../goapple2/source/applesoft"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var o lines.OsOpener
|
||||
|
||||
ss := asm.NewAssembler(scma.New(), o)
|
||||
ss.Reset()
|
||||
|
||||
if err := ss.Load("S.ACF"); err != nil {
|
||||
t.Fatalf(`ss.Load("S.ACF") failed: %s`, err)
|
||||
}
|
||||
if _, err := ss.Pass(true, false); err != nil {
|
||||
t.Fatalf(`ss.Pass(true, false) failed: %s`, err)
|
||||
}
|
||||
isFinal, err := ss.Pass(true, true)
|
||||
if err != nil {
|
||||
t.Fatalf(`ss.Pass(true, true) failed: %s`, err)
|
||||
}
|
||||
if !isFinal {
|
||||
t.Fatalf(`ss.Pass(true, true) couldn't finalize`)
|
||||
}
|
||||
bb, err := ss.RawBytes()
|
||||
if err != nil {
|
||||
t.Fatalf(`ss.RawBytes() failed: %s`, err)
|
||||
}
|
||||
_ = bb
|
||||
}
|
142
asm/flavors/common/common.go
Normal file
142
asm/flavors/common/common.go
Normal file
@ -0,0 +1,142 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zellyn/go6502/asm/context"
|
||||
"github.com/zellyn/go6502/asm/inst"
|
||||
"github.com/zellyn/go6502/opcodes"
|
||||
)
|
||||
|
||||
// DecodeOp contains the common code that decodes an Opcode, once we
|
||||
// have fully parsed it.
|
||||
func DecodeOp(c context.Context, in inst.I, summary opcodes.OpSummary, indirect bool, xy rune) (inst.I, error) {
|
||||
i := inst.I{}
|
||||
ex := in.Exprs[0]
|
||||
|
||||
// At this point we've parsed the Opcode. Let's see if it makes sense.
|
||||
if indirect {
|
||||
switch xy {
|
||||
case 'x':
|
||||
op, ok := summary.OpForMode(opcodes.MODE_INDIRECT_X)
|
||||
if !ok {
|
||||
return i, fmt.Errorf("%s doesn't support indexed indirect (addr,X) mode", in.Command)
|
||||
}
|
||||
in.Op = op.Byte
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 2
|
||||
in.MaxWidth = 2
|
||||
in.Mode = opcodes.MODE_INDIRECT_X
|
||||
return in, nil
|
||||
case 'y':
|
||||
op, ok := summary.OpForMode(opcodes.MODE_INDIRECT_Y)
|
||||
if !ok {
|
||||
return i, fmt.Errorf("%s doesn't support indirect indexed (addr),Y mode", in.Command)
|
||||
}
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 2
|
||||
in.MaxWidth = 2
|
||||
in.Mode = opcodes.MODE_INDIRECT_Y
|
||||
in.Op = op.Byte
|
||||
return in, nil
|
||||
default:
|
||||
op, ok := summary.OpForMode(opcodes.MODE_INDIRECT)
|
||||
if !ok {
|
||||
return i, fmt.Errorf("%s doesn't support indirect (addr) mode", in.Command)
|
||||
}
|
||||
in.Op = op.Byte
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 3
|
||||
in.MaxWidth = 3
|
||||
in.Mode = opcodes.MODE_INDIRECT
|
||||
return in, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Branch
|
||||
if summary.Modes == opcodes.MODE_RELATIVE {
|
||||
op, ok := summary.OpForMode(opcodes.MODE_RELATIVE)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("opcode error: %s has no MODE_RELATIVE opcode", in.Command))
|
||||
}
|
||||
in.Op = op.Byte
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 2
|
||||
in.MaxWidth = 2
|
||||
in.Mode = opcodes.MODE_RELATIVE
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// No ,X or ,Y, and width is forced to 1-byte: immediate mode.
|
||||
if xy == '-' && ex.Width() == 1 && summary.AnyModes(opcodes.MODE_IMMEDIATE) {
|
||||
op, ok := summary.OpForMode(opcodes.MODE_IMMEDIATE)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("opcode error: %s has no MODE_IMMEDIATE opcode", in.Command))
|
||||
}
|
||||
in.Op = op.Byte
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 2
|
||||
in.MaxWidth = 2
|
||||
in.Mode = opcodes.MODE_IMMEDIATE
|
||||
return in, nil
|
||||
}
|
||||
|
||||
var zp, wide opcodes.AddressingMode
|
||||
var zpS, wideS string
|
||||
switch xy {
|
||||
case 'x':
|
||||
zp, wide = opcodes.MODE_ZP_X, opcodes.MODE_ABS_X
|
||||
zpS, wideS = "ZeroPage,X", "Absolute,X"
|
||||
case 'y':
|
||||
zp, wide = opcodes.MODE_ZP_Y, opcodes.MODE_ABS_Y
|
||||
zpS, wideS = "ZeroPage,Y", "Absolute,Y"
|
||||
default:
|
||||
zp, wide = opcodes.MODE_ZP, opcodes.MODE_ABSOLUTE
|
||||
zpS, wideS = "ZeroPage", "Absolute"
|
||||
}
|
||||
|
||||
opWide, wideOk := summary.OpForMode(wide)
|
||||
opZp, zpOk := summary.OpForMode(zp)
|
||||
|
||||
if !summary.AnyModes(zp | wide) {
|
||||
return i, fmt.Errorf("%s opcode doesn't support %s or %s modes.", zpS, wideS)
|
||||
}
|
||||
|
||||
if !summary.AnyModes(zp) {
|
||||
if !wideOk {
|
||||
panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, wideS))
|
||||
}
|
||||
in.Op = opWide.Byte
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 3
|
||||
in.MaxWidth = 3
|
||||
in.Mode = wide
|
||||
return in, nil
|
||||
}
|
||||
if !summary.AnyModes(wide) {
|
||||
if !zpOk {
|
||||
panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, zpS))
|
||||
}
|
||||
in.Op = opZp.Byte
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 2
|
||||
in.MaxWidth = 2
|
||||
in.Mode = zp
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// Okay, we don't know whether it's wide or narrow: store enough info for either.
|
||||
if !zpOk {
|
||||
panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, zpS))
|
||||
}
|
||||
if !wideOk {
|
||||
panic(fmt.Sprintf("opcode error: %s has no %s opcode", in.Command, wideS))
|
||||
}
|
||||
in.Op = opWide.Byte
|
||||
in.ZeroOp = opZp.Byte
|
||||
in.Mode = wide
|
||||
in.ZeroMode = zp
|
||||
in.MinWidth = 2
|
||||
in.MaxWidth = 3
|
||||
return in, nil
|
||||
}
|
1
asm/flavors/flavors.go
Normal file
1
asm/flavors/flavors.go
Normal file
@ -0,0 +1 @@
|
||||
package flavors
|
34
asm/flavors/merlin/merlin.go
Normal file
34
asm/flavors/merlin/merlin.go
Normal file
@ -0,0 +1,34 @@
|
||||
package merlin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/zellyn/go6502/asm/context"
|
||||
"github.com/zellyn/go6502/asm/inst"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
)
|
||||
|
||||
// Merlin implements the Merlin-compatible assembler flavor.
|
||||
// See http://en.wikipedia.org/wiki/Merlin_(assembler) and
|
||||
// http://www.apple-iigs.info/doc/fichiers/merlin816.pdf
|
||||
|
||||
type Merlin struct {
|
||||
context.SimpleContext
|
||||
}
|
||||
|
||||
func New() *Merlin {
|
||||
return &Merlin{}
|
||||
}
|
||||
|
||||
// Parse an entire instruction, or return an appropriate error.
|
||||
func (a *Merlin) ParseInstr(line lines.Line) (inst.I, error) {
|
||||
return inst.I{}, nil
|
||||
}
|
||||
|
||||
func (a *Merlin) Zero() (uint16, error) {
|
||||
return 0, errors.New("Division by zero.")
|
||||
}
|
||||
|
||||
func (a *Merlin) DefaultOrigin() (uint16, error) {
|
||||
return 0x8000, nil
|
||||
}
|
518
asm/flavors/scma/scma.go
Normal file
518
asm/flavors/scma/scma.go
Normal file
@ -0,0 +1,518 @@
|
||||
package scma
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/zellyn/go6502/asm/context"
|
||||
"github.com/zellyn/go6502/asm/expr"
|
||||
"github.com/zellyn/go6502/asm/flavors/common"
|
||||
"github.com/zellyn/go6502/asm/inst"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
"github.com/zellyn/go6502/opcodes"
|
||||
)
|
||||
|
||||
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
const digits = "0123456789"
|
||||
const hexdigits = digits + "abcdefABCDEF"
|
||||
const labelChars = letters + digits + ".:"
|
||||
const whitespace = " \t"
|
||||
const cmdChars = letters + "."
|
||||
const fileChars = letters + digits + "."
|
||||
const operatorChars = "+-*/<>="
|
||||
|
||||
type directiveInfo struct {
|
||||
Type inst.Type
|
||||
Func func(inst.I, *lines.Parse) (inst.I, error)
|
||||
}
|
||||
|
||||
// SCMA implements the S-C Macro Assembler-compatible assembler flavor.
|
||||
// See http://www.txbobsc.com/scsc/ and http://stjarnhimlen.se/apple2/
|
||||
type SCMA struct {
|
||||
directives map[string]directiveInfo
|
||||
operators map[string]expr.Operator
|
||||
context.SimpleContext
|
||||
}
|
||||
|
||||
func New() *SCMA {
|
||||
s := &SCMA{}
|
||||
s.directives = map[string]directiveInfo{
|
||||
".IN": {inst.TypeInclude, s.parseInclude},
|
||||
".OR": {inst.TypeOrg, s.parseAddress},
|
||||
".TA": {inst.TypeTarget, s.parseNotImplemented},
|
||||
".TF": {inst.TypeNone, nil},
|
||||
".EN": {inst.TypeEnd, s.parseNoArgDir},
|
||||
".EQ": {inst.TypeEqu, s.parseEquate},
|
||||
".DA": {inst.TypeData, s.parseData},
|
||||
".HS": {inst.TypeData, s.parseHexString},
|
||||
".AS": {inst.TypeData, s.parseAscii},
|
||||
".AT": {inst.TypeData, s.parseAscii},
|
||||
".BS": {inst.TypeBlock, s.parseBlockStorage},
|
||||
".TI": {inst.TypeNone, nil},
|
||||
".LIST": {inst.TypeNone, nil},
|
||||
".PG": {inst.TypeNone, nil},
|
||||
".DO": {inst.TypeIfdef, s.parseDo},
|
||||
".ELSE": {inst.TypeIfdefElse, s.parseNoArgDir},
|
||||
".FIN": {inst.TypeIfdefEnd, s.parseNoArgDir},
|
||||
".MA": {inst.TypeMacroStart, s.parseNoArgDir},
|
||||
".EM": {inst.TypeMacroEnd, s.parseNoArgDir},
|
||||
".US": {inst.TypeNone, s.parseNotImplemented},
|
||||
}
|
||||
s.operators = map[string]expr.Operator{
|
||||
"*": expr.OpMul,
|
||||
"/": expr.OpDiv,
|
||||
"+": expr.OpPlus,
|
||||
"-": expr.OpMinus,
|
||||
"<": expr.OpLt,
|
||||
">": expr.OpGt,
|
||||
"=": expr.OpEq,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Parse an entire instruction, or return an appropriate error.
|
||||
func (a *SCMA) ParseInstr(line lines.Line) (inst.I, error) {
|
||||
lp := line.Parse
|
||||
in := inst.I{Line: &line}
|
||||
|
||||
// Lines that start with a digit are considered to have a declared line number.
|
||||
if lp.AcceptRun(digits) {
|
||||
s := lp.Emit()
|
||||
if len(s) != 4 {
|
||||
return inst.I{}, fmt.Errorf("Line number must be exactly 4 digits: %s", s)
|
||||
}
|
||||
if !lp.Consume(" ") && lp.Peek() != lines.Eol {
|
||||
return inst.I{}, fmt.Errorf("Line number (%s) followed by non-space", s)
|
||||
}
|
||||
i, err := strconv.ParseUint(s, 10, 16)
|
||||
if err != nil {
|
||||
return inst.I{}, fmt.Errorf("Invalid line number: %s: %s", s, err)
|
||||
}
|
||||
in.DeclaredLine = uint16(i)
|
||||
}
|
||||
|
||||
// Empty line or comment
|
||||
trimmed := strings.TrimSpace(lp.Rest())
|
||||
if trimmed == "" || trimmed[0] == '*' {
|
||||
in.Type = inst.TypeNone
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// See if we have a label at the start
|
||||
if lp.AcceptRun(labelChars) {
|
||||
in.Label = lp.Emit()
|
||||
}
|
||||
|
||||
// Ignore whitespace at the start or after the label.
|
||||
lp.IgnoreRun(whitespace)
|
||||
|
||||
if lp.Peek() == lines.Eol {
|
||||
in.Type = inst.TypeNone
|
||||
return in, nil
|
||||
}
|
||||
return a.parseCmd(in, lp)
|
||||
}
|
||||
|
||||
func (a *SCMA) Zero() (uint16, error) {
|
||||
return uint16(0xffff), nil
|
||||
}
|
||||
|
||||
func (a *SCMA) DefaultOrigin() (uint16, error) {
|
||||
return 0x0800, nil
|
||||
}
|
||||
|
||||
// parseCmd parses the "command" part of an instruction: we expect to be
|
||||
// looking at a non-whitespace character.
|
||||
func (a *SCMA) parseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
if lp.Consume(">") {
|
||||
return a.parseMacroCall(in, lp)
|
||||
}
|
||||
if !lp.AcceptRun(cmdChars) {
|
||||
c := lp.Next()
|
||||
return inst.I{}, fmt.Errorf("Expecting instruction, found '%c' (%d)", c, c)
|
||||
}
|
||||
in.Command = lp.Emit()
|
||||
if dir, ok := a.directives[in.Command]; ok {
|
||||
in.Type = dir.Type
|
||||
if dir.Func == nil {
|
||||
return in, nil
|
||||
}
|
||||
return dir.Func(in, lp)
|
||||
}
|
||||
|
||||
if summary, ok := opcodes.ByName[in.Command]; ok {
|
||||
in.Type = inst.TypeOp
|
||||
return a.parseOpArgs(in, lp, summary)
|
||||
}
|
||||
return inst.I{}, fmt.Errorf(`Not implemented: "%s": `, in.Command, in.Line)
|
||||
}
|
||||
|
||||
// parseMacroCall parses a macro call. We expect to be looking at a the
|
||||
// first character of the macro name.
|
||||
func (a *SCMA) parseMacroCall(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
in.Type = inst.TypeMacroCall
|
||||
if !lp.AcceptRun(cmdChars) {
|
||||
c := lp.Next()
|
||||
return inst.I{}, fmt.Errorf("Expecting macro name, found '%c' (%d)", c, c)
|
||||
}
|
||||
in.Command = lp.Emit()
|
||||
|
||||
lp.Consume(whitespace)
|
||||
|
||||
for {
|
||||
s, err := a.parseMacroArg(lp)
|
||||
if err != nil {
|
||||
return inst.I{}, err
|
||||
}
|
||||
in.MacroArgs = append(in.MacroArgs, s)
|
||||
if !lp.Consume(",") {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// parseMacroArg parses a single macro argument. We expect to be looking at the first
|
||||
// character of a macro argument.
|
||||
func (a *SCMA) parseMacroArg(lp *lines.Parse) (string, error) {
|
||||
if lp.Peek() == '"' {
|
||||
return a.parseQuoted(lp)
|
||||
}
|
||||
lp.AcceptUntil(whitespace + ",")
|
||||
return lp.Emit(), nil
|
||||
}
|
||||
|
||||
// parseQuoted parses a single quoted string macro argument. We expect
|
||||
// to be looking at the first quote.
|
||||
func (a *SCMA) parseQuoted(lp *lines.Parse) (string, error) {
|
||||
if !lp.Consume(`"`) {
|
||||
panic(fmt.Sprintf("parseQuoted called not looking at a quote"))
|
||||
}
|
||||
for {
|
||||
lp.AcceptUntil(`"`)
|
||||
// We're done, unless there's an escaped quote
|
||||
if !lp.AcceptString(`""`) {
|
||||
break
|
||||
}
|
||||
}
|
||||
s := lp.Emit()
|
||||
if !lp.Consume(`"`) {
|
||||
c := lp.Peek()
|
||||
return "", fmt.Errorf("Expected closing quote; got %s", c)
|
||||
}
|
||||
|
||||
c := lp.Peek()
|
||||
if c != ',' && c != ' ' && c != lines.Eol && c != '\t' {
|
||||
return "", fmt.Errorf("Unexpected char after quoted string: '%s'", c)
|
||||
}
|
||||
|
||||
return strings.Replace(s, `""`, `"`, -1), nil
|
||||
}
|
||||
|
||||
// parseOpArgs parses the arguments to an assembly op. We expect to be looking at the first
|
||||
// non-op character (probably whitespace)
|
||||
func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary) (inst.I, error) {
|
||||
i := inst.I{}
|
||||
|
||||
// MODE_IMPLIED: we don't really care what comes next: it's a comment.
|
||||
if summary.Modes == opcodes.MODE_IMPLIED {
|
||||
op := summary.Ops[0]
|
||||
in.Data = []byte{op.Byte}
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 1
|
||||
in.MaxWidth = 1
|
||||
in.Final = true
|
||||
in.Mode = opcodes.MODE_IMPLIED
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// Nothing else on the line? Must be MODE_A
|
||||
lp.Consume(whitespace)
|
||||
if lp.Consume(whitespace) || lp.Peek() == lines.Eol {
|
||||
if !summary.AnyModes(opcodes.MODE_A) {
|
||||
return i, fmt.Errorf("%s with no arguments", in.Command)
|
||||
}
|
||||
op, ok := summary.OpForMode(opcodes.MODE_A)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s doesn't support accumulator mode", in.Command))
|
||||
}
|
||||
in.Data = []byte{op.Byte}
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 1
|
||||
in.MaxWidth = 1
|
||||
in.Final = true
|
||||
in.Mode = opcodes.MODE_A
|
||||
return in, nil
|
||||
}
|
||||
|
||||
indirect := lp.Consume("(")
|
||||
if indirect && !summary.AnyModes(opcodes.MODE_INDIRECT_ANY) {
|
||||
return i, fmt.Errorf("%s doesn't support any indirect modes", in.Command)
|
||||
}
|
||||
xy := '-'
|
||||
expr, err := a.parseExpression(lp)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
in.Exprs = append(in.Exprs, expr)
|
||||
comma := lp.Consume(",")
|
||||
if comma {
|
||||
if lp.Consume("xX") {
|
||||
xy = 'x'
|
||||
} else if lp.Consume("yY") {
|
||||
if indirect {
|
||||
return i, fmt.Errorf(",Y unexpected inside parens")
|
||||
}
|
||||
xy = 'y'
|
||||
} else {
|
||||
return i, fmt.Errorf("X or Y expected after comma")
|
||||
}
|
||||
}
|
||||
comma2 := false
|
||||
if indirect {
|
||||
if !lp.Consume(")") {
|
||||
return i, fmt.Errorf("Expected closing paren")
|
||||
}
|
||||
comma2 = lp.Consume(",")
|
||||
if comma2 {
|
||||
if comma {
|
||||
return i, fmt.Errorf("Cannot have ,X or ,Y twice.")
|
||||
}
|
||||
if !lp.Consume("yY") {
|
||||
return i, fmt.Errorf("Only ,Y can follow parens.")
|
||||
}
|
||||
xy = 'y'
|
||||
}
|
||||
}
|
||||
|
||||
return common.DecodeOp(a, in, summary, indirect, xy)
|
||||
}
|
||||
|
||||
func (a *SCMA) parseAddress(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
expr, err := a.parseExpression(lp)
|
||||
if err != nil {
|
||||
return inst.I{}, err
|
||||
}
|
||||
in.Exprs = append(in.Exprs, expr)
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 0
|
||||
in.MaxWidth = 0
|
||||
in.Final = true
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseAscii(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
var invert, invertLast byte
|
||||
if lp.Consume("-") {
|
||||
invert = 0x80
|
||||
}
|
||||
if in.Command == ".AT" {
|
||||
invertLast = 0x80
|
||||
}
|
||||
delim := lp.Next()
|
||||
if delim == lines.Eol || strings.IndexRune(whitespace, delim) >= 0 {
|
||||
return inst.I{}, fmt.Errorf("%s expects delimeter, found '%s'", in.Command, delim)
|
||||
}
|
||||
lp.Ignore()
|
||||
lp.AcceptUntil(string(delim))
|
||||
delim2 := lp.Next()
|
||||
if delim != delim2 {
|
||||
return inst.I{}, fmt.Errorf("%s: expected closing delimeter '%s'; got '%s'", in.Command, delim, delim2)
|
||||
}
|
||||
lp.Backup()
|
||||
in.Data = []byte(lp.Emit())
|
||||
for i := range in.Data {
|
||||
in.Data[i] ^= invert
|
||||
if i == len(in.Data)-1 {
|
||||
in.Data[i] ^= invertLast
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
ex, err := a.parseExpression(lp)
|
||||
if err != nil {
|
||||
return inst.I{}, err
|
||||
}
|
||||
in.Exprs = append(in.Exprs, ex)
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseData(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
for {
|
||||
ex, err := a.parseExpression(lp)
|
||||
if err != nil {
|
||||
return inst.I{}, err
|
||||
}
|
||||
in.Exprs = append(in.Exprs, ex)
|
||||
if !lp.Consume(",") {
|
||||
break
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseDo(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
expr, err := a.parseExpression(lp)
|
||||
if err != nil {
|
||||
return inst.I{}, err
|
||||
}
|
||||
in.Exprs = append(in.Exprs, expr)
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 0
|
||||
in.MaxWidth = 0
|
||||
in.Final = true
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseEquate(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
expr, err := a.parseExpression(lp)
|
||||
if err != nil {
|
||||
return inst.I{}, err
|
||||
}
|
||||
in.Exprs = append(in.Exprs, expr)
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 0
|
||||
in.MaxWidth = 0
|
||||
in.Final = true
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseHexString(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
if !lp.AcceptRun(hexdigits) {
|
||||
return inst.I{}, fmt.Errorf("%s expects hex digits; got '%s'", in.Command, lp.Next())
|
||||
}
|
||||
hs := lp.Emit()
|
||||
if len(hs)%2 != 0 {
|
||||
return inst.I{}, fmt.Errorf("%s expects pairs of hex digits; got %d", in.Command, len(hs))
|
||||
}
|
||||
var err error
|
||||
if in.Data, err = hex.DecodeString(hs); err != nil {
|
||||
return inst.I{}, fmt.Errorf("%s: error decoding hex string: %s", in.Command, err)
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseInclude(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
if !lp.AcceptRun(fileChars) {
|
||||
return inst.I{}, fmt.Errorf("Expecting filename, found '%c'", lp.Next())
|
||||
}
|
||||
in.TextArg = lp.Emit()
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 0
|
||||
in.MaxWidth = 0
|
||||
in.Final = true
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseNoArgDir(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
in.WidthKnown = true
|
||||
in.MinWidth = 0
|
||||
in.MaxWidth = 0
|
||||
in.Final = true
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseNotImplemented(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
return inst.I{}, fmt.Errorf("Not implemented (yet?): %s", in.Command)
|
||||
}
|
||||
|
||||
func (a *SCMA) parseExpression(lp *lines.Parse) (*expr.E, error) {
|
||||
var outer *expr.E
|
||||
if lp.Accept("#/") {
|
||||
switch lp.Emit() {
|
||||
case "#":
|
||||
outer = &expr.E{Op: expr.OpLsb}
|
||||
case "/":
|
||||
outer = &expr.E{Op: expr.OpMsb}
|
||||
}
|
||||
}
|
||||
|
||||
tree, err := a.parseTerm(lp)
|
||||
if err != nil {
|
||||
return &expr.E{}, err
|
||||
}
|
||||
|
||||
for lp.Accept(operatorChars) {
|
||||
c := lp.Emit()
|
||||
right, err := a.parseTerm(lp)
|
||||
if err != nil {
|
||||
return &expr.E{}, err
|
||||
}
|
||||
tree = &expr.E{Op: a.operators[c], Left: tree, Right: right}
|
||||
}
|
||||
|
||||
if outer != nil {
|
||||
outer.Left = tree
|
||||
return outer, nil
|
||||
}
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) parseTerm(lp *lines.Parse) (*expr.E, error) {
|
||||
ex := &expr.E{}
|
||||
top := ex
|
||||
|
||||
// Unary minus: just wrap the current expression
|
||||
if lp.Consume("-") {
|
||||
top = &expr.E{Op: expr.OpMinus, Left: ex}
|
||||
}
|
||||
|
||||
// Current location
|
||||
if lp.Consume("*") {
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Text = "*"
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// Hex
|
||||
if lp.Consume("$") {
|
||||
if !lp.AcceptRun(hexdigits) {
|
||||
c := lp.Next()
|
||||
return &expr.E{}, fmt.Errorf("Expecting hex number, found '%c' (%d)", c, c)
|
||||
}
|
||||
s := lp.Emit()
|
||||
i, err := strconv.ParseUint(s, 16, 16)
|
||||
if err != nil {
|
||||
return &expr.E{}, fmt.Errorf("Invalid hex number: %s: %s", s, err)
|
||||
}
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Val = uint16(i)
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// Decimal
|
||||
if lp.AcceptRun(digits) {
|
||||
s := lp.Emit()
|
||||
i, err := strconv.ParseUint(s, 10, 16)
|
||||
if err != nil {
|
||||
return &expr.E{}, fmt.Errorf("Invalid number: %s: %s", s, err)
|
||||
}
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Val = uint16(i)
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// Label
|
||||
if !lp.AcceptRun(labelChars) {
|
||||
c := lp.Next()
|
||||
return &expr.E{}, fmt.Errorf("Expecting *, (hex) number, or label; found '%c' (%d)", c, c)
|
||||
}
|
||||
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Text = lp.Emit()
|
||||
return top, nil
|
||||
}
|
197
asm/flavors/simple_parse_test.go
Normal file
197
asm/flavors/simple_parse_test.go
Normal file
@ -0,0 +1,197 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/zellyn/go6502/asm"
|
||||
"github.com/zellyn/go6502/asm/flavors/as65"
|
||||
"github.com/zellyn/go6502/asm/flavors/merlin"
|
||||
"github.com/zellyn/go6502/asm/flavors/scma"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
)
|
||||
|
||||
func TestSimpleCommonFunctions(t *testing.T) {
|
||||
ss := scma.New()
|
||||
aa := as65.New()
|
||||
mm := merlin.New()
|
||||
|
||||
tests := []struct {
|
||||
a asm.Flavor // assembler flavor
|
||||
i string // input string
|
||||
p string // printed instruction, expected
|
||||
b string // bytes, expected
|
||||
}{
|
||||
{ss, "* Comment", "{-}", ""},
|
||||
{ss, "* Comment", "{-}", ""},
|
||||
{aa, "; Comment", "{-}", ""},
|
||||
{mm, "* Comment", "{-}", ""},
|
||||
{ss, "Label", "{- 'Label'}", ""},
|
||||
{aa, "Label", "{- 'Label'}", ""},
|
||||
{mm, "Label", "{- 'Label'}", ""},
|
||||
{ss, " .IN FILE.NAME", "{inc 'FILE.NAME'}", ""},
|
||||
{ss, " .IN S.DEFS", "{inc 'S.DEFS'}", ""},
|
||||
{aa, ` include "FILE.NAME"`, "{inc 'FILE.NAME'}", ""},
|
||||
{mm, " PUT !FILE.NAME", "{inc 'FILE.NAME'}", ""},
|
||||
{ss, " .TI 76,Title here", "{-}", ""},
|
||||
{aa, ` title "Title here"`, "{-}", ""},
|
||||
{mm, ` TTL "Title here"`, "{-}", ""},
|
||||
{ss, " .TF OUT.BIN", "{-}", ""},
|
||||
{mm, " DSK OUTFILE", "{-}", ""},
|
||||
{mm, " SAV OUTFILE", "{-}", ""},
|
||||
{ss, " .OR $D000", "{org $d000}", ""},
|
||||
{aa, " org $D000", "{org $d000}", ""},
|
||||
{mm, " ORG $D000", "{org $d000}", ""},
|
||||
// {ss, " .TA *-1234", "{target (- * $04d2)}", ""},
|
||||
{ss, " .DA $1234", "{data $1234}", "3412"},
|
||||
{aa, " dw $1234", "{data $1234}", "3412"},
|
||||
{mm, " DW $1234", "{data $1234}", "3412"},
|
||||
{ss, " .DA/$1234,#$1234,$1234", "{data (msb $1234),(lsb $1234),$1234}", "12343412"},
|
||||
{ss, " ROL", "{ROL/a}", "2a"},
|
||||
{aa, " rol a", "{ROL/a}", "2a"},
|
||||
{mm, " ROL", "{ROL/a}", "2a"},
|
||||
{ss, " ROL Comment after two spaces", "{ROL/a}", "2a"},
|
||||
{ss, " ROL $1234", "{ROL/abs $1234}", "2e3412"},
|
||||
{aa, " rol $1234", "{ROL/abs $1234}", "2e3412"},
|
||||
{mm, " ROL $1234", "{ROL/abs $1234}", "2e3412"},
|
||||
{ss, " ROL $12", "{ROL/zp $0012}", "2612"},
|
||||
{aa, " rol $12", "{ROL/zp $0012}", "2612"},
|
||||
{mm, " ROL $12", "{ROL/zp $0012}", "2612"},
|
||||
{ss, " LDA #$12", "{LDA/imm (lsb $0012)}", "a912"},
|
||||
{aa, " lda #$12", "{LDA/imm (lsb $0012)}", "a912"},
|
||||
{mm, " LDA #$12", "{LDA/imm (lsb $0012)}", "a912"},
|
||||
{ss, " JMP $1234", "{JMP/abs $1234}", "4c3412"},
|
||||
{aa, " jmp $1234", "{JMP/abs $1234}", "4c3412"},
|
||||
{mm, " JMP $1234", "{JMP/abs $1234}", "4c3412"},
|
||||
{ss, " JMP ($1234)", "{JMP/ind $1234}", "6c3412"},
|
||||
{aa, " jmp ($1234)", "{JMP/ind $1234}", "6c3412"},
|
||||
{mm, " JMP ($1234)", "{JMP/ind $1234}", "6c3412"},
|
||||
{ss, " BEQ $2345", "{BEQ/rel $2345}", "f0fe"},
|
||||
{aa, " beq $2345", "{BEQ/rel $2345}", "f0fe"},
|
||||
{mm, " BEQ $2345", "{BEQ/rel $2345}", "f0fe"},
|
||||
{ss, " BEQ $2347", "{BEQ/rel $2347}", "f000"},
|
||||
{aa, " beq $2347", "{BEQ/rel $2347}", "f000"},
|
||||
{mm, " BEQ $2347", "{BEQ/rel $2347}", "f000"},
|
||||
{ss, " BEQ $2343", "{BEQ/rel $2343}", "f0fc"},
|
||||
{aa, " beq $2343", "{BEQ/rel $2343}", "f0fc"},
|
||||
{mm, " BEQ $2343", "{BEQ/rel $2343}", "f0fc"},
|
||||
{ss, " LDA $1234", "{LDA/abs $1234}", "ad3412"},
|
||||
{aa, " lda $1234", "{LDA/abs $1234}", "ad3412"},
|
||||
{mm, " LDA $1234", "{LDA/abs $1234}", "ad3412"},
|
||||
{ss, " LDA $1234,X", "{LDA/absX $1234}", "bd3412"},
|
||||
{aa, " lda $1234,x", "{LDA/absX $1234}", "bd3412"},
|
||||
{mm, " LDA $1234,X", "{LDA/absX $1234}", "bd3412"},
|
||||
{ss, " STA $1234,Y", "{STA/absY $1234}", "993412"},
|
||||
{aa, " sta $1234,y", "{STA/absY $1234}", "993412"},
|
||||
{mm, " STA $1234,Y", "{STA/absY $1234}", "993412"},
|
||||
{ss, " LDA $12", "{LDA/zp $0012}", "a512"},
|
||||
{aa, " lda $12", "{LDA/zp $0012}", "a512"},
|
||||
{mm, " LDA $12", "{LDA/zp $0012}", "a512"},
|
||||
{ss, " LDA $12,X", "{LDA/zpX $0012}", "b512"},
|
||||
{aa, " lda $12,x", "{LDA/zpX $0012}", "b512"},
|
||||
{mm, " LDA $12,X", "{LDA/zpX $0012}", "b512"},
|
||||
{ss, " LDX $12,Y", "{LDX/zpY $0012}", "b612"},
|
||||
{aa, " ldx $12,y", "{LDX/zpY $0012}", "b612"},
|
||||
{mm, " LDX $12,Y", "{LDX/zpY $0012}", "b612"},
|
||||
{ss, " LDA ($12),Y", "{LDA/indY $0012}", "b112"},
|
||||
{aa, " lda ($12),y", "{LDA/indY $0012}", "b112"},
|
||||
{mm, " LDA ($12),Y", "{LDA/indY $0012}", "b112"},
|
||||
{ss, " LDA ($12,X)", "{LDA/indX $0012}", "a112"},
|
||||
{aa, " lda ($12,x)", "{LDA/indX $0012}", "a112"},
|
||||
{mm, " LDA ($12,X)", "{LDA/indX $0012}", "a112"},
|
||||
{ss, ` .AS "ABC"`, "{data}", "414243"},
|
||||
{ss, ` .AT "ABC"`, "{data}", "4142c3"},
|
||||
{ss, ` .AS /ABC/`, "{data}", "414243"},
|
||||
{ss, ` .AT /ABC/`, "{data}", "4142c3"},
|
||||
{ss, ` .AS -"ABC"`, "{data}", "c1c2c3"},
|
||||
{ss, ` .AT -"ABC"`, "{data}", "c1c243"},
|
||||
{ss, ` .AS -dABCd`, "{data}", "c1c2c3"},
|
||||
{ss, ` .AT -dABCd`, "{data}", "c1c243"},
|
||||
{ss, " .HS 0001ffAb", "{data}", "0001ffab"},
|
||||
{ss, "A.B .EQ *-C.D", "{= 'A.B' (- * C.D)}", ""},
|
||||
{ss, " .BS $8", "{block $0008}", ""},
|
||||
{ss, " .DO A<$3", "{if (< A $0003)}", ""},
|
||||
{ss, " .ELSE", "{else}", ""},
|
||||
{ss, " .FIN", "{endif}", ""},
|
||||
{ss, "Label .MA", "{macro 'Label'}", ""},
|
||||
{ss, " .EM", "{endm}", ""},
|
||||
{ss, " .EN", "{end}", ""},
|
||||
{ss, `>SAM AB,$12,"A B","A, B, "" C"`,
|
||||
`{call SAM {"AB", "$12", "A B", "A, B, \" C"}}`, ""},
|
||||
}
|
||||
|
||||
// TODO(zellyn): Add tests for finalization of four SCMA directives:
|
||||
// "Labels used in operand expressions after .OR, TA, .BS,
|
||||
// and .EQ directives must be defined prior to use (to prevent an
|
||||
// undefined or ambiguous location counter)."
|
||||
|
||||
for i, tt := range tests {
|
||||
// TODO(zellyn): Test AS65 and Merlin too.
|
||||
if tt.a != ss {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i))
|
||||
if err != nil {
|
||||
t.Errorf(`%d. %T.ParseInstr("%s") => error: %s`, i, tt.a, 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, true)
|
||||
if err != nil {
|
||||
t.Errorf(`%d. %s.Compute(tt.a, true, true) => error: %s`, i, inst, err)
|
||||
continue
|
||||
}
|
||||
if inst.String() != tt.p {
|
||||
t.Errorf(`%d. %T.ParseInstr("%s") = %s; want %s`, i, tt.a, tt.i, inst.String(), tt.p)
|
||||
continue
|
||||
}
|
||||
|
||||
if tt.b != "?" {
|
||||
hx := hex.EncodeToString(inst.Data)
|
||||
if hx != tt.b {
|
||||
t.Fatalf(`%d. %T.ParseInstr("%s").Data = [%s]; want [%s]`, i, tt.a, tt.i, hx, tt.b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleErrors(t *testing.T) {
|
||||
ss := scma.New()
|
||||
aa := as65.New()
|
||||
mm := merlin.New()
|
||||
|
||||
tests := []struct {
|
||||
a asm.Flavor // assembler flavor
|
||||
i string // input string
|
||||
}{
|
||||
|
||||
{ss, " LDA"}, // missing arg
|
||||
{aa, " lda"}, //
|
||||
{mm, " LDA"}, //
|
||||
{aa, " rol"}, // missing arg (for assemblers that need "A")
|
||||
{ss, " .DA $1234,"}, // data: trailing comma
|
||||
{ss, `>MACRO "ABC`}, // macro: unclosed quote on arg
|
||||
{ss, `>MACRO "ABC"$12`}, // macro: stuff after closing quote
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
// TODO(zellyn): Test AS65 and Merlin too.
|
||||
if tt.a != ss {
|
||||
continue
|
||||
}
|
||||
inst, err := tt.a.ParseInstr(lines.NewSimple(tt.i))
|
||||
if err == nil {
|
||||
t.Errorf(`%d. %T.ParseInstr("%s") want err; got %s`, i, tt.a, tt.i, inst)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
3
asm/flavors/todos.org
Normal file
3
asm/flavors/todos.org
Normal file
@ -0,0 +1,3 @@
|
||||
* TODO only figure out what the opcode *looks* like in the assembler: more DRY
|
||||
* TODO implement byte value check in simple instruction test
|
||||
* TODO add line/character reporting to parse errors
|
405
asm/inst/instruction.go
Normal file
405
asm/inst/instruction.go
Normal file
@ -0,0 +1,405 @@
|
||||
package inst
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zellyn/go6502/asm/context"
|
||||
"github.com/zellyn/go6502/asm/expr"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
"github.com/zellyn/go6502/opcodes"
|
||||
)
|
||||
|
||||
type Type int
|
||||
|
||||
const (
|
||||
TypeUnknown Type = iota
|
||||
|
||||
TypeNone // No type (eg. just a label, just a comment, empty, ignored directive)
|
||||
TypeMacroStart // Start of macro definition
|
||||
TypeMacroEnd // End of macro definition
|
||||
TypeMacroCall // Macro invocation
|
||||
TypeMacroExit // Macro early exit
|
||||
TypeIfdef // Ifdef start
|
||||
TypeIfdefElse // Ifdef else block
|
||||
TypeIfdefEnd // Ifdef end
|
||||
TypeInclude // Include a file
|
||||
TypeData // Data: hex, ascii, etc., etc.
|
||||
TypeBlock // Block storage
|
||||
TypeOrg // Where to store assembled code
|
||||
TypeTarget // Target address to use for jumps, labels, etc.
|
||||
TypeSegment // Segments: bss/code/data
|
||||
TypeEqu // Equate
|
||||
TypeOp // An actual asm opcode
|
||||
TypeEnd // End assembly
|
||||
)
|
||||
|
||||
type I struct {
|
||||
Type Type // Type of instruction
|
||||
Label string // Text of label part
|
||||
MacroArgs []string // Macro args
|
||||
Command string // Text of command part
|
||||
TextArg string // Text of argument part
|
||||
Exprs []*expr.E // Expression(s)
|
||||
Data []byte // Actual bytes
|
||||
WidthKnown bool // Do we know how many bytes this instruction takes yet?
|
||||
MinWidth uint16 // minimum width in bytes
|
||||
MaxWidth uint16 // maximum width in bytes
|
||||
Final bool // Do we know the actual bytes yet?
|
||||
Op byte // Opcode
|
||||
Mode opcodes.AddressingMode // Opcode mode
|
||||
ZeroMode opcodes.AddressingMode // Possible ZP-option mode
|
||||
ZeroOp byte // Possible ZP-option Opcode
|
||||
Value uint16 // For Equates, the value
|
||||
DeclaredLine uint16
|
||||
Line *lines.Line
|
||||
}
|
||||
|
||||
func (i I) TypeString() string {
|
||||
switch i.Type {
|
||||
case TypeNone:
|
||||
return "-"
|
||||
case TypeMacroStart:
|
||||
return "macro"
|
||||
case TypeMacroEnd:
|
||||
return "endm"
|
||||
case TypeMacroCall:
|
||||
return "call " + i.Command
|
||||
case TypeMacroExit:
|
||||
return "exitm"
|
||||
case TypeIfdef:
|
||||
return "if"
|
||||
case TypeIfdefElse:
|
||||
return "else"
|
||||
case TypeIfdefEnd:
|
||||
return "endif"
|
||||
case TypeInclude:
|
||||
return "inc"
|
||||
case TypeData:
|
||||
return "data"
|
||||
case TypeBlock:
|
||||
return "block"
|
||||
case TypeOrg:
|
||||
return "org"
|
||||
case TypeTarget:
|
||||
return "target"
|
||||
case TypeSegment:
|
||||
return "seg"
|
||||
case TypeEqu:
|
||||
return "="
|
||||
case TypeEnd:
|
||||
return "end"
|
||||
case TypeOp:
|
||||
modeStr := "?"
|
||||
switch i.Mode {
|
||||
case opcodes.MODE_IMPLIED:
|
||||
modeStr = "imp"
|
||||
case opcodes.MODE_ABSOLUTE:
|
||||
modeStr = "abs"
|
||||
case opcodes.MODE_INDIRECT:
|
||||
modeStr = "ind"
|
||||
case opcodes.MODE_RELATIVE:
|
||||
modeStr = "rel"
|
||||
case opcodes.MODE_IMMEDIATE:
|
||||
modeStr = "imm"
|
||||
case opcodes.MODE_ABS_X:
|
||||
modeStr = "absX"
|
||||
case opcodes.MODE_ABS_Y:
|
||||
modeStr = "absY"
|
||||
case opcodes.MODE_ZP:
|
||||
modeStr = "zp"
|
||||
case opcodes.MODE_ZP_X:
|
||||
modeStr = "zpX"
|
||||
case opcodes.MODE_ZP_Y:
|
||||
modeStr = "zpY"
|
||||
case opcodes.MODE_INDIRECT_Y:
|
||||
modeStr = "indY"
|
||||
case opcodes.MODE_INDIRECT_X:
|
||||
modeStr = "indX"
|
||||
case opcodes.MODE_A:
|
||||
modeStr = "a"
|
||||
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", i.Command, modeStr)
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
func (i I) String() string {
|
||||
switch i.Type {
|
||||
case TypeInclude:
|
||||
return fmt.Sprintf("{inc '%s'}", i.TextArg)
|
||||
}
|
||||
s := "{" + i.TypeString()
|
||||
if i.Label != "" {
|
||||
s += fmt.Sprintf(" '%s'", i.Label)
|
||||
}
|
||||
if len(i.MacroArgs) > 0 {
|
||||
ma := fmt.Sprintf("%#v", i.MacroArgs)[8:]
|
||||
s += " " + ma
|
||||
}
|
||||
if len(i.Exprs) > 0 {
|
||||
exprs := []string{}
|
||||
for _, expr := range i.Exprs {
|
||||
exprs = append(exprs, expr.String())
|
||||
}
|
||||
s += " " + strings.Join(exprs, ",")
|
||||
}
|
||||
return s + "}"
|
||||
}
|
||||
|
||||
// Compute attempts to finalize the instruction.
|
||||
func (i *I) Compute(c context.Context, setWidth bool, final bool) (bool, error) {
|
||||
if i.Type == TypeEqu || i.Type == TypeTarget || i.Type == TypeOrg {
|
||||
return i.computeMustKnow(c, setWidth, final)
|
||||
}
|
||||
if err := i.computeLabel(c, setWidth, final); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if i.Final {
|
||||
return true, nil
|
||||
}
|
||||
switch i.Type {
|
||||
case TypeOp:
|
||||
return i.computeOp(c, setWidth, final)
|
||||
case TypeData:
|
||||
return i.computeData(c, setWidth, final)
|
||||
case TypeBlock:
|
||||
return i.computeBlock(c, setWidth, final)
|
||||
}
|
||||
|
||||
// Everything else is zero-width
|
||||
i.WidthKnown = true
|
||||
i.MinWidth = 0
|
||||
i.MaxWidth = 0
|
||||
i.Final = true
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// FixLabels attempts to turn .1 into LAST_LABEL.1
|
||||
func (i *I) FixLabels(last string) error {
|
||||
if i.Label != "" && i.Label[0] == '.' {
|
||||
if last == "" {
|
||||
return fmt.Errorf("Reference to sub-label '%s' before full label.", i.Label)
|
||||
}
|
||||
i.Label = last + i.Label
|
||||
}
|
||||
|
||||
for _, e := range i.Exprs {
|
||||
if err := e.FixLabels(last); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// computeLabel attempts to compute equates and label values.
|
||||
func (i *I) computeLabel(c context.Context, setWidth bool, 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 fmt.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, setWidth bool, final bool) (bool, error) {
|
||||
if len(i.Data) > 0 {
|
||||
i.WidthKnown = true
|
||||
i.MinWidth = uint16(len(i.Data))
|
||||
i.MaxWidth = i.MinWidth
|
||||
i.Final = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
allFinal := true
|
||||
data := []byte{}
|
||||
var width uint16
|
||||
for _, e := range i.Exprs {
|
||||
w := e.Width()
|
||||
width += w
|
||||
val, labelMissing, err := e.CheckedEval(c)
|
||||
if err != nil && !labelMissing {
|
||||
return false, err
|
||||
}
|
||||
if labelMissing {
|
||||
allFinal = false
|
||||
if final {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
switch w {
|
||||
case 1:
|
||||
data = append(data, byte(val))
|
||||
case 2:
|
||||
data = append(data, byte(val), byte(val>>8))
|
||||
}
|
||||
}
|
||||
i.MinWidth = width
|
||||
i.MaxWidth = width
|
||||
i.WidthKnown = true
|
||||
if allFinal {
|
||||
i.Data = data
|
||||
i.Final = true
|
||||
}
|
||||
|
||||
return i.Final, nil
|
||||
}
|
||||
|
||||
func (i *I) computeBlock(c context.Context, setWidth bool, final bool) (bool, error) {
|
||||
val, err := i.Exprs[0].Eval(c)
|
||||
if err == nil {
|
||||
i.Value = val
|
||||
i.WidthKnown = true
|
||||
i.Final = true
|
||||
i.MinWidth = val
|
||||
i.MaxWidth = val
|
||||
} else {
|
||||
if setWidth || final {
|
||||
return false, fmt.Errorf("block storage with unknown size")
|
||||
}
|
||||
}
|
||||
return i.Final, nil
|
||||
}
|
||||
|
||||
func (i *I) computeMustKnow(c context.Context, setWidth bool, final bool) (bool, error) {
|
||||
i.WidthKnown = true
|
||||
i.MinWidth = 0
|
||||
i.MaxWidth = 0
|
||||
i.Final = true
|
||||
val, err := i.Exprs[0].Eval(c)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
i.Value = val
|
||||
switch i.Type {
|
||||
case TypeTarget:
|
||||
return false, errors.New("Target not implemented yet.")
|
||||
case TypeOrg:
|
||||
c.SetAddr(val)
|
||||
case TypeEqu:
|
||||
c.Set(i.Label, val)
|
||||
// Don't handle labels.
|
||||
return true, nil
|
||||
}
|
||||
if err := i.computeLabel(c, setWidth, final); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (i *I) computeOp(c context.Context, setWidth bool, final bool) (bool, error) {
|
||||
// If the width is not known, we better have a ZeroPage alternative.
|
||||
if !i.WidthKnown && (i.ZeroOp == 0 || i.ZeroMode == 0) {
|
||||
panic(fmt.Sprintf("Reached computeOp for '%s' with no ZeroPage alternative, i.Command"))
|
||||
}
|
||||
// An op with no args would be final already, so we must have an Expression.
|
||||
if len(i.Exprs) == 0 {
|
||||
panic(fmt.Sprintf("Reached computeOp for '%s' with no expressions", i.Command))
|
||||
}
|
||||
val, labelMissing, err := i.Exprs[0].CheckedEval(c)
|
||||
// A real error
|
||||
if !labelMissing && err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if labelMissing {
|
||||
// Don't know, do care.
|
||||
if final {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Already know enough.
|
||||
if i.WidthKnown {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Do we know the width, even though the value is unknown?
|
||||
if i.Exprs[0].Width() == 1 {
|
||||
i.WidthKnown = true
|
||||
i.MinWidth, i.MaxWidth = 2, 2
|
||||
i.Op, i.Mode = i.ZeroOp, i.ZeroMode
|
||||
i.ZeroOp, i.ZeroMode = 0, 0
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Don't know the width, but don't care on this pass.
|
||||
if !setWidth {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Okay, we have to set the width: since we don't know, go wide.
|
||||
i.WidthKnown = true
|
||||
i.MinWidth, i.MaxWidth = 3, 3
|
||||
i.ZeroOp, i.ZeroMode = 0, 0
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// If we got here, we got an actual value.
|
||||
|
||||
// We need to figure out the width
|
||||
if !i.WidthKnown {
|
||||
if val < 0x100 {
|
||||
i.MinWidth = 2
|
||||
i.MaxWidth = 2
|
||||
i.Op = i.ZeroOp
|
||||
i.Mode = i.ZeroMode
|
||||
} else {
|
||||
i.MinWidth = 3
|
||||
i.MaxWidth = 3
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// It's a branch
|
||||
if i.Mode == opcodes.MODE_RELATIVE {
|
||||
curr, ok := c.GetAddr()
|
||||
if !ok {
|
||||
if final {
|
||||
return false, fmt.Errorf("Cannot determine current address for '%s'", i.Command)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
// Found both current and target addresses
|
||||
offset := int32(val) - (int32(curr) + 2)
|
||||
if offset > 127 {
|
||||
return false, fmt.Errorf("%s cannot jump forward %d (max 127)", i.Command, offset)
|
||||
}
|
||||
if offset < -128 {
|
||||
return false, fmt.Errorf("%s cannot jump back %d (max -128)", i.Command, offset)
|
||||
}
|
||||
val = uint16(offset)
|
||||
}
|
||||
|
||||
i.WidthKnown = true
|
||||
i.Final = true
|
||||
i.ZeroOp = 0
|
||||
i.ZeroMode = 0
|
||||
|
||||
switch i.MinWidth {
|
||||
case 2:
|
||||
// TODO(zellyn): Warn if > 0xff
|
||||
i.Data = []byte{i.Op, byte(val)}
|
||||
case 3:
|
||||
i.Data = []byte{i.Op, byte(val), byte(val >> 8)}
|
||||
default:
|
||||
panic(fmt.Sprintf("computeOp reached erroneously for '%s'", i.Command))
|
||||
}
|
||||
return true, nil
|
||||
}
|
82
asm/inst/instruction_test.go
Normal file
82
asm/inst/instruction_test.go
Normal file
@ -0,0 +1,82 @@
|
||||
package inst
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zellyn/go6502/asm/context"
|
||||
"github.com/zellyn/go6502/asm/expr"
|
||||
)
|
||||
|
||||
func TestComputeLabel(t *testing.T) {
|
||||
i := I{
|
||||
Label: "L1",
|
||||
}
|
||||
c := &context.SimpleContext{}
|
||||
i.computeLabel(c, false, false)
|
||||
}
|
||||
|
||||
func TestWidthDoesNotChange(t *testing.T) {
|
||||
i := I{
|
||||
Type: TypeOp,
|
||||
Command: "LDA",
|
||||
Exprs: []*expr.E{
|
||||
&expr.E{
|
||||
Op: expr.OpMinus,
|
||||
Left: &expr.E{Op: expr.OpLeaf, Text: "L1"},
|
||||
Right: &expr.E{Op: expr.OpLeaf, Text: "L2"},
|
||||
},
|
||||
},
|
||||
MinWidth: 0x2,
|
||||
MaxWidth: 0x3,
|
||||
Final: false,
|
||||
Op: 0xad,
|
||||
Mode: 0x2,
|
||||
ZeroMode: 0x80,
|
||||
ZeroOp: 0xa5,
|
||||
}
|
||||
c := &context.SimpleContext{}
|
||||
c.Set("L1", 0x102)
|
||||
final, err := i.Compute(c, false, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if final {
|
||||
t.Fatal("First pass shouldn't be able to finalize expression with unknown width")
|
||||
}
|
||||
|
||||
final, err = i.Compute(c, true, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if final {
|
||||
t.Fatal("Second pass shouldn't be able to finalize expression with unknown variable.")
|
||||
}
|
||||
if !i.WidthKnown {
|
||||
t.Fatal("Second pass should have set width.")
|
||||
}
|
||||
if i.MinWidth != i.MaxWidth {
|
||||
t.Fatalf("i.WidthKnown, but i.MinWidth(%d) != i.MaxWidth(%d)", i.MinWidth, i.MaxWidth)
|
||||
}
|
||||
if i.MinWidth != 3 {
|
||||
t.Fatalf("i.MinWidth should be 3; got %d", i.MinWidth)
|
||||
}
|
||||
|
||||
c.Set("L2", 0x101)
|
||||
|
||||
final, err = i.Compute(c, true, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !final {
|
||||
t.Fatal("Third pass should be able to finalize expression.")
|
||||
}
|
||||
if !i.WidthKnown {
|
||||
t.Fatal("Third pass should left width unchanged.")
|
||||
}
|
||||
if i.MinWidth != i.MaxWidth {
|
||||
t.Fatalf("i.WidthKnown, but i.MinWidth(%d) != i.MaxWidth(%d)", i.MinWidth, i.MaxWidth)
|
||||
}
|
||||
if i.MinWidth != 3 {
|
||||
t.Fatalf("i.MinWidth should still be 3; got %d", i.MinWidth)
|
||||
}
|
||||
}
|
80
asm/lines/files.go
Normal file
80
asm/lines/files.go
Normal file
@ -0,0 +1,80 @@
|
||||
package lines
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Opener interface {
|
||||
Open(filename string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type OsOpener struct{}
|
||||
|
||||
func (o OsOpener) Open(filename string) (io.ReadCloser, error) {
|
||||
return os.Open(filename)
|
||||
}
|
||||
|
||||
type FileLineSource struct {
|
||||
context Context
|
||||
lines []string
|
||||
size int
|
||||
curr int
|
||||
}
|
||||
|
||||
func (fls *FileLineSource) Next() (line Line, done bool, err error) {
|
||||
if fls.curr >= fls.size {
|
||||
return Line{}, true, nil
|
||||
}
|
||||
fls.curr++
|
||||
return NewLine(fls.lines[fls.curr-1], fls.curr, &fls.context), false, nil
|
||||
}
|
||||
|
||||
func (fls FileLineSource) Context() Context {
|
||||
return fls.context
|
||||
}
|
||||
|
||||
func NewFileLineSource(filename string, context Context, opener Opener) (LineSource, error) {
|
||||
fls := &FileLineSource{context: context}
|
||||
file, err := opener.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
fls.lines = append(fls.lines, scanner.Text())
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fls.size = len(fls.lines)
|
||||
return fls, nil
|
||||
}
|
||||
|
||||
type TestOpener map[string]string
|
||||
|
||||
func (o TestOpener) Open(filename string) (io.ReadCloser, error) {
|
||||
contents, ok := o[filename]
|
||||
if ok {
|
||||
return ioutil.NopCloser(strings.NewReader(contents)), nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("File not found: %s", filename)
|
||||
}
|
||||
}
|
||||
|
||||
func (o TestOpener) Clear() {
|
||||
for k := range o {
|
||||
delete(o, k)
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestOpener() TestOpener {
|
||||
return make(TestOpener)
|
||||
}
|
118
asm/lines/lineparse.go
Normal file
118
asm/lines/lineparse.go
Normal file
@ -0,0 +1,118 @@
|
||||
package lines
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const Eol rune = '\n'
|
||||
|
||||
/*
|
||||
Parse is a struct representing the parse/lex of a single line. It's
|
||||
based on the lexer presented in Rob Pike's "Lexical Scannign in Go"
|
||||
talk: http://cuddle.googlecode.com/hg/talk/lex.html, but it's simpler,
|
||||
because assembly files are entirely line-based.
|
||||
*/
|
||||
type Parse struct {
|
||||
line string
|
||||
start int
|
||||
pos int
|
||||
width int
|
||||
}
|
||||
|
||||
func NewParse(text string) *Parse {
|
||||
text = strings.TrimRightFunc(text, unicode.IsSpace)
|
||||
return &Parse{line: text}
|
||||
}
|
||||
|
||||
func (lp *Parse) Emit() string {
|
||||
emitted := lp.line[lp.start:lp.pos]
|
||||
lp.start = lp.pos
|
||||
return emitted
|
||||
}
|
||||
|
||||
func (lp *Parse) Ignore() {
|
||||
lp.start = lp.pos
|
||||
}
|
||||
|
||||
func (lp *Parse) Next() (c rune) {
|
||||
if lp.pos >= len(lp.line) {
|
||||
lp.width = 0
|
||||
return Eol
|
||||
}
|
||||
c, lp.width = utf8.DecodeRuneInString(lp.line[lp.pos:])
|
||||
lp.pos += lp.width
|
||||
return c
|
||||
}
|
||||
|
||||
func (lp *Parse) Backup() {
|
||||
lp.pos -= lp.width
|
||||
}
|
||||
|
||||
func (lp *Parse) Peek() rune {
|
||||
c := lp.Next()
|
||||
lp.Backup()
|
||||
return c
|
||||
}
|
||||
|
||||
func (lp *Parse) Accept(valid string) bool {
|
||||
if strings.IndexRune(valid, lp.Next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
lp.Backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// Consume accepts and ignores a single character of input.
|
||||
func (lp *Parse) Consume(valid string) bool {
|
||||
if strings.IndexRune(valid, lp.Next()) >= 0 {
|
||||
lp.start = lp.pos
|
||||
return true
|
||||
}
|
||||
lp.Backup()
|
||||
return false
|
||||
}
|
||||
|
||||
func (lp *Parse) AcceptRun(valid string) bool {
|
||||
some := false
|
||||
for strings.IndexRune(valid, lp.Next()) >= 0 {
|
||||
some = true
|
||||
}
|
||||
lp.Backup()
|
||||
return some
|
||||
}
|
||||
|
||||
func (lp *Parse) AcceptUntil(until string) bool {
|
||||
until += "\n"
|
||||
some := false
|
||||
for strings.IndexRune(until, lp.Next()) < 0 {
|
||||
some = true
|
||||
}
|
||||
lp.Backup()
|
||||
return some
|
||||
}
|
||||
|
||||
func (lp *Parse) IgnoreRun(valid string) bool {
|
||||
if lp.AcceptRun(valid) {
|
||||
lp.Ignore()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (lp *Parse) AcceptString(prefix string) bool {
|
||||
if strings.HasPrefix(lp.line[lp.pos:], prefix) {
|
||||
lp.pos += len(prefix)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (lp *Parse) Rest() string {
|
||||
return lp.line[lp.pos:]
|
||||
}
|
||||
|
||||
func (lp *Parse) Text() string {
|
||||
return lp.line
|
||||
}
|
43
asm/lines/lines.go
Normal file
43
asm/lines/lines.go
Normal file
@ -0,0 +1,43 @@
|
||||
package lines
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Context struct {
|
||||
Filename string // Pointer to the filename
|
||||
Parent *Line // Pointer to parent line (eg. include, macro)
|
||||
}
|
||||
|
||||
type Line struct {
|
||||
LineNo int // Actual line number in file
|
||||
DeclaredLineNo int // Declared line number in file, or 0
|
||||
Context *Context // Pointer to the file/include/macro context
|
||||
Parse *Parse // The lineparser
|
||||
}
|
||||
|
||||
type LineSource interface {
|
||||
Next() (line Line, done bool, err error)
|
||||
Context() Context
|
||||
}
|
||||
|
||||
func NewLine(s string, lineNo int, context *Context) Line {
|
||||
l := Line{
|
||||
LineNo: lineNo,
|
||||
Context: context,
|
||||
Parse: NewParse(s),
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
var testFilename = "(test)"
|
||||
|
||||
func NewSimple(s string) Line {
|
||||
return NewLine(s, 0, &Context{Filename: testFilename})
|
||||
}
|
||||
|
||||
func (l Line) Text() string {
|
||||
return l.Parse.Text()
|
||||
}
|
||||
|
||||
func (l Line) String() string {
|
||||
return fmt.Sprintf("%s:%d: %s", l.Context.Filename, l.LineNo, l.Parse.Text())
|
||||
}
|
@ -42,9 +42,7 @@ type Memory interface {
|
||||
}
|
||||
|
||||
// Ticker interface, for keeping track of cycles.
|
||||
type Ticker interface {
|
||||
Tick()
|
||||
}
|
||||
type Ticker func()
|
||||
|
||||
// Interrupt vectors.
|
||||
const (
|
||||
@ -146,7 +144,7 @@ func (c *cpu) Step() error {
|
||||
c.oldPC = c.r.PC
|
||||
i := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
|
||||
if f, ok := Opcodes[i]; ok {
|
||||
f(c)
|
||||
|
@ -24,7 +24,7 @@ func clearFlag(flag byte) func(*cpu) {
|
||||
return func(c *cpu) {
|
||||
c.r.P &^= flag
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ func setFlag(flag byte) func(*cpu) {
|
||||
return func(c *cpu) {
|
||||
c.r.P |= flag
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,12 +45,12 @@ func branch(mask, value byte) func(*cpu) {
|
||||
// T1
|
||||
offset := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
oldPC := c.r.PC
|
||||
if c.r.P&mask == value {
|
||||
c.m.Read(oldPC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.r.PC = c.r.PC + uint16(offset)
|
||||
if offset >= 128 {
|
||||
@ -58,7 +58,7 @@ func branch(mask, value byte) func(*cpu) {
|
||||
}
|
||||
if !samePage(c.r.PC, oldPC) {
|
||||
c.m.Read((oldPC & 0xFF00) | (c.r.PC & 0x00FF))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,7 +121,7 @@ func adc_d(c *cpu, value byte) {
|
||||
c.r.P |= FLAG_Z
|
||||
}
|
||||
case VERSION_65C02:
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
c.setNZ(byte(a & 0xFF))
|
||||
default:
|
||||
panic("Unknown chip version")
|
||||
@ -155,27 +155,27 @@ func brk(c *cpu) {
|
||||
// T1
|
||||
c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC>>8))
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC&0xff))
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
c.m.Write(0x100+uint16(c.r.SP), c.r.P|FLAG_B) // Set B flag
|
||||
c.r.SP--
|
||||
c.r.P |= FLAG_I // Disable interrupts
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
addr := uint16(c.m.Read(IRQ_VECTOR))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T6
|
||||
addr |= (uint16(c.m.Read(IRQ_VECTOR+1)) << 8)
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func cmp(c *cpu, value byte) {
|
||||
@ -215,14 +215,14 @@ func dex(c *cpu) {
|
||||
c.r.X--
|
||||
c.setNZ(c.r.X)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func dey(c *cpu) {
|
||||
c.r.Y--
|
||||
c.setNZ(c.r.Y)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func eor(c *cpu, value byte) {
|
||||
@ -240,40 +240,40 @@ func inx(c *cpu) {
|
||||
c.r.X++
|
||||
c.setNZ(c.r.X)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func iny(c *cpu) {
|
||||
c.r.Y++
|
||||
c.setNZ(c.r.Y)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func jmpAbsolute(c *cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.r.PC++
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func jmpIndirect(c *cpu) {
|
||||
// T1
|
||||
iAddr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
iAddr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
addr := uint16(c.m.Read(iAddr))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
// 6502 jumps to (xxFF,xx00) instead of (xxFF,xxFF+1).
|
||||
// See http://en.wikipedia.org/wiki/MOS_Technology_6502#Bugs_and_quirks
|
||||
@ -290,29 +290,29 @@ func jmpIndirect(c *cpu) {
|
||||
panic("Unknown chip version")
|
||||
}
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func jsr(c *cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC)) // We actually push PC(next) - 1
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(0x100 + uint16(c.r.SP)) // Ignored read on stack
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC>>8)) // Write PC|hi to stack
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
c.m.Write(0x100+uint16(c.r.SP), byte(c.r.PC&0xff)) // Write PC|lo to stack
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func lda(c *cpu, value byte) {
|
||||
@ -344,44 +344,44 @@ func ora(c *cpu, value byte) {
|
||||
|
||||
func nop(c *cpu) {
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func pha(c *cpu) {
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
c.m.Write(0x100+uint16(c.r.SP), c.r.A)
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func pla(c *cpu) {
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
c.m.Read(0x100 + uint16(c.r.SP))
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
c.r.A = c.m.Read(0x100 + uint16(c.r.SP))
|
||||
c.setNZ(c.r.A)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func php(c *cpu) {
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
c.m.Write(0x100+uint16(c.r.SP), c.r.P)
|
||||
c.r.SP--
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func plp(c *cpu) {
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
c.m.Read(0x100 + uint16(c.r.SP))
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
c.r.P = c.m.Read(0x100+uint16(c.r.SP)) | FLAG_UNUSED | FLAG_B
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func rol(c *cpu, value byte) byte {
|
||||
@ -401,43 +401,43 @@ func ror(c *cpu, value byte) byte {
|
||||
func rts(c *cpu) {
|
||||
// T1
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(0x100 + uint16(c.r.SP))
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
addr := uint16(c.m.Read(0x100 + uint16(c.r.SP)))
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
addr |= (uint16(c.m.Read(0x100+uint16(c.r.SP))) << 8)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
c.m.Read(addr)
|
||||
c.r.PC = addr + 1 // Since we pushed PC(next) - 1
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func rti(c *cpu) {
|
||||
// T1
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(0x100 + uint16(c.r.SP))
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.r.P = c.m.Read(0x100+uint16(c.r.SP)) | FLAG_UNUSED
|
||||
c.r.SP++
|
||||
// T4
|
||||
addr := uint16(c.m.Read(0x100 + uint16(c.r.SP)))
|
||||
c.r.SP++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
addr |= (uint16(c.m.Read(0x100+uint16(c.r.SP))) << 8)
|
||||
c.r.PC = addr
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func sbc(c *cpu, value byte) {
|
||||
@ -502,7 +502,7 @@ func sbc_d(c *cpu, value byte) {
|
||||
}
|
||||
// fmt.Printf(" a=$%04X ($%02X)\n", a, byte(a))
|
||||
c.r.A = byte(a)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
c.setNZ(c.r.A)
|
||||
default:
|
||||
panic("Unknown chip version")
|
||||
@ -525,39 +525,39 @@ func tax(c *cpu) {
|
||||
c.r.X = c.r.A
|
||||
c.setNZ(c.r.X)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func tay(c *cpu) {
|
||||
c.r.Y = c.r.A
|
||||
c.setNZ(c.r.Y)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func tsx(c *cpu) {
|
||||
c.r.X = c.r.SP
|
||||
c.setNZ(c.r.X)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func txa(c *cpu) {
|
||||
c.r.A = c.r.X
|
||||
c.setNZ(c.r.A)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func txs(c *cpu) {
|
||||
c.r.SP = c.r.X
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
||||
func tya(c *cpu) {
|
||||
c.r.A = c.r.Y
|
||||
c.setNZ(c.r.A)
|
||||
c.m.Read(c.r.PC)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ func immediate2(f func(*cpu, byte)) func(*cpu) {
|
||||
value := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,15 +29,15 @@ func absolute4r(f func(*cpu, byte)) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
value := c.m.Read(addr)
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,14 +47,14 @@ func absolute4w(f func(*cpu) byte) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Write(addr, f(c))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,11 +64,11 @@ func zp3r(f func(*cpu, byte)) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
value := c.m.Read(addr)
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,10 +78,10 @@ func zp3w(f func(*cpu) byte) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Write(addr, f(c))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,21 +91,21 @@ func absx4r(f func(*cpu, byte)) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
addrX := addr + uint16(c.r.X)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
if !samePage(addr, addrX) {
|
||||
c.m.Read(addrX - 0x100)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
// T3(cotd.) or T4
|
||||
value := c.m.Read(addrX)
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,21 +115,21 @@ func absy4r(f func(*cpu, byte)) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
addrY := addr + uint16(c.r.Y)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
if !samePage(addr, addrY) {
|
||||
c.m.Read(addrY - 0x100)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
// T3(cotd.) or T4
|
||||
value := c.m.Read(addrY)
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,18 +139,18 @@ func absx5w(f func(*cpu) byte) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
addrX := addr + uint16(c.r.X)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Read((addr & 0xFF00) | (addrX & 0x00FF))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
c.m.Write(addrX, f(c))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,18 +160,18 @@ func absy5w(f func(*cpu) byte) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
addrY := addr + uint16(c.r.Y)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Read((addr & 0xFF00) | (addrY & 0x00FF))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
c.m.Write(addrY, f(c))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,14 +182,14 @@ func zpx4r(f func(*cpu, byte)) func(*cpu) {
|
||||
addr := c.m.Read(c.r.PC)
|
||||
addrX := uint16(addr + c.r.X)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(uint16(addr))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
value := c.m.Read(addrX)
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,13 +200,13 @@ func zpx4w(f func(*cpu) byte) func(*cpu) {
|
||||
addr := c.m.Read(c.r.PC)
|
||||
addrX := uint16(addr + c.r.X)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(uint16(addr))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Write(uint16(addrX), f(c))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,14 +217,14 @@ func zpy4r(f func(*cpu, byte)) func(*cpu) {
|
||||
addr := c.m.Read(c.r.PC)
|
||||
addrY := uint16(addr + c.r.Y)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(uint16(addr))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
value := c.m.Read(uint16(addrY))
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,13 +235,13 @@ func zpy4w(f func(*cpu) byte) func(*cpu) {
|
||||
addr := c.m.Read(c.r.PC)
|
||||
addrY := uint16(addr + c.r.Y)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(uint16(addr))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Write(addrY, f(c))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,23 +251,23 @@ func zpiy5r(f func(*cpu, byte)) func(*cpu) {
|
||||
// T1
|
||||
iAddr := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr := uint16(c.m.Read(uint16(iAddr)))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
addr |= (uint16(c.m.Read(uint16(iAddr+1))) << 8)
|
||||
addrY := addr + uint16(c.r.Y)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
if !samePage(addr, addrY) {
|
||||
c.m.Read((addr & 0xFF00) | (addrY & 0x00FF))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
// T4(cotd.) or T5
|
||||
value := c.m.Read(addr + uint16(c.r.Y))
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,20 +277,20 @@ func zpiy6w(f func(*cpu) byte) func(*cpu) {
|
||||
// T1
|
||||
iAddr := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr := uint16(uint16(c.m.Read(uint16(iAddr))))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
addr |= (uint16(c.m.Read(uint16(iAddr+1))) << 8)
|
||||
addrY := addr + uint16(c.r.Y)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
c.m.Read((addr & 0xFF00) | (addrY & 0x00FF))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
c.m.Write(addr+uint16(c.r.Y), f(c))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,20 +300,20 @@ func zpxi6r(f func(*cpu, byte)) func(*cpu) {
|
||||
// T1
|
||||
iAddr := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(uint16(iAddr))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
addr := uint16(uint16(c.m.Read(uint16(iAddr + c.r.X))))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
addr |= (uint16(c.m.Read(uint16(iAddr+c.r.X+1))) << 8)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
value := c.m.Read(addr)
|
||||
f(c, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,19 +323,19 @@ func zpxi6w(f func(*cpu) byte) func(*cpu) {
|
||||
// T1
|
||||
iAddr := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(uint16(iAddr))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
addr := uint16(uint16(c.m.Read(uint16(iAddr + c.r.X))))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
addr |= (uint16(c.m.Read(uint16(iAddr+c.r.X+1))) << 8)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
c.m.Write(addr, f(c))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,7 +345,7 @@ func acc2rmw(f func(*cpu, byte) byte) func(*cpu) {
|
||||
// T1
|
||||
c.m.Read(c.r.PC)
|
||||
c.r.A = f(c, c.r.A)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,16 +355,16 @@ func zp5rmw(f func(*cpu, byte) byte) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
value := c.m.Read(addr)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Write(addr, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
c.m.Write(addr, f(c, value))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,20 +374,20 @@ func abs6rmw(f func(*cpu, byte) byte) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
value := c.m.Read(addr)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
c.m.Write(addr, value) // Spurious write...
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
c.m.Write(addr, f(c, value))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,20 +397,20 @@ func zpx6rmw(f func(*cpu, byte) byte) func(*cpu) {
|
||||
// T1
|
||||
addr8 := c.m.Read(c.r.PC)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
c.m.Read(uint16(addr8))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
addr := uint16(addr8 + c.r.X)
|
||||
value := c.m.Read(addr)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
c.m.Write(addr, value)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
c.m.Write(addr, f(c, value))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,23 +420,23 @@ func absx7rmw(f func(*cpu, byte) byte) func(*cpu) {
|
||||
// T1
|
||||
addr := uint16(c.m.Read(c.r.PC))
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T2
|
||||
addr |= (uint16(c.m.Read(c.r.PC)) << 8)
|
||||
addr += uint16(c.r.X)
|
||||
c.r.PC++
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T3
|
||||
c.m.Read(addr)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T4
|
||||
value := c.m.Read(addr)
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T5
|
||||
c.m.Write(addr, value) // Spurious write
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
// T6
|
||||
c.m.Write(addr, f(c, value))
|
||||
c.t.Tick()
|
||||
c.t()
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,12 @@ function tables.
|
||||
package opcodes
|
||||
|
||||
// Opcode addressing modes.
|
||||
type AddressingMode int
|
||||
type AddressingMode uint
|
||||
|
||||
const MODE_UNKNOWN = 0
|
||||
|
||||
const (
|
||||
MODE_IMPLIED AddressingMode = iota
|
||||
MODE_IMPLIED AddressingMode = 1 << iota
|
||||
MODE_ABSOLUTE
|
||||
MODE_INDIRECT
|
||||
MODE_RELATIVE
|
||||
@ -24,6 +26,9 @@ const (
|
||||
MODE_A
|
||||
)
|
||||
|
||||
// Logical OR of the three indirect modes
|
||||
const MODE_INDIRECT_ANY AddressingMode = MODE_INDIRECT | MODE_INDIRECT_X | MODE_INDIRECT_Y
|
||||
|
||||
// Opcode read/write semantics: does the opcode read, write, or
|
||||
// rmw. Useful to distinguish between instructions further than just
|
||||
// addressing mode.
|
||||
@ -162,20 +167,20 @@ var Opcodes = map[byte]Opcode{
|
||||
|
||||
// 3-opcode, 4*-cycle abs,X/Y
|
||||
0x1D: {"ORA", MODE_ABS_X, RW_R},
|
||||
0x19: {"ORA", MODE_ABS_X, RW_R},
|
||||
0x39: {"AND", MODE_ABS_X, RW_R},
|
||||
0x19: {"ORA", MODE_ABS_Y, RW_R},
|
||||
0x39: {"AND", MODE_ABS_Y, RW_R},
|
||||
0x3D: {"AND", MODE_ABS_X, RW_R},
|
||||
0x59: {"EOR", MODE_ABS_X, RW_R},
|
||||
0x59: {"EOR", MODE_ABS_Y, RW_R},
|
||||
0x5D: {"EOR", MODE_ABS_X, RW_R},
|
||||
0x79: {"ADC", MODE_ABS_X, RW_R},
|
||||
0x79: {"ADC", MODE_ABS_Y, RW_R},
|
||||
0x7D: {"ADC", MODE_ABS_X, RW_R},
|
||||
0xB9: {"LDA", MODE_ABS_Y, RW_R},
|
||||
0xBD: {"LDA", MODE_ABS_X, RW_R},
|
||||
0xB9: {"LDA", MODE_ABS_X, RW_R},
|
||||
0xD9: {"CMP", MODE_ABS_X, RW_R},
|
||||
0xD9: {"CMP", MODE_ABS_Y, RW_R},
|
||||
0xDD: {"CMP", MODE_ABS_X, RW_R},
|
||||
0xF9: {"SBC", MODE_ABS_X, RW_R},
|
||||
0xF9: {"SBC", MODE_ABS_Y, RW_R},
|
||||
0xFD: {"SBC", MODE_ABS_X, RW_R},
|
||||
0xBE: {"LDX", MODE_ABS_X, RW_R},
|
||||
0xBE: {"LDX", MODE_ABS_Y, RW_R},
|
||||
0xBC: {"LDY", MODE_ABS_X, RW_R},
|
||||
|
||||
// 3-opcode, 5-cycle abs,X/Y
|
||||
@ -254,3 +259,42 @@ var Opcodes = map[byte]Opcode{
|
||||
0xDE: {"DEC", MODE_ABS_X, RW_RMW},
|
||||
0xFE: {"INC", MODE_ABS_X, RW_RMW},
|
||||
}
|
||||
|
||||
// Information for lookup of opcodes by name and mode -------------------------
|
||||
|
||||
type OpInfo struct {
|
||||
Mode AddressingMode
|
||||
Length int
|
||||
Byte byte
|
||||
}
|
||||
|
||||
type OpSummary struct {
|
||||
Modes AddressingMode // bitmask of supported modes
|
||||
Ops []OpInfo
|
||||
}
|
||||
|
||||
var ByName map[string]OpSummary
|
||||
|
||||
func init() {
|
||||
ByName = make(map[string]OpSummary)
|
||||
for b, oc := range Opcodes {
|
||||
info := OpInfo{oc.Mode, ModeLengths[oc.Mode], b}
|
||||
summary := ByName[oc.Name]
|
||||
summary.Modes |= oc.Mode
|
||||
summary.Ops = append(summary.Ops, info)
|
||||
ByName[oc.Name] = summary
|
||||
}
|
||||
}
|
||||
|
||||
func (s OpSummary) AnyModes(modes AddressingMode) bool {
|
||||
return modes&s.Modes != 0
|
||||
}
|
||||
|
||||
func (s OpSummary) OpForMode(mode AddressingMode) (OpInfo, bool) {
|
||||
for _, o := range s.Ops {
|
||||
if o.Mode == mode {
|
||||
return o, true
|
||||
}
|
||||
}
|
||||
return OpInfo{}, false
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func TestFunctionalTestCompare(t *testing.T) {
|
||||
}
|
||||
|
||||
var cc CycleCount
|
||||
c := cpu.NewCPU(&m, &cc, cpu.VERSION_6502)
|
||||
c := cpu.NewCPU(&m, cc.Tick, cpu.VERSION_6502)
|
||||
c.Reset()
|
||||
|
||||
m.Reset(MODE_RECORD)
|
||||
|
@ -24,7 +24,7 @@ func (m *K64) Write(address uint16, value byte) {
|
||||
m[address] = value
|
||||
}
|
||||
|
||||
// Cycle counter for the tests. Satisfies the cpu.Ticker interface.
|
||||
// Cycle counter for the tests.
|
||||
type CycleCount uint64
|
||||
|
||||
func (c *CycleCount) Tick() {
|
||||
@ -60,7 +60,7 @@ func TestFunctionalTestInstructions(t *testing.T) {
|
||||
var cc CycleCount
|
||||
OFFSET := 0xa
|
||||
copy(m[OFFSET:len(bytes)+OFFSET], bytes)
|
||||
c := cpu.NewCPU(&m, &cc, cpu.VERSION_6502)
|
||||
c := cpu.NewCPU(&m, cc.Tick, cpu.VERSION_6502)
|
||||
c.Reset()
|
||||
c.SetPC(0x1000)
|
||||
for {
|
||||
@ -150,7 +150,7 @@ func TestDecimalMode6502(t *testing.T) {
|
||||
OFFSET := 0x1000
|
||||
copy(m[OFFSET:len(bytes)+OFFSET], bytes)
|
||||
m[1] = 0 // 6502
|
||||
c := cpu.NewCPU(&m, &cc, cpu.VERSION_6502)
|
||||
c := cpu.NewCPU(&m, cc.Tick, cpu.VERSION_6502)
|
||||
c.Reset()
|
||||
c.SetPC(0x1000)
|
||||
for {
|
||||
@ -186,7 +186,7 @@ func TestDecimalMode65C02(t *testing.T) {
|
||||
OFFSET := 0x1000
|
||||
copy(m[OFFSET:len(bytes)+OFFSET], bytes)
|
||||
m[1] = 1 // 65C02
|
||||
c := cpu.NewCPU(&m, &cc, cpu.VERSION_65C02)
|
||||
c := cpu.NewCPU(&m, cc.Tick, cpu.VERSION_65C02)
|
||||
c.Reset()
|
||||
c.SetPC(0x1000)
|
||||
for {
|
||||
|
Loading…
x
Reference in New Issue
Block a user