Add engine and state restart on empty termination node
This commit is contained in:
parent
957d59bb1a
commit
bf1d634474
31
README.md
31
README.md
@ -56,6 +56,8 @@ The signal flag arguments should only set a single flag to be tested. If more th
|
|||||||
|
|
||||||
First 8 flags are reserved and used for internal VM operations.
|
First 8 flags are reserved and used for internal VM operations.
|
||||||
|
|
||||||
|
When a signal is caught, the *bytecode buffer is flushed* before the target symbol code is loaded.
|
||||||
|
|
||||||
|
|
||||||
### Avoid duplicate menu items
|
### Avoid duplicate menu items
|
||||||
|
|
||||||
@ -144,17 +146,17 @@ Currently the following rules apply for encoding in version `0`:
|
|||||||
|
|
||||||
This repository provides a `golang` reference implementation for the `vise` concept.
|
This repository provides a `golang` reference implementation for the `vise` concept.
|
||||||
|
|
||||||
In this reference implementation some constraints apply
|
|
||||||
|
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
- `vm`: Defines instructions, and applies transformations according to the instructions.
|
- `asm`: Assembly parser and compiler.
|
||||||
- `state`: Holds the bytecode buffer, error states and navigation states.
|
|
||||||
- `cache`: Holds and manages all loaded content.
|
- `cache`: Holds and manages all loaded content.
|
||||||
- `resource`: Retrieves data and bytecode from external symbols, and retrieves templates.
|
|
||||||
- `render`: Renders menu and templates, and enforces output size constraints.
|
|
||||||
- `engine`: Outermost interface. Orchestrates execution of bytecode against input.
|
- `engine`: Outermost interface. Orchestrates execution of bytecode against input.
|
||||||
|
- `persist`: Interface and reference implementation of `state` and `cache` persistence across asynchronous vm executions.
|
||||||
|
- `render`: Renders menu and templates, and enforces output size constraints.
|
||||||
|
- `resource`: Retrieves data and bytecode from external symbols, and retrieves templates.
|
||||||
|
- `state`: Holds the bytecode buffer, error states and navigation states.
|
||||||
|
- `vm`: Defines instructions, and applies transformations according to the instructions.
|
||||||
|
|
||||||
|
|
||||||
### Template rendering
|
### Template rendering
|
||||||
@ -164,6 +166,21 @@ Template rendering is done using the `text/template` faciilty in the `golang` st
|
|||||||
It expects all replacement symbols to be available at time of rendering, and has no tolerance for missing ones.
|
It expects all replacement symbols to be available at time of rendering, and has no tolerance for missing ones.
|
||||||
|
|
||||||
|
|
||||||
|
### Runtime engine
|
||||||
|
|
||||||
|
The runtime engine:
|
||||||
|
|
||||||
|
* Validates client input
|
||||||
|
* Runs VM with client input
|
||||||
|
* Renders result
|
||||||
|
* Restarts execution from top if the vm has nothing more to do.
|
||||||
|
|
||||||
|
There are two flavors of the engine:
|
||||||
|
|
||||||
|
* `engine.Loop` - class used for continuous, in-memory interaction with the vm (e.g. terminal).
|
||||||
|
* `engine.RunPersisted` - method which combines single vm executions with persisted state (e.g. http).
|
||||||
|
|
||||||
|
|
||||||
## Bytecode examples
|
## Bytecode examples
|
||||||
|
|
||||||
(Minimal, WIP)
|
(Minimal, WIP)
|
||||||
@ -182,7 +199,7 @@ It expects all replacement symbols to be available at time of rendering, and has
|
|||||||
|
|
||||||
## Assembly examples
|
## Assembly examples
|
||||||
|
|
||||||
See `testdata/*.fst`
|
See `testdata/*.vis`
|
||||||
|
|
||||||
|
|
||||||
## Development tools
|
## Development tools
|
||||||
|
@ -28,6 +28,7 @@ type Engine struct {
|
|||||||
rs resource.Resource
|
rs resource.Resource
|
||||||
ca cache.Memory
|
ca cache.Memory
|
||||||
vm *vm.Vm
|
vm *vm.Vm
|
||||||
|
root string
|
||||||
initd bool
|
initd bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,11 +45,8 @@ func NewEngine(cfg Config, st *state.State, rs resource.Resource, ca cache.Memor
|
|||||||
ca: ca,
|
ca: ca,
|
||||||
vm: vm.NewVm(st, rs, ca, szr),
|
vm: vm.NewVm(st, rs, ca, szr),
|
||||||
}
|
}
|
||||||
var err error
|
engine.root = cfg.Root
|
||||||
if st.Moves == 0 {
|
return engine, nil
|
||||||
err = engine.Init(cfg.Root, ctx)
|
|
||||||
}
|
|
||||||
return engine, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init must be explicitly called before using the Engine instance.
|
// Init must be explicitly called before using the Engine instance.
|
||||||
@ -62,6 +60,7 @@ func(en *Engine) Init(sym string, ctx context.Context) error {
|
|||||||
if sym == "" {
|
if sym == "" {
|
||||||
return fmt.Errorf("start sym empty")
|
return fmt.Errorf("start sym empty")
|
||||||
}
|
}
|
||||||
|
inSave, _ := en.st.GetInput()
|
||||||
err := en.st.SetInput([]byte{})
|
err := en.st.SetInput([]byte{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -77,6 +76,10 @@ func(en *Engine) Init(sym string, ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
log.Printf("ended init VM run with code %x", b)
|
log.Printf("ended init VM run with code %x", b)
|
||||||
en.st.SetCode(b)
|
en.st.SetCode(b)
|
||||||
|
err = en.st.SetInput(inSave)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
en.initd = true
|
en.initd = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -92,7 +95,17 @@ func(en *Engine) Init(sym string, ctx context.Context) error {
|
|||||||
// - no current bytecode is available
|
// - no current bytecode is available
|
||||||
// - input processing against bytcode failed
|
// - input processing against bytcode failed
|
||||||
func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
|
func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
|
||||||
err := vm.ValidInput(input)
|
var err error
|
||||||
|
if en.st.Moves == 0 {
|
||||||
|
err = en.Init(en.root, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(input) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = vm.ValidInput(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
@ -109,6 +122,7 @@ func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
|
|||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
return false, fmt.Errorf("no code to execute")
|
return false, fmt.Errorf("no code to execute")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("start new VM run with code %x", code)
|
log.Printf("start new VM run with code %x", code)
|
||||||
code, err = en.vm.Run(code, ctx)
|
code, err = en.vm.Run(code, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -124,13 +138,14 @@ func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
|
|||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
log.Printf("terminated with code remaining: %x", code)
|
log.Printf("terminated with code remaining: %x", code)
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
en.st.SetCode(code)
|
en.st.SetCode(code)
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
log.Printf("runner finished with no remaining code")
|
log.Printf("runner finished with no remaining code")
|
||||||
return false, nil
|
err = en.reset(ctx)
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -149,3 +164,22 @@ func(en *Engine) WriteResult(w io.Writer, ctx context.Context) (int, error) {
|
|||||||
}
|
}
|
||||||
return io.WriteString(w, r)
|
return io.WriteString(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func(en *Engine) reset(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
var isTop bool
|
||||||
|
for !isTop {
|
||||||
|
isTop, err = en.st.Top()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = en.st.Up()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
en.ca.Pop()
|
||||||
|
}
|
||||||
|
en.st.Restart()
|
||||||
|
en.initd = false
|
||||||
|
return en.Init(en.root, ctx)
|
||||||
|
}
|
||||||
|
@ -87,7 +87,11 @@ func TestEngineInit(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
//
|
|
||||||
|
err = en.Init("root", ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
w := bytes.NewBuffer(nil)
|
w := bytes.NewBuffer(nil)
|
||||||
_, err = en.WriteResult(w, ctx)
|
_, err = en.WriteResult(w, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -152,3 +156,40 @@ func TestEngineExecInvalidInput(t *testing.T) {
|
|||||||
t.Fatalf("expected fail on invalid input")
|
t.Fatalf("expected fail on invalid input")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEngineResumeTerminated(t *testing.T) {
|
||||||
|
generateTestData(t)
|
||||||
|
ctx := context.TODO()
|
||||||
|
st := state.NewState(17)
|
||||||
|
rs := NewFsWrapper(dataDir, &st)
|
||||||
|
ca := cache.NewCache().WithCacheSize(1024)
|
||||||
|
|
||||||
|
en, err := NewEngine(Config{
|
||||||
|
Root: "root",
|
||||||
|
}, &st, &rs, ca, ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = en.Init("root", ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = en.Exec([]byte("1"), ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = en.Exec([]byte("1"), ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
location, idx := st.Where()
|
||||||
|
if location != "root" {
|
||||||
|
t.Fatalf("expected 'root', got %s", location)
|
||||||
|
}
|
||||||
|
if idx != 0 {
|
||||||
|
t.Fatalf("expected idx '0', got %v", idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,8 +6,6 @@ const (
|
|||||||
FLAG_TERMINATE = 3
|
FLAG_TERMINATE = 3
|
||||||
FLAG_DIRTY = 4
|
FLAG_DIRTY = 4
|
||||||
FLAG_LOADFAIL = 5
|
FLAG_LOADFAIL = 5
|
||||||
FLAG_USERSTART = 9
|
|
||||||
//FLAG_WRITEABLE = FLAG_LOADFAIL
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsWriteableFlag(flag uint32) bool {
|
func IsWriteableFlag(flag uint32) bool {
|
||||||
|
@ -327,10 +327,22 @@ func(st *State) SetInput(input []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func(st *State) Reset() error {
|
// Reset re-initializes the state to run from top node with accumulated client state.
|
||||||
|
func(st *State) Restart() error {
|
||||||
|
st.resetBaseFlags()
|
||||||
|
st.Moves = 0
|
||||||
|
st.SizeIdx = 0
|
||||||
|
st.input = []byte{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String implements String interface
|
||||||
func(st State) String() string {
|
func(st State) String() string {
|
||||||
return fmt.Sprintf("moves %v idx %v path: %s", st.Moves, st.SizeIdx, strings.Join(st.ExecPath, "/"))
|
return fmt.Sprintf("moves %v idx %v path: %s", st.Moves, st.SizeIdx, strings.Join(st.ExecPath, "/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initializes all flags not in control of client.
|
||||||
|
func(st *State) resetBaseFlags() {
|
||||||
|
st.Flags[0] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -195,7 +195,6 @@ func(vm *Vm) RunCatch(b []byte, ctx context.Context) ([]byte, error) {
|
|||||||
b = bh
|
b = bh
|
||||||
vm.st.Down(sym)
|
vm.st.Down(sym)
|
||||||
vm.ca.Push()
|
vm.ca.Push()
|
||||||
vm.Reset()
|
|
||||||
}
|
}
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
@ -213,7 +212,6 @@ func(vm *Vm) RunCroak(b []byte, ctx context.Context) ([]byte, error) {
|
|||||||
if r {
|
if r {
|
||||||
log.Printf("croak at flag %v, purging and moving to top", sig)
|
log.Printf("croak at flag %v, purging and moving to top", sig)
|
||||||
vm.Reset()
|
vm.Reset()
|
||||||
vm.st.Reset()
|
|
||||||
vm.pg.Reset()
|
vm.pg.Reset()
|
||||||
vm.ca.Reset()
|
vm.ca.Reset()
|
||||||
b = []byte{}
|
b = []byte{}
|
||||||
|
Loading…
Reference in New Issue
Block a user