commit aec0564ceae11caee70e02393a093489099f7c0f
parent 8bd0c44a615ac3bc42d1d06db1aa0edb6e79f9de
Author: lash <dev@holbrook.no>
Date: Sun, 2 Apr 2023 13:59:40 +0100
Factor out instruction parse from runner in vm
Diffstat:
M | go/vm/runner.go | | | 206 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
M | go/vm/runner_test.go | | | 43 | ++++++++++++++++++++++++------------------- |
M | go/vm/vm.go | | | 108 | +++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- |
M | go/vm/vm_test.go | | | 60 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
4 files changed, 259 insertions(+), 158 deletions(-)
diff --git a/go/vm/runner.go b/go/vm/runner.go
@@ -1,7 +1,6 @@
package vm
import (
- "encoding/binary"
"context"
"fmt"
"log"
@@ -17,176 +16,183 @@ import (
// Each step may update the state.
//
// On error, the remaining instructions will be returned. State will not be rolled back.
-func Run(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- var err error
- for len(instruction) > 0 {
- log.Printf("instruction is now 0x%x", instruction)
- op := binary.BigEndian.Uint16(instruction[:2])
- if op > _MAX {
- return instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX)
+func Run(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+ running := true
+ for running {
+ log.Printf("code before 0x%x", b)
+ op, bb, err := opSplit(b)
+ if err != nil {
+ return b, err
}
+ b = bb
+ log.Printf("code after 0x%x", b)
switch op {
case CATCH:
- instruction, err = RunCatch(instruction[2:], st, rs, ctx)
+ b, err = RunCatch(b, st, rs, ctx)
case CROAK:
- instruction, err = RunCroak(instruction[2:], st, rs, ctx)
+ b, err = RunCroak(b, st, rs, ctx)
case LOAD:
- instruction, err = RunLoad(instruction[2:], st, rs, ctx)
+ b, err = RunLoad(b, st, rs, ctx)
case RELOAD:
- instruction, err = RunReload(instruction[2:], st, rs, ctx)
+ b, err = RunReload(b, st, rs, ctx)
case MAP:
- instruction, err = RunMap(instruction[2:], st, rs, ctx)
+ b, err = RunMap(b, st, rs, ctx)
case MOVE:
- instruction, err = RunMove(instruction[2:], st, rs, ctx)
+ b, err = RunMove(b, st, rs, ctx)
case INCMP:
- instruction, err = RunIncmp(instruction[2:], st, rs, ctx)
+ b, err = RunInCmp(b, st, rs, ctx)
+ log.Printf("bb %v", b)
case HALT:
- return RunHalt(instruction[2:], st, rs, ctx)
+ b, err = RunHalt(b, st, rs, ctx)
+ return b, err
default:
err = fmt.Errorf("Unhandled state: %v", op)
}
if err != nil {
- return instruction, err
+ return b, err
+ }
+ log.Printf("aa %v", b)
+ if len(b) == 0 {
+ return []byte{}, nil
}
}
- return instruction, nil
+ return b, nil
}
// RunMap executes the MAP opcode
-func RunMap(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- head, tail, err := instructionSplit(instruction)
- if err != nil {
- return instruction, err
- }
- err = st.Map(head)
- return tail, err
+func RunMap(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+ sym, b, err := ParseMap(b)
+ err = st.Map(sym)
+ return b, err
}
// RunMap executes the CATCH opcode
-func RunCatch(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- head, tail, err := instructionSplit(instruction)
+func RunCatch(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+ sym, sig, mode, b, err := ParseCatch(b)
if err != nil {
- return instruction, err
- }
- bitFieldSize := tail[0]
- bitField := tail[1:1+bitFieldSize]
- tail = tail[1+bitFieldSize:]
- matchMode := tail[0] // matchmode 1 is match NOT set bit
- tail = tail[1:]
- match := false
- if matchMode > 0 {
- if !st.GetIndex(bitField) {
- match = true
- }
- } else if st.GetIndex(bitField) {
- match = true
+ return b, err
}
-
- if match {
- log.Printf("catch at flag %v, moving to %v", bitField, head)
- st.Down(head)
- tail = []byte{}
+ r, err := matchFlag(st, sig, mode)
+ if err != nil {
+ return b, err
+ }
+ if r {
+ log.Printf("catch at flag %v, moving to %v", sig, sym) //bitField, d)
+ st.Down(sym)
+ b = []byte{}
}
- return tail, nil
+ return b, nil
}
// RunMap executes the CROAK opcode
-func RunCroak(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- head, tail, err := instructionSplit(instruction)
+func RunCroak(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+ sig, mode, b, err := ParseCroak(b)
+ if err != nil {
+ return b, err
+ }
+ r, err := matchFlag(st, sig, mode)
if err != nil {
- return instruction, err
+ return b, err
+ }
+ if r {
+ log.Printf("croak at flag %v, purging and moving to top", sig)
+ st.Reset()
+ b = []byte{}
}
- _ = head
- _ = tail
- st.Reset()
return []byte{}, nil
}
// RunLoad executes the LOAD opcode
-func RunLoad(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- head, tail, err := instructionSplit(instruction)
+func RunLoad(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+// head, tail, err := instructionSplit(b)
+// if err != nil {
+// return b, err
+// }
+// if !st.Check(head) {
+// return b, fmt.Errorf("key %v already loaded", head)
+// }
+// sz := uint16(tail[0])
+// tail = tail[1:]
+ sym, sz, b, err := ParseLoad(b)
if err != nil {
- return instruction, err
- }
- if !st.Check(head) {
- return instruction, fmt.Errorf("key %v already loaded", head)
+ return b, err
}
- sz := uint16(tail[0])
- tail = tail[1:]
- r, err := refresh(head, rs, ctx)
+ r, err := refresh(sym, rs, ctx)
if err != nil {
- return tail, err
+ return b, err
}
- err = st.Add(head, r, sz)
- return tail, err
+ err = st.Add(sym, r, uint16(sz))
+ return b, err
}
// RunLoad executes the RELOAD opcode
-func RunReload(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- head, tail, err := instructionSplit(instruction)
+func RunReload(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+// head, tail, err := instructionSplit(b)
+// if err != nil {
+// return b, err
+// }
+ sym, b, err := ParseReload(b)
if err != nil {
- return instruction, err
+ return b, err
}
- r, err := refresh(head, rs, ctx)
+
+ r, err := refresh(sym, rs, ctx)
if err != nil {
- return tail, err
+ return b, err
}
- st.Update(head, r)
- return tail, nil
+ st.Update(sym, r)
+ return b, nil
}
// RunLoad executes the MOVE opcode
-func RunMove(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- head, tail, err := instructionSplit(instruction)
+func RunMove(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+ sym, b, err := ParseMove(b)
+// head, tail, err := instructionSplit(b)
if err != nil {
- return instruction, err
+ return b, err
}
- st.Down(head)
- return tail, nil
-}
-
-// RunLoad executes the BACK opcode
-func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- st.Up()
- return instruction, nil
+ st.Down(sym)
+ return b, nil
}
// RunIncmp executes the INCMP opcode
-func RunIncmp(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
- head, tail, err := instructionSplit(instruction)
+func RunInCmp(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+ //head, tail, err := instructionSplit(b)
+ sym, target, b, err := ParseInCmp(b)
if err != nil {
- return instruction, err
- }
- sym, tail, err := instructionSplit(tail)
- if err != nil {
- return instruction, err
+ return b, err
}
v, err := st.GetFlag(state.FLAG_INMATCH)
if err != nil {
- return tail, err
+ return b, err
}
if v {
- return tail, nil
+ return b, nil
}
input, err := st.GetInput()
if err != nil {
- return tail, err
+ return b, err
}
- log.Printf("checking input %v %v", input, head)
- if head == string(input) {
+ if sym == string(input) {
log.Printf("input match for '%s'", input)
_, err = st.SetFlag(state.FLAG_INMATCH)
- st.Down(sym)
+ st.Down(target)
}
- return tail, err
+ log.Printf("b last %v", b)
+ return b, err
}
// RunHalt executes the HALT opcode
-func RunHalt(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+func RunHalt(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
+ var err error
+ b, err = ParseHalt(b)
+ if err != nil {
+ return b, err
+ }
log.Printf("found HALT, stopping")
- _, err := st.ResetFlag(state.FLAG_INMATCH)
- return instruction, err
+ _, err = st.ResetFlag(state.FLAG_INMATCH)
+ return b, err
}
diff --git a/go/vm/runner_test.go b/go/vm/runner_test.go
@@ -80,11 +80,13 @@ func(r *TestResource) GetCode(sym string) ([]byte, error) {
func TestRun(t *testing.T) {
st := state.NewState(5)
rs := TestResource{}
- b := []byte{0x00, MOVE, 0x03}
- b = append(b, []byte("foo")...)
+
+ b := NewLine(nil, MOVE, []string{"foo"}, nil, nil)
+ //b := []byte{0x00, MOVE, 0x03}
+ //b = append(b, []byte("foo")...)
_, err := Run(b, &st, &rs, context.TODO())
if err != nil {
- t.Errorf("error on valid opcode: %v", err)
+ t.Errorf("run error: %v", err)
}
b = []byte{0x01, 0x02}
@@ -98,11 +100,10 @@ func TestRunLoadRender(t *testing.T) {
st := state.NewState(5)
st.Down("barbarbar")
rs := TestResource{}
- sym := "one"
- ins := append([]byte{uint8(len(sym))}, []byte(sym)...)
- ins = append(ins, 0x0a)
+
var err error
- _, err = RunLoad(ins, &st, &rs, context.TODO())
+ b := NewLine(nil, LOAD, []string{"one"}, []byte{0x0a}, nil)
+ b, err = Run(b, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@@ -124,10 +125,13 @@ func TestRunLoadRender(t *testing.T) {
t.Errorf("expected error for render of bar: %v" ,err)
}
- sym = "two"
- ins = append([]byte{uint8(len(sym))}, []byte(sym)...)
- ins = append(ins, 0)
- _, err = RunLoad(ins, &st, &rs, context.TODO())
+ b = NewLine(nil, LOAD, []string{"two"}, []byte{0x0a}, nil)
+ b, err = Run(b, &st, &rs, context.TODO())
+ if err != nil {
+ t.Error(err)
+ }
+ b = NewLine(nil, MAP, []string{"one"}, nil, nil)
+ _, err = Run(b, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@@ -148,20 +152,21 @@ func TestRunLoadRender(t *testing.T) {
func TestRunMultiple(t *testing.T) {
st := state.NewState(5)
rs := TestResource{}
- b := []byte{}
- b = NewLine(b, LOAD, []string{"one"}, nil, []uint8{0})
- b = NewLine(b, LOAD, []string{"two"}, nil, []uint8{42})
- _, err := Run(b, &st, &rs, context.TODO())
+ b := NewLine(nil, LOAD, []string{"one"}, []byte{0x00}, nil)
+ b = NewLine(b, LOAD, []string{"two"}, []byte{42}, nil)
+ b, err := Run(b, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
+ if len(b) > 0 {
+ t.Errorf("expected empty code")
+ }
}
func TestRunReload(t *testing.T) {
st := state.NewState(5)
rs := TestResource{}
- b := []byte{}
- b = NewLine(b, LOAD, []string{"dyn"}, nil, []uint8{0})
+ b := NewLine(nil, LOAD, []string{"dyn"}, nil, []uint8{0})
b = NewLine(b, MAP, []string{"dyn"}, nil, nil)
_, err := Run(b, &st, &rs, context.TODO())
if err != nil {
@@ -242,8 +247,8 @@ func TestRunInputHandler(t *testing.T) {
bi := NewLine([]byte{}, INCMP, []string{"bar", "aiee"}, nil, nil)
bi = NewLine(bi, INCMP, []string{"baz", "foo"}, nil, nil)
- bi = NewLine(bi, LOAD, []string{"one"}, nil, []uint8{0})
- bi = NewLine(bi, LOAD, []string{"two"}, nil, []uint8{3})
+ bi = NewLine(bi, LOAD, []string{"one"}, []byte{0x00}, nil)
+ bi = NewLine(bi, LOAD, []string{"two"}, []byte{0x03}, nil)
bi = NewLine(bi, MAP, []string{"one"}, nil, nil)
bi = NewLine(bi, MAP, []string{"two"}, nil, nil)
diff --git a/go/vm/vm.go b/go/vm/vm.go
@@ -3,50 +3,55 @@ package vm
import (
"encoding/binary"
"fmt"
+
+ "git.defalsify.org/festive/state"
)
+func Parse(b []byte) (Opcode, []byte, error) {
+ op, b, err := opSplit(b)
+ if err != nil {
+ return NOOP, b, err
+ }
+ return op, b, nil
+}
func ParseLoad(b []byte) (string, uint32, []byte, error) {
- return parseSymLen(b, LOAD)
+ return parseSymLen(b)
}
func ParseReload(b []byte) (string, []byte, error) {
- return parseSym(b, RELOAD)
+ return parseSym(b)
}
func ParseMap(b []byte) (string, []byte, error) {
- return parseSym(b, MAP)
+ return parseSym(b)
}
func ParseMove(b []byte) (string, []byte, error) {
- return parseSym(b, MOVE)
+ return parseSym(b)
}
func ParseHalt(b []byte) ([]byte, error) {
- return parseNoArg(b, HALT)
+ return parseNoArg(b)
}
-func ParseCatch(b []byte) (string, uint8, []byte, error) {
- return parseSymSig(b, CATCH)
+func ParseCatch(b []byte) (string, uint32, bool, []byte, error) {
+ return parseSymSig(b)
}
-func ParseCroak(b []byte) (string, uint8, []byte, error) {
- return parseSymSig(b, CROAK)
+func ParseCroak(b []byte) (uint32, bool, []byte, error) {
+ return parseSig(b)
}
func ParseInCmp(b []byte) (string, string, []byte, error) {
- return parseTwoSym(b, INCMP)
+ return parseTwoSym(b)
}
-func parseNoArg(b []byte, op Opcode) ([]byte, error) {
- return opCheck(b, op)
+func parseNoArg(b []byte) ([]byte, error) {
+ return b, nil
}
-func parseSym(b []byte, op Opcode) (string, []byte, error) {
- b, err := opCheck(b, op)
- if err != nil {
- return "", b, err
- }
+func parseSym(b []byte) (string, []byte, error) {
sym, tail, err := instructionSplit(b)
if err != nil {
return "", b, err
@@ -54,11 +59,7 @@ func parseSym(b []byte, op Opcode) (string, []byte, error) {
return sym, tail, nil
}
-func parseTwoSym(b []byte, op Opcode) (string, string, []byte, error) {
- b, err := opCheck(b, op)
- if err != nil {
- return "", "", b, err
- }
+func parseTwoSym(b []byte) (string, string, []byte, error) {
symOne, tail, err := instructionSplit(b)
if err != nil {
return "", "", b, err
@@ -70,11 +71,7 @@ func parseTwoSym(b []byte, op Opcode) (string, string, []byte, error) {
return symOne, symTwo, tail, nil
}
-func parseSymLen(b []byte, op Opcode) (string, uint32, []byte, error) {
- b, err := opCheck(b, op)
- if err != nil {
- return "", 0, b, err
- }
+func parseSymLen(b []byte) (string, uint32, []byte, error) {
sym, tail, err := instructionSplit(b)
if err != nil {
return "", 0, b, err
@@ -86,21 +83,51 @@ func parseSymLen(b []byte, op Opcode) (string, uint32, []byte, error) {
return sym, sz, tail, nil
}
-func parseSymSig(b []byte, op Opcode) (string, uint8, []byte, error) {
- b, err := opCheck(b, op)
+func parseSymSig(b []byte) (string, uint32, bool, []byte, error) {
+ sym, tail, err := instructionSplit(b)
if err != nil {
- return "", 0, b, err
+ return "", 0, false, b, err
}
- sym, tail, err := instructionSplit(b)
+ sig, tail, err := intSplit(tail)
if err != nil {
- return "", 0, b, err
+ return "", 0, false, b, err
}
if len(tail) == 0 {
- return "", 0, b, fmt.Errorf("instruction too short")
+ return "", 0, false, b, fmt.Errorf("instruction too short")
}
- n := tail[0]
+ matchmode := tail[0] > 0
tail = tail[1:]
- return sym, n, tail, nil
+
+ return sym, sig, matchmode, tail, nil
+}
+
+func parseSig(b []byte) (uint32, bool, []byte, error) {
+ sig, b, err := intSplit(b)
+ if err != nil {
+ return 0, false, b, err
+ }
+ if len(b) == 0 {
+ return 0, false, b, fmt.Errorf("instruction too short")
+ }
+ matchmode := b[0] > 0
+ b = b[1:]
+
+ return sig, matchmode, b, nil
+}
+
+func matchFlag(st *state.State, sig uint32, invertMatch bool) (bool, error) {
+ r, err := st.GetFlag(sig)
+ if err != nil {
+ return false, err
+ }
+ if invertMatch {
+ if !r {
+ return true, nil
+ }
+ } else if r {
+ return true, nil
+ }
+ return false, nil
}
// NewLine creates a new instruction line for the VM.
@@ -124,6 +151,13 @@ func NewLine(instructionList []byte, instruction uint16, strargs []string, bytea
return append(instructionList, b...)
}
+func byteSplit(b []byte) ([]byte, []byte, error) {
+ bitFieldSize := b[0]
+ bitField := b[1:1+bitFieldSize]
+ b = b[1+bitFieldSize:]
+ return bitField, b, nil
+}
+
func intSplit(b []byte) (uint32, []byte, error) {
l := uint8(b[0])
sz := uint32(l)
@@ -163,10 +197,12 @@ func instructionSplit(b []byte) (string, []byte, error) {
}
func opCheck(b []byte, opIn Opcode) ([]byte, error) {
- op, b, err := opSplit(b)
+ var bb []byte
+ op, bb, err := opSplit(b)
if err != nil {
return b, err
}
+ b = bb
if op != opIn {
return b, fmt.Errorf("not a %v instruction", op)
}
diff --git a/go/vm/vm_test.go b/go/vm/vm_test.go
@@ -6,14 +6,19 @@ import (
func TestParseNoArg(t *testing.T) {
b := NewLine(nil, HALT, nil, nil, nil)
+ _, b, _ = opSplit(b)
b, err := ParseHalt(b)
if err != nil {
t.Fatal(err)
}
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
}
func TestParseSym(t *testing.T) {
b := NewLine(nil, MAP, []string{"baz"}, nil, nil)
+ _, b, _ = opSplit(b)
sym, b, err := ParseMap(b)
if err != nil {
t.Fatal(err)
@@ -21,8 +26,12 @@ func TestParseSym(t *testing.T) {
if sym != "baz" {
t.Fatalf("expected sym baz, got %v", sym)
}
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
b = NewLine(nil, RELOAD, []string{"xyzzy"}, nil, nil)
+ _, b, _ = opSplit(b)
sym, b, err = ParseReload(b)
if err != nil {
t.Fatal(err)
@@ -30,8 +39,12 @@ func TestParseSym(t *testing.T) {
if sym != "xyzzy" {
t.Fatalf("expected sym xyzzy, got %v", sym)
}
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
b = NewLine(nil, MOVE, []string{"plugh"}, nil, nil)
+ _, b, _ = opSplit(b)
sym, b, err = ParseMove(b)
if err != nil {
t.Fatal(err)
@@ -39,10 +52,14 @@ func TestParseSym(t *testing.T) {
if sym != "plugh" {
t.Fatalf("expected sym plugh, got %v", sym)
}
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
}
func TestParseTwoSym(t *testing.T) {
b := NewLine(nil, INCMP, []string{"foo", "bar"}, nil, nil)
+ _, b, _ = opSplit(b)
one, two, b, err := ParseInCmp(b)
if err != nil {
t.Fatal(err)
@@ -53,24 +70,53 @@ func TestParseTwoSym(t *testing.T) {
if two != "bar" {
t.Fatalf("expected symtwo bar, got %v", two)
}
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
+}
+
+func TestParseSig(t *testing.T) {
+ b := NewLine(nil, CROAK, nil, []byte{0x0b, 0x13}, []uint8{0x04})
+ _, b, _ = opSplit(b)
+ n, m, b, err := ParseCroak(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n != 2835 {
+ t.Fatalf("expected n 13, got %v", n)
+ }
+ if !m {
+ t.Fatalf("expected m true")
+ }
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
}
func TestParseSymSig(t *testing.T) {
- b := NewLine(nil, CATCH, []string{"baz"}, nil, []uint8{0x0d})
- sym, n, b, err := ParseCatch(b)
+ b := NewLine(nil, CATCH, []string{"baz"}, []byte{0x0a, 0x13}, []uint8{0x01})
+ _, b, _ = opSplit(b)
+ sym, n, m, b, err := ParseCatch(b)
if err != nil {
t.Fatal(err)
}
if sym != "baz" {
t.Fatalf("expected sym baz, got %v", sym)
}
- if n != 13 {
+ if n != 2579 {
t.Fatalf("expected n 13, got %v", n)
}
+ if !m {
+ t.Fatalf("expected m true")
+ }
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
}
func TestParseSymAndLen(t *testing.T) {
b := NewLine(nil, LOAD, []string{"foo"}, []byte{0x2a}, nil)
+ _, b, _ = opSplit(b)
sym, n, b, err := ParseLoad(b)
if err != nil {
t.Fatal(err)
@@ -83,6 +129,7 @@ func TestParseSymAndLen(t *testing.T) {
}
b = NewLine(nil, LOAD, []string{"bar"}, []byte{0x02, 0x9a}, nil)
+ _, b, _ = opSplit(b)
sym, n, b, err = ParseLoad(b)
if err != nil {
t.Fatal(err)
@@ -93,8 +140,12 @@ func TestParseSymAndLen(t *testing.T) {
if n != 666 {
t.Fatalf("expected n 666, got %v", n)
}
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
b = NewLine(nil, LOAD, []string{"baz"}, []byte{0x0}, nil)
+ _, b, _ = opSplit(b)
sym, n, b, err = ParseLoad(b)
if err != nil {
t.Fatal(err)
@@ -105,4 +156,7 @@ func TestParseSymAndLen(t *testing.T) {
if n != 0 {
t.Fatalf("expected n 666, got %v", n)
}
+ if len(b) > 0 {
+ t.Fatalf("expected empty code")
+ }
}