mirror of https://github.com/zellyn/go6502.git
890 lines
22 KiB
Go
890 lines
22 KiB
Go
package common
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/zellyn/go6502/asm/context"
|
|
"github.com/zellyn/go6502/asm/expr"
|
|
"github.com/zellyn/go6502/asm/flavors"
|
|
"github.com/zellyn/go6502/asm/inst"
|
|
"github.com/zellyn/go6502/asm/lines"
|
|
"github.com/zellyn/go6502/asm/opcodes"
|
|
)
|
|
|
|
const Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
|
|
const Digits = "0123456789"
|
|
const binarydigits = "01"
|
|
const hexdigits = Digits + "abcdefABCDEF"
|
|
const Whitespace = " \t"
|
|
const cmdChars = Letters + Digits + ".<>_:@"
|
|
const fileChars = Letters + Digits + "."
|
|
|
|
type DirectiveInfo struct {
|
|
Type inst.Type
|
|
Func func(context.Context, inst.I, *lines.Parse) (inst.I, error)
|
|
Var inst.Variant
|
|
}
|
|
|
|
type Requiredness int
|
|
|
|
const (
|
|
ReqDisallowed Requiredness = iota
|
|
ReqOptional
|
|
ReqRequired
|
|
)
|
|
|
|
// Base implements the S-C Macro Assembler-compatible assembler flavor.
|
|
// See http://www.txbobsc.com/scsc/ and http://stjarnhimlen.se/apple2/
|
|
type Base struct {
|
|
Name string
|
|
Directives map[string]DirectiveInfo
|
|
OpcodesByName map[string]opcodes.OpSummary
|
|
Operators map[string]expr.Operator
|
|
EquateDirectives map[string]bool
|
|
LabelChars string
|
|
LabelColons Requiredness
|
|
ExplicitARegister Requiredness
|
|
HexCommas Requiredness
|
|
SpacesForComment int // this many spaces after command means it's the comment field
|
|
StringEndOptional bool // can omit closing delimeter from string args?
|
|
SuffixForWide bool // is eg. "LDA:" a force-wide on "LDA"? (Merlin)
|
|
CommentChar rune
|
|
BinaryChar rune
|
|
MsbChars string
|
|
LsbChars string
|
|
ImmediateChars string
|
|
operatorChars string
|
|
CharChars string
|
|
InvCharChars string
|
|
MacroArgSep string
|
|
ExtraCommenty func(string) bool
|
|
SetAsciiVariation func(context.Context, *inst.I, *lines.Parse)
|
|
ParseMacroCall func(context.Context, inst.I, *lines.Parse) (inst.I, bool, error)
|
|
IsNewParentLabel func(label string) bool
|
|
InitContextFunc func(context.Context)
|
|
FixLabel func(context.Context, string) (string, error)
|
|
LocalMacroLabelsVal bool
|
|
DivZeroVal *uint16
|
|
DefaultOriginVal uint16
|
|
}
|
|
|
|
func (a *Base) String() string {
|
|
return a.Name
|
|
}
|
|
|
|
// Parse an entire instruction, or return an appropriate error.
|
|
func (a *Base) ParseInstr(ctx context.Context, line lines.Line, mode flavors.ParseMode) (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 in, line.Errorf("line number must be exactly 4 digits: %s", s)
|
|
}
|
|
if !lp.Consume(" ") && lp.Peek() != lines.Eol {
|
|
return in, line.Errorf("line number (%s) followed by non-space", s)
|
|
}
|
|
i, err := strconv.ParseUint(s, 10, 16)
|
|
if err != nil {
|
|
return in, line.Errorf("invalid line number: %s: %s", s, err)
|
|
}
|
|
in.DeclaredLine = uint16(i)
|
|
}
|
|
|
|
// Flavor considers this line extra commenty for some reason?
|
|
if a.ExtraCommenty != nil && a.ExtraCommenty(lp.Rest()) {
|
|
in.Type = inst.TypeNone
|
|
in.Final = true
|
|
in.Width = 0
|
|
return in, nil
|
|
}
|
|
|
|
// Empty line or comment
|
|
trimmed := strings.TrimSpace(lp.Rest())
|
|
if trimmed == "" || trimmed[0] == '*' || rune(trimmed[0]) == a.CommentChar {
|
|
in.Type = inst.TypeNone
|
|
in.Final = true
|
|
in.Width = 0
|
|
return in, nil
|
|
}
|
|
|
|
// See if we have a label at the start
|
|
if lp.AcceptRun(a.LabelChars) {
|
|
in.Label = lp.Emit()
|
|
|
|
// Some need colons after labels, some allow them.
|
|
switch a.LabelColons {
|
|
case ReqRequired:
|
|
if !lp.Consume(":") {
|
|
return in, line.Errorf("label '%s' must end in colon", in.Label)
|
|
}
|
|
case ReqOptional:
|
|
lp.Consume(":")
|
|
}
|
|
}
|
|
|
|
// Ignore whitespace at the start or after the label.
|
|
lp.IgnoreRun(Whitespace)
|
|
|
|
if lp.Peek() == lines.Eol || lp.Peek() == a.CommentChar {
|
|
in.Type = inst.TypeNone
|
|
if mode == flavors.ParseModeNormal {
|
|
if err := a.handleLabel(ctx, in); err != nil {
|
|
return in, err
|
|
}
|
|
}
|
|
in.Final = true
|
|
in.Width = 0
|
|
return in, nil
|
|
}
|
|
return a.parseCmd(ctx, in, lp, mode)
|
|
}
|
|
|
|
func (a *Base) handleLabel(ctx context.Context, in inst.I) error {
|
|
if in.Label == "" {
|
|
return nil
|
|
}
|
|
addr := ctx.GetAddr()
|
|
|
|
// Munge for macros, relative labels, etc. If appropriate,
|
|
// set the last parent label.
|
|
parent := a.IsNewParentLabel(in.Label)
|
|
newL, err := a.FixLabel(ctx, in.Label)
|
|
if err != nil {
|
|
return in.Errorf("%v", err)
|
|
}
|
|
in.Label = newL
|
|
if parent {
|
|
ctx.SetLastLabel(in.Label)
|
|
}
|
|
|
|
lval, lok := ctx.Get(in.Label)
|
|
if lok && int64(addr) != lval {
|
|
return in.Errorf("Trying to set label '%s' to $%04x, but it already has value $%04x", in.Label, addr, lval)
|
|
}
|
|
ctx.Set(in.Label, int64(addr))
|
|
return nil
|
|
}
|
|
|
|
// parseCmd parses the "command" part of an instruction: we expect to be
|
|
// looking at a non-whitespace character.
|
|
func (a *Base) parseCmd(ctx context.Context, in inst.I, lp *lines.Parse, mode flavors.ParseMode) (inst.I, error) {
|
|
if !lp.AcceptRun(cmdChars) && !(a.Directives["="].Func != nil && lp.Accept("=")) {
|
|
c := lp.Next()
|
|
return in, in.Errorf("expecting instruction, found '%c' (%d)", c, c)
|
|
}
|
|
in.Command = lp.Emit()
|
|
|
|
if mode == flavors.ParseModeMacroSave {
|
|
// all we care about is labels (already covered) and end-of-macro.
|
|
if dir, ok := a.Directives[in.Command]; ok {
|
|
in.Type = dir.Type
|
|
}
|
|
return in, nil
|
|
}
|
|
|
|
if mode == flavors.ParseModeInactive {
|
|
// all we care about are endif and else.
|
|
if dir, ok := a.Directives[in.Command]; ok {
|
|
in.Type = dir.Type
|
|
if dir.Type == inst.TypeIfdefElse || dir.Type == inst.TypeIfdefEnd {
|
|
// It's weird, but handle labels on else/endif lines.
|
|
if err := a.handleLabel(ctx, in); err != nil {
|
|
return in, err
|
|
}
|
|
}
|
|
}
|
|
return in, nil
|
|
}
|
|
|
|
// We don't need to handle labels if it's an equate.
|
|
if !a.EquateDirectives[in.Command] {
|
|
if err := a.handleLabel(ctx, in); err != nil {
|
|
return in, err
|
|
}
|
|
}
|
|
|
|
// Give ParseMacroCall a chance
|
|
if a.ParseMacroCall != nil {
|
|
i, isMacro, err := a.ParseMacroCall(ctx, in, lp)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
if isMacro {
|
|
i.Width = 0
|
|
i.Final = true
|
|
return i, nil
|
|
}
|
|
}
|
|
|
|
if dir, ok := a.Directives[in.Command]; ok {
|
|
in.Type = dir.Type
|
|
in.Var = dir.Var
|
|
if dir.Func == nil {
|
|
in.Width = 0
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
return dir.Func(ctx, in, lp)
|
|
}
|
|
|
|
if ctx.HasSetting(in.Command) {
|
|
return a.parseSetting(ctx, in, lp)
|
|
}
|
|
|
|
upperCmd := strings.ToUpper(in.Command)
|
|
if summary, ok := a.OpcodesByName[upperCmd]; ok {
|
|
in.Type = inst.TypeOp
|
|
return a.parseOpArgs(ctx, in, lp, summary, false)
|
|
}
|
|
|
|
// Merlin lets you say "LDA:" or "LDA@" or "LDAZ" to force non-zero-page.
|
|
if a.SuffixForWide && len(in.Command) == 4 {
|
|
prefix := in.Command[:len(in.Command)-1]
|
|
if summary, ok := a.OpcodesByName[prefix]; ok {
|
|
in.Command = prefix
|
|
in.Type = inst.TypeOp
|
|
return a.parseOpArgs(ctx, in, lp, summary, true)
|
|
}
|
|
}
|
|
|
|
return in, in.Errorf(`unknown command/instruction: "%s"`, in.Command)
|
|
}
|
|
|
|
func (a *Base) parseSetting(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
in.Type = inst.TypeSetting
|
|
lp.IgnoreRun(Whitespace)
|
|
if !lp.AcceptRun(Letters) {
|
|
c := lp.Next()
|
|
return in, in.Errorf("expecting ON/OFF, found '%s'", c)
|
|
}
|
|
in.TextArg = lp.Emit()
|
|
switch in.TextArg {
|
|
case "ON":
|
|
ctx.SettingOn(in.Command)
|
|
case "OFF":
|
|
ctx.SettingOff(in.Command)
|
|
default:
|
|
return in, in.Errorf("expecting ON/OFF, found '%s'", in.TextArg)
|
|
}
|
|
in.Width = 0
|
|
in.Final = true
|
|
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 + a.MacroArgSep)
|
|
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(ctx context.Context, in inst.I, lp *lines.Parse, summary opcodes.OpSummary, forceWide bool) (inst.I, error) {
|
|
|
|
// 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.Width = 1
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
// Nothing else on the line? Must be MODE_A
|
|
lp.AcceptRun(Whitespace)
|
|
ws := lp.Emit()
|
|
atEnd := false
|
|
if a.SpacesForComment != 0 && len(ws) >= a.SpacesForComment {
|
|
atEnd = true
|
|
}
|
|
if lp.Peek() == lines.Eol || lp.Peek() == a.CommentChar {
|
|
atEnd = true
|
|
}
|
|
|
|
if atEnd {
|
|
// Nothing left on line except comments.
|
|
if !summary.AnyModes(opcodes.MODE_A) {
|
|
return in, 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.Width = 1
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
indirect := lp.Consume("(")
|
|
if indirect && !summary.AnyModes(opcodes.MODE_INDIRECT_ANY) {
|
|
return in, in.Errorf("%s doesn't support any indirect modes", in.Command)
|
|
}
|
|
xy := '-'
|
|
expr, err := a.parseExpression(ctx, in, lp)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
if !indirect && (expr.Text == "a" || expr.Text == "A") {
|
|
if !summary.AnyModes(opcodes.MODE_A) {
|
|
return in, in.Errorf("%s doesn't support A mode", in.Command)
|
|
}
|
|
switch a.ExplicitARegister {
|
|
case ReqDisallowed:
|
|
return in, in.Errorf("Assembler flavor doesn't support A mode", in.Command)
|
|
case ReqOptional, ReqRequired:
|
|
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.Width = 1
|
|
in.Final = true
|
|
in.Exprs = nil
|
|
return in, nil
|
|
|
|
}
|
|
}
|
|
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 in, in.Errorf(",Y unexpected inside parens")
|
|
}
|
|
xy = 'y'
|
|
} else {
|
|
return in, in.Errorf("X or Y expected after comma")
|
|
}
|
|
}
|
|
comma2 := false
|
|
if indirect {
|
|
if !lp.Consume(")") {
|
|
return in, in.Errorf("Expected closing paren")
|
|
}
|
|
comma2 = lp.Consume(",")
|
|
if comma2 {
|
|
if comma {
|
|
return in, in.Errorf("Cannot have ,X or ,Y twice.")
|
|
}
|
|
if !lp.Consume("yY") {
|
|
return in, in.Errorf("Only ,Y can follow parens.")
|
|
}
|
|
xy = 'y'
|
|
}
|
|
}
|
|
|
|
return DecodeOp(ctx, in, summary, indirect, xy, forceWide)
|
|
}
|
|
|
|
func (a *Base) ParseOrg(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.IgnoreRun(Whitespace)
|
|
expr, err := a.parseExpression(ctx, in, lp)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
in.Exprs = append(in.Exprs, expr)
|
|
val, err := expr.Eval(ctx, in.Line)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
in.Width = 0
|
|
in.Final = true
|
|
in.Addr = uint16(val)
|
|
return in, nil
|
|
}
|
|
|
|
// ParseAscii parses data definition instructions that have some
|
|
// variant of ASCII as their data.
|
|
func (a *Base) ParseAscii(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.IgnoreRun(Whitespace)
|
|
a.SetAsciiVariation(ctx, &in, lp)
|
|
var invert, invertLast byte
|
|
switch in.Var {
|
|
case inst.VarAscii:
|
|
case inst.VarAsciiHi:
|
|
invert = 0x80
|
|
case inst.VarAsciiFlip:
|
|
invertLast = 0x80
|
|
case inst.VarAsciiHiFlip:
|
|
invert = 0x80
|
|
invertLast = 0x80
|
|
default:
|
|
panic(fmt.Sprintf("ParseAscii with weird Variation: %d", in.Var))
|
|
}
|
|
delim := lp.Next()
|
|
if delim == lines.Eol || strings.IndexRune(Whitespace, delim) >= 0 {
|
|
return in, in.Errorf("%s expects delimeter, found '%s'", in.Command, delim)
|
|
}
|
|
lp.Ignore()
|
|
lp.AcceptUntil(string(delim))
|
|
delim2 := lp.Next()
|
|
if delim != delim2 && !(delim2 == lines.Eol && a.StringEndOptional) {
|
|
return in, 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
|
|
}
|
|
}
|
|
in.Width = uint16(len(in.Data))
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
func (a *Base) ParseBlockStorage(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.IgnoreRun(Whitespace)
|
|
ex, err := a.parseExpression(ctx, in, lp)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
in.Exprs = append(in.Exprs, ex)
|
|
val, err := ex.Eval(ctx, in.Line)
|
|
if err != nil {
|
|
return in, in.Errorf("Cannot evaluate size of block storage on first pass")
|
|
}
|
|
|
|
in.Final = true
|
|
in.Width = uint16(val)
|
|
return in, nil
|
|
}
|
|
|
|
func (a *Base) ParseData(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.IgnoreRun(Whitespace)
|
|
for {
|
|
ex, err := a.parseExpression(ctx, in, lp)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
in.Exprs = append(in.Exprs, ex)
|
|
if !lp.Consume(",") {
|
|
break
|
|
}
|
|
}
|
|
switch in.Var {
|
|
case inst.VarBytes:
|
|
in.Width = uint16(len(in.Exprs))
|
|
in.Final = true
|
|
for _, expr := range in.Exprs {
|
|
val, err := expr.Eval(ctx, in.Line)
|
|
if err != nil {
|
|
in.Final = false
|
|
in.Data = nil
|
|
break
|
|
}
|
|
in.Data = append(in.Data, byte(val))
|
|
}
|
|
case inst.VarWordsLe, inst.VarWordsBe:
|
|
in.Width = 2 * uint16(len(in.Exprs))
|
|
in.Final = true
|
|
for _, expr := range in.Exprs {
|
|
val, err := expr.Eval(ctx, in.Line)
|
|
if err != nil {
|
|
in.Final = false
|
|
in.Data = nil
|
|
break
|
|
}
|
|
if in.Var == inst.VarWordsLe {
|
|
in.Data = append(in.Data, byte(val), byte(val>>8))
|
|
} else {
|
|
in.Data = append(in.Data, byte(val>>8), byte(val))
|
|
}
|
|
}
|
|
case inst.VarMixed:
|
|
in.Final = true
|
|
for _, expr := range in.Exprs {
|
|
in.Width += expr.Width()
|
|
val, err := expr.Eval(ctx, in.Line)
|
|
if err != nil {
|
|
in.Final = false
|
|
in.Data = nil
|
|
} else {
|
|
if in.Final {
|
|
switch expr.Width() {
|
|
case 1:
|
|
in.Data = append(in.Data, byte(val))
|
|
case 2:
|
|
in.Data = append(in.Data, byte(val), byte(val>>8))
|
|
default:
|
|
return in, in.Errorf("Unsupported expression width: %d", expr.Width())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case inst.VarBytesZero:
|
|
in.Final = true
|
|
if len(in.Exprs) != 1 {
|
|
return in, in.Errorf("%v expected one expression (length); got %d", in.Command, len(in.Exprs))
|
|
}
|
|
val, err := in.Exprs[0].Eval(ctx, in.Line)
|
|
if err != nil {
|
|
in.Final = false
|
|
in.Data = nil
|
|
} else {
|
|
in.Final = true
|
|
in.Width = uint16(val)
|
|
for i := int(val); i > 0; i-- {
|
|
in.Data = append(in.Data, 0)
|
|
}
|
|
}
|
|
default:
|
|
return in, in.Errorf("Unknown Var(%d) with ParseData for %s", in.Var, in.Command)
|
|
}
|
|
return in, nil
|
|
}
|
|
|
|
func (a *Base) ParseDo(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.IgnoreRun(Whitespace)
|
|
expr, err := a.parseExpression(ctx, in, lp)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
in.Exprs = append(in.Exprs, expr)
|
|
in.Width = 0
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
func (a *Base) ParseEquate(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.IgnoreRun(Whitespace)
|
|
expr, err := a.parseExpression(ctx, in, lp)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
|
|
val, err := expr.Eval(ctx, in.Line)
|
|
if err != nil {
|
|
return in, err
|
|
}
|
|
ctx.Set(in.Label, val)
|
|
in.Exprs = append(in.Exprs, expr)
|
|
in.Width = 0
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
func (a *Base) ParseHexString(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.AcceptRun(Whitespace)
|
|
for {
|
|
lp.Ignore()
|
|
if !lp.AcceptRun(hexdigits) {
|
|
return in, in.Errorf("%s expects hex digits; got '%s'", in.Command, lp.Next())
|
|
}
|
|
hs := lp.Emit()
|
|
if len(hs)%2 != 0 {
|
|
return in, in.Errorf("%s expects pairs of hex digits; got %d", in.Command, len(hs))
|
|
}
|
|
data, err := hex.DecodeString(hs)
|
|
if err != nil {
|
|
return in, in.Errorf("%s: error decoding hex string: %s", in.Command, err)
|
|
}
|
|
in.Data = append(in.Data, data...)
|
|
|
|
// Keep going if we allow commas and have one
|
|
if a.HexCommas == ReqDisallowed || !lp.Accept(",") {
|
|
break
|
|
}
|
|
}
|
|
in.Width = uint16(len(in.Data))
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
func (a *Base) ParseInclude(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.IgnoreRun(Whitespace)
|
|
if !lp.AcceptRun(fileChars) {
|
|
return in, in.Errorf("Expecting filename, found '%c'", lp.Next())
|
|
}
|
|
in.TextArg = lp.Emit()
|
|
in.Width = 0
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
// For assemblers where the macro name follows the macro directive.
|
|
func (a *Base) ParseMacroStart(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
lp.IgnoreRun(Whitespace)
|
|
if !lp.AcceptRun(cmdChars) {
|
|
return in, in.Errorf("Expecting valid macro name, found '%c'", lp.Next())
|
|
}
|
|
in.TextArg = lp.Emit()
|
|
in.Width = 0
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
// For assemblers where the macro name is the label, followed by the directive.
|
|
func (a *Base) MarkMacroStart(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
in.TextArg, in.Label = in.Label, ""
|
|
in.Width = 0
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
func (a *Base) ParseNoArgDir(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
in.Width = 0
|
|
in.Final = true
|
|
return in, nil
|
|
}
|
|
|
|
func (a *Base) ParseNotImplemented(ctx context.Context, in inst.I, lp *lines.Parse) (inst.I, error) {
|
|
return in, in.Errorf("not implemented (yet?): %s", in.Command)
|
|
}
|
|
|
|
func (a *Base) parseExpression(ctx context.Context, in inst.I, lp *lines.Parse) (*expr.E, error) {
|
|
|
|
if a.operatorChars == "" {
|
|
for k, _ := range a.Operators {
|
|
a.operatorChars += k
|
|
}
|
|
}
|
|
|
|
var outer *expr.E
|
|
if lp.AcceptRun(a.MsbChars + a.LsbChars + a.ImmediateChars) {
|
|
pc := lp.Emit()
|
|
switch len(pc) {
|
|
case 1:
|
|
switch {
|
|
case strings.Contains(a.MsbChars, pc[:1]):
|
|
outer = &expr.E{Op: expr.OpMsb}
|
|
case strings.Contains(a.LsbChars+a.ImmediateChars, pc[:1]):
|
|
outer = &expr.E{Op: expr.OpLsb}
|
|
}
|
|
case 2:
|
|
err := in.Errorf("Got strange number prefix: '%s'", pc)
|
|
switch {
|
|
case !strings.Contains(a.ImmediateChars, pc[:1]):
|
|
return &expr.E{}, err
|
|
case strings.Contains(a.MsbChars, pc[1:]):
|
|
outer = &expr.E{Op: expr.OpMsb}
|
|
case strings.Contains(a.LsbChars, pc[1:]):
|
|
outer = &expr.E{Op: expr.OpLsb}
|
|
default:
|
|
return &expr.E{}, err
|
|
}
|
|
default:
|
|
return &expr.E{}, in.Errorf("Expected one or two number prefixes, got '%s'", pc)
|
|
}
|
|
}
|
|
|
|
tree, err := a.ParseTerm(ctx, in, lp)
|
|
if err != nil {
|
|
return &expr.E{}, err
|
|
}
|
|
|
|
for lp.Accept(a.operatorChars) {
|
|
c := lp.Emit()
|
|
right, err := a.ParseTerm(ctx, 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(ctx context.Context, 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.ParseInt(s, 16, 64)
|
|
if err != nil {
|
|
return &expr.E{}, in.Errorf("invalid hex number: %s: %s", s, err)
|
|
}
|
|
ex.Op = expr.OpLeaf
|
|
ex.Val = i
|
|
return top, nil
|
|
}
|
|
|
|
// Binary
|
|
if lp.Consume(string(a.BinaryChar)) {
|
|
if !lp.AcceptRun(binarydigits) {
|
|
c := lp.Next()
|
|
return &expr.E{}, in.Errorf("expecting binary number, found '%c' (%d)", c, c)
|
|
}
|
|
s := lp.Emit()
|
|
i, err := strconv.ParseInt(s, 2, 64)
|
|
if err != nil {
|
|
return &expr.E{}, in.Errorf("invalid binary number: %s: %s", s, err)
|
|
}
|
|
ex.Op = expr.OpLeaf
|
|
ex.Val = i
|
|
return top, nil
|
|
}
|
|
|
|
// Decimal
|
|
if lp.AcceptRun(Digits) {
|
|
s := lp.Emit()
|
|
i, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
return &expr.E{}, in.Errorf("invalid number: %s: %s", s, err)
|
|
}
|
|
ex.Op = expr.OpLeaf
|
|
ex.Val = i
|
|
return top, nil
|
|
}
|
|
|
|
// Character
|
|
allChars := a.CharChars + a.InvCharChars
|
|
if allChars != "" && lp.Accept(allChars) {
|
|
quote := lp.Emit()
|
|
c := lp.Next()
|
|
if c == lines.Eol {
|
|
return &expr.E{}, in.Errorf("end of line after quote")
|
|
}
|
|
ex.Op = expr.OpLeaf
|
|
ex.Val = int64(c)
|
|
if strings.Contains(a.InvCharChars, quote[:1]) {
|
|
ex.Val |= 0x80
|
|
}
|
|
lp.Consume(quote) // 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()
|
|
newL, err := a.FixLabel(ctx, ex.Text)
|
|
if err != nil {
|
|
return &expr.E{}, in.Errorf("%v", err)
|
|
}
|
|
ex.Text = newL
|
|
return top, nil
|
|
}
|
|
|
|
var macroArgRe = regexp.MustCompile("][0-9]+")
|
|
|
|
func (a *Base) ReplaceMacroArgs(line string, args []string, kwargs map[string]string) (string, error) {
|
|
var err 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 in
|
|
}))
|
|
return line, err
|
|
}
|
|
|
|
func (a *Base) DefaultIsNewParentLabel(label string) bool {
|
|
return label != "" && label[0] != '.'
|
|
}
|
|
|
|
func (a *Base) DefaultFixLabel(ctx context.Context, label string) (string, error) {
|
|
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 label[0] == ':':
|
|
_, macroCall, _ := ctx.GetMacroCall()
|
|
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
|
|
}
|
|
|
|
func (a *Base) LocalMacroLabels() bool {
|
|
return a.LocalMacroLabelsVal
|
|
}
|
|
|
|
func (a *Base) Zero() (uint16, error) {
|
|
if a.DivZeroVal == nil {
|
|
return 0, errors.New("Division by zero.")
|
|
}
|
|
return *a.DivZeroVal, nil
|
|
}
|
|
|
|
func (a *Base) DefaultOrigin() uint16 {
|
|
return a.DefaultOriginVal
|
|
}
|
|
|
|
func (a *Base) InitContext(ctx context.Context) {
|
|
if a.InitContextFunc != nil {
|
|
a.InitContextFunc(ctx)
|
|
}
|
|
}
|