go-vise

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

commit 064418cb8329d170eec45236a1c9ddd3025fd93d
parent 15c64a46e697d999123fbc0fe96a270c08e62340
Author: lash <dev@holbrook.no>
Date:   Wed, 12 Apr 2023 23:42:36 +0100

Add persist module

Diffstat:
Aasm/doc.go | 2++
Mcache/cache.go | 12++++++------
Mgo.mod | 3+++
Apersist/fs.go | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apersist/fs_test.go | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apersist/persist.go | 9+++++++++
Mstate/state.go | 129+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mstate/state_test.go | 18+++++++++---------
8 files changed, 260 insertions(+), 80 deletions(-)

diff --git a/asm/doc.go b/asm/doc.go @@ -0,0 +1,2 @@ +// Package asm parses and compiles festive assembly code to bytecode. +package asm diff --git a/cache/cache.go b/cache/cache.go @@ -10,14 +10,14 @@ type Cache struct { CacheSize uint32 // Total allowed cumulative size of values (not code) in cache CacheUseSize uint32 // Currently used bytes by all values (not code) in cache Cache []map[string]string // All loaded cache items - sizes map[string]uint16 // Size limits for all loaded symbols. + Sizes map[string]uint16 // Size limits for all loaded symbols. } // NewCache creates a new ready-to-use cache object func NewCache() *Cache { ca := &Cache{ Cache: []map[string]string{make(map[string]string)}, - sizes: make(map[string]uint16), + Sizes: make(map[string]uint16), } return ca } @@ -58,13 +58,13 @@ func(ca *Cache) Add(key string, value string, sizeLimit uint16) error { log.Printf("add key %s value size %v limit %v", key, sz, sizeLimit) ca.Cache[len(ca.Cache)-1][key] = value ca.CacheUseSize += sz - ca.sizes[key] = sizeLimit + ca.Sizes[key] = sizeLimit return nil } // ReservedSize returns the maximum byte size available for the given symbol. func(ca *Cache) ReservedSize(key string) (uint16, error) { - v, ok := ca.sizes[key] + v, ok := ca.Sizes[key] if !ok { return 0, fmt.Errorf("unknown symbol: %s", key) } @@ -80,8 +80,8 @@ func(ca *Cache) ReservedSize(key string) (uint16, error) { // - value is longer than size limit // - replacing value exceeds cumulative cache capacity func(ca *Cache) Update(key string, value string) error { - sizeLimit := ca.sizes[key] - if ca.sizes[key] > 0 { + sizeLimit := ca.Sizes[key] + if ca.Sizes[key] > 0 { l := uint16(len(value)) if l > sizeLimit { return fmt.Errorf("update value length %v exceeds value size limit %v", l, sizeLimit) diff --git a/go.mod b/go.mod @@ -4,5 +4,8 @@ go 1.20 require ( github.com/alecthomas/participle/v2 v2.0.0 + github.com/fxamacker/cbor/v2 v2.4.0 github.com/peteole/testdata-loader v0.3.0 ) + +require github.com/x448/float16 v0.8.4 // indirect diff --git a/persist/fs.go b/persist/fs.go @@ -0,0 +1,61 @@ +package persist + +import ( + "io/ioutil" + "path" + "path/filepath" + "github.com/fxamacker/cbor/v2" + + "git.defalsify.org/festive/cache" + "git.defalsify.org/festive/state" +) + +type FsPersister struct { + State *state.State + Memory *cache.Cache + dir string +} + +func NewFsPersister(dir string) *FsPersister { + fp, err := filepath.Abs(dir) + if err != nil { + panic(err) + } + return &FsPersister{ + dir: fp, + } +} + +func(p *FsPersister) WithContent(st *state.State, ca *cache.Cache) *FsPersister { + p.State = st + p.Memory = ca + return p +} + +func(p *FsPersister) Serialize() ([]byte, error) { + return cbor.Marshal(p) +} + +func(p *FsPersister) Deserialize(b []byte) error { + err := cbor.Unmarshal(b, p) + return err +} + +func(p *FsPersister) Save(key string) error { + b, err := p.Serialize() + if err != nil { + return err + } + fp := path.Join(p.dir, key) + return ioutil.WriteFile(fp, b, 0600) +} + +func(p *FsPersister) Load(key string) error { + fp := path.Join(p.dir, key) + b, err := ioutil.ReadFile(fp) + if err != nil { + return err + } + err = p.Deserialize(b) + return err +} diff --git a/persist/fs_test.go b/persist/fs_test.go @@ -0,0 +1,106 @@ +package persist + +import ( + "bytes" + "io/ioutil" + "log" + "reflect" + "testing" + + "git.defalsify.org/festive/cache" + "git.defalsify.org/festive/state" + "git.defalsify.org/festive/vm" +) + +func TestSerializeState(t *testing.T) { + st := state.NewState(12) + st.Down("foo") + st.Down("bar") + st.Down("baz") + st.Next() + st.Next() + + b := vm.NewLine(nil, vm.LOAD, []string{"foo"}, []byte{42}, nil) + b = vm.NewLine(b, vm.HALT, nil, nil, nil) + st.SetCode(b) + + ca := cache.NewCache().WithCacheSize(1024) + ca.Add("inky", "pinky", 13) + ca.Add("blinky", "clyde", 42) + + pr := NewFsPersister(".").WithContent(&st, ca) + v, err := pr.Serialize() + if err != nil { + t.Error(err) + } + log.Printf("v %b", v) + + prnew := NewFsPersister(".") + err = prnew.Deserialize(v) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(prnew.State.ExecPath, pr.State.ExecPath) { + t.Fatalf("expected %s, got %s", prnew.State.ExecPath, pr.State.ExecPath) + } + if !bytes.Equal(prnew.State.Code, pr.State.Code) { + t.Fatalf("expected %x, got %x", prnew.State.Code, pr.State.Code) + } + if prnew.State.BitSize != pr.State.BitSize { + t.Fatalf("expected %v, got %v", prnew.State.BitSize, pr.State.BitSize) + } + if prnew.State.SizeIdx != pr.State.SizeIdx { + t.Fatalf("expected %v, got %v", prnew.State.SizeIdx, pr.State.SizeIdx) + } + if !reflect.DeepEqual(prnew.Memory, pr.Memory) { + t.Fatalf("expected %v, got %v", prnew.Memory, pr.Memory) + } +} + +func TestSaveLoad(t *testing.T) { + st := state.NewState(12) + st.Down("foo") + st.Down("bar") + st.Down("baz") + st.Next() + st.Next() + + b := vm.NewLine(nil, vm.LOAD, []string{"foo"}, []byte{42}, nil) + b = vm.NewLine(b, vm.HALT, nil, nil, nil) + st.SetCode(b) + + ca := cache.NewCache().WithCacheSize(1024) + ca.Add("inky", "pinky", 13) + ca.Add("blinky", "clyde", 42) + + dir, err := ioutil.TempDir("", "festive_persist") + if err != nil { + t.Error(err) + } + pr := NewFsPersister(dir).WithContent(&st, ca) + err = pr.Save("xyzzy") + if err != nil { + t.Error(err) + } + + prnew := NewFsPersister(dir) + err = prnew.Load("xyzzy") + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(prnew.State.ExecPath, pr.State.ExecPath) { + t.Fatalf("expected %s, got %s", prnew.State.ExecPath, pr.State.ExecPath) + } + if !bytes.Equal(prnew.State.Code, pr.State.Code) { + t.Fatalf("expected %x, got %x", prnew.State.Code, pr.State.Code) + } + if prnew.State.BitSize != pr.State.BitSize { + t.Fatalf("expected %v, got %v", prnew.State.BitSize, pr.State.BitSize) + } + if prnew.State.SizeIdx != pr.State.SizeIdx { + t.Fatalf("expected %v, got %v", prnew.State.SizeIdx, pr.State.SizeIdx) + } + if !reflect.DeepEqual(prnew.Memory, pr.Memory) { + t.Fatalf("expected %v, got %v", prnew.Memory, pr.Memory) + } +} diff --git a/persist/persist.go b/persist/persist.go @@ -0,0 +1,9 @@ +package persist + +type Persister interface { + Serialize() ([]byte, error) + Deserialize(b []byte) error + Save(key string) error + Load(key string) error +} + diff --git a/state/state.go b/state/state.go @@ -29,25 +29,24 @@ func(err *IndexError) Error() string { // // 8 first flags are reserved. type State struct { - Flags []byte // Error state + Code []byte // Pending bytecode to execute + ExecPath []string // Command symbols stack + BitSize uint32 // size of (32-bit capacity) bit flag byte array + SizeIdx uint16 + flags []byte // Error state input []byte // Last input - code []byte // Pending bytecode to execute - execPath []string // Command symbols stack - arg *string // Optional argument. Nil if not set. - bitSize uint32 // size of (32-bit capacity) bit flag byte array - sizeIdx uint16 } // number of bytes necessary to represent a bitfield of the given size. -func toByteSize(bitSize uint32) uint8 { - if bitSize == 0 { +func toByteSize(BitSize uint32) uint8 { + if BitSize == 0 { return 0 } - n := bitSize % 8 + n := BitSize % 8 if n > 0 { - bitSize += (8 - n) + BitSize += (8 - n) } - return uint8(bitSize / 8) + return uint8(BitSize / 8) } // Retrieve the state of a state flag @@ -58,16 +57,16 @@ func getFlag(bitIndex uint32, bitField []byte) bool { return (b & (1 << localBitIndex)) > 0 } -// NewState creates a new State object with bitSize number of error condition states in ADDITION to the 8 builtin flags. -func NewState(bitSize uint32) State { +// NewState creates a new State object with BitSize number of error condition states in ADDITION to the 8 builtin flags. +func NewState(BitSize uint32) State { st := State{ - bitSize: bitSize + 8, + BitSize: BitSize + 8, } - byteSize := toByteSize(bitSize + 8) + byteSize := toByteSize(BitSize + 8) if byteSize > 0 { - st.Flags = make([]byte, byteSize) + st.flags = make([]byte, byteSize) } else { - st.Flags = []byte{} + st.flags = []byte{} } return st } @@ -78,17 +77,17 @@ func NewState(bitSize uint32) State { // // Fails if bitindex is out of range. func(st *State) SetFlag(bitIndex uint32) (bool, error) { - if bitIndex + 1 > st.bitSize { - return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.bitSize) + if bitIndex + 1 > st.BitSize { + return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.BitSize) } - r := getFlag(bitIndex, st.Flags) + r := getFlag(bitIndex, st.flags) if r { return false, nil } byteIndex := bitIndex / 8 localBitIndex := bitIndex % 8 - b := st.Flags[byteIndex] - st.Flags[byteIndex] = b | (1 << localBitIndex) + b := st.flags[byteIndex] + st.flags[byteIndex] = b | (1 << localBitIndex) return true, nil } @@ -99,17 +98,17 @@ func(st *State) SetFlag(bitIndex uint32) (bool, error) { // // Fails if bitindex is out of range. func(st *State) ResetFlag(bitIndex uint32) (bool, error) { - if bitIndex + 1 > st.bitSize { - return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.bitSize) + if bitIndex + 1 > st.BitSize { + return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.BitSize) } - r := getFlag(bitIndex, st.Flags) + r := getFlag(bitIndex, st.flags) if !r { return false, nil } byteIndex := bitIndex / 8 localBitIndex := bitIndex % 8 - b := st.Flags[byteIndex] - st.Flags[byteIndex] = b & (^(1 << localBitIndex)) + b := st.flags[byteIndex] + st.flags[byteIndex] = b & (^(1 << localBitIndex)) return true, nil } @@ -117,20 +116,20 @@ func(st *State) ResetFlag(bitIndex uint32) (bool, error) { // // Fails if bit field index is out of range. func(st *State) GetFlag(bitIndex uint32) (bool, error) { - if bitIndex + 1 > st.bitSize { - return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.bitSize) + if bitIndex + 1 > st.BitSize { + return false, fmt.Errorf("bit index %v is out of range of bitfield size %v", bitIndex, st.BitSize) } - return getFlag(bitIndex, st.Flags), nil + return getFlag(bitIndex, st.flags), nil } // FlagBitSize reports the amount of bits available in the bit field index. func(st *State) FlagBitSize() uint32 { - return st.bitSize + return st.BitSize } // FlagBitSize reports the amount of bits available in the bit field index. func(st *State) FlagByteSize() uint8 { - return uint8(len(st.Flags)) + return uint8(len(st.flags)) } // MatchFlag matches the current state of the given flag. @@ -158,7 +157,7 @@ func(st *State) MatchFlag(sig uint32, invertMatch bool) (bool, error) { // If the given byte slice is too small for the bit field bitsize, the check will terminate at end-of-data without error. func(st *State) GetIndex(flags []byte) bool { var globalIndex uint32 - if st.bitSize == 0 { + if st.BitSize == 0 { return false } if len(flags) == 0 { @@ -168,9 +167,9 @@ func(st *State) GetIndex(flags []byte) bool { var localIndex uint8 l := uint8(len(flags)) var i uint32 - for i = 0; i < st.bitSize; i++ { + for i = 0; i < st.BitSize; i++ { testVal := flags[byteIndex] & (1 << localIndex) - if (testVal & st.Flags[byteIndex]) > 0 { + if (testVal & st.flags[byteIndex]) > 0 { return true } globalIndex += 1 @@ -189,50 +188,50 @@ func(st *State) GetIndex(flags []byte) bool { // Where returns the current active rendering symbol. func(st *State) Where() (string, uint16) { - if len(st.execPath) == 0 { + if len(st.ExecPath) == 0 { return "", 0 } - l := len(st.execPath) - return st.execPath[l-1], st.sizeIdx + l := len(st.ExecPath) + return st.ExecPath[l-1], st.SizeIdx } // Next moves to the next sink page index. func(st *State) Next() (uint16, error) { - if len(st.execPath) == 0 { + if len(st.ExecPath) == 0 { return 0, fmt.Errorf("state root node not yet defined") } - st.sizeIdx += 1 + st.SizeIdx += 1 s, idx := st.Where() log.Printf("next page for %s: %v", s, idx) - return st.sizeIdx, nil + return st.SizeIdx, nil } // Previous moves to the next sink page index. // // Fails if try to move beyond index 0. func(st *State) Previous() (uint16, error) { - if len(st.execPath) == 0 { + if len(st.ExecPath) == 0 { return 0, fmt.Errorf("state root node not yet defined") } - if st.sizeIdx == 0 { + if st.SizeIdx == 0 { return 0, &IndexError{} // ("already at first index") } - st.sizeIdx -= 1 + st.SizeIdx -= 1 s, idx := st.Where() log.Printf("previous page for %s: %v", s, idx) - return st.sizeIdx, nil + return st.SizeIdx, nil } // Sides informs the caller which index page options will currently succeed. // // Two values are returned, for the "next" and "previous" options in that order. A false value means the option is not available in the current state. func(st *State) Sides() (bool, bool) { - if len(st.execPath) == 0 { + if len(st.ExecPath) == 0 { return false, false } next := true - log.Printf("sides %v", st.sizeIdx) - if st.sizeIdx == 0 { + log.Printf("sides %v", st.SizeIdx) + if st.SizeIdx == 0 { return next, false } return next, true @@ -242,18 +241,18 @@ func(st *State) Sides() (bool, bool) { // // Fails if first Down() was never called. func(st *State) Top() (bool, error) { - if len(st.execPath) == 0 { + if len(st.ExecPath) == 0 { return false, fmt.Errorf("state root node not yet defined") } - return len(st.execPath) == 1, nil + return len(st.ExecPath) == 1, nil } // Down adds the given symbol to the command stack. // // Clears mapping and sink. func(st *State) Down(input string) error { - st.execPath = append(st.execPath, input) - st.sizeIdx = 0 + st.ExecPath = append(st.ExecPath, input) + st.SizeIdx = 0 return nil } @@ -265,29 +264,29 @@ func(st *State) Down(input string) error { // // Fails if called at top frame. func(st *State) Up() (string, error) { - l := len(st.execPath) + l := len(st.ExecPath) if l == 0 { return "", fmt.Errorf("exit called beyond top frame") } - log.Printf("execpath before %v", st.execPath) - st.execPath = st.execPath[:l-1] + log.Printf("execpath before %v", st.ExecPath) + st.ExecPath = st.ExecPath[:l-1] sym := "" - if len(st.execPath) > 0 { - sym = st.execPath[len(st.execPath)-1] + if len(st.ExecPath) > 0 { + sym = st.ExecPath[len(st.ExecPath)-1] } - st.sizeIdx = 0 - log.Printf("execpath after %v", st.execPath) + st.SizeIdx = 0 + log.Printf("execpath after %v", st.ExecPath) return sym, nil } // Depth returns the current call stack depth. func(st *State) Depth() uint8 { - return uint8(len(st.execPath)-1) + return uint8(len(st.ExecPath)-1) } // Appendcode adds the given bytecode to the end of the existing code. func(st *State) AppendCode(b []byte) error { - st.code = append(st.code, b...) + st.Code = append(st.Code, b...) log.Printf("code changed to 0x%x", b) return nil } @@ -295,13 +294,13 @@ func(st *State) AppendCode(b []byte) error { // SetCode replaces the current bytecode with the given bytecode. func(st *State) SetCode(b []byte) { log.Printf("code set to 0x%x", b) - st.code = b + st.Code = b } // Get the remaning cached bytecode func(st *State) GetCode() ([]byte, error) { - b := st.code - st.code = []byte{} + b := st.Code + st.Code = []byte{} return b, nil } @@ -328,5 +327,5 @@ func(st *State) Reset() error { } func(st State) String() string { - return fmt.Sprintf("path: %s", strings.Join(st.execPath, "/")) + return fmt.Sprintf("idx %v path: %s", st.SizeIdx, strings.Join(st.ExecPath, "/")) } diff --git a/state/state_test.go b/state/state_test.go @@ -8,20 +8,20 @@ import ( // Check creation func TestNewState(t *testing.T) { st := NewState(5) - if len(st.Flags) != 2 { - t.Fatalf("invalid state flag length: %v", len(st.Flags)) + if len(st.flags) != 2 { + t.Fatalf("invalid state flag length: %v", len(st.flags)) } st = NewState(8) - if len(st.Flags) != 2 { - t.Fatalf("invalid state flag length: %v", len(st.Flags)) + if len(st.flags) != 2 { + t.Fatalf("invalid state flag length: %v", len(st.flags)) } st = NewState(17) - if len(st.Flags) != 4 { - t.Fatalf("invalid state flag length: %v", len(st.Flags)) + if len(st.flags) != 4 { + t.Fatalf("invalid state flag length: %v", len(st.flags)) } } -func TestStateFlags(t *testing.T) { +func TestStateflags(t *testing.T) { st := NewState(9) v, err := st.GetFlag(2) if err != nil { @@ -98,8 +98,8 @@ func TestStateFlags(t *testing.T) { if err == nil { t.Fatalf("Expected out of range for bit index 17") } - if !bytes.Equal(st.Flags[:3], []byte{0x04, 0x04, 0x01}) { - t.Fatalf("Expected 0x040401, got %v", st.Flags[:3]) + if !bytes.Equal(st.flags[:3], []byte{0x04, 0x04, 0x01}) { + t.Fatalf("Expected 0x040401, got %v", st.flags[:3]) } }