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:
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