commit ee55f3e5fe7e37a18bef715e962495dd0d69884d
parent 6dbc0041385cb5e4800833477999ea2da5689923
Author: lash <dev@holbrook.no>
Date: Fri, 31 Mar 2023 15:03:54 +0100
Add run loop
Diffstat:
4 files changed, 148 insertions(+), 96 deletions(-)
diff --git 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
@@ -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
@@ -9,108 +9,130 @@ 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, nil
+ return st, instruction, nil
}
-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)
- }
- 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) {
+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, err
+ return st, instruction, err
}
- _ = tail
st.Map(head)
- return st, nil
+ return st, tail, nil
}
-func RunSink(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) {
- return st, 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, error) {
+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, err
+ return st, instruction, err
}
r, err := rs.Get(head)
if err != nil {
- return st, err
+ return st, instruction, err
}
- _ = tail
- st.Add(head, r)
- return st, nil
+ 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, error) {
+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, err
+ return st, instruction, err
}
_ = head
- _ = tail
st.Reset()
- return st, nil
+ return st, tail, nil
}
-func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) {
+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, err
+ return st, instruction, err
}
if !st.Check(head) {
- return st, fmt.Errorf("key %v already loaded", head)
+ return st, instruction, fmt.Errorf("key %v already loaded", head)
}
- fn, err := rs.FuncFor(head)
+ sz := uint32(tail[0])
+ tail = tail[1:]
+
+ r, err := refresh(head, tail, rs, ctx)
if err != nil {
- return st, err
+ return st, tail, err
}
- r, err := fn(tail, ctx)
+ 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, err
+ return st, instruction, err
}
- st.Add(head, r)
- return st, nil
+ 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 RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) {
- return st, nil
+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 < 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
}
diff --git 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)
}