1
0
mirror of https://github.com/zellyn/go6502.git synced 2024-06-30 13:29:30 +00:00
go6502/asm/inst/instruction.go
2014-09-09 17:32:15 -07:00

347 lines
8.4 KiB
Go

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 = 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
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
TypeSetting // An on/off setting toggle
)
type Variant int
// Variants for instructions. These tell the instruction how to
// interpret the raw data that comes in on the first or second pass.
const (
VarUnknown = Variant(iota)
VarBytes // Data: expressions, but forced to one byte per
VarMixed // Bytes or words (LE), depending on individual expression widths
VarWordsLe // Data: expressions, but forced to one word per, little-endian
VarWordsBe // Data: expressions, but forced to one word per, big-endian
VarAscii // Data: from ASCII strings, high bit clear
VarAsciiFlip // Data: from ASCII strings, high bit clear, except last char
VarAsciiHi // Data: from ASCII strings, high bit set
VarAsciiHiFlip // Data: from ASCII strings, high bit set, except last char
VarRelative // For branches: a one-byte relative address
VarEquNormal // Equ: a normal equate
VarEquPageZero // Equ: a page-zero equate
VarOpByte // An op with a one-byte argument
VarOpWord // An op with a one-word argument
VarOpBranch // An op with a one-byte relative address argument
)
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
Width uint16 // width in bytes
Final bool // Do we know the actual bytes yet?
Op byte // Opcode
Mode opcodes.AddressingMode // Opcode mode
Value uint16 // For Equates, the value
DeclaredLine uint16 // Line number listed in file
Line *lines.Line // Line object for this line
Addr uint16 // Current memory address
Var Variant // Variant of instruction type
}
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:
switch i.Var {
case VarMixed:
return "data"
case VarBytes:
return "data/b"
case VarWordsLe:
return "data/wle"
case VarWordsBe:
return "data/wbe"
case VarAscii, VarAsciiHi, VarAsciiFlip, VarAsciiHiFlip:
return "data/b"
default:
panic(fmt.Sprintf("unknown data variant: %d", i.Var))
}
case TypeBlock:
return "block"
case TypeOrg:
return "org"
case TypeTarget:
return "target"
case TypeSegment:
return "seg"
case TypeEqu:
return "="
case TypeEnd:
return "end"
case TypeSetting:
return "set"
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)
case TypeSetting:
return fmt.Sprintf("{set %s %s}", i.Command, i.TextArg)
}
s := "{" + i.TypeString()
if i.Label != "" {
s += fmt.Sprintf(" '%s'", i.Label)
}
if i.TextArg != "" {
s += fmt.Sprintf(` "%s"`, i.TextArg)
}
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) error {
if i.Type == TypeEqu || i.Type == TypeTarget {
return i.computeMustKnow(c)
}
if i.Final {
return nil
}
switch i.Type {
case TypeOp:
return i.computeOp(c)
case TypeData:
return i.computeData(c)
case TypeBlock:
panic("Compute called with TypeBlock")
}
// Everything else is zero-width
i.Width = 0
i.Final = true
return nil
}
func (i *I) computeData(c context.Context) error {
if len(i.Data) > 0 {
i.Width = uint16(len(i.Data))
i.Final = true
return nil
}
data := []byte{}
var width uint16
for _, e := range i.Exprs {
var w uint16
switch i.Var {
case VarMixed:
w = e.Width()
case VarBytes:
w = 1
case VarWordsLe, VarWordsBe:
w = 2
}
width += w
val, err := e.Eval(c, i.Line)
if err != nil {
return err
}
switch i.Var {
case VarMixed:
switch w {
case 1:
data = append(data, byte(val))
case 2:
data = append(data, byte(val), byte(val>>8))
}
case VarBytes:
data = append(data, byte(val))
case VarWordsLe:
data = append(data, byte(val), byte(val>>8))
case VarWordsBe:
data = append(data, byte(val>>8), byte(val))
default:
panic(fmt.Sprintf("Unknown data variant handed to computeData: %d", i.Var))
}
}
i.Width = width
i.Data = data
i.Final = true
return nil
}
func (i *I) computeBlock(c context.Context, final bool) (bool, error) {
val, err := i.Exprs[0].Eval(c, i.Line)
if err == nil {
i.Value = val
i.Final = true
i.Width = val
} else {
if final {
return false, i.Errorf("block storage with unknown size")
}
}
return i.Final, nil
}
func (i *I) computeMustKnow(c context.Context) error {
i.Width = 0
i.Final = true
val, err := i.Exprs[0].Eval(c, i.Line)
if err != nil {
return err
}
i.Value = val
switch i.Type {
case TypeTarget:
return errors.New("Target not implemented yet.")
case TypeOrg:
c.SetAddr(val)
case TypeEqu:
c.Set(i.Label, val)
// Don't handle labels.
return nil
}
return nil
}
func (i *I) computeOp(c context.Context) error {
// 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, err := i.Exprs[0].Eval(c, i.Line)
// A real error
if err != nil {
return err
}
// If we got here, we got an actual value.
// It's a branch
if i.Mode == opcodes.MODE_RELATIVE {
curr := c.GetAddr()
offset := int32(val) - (int32(curr) + 2)
if offset > 127 {
return i.Errorf("%s cannot jump forward %d (max 127) from $%04x to $%04x", i.Command, offset, curr+2, val)
}
if offset < -128 {
return i.Errorf("%s cannot jump back %d (max -128) from $%04x to $%04x", i.Command, offset, curr+2, val)
}
val = uint16(offset)
}
i.Final = true
switch i.Width {
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 nil
}
func (i I) Errorf(format string, a ...interface{}) error {
return i.Line.Errorf(format, a...)
}
func (i I) Sprintf(format string, a ...interface{}) string {
return i.Line.Sprintf(format, a...)
}