commit 0ee0159b513122c43680c1e98a2838a322d32ba9
parent f322102e49b4c8f2a00679aee9428faf3c4e2241
Author: lash <dev@holbrook.no>
Date:   Wed, 28 Aug 2024 02:42:39 +0100
Add gdbm persister
Diffstat:
14 files changed, 203 insertions(+), 28 deletions(-)
diff --git a/dev/gdbm/main.go b/dev/gdbm/main.go
@@ -12,7 +12,7 @@ import (
 
 	gdbm "github.com/graygnuorg/go-gdbm"
 
-	"git.defalsify.org/vise.git/resource"
+	"git.defalsify.org/vise.git/db"
 )
 
 var (
@@ -45,7 +45,7 @@ func(sc *scanner) Scan(fp string, d fs.DirEntry, err error) error {
 	if err != nil {
 		return err
 	}
-	typ = resource.FSRESOURCETYPE_UNKNOWN
+	typ = db.DATATYPE_UNKNOWN
 	if d.IsDir() {
 		return nil
 	}
@@ -53,9 +53,9 @@ func(sc *scanner) Scan(fp string, d fs.DirEntry, err error) error {
 	fb := path.Base(fp)
 	switch fx {
 		case binaryPrefix:
-			typ = resource.FSRESOURCETYPE_BIN
+			typ = db.DATATYPE_BIN
 		case templatePrefix:
-			typ = resource.FSRESOURCETYPE_TEMPLATE
+			typ = db.DATATYPE_TEMPLATE
 		default:
 			log.Printf("skip foreign file: %s", fp)
 			return nil
@@ -72,7 +72,7 @@ func(sc *scanner) Scan(fp string, d fs.DirEntry, err error) error {
 
 	log.Printf("fx fb %s %s", fx, fb)
 	ft := fb[:len(fb)-len(fx)]
-	k := resource.ToDbKey(typ, ft, nil)
+	k := db.ToDbKey(typ, ft, nil)
 	err = sc.db.Store(k, v, true)
 	if err != nil {
 		return err
diff --git a/examples/gdbm/Makefile b/examples/gdbm/Makefile
@@ -0,0 +1,11 @@
+INPUTS = $(wildcard ./*.vis)
+TXTS = $(wildcard ./*.txt.orig)
+
+%.vis:
+	go run ../../dev/asm $(basename $@).vis > $(basename $@).bin
+	go run ../../dev/gdbm/main.go .
+
+all: $(INPUTS) $(TXTS)
+
+%.txt.orig:
+	cp -v $(basename $@).orig $(basename $@)
diff --git a/examples/gdbm/aiee.vis b/examples/gdbm/aiee.vis
@@ -0,0 +1,2 @@
+LOAD do 0
+HALT
diff --git a/examples/gdbm/main.go b/examples/gdbm/main.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path"
+
+	testdataloader "github.com/peteole/testdata-loader"
+
+	"git.defalsify.org/vise.git/cache"
+	"git.defalsify.org/vise.git/engine"
+	"git.defalsify.org/vise.git/resource"
+	"git.defalsify.org/vise.git/state"
+)
+
+var (
+	baseDir = testdataloader.GetBasePath()
+	scriptDir = path.Join(baseDir, "examples", "gdbm")
+	dbFile = path.Join(scriptDir, "vise.gdbm")
+)
+
+func do(ctx context.Context, sym string, input []byte) (resource.Result, error) {
+	return resource.Result{
+		Content: "bye",
+	}, nil
+}
+	     
+func main() {
+	var err error
+	root := "root"
+	fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, scriptDir)
+
+	st := state.NewState(0)
+	rs := resource.NewGdbmResource(dbFile)
+	ca := cache.NewCache()
+	if err != nil {
+		panic(err)
+	}
+	cfg := engine.Config{
+		Root: "root",
+		Language: "nor",
+	}
+	ctx := context.Background()
+	en := engine.NewEngine(ctx, cfg, &st, rs, ca)
+
+	rs.AddLocalFunc("do", do)
+
+	_, err = en.Init(ctx)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "engine init fail: %v\n", err)
+		os.Exit(1)
+	}
+	err = engine.Loop(ctx, &en, os.Stdin, os.Stdout)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err)
+		os.Exit(1)
+	}
+}
diff --git a/examples/gdbm/menu b/examples/gdbm/menu
@@ -0,0 +1 @@
+welcome
+\ No newline at end of file
diff --git a/examples/gdbm/menu.vis b/examples/gdbm/menu.vis
@@ -0,0 +1,5 @@
+MOUT again 0
+MOUT quit 1
+HALT
+INCMP ^ 0
+INCMP aiee 1
diff --git a/examples/gdbm/quit_menu b/examples/gdbm/quit_menu
@@ -0,0 +1 @@
+or quit
+\ No newline at end of file
diff --git a/examples/gdbm/root b/examples/gdbm/root
@@ -0,0 +1 @@
+ready?
+\ No newline at end of file
diff --git a/examples/gdbm/root.vis b/examples/gdbm/root.vis
@@ -0,0 +1,2 @@
+HALT
+INCMP menu *
diff --git a/examples/gdbm/root_nor b/examples/gdbm/root_nor
@@ -0,0 +1 @@
+klar?
+\ No newline at end of file
diff --git a/persist/fs.go b/persist/fs.go
@@ -67,8 +67,12 @@ func(p *FsPersister) Save(key string) error {
 		return err
 	}
 	fp := path.Join(p.dir, key)
+	err = ioutil.WriteFile(fp, b, 0600)
+	if err != nil {
+		return err
+	}
 	Logg.Debugf("saved state and cache", "key", key, "bytecode", p.State.Code, "flags", p.State.Flags)
-	return ioutil.WriteFile(fp, b, 0600)
+	return nil
 }
 
 // Load implements the Persister interface.
@@ -79,6 +83,9 @@ func(p *FsPersister) Load(key string) error {
 		return err
 	}
 	err = p.Deserialize(b)
+	if err != nil {
+		return err
+	}
 	Logg.Debugf("loaded state and cache", "key", key, "bytecode", p.State.Code)
-	return err
+	return nil
 }
diff --git a/persist/gdbm.go b/persist/gdbm.go
@@ -0,0 +1,92 @@
+package persist
+
+import (
+	"github.com/fxamacker/cbor/v2"
+	gdbm "github.com/graygnuorg/go-gdbm"
+
+	"git.defalsify.org/vise.git/cache"
+	"git.defalsify.org/vise.git/state"
+	"git.defalsify.org/vise.git/db"
+)
+
+// gdbmPersister is an implementation of Persister that saves state to the file system.
+type gdbmPersister struct {
+	State *state.State
+	Memory *cache.Cache
+	db *gdbm.Database
+}
+
+func NewGdbmPersiser(fp string) *gdbmPersister {
+	gdb, err := gdbm.Open(fp, gdbm.ModeReader)
+	if err != nil {
+		panic(err)
+	}
+	return NewGdbmPersisterFromDatabase(gdb)
+}
+
+func NewGdbmPersisterFromDatabase(gdb *gdbm.Database) *gdbmPersister {
+	return &gdbmPersister{
+		db: gdb,
+	}
+}
+
+// WithContent sets a current State and Cache object.
+//
+// This method is normally called before Serialize / Save.
+func(p *gdbmPersister) WithContent(st *state.State, ca *cache.Cache) *gdbmPersister {
+	p.State = st
+	p.Memory = ca
+	return p
+}
+
+// TODO: DRY
+// GetState implements the Persister interface.
+func(p *gdbmPersister) GetState() *state.State {
+	return p.State
+}
+
+// GetMemory implements the Persister interface.
+func(p *gdbmPersister) GetMemory() cache.Memory {
+	return p.Memory
+}
+
+// Serialize implements the Persister interface.
+func(p *gdbmPersister) Serialize() ([]byte, error) {
+	return cbor.Marshal(p)
+}
+
+// Deserialize implements the Persister interface.
+func(p *gdbmPersister) Deserialize(b []byte) error {
+	err := cbor.Unmarshal(b, p)
+	return err
+}
+
+// Save implements the Persister interface.
+func(p *gdbmPersister) Save(key string) error {
+	b, err := p.Serialize()
+	if err != nil {
+		return err
+	}
+	k := db.ToDbKey(db.DATATYPE_STATE, key, nil)
+	err = p.db.Store(k, b, true)
+	if err != nil {
+		return err
+	}
+	Logg.Debugf("saved state and cache", "key", key, "bytecode", p.State.Code, "flags", p.State.Flags)
+	return nil
+}
+
+// Load implements the Persister interface.
+func(p *gdbmPersister) Load(key string) error {
+	k := db.ToDbKey(db.DATATYPE_STATE, key, nil)
+	b, err := p.db.Fetch(k)
+	if err != nil {
+		return err
+	}
+	err = p.Deserialize(b)
+	if err != nil {
+		return err
+	}
+	Logg.Debugf("loaded state and cache", "key", key, "bytecode", p.State.Code)
+	return nil
+}
diff --git a/resource/fs.go b/resource/fs.go
@@ -13,12 +13,6 @@ import (
 	"git.defalsify.org/vise.git/lang"
 )
 
-const (
-	FSRESOURCETYPE_UNKNOWN = iota
-	FSRESOURCETYPE_BIN
-	FSRESOURCETYPE_TEMPLATE
-)
-
 type FsResource struct {
 	MenuResource
 	Path string
diff --git a/resource/gdbm.go b/resource/gdbm.go
@@ -6,44 +6,39 @@ import (
 
 	gdbm "github.com/graygnuorg/go-gdbm"
 	"git.defalsify.org/vise.git/lang"
+	"git.defalsify.org/vise.git/db"
 )
 
-
 type gdbmResource struct {
 	db *gdbm.Database
 	fns map[string]EntryFunc
 }
 
 func NewGdbmResource(fp string) *gdbmResource {
-	db, err := gdbm.Open(fp, gdbm.ModeReader)
+	gdb, err := gdbm.Open(fp, gdbm.ModeReader)
 	if err != nil {
 		panic(err)
 	}
-	return &gdbmResource{
-		db: db,
-	}
+	return NewGdbmResourceFromDatabase(gdb)
 }
 
-func ToDbKey(typ uint8, s string, l *lang.Language) []byte {
-	k := []byte{typ}
-	if l != nil && l.Code != "" {
-		s += "_" + l.Code
+func NewGdbmResourceFromDatabase(gdb *gdbm.Database) *gdbmResource {
+	return &gdbmResource{
+		db: gdb,
 	}
-	return append(k, []byte(s)...)
 }
 
-
 func(dbr *gdbmResource) GetTemplate(ctx context.Context, sym string) (string, error) {
 	var ln lang.Language
 	v := ctx.Value("Language")
 	if v != nil {
 		ln = v.(lang.Language)
 	}
-	k := ToDbKey(FSRESOURCETYPE_TEMPLATE, sym, &ln)
+	k := db.ToDbKey(db.DATATYPE_TEMPLATE, sym, &ln)
 	r, err := dbr.db.Fetch(k)
 	if err != nil {
 		if err.(*gdbm.GdbmError).Is(gdbm.ErrItemNotFound) {
-			k = ToDbKey(FSRESOURCETYPE_TEMPLATE, sym, nil)
+			k = db.ToDbKey(db.DATATYPE_TEMPLATE, sym, nil)
 			r, err = dbr.db.Fetch(k)
 			if err != nil {
 				return "", err
@@ -54,7 +49,7 @@ func(dbr *gdbmResource) GetTemplate(ctx context.Context, sym string) (string, er
 }
 
 func(dbr *gdbmResource) GetCode(sym string) ([]byte, error) {
-	k := ToDbKey(FSRESOURCETYPE_BIN, sym, nil)
+	k := db.ToDbKey(db.DATATYPE_BIN, sym, nil)
 	return dbr.db.Fetch(k)
 }
 
@@ -65,7 +60,7 @@ func(dbr *gdbmResource) GetMenu(ctx context.Context, sym string) (string, error)
 	if v != nil {
 		ln = v.(lang.Language)
 	}
-	k := ToDbKey(FSRESOURCETYPE_TEMPLATE, msym, &ln)
+	k := db.ToDbKey(db.DATATYPE_TEMPLATE, msym, &ln)
 	r, err := dbr.db.Fetch(k)
 	if err != nil {
 		if err.(*gdbm.GdbmError).Is(gdbm.ErrItemNotFound) {