1
0
mirror of https://github.com/zellyn/go6502.git synced 2024-06-24 15:29:40 +00:00

scma: assembles applesoft without erroring

This commit is contained in:
Zellyn Hunter 2014-05-04 20:51:58 -07:00
parent 42bde82f34
commit a241c48657
11 changed files with 173 additions and 89 deletions

View File

@ -11,6 +11,7 @@ import (
type Flavor interface { type Flavor interface {
ParseInstr(Line lines.Line) (inst.I, error) ParseInstr(Line lines.Line) (inst.I, error)
DefaultOrigin() (uint16, error) DefaultOrigin() (uint16, error)
SetWidthsOnFirstPass() bool
context.Context context.Context
} }
@ -56,49 +57,49 @@ func (a *Assembler) Load(filename string) error {
return err return err
} }
if _, err := a.passInst(&in, false, false); err != nil { if _, err := a.passInst(&in, a.Flavor.SetWidthsOnFirstPass(), false); err != nil {
return err return err
} }
switch in.Type { switch in.Type {
case inst.TypeUnknown: case inst.TypeUnknown:
return fmt.Errorf("Unknown instruction: %s", line) return in.Errorf("unknown instruction: %s", line)
case inst.TypeMacroStart: case inst.TypeMacroStart:
return fmt.Errorf("Macro start not (yet) implemented: %s", line) return in.Errorf("macro start not (yet) implemented: %s", line)
case inst.TypeMacroCall: case inst.TypeMacroCall:
return fmt.Errorf("Macro call not (yet) implemented: %s", line) return in.Errorf("macro call not (yet) implemented: %s", line)
case inst.TypeIfdef: case inst.TypeIfdef:
if len(in.Exprs) == 0 { if len(in.Exprs) == 0 {
panic(fmt.Sprintf("Ifdef got parsed with no expression: %s", line)) panic(fmt.Sprintf("Ifdef got parsed with no expression: %s", line))
} }
val, err := in.Exprs[0].Eval(a.Flavor) val, err := in.Exprs[0].Eval(a.Flavor, in.Line)
if err != nil { if err != nil {
return fmt.Errorf("Cannot eval ifdef condition: %v", err) return in.Errorf("cannot eval ifdef condition: %v", err)
} }
ifdefs = append([]bool{val != 0}, ifdefs...) ifdefs = append([]bool{val != 0}, ifdefs...)
case inst.TypeIfdefElse: case inst.TypeIfdefElse:
if len(ifdefs) == 0 { if len(ifdefs) == 0 {
return fmt.Errorf("Ifdef else branch encountered outside ifdef: %s", line) return in.Errorf("ifdef else branch encountered outside ifdef: %s", line)
} }
ifdefs[0] = !ifdefs[0] ifdefs[0] = !ifdefs[0]
case inst.TypeIfdefEnd: case inst.TypeIfdefEnd:
if len(ifdefs) == 0 { if len(ifdefs) == 0 {
return fmt.Errorf("Ifdef end encountered outside ifdef: %s", line) return in.Errorf("ifdef end encountered outside ifdef: %s", line)
} }
ifdefs = ifdefs[1:] ifdefs = ifdefs[1:]
case inst.TypeInclude: case inst.TypeInclude:
subContext := lines.Context{Filename: in.TextArg, Parent: in.Line} subContext := lines.Context{Filename: in.TextArg, Parent: in.Line}
subLs, err := lines.NewFileLineSource(in.TextArg, subContext, a.Opener) subLs, err := lines.NewFileLineSource(in.TextArg, subContext, a.Opener)
if err != nil { if err != nil {
return fmt.Errorf("error including file: %v", err) return in.Errorf("error including file: %v", err)
} }
lineSources = append([]lines.LineSource{subLs}, lineSources...) lineSources = append([]lines.LineSource{subLs}, lineSources...)
continue // no need to append continue // no need to append
case inst.TypeTarget: case inst.TypeTarget:
return fmt.Errorf("Target not (yet) implemented: %s", line) return in.Errorf("target not (yet) implemented: %s", line)
case inst.TypeSegment: case inst.TypeSegment:
return fmt.Errorf("Segment not (yet) implemented: %s", line) return in.Errorf("segment not (yet) implemented: %s", line)
case inst.TypeEnd: case inst.TypeEnd:
return nil return nil
default: default:
@ -115,7 +116,7 @@ func (a *Assembler) initPass() {
if org, err := a.Flavor.DefaultOrigin(); err == nil { if org, err := a.Flavor.DefaultOrigin(); err == nil {
a.Flavor.SetAddr(org) a.Flavor.SetAddr(org)
} else { } else {
a.Flavor.ClearAddr() a.Flavor.ClearAddr("beginning of assembly")
} }
} }
@ -125,9 +126,9 @@ func (a *Assembler) initPass() {
// instruction to decide its final width. If final is true, and the // instruction to decide its final width. If final is true, and the
// instruction cannot be finalized, it returns an error. // instruction cannot be finalized, it returns an error.
func (a *Assembler) passInst(in *inst.I, setWidth, final bool) (isFinal bool, err error) { func (a *Assembler) passInst(in *inst.I, setWidth, final bool) (isFinal bool, err error) {
fmt.Printf("PLUGH: in.Compute(a.Flavor, true, true) on %s\n", in) // fmt.Printf("PLUGH: in.Compute(a.Flavor, true, true) on %s\n", in)
isFinal, err = in.Compute(a.Flavor, setWidth, final) isFinal, err = in.Compute(a.Flavor, setWidth, final)
fmt.Printf("PLUGH: isFinal=%v, in.Final=%v, in.WidthKnown=%v, in.MinWidth=%v\n", isFinal, in.Final, in.WidthKnown, in.MinWidth) // fmt.Printf("PLUGH: isFinal=%v, in.Final=%v, in.WidthKnown=%v, in.MinWidth=%v\n", isFinal, in.Final, in.WidthKnown, in.MinWidth)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -140,7 +141,9 @@ func (a *Assembler) passInst(in *inst.I, setWidth, final bool) (isFinal bool, er
addr, _ := a.Flavor.GetAddr() addr, _ := a.Flavor.GetAddr()
a.Flavor.SetAddr(addr + in.MinWidth) a.Flavor.SetAddr(addr + in.MinWidth)
} else { } else {
a.Flavor.ClearAddr() if a.Flavor.AddrKnown() {
a.Flavor.ClearAddr(in.Sprintf("lost known address"))
}
} }
return isFinal, nil return isFinal, nil
@ -150,7 +153,7 @@ func (a *Assembler) passInst(in *inst.I, setWidth, final bool) (isFinal bool, er
// instructions to set their final width. If final is true, it returns // instructions to set their final width. If final is true, it returns
// an error for any instruction that cannot be finalized. // an error for any instruction that cannot be finalized.
func (a *Assembler) Pass(setWidth, final bool) (isFinal bool, err error) { func (a *Assembler) Pass(setWidth, final bool) (isFinal bool, err error) {
fmt.Printf("PLUGH: Pass(%v, %v): %d instructions\n", setWidth, final, len(a.Insts)) // fmt.Printf("PLUGH: Pass(%v, %v): %d instructions\n", setWidth, final, len(a.Insts))
setWidth = setWidth || final // final ⊢ setWidth setWidth = setWidth || final // final ⊢ setWidth
a.initPass() a.initPass()
@ -162,9 +165,9 @@ func (a *Assembler) Pass(setWidth, final bool) (isFinal bool, err error) {
return false, err return false, err
} }
if final && !instFinal { if final && !instFinal {
return false, fmt.Errorf("Cannot finalize instruction: %s", in) return false, in.Errorf("cannot finalize instruction: %s", in)
} }
fmt.Printf("PLUGH: instFinal=%v, in.Final=%v, in.WidthKnown=%v, in.MinWidth=%v\n", instFinal, in.Final, in.WidthKnown, in.MinWidth) // fmt.Printf("PLUGH: instFinal=%v, in.Final=%v, in.WidthKnown=%v, in.MinWidth=%v\n", instFinal, in.Final, in.WidthKnown, in.MinWidth)
isFinal = isFinal && instFinal isFinal = isFinal && instFinal
} }
@ -177,7 +180,7 @@ func (a *Assembler) RawBytes() ([]byte, error) {
result := []byte{} result := []byte{}
for _, in := range a.Insts { for _, in := range a.Insts {
if !in.Final { if !in.Final {
return []byte{}, fmt.Errorf("cannot finalize value: %s", in) return []byte{}, in.Errorf("cannot finalize value: %s", in)
} }
result = append(result, in.Data...) result = append(result, in.Data...)
} }

View File

@ -6,7 +6,8 @@ type Context interface {
Set(name string, value uint16) Set(name string, value uint16)
Get(name string) (uint16, bool) Get(name string) (uint16, bool)
SetAddr(uint16) SetAddr(uint16)
ClearAddr() ClearAddr(message string)
ClearMesg() string
GetAddr() (uint16, bool) GetAddr() (uint16, bool)
Zero() (uint16, error) // type ZeroFunc Zero() (uint16, error) // type ZeroFunc
RemoveChanged() RemoveChanged()
@ -20,6 +21,7 @@ type SimpleContext struct {
symbols map[string]symbolValue symbols map[string]symbolValue
addr int32 addr int32
lastLabel string lastLabel string
clearMesg string // Saved message describing why Addr was cleared.
} }
type symbolValue struct { type symbolValue struct {
@ -46,14 +48,19 @@ func (sc *SimpleContext) Get(name string) (uint16, bool) {
return s.v, found return s.v, found
} }
func (sc *SimpleContext) ClearAddr() { func (sc *SimpleContext) ClearAddr(message string) {
sc.addr = -1 sc.addr = -1
sc.clearMesg = message
} }
func (sc *SimpleContext) SetAddr(addr uint16) { func (sc *SimpleContext) SetAddr(addr uint16) {
sc.addr = int32(addr) sc.addr = int32(addr)
} }
func (sc *SimpleContext) ClearMesg() string {
return sc.clearMesg
}
func (sc *SimpleContext) GetAddr() (uint16, bool) { func (sc *SimpleContext) GetAddr() (uint16, bool) {
if sc.addr == -1 { if sc.addr == -1 {
return 0, false return 0, false

View File

@ -8,6 +8,8 @@ additional flavors should be straightforward.
TODO(zellyn): make errors return line and character position. TODO(zellyn): make errors return line and character position.
TODO(zellyn): scma requires .EQ and .BS to have known values. Is this universal? TODO(zellyn): scma requires .EQ and .BS to have known values. Is this universal?
TODO(zellyn): make lineparse have a line, rather than the reverse.
TODO(zellyn): implement ca65 and compile ehbasic
*/ */
package asm package asm

View File

@ -5,14 +5,15 @@ import (
"fmt" "fmt"
"github.com/zellyn/go6502/asm/context" "github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/lines"
) )
type UnknownLabelError struct { type UnknownLabelError struct {
Label string Err error
} }
func (e UnknownLabelError) Error() string { func (e UnknownLabelError) Error() string {
return fmt.Sprintf(`unknown label: "%s"`, e.Label) return e.Err.Error()
} }
type Operator int type Operator int
@ -99,7 +100,7 @@ func (e *E) Width() uint16 {
return 2 return 2
} }
func (e *E) Eval(ctx context.Context) (uint16, error) { func (e *E) Eval(ctx context.Context, ln *lines.Line) (uint16, error) {
if e == nil { if e == nil {
return 0, errors.New("cannot Eval() nil expression") return 0, errors.New("cannot Eval() nil expression")
} }
@ -111,22 +112,27 @@ func (e *E) Eval(ctx context.Context) (uint16, error) {
if val, ok := ctx.Get(e.Text); ok { if val, ok := ctx.Get(e.Text); ok {
return val, nil return val, nil
} }
return 0, UnknownLabelError{Label: e.Text} if e.Text == "*" && !ctx.AddrKnown() {
e := ln.Errorf("address unknown due to %v", ctx.ClearMesg())
return 0, UnknownLabelError{Err: e}
}
return 0, UnknownLabelError{Err: ln.Errorf("unknown label: %s", e.Text)}
case OpMinus: case OpMinus:
l, err := e.Left.Eval(ctx) l, err := e.Left.Eval(ctx, ln)
if err != nil { if err != nil {
return 0, err return 0, err
} }
if e.Right == nil { if e.Right == nil {
return -l, nil return -l, nil
} }
r, err := e.Right.Eval(ctx) r, err := e.Right.Eval(ctx, ln)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return l - r, nil return l - r, nil
case OpMsb, OpLsb: case OpMsb, OpLsb:
l, err := e.Left.Eval(ctx) l, err := e.Left.Eval(ctx, ln)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -137,11 +143,11 @@ func (e *E) Eval(ctx context.Context) (uint16, error) {
case OpByte: case OpByte:
return e.Val, nil return e.Val, nil
case OpPlus, OpMul, OpDiv, OpLt, OpGt, OpEq: case OpPlus, OpMul, OpDiv, OpLt, OpGt, OpEq:
l, err := e.Left.Eval(ctx) l, err := e.Left.Eval(ctx, ln)
if err != nil { if err != nil {
return 0, err return 0, err
} }
r, err := e.Right.Eval(ctx) r, err := e.Right.Eval(ctx, ln)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -177,8 +183,8 @@ func (e *E) Eval(ctx context.Context) (uint16, error) {
} }
// CheckedEval calls Eval, but also turns UnknownLabelErrors into labelMissing booleans. // CheckedEval calls Eval, but also turns UnknownLabelErrors into labelMissing booleans.
func (e *E) CheckedEval(ctx context.Context) (val uint16, labelMissing bool, err error) { func (e *E) CheckedEval(ctx context.Context, ln *lines.Line) (val uint16, labelMissing bool, err error) {
val, err = e.Eval(ctx) val, err = e.Eval(ctx, ln)
switch err.(type) { switch err.(type) {
case nil: case nil:
return val, false, nil return val, false, nil
@ -189,21 +195,21 @@ func (e *E) CheckedEval(ctx context.Context) (val uint16, labelMissing bool, err
} }
// FixLabels attempts to turn .1 into LAST_LABEL.1 // FixLabels attempts to turn .1 into LAST_LABEL.1
func (e *E) FixLabels(last string) error { func (e *E) FixLabels(last string, ln *lines.Line) error {
if e.Text != "" && e.Text[0] == '.' { if e.Text != "" && e.Text[0] == '.' {
if last == "" { if last == "" {
return fmt.Errorf("Reference to sub-label '%s' before full label.", e.Text) return ln.Errorf("reference to sub-label '%s' before full label.", e.Text)
} }
e.Text = last + e.Text e.Text = last + "/" + e.Text
} }
if e.Left != nil { if e.Left != nil {
if err := e.Left.FixLabels(last); err != nil { if err := e.Left.FixLabels(last, ln); err != nil {
return err return err
} }
} }
if e.Right != nil { if e.Right != nil {
return e.Right.FixLabels(last) return e.Right.FixLabels(last, ln)
} }
return nil return nil

View File

@ -31,3 +31,7 @@ func (a *AS65) Zero() (uint16, error) {
func (a *AS65) DefaultOrigin() (uint16, error) { func (a *AS65) DefaultOrigin() (uint16, error) {
return 0, nil return 0, nil
} }
func (a *AS65) SetWidthsOnFirstPass() bool {
return false
}

View File

@ -32,6 +32,13 @@ func TestMultiline(t *testing.T) {
"L2 NOP", "L2 NOP",
}, nil, "ad0300ea", false}, }, nil, "ad0300ea", false},
// sc-asm sets instruction widths on the first pass
{ss, "Later label: wide", []string{
" LDA FOO",
"FOO .EQ $FF",
" NOP",
}, nil, "adff00ea", true},
// Sub-labels // Sub-labels
{ss, "Sublabels", []string{ {ss, "Sublabels", []string{
"L1 BEQ .1", "L1 BEQ .1",

View File

@ -32,3 +32,7 @@ func (a *Merlin) Zero() (uint16, error) {
func (a *Merlin) DefaultOrigin() (uint16, error) { func (a *Merlin) DefaultOrigin() (uint16, error) {
return 0x8000, nil return 0x8000, nil
} }
func (a *Merlin) SetWidthsOnFirstPass() bool {
panic("don't know yet")
}

View File

@ -23,6 +23,9 @@ const cmdChars = letters + "."
const fileChars = letters + digits + "." const fileChars = letters + digits + "."
const operatorChars = "+-*/<>=" const operatorChars = "+-*/<>="
// 40 spaces = comment column
const comment_whitespace_prefix = " "
type directiveInfo struct { type directiveInfo struct {
Type inst.Type Type inst.Type
Func func(inst.I, *lines.Parse) (inst.I, error) Func func(inst.I, *lines.Parse) (inst.I, error)
@ -82,18 +85,24 @@ func (a *SCMA) ParseInstr(line lines.Line) (inst.I, error) {
if lp.AcceptRun(digits) { if lp.AcceptRun(digits) {
s := lp.Emit() s := lp.Emit()
if len(s) != 4 { if len(s) != 4 {
return inst.I{}, fmt.Errorf("Line number must be exactly 4 digits: %s", s) return inst.I{}, line.Errorf("line number must be exactly 4 digits: %s", s)
} }
if !lp.Consume(" ") && lp.Peek() != lines.Eol { if !lp.Consume(" ") && lp.Peek() != lines.Eol {
return inst.I{}, fmt.Errorf("Line number (%s) followed by non-space", s) return inst.I{}, line.Errorf("line number (%s) followed by non-space", s)
} }
i, err := strconv.ParseUint(s, 10, 16) i, err := strconv.ParseUint(s, 10, 16)
if err != nil { if err != nil {
return inst.I{}, fmt.Errorf("Invalid line number: %s: %s", s, err) return inst.I{}, line.Errorf("invalid line number: %s: %s", s, err)
} }
in.DeclaredLine = uint16(i) 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 // Empty line or comment
trimmed := strings.TrimSpace(lp.Rest()) trimmed := strings.TrimSpace(lp.Rest())
if trimmed == "" || trimmed[0] == '*' { if trimmed == "" || trimmed[0] == '*' {
@ -124,6 +133,10 @@ func (a *SCMA) DefaultOrigin() (uint16, error) {
return 0x0800, nil return 0x0800, nil
} }
func (a *SCMA) SetWidthsOnFirstPass() bool {
return true
}
// parseCmd parses the "command" part of an instruction: we expect to be // parseCmd parses the "command" part of an instruction: we expect to be
// looking at a non-whitespace character. // looking at a non-whitespace character.
func (a *SCMA) parseCmd(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
@ -132,7 +145,7 @@ func (a *SCMA) parseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
} }
if !lp.AcceptRun(cmdChars) { if !lp.AcceptRun(cmdChars) {
c := lp.Next() c := lp.Next()
return inst.I{}, fmt.Errorf("Expecting instruction, found '%c' (%d)", c, c) return inst.I{}, in.Errorf("expecting instruction, found '%c' (%d)", c, c)
} }
in.Command = lp.Emit() in.Command = lp.Emit()
if dir, ok := a.directives[in.Command]; ok { if dir, ok := a.directives[in.Command]; ok {
@ -147,7 +160,7 @@ func (a *SCMA) parseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
in.Type = inst.TypeOp in.Type = inst.TypeOp
return a.parseOpArgs(in, lp, summary) return a.parseOpArgs(in, lp, summary)
} }
return inst.I{}, fmt.Errorf(`Not implemented: "%s": `, in.Command, in.Line) return inst.I{}, in.Errorf(`not implemented: "%s": `, in.Command, in.Line)
} }
// parseMacroCall parses a macro call. We expect to be looking at a the // parseMacroCall parses a macro call. We expect to be looking at a the
@ -156,14 +169,14 @@ func (a *SCMA) parseMacroCall(in inst.I, lp *lines.Parse) (inst.I, error) {
in.Type = inst.TypeMacroCall in.Type = inst.TypeMacroCall
if !lp.AcceptRun(cmdChars) { if !lp.AcceptRun(cmdChars) {
c := lp.Next() c := lp.Next()
return inst.I{}, fmt.Errorf("Expecting macro name, found '%c' (%d)", c, c) return inst.I{}, in.Errorf("expecting macro name, found '%c' (%d)", c, c)
} }
in.Command = lp.Emit() in.Command = lp.Emit()
lp.Consume(whitespace) lp.Consume(whitespace)
for { for {
s, err := a.parseMacroArg(lp) s, err := a.parseMacroArg(in, lp)
if err != nil { if err != nil {
return inst.I{}, err return inst.I{}, err
} }
@ -178,9 +191,9 @@ func (a *SCMA) parseMacroCall(in inst.I, lp *lines.Parse) (inst.I, error) {
// parseMacroArg parses a single macro argument. We expect to be looking at the first // parseMacroArg parses a single macro argument. We expect to be looking at the first
// character of a macro argument. // character of a macro argument.
func (a *SCMA) parseMacroArg(lp *lines.Parse) (string, error) { func (a *SCMA) parseMacroArg(in inst.I, lp *lines.Parse) (string, error) {
if lp.Peek() == '"' { if lp.Peek() == '"' {
return a.parseQuoted(lp) return a.parseQuoted(in, lp)
} }
lp.AcceptUntil(whitespace + ",") lp.AcceptUntil(whitespace + ",")
return lp.Emit(), nil return lp.Emit(), nil
@ -188,7 +201,7 @@ func (a *SCMA) parseMacroArg(lp *lines.Parse) (string, error) {
// parseQuoted parses a single quoted string macro argument. We expect // parseQuoted parses a single quoted string macro argument. We expect
// to be looking at the first quote. // to be looking at the first quote.
func (a *SCMA) parseQuoted(lp *lines.Parse) (string, error) { func (a *SCMA) parseQuoted(in inst.I, lp *lines.Parse) (string, error) {
if !lp.Consume(`"`) { if !lp.Consume(`"`) {
panic(fmt.Sprintf("parseQuoted called not looking at a quote")) panic(fmt.Sprintf("parseQuoted called not looking at a quote"))
} }
@ -202,12 +215,12 @@ func (a *SCMA) parseQuoted(lp *lines.Parse) (string, error) {
s := lp.Emit() s := lp.Emit()
if !lp.Consume(`"`) { if !lp.Consume(`"`) {
c := lp.Peek() c := lp.Peek()
return "", fmt.Errorf("Expected closing quote; got %s", c) return "", in.Errorf("Expected closing quote; got %s", c)
} }
c := lp.Peek() c := lp.Peek()
if c != ',' && c != ' ' && c != lines.Eol && c != '\t' { if c != ',' && c != ' ' && c != lines.Eol && c != '\t' {
return "", fmt.Errorf("Unexpected char after quoted string: '%s'", c) return "", in.Errorf("Unexpected char after quoted string: '%s'", c)
} }
return strings.Replace(s, `""`, `"`, -1), nil return strings.Replace(s, `""`, `"`, -1), nil
@ -234,7 +247,7 @@ func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
lp.Consume(whitespace) lp.Consume(whitespace)
if lp.Consume(whitespace) || lp.Peek() == lines.Eol { if lp.Consume(whitespace) || lp.Peek() == lines.Eol {
if !summary.AnyModes(opcodes.MODE_A) { if !summary.AnyModes(opcodes.MODE_A) {
return i, fmt.Errorf("%s with no arguments", in.Command) return i, in.Errorf("%s with no arguments", in.Command)
} }
op, ok := summary.OpForMode(opcodes.MODE_A) op, ok := summary.OpForMode(opcodes.MODE_A)
if !ok { if !ok {
@ -251,10 +264,10 @@ func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
indirect := lp.Consume("(") indirect := lp.Consume("(")
if indirect && !summary.AnyModes(opcodes.MODE_INDIRECT_ANY) { if indirect && !summary.AnyModes(opcodes.MODE_INDIRECT_ANY) {
return i, fmt.Errorf("%s doesn't support any indirect modes", in.Command) return i, in.Errorf("%s doesn't support any indirect modes", in.Command)
} }
xy := '-' xy := '-'
expr, err := a.parseExpression(lp) expr, err := a.parseExpression(in, lp)
if err != nil { if err != nil {
return i, err return i, err
} }
@ -265,25 +278,25 @@ func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
xy = 'x' xy = 'x'
} else if lp.Consume("yY") { } else if lp.Consume("yY") {
if indirect { if indirect {
return i, fmt.Errorf(",Y unexpected inside parens") return i, in.Errorf(",Y unexpected inside parens")
} }
xy = 'y' xy = 'y'
} else { } else {
return i, fmt.Errorf("X or Y expected after comma") return i, in.Errorf("X or Y expected after comma")
} }
} }
comma2 := false comma2 := false
if indirect { if indirect {
if !lp.Consume(")") { if !lp.Consume(")") {
return i, fmt.Errorf("Expected closing paren") return i, in.Errorf("Expected closing paren")
} }
comma2 = lp.Consume(",") comma2 = lp.Consume(",")
if comma2 { if comma2 {
if comma { if comma {
return i, fmt.Errorf("Cannot have ,X or ,Y twice.") return i, in.Errorf("Cannot have ,X or ,Y twice.")
} }
if !lp.Consume("yY") { if !lp.Consume("yY") {
return i, fmt.Errorf("Only ,Y can follow parens.") return i, in.Errorf("Only ,Y can follow parens.")
} }
xy = 'y' xy = 'y'
} }
@ -294,7 +307,7 @@ func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
func (a *SCMA) parseAddress(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseAddress(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace) lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp) expr, err := a.parseExpression(in, lp)
if err != nil { if err != nil {
return inst.I{}, err return inst.I{}, err
} }
@ -317,13 +330,13 @@ func (a *SCMA) parseAscii(in inst.I, lp *lines.Parse) (inst.I, error) {
} }
delim := lp.Next() delim := lp.Next()
if delim == lines.Eol || strings.IndexRune(whitespace, delim) >= 0 { if delim == lines.Eol || strings.IndexRune(whitespace, delim) >= 0 {
return inst.I{}, fmt.Errorf("%s expects delimeter, found '%s'", in.Command, delim) return inst.I{}, in.Errorf("%s expects delimeter, found '%s'", in.Command, delim)
} }
lp.Ignore() lp.Ignore()
lp.AcceptUntil(string(delim)) lp.AcceptUntil(string(delim))
delim2 := lp.Next() delim2 := lp.Next()
if delim != delim2 { if delim != delim2 {
return inst.I{}, fmt.Errorf("%s: expected closing delimeter '%s'; got '%s'", in.Command, delim, delim2) return inst.I{}, in.Errorf("%s: expected closing delimeter '%s'; got '%s'", in.Command, delim, delim2)
} }
lp.Backup() lp.Backup()
in.Data = []byte(lp.Emit()) in.Data = []byte(lp.Emit())
@ -338,7 +351,7 @@ func (a *SCMA) parseAscii(in inst.I, lp *lines.Parse) (inst.I, error) {
func (a *SCMA) parseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace) lp.IgnoreRun(whitespace)
ex, err := a.parseExpression(lp) ex, err := a.parseExpression(in, lp)
if err != nil { if err != nil {
return inst.I{}, err return inst.I{}, err
} }
@ -349,7 +362,7 @@ func (a *SCMA) parseBlockStorage(in inst.I, lp *lines.Parse) (inst.I, error) {
func (a *SCMA) parseData(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseData(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace) lp.IgnoreRun(whitespace)
for { for {
ex, err := a.parseExpression(lp) ex, err := a.parseExpression(in, lp)
if err != nil { if err != nil {
return inst.I{}, err return inst.I{}, err
} }
@ -363,7 +376,7 @@ func (a *SCMA) parseData(in inst.I, lp *lines.Parse) (inst.I, error) {
func (a *SCMA) parseDo(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseDo(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace) lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp) expr, err := a.parseExpression(in, lp)
if err != nil { if err != nil {
return inst.I{}, err return inst.I{}, err
} }
@ -377,7 +390,7 @@ func (a *SCMA) parseDo(in inst.I, lp *lines.Parse) (inst.I, error) {
func (a *SCMA) parseEquate(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseEquate(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace) lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp) expr, err := a.parseExpression(in, lp)
if err != nil { if err != nil {
return inst.I{}, err return inst.I{}, err
} }
@ -392,15 +405,15 @@ func (a *SCMA) parseEquate(in inst.I, lp *lines.Parse) (inst.I, error) {
func (a *SCMA) parseHexString(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseHexString(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace) lp.IgnoreRun(whitespace)
if !lp.AcceptRun(hexdigits) { if !lp.AcceptRun(hexdigits) {
return inst.I{}, fmt.Errorf("%s expects hex digits; got '%s'", in.Command, lp.Next()) return inst.I{}, in.Errorf("%s expects hex digits; got '%s'", in.Command, lp.Next())
} }
hs := lp.Emit() hs := lp.Emit()
if len(hs)%2 != 0 { if len(hs)%2 != 0 {
return inst.I{}, fmt.Errorf("%s expects pairs of hex digits; got %d", in.Command, len(hs)) return inst.I{}, in.Errorf("%s expects pairs of hex digits; got %d", in.Command, len(hs))
} }
var err error var err error
if in.Data, err = hex.DecodeString(hs); err != nil { if in.Data, err = hex.DecodeString(hs); err != nil {
return inst.I{}, fmt.Errorf("%s: error decoding hex string: %s", in.Command, err) return inst.I{}, in.Errorf("%s: error decoding hex string: %s", in.Command, err)
} }
return in, nil return in, nil
} }
@ -408,7 +421,7 @@ func (a *SCMA) parseHexString(in inst.I, lp *lines.Parse) (inst.I, error) {
func (a *SCMA) parseInclude(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseInclude(in inst.I, lp *lines.Parse) (inst.I, error) {
lp.IgnoreRun(whitespace) lp.IgnoreRun(whitespace)
if !lp.AcceptRun(fileChars) { if !lp.AcceptRun(fileChars) {
return inst.I{}, fmt.Errorf("Expecting filename, found '%c'", lp.Next()) return inst.I{}, in.Errorf("Expecting filename, found '%c'", lp.Next())
} }
in.TextArg = lp.Emit() in.TextArg = lp.Emit()
in.WidthKnown = true in.WidthKnown = true
@ -427,10 +440,10 @@ func (a *SCMA) parseNoArgDir(in inst.I, lp *lines.Parse) (inst.I, error) {
} }
func (a *SCMA) parseNotImplemented(in inst.I, lp *lines.Parse) (inst.I, error) { func (a *SCMA) parseNotImplemented(in inst.I, lp *lines.Parse) (inst.I, error) {
return inst.I{}, fmt.Errorf("Not implemented (yet?): %s", in.Command) return inst.I{}, in.Errorf("not implemented (yet?): %s", in.Command)
} }
func (a *SCMA) parseExpression(lp *lines.Parse) (*expr.E, error) { func (a *SCMA) parseExpression(in inst.I, lp *lines.Parse) (*expr.E, error) {
var outer *expr.E var outer *expr.E
if lp.Accept("#/") { if lp.Accept("#/") {
switch lp.Emit() { switch lp.Emit() {
@ -441,14 +454,14 @@ func (a *SCMA) parseExpression(lp *lines.Parse) (*expr.E, error) {
} }
} }
tree, err := a.parseTerm(lp) tree, err := a.parseTerm(in, lp)
if err != nil { if err != nil {
return &expr.E{}, err return &expr.E{}, err
} }
for lp.Accept(operatorChars) { for lp.Accept(operatorChars) {
c := lp.Emit() c := lp.Emit()
right, err := a.parseTerm(lp) right, err := a.parseTerm(in, lp)
if err != nil { if err != nil {
return &expr.E{}, err return &expr.E{}, err
} }
@ -462,7 +475,7 @@ func (a *SCMA) parseExpression(lp *lines.Parse) (*expr.E, error) {
return tree, nil return tree, nil
} }
func (a *SCMA) parseTerm(lp *lines.Parse) (*expr.E, error) { func (a *SCMA) parseTerm(in inst.I, lp *lines.Parse) (*expr.E, error) {
ex := &expr.E{} ex := &expr.E{}
top := ex top := ex
@ -482,12 +495,12 @@ func (a *SCMA) parseTerm(lp *lines.Parse) (*expr.E, error) {
if lp.Consume("$") { if lp.Consume("$") {
if !lp.AcceptRun(hexdigits) { if !lp.AcceptRun(hexdigits) {
c := lp.Next() c := lp.Next()
return &expr.E{}, fmt.Errorf("Expecting hex number, found '%c' (%d)", c, c) return &expr.E{}, in.Errorf("expecting hex number, found '%c' (%d)", c, c)
} }
s := lp.Emit() s := lp.Emit()
i, err := strconv.ParseUint(s, 16, 16) i, err := strconv.ParseUint(s, 16, 16)
if err != nil { if err != nil {
return &expr.E{}, fmt.Errorf("Invalid hex number: %s: %s", s, err) return &expr.E{}, in.Errorf("invalid hex number: %s: %s", s, err)
} }
ex.Op = expr.OpLeaf ex.Op = expr.OpLeaf
ex.Val = uint16(i) ex.Val = uint16(i)
@ -499,17 +512,29 @@ func (a *SCMA) parseTerm(lp *lines.Parse) (*expr.E, error) {
s := lp.Emit() s := lp.Emit()
i, err := strconv.ParseUint(s, 10, 16) i, err := strconv.ParseUint(s, 10, 16)
if err != nil { if err != nil {
return &expr.E{}, fmt.Errorf("Invalid number: %s: %s", s, err) return &expr.E{}, in.Errorf("invalid number: %s: %s", s, err)
} }
ex.Op = expr.OpLeaf ex.Op = expr.OpLeaf
ex.Val = uint16(i) ex.Val = uint16(i)
return top, nil 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.Ignore()
return top, nil
}
// Label // Label
if !lp.AcceptRun(labelChars) { if !lp.AcceptRun(labelChars) {
c := lp.Next() c := lp.Next()
return &expr.E{}, fmt.Errorf("Expecting *, (hex) number, or label; found '%c' (%d)", c, c) return &expr.E{}, in.Errorf("expecting *, (hex) number, or label; found '%c' (%d)", c, c)
} }
ex.Op = expr.OpLeaf ex.Op = expr.OpLeaf

View File

@ -26,6 +26,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
{ss, "* Comment", "{-}", ""}, {ss, "* Comment", "{-}", ""},
{aa, "; Comment", "{-}", ""}, {aa, "; Comment", "{-}", ""},
{mm, "* Comment", "{-}", ""}, {mm, "* Comment", "{-}", ""},
{ss, " far-out-comment", "{-}", ""},
{ss, "Label", "{- 'Label'}", ""}, {ss, "Label", "{- 'Label'}", ""},
{aa, "Label", "{- 'Label'}", ""}, {aa, "Label", "{- 'Label'}", ""},
{mm, "Label", "{- 'Label'}", ""}, {mm, "Label", "{- 'Label'}", ""},
@ -118,6 +119,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
{ss, " .EN", "{end}", ""}, {ss, " .EN", "{end}", ""},
{ss, `>SAM AB,$12,"A B","A, B, "" C"`, {ss, `>SAM AB,$12,"A B","A, B, "" C"`,
`{call SAM {"AB", "$12", "A B", "A, B, \" C"}}`, ""}, `{call SAM {"AB", "$12", "A B", "A, B, \" C"}}`, ""},
{ss, " LDX #']+$80", "{LDX/imm (lsb (+ $005d $0080))}", "a2dd"},
} }
// TODO(zellyn): Add tests for finalization of four SCMA directives: // TODO(zellyn): Add tests for finalization of four SCMA directives:

View File

@ -182,13 +182,13 @@ func (i *I) Compute(c context.Context, setWidth bool, final bool) (bool, error)
func (i *I) FixLabels(last string) error { func (i *I) FixLabels(last string) error {
if i.Label != "" && i.Label[0] == '.' { if i.Label != "" && i.Label[0] == '.' {
if last == "" { if last == "" {
return fmt.Errorf("Reference to sub-label '%s' before full label.", i.Label) return i.Errorf("Reference to sub-label '%s' before full label.", i.Label)
} }
i.Label = last + i.Label i.Label = last + "/" + i.Label
} }
for _, e := range i.Exprs { for _, e := range i.Exprs {
if err := e.FixLabels(last); err != nil { if err := e.FixLabels(last, i.Line); err != nil {
return err return err
} }
} }
@ -213,7 +213,7 @@ func (i *I) computeLabel(c context.Context, setWidth bool, final bool) error {
lval, lok := c.Get(i.Label) lval, lok := c.Get(i.Label)
if lok && addr != lval { if lok && addr != lval {
return fmt.Errorf("Trying to set label '%s' to $%04x, but it already has value $%04x", i.Label, addr, lval) return i.Errorf("Trying to set label '%s' to $%04x, but it already has value $%04x", i.Label, addr, lval)
} }
c.Set(i.Label, addr) c.Set(i.Label, addr)
return nil return nil
@ -234,7 +234,7 @@ func (i *I) computeData(c context.Context, setWidth bool, final bool) (bool, err
for _, e := range i.Exprs { for _, e := range i.Exprs {
w := e.Width() w := e.Width()
width += w width += w
val, labelMissing, err := e.CheckedEval(c) val, labelMissing, err := e.CheckedEval(c, i.Line)
if err != nil && !labelMissing { if err != nil && !labelMissing {
return false, err return false, err
} }
@ -263,7 +263,7 @@ func (i *I) computeData(c context.Context, setWidth bool, final bool) (bool, err
} }
func (i *I) computeBlock(c context.Context, setWidth bool, final bool) (bool, error) { func (i *I) computeBlock(c context.Context, setWidth bool, final bool) (bool, error) {
val, err := i.Exprs[0].Eval(c) val, err := i.Exprs[0].Eval(c, i.Line)
if err == nil { if err == nil {
i.Value = val i.Value = val
i.WidthKnown = true i.WidthKnown = true
@ -272,7 +272,7 @@ func (i *I) computeBlock(c context.Context, setWidth bool, final bool) (bool, er
i.MaxWidth = val i.MaxWidth = val
} else { } else {
if setWidth || final { if setWidth || final {
return false, fmt.Errorf("block storage with unknown size") return false, i.Errorf("block storage with unknown size")
} }
} }
return i.Final, nil return i.Final, nil
@ -283,7 +283,7 @@ func (i *I) computeMustKnow(c context.Context, setWidth bool, final bool) (bool,
i.MinWidth = 0 i.MinWidth = 0
i.MaxWidth = 0 i.MaxWidth = 0
i.Final = true i.Final = true
val, err := i.Exprs[0].Eval(c) val, err := i.Exprs[0].Eval(c, i.Line)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -313,7 +313,7 @@ func (i *I) computeOp(c context.Context, setWidth bool, final bool) (bool, error
if len(i.Exprs) == 0 { if len(i.Exprs) == 0 {
panic(fmt.Sprintf("Reached computeOp for '%s' with no expressions", i.Command)) panic(fmt.Sprintf("Reached computeOp for '%s' with no expressions", i.Command))
} }
val, labelMissing, err := i.Exprs[0].CheckedEval(c) val, labelMissing, err := i.Exprs[0].CheckedEval(c, i.Line)
// A real error // A real error
if !labelMissing && err != nil { if !labelMissing && err != nil {
return false, err return false, err
@ -372,17 +372,17 @@ func (i *I) computeOp(c context.Context, setWidth bool, final bool) (bool, error
curr, ok := c.GetAddr() curr, ok := c.GetAddr()
if !ok { if !ok {
if final { if final {
return false, fmt.Errorf("Cannot determine current address for '%s'", i.Command) return false, i.Errorf("cannot determine current address for '%s'", i.Command)
} }
return false, nil return false, nil
} }
// Found both current and target addresses // Found both current and target addresses
offset := int32(val) - (int32(curr) + 2) offset := int32(val) - (int32(curr) + 2)
if offset > 127 { if offset > 127 {
return false, fmt.Errorf("%s cannot jump forward %d (max 127)", i.Command, offset) return false, i.Errorf("%s cannot jump forward %d (max 127)", i.Command, offset)
} }
if offset < -128 { if offset < -128 {
return false, fmt.Errorf("%s cannot jump back %d (max -128)", i.Command, offset) return false, i.Errorf("%s cannot jump back %d (max -128)", i.Command, offset)
} }
val = uint16(offset) val = uint16(offset)
} }
@ -403,3 +403,11 @@ func (i *I) computeOp(c context.Context, setWidth bool, final bool) (bool, error
} }
return true, nil return true, 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...)
}

View File

@ -41,3 +41,19 @@ func (l Line) Text() string {
func (l Line) String() string { func (l Line) String() string {
return fmt.Sprintf("%s:%d: %s", l.Context.Filename, l.LineNo, l.Parse.Text()) return fmt.Sprintf("%s:%d: %s", l.Context.Filename, l.LineNo, l.Parse.Text())
} }
func (l Line) Errorf(format string, a ...interface{}) error {
filename := "unknown file"
if l.Context != nil {
filename = l.Context.Filename
}
return fmt.Errorf(fmt.Sprintf("%s:%d: %s", filename, l.LineNo, format), a...)
}
func (l Line) Sprintf(format string, a ...interface{}) string {
filename := "unknown file"
if l.Context != nil {
filename = l.Context.Filename
}
return fmt.Sprintf(fmt.Sprintf("%s:%d: %s", filename, l.LineNo, format), a...)
}