WIP implement engine object

This commit is contained in:
lash 2023-04-01 14:47:03 +01:00
parent 39eafc8ff2
commit 4f473c12f0
Signed by untrusted user who does not match committer: lash
GPG Key ID: 21D2E7BB88C2A746
6 changed files with 263 additions and 176 deletions

View File

@ -2,6 +2,7 @@ package engine
import (
"context"
"fmt"
"io"
"log"
@ -16,11 +17,11 @@ import (
//}
type Engine struct {
st state.State
st *state.State
rs resource.Resource
}
func NewEngine(st state.State, rs resource.Resource) Engine {
func NewEngine(st *state.State, rs resource.Resource) Engine {
engine := Engine{st, rs}
return engine
}
@ -28,7 +29,33 @@ func NewEngine(st state.State, rs resource.Resource) Engine {
func(en *Engine) Init(ctx context.Context) error {
b := vm.NewLine([]byte{}, vm.MOVE, []string{"root"}, nil, nil)
var err error
en.st, _, err = vm.Run(b, en.st, en.rs, ctx)
_, err = vm.Run(b, en.st, en.rs, ctx)
if err != nil {
return err
}
location := en.st.Where()
code, err := en.rs.GetCode(location)
if err != nil {
return err
}
return en.st.AppendCode(code)
}
func (en *Engine) Exec(input []byte, ctx context.Context) error {
l := uint8(len(input))
if l > 255 {
return fmt.Errorf("input too long (%v)", l)
}
input = append([]byte{l}, input...)
code, err := en.st.GetCode()
if err != nil {
return err
}
if len(code) == 0 {
return fmt.Errorf("no code to execute")
}
code, err = vm.Apply(input, code, en.st, en.rs, ctx)
en.st.SetCode(code)
return err
}

View File

@ -3,6 +3,8 @@ package engine
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"log"
"path"
"text/template"
@ -16,10 +18,10 @@ import (
type FsWrapper struct {
*resource.FsResource
st state.State
st *state.State
}
func NewFsWrapper(path string, st state.State, ctx context.Context) FsWrapper {
func NewFsWrapper(path string, st *state.State, ctx context.Context) FsWrapper {
rs := resource.NewFsResource(path, ctx)
return FsWrapper {
&rs,
@ -42,12 +44,27 @@ func (r FsWrapper) RenderTemplate(sym string, values map[string]string) (string,
if err != nil {
return "", err
}
log.Printf("template is %v render is %v", v, b)
return b.String(), err
}
func(fs FsWrapper) one(ctx context.Context) (string, error) {
return "one", nil
}
func(fs FsWrapper) FuncFor(sym string) (resource.EntryFunc, error) {
return nil, nil
switch sym {
case "one":
return fs.one, nil
}
return nil, fmt.Errorf("function for %v not found", sym)
}
func(fs FsWrapper) GetCode(sym string) ([]byte, error) {
sym += ".bin"
fp := path.Join(fs.Path, sym)
r, err := ioutil.ReadFile(fp)
log.Printf("getcode for %v %v", fp, r)
return r, err
}
func TestEngineInit(t *testing.T) {
@ -62,8 +79,8 @@ func TestEngineInit(t *testing.T) {
// }
dir := path.Join(testdataloader.GetBasePath(), "testdata")
ctx := context.TODO()
rs := NewFsWrapper(dir, st, ctx)
en := NewEngine(st, rs)
rs := NewFsWrapper(dir, &st, ctx)
en := NewEngine(&st, &rs)
err := en.Init(ctx)
if err != nil {
t.Fatal(err)
@ -78,4 +95,13 @@ func TestEngineInit(t *testing.T) {
if !bytes.Equal(b, []byte("hello world")) {
t.Fatalf("expected result 'hello world', got %v", b)
}
input := []byte("foo")
err = en.Exec(input, ctx)
if err != nil {
t.Fatal(err)
}
r := st.Where()
if r != "bar" {
t.Fatalf("expected where-string 'bar', got %v", r)
}
}

View File

@ -9,19 +9,19 @@ import (
)
type FsResource struct {
path string
Path string
ctx context.Context
}
func NewFsResource(path string, ctx context.Context) (FsResource) {
return FsResource{
path: path,
Path: path,
ctx: ctx,
}
}
func(fs FsResource) GetTemplate(sym string) (string, error) {
fp := path.Join(fs.path, sym)
fp := path.Join(fs.Path, sym)
r, err := ioutil.ReadFile(fp)
s := string(r)
return strings.TrimSpace(s), err

View File

@ -26,7 +26,7 @@ type State struct {
CacheUseSize uint32 // Currently used bytes by all values in cache
Cache []map[string]string // All loaded cache items
CacheMap map[string]string // Mapped
Code []byte // Pending bytecode to execute
code []byte // Pending bytecode to execute
execPath []string // Command symbols stack
arg *string // Optional argument. Nil if not set.
sizes map[string]uint16 // Size limits for all loaded symbols.
@ -366,7 +366,7 @@ func(st *State) Check(key string) bool {
return st.frameOf(key) == -1
}
// Returns size used by values, and remaining size available
// Size returns size used by values, and remaining size available
func(st *State) Size() (uint32, uint32) {
var l int
var c uint16
@ -378,6 +378,25 @@ func(st *State) Size() (uint32, uint32) {
return r, uint32(c)-r
}
// Appendcode adds the given bytecode to the end of the existing code.
func(st *State) AppendCode(b []byte) error {
st.code = append(st.code, b...)
log.Printf("code changed to %v", b)
return nil
}
// SetCode replaces the current bytecode with the given bytecode.
func(st *State) SetCode(b []byte) {
log.Printf("code set to %v", b)
st.code = b
}
func(st *State) GetCode() ([]byte, error) {
b := st.code
st.code = []byte{}
return b, nil
}
// return 0-indexed frame number where key is defined. -1 if not defined
func(st *State) frameOf(key string) int {
for i, m := range st.Cache {
@ -408,4 +427,3 @@ func(st *State) resetCurrent() {
st.sink = nil
st.CacheMap = make(map[string]string)
}

View File

@ -29,34 +29,43 @@ func argFromBytes(input []byte) (string, []byte, error) {
// If the router indicates an argument input, the optional argument is set on the state.
//
// TODO: the bytecode load is a separate step so Run should be run separately.
func Apply(input []byte, instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func Apply(input []byte, instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
var err error
log.Printf("running input %v against instruction %v", input, instruction)
arg, input, err := argFromBytes(input)
if err != nil {
return st, input, err
return input, err
}
rt := router.FromBytes(input)
rt := router.FromBytes(instruction)
sym := rt.Get(arg)
if sym == "" {
sym = rt.Default()
st.PutArg(arg)
}
if sym == "" {
instruction = NewLine([]byte{}, MOVE, []string{"_catch"}, nil, nil)
} else if sym == "_" {
instruction = NewLine([]byte{}, BACK, nil, nil, nil)
} else {
new_instruction := NewLine([]byte{}, MOVE, []string{sym}, nil, nil)
instruction = append(new_instruction, instruction...)
}
st, instruction, err = Run(instruction, st, rs, ctx)
if err != nil {
return st, instruction, err
if sym == "" {
instruction = NewLine([]byte{}, MOVE, []string{"_catch"}, nil, nil)
} else {
instruction, err = rs.GetCode(sym)
if err != nil {
return instruction, err
}
if sym == "_" {
instruction = NewLine([]byte{}, BACK, nil, nil, nil)
} else {
new_instruction := NewLine([]byte{}, MOVE, []string{sym}, nil, nil)
instruction = append(new_instruction, instruction...)
}
}
return st, instruction, nil
instruction, err = Run(instruction, st, rs, ctx)
if err != nil {
return instruction, err
}
return instruction, nil
}
// Run extracts individual op codes and arguments and executes them.
@ -64,61 +73,54 @@ func Apply(input []byte, instruction []byte, st state.State, rs resource.Resourc
// Each step may update the state.
//
// On error, the remaining instructions will be returned. State will not be rolled back.
func Run(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func Run(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
var err error
for len(instruction) > 0 {
log.Printf("instruction is now %v", instruction)
op := binary.BigEndian.Uint16(instruction[:2])
if op > _MAX {
return st, instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX)
return instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX)
}
switch op {
case CATCH:
st, instruction, err = RunCatch(instruction[2:], st, rs, ctx)
break
instruction, err = RunCatch(instruction[2:], st, rs, ctx)
case CROAK:
st, instruction, err = RunCroak(instruction[2:], st, rs, ctx)
break
instruction, err = RunCroak(instruction[2:], st, rs, ctx)
case LOAD:
st, instruction, err = RunLoad(instruction[2:], st, rs, ctx)
break
instruction, err = RunLoad(instruction[2:], st, rs, ctx)
case RELOAD:
st, instruction, err = RunReload(instruction[2:], st, rs, ctx)
break
instruction, err = RunReload(instruction[2:], st, rs, ctx)
case MAP:
st, instruction, err = RunMap(instruction[2:], st, rs, ctx)
break
instruction, err = RunMap(instruction[2:], st, rs, ctx)
case MOVE:
st, instruction, err = RunMove(instruction[2:], st, rs, ctx)
break
instruction, err = RunMove(instruction[2:], st, rs, ctx)
case BACK:
st, instruction, err = RunBack(instruction[2:], st, rs, ctx)
break
instruction, err = RunBack(instruction[2:], st, rs, ctx)
default:
err = fmt.Errorf("Unhandled state: %v", op)
}
if err != nil {
return st, instruction, err
return instruction, err
}
}
return st, instruction, nil
return instruction, nil
}
// RunMap executes the MAP opcode
func RunMap(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func RunMap(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
return instruction, err
}
err = st.Map(head)
return st, tail, err
return tail, err
}
// RunMap executes the CATCH opcode
func RunCatch(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func RunCatch(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
return instruction, err
}
bitFieldSize := tail[0]
bitField := tail[1:1+bitFieldSize]
@ -128,69 +130,69 @@ func RunCatch(instruction []byte, st state.State, rs resource.Resource, ctx cont
st.Down(head)
tail = []byte{}
}
return st, tail, nil
return tail, nil
}
// RunMap executes the CROAK opcode
func RunCroak(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func RunCroak(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
return instruction, err
}
_ = head
_ = tail
st.Reset()
return st, []byte{}, nil
return []byte{}, nil
}
// RunLoad executes the LOAD opcode
func RunLoad(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func RunLoad(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
return instruction, err
}
if !st.Check(head) {
return st, instruction, fmt.Errorf("key %v already loaded", head)
return instruction, fmt.Errorf("key %v already loaded", head)
}
sz := uint16(tail[0])
tail = tail[1:]
r, err := refresh(head, rs, ctx)
if err != nil {
return st, tail, err
return tail, err
}
err = st.Add(head, r, sz)
return st, tail, err
return tail, err
}
// RunLoad executes the RELOAD opcode
func RunReload(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func RunReload(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
return instruction, err
}
r, err := refresh(head, rs, ctx)
if err != nil {
return st, tail, err
return tail, err
}
st.Update(head, r)
return st, tail, nil
return tail, nil
}
// RunLoad executes the MOVE opcode
func RunMove(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func RunMove(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
head, tail, err := instructionSplit(instruction)
if err != nil {
return st, instruction, err
return instruction, err
}
st.Down(head)
return st, tail, nil
return tail, nil
}
// RunLoad executes the BACK opcode
func RunBack(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) {
func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
st.Up()
return st, instruction, nil
return instruction, nil
}
// retrieve data for key
@ -199,6 +201,9 @@ func refresh(key string, rs resource.Resource, ctx context.Context) (string, err
if err != nil {
return "", err
}
if fn == nil {
return "", fmt.Errorf("no retrieve function for external symbol %v", key)
}
return fn(ctx)
}

View File

@ -96,17 +96,16 @@ func TestRun(t *testing.T) {
rs := TestResource{}
b := []byte{0x00, MOVE, 0x03}
b = append(b, []byte("foo")...)
r, _, err := Run(b, st, &rs, context.TODO())
_, err := Run(b, &st, &rs, context.TODO())
if err != nil {
t.Errorf("error on valid opcode: %v", err)
}
b = []byte{0x01, 0x02}
r, _, err = Run(b, st, &rs, context.TODO())
_, err = Run(b, &st, &rs, context.TODO())
if err == nil {
t.Errorf("no error on invalid opcode")
}
_ = r
}
func TestRunLoadRender(t *testing.T) {
@ -117,7 +116,7 @@ func TestRunLoadRender(t *testing.T) {
ins := append([]byte{uint8(len(sym))}, []byte(sym)...)
ins = append(ins, 0x0a)
var err error
st, _, err = RunLoad(ins, st, &rs, context.TODO())
_, err = RunLoad(ins, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@ -142,7 +141,7 @@ func TestRunLoadRender(t *testing.T) {
sym = "two"
ins = append([]byte{uint8(len(sym))}, []byte(sym)...)
ins = append(ins, 0)
st, _, err = RunLoad(ins, st, &rs, context.TODO())
_, err = RunLoad(ins, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@ -166,7 +165,7 @@ func TestRunMultiple(t *testing.T) {
b := []byte{}
b = NewLine(b, LOAD, []string{"one"}, nil, []uint8{0})
b = NewLine(b, LOAD, []string{"two"}, nil, []uint8{42})
st, _, err := Run(b, st, &rs, context.TODO())
_, err := Run(b, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@ -178,7 +177,7 @@ func TestRunReload(t *testing.T) {
b := []byte{}
b = NewLine(b, LOAD, []string{"dyn"}, nil, []uint8{0})
b = NewLine(b, MAP, []string{"dyn"}, nil, nil)
st, _, err := Run(b, st, &rs, context.TODO())
_, err := Run(b, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@ -192,7 +191,7 @@ func TestRunReload(t *testing.T) {
dynVal = "baz"
b = []byte{}
b = NewLine(b, RELOAD, []string{"dyn"}, nil, nil)
st, _, err = Run(b, st, &rs, context.TODO())
_, err = Run(b, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@ -209,14 +208,15 @@ func TestRunReload(t *testing.T) {
func TestRunArg(t *testing.T) {
st := state.NewState(5)
rs := TestResource{}
rt := router.NewRouter()
rt.Add("foo", "bar")
rt.Add("baz", "xyzzy")
b := []byte{0x03}
b = append(b, []byte("baz")...)
b = append(b, rt.ToBytes()...)
//b = append(b, rt.ToBytes()...)
var err error
st, b, err = Apply(b, []byte{}, st, nil, context.TODO())
b, err = Apply(b, rt.ToBytes(), &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@ -237,9 +237,9 @@ func TestRunArgInvalid(t *testing.T) {
rt.Add("baz", "xyzzy")
b := []byte{0x03}
b = append(b, []byte("bar")...)
b = append(b, rt.ToBytes()...)
//b = append(b, rt.ToBytes()...)
var err error
st, b, err = Apply(b, []byte{}, st, nil, context.TODO())
b, err = Apply(b, rt.ToBytes(), &st, nil, context.TODO())
if err != nil {
t.Error(err)
}
@ -253,97 +253,108 @@ func TestRunArgInvalid(t *testing.T) {
}
}
func TestRunArgInstructions(t *testing.T) {
st := state.NewState(5)
rs := TestResource{}
rt := router.NewRouter()
rt.Add("foo", "bar")
b := []byte{0x03}
b = append(b, []byte("foo")...)
b = append(b, rt.ToBytes()...)
bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0})
bi = NewLine(bi, LOAD, []string{"two"}, nil, []uint8{3})
bi = NewLine(bi, MAP, []string{"one"}, nil, nil)
bi = NewLine(bi, MAP, []string{"two"}, nil, nil)
var err error
st, b, err = Apply(b, bi, st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
l := len(b)
if l != 0 {
t.Errorf("expected empty remainder, got length %v: %v", l, b)
}
loc := st.Where()
if loc != "bar" {
t.Errorf("expected where-state _catch, got %v", loc)
}
m, err := st.Get()
if err != nil {
t.Error(err)
}
r, err := rs.RenderTemplate(loc, m)
if err != nil {
t.Error(err)
}
_ = r
}
func TestRunMoveAndBack(t *testing.T) {
st := state.NewState(5)
rs := TestResource{}
rt := router.NewRouter()
rt.Add("foo", "bar")
b := []byte{0x03}
b = append(b, []byte("foo")...)
b = append(b, rt.ToBytes()...)
bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0})
var err error
st, b, err = Apply(b, bi, st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
l := len(b)
if l != 0 {
t.Errorf("expected empty remainder, got length %v: %v", l, b)
}
rt = router.NewRouter()
rt.Add("foo", "baz")
b = []byte{0x03}
b = append(b, []byte("foo")...)
b = append(b, rt.ToBytes()...)
bi = NewLine([]byte{}, LOAD, []string{"two"}, nil, []uint8{0})
st, b, err = Apply(b, bi, st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
l = len(b)
if l != 0 {
t.Errorf("expected empty remainder, got length %v: %v", l, b)
}
rt = router.NewRouter()
rt.Add("foo", "_")
b = []byte{0x03}
b = append(b, []byte("foo")...)
b = append(b, rt.ToBytes()...)
st, b, err = Apply(b, []byte{}, st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
l = len(b)
if l != 0 {
t.Errorf("expected empty remainder, got length %v: %v", l, b)
}
loc := st.Where()
if loc != "bar" {
t.Errorf("expected where-string 'bar', got %v", loc)
}
}
//func TestRunArgInstructions(t *testing.T) {
// st := state.NewState(5)
// rs := TestResource{}
//
// rt := router.NewRouter()
// rt.Add("foo", "bar")
// b := []byte{0x03}
// b = append(b, []byte("foo")...)
// b = append(b, rt.ToBytes()...)
//
// bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0})
// bi = NewLine(bi, LOAD, []string{"two"}, nil, []uint8{3})
// bi = NewLine(bi, MAP, []string{"one"}, nil, nil)
// bi = NewLine(bi, MAP, []string{"two"}, nil, nil)
// var err error
// b, err = Apply(b, bi, &st, &rs, context.TODO())
// if err != nil {
// t.Error(err)
// }
// l := len(b)
// if l != 0 {
// t.Errorf("expected empty remainder, got length %v: %v", l, b)
// }
// loc := st.Where()
// if loc != "bar" {
// t.Errorf("expected where-state bar, got %v", loc)
// }
// m, err := st.Get()
// if err != nil {
// t.Fatal(err)
// }
// _, err = rs.RenderTemplate(loc, m)
// if err == nil {
// t.Fatalf("expected error to generate template")
// }
// _, err = Run(bi, &st, &rs, context.TODO())
// if err != nil {
// t.Error(err)
// }
// m, err = st.Get()
// if err != nil {
// t.Fatal(err)
// }
// _, err = rs.RenderTemplate(loc, m)
// if err != nil {
// t.Fatal(err)
// }
//}
//
//func TestRunMoveAndBack(t *testing.T) {
// st := state.NewState(5)
// rs := TestResource{}
// rt := router.NewRouter()
// rt.Add("foo", "bar")
// b := []byte{0x03}
// b = append(b, []byte("foo")...)
// //b = append(b, rt.ToBytes()...)
// bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0})
//
// var err error
// b, err = Apply(b, bi, &st, &rs, context.TODO())
// if err != nil {
// t.Error(err)
// }
// l := len(b)
// if l != 0 {
// t.Errorf("expected empty remainder, got length %v: %v", l, b)
// }
//
// rt = router.NewRouter()
// rt.Add("foo", "baz")
// b = []byte{0x03}
// b = append(b, []byte("foo")...)
// b = append(b, rt.ToBytes()...)
// bi = NewLine([]byte{}, LOAD, []string{"two"}, nil, []uint8{0})
// b, err = Apply(b, bi, &st, &rs, context.TODO())
// if err != nil {
// t.Error(err)
// }
// l = len(b)
// if l != 0 {
// t.Errorf("expected empty remainder, got length %v: %v", l, b)
// }
//
// rt = router.NewRouter()
// rt.Add("foo", "_")
// b = []byte{0x03}
// b = append(b, []byte("foo")...)
// //b = append(b, rt.ToBytes()...)
// b, err = Apply(b, rt.ToBytes(), &st, &rs, context.TODO())
// if err != nil {
// t.Error(err)
// }
// l = len(b)
// if l != 0 {
// t.Errorf("expected empty remainder, got length %v: %v", l, b)
// }
// loc := st.Where()
// if loc != "bar" {
// t.Errorf("expected where-string 'bar', got %v", loc)
// }
//}
func TestCatchAndBack(t *testing.T) {
st := state.NewState(5)
@ -353,7 +364,7 @@ func TestCatchAndBack(t *testing.T) {
b := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0})
b = NewLine(b, CATCH, []string{"bar"}, []byte{0x04}, nil)
b = NewLine(b, MOVE, []string{"foo"}, nil, nil)
st, _, err := Run(b, st, &rs, context.TODO())
_, err := Run(b, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}
@ -366,7 +377,7 @@ func TestCatchAndBack(t *testing.T) {
b = NewLine([]byte{}, LOAD, []string{"two"}, nil, []uint8{0})
b = NewLine(b, CATCH, []string{"bar"}, []byte{0x04}, nil)
b = NewLine(b, MOVE, []string{"foo"}, nil, nil)
st, _, err = Run(b, st, &rs, context.TODO())
_, err = Run(b, &st, &rs, context.TODO())
if err != nil {
t.Error(err)
}