go-vise

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

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:
MCHANGELOG | 1+
Mengine/db.go | 44++++++++++++++++++++++++++++++--------------
Mengine/engine_test.go | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtestdata/testdata.go | 2+-
Mtestdata/testdata_legacy.go | 20++++++++++++++++++--
Mvm/runner.go | 3+++
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)