commit 6dbc0041385cb5e4800833477999ea2da5689923
parent a713d562c6bc28620c669b63384634898e0604a5
Author: lash <dev@holbrook.no>
Date: Fri, 31 Mar 2023 14:04:08 +0100
Add duplicate key check
Diffstat:
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