1
0
mirror of https://github.com/zellyn/go6502.git synced 2024-09-28 17:54:52 +00:00
go6502/asm/asm.go
2014-08-22 08:24:30 -07:00

296 lines
7.3 KiB
Go

package asm
import (
"fmt"
"io"
"path/filepath"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/flavors"
"github.com/zellyn/go6502/asm/inst"
"github.com/zellyn/go6502/asm/lines"
"github.com/zellyn/go6502/asm/macros"
"github.com/zellyn/go6502/asm/membuf"
)
type Assembler struct {
Flavor flavors.F
Opener lines.Opener
Insts []*inst.I
Macros map[string]macros.M
Ctx *context.SimpleContext
}
func NewAssembler(flavor flavors.F, opener lines.Opener) *Assembler {
ctx := &context.SimpleContext{}
flavor.InitContext(ctx)
return &Assembler{
Flavor: flavor,
Opener: opener,
Macros: make(map[string]macros.M),
Ctx: ctx,
}
}
// Load loads a new assembler file, deleting any previous data.
func (a *Assembler) Load(filename string, prefix int) error {
a.initPass()
context := lines.Context{Filename: filename}
ls, err := lines.NewFileLineSource(filename, context, a.Opener, prefix)
if err != nil {
return err
}
lineSources := []lines.LineSource{ls}
ifdefs := []bool{}
macroCall := 0
for len(lineSources) > 0 {
line, done, err := lineSources[0].Next()
if err != nil {
return err
}
if done {
lineSources = lineSources[1:]
continue
}
in, parseErr := a.Flavor.ParseInstr(a.Ctx, line, false)
if len(ifdefs) > 0 && !ifdefs[0] && in.Type != inst.TypeIfdefElse && in.Type != inst.TypeIfdefEnd {
// we're in an inactive ifdef branch
continue
}
if err != nil {
return err
}
if _, err := a.passInst(&in, false); err != nil {
return err
}
switch in.Type {
case inst.TypeUnknown:
if parseErr != nil {
return parseErr
}
return line.Errorf("unknown instruction: %s", line.Parse.Text())
case inst.TypeMacroStart:
if err := a.readMacro(in, lineSources[0]); err != nil {
return err
}
continue // no need to append
case inst.TypeMacroCall:
macroCall++
m, ok := a.Macros[in.Command]
if !ok {
return in.Errorf(`unknown macro: "%s"`, in.Command)
}
subLs, err := m.LineSource(a.Flavor, in, macroCall, prefix)
if err != nil {
return in.Errorf(`error calling macro "%s": %v`, m.Name, err)
}
lineSources = append([]lines.LineSource{subLs}, lineSources...)
a.Ctx.PushMacroCall(m.Name, macroCall, m.Locals)
case inst.TypeMacroEnd:
// If we reached here, it's in a macro call, not a definition.
if !a.Ctx.PopMacroCall() {
return in.Errorf("unexpected end of macro")
}
case inst.TypeIfdef:
if len(in.Exprs) == 0 {
panic(fmt.Sprintf("Ifdef got parsed with no expression: %s", line))
}
val, err := in.Exprs[0].Eval(a.Ctx, in.Line)
if err != nil {
return in.Errorf("cannot eval ifdef condition: %v", err)
}
ifdefs = append([]bool{val != 0}, ifdefs...)
case inst.TypeIfdefElse:
if len(ifdefs) == 0 {
return in.Errorf("ifdef else branch encountered outside ifdef: %s", line)
}
ifdefs[0] = !ifdefs[0]
case inst.TypeIfdefEnd:
if len(ifdefs) == 0 {
return in.Errorf("ifdef end encountered outside ifdef: %s", line)
}
ifdefs = ifdefs[1:]
case inst.TypeInclude:
filename = filepath.Join(filepath.Dir(in.Line.Context.Filename), in.TextArg)
subContext := lines.Context{Filename: filename, Parent: in.Line}
subLs, err := lines.NewFileLineSource(filename, subContext, a.Opener, prefix)
if err != nil {
return in.Errorf("error including file: %v", err)
}
lineSources = append([]lines.LineSource{subLs}, lineSources...)
case inst.TypeTarget:
return in.Errorf("target not (yet) implemented: %s", line)
case inst.TypeSegment:
return in.Errorf("segment not (yet) implemented: %s", line)
case inst.TypeEnd:
return nil
default:
}
a.Insts = append(a.Insts, &in)
}
return nil
}
func (a *Assembler) Assemble(filename string) error {
return a.AssembleWithPrefix(filename, 0)
}
func (a *Assembler) AssembleWithPrefix(filename string, prefix int) error {
a.Reset()
if err := a.Load(filename, prefix); err != nil {
return err
}
// Final pass.
if err := a.Pass2(); err != nil {
return err
}
return nil
}
func (a *Assembler) readMacro(in inst.I, ls lines.LineSource) error {
m := macros.M{
Name: in.TextArg,
Args: in.MacroArgs,
}
if a.Flavor.LocalMacroLabels() {
m.Locals = make(map[string]bool)
}
for {
line, done, err := ls.Next()
if err != nil {
return in.Errorf("error while reading macro %s: %v", m.Name)
}
if done {
return in.Errorf("end of file while reading macro %s", m.Name)
}
in2, err := a.Flavor.ParseInstr(a.Ctx, line, true)
if a.Flavor.LocalMacroLabels() && in2.Label != "" {
m.Locals[in2.Label] = true
}
m.Lines = append(m.Lines, line.Parse.Text())
if err == nil && in2.Type == inst.TypeMacroEnd {
a.Macros[m.Name] = m
a.Ctx.AddMacroName(m.Name)
return nil
}
}
}
// Clear out stuff that may be hanging around from the previous pass, set origin to default, etc.
func (a *Assembler) initPass() {
a.Ctx.SetLastLabel("") // No last label (yet)
a.Ctx.RemoveChanged() // Remove any variables whose value ever changed.
a.Ctx.SetAddr(a.Flavor.DefaultOrigin())
}
// passInst performs a pass on a single instruction. It forces the
// instruction to decide its width, but may not know all the
// arguments. If final is true, and the instruction cannot be
// finalized, it returns an error.
func (a *Assembler) passInst(in *inst.I, final bool) (isFinal bool, err error) {
isFinal, err = in.Compute(a.Ctx, final)
if err != nil {
return false, err
}
// Update address
addr, _ := a.Ctx.GetAddr()
in.Addr = addr
a.Ctx.SetAddr(addr + in.Width)
return isFinal, nil
}
// Pass2 performs the second assembly pass. It returns an error for
// any instruction that cannot be finalized.
func (a *Assembler) Pass2() error {
a.initPass()
for _, in := range a.Insts {
instFinal, err := a.passInst(in, true)
if err != nil {
return err
}
if !instFinal {
return in.Errorf("cannot finalize instruction: %s", in)
}
}
return nil
}
// RawBytes returns the raw bytes, sequentially in the order of the
// lines of the file. 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{}, in.Errorf("cannot finalize value: %s", in)
}
result = append(result, in.Data...)
}
return result, nil
}
func (a *Assembler) Reset() {
a.Insts = nil
}
func (a *Assembler) Membuf() (*membuf.Membuf, error) {
m := &membuf.Membuf{}
for _, in := range a.Insts {
if !in.Final {
return nil, in.Errorf("cannot finalize value: %s", in)
}
if in.Width > 0 {
m.Write(int(in.Addr), in.Data)
}
}
return m, nil
}
func (a *Assembler) GenerateListing(w io.Writer, width int) error {
for _, in := range a.Insts {
if !in.Final {
return in.Errorf("cannot finalize value: %s", in)
}
for i := 0; i < len(in.Data) || i < width; i++ {
if i%width == 0 {
s := fmt.Sprintf("%04x:", int(in.Addr)+i)
if i > 0 {
s = "\n" + s
}
if _, err := fmt.Fprint(w, s); err != nil {
return err
}
}
s := " "
if i < len(in.Data) {
s = fmt.Sprintf(" %02x", in.Data[i])
}
if _, err := fmt.Fprint(w, s); err != nil {
return err
}
if i == width-1 {
if _, err := fmt.Fprint(w, " "+in.Line.Text()); err != nil {
return err
}
}
}
if _, err := fmt.Fprint(w, "\n"); err != nil {
return err
}
}
return nil
}