commit 0917e9132fbbabd5dc54d68e46c36f2c1f49a66e
parent 0af679e9c3bb9f870eb0934016737daf9693c4c2
Author: lash <dev@holbrook.no>
Date: Mon, 2 Sep 2024 03:52:23 +0100
Update docs
Diffstat:
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
-}