vise/engine/engine.go

184 lines
4.4 KiB
Go
Raw Normal View History

2023-04-01 11:58:02 +02:00
package engine
import (
"context"
2023-04-01 15:47:03 +02:00
"fmt"
2023-04-01 11:58:02 +02:00
"io"
"log"
"git.grassecon.net/kamikazechaser/vise/cache"
"git.grassecon.net/kamikazechaser/vise/render"
"git.grassecon.net/kamikazechaser/vise/resource"
"git.grassecon.net/kamikazechaser/vise/state"
"git.grassecon.net/kamikazechaser/vise/vm"
2023-04-01 11:58:02 +02:00
)
2023-04-06 13:41:36 +02:00
2023-04-12 19:04:36 +02:00
// Config globally defines behavior of all components driven by the engine.
type Config struct {
2023-04-12 19:04:36 +02:00
OutputSize uint32 // Maximum size of output from a single rendered page
SessionId string
Root string
FlagCount uint32
CacheSize uint32
}
2023-04-01 11:58:02 +02:00
2023-04-12 19:04:36 +02:00
// Engine is an execution engine that handles top-level errors when running client inputs against code in the bytecode buffer.
2023-04-01 11:58:02 +02:00
type Engine struct {
st *state.State
rs resource.Resource
ca cache.Memory
vm *vm.Vm
root string
2023-04-13 01:38:33 +02:00
initd bool
2023-04-01 11:58:02 +02:00
}
2023-04-01 16:04:38 +02:00
// NewEngine creates a new Engine
func NewEngine(cfg Config, st *state.State, rs resource.Resource, ca cache.Memory, ctx context.Context) Engine {
var szr *render.Sizer
if cfg.OutputSize > 0 {
szr = render.NewSizer(cfg.OutputSize)
}
2023-04-15 08:06:03 +02:00
ctx = context.WithValue(ctx, "sessionId", cfg.SessionId)
engine := Engine{
st: st,
rs: rs,
ca: ca,
vm: vm.NewVm(st, rs, ca, szr),
}
engine.root = cfg.Root
return engine
2023-04-01 11:58:02 +02:00
}
2023-04-01 16:04:38 +02:00
// Init must be explicitly called before using the Engine instance.
//
2023-04-06 13:08:30 +02:00
// It loads and executes code for the start node.
func (en *Engine) Init(ctx context.Context) (bool, error) {
2023-04-13 01:38:33 +02:00
if en.initd {
log.Printf("already initialized")
2023-04-17 07:35:36 +02:00
return true, nil
2023-04-13 01:38:33 +02:00
}
sym := en.root
2023-04-13 01:38:33 +02:00
if sym == "" {
2023-04-17 07:35:36 +02:00
return false, fmt.Errorf("start sym empty")
2023-04-13 01:38:33 +02:00
}
inSave, _ := en.st.GetInput()
err := en.st.SetInput([]byte{})
if err != nil {
2023-04-17 07:35:36 +02:00
return false, err
}
b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil)
log.Printf("start new init VM run with code %x", b)
2023-04-09 16:35:26 +02:00
b, err = en.vm.Run(b, ctx)
2023-04-01 15:47:03 +02:00
if err != nil {
2023-04-17 07:35:36 +02:00
return false, err
2023-04-16 09:51:16 +02:00
}
log.Printf("ended init VM run with code %x", b)
en.st.SetCode(b)
err = en.st.SetInput(inSave)
if err != nil {
2023-04-17 07:35:36 +02:00
return false, err
}
2023-04-17 07:35:36 +02:00
return len(b) > 0, nil
2023-04-01 15:47:03 +02:00
}
2023-04-01 16:04:38 +02:00
// Exec processes user input against the current state of the virtual machine environment.
//
2023-04-06 13:08:30 +02:00
// If successfully executed, output of the last execution is available using the WriteResult call.
//
2023-04-06 13:08:30 +02:00
// A bool return valus of false indicates that execution should be terminated. Calling Exec again has undefined effects.
2023-04-01 16:04:38 +02:00
//
// Fails if:
2023-04-06 13:08:30 +02:00
// - input is formally invalid (too long etc)
2023-04-01 16:04:38 +02:00
// - no current bytecode is available
// - input processing against bytcode failed
2023-04-06 11:08:40 +02:00
func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
var err error
if en.st.Moves == 0 {
2023-04-17 07:35:36 +02:00
cont, err := en.Init(ctx)
if err != nil {
return false, err
}
2023-04-17 07:35:36 +02:00
return cont, nil
}
err = vm.ValidInput(input)
2023-04-06 13:41:36 +02:00
if err != nil {
2023-04-06 13:47:09 +02:00
return true, err
2023-04-06 13:41:36 +02:00
}
err = en.st.SetInput(input)
if err != nil {
2023-04-06 11:08:40 +02:00
return false, err
2023-04-01 15:47:03 +02:00
}
2023-04-06 13:47:09 +02:00
log.Printf("new execution with input '%s' (0x%x)", input, input)
2023-04-01 15:47:03 +02:00
code, err := en.st.GetCode()
if err != nil {
2023-04-06 11:08:40 +02:00
return false, err
2023-04-01 15:47:03 +02:00
}
if len(code) == 0 {
2023-04-06 11:08:40 +02:00
return false, fmt.Errorf("no code to execute")
2023-04-01 15:47:03 +02:00
}
log.Printf("start new VM run with code %x", code)
2023-04-09 16:35:26 +02:00
code, err = en.vm.Run(code, ctx)
2023-04-06 11:08:40 +02:00
if err != nil {
return false, err
}
log.Printf("ended VM run with code %x", code)
2023-04-06 11:08:40 +02:00
v, err := en.st.MatchFlag(state.FLAG_TERMINATE, false)
if err != nil {
return false, err
}
if v {
if len(code) > 0 {
log.Printf("terminated with code remaining: %x", code)
}
return false, err
2023-04-06 11:08:40 +02:00
}
2023-04-01 15:47:03 +02:00
en.st.SetCode(code)
2023-04-06 13:08:30 +02:00
if len(code) == 0 {
log.Printf("runner finished with no remaining code")
2023-04-17 07:35:36 +02:00
_, err = en.reset(ctx)
return false, err
2023-04-06 13:08:30 +02:00
}
2023-04-06 11:08:40 +02:00
return true, nil
2023-04-01 11:58:02 +02:00
}
2023-04-01 16:04:38 +02:00
// 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 *Engine) WriteResult(w io.Writer, ctx context.Context) (int, error) {
2023-04-12 09:30:35 +02:00
r, err := en.vm.Render(ctx)
2023-04-01 11:58:02 +02:00
if err != nil {
return 0, err
2023-04-01 11:58:02 +02:00
}
return io.WriteString(w, r)
2023-04-01 11:58:02 +02:00
}
// start execution over at top node while keeping current state of client error flags.
func (en *Engine) reset(ctx context.Context) (bool, error) {
var err error
var isTop bool
for !isTop {
isTop, err = en.st.Top()
if err != nil {
2023-04-17 07:35:36 +02:00
return false, err
}
_, err = en.st.Up()
if err != nil {
2023-04-17 07:35:36 +02:00
return false, err
}
en.ca.Pop()
}
en.st.Restart()
en.initd = false
return en.Init(ctx)
}