commit bc0d9312f1a1489aff93d1aca5a0bd50b58e3388
parent 3558025e4e3e75dea7a54bd2fcb994b5866b96c7
Author: lash <dev@holbrook.no>
Date: Wed, 18 Sep 2024 15:22:08 +0100
Use template as default out, wait node reset until writeresult
Diffstat:
6 files changed, 152 insertions(+), 19 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -1,6 +1,7 @@
- 0.2.0
* Remove Init from engine interface, allowing input to be passed in all parts of engine execution.
* Enable alternate input validators if default validator fails.
+ * Use template as output by default on empty bytecode terminate.
- 0.1.0
* Data storage interface (db.Db) with implementations for memory (db.memDb), filesystem (db.fsDb), gdbm (db.gdbmDb) and Postgres (db.pgDb).
* Replace resource.FsResource and resource.MemResource with resource.DbResource using corresponding db.Db backend.
diff --git a/engine/db.go b/engine/db.go
@@ -26,6 +26,7 @@ type DefaultEngine struct {
first resource.EntryFunc
initd bool
exit string
+ exiting bool
regexCount int
}
@@ -261,6 +262,8 @@ func(en *DefaultEngine) setupVm() {
// prepare engine for Init run.
func(en *DefaultEngine) prepare() error {
+ en.exit = ""
+ en.exiting = false
if en.initd {
return nil
}
@@ -363,6 +366,20 @@ func(en *DefaultEngine) restore() {
}
}
+func(en *DefaultEngine) setCode(ctx context.Context, code []byte) error {
+ var err error
+ en.st.SetCode(code)
+ if len(code) == 0 {
+ logg.Infof("runner finished with no remaining code", "state", en.st)
+ if en.st.MatchFlag(state.FLAG_DIRTY, true) {
+ logg.Debugf("have output for quitting")
+ en.exiting = true
+ en.exit = en.ca.Last()
+ }
+ }
+ return err
+}
+
// Init implements the Engine interface.
//
// It loads and executes code for the start node.
@@ -412,7 +429,11 @@ func(en *DefaultEngine) init(ctx context.Context, input []byte) (bool, error) {
en.dbg.Break(en.st, en.ca)
}
logg.DebugCtxf(ctx, "end new init VM run", "code", b)
- en.st.SetCode(b)
+ err = en.setCode(ctx, b)
+ if err != nil {
+ return false, err
+ }
+
err = en.st.SetInput(inSave)
if err != nil {
return false, err
@@ -490,22 +511,12 @@ func(en *DefaultEngine) exec(ctx context.Context, input []byte) (bool, error) {
}
return false, err
}
-
- en.st.SetCode(code)
- if len(code) == 0 {
- logg.Infof("runner finished with no remaining code", "state", en.st)
- if en.st.MatchFlag(state.FLAG_DIRTY, true) {
- logg.Debugf("have output for quitting")
- en.exit = en.ca.Last()
- }
- _, err = en.reset(ctx)
- return false, err
- }
+ err = en.setCode(ctx, code)
if en.dbg != nil {
en.dbg.Break(en.st, en.ca)
}
- return true, nil
+ return true, err
}
// WriteResult implements the Engine interface.
@@ -543,7 +554,12 @@ func(en *DefaultEngine) WriteResult(ctx context.Context, w io.Writer) (int, erro
}
l += n
}
- return l, nil
+ if en.exiting {
+ _, err = en.reset(ctx)
+ en.exiting = false
+ }
+
+ return l, err
}
// start execution over at top node while keeping current state of client error flags.
diff --git a/engine/engine_test.go b/engine/engine_test.go
@@ -46,6 +46,7 @@ func newTestWrapper(path string, st *state.State) testWrapper {
rs.AddLocalFunc("pinky", wr.pinky)
rs.AddLocalFunc("set_lang", wr.set_lang)
rs.AddLocalFunc("translate", wr.translate)
+ rs.AddLocalFunc("quit", quitFunc)
return wr
}
@@ -113,6 +114,12 @@ func generateTestData(t *testing.T) {
}
}
+func quitFunc(ctx context.Context, sym string, input []byte) (resource.Result, error) {
+ return resource.Result{
+ Content: "these aren't the droids you are looking for",
+ }, nil
+}
+
func TestEngineInit(t *testing.T) {
var err error
generateTestData(t)
@@ -231,8 +238,8 @@ func TestEngineResumeTerminated(t *testing.T) {
}
location, idx := st.Where()
- if location != "" {
- t.Fatalf("expected '', got %s", location)
+ if location != "baz" {
+ t.Fatalf("expected 'baz', got %s", location)
}
if idx != 0 {
t.Fatalf("expected idx '0', got %v", idx)
@@ -444,3 +451,93 @@ func TestPreVm(t *testing.T) {
t.Fatalf("expected init to return 'continue'")
}
}
+
+func TestManyQuits(t *testing.T) {
+ b := bytes.NewBuffer(nil)
+ ctx := context.Background()
+ st := state.NewState(0)
+ st.UseDebug()
+ generateTestData(t)
+ rs := newTestWrapper(dataDir, st)
+ ca := cache.NewCache()
+
+ cfg := Config{
+ Root: "nothing",
+ }
+
+ en := NewEngine(cfg, rs)
+ en = en.WithState(st)
+ en = en.WithMemory(ca)
+ r, err := en.Exec(ctx, []byte{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if r {
+ t.Fatalf("expected init to return 'not continue'")
+ }
+ en.WriteResult(ctx, b)
+
+ en = NewEngine(cfg, rs)
+ en = en.WithState(st)
+ en = en.WithMemory(ca)
+ r, err = en.Exec(ctx, []byte{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if r {
+ t.Fatalf("expected init to return 'not continue'")
+ }
+ en.WriteResult(ctx, b)
+
+ en = NewEngine(cfg, rs)
+ en = en.WithState(st)
+ en = en.WithMemory(ca)
+ r, err = en.Exec(ctx, []byte{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if r {
+ t.Fatalf("expected init to return 'not continue'")
+ }
+
+ b = bytes.NewBuffer(nil)
+ _, err = en.WriteResult(ctx, b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ x := "these aren't the droids you are looking for"
+ if !bytes.Equal(b.Bytes(), []byte(x)) {
+ t.Fatalf("expected '%s', got '%s'", x, b.Bytes())
+ }
+}
+
+func TestOutEmpty(t *testing.T) {
+ ctx := context.Background()
+ st := state.NewState(0)
+ st.UseDebug()
+ generateTestData(t)
+ rs := newTestWrapper(dataDir, st)
+ ca := cache.NewCache()
+
+ cfg := Config{
+ Root: "something",
+ }
+
+ en := NewEngine(cfg, rs)
+ en = en.WithState(st)
+ en = en.WithMemory(ca)
+ r, err := en.Exec(ctx, []byte{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if r {
+ t.Fatalf("expected init to return 'not continue'")
+ }
+
+ v := bytes.NewBuffer(nil)
+ en.WriteResult(ctx, v)
+ x := "mmmm, something..."
+ if !bytes.Equal(v.Bytes(), []byte(x)) {
+ t.Fatalf("expected '%s', got '%s'", x, v.Bytes())
+ }
+}
diff --git a/testdata/testdata.go b/testdata/testdata.go
@@ -64,7 +64,7 @@ func generate() error {
store.SetLock(db.DATATYPE_MENU, false)
store.SetLock(db.DATATYPE_STATICLOAD, false)
- fns := []genFunc{root, foo, bar, baz, long, lang, defaultCatch}
+ fns := []genFunc{root, foo, bar, baz, long, lang, nothing, something, defaultCatch}
for _, fn := range fns {
err = fn()
if err != nil {
diff --git a/testdata/testdata_legacy.go b/testdata/testdata_legacy.go
@@ -158,7 +158,6 @@ func lang() error {
b = vm.NewLine(b, vm.INCMP, []string{"_", "*"}, nil, nil)
tpl := "this changes with language {{.inky}}"
-
err := out("lang", b, tpl, nil)
if err != nil {
return err
@@ -176,6 +175,23 @@ func lang() error {
return os.WriteFile(fp, []byte(menu), 0600)
}
+func nothing() error {
+ b := vm.NewLine(nil, vm.LOAD, []string{"quit"}, []byte{0x00}, nil)
+ b = vm.NewLine(b, vm.RELOAD, []string{"quit"}, nil, nil)
+ b = vm.NewLine(b, vm.HALT, nil, nil, nil)
+
+ fp := path.Join(DataDir, "nothing.bin")
+ err := os.WriteFile(fp, b, 0600)
+ return err
+}
+
+func something() error {
+ b := vm.NewLine(nil, vm.HALT, nil, nil, nil)
+
+ tpl := "mmmm, something..."
+ return out("something", b, tpl, nil)
+}
+
func generateLegacy() error {
out = outLegacy
err := os.MkdirAll(DataDir, 0755)
@@ -183,7 +199,7 @@ func generateLegacy() error {
return err
}
- fns := []genFunc{root, foo, bar, baz, long, lang, defaultCatch}
+ fns := []genFunc{root, foo, bar, baz, long, lang, nothing, something, defaultCatch}
for _, fn := range fns {
err = fn()
if err != nil {
diff --git a/vm/runner.go b/vm/runner.go
@@ -455,6 +455,9 @@ func(vm *Vm) Render(ctx context.Context) (string, error) {
return "", nil
}
sym, idx := vm.st.Where()
+ if sym == "" {
+ return "", nil
+ }
r, err := vm.pg.Render(ctx, sym, idx)
var ok bool
_, ok = err.(*render.BrowseError)