mirror of
https://github.com/zellyn/go6502.git
synced 2025-02-11 12:32:07 +00:00
working on redbook-ish assmebler
This commit is contained in:
parent
458911e505
commit
9a7f1c2cca
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/zellyn/go6502/asm"
|
||||
"github.com/zellyn/go6502/asm/flavors"
|
||||
"github.com/zellyn/go6502/asm/flavors/redbook"
|
||||
"github.com/zellyn/go6502/asm/flavors/scma"
|
||||
"github.com/zellyn/go6502/asm/ihex"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
@ -18,7 +19,8 @@ var flavor string
|
||||
|
||||
func init() {
|
||||
flavorsByName = map[string]flavors.F{
|
||||
"scma": scma.New(),
|
||||
"scma": scma.New(),
|
||||
"redbook": redbook.New(),
|
||||
}
|
||||
var names []string
|
||||
for name := range flavorsByName {
|
||||
|
@ -61,3 +61,10 @@ https://github.com/Klaus2m5/6502_65C02_functional_tests/blob/master/6502_functio
|
||||
.MA -- Macro Definition 5-11
|
||||
.EM -- End of Macro 5-11
|
||||
|
||||
|
||||
* Edasm notes
|
||||
http://mirrors.apple2.org.za/ftp.apple.asimov.net/images/programming/assembler/EDASM.DSK
|
||||
|
||||
** How to run
|
||||
Boot from the disk
|
||||
type "- edasm.system"
|
||||
|
567
asm/flavors/oldschool/oldschool.go
Normal file
567
asm/flavors/oldschool/oldschool.go
Normal file
@ -0,0 +1,567 @@
|
||||
package oldschool
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"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 whitespace = " \t"
|
||||
const cmdChars = Letters + "."
|
||||
const macroNameChars = Letters + Digits + "._"
|
||||
const fileChars = Letters + Digits + "."
|
||||
const operatorChars = "+-*/<>="
|
||||
|
||||
// 40 spaces = comment column
|
||||
const comment_whitespace_prefix = " "
|
||||
|
||||
type DirectiveInfo struct {
|
||||
Type inst.Type
|
||||
Func func(inst.I, *lines.Parse) (inst.I, error)
|
||||
}
|
||||
|
||||
type Requiredness int
|
||||
|
||||
const (
|
||||
ReqOptional Requiredness = iota
|
||||
ReqRequired
|
||||
ReqDisallowed
|
||||
)
|
||||
|
||||
// Base implements the S-C Macro Assembler-compatible assembler flavor.
|
||||
// See http://www.txbobsc.com/scsc/ and http://stjarnhimlen.se/apple2/
|
||||
type Base struct {
|
||||
Directives map[string]DirectiveInfo
|
||||
Operators map[string]expr.Operator
|
||||
context.SimpleContext
|
||||
context.LabelerBase
|
||||
LabelChars string
|
||||
LabelColons Requiredness
|
||||
ExplicitARegister Requiredness
|
||||
}
|
||||
|
||||
// Parse an entire instruction, or return an appropriate error.
|
||||
func (a *Base) 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{}, line.Errorf("line number must be exactly 4 digits: %s", s)
|
||||
}
|
||||
if !lp.Consume(" ") && lp.Peek() != lines.Eol {
|
||||
return inst.I{}, line.Errorf("line number (%s) followed by non-space", s)
|
||||
}
|
||||
i, err := strconv.ParseUint(s, 10, 16)
|
||||
if err != nil {
|
||||
return inst.I{}, line.Errorf("invalid line number: %s: %s", s, err)
|
||||
}
|
||||
in.DeclaredLine = uint16(i)
|
||||
}
|
||||
|
||||
// Comment by virtue of long whitespace prefix
|
||||
if strings.HasPrefix(lp.Rest(), comment_whitespace_prefix) {
|
||||
in.Type = inst.TypeNone
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// 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(a.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 *Base) DefaultOrigin() (uint16, error) {
|
||||
return 0x0800, nil
|
||||
}
|
||||
|
||||
func (a *Base) SetWidthsOnFirstPass() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ParseCmd parses the "command" part of an instruction: we expect to be
|
||||
// looking at a non-whitespace character.
|
||||
func (a *Base) 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{}, in.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{}, in.Errorf(`unknown command/instruction: "%s"`, in.Command)
|
||||
}
|
||||
|
||||
// ParseMacroCall parses a macro call. We expect to be looking at a the
|
||||
// first character of the macro name.
|
||||
func (a *Base) ParseMacroCall(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
in.Type = inst.TypeMacroCall
|
||||
if !lp.AcceptRun(macroNameChars) {
|
||||
c := lp.Next()
|
||||
return inst.I{}, in.Errorf("expecting macro name, found '%c' (%d)", c, c)
|
||||
}
|
||||
in.Command = lp.Emit()
|
||||
|
||||
lp.Consume(whitespace)
|
||||
|
||||
for {
|
||||
s, err := a.ParseMacroArg(in, 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 *Base) ParseMacroArg(in inst.I, lp *lines.Parse) (string, error) {
|
||||
if lp.Peek() == '"' {
|
||||
return a.ParseQuoted(in, 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 *Base) ParseQuoted(in inst.I, 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 "", in.Errorf("Expected closing quote; got %s", c)
|
||||
}
|
||||
|
||||
c := lp.Peek()
|
||||
if c != ',' && c != ' ' && c != lines.Eol && c != '\t' {
|
||||
return "", in.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 *Base) 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, in.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, in.Errorf("%s doesn't support any indirect modes", in.Command)
|
||||
}
|
||||
xy := '-'
|
||||
expr, err := a.ParseExpression(in, 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, in.Errorf(",Y unexpected inside parens")
|
||||
}
|
||||
xy = 'y'
|
||||
} else {
|
||||
return i, in.Errorf("X or Y expected after comma")
|
||||
}
|
||||
}
|
||||
comma2 := false
|
||||
if indirect {
|
||||
if !lp.Consume(")") {
|
||||
return i, in.Errorf("Expected closing paren")
|
||||
}
|
||||
comma2 = lp.Consume(",")
|
||||
if comma2 {
|
||||
if comma {
|
||||
return i, in.Errorf("Cannot have ,X or ,Y twice.")
|
||||
}
|
||||
if !lp.Consume("yY") {
|
||||
return i, in.Errorf("Only ,Y can follow parens.")
|
||||
}
|
||||
xy = 'y'
|
||||
}
|
||||
}
|
||||
|
||||
return common.DecodeOp(a, in, summary, indirect, xy)
|
||||
}
|
||||
|
||||
func (a *Base) ParseAddress(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
expr, err := a.ParseExpression(in, 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 *Base) 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{}, in.Errorf("%s expects delimeter, found '%s'", in.Command, delim)
|
||||
}
|
||||
lp.Ignore()
|
||||
lp.AcceptUntil(string(delim))
|
||||
delim2 := lp.Next()
|
||||
if delim != delim2 {
|
||||
return inst.I{}, in.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 *Base) ParseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
ex, err := a.ParseExpression(in, lp)
|
||||
if err != nil {
|
||||
return inst.I{}, err
|
||||
}
|
||||
in.Exprs = append(in.Exprs, ex)
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *Base) ParseData(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
for {
|
||||
ex, err := a.ParseExpression(in, lp)
|
||||
if err != nil {
|
||||
return inst.I{}, err
|
||||
}
|
||||
in.Exprs = append(in.Exprs, ex)
|
||||
if !lp.Consume(",") {
|
||||
break
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *Base) ParseDo(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
expr, err := a.ParseExpression(in, 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 *Base) ParseEquate(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
expr, err := a.ParseExpression(in, 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 *Base) ParseHexString(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
if !lp.AcceptRun(hexdigits) {
|
||||
return inst.I{}, in.Errorf("%s expects hex digits; got '%s'", in.Command, lp.Next())
|
||||
}
|
||||
hs := lp.Emit()
|
||||
if len(hs)%2 != 0 {
|
||||
return inst.I{}, in.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{}, in.Errorf("%s: error decoding hex string: %s", in.Command, err)
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (a *Base) ParseInclude(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
if !lp.AcceptRun(fileChars) {
|
||||
return inst.I{}, in.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 *Base) ParseMacroStart(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
if !lp.AcceptRun(macroNameChars) {
|
||||
return inst.I{}, in.Errorf("Expecting valid macro name, 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 *Base) 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 *Base) ParseNotImplemented(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
return inst.I{}, in.Errorf("not implemented (yet?): %s", in.Command)
|
||||
}
|
||||
|
||||
func (a *Base) ParseExpression(in inst.I, 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(in, lp)
|
||||
if err != nil {
|
||||
return &expr.E{}, err
|
||||
}
|
||||
|
||||
for lp.Accept(operatorChars) {
|
||||
c := lp.Emit()
|
||||
right, err := a.ParseTerm(in, 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 *Base) ParseTerm(in inst.I, 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{}, in.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{}, in.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{}, in.Errorf("invalid number: %s: %s", s, err)
|
||||
}
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Val = uint16(i)
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// Character
|
||||
if lp.Consume("'") {
|
||||
c := lp.Next()
|
||||
if c == lines.Eol {
|
||||
return &expr.E{}, in.Errorf("end of line after quote")
|
||||
}
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Val = uint16(c)
|
||||
lp.Consume("'") // optional closing quote
|
||||
lp.Ignore()
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// Label
|
||||
if !lp.AcceptRun(a.LabelChars) {
|
||||
c := lp.Next()
|
||||
return &expr.E{}, in.Errorf("expecting *, (hex) number, or label; found '%c' (%d)", c, c)
|
||||
}
|
||||
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Text = lp.Emit()
|
||||
return top, nil
|
||||
}
|
||||
|
||||
var macroArgRe = regexp.MustCompile("][0-9]+")
|
||||
|
||||
func (a *Base) ReplaceMacroArgs(line string, args []string, kwargs map[string]string) (string, error) {
|
||||
line = strings.Replace(line, "]#", strconv.Itoa(len(args)), -1)
|
||||
line = string(macroArgRe.ReplaceAllFunc([]byte(line), func(in []byte) []byte {
|
||||
n, _ := strconv.Atoi(string(in[1:]))
|
||||
if n > 0 && n <= len(args) {
|
||||
return []byte(args[n-1])
|
||||
}
|
||||
return []byte{}
|
||||
}))
|
||||
return line, nil
|
||||
}
|
||||
|
||||
func (a *Base) IsNewParentLabel(label string) bool {
|
||||
return label != "" && label[0] != '.'
|
||||
}
|
||||
|
||||
func (a *Base) FixLabel(label string, macroCall int) (string, error) {
|
||||
switch {
|
||||
case label == "":
|
||||
return label, nil
|
||||
case label[0] == '.':
|
||||
if last := a.LastLabel(); last == "" {
|
||||
return "", fmt.Errorf("sublabel '%s' without previous label", label)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%s", last, label), nil
|
||||
}
|
||||
case label[0] == ':':
|
||||
if macroCall == 0 {
|
||||
return "", fmt.Errorf("macro-local label '%s' seen outside macro", label)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%d", label, macroCall), nil
|
||||
}
|
||||
}
|
||||
return label, nil
|
||||
}
|
61
asm/flavors/redbook/redbook.go
Normal file
61
asm/flavors/redbook/redbook.go
Normal file
@ -0,0 +1,61 @@
|
||||
package redbook
|
||||
|
||||
import (
|
||||
"github.com/zellyn/go6502/asm/expr"
|
||||
"github.com/zellyn/go6502/asm/flavors/oldschool"
|
||||
"github.com/zellyn/go6502/asm/inst"
|
||||
)
|
||||
|
||||
// RedBook implements a Redbook-listing-compatible-ish assembler flavor.
|
||||
type RedBook struct {
|
||||
oldschool.Base
|
||||
}
|
||||
|
||||
func New() *RedBook {
|
||||
a := &RedBook{}
|
||||
|
||||
a.LabelChars = oldschool.Letters + oldschool.Digits + "."
|
||||
a.LabelColons = oldschool.ReqOptional
|
||||
a.ExplicitARegister = oldschool.ReqRequired
|
||||
|
||||
a.Directives = map[string]oldschool.DirectiveInfo{
|
||||
".IN": {inst.TypeInclude, a.ParseInclude},
|
||||
"ORG": {inst.TypeOrg, a.ParseAddress},
|
||||
"OBJ": {inst.TypeNone, nil},
|
||||
".TF": {inst.TypeNone, nil},
|
||||
".EN": {inst.TypeEnd, a.ParseNoArgDir},
|
||||
"EQU": {inst.TypeEqu, a.ParseEquate},
|
||||
".DA": {inst.TypeData, a.ParseData},
|
||||
"DFB": {inst.TypeDataBytes, a.ParseData},
|
||||
"DW": {inst.TypeDataWords, a.ParseData},
|
||||
".HS": {inst.TypeData, a.ParseHexString},
|
||||
".AS": {inst.TypeData, a.ParseAscii},
|
||||
".AT": {inst.TypeData, a.ParseAscii},
|
||||
".BS": {inst.TypeBlock, a.ParseBlockStorage},
|
||||
".LIST": {inst.TypeNone, nil},
|
||||
".PG": {inst.TypeNone, nil},
|
||||
".DO": {inst.TypeIfdef, a.ParseDo},
|
||||
".ELSE": {inst.TypeIfdefElse, a.ParseNoArgDir},
|
||||
".FIN": {inst.TypeIfdefEnd, a.ParseNoArgDir},
|
||||
".MA": {inst.TypeMacroStart, a.ParseMacroStart},
|
||||
".EM": {inst.TypeMacroEnd, a.ParseNoArgDir},
|
||||
".US": {inst.TypeNone, a.ParseNotImplemented},
|
||||
"PAGE": {inst.TypeNone, nil}, // New page
|
||||
"LST": {inst.TypeNone, nil}, // Listing on/off
|
||||
"SBTL": {inst.TypeNone, nil}, // Subtitle
|
||||
"SKP": {inst.TypeNone, nil}, // Skip lines
|
||||
"REP": {inst.TypeNone, nil}, // Repeat character
|
||||
"CHR": {inst.TypeNone, nil}, // Set repeated character
|
||||
}
|
||||
a.Operators = map[string]expr.Operator{
|
||||
"*": expr.OpMul,
|
||||
"/": expr.OpDiv,
|
||||
"+": expr.OpPlus,
|
||||
"-": expr.OpMinus,
|
||||
"<": expr.OpLt,
|
||||
">": expr.OpGt,
|
||||
"=": expr.OpEq,
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
@ -1,72 +1,46 @@
|
||||
package scma
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"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/flavors/oldschool"
|
||||
"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 macroNameChars = letters + digits + "._"
|
||||
const fileChars = letters + digits + "."
|
||||
const operatorChars = "+-*/<>="
|
||||
|
||||
// 40 spaces = comment column
|
||||
const comment_whitespace_prefix = " "
|
||||
|
||||
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
|
||||
context.LabelerBase
|
||||
oldschool.Base
|
||||
}
|
||||
|
||||
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},
|
||||
a := &SCMA{}
|
||||
a.LabelChars = oldschool.Letters + oldschool.Digits + ".:"
|
||||
a.LabelColons = oldschool.ReqDisallowed
|
||||
a.ExplicitARegister = oldschool.ReqDisallowed
|
||||
|
||||
a.Directives = map[string]oldschool.DirectiveInfo{
|
||||
".IN": {inst.TypeInclude, a.ParseInclude},
|
||||
".OR": {inst.TypeOrg, a.ParseAddress},
|
||||
".TA": {inst.TypeTarget, a.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},
|
||||
".EN": {inst.TypeEnd, a.ParseNoArgDir},
|
||||
".EQ": {inst.TypeEqu, a.ParseEquate},
|
||||
".DA": {inst.TypeData, a.ParseData},
|
||||
".HS": {inst.TypeData, a.ParseHexString},
|
||||
".AS": {inst.TypeData, a.ParseAscii},
|
||||
".AT": {inst.TypeData, a.ParseAscii},
|
||||
".BS": {inst.TypeBlock, a.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.parseMacroStart},
|
||||
".EM": {inst.TypeMacroEnd, s.parseNoArgDir},
|
||||
".US": {inst.TypeNone, s.parseNotImplemented},
|
||||
".DO": {inst.TypeIfdef, a.ParseDo},
|
||||
".ELSE": {inst.TypeIfdefElse, a.ParseNoArgDir},
|
||||
".FIN": {inst.TypeIfdefEnd, a.ParseNoArgDir},
|
||||
".MA": {inst.TypeMacroStart, a.ParseMacroStart},
|
||||
".EM": {inst.TypeMacroEnd, a.ParseNoArgDir},
|
||||
".US": {inst.TypeNone, a.ParseNotImplemented},
|
||||
}
|
||||
s.operators = map[string]expr.Operator{
|
||||
a.Operators = map[string]expr.Operator{
|
||||
"*": expr.OpMul,
|
||||
"/": expr.OpDiv,
|
||||
"+": expr.OpPlus,
|
||||
@ -76,523 +50,9 @@ func New() *SCMA {
|
||||
"=": 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{}, line.Errorf("line number must be exactly 4 digits: %s", s)
|
||||
}
|
||||
if !lp.Consume(" ") && lp.Peek() != lines.Eol {
|
||||
return inst.I{}, line.Errorf("line number (%s) followed by non-space", s)
|
||||
}
|
||||
i, err := strconv.ParseUint(s, 10, 16)
|
||||
if err != nil {
|
||||
return inst.I{}, line.Errorf("invalid line number: %s: %s", s, err)
|
||||
}
|
||||
in.DeclaredLine = uint16(i)
|
||||
}
|
||||
|
||||
// Comment by virtue of long whitespace prefix
|
||||
if strings.HasPrefix(lp.Rest(), comment_whitespace_prefix) {
|
||||
in.Type = inst.TypeNone
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *SCMA) Zero() (uint16, error) {
|
||||
return uint16(0xffff), nil
|
||||
}
|
||||
|
||||
func (a *SCMA) DefaultOrigin() (uint16, error) {
|
||||
return 0x0800, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) SetWidthsOnFirstPass() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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{}, in.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{}, in.Errorf(`unknown command/instruction: "%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(macroNameChars) {
|
||||
c := lp.Next()
|
||||
return inst.I{}, in.Errorf("expecting macro name, found '%c' (%d)", c, c)
|
||||
}
|
||||
in.Command = lp.Emit()
|
||||
|
||||
lp.Consume(whitespace)
|
||||
|
||||
for {
|
||||
s, err := a.parseMacroArg(in, 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(in inst.I, lp *lines.Parse) (string, error) {
|
||||
if lp.Peek() == '"' {
|
||||
return a.parseQuoted(in, 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(in inst.I, 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 "", in.Errorf("Expected closing quote; got %s", c)
|
||||
}
|
||||
|
||||
c := lp.Peek()
|
||||
if c != ',' && c != ' ' && c != lines.Eol && c != '\t' {
|
||||
return "", in.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, in.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, in.Errorf("%s doesn't support any indirect modes", in.Command)
|
||||
}
|
||||
xy := '-'
|
||||
expr, err := a.parseExpression(in, 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, in.Errorf(",Y unexpected inside parens")
|
||||
}
|
||||
xy = 'y'
|
||||
} else {
|
||||
return i, in.Errorf("X or Y expected after comma")
|
||||
}
|
||||
}
|
||||
comma2 := false
|
||||
if indirect {
|
||||
if !lp.Consume(")") {
|
||||
return i, in.Errorf("Expected closing paren")
|
||||
}
|
||||
comma2 = lp.Consume(",")
|
||||
if comma2 {
|
||||
if comma {
|
||||
return i, in.Errorf("Cannot have ,X or ,Y twice.")
|
||||
}
|
||||
if !lp.Consume("yY") {
|
||||
return i, in.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(in, 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{}, in.Errorf("%s expects delimeter, found '%s'", in.Command, delim)
|
||||
}
|
||||
lp.Ignore()
|
||||
lp.AcceptUntil(string(delim))
|
||||
delim2 := lp.Next()
|
||||
if delim != delim2 {
|
||||
return inst.I{}, in.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(in, 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(in, 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(in, 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(in, 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{}, in.Errorf("%s expects hex digits; got '%s'", in.Command, lp.Next())
|
||||
}
|
||||
hs := lp.Emit()
|
||||
if len(hs)%2 != 0 {
|
||||
return inst.I{}, in.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{}, in.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{}, in.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) parseMacroStart(in inst.I, lp *lines.Parse) (inst.I, error) {
|
||||
lp.IgnoreRun(whitespace)
|
||||
if !lp.AcceptRun(macroNameChars) {
|
||||
return inst.I{}, in.Errorf("Expecting valid macro name, 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{}, in.Errorf("not implemented (yet?): %s", in.Command)
|
||||
}
|
||||
|
||||
func (a *SCMA) parseExpression(in inst.I, 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(in, lp)
|
||||
if err != nil {
|
||||
return &expr.E{}, err
|
||||
}
|
||||
|
||||
for lp.Accept(operatorChars) {
|
||||
c := lp.Emit()
|
||||
right, err := a.parseTerm(in, 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(in inst.I, 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{}, in.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{}, in.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{}, in.Errorf("invalid number: %s: %s", s, err)
|
||||
}
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Val = uint16(i)
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// Character
|
||||
if lp.Consume("'") {
|
||||
c := lp.Next()
|
||||
if c == lines.Eol {
|
||||
return &expr.E{}, in.Errorf("end of line after quote")
|
||||
}
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Val = uint16(c)
|
||||
lp.Consume("'") // optional closing quote
|
||||
lp.Ignore()
|
||||
return top, nil
|
||||
}
|
||||
|
||||
// Label
|
||||
if !lp.AcceptRun(labelChars) {
|
||||
c := lp.Next()
|
||||
return &expr.E{}, in.Errorf("expecting *, (hex) number, or label; found '%c' (%d)", c, c)
|
||||
}
|
||||
|
||||
ex.Op = expr.OpLeaf
|
||||
ex.Text = lp.Emit()
|
||||
return top, nil
|
||||
}
|
||||
|
||||
var macroArgRe = regexp.MustCompile("][0-9]+")
|
||||
|
||||
func (a *SCMA) ReplaceMacroArgs(line string, args []string, kwargs map[string]string) (string, error) {
|
||||
line = strings.Replace(line, "]#", strconv.Itoa(len(args)), -1)
|
||||
line = string(macroArgRe.ReplaceAllFunc([]byte(line), func(in []byte) []byte {
|
||||
n, _ := strconv.Atoi(string(in[1:]))
|
||||
if n > 0 && n <= len(args) {
|
||||
return []byte(args[n-1])
|
||||
}
|
||||
return []byte{}
|
||||
}))
|
||||
return line, nil
|
||||
}
|
||||
|
||||
func (a *SCMA) IsNewParentLabel(label string) bool {
|
||||
return label != "" && label[0] != '.'
|
||||
}
|
||||
|
||||
func (a *SCMA) FixLabel(label string, macroCall int) (string, error) {
|
||||
switch {
|
||||
case label == "":
|
||||
return label, nil
|
||||
case label[0] == '.':
|
||||
if last := a.LastLabel(); last == "" {
|
||||
return "", fmt.Errorf("sublabel '%s' without previous label", label)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%s", last, label), nil
|
||||
}
|
||||
case label[0] == ':':
|
||||
if macroCall == 0 {
|
||||
return "", fmt.Errorf("macro-local label '%s' seen outside macro", label)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%d", label, macroCall), nil
|
||||
}
|
||||
}
|
||||
return label, nil
|
||||
}
|
||||
|
@ -7,12 +7,14 @@ import (
|
||||
"github.com/zellyn/go6502/asm/flavors"
|
||||
"github.com/zellyn/go6502/asm/flavors/as65"
|
||||
"github.com/zellyn/go6502/asm/flavors/merlin"
|
||||
"github.com/zellyn/go6502/asm/flavors/redbook"
|
||||
"github.com/zellyn/go6502/asm/flavors/scma"
|
||||
"github.com/zellyn/go6502/asm/lines"
|
||||
)
|
||||
|
||||
func TestSimpleCommonFunctions(t *testing.T) {
|
||||
ss := scma.New()
|
||||
rb := redbook.New()
|
||||
aa := as65.New()
|
||||
mm := merlin.New()
|
||||
|
||||
@ -23,11 +25,13 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
b string // bytes, expected
|
||||
}{
|
||||
{ss, "* Comment", "{-}", ""},
|
||||
{ss, "* Comment", "{-}", ""},
|
||||
{rb, "* Comment", "{-}", ""},
|
||||
{aa, "; Comment", "{-}", ""},
|
||||
{mm, "* Comment", "{-}", ""},
|
||||
{ss, " far-out-comment", "{-}", ""},
|
||||
{ss, "Label", "{- 'Label'}", ""},
|
||||
{rb, "Label", "{- 'Label'}", ""},
|
||||
{rb, "Label:", "{- 'Label'}", ""},
|
||||
{aa, "Label", "{- 'Label'}", ""},
|
||||
{mm, "Label", "{- 'Label'}", ""},
|
||||
{ss, " .IN FILE.NAME", "{inc 'FILE.NAME'}", ""},
|
||||
@ -35,12 +39,14 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
{aa, ` include "FILE.NAME"`, "{inc 'FILE.NAME'}", ""},
|
||||
{mm, " PUT !FILE.NAME", "{inc 'FILE.NAME'}", ""},
|
||||
{ss, " .TI 76,Title here", "{-}", ""},
|
||||
{rb, ` SBTL 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}", ""},
|
||||
{rb, " ORG $D000", "{org $d000}", ""},
|
||||
{aa, " org $D000", "{org $d000}", ""},
|
||||
{mm, " ORG $D000", "{org $d000}", ""},
|
||||
// {ss, " .TA *-1234", "{target (- * $04d2)}", ""},
|
||||
@ -50,6 +56,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
{ss, " .DA/$1234,#$1234,$1234", "{data (msb $1234),(lsb $1234),$1234}", "12343412"},
|
||||
{ss, " ROL", "{ROL/a}", "2a"},
|
||||
{aa, " rol a", "{ROL/a}", "2a"},
|
||||
{rb, " 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"},
|
||||
@ -131,7 +138,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
|
||||
|
||||
for i, tt := range tests {
|
||||
// TODO(zellyn): Test AS65 and Merlin too.
|
||||
if tt.a != ss {
|
||||
if tt.a != ss && tt.a != rb {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,8 @@ const (
|
||||
TypeIfdefEnd // Ifdef end
|
||||
TypeInclude // Include a file
|
||||
TypeData // Data: hex, ascii, etc., etc.
|
||||
TypeDataBytes // Data: expressions, but forced to one byte per
|
||||
TypeDataWords // Data: expressions, but forced to one word per
|
||||
TypeBlock // Block storage
|
||||
TypeOrg // Where to store assembled code
|
||||
TypeTarget // Target address to use for jumps, labels, etc.
|
||||
|
@ -57,6 +57,9 @@ func (lp *Parse) Peek() rune {
|
||||
}
|
||||
|
||||
func (lp *Parse) Accept(valid string) bool {
|
||||
if len(valid) == 0 {
|
||||
panic(`valid = ""`)
|
||||
}
|
||||
if strings.IndexRune(valid, lp.Next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
@ -66,6 +69,9 @@ func (lp *Parse) Accept(valid string) bool {
|
||||
|
||||
// Consume accepts and ignores a single character of input.
|
||||
func (lp *Parse) Consume(valid string) bool {
|
||||
if len(valid) == 0 {
|
||||
panic(`valid = ""`)
|
||||
}
|
||||
if strings.IndexRune(valid, lp.Next()) >= 0 {
|
||||
lp.start = lp.pos
|
||||
return true
|
||||
@ -75,6 +81,9 @@ func (lp *Parse) Consume(valid string) bool {
|
||||
}
|
||||
|
||||
func (lp *Parse) AcceptRun(valid string) bool {
|
||||
if len(valid) == 0 {
|
||||
panic(`valid = ""`)
|
||||
}
|
||||
some := false
|
||||
for strings.IndexRune(valid, lp.Next()) >= 0 {
|
||||
some = true
|
||||
@ -94,6 +103,9 @@ func (lp *Parse) AcceptUntil(until string) bool {
|
||||
}
|
||||
|
||||
func (lp *Parse) IgnoreRun(valid string) bool {
|
||||
if len(valid) == 0 {
|
||||
panic(`valid = ""`)
|
||||
}
|
||||
if lp.AcceptRun(valid) {
|
||||
lp.Ignore()
|
||||
return true
|
||||
@ -102,6 +114,9 @@ func (lp *Parse) IgnoreRun(valid string) bool {
|
||||
}
|
||||
|
||||
func (lp *Parse) AcceptString(prefix string) bool {
|
||||
if len(prefix) == 0 {
|
||||
panic(`prefix = ""`)
|
||||
}
|
||||
if strings.HasPrefix(lp.line[lp.pos:], prefix) {
|
||||
lp.pos += len(prefix)
|
||||
return true
|
||||
|
Loading…
x
Reference in New Issue
Block a user