1
0
mirror of https://github.com/zellyn/go6502.git synced 2024-06-16 10:29:29 +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 {
ParseInstr(Line lines.Line) (inst.I, error)
DefaultOrigin() (uint16, error)
SetWidthsOnFirstPass() bool
context.Context
}
@ -56,49 +57,49 @@ func (a *Assembler) Load(filename string) error {
return err
}
if _, err := a.passInst(&in, false, false); err != nil {
if _, err := a.passInst(&in, a.Flavor.SetWidthsOnFirstPass(), false); err != nil {
return err
}
switch in.Type {
case inst.TypeUnknown:
return fmt.Errorf("Unknown instruction: %s", line)
return in.Errorf("unknown instruction: %s", line)
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:
return fmt.Errorf("Macro call not (yet) implemented: %s", line)
return in.Errorf("macro call not (yet) implemented: %s", line)
case inst.TypeIfdef:
if len(in.Exprs) == 0 {
panic(fmt.Sprintf("Ifdef got parsed with no expression: %s", line))
}
val, err := in.Exprs[0].Eval(a.Flavor)
val, err := in.Exprs[0].Eval(a.Flavor, in.Line)
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...)
case inst.TypeIfdefElse:
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]
case inst.TypeIfdefEnd:
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:]
case inst.TypeInclude:
subContext := lines.Context{Filename: in.TextArg, Parent: in.Line}
subLs, err := lines.NewFileLineSource(in.TextArg, subContext, a.Opener)
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...)
continue // no need to append
case inst.TypeTarget:
return fmt.Errorf("Target not (yet) implemented: %s", line)
return in.Errorf("target not (yet) implemented: %s", line)
case inst.TypeSegment:
return fmt.Errorf("Segment not (yet) implemented: %s", line)
return in.Errorf("segment not (yet) implemented: %s", line)
case inst.TypeEnd:
return nil
default:
@ -115,7 +116,7 @@ func (a *Assembler) initPass() {
if org, err := a.Flavor.DefaultOrigin(); err == nil {
a.Flavor.SetAddr(org)
} 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 cannot be finalized, it returns an 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)
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 {
return false, err
}
@ -140,7 +141,9 @@ func (a *Assembler) passInst(in *inst.I, setWidth, final bool) (isFinal bool, er
addr, _ := a.Flavor.GetAddr()
a.Flavor.SetAddr(addr + in.MinWidth)
} else {
a.Flavor.ClearAddr()
if a.Flavor.AddrKnown() {
a.Flavor.ClearAddr(in.Sprintf("lost known address"))
}
}
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
// an error for any instruction that cannot be finalized.
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
a.initPass()
@ -162,9 +165,9 @@ func (a *Assembler) Pass(setWidth, final bool) (isFinal bool, err error) {
return false, err
}
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
}
@ -177,7 +180,7 @@ func (a *Assembler) RawBytes() ([]byte, error) {
result := []byte{}
for _, in := range a.Insts {
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...)
}

View File

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

View File

@ -5,14 +5,15 @@ import (
"fmt"
"github.com/zellyn/go6502/asm/context"
"github.com/zellyn/go6502/asm/lines"
)
type UnknownLabelError struct {
Label string
Err error
}
func (e UnknownLabelError) Error() string {
return fmt.Sprintf(`unknown label: "%s"`, e.Label)
return e.Err.Error()
}
type Operator int
@ -99,7 +100,7 @@ func (e *E) Width() uint16 {
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 {
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 {
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:
l, err := e.Left.Eval(ctx)
l, err := e.Left.Eval(ctx, ln)
if err != nil {
return 0, err
}
if e.Right == nil {
return -l, nil
}
r, err := e.Right.Eval(ctx)
r, err := e.Right.Eval(ctx, ln)
if err != nil {
return 0, err
}
return l - r, nil
case OpMsb, OpLsb:
l, err := e.Left.Eval(ctx)
l, err := e.Left.Eval(ctx, ln)
if err != nil {
return 0, err
}
@ -137,11 +143,11 @@ func (e *E) Eval(ctx context.Context) (uint16, error) {
case OpByte:
return e.Val, nil
case OpPlus, OpMul, OpDiv, OpLt, OpGt, OpEq:
l, err := e.Left.Eval(ctx)
l, err := e.Left.Eval(ctx, ln)
if err != nil {
return 0, err
}
r, err := e.Right.Eval(ctx)
r, err := e.Right.Eval(ctx, ln)
if err != nil {
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.
func (e *E) CheckedEval(ctx context.Context) (val uint16, labelMissing bool, err error) {
val, err = e.Eval(ctx)
func (e *E) CheckedEval(ctx context.Context, ln *lines.Line) (val uint16, labelMissing bool, err error) {
val, err = e.Eval(ctx, ln)
switch err.(type) {
case 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
func (e *E) FixLabels(last string) error {
func (e *E) FixLabels(last string, ln *lines.Line) error {
if e.Text != "" && e.Text[0] == '.' {
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 err := e.Left.FixLabels(last); err != nil {
if err := e.Left.FixLabels(last, ln); err != nil {
return err
}
}
if e.Right != nil {
return e.Right.FixLabels(last)
return e.Right.FixLabels(last, ln)
}
return nil

View File

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

View File

@ -32,6 +32,13 @@ func TestMultiline(t *testing.T) {
"L2 NOP",
}, 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
{ss, "Sublabels", []string{
"L1 BEQ .1",

View File

@ -32,3 +32,7 @@ func (a *Merlin) Zero() (uint16, error) {
func (a *Merlin) DefaultOrigin() (uint16, error) {
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 operatorChars = "+-*/<>="
// 40 spaces = comment column
const comment_whitespace_prefix = " "
type directiveInfo struct {
Type inst.Type
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) {
s := lp.Emit()
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 {
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)
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)
}
// 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] == '*' {
@ -124,6 +133,10 @@ 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) {
@ -132,7 +145,7 @@ func (a *SCMA) parseCmd(in inst.I, lp *lines.Parse) (inst.I, error) {
}
if !lp.AcceptRun(cmdChars) {
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()
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
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
@ -156,14 +169,14 @@ func (a *SCMA) parseMacroCall(in inst.I, lp *lines.Parse) (inst.I, error) {
in.Type = inst.TypeMacroCall
if !lp.AcceptRun(cmdChars) {
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()
lp.Consume(whitespace)
for {
s, err := a.parseMacroArg(lp)
s, err := a.parseMacroArg(in, lp)
if err != nil {
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
// 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() == '"' {
return a.parseQuoted(lp)
return a.parseQuoted(in, lp)
}
lp.AcceptUntil(whitespace + ",")
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
// 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(`"`) {
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()
if !lp.Consume(`"`) {
c := lp.Peek()
return "", fmt.Errorf("Expected closing quote; got %s", c)
return "", in.Errorf("Expected closing quote; got %s", c)
}
c := lp.Peek()
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
@ -234,7 +247,7 @@ func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
lp.Consume(whitespace)
if lp.Consume(whitespace) || lp.Peek() == lines.Eol {
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)
if !ok {
@ -251,10 +264,10 @@ func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
indirect := lp.Consume("(")
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 := '-'
expr, err := a.parseExpression(lp)
expr, err := a.parseExpression(in, lp)
if err != nil {
return i, err
}
@ -265,25 +278,25 @@ func (a *SCMA) parseOpArgs(in inst.I, lp *lines.Parse, summary opcodes.OpSummary
xy = 'x'
} else if lp.Consume("yY") {
if indirect {
return i, fmt.Errorf(",Y unexpected inside parens")
return i, in.Errorf(",Y unexpected inside parens")
}
xy = 'y'
} else {
return i, fmt.Errorf("X or Y expected after comma")
return i, in.Errorf("X or Y expected after comma")
}
}
comma2 := false
if indirect {
if !lp.Consume(")") {
return i, fmt.Errorf("Expected closing paren")
return i, in.Errorf("Expected closing paren")
}
comma2 = lp.Consume(",")
if comma2 {
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") {
return i, fmt.Errorf("Only ,Y can follow parens.")
return i, in.Errorf("Only ,Y can follow parens.")
}
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) {
lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp)
expr, err := a.parseExpression(in, lp)
if err != nil {
return inst.I{}, err
}
@ -317,13 +330,13 @@ func (a *SCMA) parseAscii(in inst.I, lp *lines.Parse) (inst.I, error) {
}
delim := lp.Next()
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.AcceptUntil(string(delim))
delim2 := lp.Next()
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()
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) {
lp.IgnoreRun(whitespace)
ex, err := a.parseExpression(lp)
ex, err := a.parseExpression(in, lp)
if err != nil {
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) {
lp.IgnoreRun(whitespace)
for {
ex, err := a.parseExpression(lp)
ex, err := a.parseExpression(in, lp)
if err != nil {
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) {
lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp)
expr, err := a.parseExpression(in, lp)
if err != nil {
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) {
lp.IgnoreRun(whitespace)
expr, err := a.parseExpression(lp)
expr, err := a.parseExpression(in, lp)
if err != nil {
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) {
lp.IgnoreRun(whitespace)
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()
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
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
}
@ -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) {
lp.IgnoreRun(whitespace)
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.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) {
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
if lp.Accept("#/") {
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 {
return &expr.E{}, err
}
for lp.Accept(operatorChars) {
c := lp.Emit()
right, err := a.parseTerm(lp)
right, err := a.parseTerm(in, lp)
if err != nil {
return &expr.E{}, err
}
@ -462,7 +475,7 @@ func (a *SCMA) parseExpression(lp *lines.Parse) (*expr.E, error) {
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{}
top := ex
@ -482,12 +495,12 @@ func (a *SCMA) parseTerm(lp *lines.Parse) (*expr.E, error) {
if lp.Consume("$") {
if !lp.AcceptRun(hexdigits) {
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()
i, err := strconv.ParseUint(s, 16, 16)
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.Val = uint16(i)
@ -499,17 +512,29 @@ func (a *SCMA) parseTerm(lp *lines.Parse) (*expr.E, error) {
s := lp.Emit()
i, err := strconv.ParseUint(s, 10, 16)
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.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.Ignore()
return top, nil
}
// Label
if !lp.AcceptRun(labelChars) {
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

View File

@ -26,6 +26,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
{ss, "* Comment", "{-}", ""},
{aa, "; Comment", "{-}", ""},
{mm, "* Comment", "{-}", ""},
{ss, " far-out-comment", "{-}", ""},
{ss, "Label", "{- 'Label'}", ""},
{aa, "Label", "{- 'Label'}", ""},
{mm, "Label", "{- 'Label'}", ""},
@ -118,6 +119,7 @@ func TestSimpleCommonFunctions(t *testing.T) {
{ss, " .EN", "{end}", ""},
{ss, `>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:

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 {
if i.Label != "" && i.Label[0] == '.' {
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 {
if err := e.FixLabels(last); err != nil {
if err := e.FixLabels(last, i.Line); err != nil {
return err
}
}
@ -213,7 +213,7 @@ func (i *I) computeLabel(c context.Context, setWidth bool, final bool) error {
lval, lok := c.Get(i.Label)
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)
return nil
@ -234,7 +234,7 @@ func (i *I) computeData(c context.Context, setWidth bool, final bool) (bool, err
for _, e := range i.Exprs {
w := e.Width()
width += w
val, labelMissing, err := e.CheckedEval(c)
val, labelMissing, err := e.CheckedEval(c, i.Line)
if err != nil && !labelMissing {
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) {
val, err := i.Exprs[0].Eval(c)
val, err := i.Exprs[0].Eval(c, i.Line)
if err == nil {
i.Value = val
i.WidthKnown = true
@ -272,7 +272,7 @@ func (i *I) computeBlock(c context.Context, setWidth bool, final bool) (bool, er
i.MaxWidth = val
} else {
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
@ -283,7 +283,7 @@ func (i *I) computeMustKnow(c context.Context, setWidth bool, final bool) (bool,
i.MinWidth = 0
i.MaxWidth = 0
i.Final = true
val, err := i.Exprs[0].Eval(c)
val, err := i.Exprs[0].Eval(c, i.Line)
if err != nil {
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 {
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
if !labelMissing && err != nil {
return false, err
@ -372,17 +372,17 @@ func (i *I) computeOp(c context.Context, setWidth bool, final bool) (bool, error
curr, ok := c.GetAddr()
if !ok {
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
}
// Found both current and target addresses
offset := int32(val) - (int32(curr) + 2)
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 {
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)
}
@ -403,3 +403,11 @@ func (i *I) computeOp(c context.Context, setWidth bool, final bool) (bool, error
}
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 {
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...)
}