From a241c4865766ab16ec738f818fffa69dab919eee Mon Sep 17 00:00:00 2001 From: Zellyn Hunter Date: Sun, 4 May 2014 20:51:58 -0700 Subject: [PATCH] scma: assembles applesoft without erroring --- asm/asm.go | 41 ++++++------ asm/context/context.go | 11 +++- asm/doc.go | 2 + asm/expr/expression.go | 38 ++++++----- asm/flavors/as65/as65.go | 4 ++ asm/flavors/assemble_test.go | 7 +++ asm/flavors/merlin/merlin.go | 4 ++ asm/flavors/scma/scma.go | 105 +++++++++++++++++++------------ asm/flavors/simple_parse_test.go | 2 + asm/inst/instruction.go | 32 ++++++---- asm/lines/lines.go | 16 +++++ 11 files changed, 173 insertions(+), 89 deletions(-) diff --git a/asm/asm.go b/asm/asm.go index 61d477b..99e0d74 100644 --- a/asm/asm.go +++ b/asm/asm.go @@ -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...) } diff --git a/asm/context/context.go b/asm/context/context.go index 034cfb2..08bd0e7 100644 --- a/asm/context/context.go +++ b/asm/context/context.go @@ -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 diff --git a/asm/doc.go b/asm/doc.go index a5c2f11..eff5964 100644 --- a/asm/doc.go +++ b/asm/doc.go @@ -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 diff --git a/asm/expr/expression.go b/asm/expr/expression.go index 9c14fa2..6865eb0 100644 --- a/asm/expr/expression.go +++ b/asm/expr/expression.go @@ -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 diff --git a/asm/flavors/as65/as65.go b/asm/flavors/as65/as65.go index eab5200..bc13ad7 100644 --- a/asm/flavors/as65/as65.go +++ b/asm/flavors/as65/as65.go @@ -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 +} diff --git a/asm/flavors/assemble_test.go b/asm/flavors/assemble_test.go index 0714f8c..b257c5b 100644 --- a/asm/flavors/assemble_test.go +++ b/asm/flavors/assemble_test.go @@ -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", diff --git a/asm/flavors/merlin/merlin.go b/asm/flavors/merlin/merlin.go index 1f35d24..ce21da3 100644 --- a/asm/flavors/merlin/merlin.go +++ b/asm/flavors/merlin/merlin.go @@ -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") +} diff --git a/asm/flavors/scma/scma.go b/asm/flavors/scma/scma.go index 6a29851..3d794ec 100644 --- a/asm/flavors/scma/scma.go +++ b/asm/flavors/scma/scma.go @@ -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 diff --git a/asm/flavors/simple_parse_test.go b/asm/flavors/simple_parse_test.go index 13ea953..29b7084 100644 --- a/asm/flavors/simple_parse_test.go +++ b/asm/flavors/simple_parse_test.go @@ -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: diff --git a/asm/inst/instruction.go b/asm/inst/instruction.go index 906a72f..d5aa5a1 100644 --- a/asm/inst/instruction.go +++ b/asm/inst/instruction.go @@ -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...) +} diff --git a/asm/lines/lines.go b/asm/lines/lines.go index 2f44741..c8c4f6c 100644 --- a/asm/lines/lines.go +++ b/asm/lines/lines.go @@ -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...) +}