go-vise

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

commit f94d7ea8599743e5fcd4f4070f98bbb177613f53
parent 338acb33332b8429f7299b56da1b50a907c107d1
Author: lash <dev@holbrook.no>
Date:   Sat, 31 Aug 2024 16:28:40 +0100

Add pre-vm hook in engine

Diffstat:
Mengine/engine.go | 51++++++++++++++++++++++++++++++++++++++++++++++++++-
Mengine/engine_test.go | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mstate/debug_test.go | 4++--
Mstate/flag.go | 4++--
Mtestdata/testdata.go | 10+++++++++-
Mvm/runner.go | 2+-
6 files changed, 128 insertions(+), 13 deletions(-)

diff --git a/engine/engine.go b/engine/engine.go @@ -27,6 +27,7 @@ type Engine struct { ca cache.Memory vm *vm.Vm dbg Debug + first resource.EntryFunc root string session string initd bool @@ -46,7 +47,7 @@ func NewEngine(ctx context.Context, cfg Config, st *state.State, rs resource.Res ca: ca, vm: vm.NewVm(st, rs, ca, szr), } - engine.root = cfg.Root + engine.root = cfg.Root engine.session = cfg.SessionId var err error @@ -70,12 +71,18 @@ func (en *Engine) SetDebugger(debugger Debug) { en.dbg = debugger } +// SetFirst sets a function which will be executed before bytecode +func(en *Engine) SetFirst(fn resource.EntryFunc) { + en.first = fn +} + // Finish implements EngineIsh interface func(en *Engine) Finish() error { Logg.Tracef("that's a wrap", "engine", en) return nil } + // change root to current state location if non-empty. func(en *Engine) restore() { location, _ := en.st.Where() @@ -88,6 +95,40 @@ func(en *Engine) restore() { } } +// execute the first function, if set +func(en *Engine) runFirst(ctx context.Context) (bool, error) { + var err error + var r bool + if en.first == nil { + return true, nil + } + Logg.DebugCtxf(ctx, "start pre-VM check") + rs := resource.NewMenuResource() + rs.AddLocalFunc("_first", en.first) + en.st.Down("_first") + pvm := vm.NewVm(en.st, rs, en.ca, nil) + b := vm.NewLine(nil, vm.LOAD, []string{"_first"}, []byte{0}, nil) + b = vm.NewLine(b, vm.HALT, nil, nil, nil) + b, err = pvm.Run(ctx, b) + if err != nil { + return false, err + } + if len(b) > 0 { + // TODO: typed error + err = fmt.Errorf("Pre-VM code cannot have remaining bytecode after execution, had: %x", b) + } else { + if en.st.MatchFlag(state.FLAG_TERMINATE, true) { + en.exit = en.ca.Last() + Logg.InfoCtxf(ctx, "Pre-VM check says not to continue execution", "state", en.st) + } else { + r = true + } + } + en.st.ResetFlag(state.FLAG_TERMINATE) + Logg.DebugCtxf(ctx, "end pre-VM check") + return r, nil +} + // Init must be explicitly called before using the Engine instance. // // It loads and executes code for the start node. @@ -109,6 +150,14 @@ func(en *Engine) Init(ctx context.Context) (bool, error) { return false, err } + r, err := en.runFirst(ctx) + if err != nil { + return false, err + } + if !r { + return false, nil + } + b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil) Logg.DebugCtxf(ctx, "start new init VM run", "code", b) b, err = en.vm.Run(ctx, b) diff --git a/engine/engine_test.go b/engine/engine_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io/ioutil" + "log" "path" "testing" @@ -103,7 +104,7 @@ func generateTestData(t *testing.T) { func TestEngineInit(t *testing.T) { var err error generateTestData(t) - ctx := context.TODO() + ctx := context.Background() st := state.NewState(17) rs := NewFsWrapper(dataDir, &st) ca := cache.NewCache().WithCacheSize(1024) @@ -161,7 +162,7 @@ it has more lines func TestEngineExecInvalidInput(t *testing.T) { generateTestData(t) - ctx := context.TODO() + ctx := context.Background() st := state.NewState(17) rs := NewFsWrapper(dataDir, &st) ca := cache.NewCache().WithCacheSize(1024) @@ -183,7 +184,7 @@ func TestEngineExecInvalidInput(t *testing.T) { func TestEngineResumeTerminated(t *testing.T) { generateTestData(t) - ctx := context.TODO() + ctx := context.Background() st := state.NewState(17) rs := NewFsWrapper(dataDir, &st) ca := cache.NewCache().WithCacheSize(1024) @@ -219,7 +220,7 @@ func TestEngineResumeTerminated(t *testing.T) { func TestLanguageSet(t *testing.T) { generateTestData(t) - ctx := context.TODO() + ctx := context.Background() st := state.NewState(0) rs := NewFsWrapper(dataDir, &st) ca := cache.NewCache().WithCacheSize(1024) @@ -272,7 +273,7 @@ func TestLanguageSet(t *testing.T) { func TestLanguageRender(t *testing.T) { generateTestData(t) - ctx := context.TODO() + ctx := context.Background() st := state.NewState(0) rs := NewFsWrapper(dataDir, &st) ca := cache.NewCache() @@ -312,7 +313,7 @@ func TestLanguageRender(t *testing.T) { func TestConfigLanguageRender(t *testing.T) { generateTestData(t) - ctx := context.TODO() + ctx := context.Background() st := state.NewState(0) rs := NewFsWrapper(dataDir, &st) ca := cache.NewCache() @@ -350,3 +351,60 @@ func TestConfigLanguageRender(t *testing.T) { t.Fatalf("expected:\n\t%s\ngot:\n\t%s", expect, r) } } + +func preBlock(ctx context.Context, sym string, input []byte) (resource.Result, error) { + log.Printf("executing preBlock") + return resource.Result{ + Content: "None shall pass", + FlagSet: []uint32{state.FLAG_TERMINATE}, + }, nil +} + +func preAllow(ctx context.Context, sym string, input []byte) (resource.Result, error) { + log.Printf("executing preAllow") + return resource.Result{}, nil +} + +func TestPreVm(t *testing.T) { + var b []byte + var out *bytes.Buffer + generateTestData(t) + ctx := context.Background() + st := state.NewState(0) + st.UseDebug() + rs := NewFsWrapper(dataDir, &st) + ca := cache.NewCache() + + cfg := Config{ + Root: "root", + } + en := NewEngine(ctx, cfg, &st, &rs, ca) + en.SetFirst(preBlock) + r, err := en.Init(ctx) + if err != nil { + t.Fatal(err) + } + if r { + t.Fatalf("expected init to return 'not continue'") + } + out = bytes.NewBuffer(b) + _, err = en.WriteResult(ctx, out) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(out.Bytes(), []byte("None shall pass")) { + t.Fatalf("expected writeresult 'None shall pass', got %s", out) + } + + st = state.NewState(0) + ca = cache.NewCache() + en = NewEngine(ctx, cfg, &st, &rs, ca) + en.SetFirst(preAllow) + r, err = en.Init(ctx) + if err != nil { + t.Fatal(err) + } + if !r { + t.Fatalf("expected init to return 'continue'") + } +} diff --git a/state/debug_test.go b/state/debug_test.go @@ -27,7 +27,7 @@ func TestDebugFlagString(t *testing.T) { } flags := []byte{0x06, 0x09} r := FlagDebugger.AsString(flags, 4) - expect := "INTERNAL_INMATCH(1),INTERNAL_TERMINATE(2),FOO(8),BAZ(11)" + expect := "INTERNAL_INMATCH(1),INTERNAL_DIRTY(2),FOO(8),BAZ(11)" if r != expect { t.Fatalf("expected '%s', got '%s'", expect, r) } @@ -45,7 +45,7 @@ func TestDebugState(t *testing.T) { st.Down("root") r := fmt.Sprintf("%s", st) - expect := "moves: 1 idx: 0 flags: INTERNAL_DIRTY(3),FOO(8) path: root lang: (default)" + expect := "moves: 1 idx: 0 flags: INTERNAL_DIRTY(2),FOO(8) path: root lang: (default)" if r != expect { t.Fatalf("expected '%s', got '%s'", expect, r) } diff --git a/state/flag.go b/state/flag.go @@ -3,17 +3,17 @@ package state const ( FLAG_READIN = iota FLAG_INMATCH - FLAG_TERMINATE FLAG_DIRTY FLAG_WAIT FLAG_LOADFAIL + FLAG_TERMINATE FLAG_RESERVED FLAG_LANG FLAG_USERSTART = 8 ) func IsWriteableFlag(flag uint32) bool { - if flag > 6 { + if flag > 4 { return true } //if flag & FLAG_WRITEABLE > 0 { diff --git a/testdata/testdata.go b/testdata/testdata.go @@ -149,6 +149,14 @@ func defaultCatch() error { return out("_catch", b, tpl, nil) } +func defaultFirst() error { + b := []byte{} + + tpl := "" + + return out("_first", b, tpl, nil) +} + func lang() error { b := []byte{} b = vm.NewLine(b, vm.MOUT, []string{"back", "0"}, nil, nil) @@ -182,7 +190,7 @@ func generate() error { return err } - fns := []genFunc{root, foo, bar, baz, long, lang, defaultCatch} + fns := []genFunc{root, foo, bar, baz, long, lang, defaultCatch, defaultFirst} for _, fn := range fns { err = fn() if err != nil { diff --git a/vm/runner.go b/vm/runner.go @@ -95,7 +95,6 @@ func(vm *Vm) Run(ctx context.Context, b []byte) ([]byte, error) { ctx = context.WithValue(ctx, "Language", *vm.st.Language) } } - waitChange := vm.st.ResetFlag(state.FLAG_WAIT) if waitChange { @@ -494,6 +493,7 @@ func(vm *Vm) refresh(key string, rs resource.Resource, ctx context.Context) (str _ = vm.st.SetFlag(state.FLAG_LOADFAIL) return "", NewExternalCodeError(key, err).WithCode(r.Status) } + Logg.TraceCtxf(ctx, "foo", "flags", r.FlagSet) for _, flag := range r.FlagReset { if !state.IsWriteableFlag(flag) { continue