diff --git a/go/state/state.go b/go/state/state.go index 4482ea5..cf7e202 100644 --- a/go/state/state.go +++ b/go/state/state.go @@ -12,6 +12,7 @@ type State struct { Cache []map[string]string CacheMap map[string]string ExecPath []string + Idx uint16 } func NewState(bitSize uint64) State { @@ -35,13 +36,13 @@ func(st State) WithCacheSize(cacheSize uint32) State { return st } -func(st *State) Enter(input string) { +func(st *State) Down(input string) { m := make(map[string]string) st.Cache = append(st.Cache, m) st.CacheMap = make(map[string]string) } -func(st *State) Add(key string, value string) error { +func(st *State) Add(key string, value string, sizeHint uint32) error { checkFrame := st.frameOf(key) if checkFrame > -1 { return fmt.Errorf("key %v already defined in frame %v", key, checkFrame) @@ -53,6 +54,26 @@ func(st *State) Add(key string, value string) error { log.Printf("add key %s value size %v", key, sz) st.Cache[len(st.Cache)-1][key] = value st.CacheUseSize += sz + _ = sizeHint + return nil +} + +func(st *State) Update(key string, value string) error { + checkFrame := st.frameOf(key) + if checkFrame == -1 { + return fmt.Errorf("key %v not defined", key) + } + r := st.Cache[checkFrame][key] + l := uint32(len(r)) + st.Cache[checkFrame][key] = "" + st.CacheUseSize -= l + sz := st.checkCapacity(value) + if sz == 0 { + baseUseSize := st.CacheUseSize + st.Cache[checkFrame][key] = r + st.CacheUseSize += l + return fmt.Errorf("Cache capacity exceeded %v of %v", baseUseSize + sz, st.CacheSize) + } return nil } @@ -76,7 +97,7 @@ func(st *State) Get() (map[string]string, error) { return st.Cache[len(st.Cache)-1], nil } -func(st *State) Exit() error { +func(st *State) Up() error { l := len(st.Cache) if l == 0 { return fmt.Errorf("exit called beyond top frame") @@ -92,16 +113,20 @@ func(st *State) Exit() error { return nil } -func(st *State) Reset() error { +func(st *State) Reset() { + if len(st.Cache) == 0 { + return + } st.Cache = st.Cache[:1] st.CacheUseSize = 0 - return nil + return } func(st *State) Check(key string) bool { return st.frameOf(key) == -1 } +// return 0-indexed frame number where key is defined. -1 if not defined func(st *State) frameOf(key string) int { log.Printf("--- %s", key) for i, m := range st.Cache { @@ -114,6 +139,8 @@ func(st *State) frameOf(key string) int { return -1 } +// bytes that will be added to cache use size for string +// returns 0 if capacity would be exceeded func(st *State) checkCapacity(v string) uint32 { sz := uint32(len(v)) if st.CacheSize == 0 { diff --git a/go/state/state_test.go b/go/state/state_test.go index 743b929..7f20622 100644 --- a/go/state/state_test.go +++ b/go/state/state_test.go @@ -35,40 +35,40 @@ func TestNewStateCache(t *testing.T) { func TestStateCacheUse(t *testing.T) { st := NewState(17) st = st.WithCacheSize(10) - st.Enter("foo") - err := st.Add("bar", "baz") + st.Down("foo") + err := st.Add("bar", "baz", 0) if err != nil { t.Error(err) } - err = st.Add("inky", "pinky") + err = st.Add("inky", "pinky", 0) if err != nil { t.Error(err) } - err = st.Add("blinky", "clyde") + err = st.Add("blinky", "clyde", 0) if err == nil { t.Errorf("expected capacity error") } } -func TestStateEnterExit(t *testing.T) { +func TestStateDownUp(t *testing.T) { st := NewState(17) - st.Enter("one") - err := st.Add("foo", "bar") + st.Down("one") + err := st.Add("foo", "bar", 0) if err != nil { t.Error(err) } - err = st.Add("baz", "xyzzy") + err = st.Add("baz", "xyzzy", 0) if err != nil { t.Error(err) } if st.CacheUseSize != 8 { t.Errorf("expected cache use size 8 got %v", st.CacheUseSize) } - err = st.Exit() + err = st.Up() if err != nil { t.Error(err) } - err = st.Exit() + err = st.Up() if err == nil { t.Errorf("expected out of top frame error") } @@ -76,17 +76,17 @@ func TestStateEnterExit(t *testing.T) { func TestStateReset(t *testing.T) { st := NewState(17) - st.Enter("one") - err := st.Add("foo", "bar") + st.Down("one") + err := st.Add("foo", "bar", 0) if err != nil { t.Error(err) } - err = st.Add("baz", "xyzzy") + err = st.Add("baz", "xyzzy", 0) if err != nil { t.Error(err) } - st.Enter("two") - st.Enter("three") + st.Down("two") + st.Down("three") st.Reset() if st.CacheUseSize != 0 { t.Errorf("expected cache use size 0, got %v", st.CacheUseSize) @@ -98,13 +98,13 @@ func TestStateReset(t *testing.T) { func TestStateLoadDup(t *testing.T) { st := NewState(17) - st.Enter("one") - err := st.Add("foo", "bar") + st.Down("one") + err := st.Add("foo", "bar", 0) if err != nil { t.Error(err) } - st.Enter("two") - err = st.Add("foo", "baz") + st.Down("two") + err = st.Add("foo", "baz", 0) if err == nil { t.Errorf("expected fail on duplicate load") } diff --git a/go/vm/vm.go b/go/vm/vm.go index 83e0cfc..dbcd8ba 100644 --- a/go/vm/vm.go +++ b/go/vm/vm.go @@ -9,31 +9,116 @@ import ( "git.defalsify.org/festive/resource" ) -type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) +type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) -func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - op := binary.BigEndian.Uint16(instruction[:2]) - if op > _MAX { - return st, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX) +func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + var err error + for len(instruction) > 0 { + op := binary.BigEndian.Uint16(instruction[:2]) + if op > _MAX { + return st, instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX) + } + switch op { + case CATCH: + st, instruction, err = RunCatch(instruction[2:], st, rs, ctx) + case CROAK: + st, instruction, err = RunCroak(instruction[2:], st, rs, ctx) + case LOAD: + st, instruction, err = RunLoad(instruction[2:], st, rs, ctx) + case RELOAD: + st, instruction, err = RunReload(instruction[2:], st, rs, ctx) + case MAP: + st, instruction, err = RunMap(instruction[2:], st, rs, ctx) + case SINK: + st, instruction, err = RunSink(instruction[2:], st, rs, ctx) + default: + err = fmt.Errorf("Unhandled state: %v", op) + } + if err != nil { + return st, instruction, err + } } - switch op { - case CATCH: - RunCatch(instruction[2:], st, rs, ctx) - case CROAK: - RunCroak(instruction[2:], st, rs, ctx) - case LOAD: - RunLoad(instruction[2:], st, rs, ctx) - case RELOAD: - RunReload(instruction[2:], st, rs, ctx) - case MAP: - RunMap(instruction[2:], st, rs, ctx) - case SINK: - RunSink(instruction[2:], st, rs, ctx) - default: - err := fmt.Errorf("Unhandled state: %v", op) - return st, err + return st, instruction, nil +} + +func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + head, tail, err := instructionSplit(instruction) + if err != nil { + return st, instruction, err } - return st, nil + st.Map(head) + return st, tail, nil +} + +func RunSink(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + return st, nil, nil +} + +func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + head, tail, err := instructionSplit(instruction) + if err != nil { + return st, instruction, err + } + r, err := rs.Get(head) + if err != nil { + return st, instruction, err + } + st.Add(head, r, uint32(len(r))) + return st, tail, nil +} + +func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + head, tail, err := instructionSplit(instruction) + if err != nil { + return st, instruction, err + } + _ = head + st.Reset() + return st, tail, nil +} + +func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + head, tail, err := instructionSplit(instruction) + if err != nil { + return st, instruction, err + } + if !st.Check(head) { + return st, instruction, fmt.Errorf("key %v already loaded", head) + } + sz := uint32(tail[0]) + tail = tail[1:] + + r, err := refresh(head, tail, rs, ctx) + if err != nil { + return st, tail, err + } + st.Add(head, r, sz) + return st, tail, nil +} + +func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + head, tail, err := instructionSplit(instruction) + if err != nil { + return st, instruction, err + } + r, err := refresh(head, tail, rs, ctx) + if err != nil { + return st, tail, err + } + st.Add(head, r, uint32(len(r))) + return st, tail, nil +} + +func RunMove(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + return st, nil, nil +} + +func refresh(key string, sym []byte, rs resource.Fetcher, ctx context.Context) (string, error) { + fn, err := rs.FuncFor(key) + if err != nil { + return "", err + } + return fn(sym, ctx) } func instructionSplit(b []byte) (string, []byte, error) { @@ -45,72 +130,9 @@ func instructionSplit(b []byte) (string, []byte, error) { return "", nil, fmt.Errorf("zero-length argument") } tailSz := uint8(len(b)) - if tailSz - 1 < sz { + if tailSz < sz { return "", nil, fmt.Errorf("corrupt instruction, len %v less than symbol length: %v", tailSz, sz) } r := string(b[1:1+sz]) return r, b[1+sz:], nil } - -func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - head, tail, err := instructionSplit(instruction) - if err != nil { - return st, err - } - _ = tail - st.Map(head) - return st, nil -} - -func RunSink(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - return st, nil -} - -func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - head, tail, err := instructionSplit(instruction) - if err != nil { - return st, err - } - r, err := rs.Get(head) - if err != nil { - return st, err - } - _ = tail - st.Add(head, r) - return st, nil -} - -func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - head, tail, err := instructionSplit(instruction) - if err != nil { - return st, err - } - _ = head - _ = tail - st.Reset() - return st, nil -} - -func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - head, tail, err := instructionSplit(instruction) - if err != nil { - return st, err - } - if !st.Check(head) { - return st, fmt.Errorf("key %v already loaded", head) - } - fn, err := rs.FuncFor(head) - if err != nil { - return st, err - } - r, err := fn(tail, ctx) - if err != nil { - return st, err - } - st.Add(head, r) - return st, nil -} - -func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - return st, nil -} diff --git a/go/vm/vm_test.go b/go/vm/vm_test.go index db70218..f951793 100644 --- a/go/vm/vm_test.go +++ b/go/vm/vm_test.go @@ -63,14 +63,15 @@ func (r *TestResource) FuncFor(sym string) (resource.EntryFunc, error) { func TestRun(t *testing.T) { st := state.NewState(5) rs := TestResource{} - b := []byte{0x00, 0x01} - r, err := Run(b, st, &rs, context.TODO()) + b := []byte{0x00, 0x01, 0x03} + b = append(b, []byte("foo")...) + r, _, err := Run(b, st, &rs, context.TODO()) if err != nil { t.Errorf("error on valid opcode: %v", err) } b = []byte{0x01, 0x02} - r, err = Run(b, st, &rs, context.TODO()) + r, _, err = Run(b, st, &rs, context.TODO()) if err == nil { t.Errorf("no error on invalid opcode") } @@ -79,12 +80,13 @@ func TestRun(t *testing.T) { func TestRunLoad(t *testing.T) { st := state.NewState(5) - st.Enter("barbarbar") + st.Down("barbarbar") rs := TestResource{} sym := "one" ins := append([]byte{uint8(len(sym))}, []byte(sym)...) + ins = append(ins, 0x0a) var err error - st, err = RunLoad(ins, st, &rs, context.TODO()) + st, _, err = RunLoad(ins, st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -108,7 +110,8 @@ func TestRunLoad(t *testing.T) { sym = "two" ins = append([]byte{uint8(len(sym))}, []byte(sym)...) - st, err = RunLoad(ins, st, &rs, context.TODO()) + ins = append(ins, 0) + st, _, err = RunLoad(ins, st, &rs, context.TODO()) if err != nil { t.Error(err) }