go-vise

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

commit 0917e9132fbbabd5dc54d68e46c36f2c1f49a66e
parent 0af679e9c3bb9f870eb0934016737daf9693c4c2
Author: lash <dev@holbrook.no>
Date:   Mon,  2 Sep 2024 03:52:23 +0100

Update docs

Diffstat:
Adb/dbtest/doc.go | 2++
Mdb/doc.go | 2+-
Adb/fs/doc.go | 2++
Adb/gdbm/doc.go | 2++
Adb/mem/doc.go | 2++
Adb/postgres/doc.go | 2++
Mengine/db.go | 118+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mengine/engine.go | 302++-----------------------------------------------------------------------------
8 files changed, 90 insertions(+), 342 deletions(-)

diff --git a/db/dbtest/doc.go b/db/dbtest/doc.go @@ -0,0 +1,2 @@ +// Package dbtest provides test vectors to verify correct operation of a db.Db implementation. +package dbtest diff --git a/db/doc.go b/db/doc.go @@ -1,2 +1,2 @@ -// package db contains a generalized data provider interface aswell as a number of built-in implementations. +// Package db contains a generalized data provider interface aswell as a number of built-in implementations. package db diff --git a/db/fs/doc.go b/db/fs/doc.go @@ -0,0 +1,2 @@ +// Package fs is a filesystem backed implementation of the db.Db interface. +package fs diff --git a/db/gdbm/doc.go b/db/gdbm/doc.go @@ -0,0 +1,2 @@ +// Package gdbm is a gdbm database backed implementation of the db.Db interface. +package gdbm diff --git a/db/mem/doc.go b/db/mem/doc.go @@ -0,0 +1,2 @@ +// Package mem is a volatile in-process memory implementation of the db.Db interface. +package mem diff --git a/db/postgres/doc.go b/db/postgres/doc.go @@ -0,0 +1,2 @@ +// Package postgres is a Postgres database backed implementation of the db.Db interface. +package postgres diff --git a/engine/db.go b/engine/db.go @@ -15,7 +15,7 @@ import ( "git.defalsify.org/vise.git/vm" ) -type dbEngine struct { +type DefaultEngine struct { st *state.State ca cache.Memory vm *vm.Vm @@ -28,11 +28,12 @@ type dbEngine struct { exit string } -func NewEngine(cfg Config, rs resource.Resource) *dbEngine { +// NewEngine instantiates the default Engine implementation. +func NewEngine(cfg Config, rs resource.Resource) *DefaultEngine { if rs == nil { panic("resource cannot be nil") } - en := &dbEngine{ + en := &DefaultEngine{ rs: rs, cfg: cfg, } @@ -42,7 +43,14 @@ func NewEngine(cfg Config, rs resource.Resource) *dbEngine { return en } -func(en *dbEngine) WithState(st *state.State) *dbEngine { +// WithState is a chainable method that explicitly sets the state object to use for the engine. +// +// If not set, the state.State object provided by the persist.Persister will be used. +// If that is not available, a new instance will be created according to engine.Config. +// +// Note that engine.Init will fail if state is set both explicitly +// and in a provided persist.Persister. +func(en *DefaultEngine) WithState(st *state.State) *DefaultEngine { if en.st != nil { panic("state already set") } @@ -53,7 +61,14 @@ func(en *dbEngine) WithState(st *state.State) *dbEngine { return en } -func(en *dbEngine) WithMemory(ca cache.Memory) *dbEngine { +// WithMemory is a chainable method that explicitly sets the memory object to use for the engine. +// +// If not set, the cache.Memory object provided by the persist.Persister will be used. +// If that is not available, a new instance will be created according to engine.Config. +// +// Note that engine.Init will fail if memory is set both explicitly +// and in a provided persist.Persister. +func(en *DefaultEngine) WithMemory(ca cache.Memory) *DefaultEngine { if en.ca != nil { panic("cache already set") } @@ -64,18 +79,10 @@ func(en *dbEngine) WithMemory(ca cache.Memory) *dbEngine { return en } -func(en *dbEngine) WithResource(rs resource.Resource) *dbEngine { - if en.rs != nil { - panic("resource already set") - } - if rs == nil { - panic("resource argument is nil") - } - en.rs = rs - return en -} - -func(en *dbEngine) WithPersister(pe *persist.Persister) *dbEngine { +// WithPersister is a chainable method that sets the persister to use with the engine. +// +// If the persister is missing state, memory or both, it will inherit them from the engine. +func(en *DefaultEngine) WithPersister(pe *persist.Persister) *DefaultEngine { if en.pe != nil { panic("persister already set") } @@ -86,7 +93,10 @@ func(en *dbEngine) WithPersister(pe *persist.Persister) *dbEngine { return en } -func(en *dbEngine) WithDebug(dbg Debug) *dbEngine { +// WithDebug is a chainable method that sets the debugger to use for the engine. +// +// If the argument is nil, the default debugger will be used. +func(en *DefaultEngine) WithDebug(dbg Debug) *DefaultEngine { if en.dbg != nil { panic("debugger already set") } @@ -98,7 +108,11 @@ func(en *dbEngine) WithDebug(dbg Debug) *dbEngine { return en } -func(en *dbEngine) WithFirst(fn resource.EntryFunc) *dbEngine { +// WithFirst is a chainable method that defines the function that will be run before +// control is handed over to the VM bytecode from the current state. +// +// If this function returns an error, execution will be aborted in engine.Init. +func(en *DefaultEngine) WithFirst(fn resource.EntryFunc) *DefaultEngine { if en.first != nil { panic("firstfunc already set") } @@ -109,7 +123,8 @@ func(en *dbEngine) WithFirst(fn resource.EntryFunc) *dbEngine { return en } -func(en *dbEngine) ensureState() { +// ensure state is present in engine. +func(en *DefaultEngine) ensureState() { if en.st == nil { st := state.NewState(en.cfg.FlagCount) en.st = &st @@ -130,7 +145,8 @@ func(en *dbEngine) ensureState() { } } -func(en *dbEngine) ensureMemory() error { +// ensure memory is present in engine. +func(en *DefaultEngine) ensureMemory() error { cac, ok := en.ca.(*cache.Cache) if cac == nil { ca := cache.NewCache() @@ -146,7 +162,8 @@ func(en *dbEngine) ensureMemory() error { return nil } -func(en *dbEngine) preparePersist() error { +// retrieve state and memory from perister if present. +func(en *DefaultEngine) preparePersist() error { if en.pe == nil { return nil } @@ -185,7 +202,8 @@ func(en *dbEngine) preparePersist() error { return nil } -func(en *dbEngine) ensurePersist() error { +// synchronize state and memory between engine and persister. +func(en *DefaultEngine) ensurePersist() error { if en.pe == nil { return nil } @@ -220,7 +238,8 @@ func(en *dbEngine) ensurePersist() error { return err } -func(en *dbEngine) setupVm() { +// create vm instance. +func(en *DefaultEngine) setupVm() { var szr *render.Sizer if en.cfg.OutputSize > 0 { szr = render.NewSizer(en.cfg.OutputSize) @@ -228,7 +247,8 @@ func(en *dbEngine) setupVm() { en.vm = vm.NewVm(en.st, en.rs, en.ca, szr) } -func(en *dbEngine) prepare() error { +// prepare engine for Init run. +func(en *DefaultEngine) prepare() error { err := en.preparePersist() if err != nil { return err @@ -246,8 +266,8 @@ func(en *dbEngine) prepare() error { return nil } -// execute the first function, if set -func(en *dbEngine) runFirst(ctx context.Context) (bool, error) { +// execute the first function, if set. +func(en *DefaultEngine) runFirst(ctx context.Context) (bool, error) { var err error var r bool if en.first == nil { @@ -285,8 +305,14 @@ func(en *dbEngine) runFirst(ctx context.Context) (bool, error) { return r, err } -// Finish implements EngineIsh interface -func(en *dbEngine) Finish() error { +// Finish implements the Engine interface. +// +// If persister is set, this call will save the state and memory. +// +// An error will be logged and returned if: +// * persistence was attempted and failed (takes precedence) +// * resource backend did not close cleanly. +func(en *DefaultEngine) Finish() error { var perr error if en.pe != nil { perr = en.pe.Save(en.cfg.SessionId) @@ -306,7 +332,7 @@ func(en *dbEngine) Finish() error { } // change root to current state location if non-empty. -func(en *dbEngine) restore() { +func(en *DefaultEngine) restore() { location, _ := en.st.Where() if len(location) == 0 { return @@ -317,10 +343,10 @@ func(en *dbEngine) restore() { } } -// Init must be explicitly called before using the Engine instance. +// Init implements the Engine interface. // // It loads and executes code for the start node. -func(en *dbEngine) Init(ctx context.Context) (bool, error) { +func(en *DefaultEngine) Init(ctx context.Context) (bool, error) { err := en.prepare() if err != nil { return false, err @@ -366,17 +392,19 @@ func(en *dbEngine) Init(ctx context.Context) (bool, error) { return len(b) > 0, nil } -// Exec processes user input against the current state of the virtual machine environment. +// Exec implements the Engine interface. +// +// It processes user input against the current state of the virtual machine environment. // // If successfully executed, output of the last execution is available using the WriteResult call. // // A bool return valus of false indicates that execution should be terminated. Calling Exec again has undefined effects. // // Fails if: -// - input is formally invalid (too long etc) -// - no current bytecode is available -// - input processing against bytcode failed -func (en *dbEngine) Exec(ctx context.Context, input []byte) (bool, error) { +// * input is formally invalid (too long etc) +// * no current bytecode is available +// * input processing against bytcode failed +func (en *DefaultEngine) Exec(ctx context.Context, input []byte) (bool, error) { var err error if en.st.Language != nil { ctx = context.WithValue(ctx, "Language", *en.st.Language) @@ -400,7 +428,7 @@ func (en *dbEngine) Exec(ctx context.Context, input []byte) (bool, error) { } // backend for Exec, after the input validity check -func(en *dbEngine) exec(ctx context.Context, input []byte) (bool, error) { +func(en *DefaultEngine) exec(ctx context.Context, input []byte) (bool, error) { logg.InfoCtxf(ctx, "new VM execution with input", "input", string(input)) code, err := en.st.GetCode() if err != nil { @@ -442,13 +470,15 @@ func(en *dbEngine) exec(ctx context.Context, input []byte) (bool, error) { return true, nil } -// WriteResult writes the output of the last vm execution to the given writer. +// WriteResult implements the Engine interface. +// +// The method writes the output of the last vm execution to the given writer. // // Fails if -// - required data inputs to the template are not available. -// - the template for the given node point is note available for retrieval using the resource.Resource implementer. -// - the supplied writer fails to process the writes. -func(en *dbEngine) WriteResult(ctx context.Context, w io.Writer) (int, error) { +// * required data inputs to the template are not available. +// * the template for the given node point is note available for retrieval using the resource.Resource implementer. +// * the supplied writer fails to process the writes. +func(en *DefaultEngine) WriteResult(ctx context.Context, w io.Writer) (int, error) { var l int if en.st.Language != nil { ctx = context.WithValue(ctx, "Language", *en.st.Language) @@ -476,7 +506,7 @@ func(en *dbEngine) WriteResult(ctx context.Context, w io.Writer) (int, error) { } // start execution over at top node while keeping current state of client error flags. -func(en *dbEngine) reset(ctx context.Context) (bool, error) { +func(en *DefaultEngine) reset(ctx context.Context) (bool, error) { var err error var isTop bool for !isTop { diff --git a/engine/engine.go b/engine/engine.go @@ -2,310 +2,18 @@ package engine import ( "context" - "fmt" "io" - - "git.defalsify.org/vise.git/cache" - "git.defalsify.org/vise.git/render" - "git.defalsify.org/vise.git/resource" - "git.defalsify.org/vise.git/state" - "git.defalsify.org/vise.git/vm" ) // EngineIsh defines the interface for execution engines that handle vm initialization and execution, and rendering outputs. type Engine interface { + // Init sets the engine up for vm execution. It must be called before Exec. Init(ctx context.Context) (bool, error) + // Exec executes the pending bytecode. Exec(ctx context.Context, input []byte) (bool, error) + // WriteResult renders output according to the state of VM execution + // to the given io.Writer. WriteResult(ctx context.Context, w io.Writer) (int, error) + // Finish must be called after the last call to Exec. Finish() error } - -// LegacyEngine is an execution engine that handles top-level errors when running client inputs against code in the bytecode buffer. -type LegacyEngine struct { - st *state.State - rs resource.Resource - ca cache.Memory - vm *vm.Vm - dbg Debug - first resource.EntryFunc - root string - session string - initd bool - exit string -} - -// NewLegacyEngine creates a new LegacyEngine -func NewLegacyEngine(ctx context.Context, cfg Config, st *state.State, rs resource.Resource, ca cache.Memory) LegacyEngine { - var szr *render.Sizer - if cfg.OutputSize > 0 { - szr = render.NewSizer(cfg.OutputSize) - } - ctx = context.WithValue(ctx, "sessionId", cfg.SessionId) - engine := LegacyEngine{ - st: st, - rs: rs, - ca: ca, - vm: vm.NewVm(st, rs, ca, szr), - } - engine.root = cfg.Root - engine.session = cfg.SessionId - - var err error - if st.Language == nil { - if cfg.Language != "" { - err = st.SetLanguage(cfg.Language) - if err != nil { - panic(err) - } - logg.InfoCtxf(ctx, "set language from config", "language", cfg.Language) - } - } - - if st.Language != nil { - st.SetFlag(state.FLAG_LANG) - } - return engine -} - -// SetDebugger sets a debugger to use. -// -// No debugger is set by default. -func (en *LegacyEngine) SetDebugger(debugger Debug) { - en.dbg = debugger -} - -// SetFirst sets a function which will be executed before bytecode -func(en *LegacyEngine) SetFirst(fn resource.EntryFunc) { - en.first = fn -} - -// Finish implements LegacyEngineIsh interface -func(en *LegacyEngine) Finish() error { - logg.Tracef("that's a wrap", "engine", en) - return nil -} - -// change root to current state location if non-empty. -func(en *LegacyEngine) restore() { - location, _ := en.st.Where() - if len(location) == 0 { - return - } - if en.root != location { - logg.Infof("restoring state", "sym", location) - en.root = "." - } -} - -// execute the first function, if set -func(en *LegacyEngine) 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 - } - } - if err != nil { - en.st.Invalidate() - en.ca.Invalidate() - } - en.st.ResetFlag(state.FLAG_TERMINATE) - en.st.ResetFlag(state.FLAG_DIRTY) - logg.DebugCtxf(ctx, "end pre-VM check") - return r, err -} - -// Init must be explicitly called before using the LegacyEngine instance. -// -// It loads and executes code for the start node. -func(en *LegacyEngine) Init(ctx context.Context) (bool, error) { - en.restore() - if en.initd { - logg.DebugCtxf(ctx, "already initialized") - return true, nil - } - - sym := en.root - if sym == "" { - return false, fmt.Errorf("start sym empty") - } - - inSave, _ := en.st.GetInput() - err := en.st.SetInput([]byte{}) - if err != nil { - 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) - if err != nil { - return false, err - } - - logg.DebugCtxf(ctx, "end new init VM run", "code", b) - en.st.SetCode(b) - err = en.st.SetInput(inSave) - if err != nil { - return false, err - } - return len(b) > 0, nil -} - -// Exec processes user input against the current state of the virtual machine environment. -// -// If successfully executed, output of the last execution is available using the WriteResult call. -// -// A bool return valus of false indicates that execution should be terminated. Calling Exec again has undefined effects. -// -// Fails if: -// - input is formally invalid (too long etc) -// - no current bytecode is available -// - input processing against bytcode failed -func (en *LegacyEngine) Exec(ctx context.Context, input []byte) (bool, error) { - var err error - if en.st.Language != nil { - ctx = context.WithValue(ctx, "Language", *en.st.Language) - } - if en.st.Moves == 0 { - cont, err := en.Init(ctx) - if err != nil { - return false, err - } - return cont, nil - } - err = vm.ValidInput(input) - if err != nil { - return true, err - } - err = en.st.SetInput(input) - if err != nil { - return false, err - } - return en.exec(ctx, input) -} - -// backend for Exec, after the input validity check -func(en *LegacyEngine) exec(ctx context.Context, input []byte) (bool, error) { - logg.InfoCtxf(ctx, "new VM execution with input", "input", string(input)) - code, err := en.st.GetCode() - if err != nil { - return false, err - } - if len(code) == 0 { - return false, fmt.Errorf("no code to execute") - } - - logg.Debugf("start new VM run", "code", code) - code, err = en.vm.Run(ctx, code) - if err != nil { - return false, err - } - logg.Debugf("end new VM run", "code", code) - - v := en.st.MatchFlag(state.FLAG_TERMINATE, true) - if v { - if len(code) > 0 { - logg.Debugf("terminated with code remaining", "code", code) - } - 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 - } - - if en.dbg != nil { - en.dbg.Break(en.st, en.ca) - } - return true, nil -} - -// WriteResult writes the output of the last vm execution to the given writer. -// -// Fails if -// - required data inputs to the template are not available. -// - the template for the given node point is note available for retrieval using the resource.Resource implementer. -// - the supplied writer fails to process the writes. -func(en *LegacyEngine) WriteResult(ctx context.Context, w io.Writer) (int, error) { - var l int - if en.st.Language != nil { - ctx = context.WithValue(ctx, "Language", *en.st.Language) - } - logg.TraceCtxf(ctx, "render with state", "state", en.st) - r, err := en.vm.Render(ctx) - if err != nil { - return 0, err - } - if len(r) > 0 { - l, err = io.WriteString(w, r) - if err != nil { - return l, err - } - } - if len(en.exit) > 0 { - logg.TraceCtxf(ctx, "have exit", "exit", en.exit) - n, err := io.WriteString(w, en.exit) - if err != nil { - return l, err - } - l += n - } - return l, nil -} - -// start execution over at top node while keeping current state of client error flags. -func(en *LegacyEngine) reset(ctx context.Context) (bool, error) { - var err error - var isTop bool - for !isTop { - isTop, err = en.st.Top() - if err != nil { - return false, err - } - _, err = en.st.Up() - if err != nil { - return false, err - } - en.ca.Pop() - } - en.st.Restart() - en.initd = false - return false, nil -}