Add run loop
This commit is contained in:
parent
6dbc004138
commit
ee55f3e5fe
@ -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 {
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
194
go/vm/vm.go
194
go/vm/vm.go
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user