Add run loop

This commit is contained in:
lash 2023-03-31 15:03:54 +01:00
parent 6dbc004138
commit ee55f3e5fe
Signed by untrusted user who does not match committer: lash
GPG Key ID: 21D2E7BB88C2A746
4 changed files with 168 additions and 116 deletions

View File

@ -12,6 +12,7 @@ type State struct {
Cache []map[string]string Cache []map[string]string
CacheMap map[string]string CacheMap map[string]string
ExecPath []string ExecPath []string
Idx uint16
} }
func NewState(bitSize uint64) State { func NewState(bitSize uint64) State {
@ -35,13 +36,13 @@ func(st State) WithCacheSize(cacheSize uint32) State {
return st return st
} }
func(st *State) Enter(input string) { func(st *State) Down(input string) {
m := make(map[string]string) m := make(map[string]string)
st.Cache = append(st.Cache, m) st.Cache = append(st.Cache, m)
st.CacheMap = make(map[string]string) 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) checkFrame := st.frameOf(key)
if checkFrame > -1 { if checkFrame > -1 {
return fmt.Errorf("key %v already defined in frame %v", key, checkFrame) 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) log.Printf("add key %s value size %v", key, sz)
st.Cache[len(st.Cache)-1][key] = value st.Cache[len(st.Cache)-1][key] = value
st.CacheUseSize += sz 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 return nil
} }
@ -76,7 +97,7 @@ func(st *State) Get() (map[string]string, error) {
return st.Cache[len(st.Cache)-1], nil return st.Cache[len(st.Cache)-1], nil
} }
func(st *State) Exit() error { func(st *State) Up() error {
l := len(st.Cache) l := len(st.Cache)
if l == 0 { if l == 0 {
return fmt.Errorf("exit called beyond top frame") return fmt.Errorf("exit called beyond top frame")
@ -92,16 +113,20 @@ func(st *State) Exit() error {
return nil return nil
} }
func(st *State) Reset() error { func(st *State) Reset() {
if len(st.Cache) == 0 {
return
}
st.Cache = st.Cache[:1] st.Cache = st.Cache[:1]
st.CacheUseSize = 0 st.CacheUseSize = 0
return nil return
} }
func(st *State) Check(key string) bool { func(st *State) Check(key string) bool {
return st.frameOf(key) == -1 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 { func(st *State) frameOf(key string) int {
log.Printf("--- %s", key) log.Printf("--- %s", key)
for i, m := range st.Cache { for i, m := range st.Cache {
@ -114,6 +139,8 @@ func(st *State) frameOf(key string) int {
return -1 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 { func(st *State) checkCapacity(v string) uint32 {
sz := uint32(len(v)) sz := uint32(len(v))
if st.CacheSize == 0 { if st.CacheSize == 0 {

View File

@ -35,40 +35,40 @@ func TestNewStateCache(t *testing.T) {
func TestStateCacheUse(t *testing.T) { func TestStateCacheUse(t *testing.T) {
st := NewState(17) st := NewState(17)
st = st.WithCacheSize(10) st = st.WithCacheSize(10)
st.Enter("foo") st.Down("foo")
err := st.Add("bar", "baz") err := st.Add("bar", "baz", 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
err = st.Add("inky", "pinky") err = st.Add("inky", "pinky", 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
err = st.Add("blinky", "clyde") err = st.Add("blinky", "clyde", 0)
if err == nil { if err == nil {
t.Errorf("expected capacity error") t.Errorf("expected capacity error")
} }
} }
func TestStateEnterExit(t *testing.T) { func TestStateDownUp(t *testing.T) {
st := NewState(17) st := NewState(17)
st.Enter("one") st.Down("one")
err := st.Add("foo", "bar") err := st.Add("foo", "bar", 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
err = st.Add("baz", "xyzzy") err = st.Add("baz", "xyzzy", 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if st.CacheUseSize != 8 { if st.CacheUseSize != 8 {
t.Errorf("expected cache use size 8 got %v", st.CacheUseSize) t.Errorf("expected cache use size 8 got %v", st.CacheUseSize)
} }
err = st.Exit() err = st.Up()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
err = st.Exit() err = st.Up()
if err == nil { if err == nil {
t.Errorf("expected out of top frame error") t.Errorf("expected out of top frame error")
} }
@ -76,17 +76,17 @@ func TestStateEnterExit(t *testing.T) {
func TestStateReset(t *testing.T) { func TestStateReset(t *testing.T) {
st := NewState(17) st := NewState(17)
st.Enter("one") st.Down("one")
err := st.Add("foo", "bar") err := st.Add("foo", "bar", 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
err = st.Add("baz", "xyzzy") err = st.Add("baz", "xyzzy", 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
st.Enter("two") st.Down("two")
st.Enter("three") st.Down("three")
st.Reset() st.Reset()
if st.CacheUseSize != 0 { if st.CacheUseSize != 0 {
t.Errorf("expected cache use size 0, got %v", st.CacheUseSize) 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) { func TestStateLoadDup(t *testing.T) {
st := NewState(17) st := NewState(17)
st.Enter("one") st.Down("one")
err := st.Add("foo", "bar") err := st.Add("foo", "bar", 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
st.Enter("two") st.Down("two")
err = st.Add("foo", "baz") err = st.Add("foo", "baz", 0)
if err == nil { if err == nil {
t.Errorf("expected fail on duplicate load") t.Errorf("expected fail on duplicate load")
} }

View File

@ -9,31 +9,116 @@ import (
"git.defalsify.org/festive/resource" "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) { func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
op := binary.BigEndian.Uint16(instruction[:2]) var err error
if op > _MAX { for len(instruction) > 0 {
return st, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX) 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 { return st, instruction, nil
case CATCH: }
RunCatch(instruction[2:], st, rs, ctx)
case CROAK: func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) {
RunCroak(instruction[2:], st, rs, ctx) head, tail, err := instructionSplit(instruction)
case LOAD: if err != nil {
RunLoad(instruction[2:], st, rs, ctx) return st, instruction, err
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 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) { 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") return "", nil, fmt.Errorf("zero-length argument")
} }
tailSz := uint8(len(b)) 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) return "", nil, fmt.Errorf("corrupt instruction, len %v less than symbol length: %v", tailSz, sz)
} }
r := string(b[1:1+sz]) r := string(b[1:1+sz])
return r, b[1+sz:], nil 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
}

View File

@ -63,14 +63,15 @@ func (r *TestResource) FuncFor(sym string) (resource.EntryFunc, error) {
func TestRun(t *testing.T) { func TestRun(t *testing.T) {
st := state.NewState(5) st := state.NewState(5)
rs := TestResource{} rs := TestResource{}
b := []byte{0x00, 0x01} b := []byte{0x00, 0x01, 0x03}
r, err := Run(b, st, &rs, context.TODO()) b = append(b, []byte("foo")...)
r, _, err := Run(b, st, &rs, context.TODO())
if err != nil { if err != nil {
t.Errorf("error on valid opcode: %v", err) t.Errorf("error on valid opcode: %v", err)
} }
b = []byte{0x01, 0x02} b = []byte{0x01, 0x02}
r, err = Run(b, st, &rs, context.TODO()) r, _, err = Run(b, st, &rs, context.TODO())
if err == nil { if err == nil {
t.Errorf("no error on invalid opcode") t.Errorf("no error on invalid opcode")
} }
@ -79,12 +80,13 @@ func TestRun(t *testing.T) {
func TestRunLoad(t *testing.T) { func TestRunLoad(t *testing.T) {
st := state.NewState(5) st := state.NewState(5)
st.Enter("barbarbar") st.Down("barbarbar")
rs := TestResource{} rs := TestResource{}
sym := "one" sym := "one"
ins := append([]byte{uint8(len(sym))}, []byte(sym)...) ins := append([]byte{uint8(len(sym))}, []byte(sym)...)
ins = append(ins, 0x0a)
var err error var err error
st, err = RunLoad(ins, st, &rs, context.TODO()) st, _, err = RunLoad(ins, st, &rs, context.TODO())
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -108,7 +110,8 @@ func TestRunLoad(t *testing.T) {
sym = "two" sym = "two"
ins = append([]byte{uint8(len(sym))}, []byte(sym)...) 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 { if err != nil {
t.Error(err) t.Error(err)
} }