go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

commit 6dbc0041385cb5e4800833477999ea2da5689923
parent a713d562c6bc28620c669b63384634898e0604a5
Author: lash <dev@holbrook.no>
Date:   Fri, 31 Mar 2023 14:04:08 +0100

Add duplicate key check

Diffstat:
Mgo/state/state.go | 43++++++++++++++++++++++++++++++++++++++-----
Mgo/state/state_test.go | 36++++++++++++++++++++++++++++++++++++
Mgo/vm/vm.go | 26++++++++++++++++++++++++++
3 files changed, 100 insertions(+), 5 deletions(-)

diff --git a/go/state/state.go b/go/state/state.go @@ -41,13 +41,17 @@ func(st *State) Enter(input string) { st.CacheMap = make(map[string]string) } -func(st *State) Add(k string, v string) error { - sz := st.checkCapacity(v) +func(st *State) Add(key string, value string) error { + checkFrame := st.frameOf(key) + if checkFrame > -1 { + return fmt.Errorf("key %v already defined in frame %v", key, checkFrame) + } + sz := st.checkCapacity(value) if sz == 0 { return fmt.Errorf("Cache capacity exceeded %v of %v", st.CacheUseSize + sz, st.CacheSize) } - log.Printf("add key %s value size %v", k, sz) - st.Cache[len(st.Cache)-1][k] = v + log.Printf("add key %s value size %v", key, sz) + st.Cache[len(st.Cache)-1][key] = value st.CacheUseSize += sz return nil } @@ -61,11 +65,18 @@ func(st *State) Map(k string) error { return nil } +func(st *State) Depth() uint8 { + return uint8(len(st.Cache)) +} + func(st *State) Get() (map[string]string, error) { + if len(st.Cache) == 0 { + return nil, fmt.Errorf("get at top frame") + } return st.Cache[len(st.Cache)-1], nil } -func (st *State) Exit() error { +func(st *State) Exit() error { l := len(st.Cache) if l == 0 { return fmt.Errorf("exit called beyond top frame") @@ -81,6 +92,28 @@ func (st *State) Exit() error { return nil } +func(st *State) Reset() error { + st.Cache = st.Cache[:1] + st.CacheUseSize = 0 + return nil +} + +func(st *State) Check(key string) bool { + return st.frameOf(key) == -1 +} + +func(st *State) frameOf(key string) int { + log.Printf("--- %s", key) + for i, m := range st.Cache { + for k, _ := range m { + if k == key { + return i + } + } + } + return -1 +} + 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 @@ -73,3 +73,39 @@ func TestStateEnterExit(t *testing.T) { t.Errorf("expected out of top frame error") } } + +func TestStateReset(t *testing.T) { + st := NewState(17) + st.Enter("one") + err := st.Add("foo", "bar") + if err != nil { + t.Error(err) + } + err = st.Add("baz", "xyzzy") + if err != nil { + t.Error(err) + } + st.Enter("two") + st.Enter("three") + st.Reset() + if st.CacheUseSize != 0 { + t.Errorf("expected cache use size 0, got %v", st.CacheUseSize) + } + if st.Depth() != 1 { + t.Errorf("expected depth 1, got %v", st.Depth()) + } +} + +func TestStateLoadDup(t *testing.T) { + st := NewState(17) + st.Enter("one") + err := st.Add("foo", "bar") + if err != nil { + t.Error(err) + } + st.Enter("two") + err = st.Add("foo", "baz") + if err == nil { + t.Errorf("expected fail on duplicate load") + } +} diff --git a/go/vm/vm.go b/go/vm/vm.go @@ -37,7 +37,13 @@ func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Co } func instructionSplit(b []byte) (string, []byte, error) { + if len(b) == 0 { + return "", nil, fmt.Errorf("argument is empty") + } sz := uint8(b[0]) + if sz == 0 { + return "", nil, fmt.Errorf("zero-length argument") + } tailSz := uint8(len(b)) if tailSz - 1 < sz { return "", nil, fmt.Errorf("corrupt instruction, len %v less than symbol length: %v", tailSz, sz) @@ -61,10 +67,27 @@ func RunSink(instruction []byte, st state.State, rs resource.Fetcher, ctx contex } 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 } @@ -73,6 +96,9 @@ func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx contex 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