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"
|
|
|
|
|
2023-04-09 11:16:41 +02:00
|
|
|
"git.defalsify.org/festive/cache"
|
2023-04-10 17:12:22 +02:00
|
|
|
"git.defalsify.org/festive/render"
|
2023-04-01 11:58:02 +02:00
|
|
|
"git.defalsify.org/festive/resource"
|
|
|
|
"git.defalsify.org/festive/state"
|
|
|
|
"git.defalsify.org/festive/vm"
|
|
|
|
)
|
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.
|
2023-04-10 17:12:22 +02:00
|
|
|
type Config struct {
|
2023-04-12 19:04:36 +02:00
|
|
|
OutputSize uint32 // Maximum size of output from a single rendered page
|
2023-04-10 17:12:22 +02:00
|
|
|
}
|
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 {
|
2023-04-01 15:47:03 +02:00
|
|
|
st *state.State
|
2023-04-01 11:58:02 +02:00
|
|
|
rs resource.Resource
|
2023-04-09 11:16:41 +02:00
|
|
|
ca cache.Memory
|
2023-04-09 16:35:26 +02:00
|
|
|
vm *vm.Vm
|
2023-04-01 11:58:02 +02:00
|
|
|
}
|
|
|
|
|
2023-04-01 16:04:38 +02:00
|
|
|
// NewEngine creates a new Engine
|
2023-04-10 17:12:22 +02:00
|
|
|
func NewEngine(cfg Config, st *state.State, rs resource.Resource, ca cache.Memory) Engine {
|
|
|
|
var szr *render.Sizer
|
|
|
|
if cfg.OutputSize > 0 {
|
|
|
|
szr = render.NewSizer(cfg.OutputSize)
|
|
|
|
}
|
2023-04-09 11:16:41 +02:00
|
|
|
engine := Engine{
|
|
|
|
st: st,
|
|
|
|
rs: rs,
|
|
|
|
ca: ca,
|
2023-04-10 17:12:22 +02:00
|
|
|
vm: vm.NewVm(st, rs, ca, szr),
|
2023-04-09 11:16:41 +02:00
|
|
|
}
|
2023-04-01 11:58:02 +02:00
|
|
|
return engine
|
|
|
|
}
|
|
|
|
|
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.
|
2023-04-02 10:07:53 +02:00
|
|
|
func(en *Engine) Init(sym string, ctx context.Context) error {
|
2023-04-06 10:14:53 +02:00
|
|
|
err := en.st.SetInput([]byte{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-04-03 00:53:21 +02:00
|
|
|
b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil)
|
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 {
|
|
|
|
return err
|
|
|
|
}
|
2023-04-03 00:53:21 +02:00
|
|
|
en.st.SetCode(b)
|
|
|
|
return 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.
|
|
|
|
//
|
|
|
|
// 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) {
|
2023-04-08 10:31:32 +02:00
|
|
|
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)
|
2023-04-01 23:19:12 +02:00
|
|
|
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
|
|
|
|
2023-04-06 10:14:53 +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
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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, nil
|
|
|
|
}
|
|
|
|
|
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")
|
|
|
|
return false, nil
|
|
|
|
}
|
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.
|
2023-04-12 09:30:35 +02:00
|
|
|
func(en *Engine) WriteResult(w io.Writer, ctx context.Context) error {
|
|
|
|
r, err := en.vm.Render(ctx)
|
2023-04-01 11:58:02 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-04-10 16:26:18 +02:00
|
|
|
_, err = io.WriteString(w, r)
|
2023-04-01 11:58:02 +02:00
|
|
|
return err
|
|
|
|
}
|