Skip to content

Commit

Permalink
Try to clean up literal errors
Browse files Browse the repository at this point in the history
  • Loading branch information
smoynes committed Oct 18, 2023
1 parent df238a3 commit a3f6510
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 19 deletions.
31 changes: 24 additions & 7 deletions internal/asm/asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func (s SymbolTable) Offset(sym string, pc uint16, n int) (uint16, error) {
}

bottom := ^(-1 << n)

return uint16(delta) & uint16(bottom), nil
}

Expand All @@ -118,6 +119,9 @@ var (

// ErrOperand causes a SyntaxError if an opcode's operands are invalid or incorrect.
ErrOperand = errors.New("operand error")

// ErrLiteral causes a SyntaxError if the literal operand is invalid.
ErrLiteral = errors.New("literal error")
)

// SyntaxError is a wrapped error returned when the assembler encounters a syntax error. If fields
Expand All @@ -141,15 +145,17 @@ func (se *SyntaxError) Error() string {
}
}

// Is checks if any SyntaxError's error-tree matches a target error.
// Is checks if SyntaxError's error-tree matches a target error.
func (se *SyntaxError) Is(target error) bool {
if se.Err != nil {
return errors.Is(target, se.Err)
} else if err, ok := target.(*SyntaxError); !ok {
return false
if errors.Is(se.Err, target) {
return true
} else if err, ok := target.(*SyntaxError); ok && errors.Is(err, err.Err) {
return true
} else {
return se.Line == err.Line && se.Pos == err.Pos &&
(se.File != "(unknown)" && se.File == err.File)
return se.Pos == err.Pos &&
se.Line == err.Line &&
se.Loc == err.Loc &&
se.File == err.File
}
}

Expand All @@ -163,6 +169,17 @@ func (oe *OffsetRangeError) Error() string {
return fmt.Sprintf("offset error: %0#4x", oe.Offset)
}

// LiteralRangeError is a wrapped error returned when an offset value exceeds its range.
type LiteralRangeError struct {
Literal string
Range uint8
}

func (le *LiteralRangeError) Error() string {
return fmt.Sprintf("literal range error: %q (%0#4x, %0#4x)",
le.Literal, -uint16(1<<(le.Range)), 1<<(le.Range-1))
}

// RegisterError is a wrapped error returned when an instruction names an invalid register.
type RegisterError struct {
op string
Expand Down
2 changes: 1 addition & 1 deletion internal/asm/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (br *BR) Parse(opcode string, opers []string) error {

off, sym, err := parseImmediate(opers[0], 9)
if err != nil {
return fmt.Errorf("br: operand error: %w", err)
return err
}

*br = BR{
Expand Down
20 changes: 13 additions & 7 deletions internal/asm/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ func parseImmediate(oper string, n uint8) (lit uint16, sym string, err error) {
// - -1
func parseLiteral(operand string, n uint8) (uint16, error) {
if len(operand) == 0 {
return 0xffff, fmt.Errorf("literal error: %s", operand)
return 0xffff, ErrLiteral
}

prefix := operand[0]
Expand All @@ -445,18 +445,24 @@ func parseLiteral(operand string, n uint8) (uint16, error) {
}

// The parsed value must not exceed n bits, i.e. its range is [0, 2ⁿ). Using strconv.Uint16
// seems like the thing to do. However, it does not accept negative literals, e.g.
// ADD R1,R1,#-1. So, we use n+1 here, giving us the range [-2ⁿ, 2ⁿ], and checking for overflow
// and converting to unsigned.
// seems like the thing to do. However, it does not accept negative decimal literals, e.g. ADD
// R1,R1,#-1, which we would like to handle. So, we use a signed integer with n+1 bits, giving
// us the range [-2ⁿ, 2ⁿ], and checking for overflow and converting to unsigned.
val64, err := strconv.ParseInt(literal, 0, int(n)+1)
if err != nil {
return 0xffff, fmt.Errorf("literal error: %s %d", operand, val64)
return 0xffff, &LiteralRangeError{
Literal: literal,
Range: n,
}
}

var bitmask int64 = 1<<n - 1

if val64 < -bitmask || val64 > bitmask { // 0 <= val64 < 2ⁿ
return 0xffff, fmt.Errorf("literal error: max: %x %s %x", 1<<n, operand, val64)
if val64 < -bitmask || val64 > bitmask {
return 0xffff, &LiteralRangeError{
Literal: literal,
Range: n,
}
}

val16 := uint16(val64) & uint16(bitmask)
Expand Down
40 changes: 37 additions & 3 deletions internal/asm/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ type errorCase struct {
want error
}

func TestParser_Errors(tt *testing.T) {
func TestAssembler_Errors(tt *testing.T) {
tt.Parallel()
t := ParserHarness{T: tt}

Expand Down Expand Up @@ -294,6 +294,39 @@ func TestParser_Errors(tt *testing.T) {
Err: nil,
},
},
{
name: "invalid opcode",
in: strings.NewReader(`XOR R1,R2`),
want: &SyntaxError{
Loc: 0,
Pos: 1,
File: "",
Line: `XOR R1,R2`,
Err: ErrOpcode,
},
},
{
name: "invalid operand count",
in: strings.NewReader(`AND R1`),
want: &SyntaxError{
Loc: 0,
Pos: 1,
File: "",
Line: `AND R1`,
Err: ErrOperand,
},
},
{
name: "immediate too large",
in: strings.NewReader(`BR #x7000`),
want: &SyntaxError{
Loc: 0,
Pos: 1,
File: "",
Line: `AND R1`,
Err: ErrLiteral,
},
},
}

for _, tc := range tcs {
Expand Down Expand Up @@ -350,14 +383,15 @@ func GenerateErrors(tc errorCase, t ParserHarness) {
}

func ParserError(err error, tc errorCase, t ParserHarness) {
t.Log(err.Error())
t.Logf("err: %v", err)

if err == tc.want {
t.Errorf("expected wrapped error: err: %#v, want: %#v", err, tc.want)
}

if !errors.Is(err, tc.want) {
t.Errorf("errors.Is: err: %#v, want: %#v", err, tc.want)
t.Errorf("errors.Is: err: %#[1]v", err)
t.Errorf("want: %#v", tc.want)

if wErr, ok := err.(interface{ Unwrap() []error }); ok {
for _, err := range wErr.Unwrap() {
Expand Down
3 changes: 2 additions & 1 deletion internal/cli/cmd/asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ func (a *assembler) Run(ctx context.Context, args []string, stdout io.Writer, lo

f, err := os.Open(fn)
if err != nil {
logger.Error("Parse error: %s: %s", fn, err)
logger.Error("Parse error", "err", err)
return 1
}

parser.Parse(f)
Expand Down

0 comments on commit a3f6510

Please sign in to comment.