1
0
mirror of https://github.com/zellyn/go6502.git synced 2024-10-01 05:56:28 +00:00
go6502/asm/flavors/merlin/merlin.go
2014-08-29 16:34:52 -07:00

208 lines
5.8 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package merlin
import (
"fmt"
"strings"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/expr"
"github.com/zellyn/go6502/asm/flavors/oldschool"
"github.com/zellyn/go6502/asm/inst"
"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 {
oldschool.Base
}
const whitespace = " \t"
const macroNameChars = oldschool.Letters + oldschool.Digits + "_"
func New() *Merlin {
m := &Merlin{}
m.Name = "merlin"
m.LabelChars = oldschool.Letters + oldschool.Digits + ":"
m.LabelColons = oldschool.ReqDisallowed
m.ExplicitARegister = oldschool.ReqOptional
m.StringEndOptional = false
m.CommentChar = ';'
m.BinaryChar = '%'
m.LsbChars = "<"
m.MsbChars = ">/"
m.ImmediateChars = "#"
m.HexCommas = oldschool.ReqOptional
m.CharChars = "'"
m.InvCharChars = `"`
m.MacroArgSep = ";"
m.SuffixForWide = true
m.LocalMacroLabelsVal = true
m.DefaultOriginVal = 0x8000
m.Directives = map[string]oldschool.DirectiveInfo{
"ORG": {inst.TypeOrg, m.ParseOrg, 0},
"OBJ": {inst.TypeNone, nil, 0},
"ENDASM": {inst.TypeEnd, m.ParseNoArgDir, 0},
"=": {inst.TypeEqu, m.ParseEquate, inst.VarEquNormal},
"HEX": {inst.TypeData, m.ParseHexString, inst.VarBytes},
"DFB": {inst.TypeData, m.ParseData, inst.VarBytes},
"DB": {inst.TypeData, m.ParseData, inst.VarBytes},
"DA": {inst.TypeData, m.ParseData, inst.VarWordsLe},
"DDB": {inst.TypeData, m.ParseData, inst.VarWordsBe},
"ASC": {inst.TypeData, m.ParseAscii, inst.VarAscii},
"DCI": {inst.TypeData, m.ParseAscii, inst.VarAsciiFlip},
".DO": {inst.TypeIfdef, m.ParseDo, 0},
".ELSE": {inst.TypeIfdefElse, m.ParseNoArgDir, 0},
".FIN": {inst.TypeIfdefEnd, m.ParseNoArgDir, 0},
"MAC": {inst.TypeMacroStart, m.MarkMacroStart, 0},
"EOM": {inst.TypeMacroEnd, m.ParseNoArgDir, 0},
"<<<": {inst.TypeMacroEnd, m.ParseNoArgDir, 0},
"PAGE": {inst.TypeNone, nil, 0}, // New page
"TTL": {inst.TypeNone, nil, 0}, // Title
"SAV": {inst.TypeNone, nil, 0}, // Save
"DSK": {inst.TypeNone, nil, 0}, // Assemble to disk
"PUT": {inst.TypeInclude, m.ParseInclude, 0},
"USE": {inst.TypeInclude, m.ParseInclude, 0},
}
m.EquateDirectives = map[string]bool{
"=": true,
}
m.Operators = map[string]expr.Operator{
"*": expr.OpMul,
"/": expr.OpDiv,
"+": expr.OpPlus,
"-": expr.OpMinus,
"<": expr.OpLt,
">": expr.OpGt,
"=": expr.OpEq,
".": expr.OpOr,
"&": expr.OpAnd,
"!": expr.OpXor,
}
m.InitContextFunc = func(ctx context.Context) {
ctx.SetOnOffDefaults(map[string]bool{
"LST": true, // Display listing: not used
"XC": false, // Extended commands: not implemented yet
"EXP": false, // How to print macro calls
"LSTDO": false, // List conditional code?
"TR": false, // truncate listing to 3 bytes?
"CYC": false, // print cycle times?
})
}
m.SetAsciiVariation = func(ctx context.Context, in *inst.I, lp *lines.Parse) {
if in.Command != "ASC" && in.Command != "DCI" {
panic(fmt.Sprintf("Unimplemented/unknown ascii directive: '%s'", in.Command))
}
invert := lp.Peek() < '\''
invertLast := in.Command == "DCI"
switch {
case !invert && !invertLast:
in.Var = inst.VarAscii
case !invert && invertLast:
in.Var = inst.VarAsciiFlip
case invert && !invertLast:
in.Var = inst.VarAsciiHi
case invert && invertLast:
in.Var = inst.VarAsciiHiFlip
}
}
// ParseMacroCall parses a macro call. We expect in.Command to hold
// the "command column" value, which caused isMacroCall to return
// true, and the lp to be pointing to the following character
// (probably whitespace).
m.ParseMacroCall = func(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, bool, error) {
if in.Command == "" {
return in, false, nil
}
byName := ctx.HasMacroName(in.Command)
// It's not a macro call.
if in.Command != ">>>" && in.Command != "PMC" && !byName {
return in, false, nil
}
in.Type = inst.TypeMacroCall
lp.IgnoreRun(oldschool.Whitespace)
if !byName {
if !lp.AcceptRun(macroNameChars) {
c := lp.Next()
return in, true, in.Errorf("Expected macro name, got char '%c'", c)
}
in.Command = lp.Emit()
if !ctx.HasMacroName(in.Command) {
return in, true, in.Errorf("Unknown macro: '%s'", in.Command)
}
if !lp.Consume(" ./,-(") {
c := lp.Next()
if c == lines.Eol || c == ';' {
return in, true, nil
}
return in, true, in.Errorf("Expected macro name/args separator [ ./,-(], got '%c'", c)
}
}
for {
s, err := m.ParseMacroArg(in, lp)
if err != nil {
return in, true, err
}
in.MacroArgs = append(in.MacroArgs, s)
if !lp.Consume(";") {
break
}
}
return in, true, nil
}
m.FixLabel = func(ctx context.Context, label string) (string, error) {
_, macroCount, locals := ctx.GetMacroCall()
switch {
case label == "":
return label, nil
case label[0] == ':':
if last := ctx.LastLabel(); last == "" {
return "", fmt.Errorf("sublabel '%s' without previous label", label)
} else {
return fmt.Sprintf("%s/%s", last, label), nil
}
case locals[label]:
return fmt.Sprintf("%s{%d}", label, macroCount), nil
}
return label, nil
}
m.IsNewParentLabel = func(label string) bool {
return label != "" && label[0] != ':'
}
return m
}
func (m *Merlin) ParseInclude(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace)
lp.AcceptUntil(";")
filename := strings.TrimSpace(lp.Emit())
prefix := "T."
if len(filename) > 0 && filename[0] < '@' {
prefix = ""
filename = strings.TrimSpace(filename[1:])
}
if filename == "" {
return in, in.Errorf("%s expects filename", in.Command)
}
in.TextArg = prefix + filename
in.WidthKnown = true
in.Width = 0
in.Final = true
return in, nil
}