WIP Factor out instruction parsing
This commit is contained in:
parent
31e011d531
commit
8c287b909b
@ -1,10 +1,9 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
const VERSION = 0
|
const VERSION = 0
|
||||||
|
|
||||||
|
type Opcode uint16
|
||||||
|
|
||||||
// VM Opcodes
|
// VM Opcodes
|
||||||
const (
|
const (
|
||||||
BACK = 0
|
BACK = 0
|
||||||
@ -18,21 +17,3 @@ const (
|
|||||||
INCMP = 8
|
INCMP = 8
|
||||||
_MAX = 8
|
_MAX = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLine creates a new instruction line for the VM.
|
|
||||||
func NewLine(instructionList []byte, instruction uint16, strargs []string, byteargs []byte, numargs []uint8) []byte {
|
|
||||||
b := []byte{0x00, 0x00}
|
|
||||||
binary.BigEndian.PutUint16(b, instruction)
|
|
||||||
for _, arg := range strargs {
|
|
||||||
b = append(b, uint8(len(arg)))
|
|
||||||
b = append(b, []byte(arg)...)
|
|
||||||
}
|
|
||||||
if byteargs != nil {
|
|
||||||
b = append(b, uint8(len(byteargs)))
|
|
||||||
b = append(b, byteargs...)
|
|
||||||
}
|
|
||||||
if numargs != nil {
|
|
||||||
b = append(b, numargs...)
|
|
||||||
}
|
|
||||||
return append(instructionList, b...)
|
|
||||||
}
|
|
||||||
|
205
go/vm/runner.go
Normal file
205
go/vm/runner.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.defalsify.org/festive/resource"
|
||||||
|
"git.defalsify.org/festive/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
//type Runner func(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error)
|
||||||
|
|
||||||
|
// Run extracts individual op codes and arguments and executes them.
|
||||||
|
//
|
||||||
|
// 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) ([]byte, error) {
|
||||||
|
var err error
|
||||||
|
for len(instruction) > 0 {
|
||||||
|
log.Printf("instruction is now 0x%x", instruction)
|
||||||
|
op := binary.BigEndian.Uint16(instruction[:2])
|
||||||
|
if op > _MAX {
|
||||||
|
return instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX)
|
||||||
|
}
|
||||||
|
switch op {
|
||||||
|
case CATCH:
|
||||||
|
instruction, err = RunCatch(instruction[2:], st, rs, ctx)
|
||||||
|
case CROAK:
|
||||||
|
instruction, err = RunCroak(instruction[2:], st, rs, ctx)
|
||||||
|
case LOAD:
|
||||||
|
instruction, err = RunLoad(instruction[2:], st, rs, ctx)
|
||||||
|
case RELOAD:
|
||||||
|
instruction, err = RunReload(instruction[2:], st, rs, ctx)
|
||||||
|
case MAP:
|
||||||
|
instruction, err = RunMap(instruction[2:], st, rs, ctx)
|
||||||
|
case MOVE:
|
||||||
|
instruction, err = RunMove(instruction[2:], st, rs, ctx)
|
||||||
|
case BACK:
|
||||||
|
instruction, err = RunBack(instruction[2:], st, rs, ctx)
|
||||||
|
case INCMP:
|
||||||
|
instruction, err = RunIncmp(instruction[2:], st, rs, ctx)
|
||||||
|
case HALT:
|
||||||
|
return RunHalt(instruction[2:], st, rs, ctx)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Unhandled state: %v", op)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instruction, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunMap executes the MAP opcode
|
||||||
|
func RunMap(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
head, tail, err := instructionSplit(instruction)
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
err = st.Map(head)
|
||||||
|
return tail, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunMap executes the CATCH opcode
|
||||||
|
func RunCatch(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
head, tail, err := instructionSplit(instruction)
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
bitFieldSize := tail[0]
|
||||||
|
bitField := tail[1:1+bitFieldSize]
|
||||||
|
tail = tail[1+bitFieldSize:]
|
||||||
|
matchMode := tail[0] // matchmode 1 is match NOT set bit
|
||||||
|
tail = tail[1:]
|
||||||
|
match := false
|
||||||
|
if matchMode > 0 {
|
||||||
|
if !st.GetIndex(bitField) {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
} else if st.GetIndex(bitField) {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
log.Printf("catch at flag %v, moving to %v", bitField, head)
|
||||||
|
st.Down(head)
|
||||||
|
tail = []byte{}
|
||||||
|
}
|
||||||
|
return tail, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunMap executes the CROAK opcode
|
||||||
|
func RunCroak(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
head, tail, err := instructionSplit(instruction)
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
_ = head
|
||||||
|
_ = tail
|
||||||
|
st.Reset()
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunLoad executes the LOAD opcode
|
||||||
|
func RunLoad(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
head, tail, err := instructionSplit(instruction)
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
if !st.Check(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 tail, err
|
||||||
|
}
|
||||||
|
err = st.Add(head, r, sz)
|
||||||
|
return tail, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunLoad executes the RELOAD opcode
|
||||||
|
func RunReload(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
head, tail, err := instructionSplit(instruction)
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
r, err := refresh(head, rs, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return tail, err
|
||||||
|
}
|
||||||
|
st.Update(head, r)
|
||||||
|
return tail, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunLoad executes the MOVE opcode
|
||||||
|
func RunMove(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
head, tail, err := instructionSplit(instruction)
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
st.Down(head)
|
||||||
|
return tail, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunLoad executes the BACK opcode
|
||||||
|
func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
st.Up()
|
||||||
|
return instruction, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunIncmp executes the INCMP opcode
|
||||||
|
func RunIncmp(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
head, tail, err := instructionSplit(instruction)
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
sym, tail, err := instructionSplit(tail)
|
||||||
|
if err != nil {
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
v, err := st.GetFlag(state.FLAG_INMATCH)
|
||||||
|
if err != nil {
|
||||||
|
return tail, err
|
||||||
|
}
|
||||||
|
if v {
|
||||||
|
return tail, nil
|
||||||
|
}
|
||||||
|
input, err := st.GetInput()
|
||||||
|
if err != nil {
|
||||||
|
return tail, err
|
||||||
|
}
|
||||||
|
log.Printf("checking input %v %v", input, head)
|
||||||
|
if head == string(input) {
|
||||||
|
log.Printf("input match for '%s'", input)
|
||||||
|
_, err = st.SetFlag(state.FLAG_INMATCH)
|
||||||
|
st.Down(sym)
|
||||||
|
}
|
||||||
|
return tail, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunHalt executes the HALT opcode
|
||||||
|
func RunHalt(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
log.Printf("found HALT, stopping")
|
||||||
|
_, err := st.ResetFlag(state.FLAG_INMATCH)
|
||||||
|
return instruction, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// retrieve data for key
|
||||||
|
func refresh(key string, rs resource.Resource, ctx context.Context) (string, error) {
|
||||||
|
fn, err := rs.FuncFor(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if fn == nil {
|
||||||
|
return "", fmt.Errorf("no retrieve function for external symbol %v", key)
|
||||||
|
}
|
||||||
|
return fn(ctx)
|
||||||
|
}
|
284
go/vm/runner_test.go
Normal file
284
go/vm/runner_test.go
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/festive/resource"
|
||||||
|
"git.defalsify.org/festive/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dynVal = "three"
|
||||||
|
|
||||||
|
type TestResource struct {
|
||||||
|
state *state.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOne(ctx context.Context) (string, error) {
|
||||||
|
return "one", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTwo(ctx context.Context) (string, error) {
|
||||||
|
return "two", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDyn(ctx context.Context) (string, error) {
|
||||||
|
return dynVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestStatefulResolver struct {
|
||||||
|
state *state.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TestResource) GetTemplate(sym string) (string, error) {
|
||||||
|
switch sym {
|
||||||
|
case "foo":
|
||||||
|
return "inky pinky blinky clyde", nil
|
||||||
|
case "bar":
|
||||||
|
return "inky pinky {{.one}} blinky {{.two}} clyde", nil
|
||||||
|
case "baz":
|
||||||
|
return "inky pinky {{.baz}} blinky clyde", nil
|
||||||
|
case "three":
|
||||||
|
return "{{.one}} inky pinky {{.three}} blinky clyde {{.two}}", nil
|
||||||
|
case "_catch":
|
||||||
|
return "aiee", nil
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("unknown symbol %s", sym))
|
||||||
|
return "", fmt.Errorf("unknown symbol %s", sym)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TestResource) RenderTemplate(sym string, values map[string]string) (string, error) {
|
||||||
|
return resource.DefaultRenderTemplate(r, sym, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TestResource) FuncFor(sym string) (resource.EntryFunc, error) {
|
||||||
|
switch sym {
|
||||||
|
case "one":
|
||||||
|
return getOne, nil
|
||||||
|
case "two":
|
||||||
|
return getTwo, nil
|
||||||
|
case "dyn":
|
||||||
|
return getDyn, nil
|
||||||
|
case "arg":
|
||||||
|
return r.getInput, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("invalid function: '%s'", sym)
|
||||||
|
}
|
||||||
|
|
||||||
|
func(r *TestResource) getInput(ctx context.Context) (string, error) {
|
||||||
|
v, err := r.state.GetInput()
|
||||||
|
return string(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func(r *TestResource) GetCode(sym string) ([]byte, error) {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
rs := TestResource{}
|
||||||
|
b := []byte{0x00, MOVE, 0x03}
|
||||||
|
b = append(b, []byte("foo")...)
|
||||||
|
_, err := Run(b, &st, &rs, context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error on valid opcode: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b = []byte{0x01, 0x02}
|
||||||
|
_, err = Run(b, &st, &rs, context.TODO())
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("no error on invalid opcode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunLoadRender(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
st.Down("barbarbar")
|
||||||
|
rs := TestResource{}
|
||||||
|
sym := "one"
|
||||||
|
ins := append([]byte{uint8(len(sym))}, []byte(sym)...)
|
||||||
|
ins = append(ins, 0x0a)
|
||||||
|
var err error
|
||||||
|
_, err = RunLoad(ins, &st, &rs, context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
m, err := st.Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
r, err := rs.RenderTemplate("foo", m)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
expect := "inky pinky blinky clyde"
|
||||||
|
if r != expect {
|
||||||
|
t.Errorf("Expected %v, got %v", []byte(expect), []byte(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err = rs.RenderTemplate("bar", m)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error for render of bar: %v" ,err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sym = "two"
|
||||||
|
ins = append([]byte{uint8(len(sym))}, []byte(sym)...)
|
||||||
|
ins = append(ins, 0)
|
||||||
|
_, err = RunLoad(ins, &st, &rs, context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
m, err = st.Get()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
r, err = rs.RenderTemplate("bar", m)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
expect = "inky pinky one blinky two clyde"
|
||||||
|
if r != expect {
|
||||||
|
t.Errorf("Expected %v, got %v", expect, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunMultiple(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
rs := TestResource{}
|
||||||
|
b := []byte{}
|
||||||
|
b = NewLine(b, LOAD, []string{"one"}, nil, []uint8{0})
|
||||||
|
b = NewLine(b, LOAD, []string{"two"}, nil, []uint8{42})
|
||||||
|
_, err := Run(b, &st, &rs, context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunReload(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
rs := TestResource{}
|
||||||
|
b := []byte{}
|
||||||
|
b = NewLine(b, LOAD, []string{"dyn"}, nil, []uint8{0})
|
||||||
|
b = NewLine(b, MAP, []string{"dyn"}, nil, nil)
|
||||||
|
_, err := Run(b, &st, &rs, context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
r, err := st.Val("dyn")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if r != "three" {
|
||||||
|
t.Errorf("expected result 'three', got %v", r)
|
||||||
|
}
|
||||||
|
dynVal = "baz"
|
||||||
|
b = []byte{}
|
||||||
|
b = NewLine(b, RELOAD, []string{"dyn"}, nil, nil)
|
||||||
|
_, err = Run(b, &st, &rs, context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
r, err = st.Val("dyn")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
log.Printf("dun now %s", r)
|
||||||
|
if r != "baz" {
|
||||||
|
t.Errorf("expected result 'baz', got %v", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHalt(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
rs := TestResource{}
|
||||||
|
b := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0})
|
||||||
|
b = NewLine(b, HALT, nil, nil, nil)
|
||||||
|
b = NewLine(b, MOVE, []string{"foo"}, nil, nil)
|
||||||
|
var err error
|
||||||
|
b, err = Run(b, &st, &rs, context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
r := st.Where()
|
||||||
|
if r == "foo" {
|
||||||
|
t.Fatalf("Expected where-symbol not to be 'foo'")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(b[:2], []byte{0x00, MOVE}) {
|
||||||
|
t.Fatalf("Expected MOVE instruction, found '%v'", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunArg(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
rs := TestResource{}
|
||||||
|
|
||||||
|
input := []byte("bar")
|
||||||
|
_ = st.SetInput(input)
|
||||||
|
|
||||||
|
bi := NewLine([]byte{}, INCMP, []string{"bar", "baz"}, nil, nil)
|
||||||
|
b, err := Run(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)
|
||||||
|
}
|
||||||
|
r := st.Where()
|
||||||
|
if r != "baz" {
|
||||||
|
t.Errorf("expected where-state baz, got %v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunInputHandler(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
rs := TestResource{}
|
||||||
|
|
||||||
|
_ = st.SetInput([]byte("baz"))
|
||||||
|
|
||||||
|
bi := NewLine([]byte{}, INCMP, []string{"bar", "aiee"}, nil, nil)
|
||||||
|
bi = NewLine(bi, INCMP, []string{"baz", "foo"}, nil, nil)
|
||||||
|
bi = NewLine(bi, 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
|
||||||
|
_, err = Run(bi, &st, &rs, context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
r := st.Where()
|
||||||
|
if r != "foo" {
|
||||||
|
t.Fatalf("expected where-sym 'foo', got '%v'", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunArgInvalid(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
rs := TestResource{}
|
||||||
|
|
||||||
|
_ = st.SetInput([]byte("foo"))
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
b := NewLine([]byte{}, INCMP, []string{"bar", "baz"}, nil, nil)
|
||||||
|
b = NewLine(b, CATCH, []string{"_catch"}, []byte{state.FLAG_INMATCH}, []uint8{1})
|
||||||
|
|
||||||
|
b, err = Run(b, &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)
|
||||||
|
}
|
||||||
|
r := st.Where()
|
||||||
|
if r != "_catch" {
|
||||||
|
t.Errorf("expected where-state _catch, got %v", r)
|
||||||
|
}
|
||||||
|
}
|
339
go/vm/vm.go
339
go/vm/vm.go
@ -2,215 +2,147 @@ package vm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"git.defalsify.org/festive/resource"
|
|
||||||
"git.defalsify.org/festive/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//type Runner func(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error)
|
|
||||||
|
|
||||||
func argFromBytes(input []byte) (string, []byte, error) {
|
func ParseLoad(b []byte) (string, uint32, []byte, error) {
|
||||||
if len(input) == 0 {
|
return parseSymLen(b, LOAD)
|
||||||
return "", input, fmt.Errorf("zero length input")
|
|
||||||
}
|
|
||||||
sz := input[0]
|
|
||||||
out := input[1:1+sz]
|
|
||||||
return string(out), input[1+sz:], nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run extracts individual op codes and arguments and executes them.
|
func ParseReload(b []byte) (string, []byte, error) {
|
||||||
//
|
return parseSym(b, RELOAD)
|
||||||
// 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) ([]byte, error) {
|
|
||||||
var err error
|
|
||||||
for len(instruction) > 0 {
|
|
||||||
log.Printf("instruction is now 0x%x", instruction)
|
|
||||||
op := binary.BigEndian.Uint16(instruction[:2])
|
|
||||||
if op > _MAX {
|
|
||||||
return instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX)
|
|
||||||
}
|
|
||||||
switch op {
|
|
||||||
case CATCH:
|
|
||||||
instruction, err = RunCatch(instruction[2:], st, rs, ctx)
|
|
||||||
case CROAK:
|
|
||||||
instruction, err = RunCroak(instruction[2:], st, rs, ctx)
|
|
||||||
case LOAD:
|
|
||||||
instruction, err = RunLoad(instruction[2:], st, rs, ctx)
|
|
||||||
case RELOAD:
|
|
||||||
instruction, err = RunReload(instruction[2:], st, rs, ctx)
|
|
||||||
case MAP:
|
|
||||||
instruction, err = RunMap(instruction[2:], st, rs, ctx)
|
|
||||||
case MOVE:
|
|
||||||
instruction, err = RunMove(instruction[2:], st, rs, ctx)
|
|
||||||
case BACK:
|
|
||||||
instruction, err = RunBack(instruction[2:], st, rs, ctx)
|
|
||||||
case INCMP:
|
|
||||||
instruction, err = RunIncmp(instruction[2:], st, rs, ctx)
|
|
||||||
case HALT:
|
|
||||||
return RunHalt(instruction[2:], st, rs, ctx)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("Unhandled state: %v", op)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return instruction, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return instruction, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunMap executes the MAP opcode
|
func ParseMap(b []byte) (string, []byte, error) {
|
||||||
func RunMap(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
return parseSym(b, MAP)
|
||||||
head, tail, err := instructionSplit(instruction)
|
}
|
||||||
|
|
||||||
|
func ParseMove(b []byte) (string, []byte, error) {
|
||||||
|
return parseSym(b, MOVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseHalt(b []byte) ([]byte, error) {
|
||||||
|
return parseNoArg(b, HALT)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseCatch(b []byte) (string, uint8, []byte, error) {
|
||||||
|
return parseSymSig(b, CATCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseCroak(b []byte) (string, uint8, []byte, error) {
|
||||||
|
return parseSymSig(b, CROAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseInCmp(b []byte) (string, string, []byte, error) {
|
||||||
|
return parseTwoSym(b, INCMP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNoArg(b []byte, op Opcode) ([]byte, error) {
|
||||||
|
return opCheck(b, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSym(b []byte, op Opcode) (string, []byte, error) {
|
||||||
|
b, err := opCheck(b, op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return instruction, err
|
return "", b, err
|
||||||
}
|
}
|
||||||
err = st.Map(head)
|
sym, tail, err := instructionSplit(b)
|
||||||
return tail, err
|
if err != nil {
|
||||||
|
return "", b, err
|
||||||
|
}
|
||||||
|
return sym, tail, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunMap executes the CATCH opcode
|
func parseTwoSym(b []byte, op Opcode) (string, string, []byte, error) {
|
||||||
func RunCatch(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
b, err := opCheck(b, op)
|
||||||
head, tail, err := instructionSplit(instruction)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return instruction, err
|
return "", "", b, err
|
||||||
}
|
}
|
||||||
bitFieldSize := tail[0]
|
symOne, tail, err := instructionSplit(b)
|
||||||
bitField := tail[1:1+bitFieldSize]
|
if err != nil {
|
||||||
tail = tail[1+bitFieldSize:]
|
return "", "", b, err
|
||||||
matchMode := tail[0] // matchmode 1 is match NOT set bit
|
}
|
||||||
|
symTwo, tail, err := instructionSplit(tail)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", tail, err
|
||||||
|
}
|
||||||
|
return symOne, symTwo, tail, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSymLen(b []byte, op Opcode) (string, uint32, []byte, error) {
|
||||||
|
b, err := opCheck(b, op)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, b, err
|
||||||
|
}
|
||||||
|
sym, tail, err := instructionSplit(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, b, err
|
||||||
|
}
|
||||||
|
sz, tail, err := intSplit(tail)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, b, err
|
||||||
|
}
|
||||||
|
return sym, sz, tail, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSymSig(b []byte, op Opcode) (string, uint8, []byte, error) {
|
||||||
|
b, err := opCheck(b, op)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, b, err
|
||||||
|
}
|
||||||
|
sym, tail, err := instructionSplit(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, b, err
|
||||||
|
}
|
||||||
|
if len(tail) == 0 {
|
||||||
|
return "", 0, b, fmt.Errorf("instruction too short")
|
||||||
|
}
|
||||||
|
n := tail[0]
|
||||||
tail = tail[1:]
|
tail = tail[1:]
|
||||||
match := false
|
return sym, n, tail, nil
|
||||||
if matchMode > 0 {
|
}
|
||||||
if !st.GetIndex(bitField) {
|
|
||||||
match = true
|
// NewLine creates a new instruction line for the VM.
|
||||||
|
func NewLine(instructionList []byte, instruction uint16, strargs []string, byteargs []byte, numargs []uint8) []byte {
|
||||||
|
if instructionList == nil {
|
||||||
|
instructionList = []byte{}
|
||||||
|
}
|
||||||
|
b := []byte{0x00, 0x00}
|
||||||
|
binary.BigEndian.PutUint16(b, instruction)
|
||||||
|
for _, arg := range strargs {
|
||||||
|
b = append(b, uint8(len(arg)))
|
||||||
|
b = append(b, []byte(arg)...)
|
||||||
|
}
|
||||||
|
if byteargs != nil {
|
||||||
|
b = append(b, uint8(len(byteargs)))
|
||||||
|
b = append(b, byteargs...)
|
||||||
|
}
|
||||||
|
if numargs != nil {
|
||||||
|
b = append(b, numargs...)
|
||||||
|
}
|
||||||
|
return append(instructionList, b...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func intSplit(b []byte) (uint32, []byte, error) {
|
||||||
|
l := uint8(b[0])
|
||||||
|
sz := uint32(l)
|
||||||
|
b = b[1:]
|
||||||
|
if l > 0 {
|
||||||
|
r := []byte{0, 0, 0, 0}
|
||||||
|
c := 0
|
||||||
|
ll := 4 - l
|
||||||
|
var i uint8
|
||||||
|
for i = 0; i < 4; i++ {
|
||||||
|
if i >= ll {
|
||||||
|
r[i] = b[c]
|
||||||
|
c += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if st.GetIndex(bitField) {
|
sz = binary.BigEndian.Uint32(r)
|
||||||
match = true
|
b = b[l:]
|
||||||
}
|
}
|
||||||
|
return sz, b, nil
|
||||||
if match {
|
|
||||||
log.Printf("catch at flag %v, moving to %v", bitField, head)
|
|
||||||
st.Down(head)
|
|
||||||
tail = []byte{}
|
|
||||||
}
|
|
||||||
return tail, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunMap executes the CROAK opcode
|
|
||||||
func RunCroak(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
|
||||||
head, tail, err := instructionSplit(instruction)
|
|
||||||
if err != nil {
|
|
||||||
return instruction, err
|
|
||||||
}
|
|
||||||
_ = head
|
|
||||||
_ = tail
|
|
||||||
st.Reset()
|
|
||||||
return []byte{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunLoad executes the LOAD opcode
|
|
||||||
func RunLoad(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
|
||||||
head, tail, err := instructionSplit(instruction)
|
|
||||||
if err != nil {
|
|
||||||
return instruction, err
|
|
||||||
}
|
|
||||||
if !st.Check(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 tail, err
|
|
||||||
}
|
|
||||||
err = st.Add(head, r, sz)
|
|
||||||
return tail, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunLoad executes the RELOAD opcode
|
|
||||||
func RunReload(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
|
||||||
head, tail, err := instructionSplit(instruction)
|
|
||||||
if err != nil {
|
|
||||||
return instruction, err
|
|
||||||
}
|
|
||||||
r, err := refresh(head, rs, ctx)
|
|
||||||
if err != nil {
|
|
||||||
return tail, err
|
|
||||||
}
|
|
||||||
st.Update(head, r)
|
|
||||||
return tail, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunLoad executes the MOVE opcode
|
|
||||||
func RunMove(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
|
||||||
head, tail, err := instructionSplit(instruction)
|
|
||||||
if err != nil {
|
|
||||||
return instruction, err
|
|
||||||
}
|
|
||||||
st.Down(head)
|
|
||||||
return tail, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunLoad executes the BACK opcode
|
|
||||||
func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
|
||||||
st.Up()
|
|
||||||
return instruction, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunIncmp executes the INCMP opcode
|
|
||||||
func RunIncmp(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
|
||||||
head, tail, err := instructionSplit(instruction)
|
|
||||||
if err != nil {
|
|
||||||
return instruction, err
|
|
||||||
}
|
|
||||||
sym, tail, err := instructionSplit(tail)
|
|
||||||
if err != nil {
|
|
||||||
return instruction, err
|
|
||||||
}
|
|
||||||
v, err := st.GetFlag(state.FLAG_INMATCH)
|
|
||||||
if err != nil {
|
|
||||||
return tail, err
|
|
||||||
}
|
|
||||||
if v {
|
|
||||||
return tail, nil
|
|
||||||
}
|
|
||||||
input, err := st.GetInput()
|
|
||||||
if err != nil {
|
|
||||||
return tail, err
|
|
||||||
}
|
|
||||||
log.Printf("checking input %v %v", input, head)
|
|
||||||
if head == string(input) {
|
|
||||||
log.Printf("input match for '%s'", input)
|
|
||||||
_, err = st.SetFlag(state.FLAG_INMATCH)
|
|
||||||
st.Down(sym)
|
|
||||||
}
|
|
||||||
return tail, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunHalt executes the HALT opcode
|
|
||||||
func RunHalt(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
|
||||||
log.Printf("found HALT, stopping")
|
|
||||||
_, err := st.ResetFlag(state.FLAG_INMATCH)
|
|
||||||
return instruction, err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// retrieve data for key
|
|
||||||
func refresh(key string, rs resource.Resource, ctx context.Context) (string, error) {
|
|
||||||
fn, err := rs.FuncFor(key)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if fn == nil {
|
|
||||||
return "", fmt.Errorf("no retrieve function for external symbol %v", key)
|
|
||||||
}
|
|
||||||
return fn(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// split instruction into symbol and arguments
|
// split instruction into symbol and arguments
|
||||||
@ -229,3 +161,26 @@ func instructionSplit(b []byte) (string, []byte, error) {
|
|||||||
r := string(b[1:1+sz])
|
r := string(b[1:1+sz])
|
||||||
return r, b[1+sz:], nil
|
return r, b[1+sz:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func opCheck(b []byte, opIn Opcode) ([]byte, error) {
|
||||||
|
op, b, err := opSplit(b)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
if op != opIn {
|
||||||
|
return b, fmt.Errorf("not a %v instruction", op)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func opSplit(b []byte) (Opcode, []byte, error) {
|
||||||
|
l := len(b)
|
||||||
|
if l < 2 {
|
||||||
|
return 0, b, fmt.Errorf("input size %v too short for opcode", l)
|
||||||
|
}
|
||||||
|
op := binary.BigEndian.Uint16(b)
|
||||||
|
if op > _MAX {
|
||||||
|
return 0, b, fmt.Errorf("invalid opcode %v", op)
|
||||||
|
}
|
||||||
|
return Opcode(op), b[2:], nil
|
||||||
|
}
|
||||||
|
360
go/vm/vm_test.go
360
go/vm/vm_test.go
@ -1,284 +1,108 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.defalsify.org/festive/resource"
|
|
||||||
"git.defalsify.org/festive/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var dynVal = "three"
|
func TestParseNoArg(t *testing.T) {
|
||||||
|
b := NewLine(nil, HALT, nil, nil, nil)
|
||||||
type TestResource struct {
|
b, err := ParseHalt(b)
|
||||||
state *state.State
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOne(ctx context.Context) (string, error) {
|
|
||||||
return "one", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTwo(ctx context.Context) (string, error) {
|
|
||||||
return "two", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDyn(ctx context.Context) (string, error) {
|
|
||||||
return dynVal, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestStatefulResolver struct {
|
|
||||||
state *state.State
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestResource) GetTemplate(sym string) (string, error) {
|
|
||||||
switch sym {
|
|
||||||
case "foo":
|
|
||||||
return "inky pinky blinky clyde", nil
|
|
||||||
case "bar":
|
|
||||||
return "inky pinky {{.one}} blinky {{.two}} clyde", nil
|
|
||||||
case "baz":
|
|
||||||
return "inky pinky {{.baz}} blinky clyde", nil
|
|
||||||
case "three":
|
|
||||||
return "{{.one}} inky pinky {{.three}} blinky clyde {{.two}}", nil
|
|
||||||
case "_catch":
|
|
||||||
return "aiee", nil
|
|
||||||
}
|
|
||||||
panic(fmt.Sprintf("unknown symbol %s", sym))
|
|
||||||
return "", fmt.Errorf("unknown symbol %s", sym)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestResource) RenderTemplate(sym string, values map[string]string) (string, error) {
|
|
||||||
return resource.DefaultRenderTemplate(r, sym, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestResource) FuncFor(sym string) (resource.EntryFunc, error) {
|
|
||||||
switch sym {
|
|
||||||
case "one":
|
|
||||||
return getOne, nil
|
|
||||||
case "two":
|
|
||||||
return getTwo, nil
|
|
||||||
case "dyn":
|
|
||||||
return getDyn, nil
|
|
||||||
case "arg":
|
|
||||||
return r.getInput, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("invalid function: '%s'", sym)
|
|
||||||
}
|
|
||||||
|
|
||||||
func(r *TestResource) getInput(ctx context.Context) (string, error) {
|
|
||||||
v, err := r.state.GetInput()
|
|
||||||
return string(v), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func(r *TestResource) GetCode(sym string) ([]byte, error) {
|
|
||||||
return []byte{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRun(t *testing.T) {
|
|
||||||
st := state.NewState(5)
|
|
||||||
rs := TestResource{}
|
|
||||||
b := []byte{0x00, MOVE, 0x03}
|
|
||||||
b = append(b, []byte("foo")...)
|
|
||||||
_, err := Run(b, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error on valid opcode: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b = []byte{0x01, 0x02}
|
|
||||||
_, err = Run(b, &st, &rs, context.TODO())
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("no error on invalid opcode")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunLoadRender(t *testing.T) {
|
|
||||||
st := state.NewState(5)
|
|
||||||
st.Down("barbarbar")
|
|
||||||
rs := TestResource{}
|
|
||||||
sym := "one"
|
|
||||||
ins := append([]byte{uint8(len(sym))}, []byte(sym)...)
|
|
||||||
ins = append(ins, 0x0a)
|
|
||||||
var err error
|
|
||||||
_, err = RunLoad(ins, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
m, err := st.Get()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
r, err := rs.RenderTemplate("foo", m)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
expect := "inky pinky blinky clyde"
|
|
||||||
if r != expect {
|
|
||||||
t.Errorf("Expected %v, got %v", []byte(expect), []byte(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err = rs.RenderTemplate("bar", m)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("expected error for render of bar: %v" ,err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sym = "two"
|
|
||||||
ins = append([]byte{uint8(len(sym))}, []byte(sym)...)
|
|
||||||
ins = append(ins, 0)
|
|
||||||
_, err = RunLoad(ins, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
m, err = st.Get()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
r, err = rs.RenderTemplate("bar", m)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
expect = "inky pinky one blinky two clyde"
|
|
||||||
if r != expect {
|
|
||||||
t.Errorf("Expected %v, got %v", expect, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunMultiple(t *testing.T) {
|
|
||||||
st := state.NewState(5)
|
|
||||||
rs := TestResource{}
|
|
||||||
b := []byte{}
|
|
||||||
b = NewLine(b, LOAD, []string{"one"}, nil, []uint8{0})
|
|
||||||
b = NewLine(b, LOAD, []string{"two"}, nil, []uint8{42})
|
|
||||||
_, err := Run(b, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunReload(t *testing.T) {
|
|
||||||
st := state.NewState(5)
|
|
||||||
rs := TestResource{}
|
|
||||||
b := []byte{}
|
|
||||||
b = NewLine(b, LOAD, []string{"dyn"}, nil, []uint8{0})
|
|
||||||
b = NewLine(b, MAP, []string{"dyn"}, nil, nil)
|
|
||||||
_, err := Run(b, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
r, err := st.Val("dyn")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if r != "three" {
|
|
||||||
t.Errorf("expected result 'three', got %v", r)
|
|
||||||
}
|
|
||||||
dynVal = "baz"
|
|
||||||
b = []byte{}
|
|
||||||
b = NewLine(b, RELOAD, []string{"dyn"}, nil, nil)
|
|
||||||
_, err = Run(b, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
r, err = st.Val("dyn")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
log.Printf("dun now %s", r)
|
|
||||||
if r != "baz" {
|
|
||||||
t.Errorf("expected result 'baz', got %v", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHalt(t *testing.T) {
|
|
||||||
st := state.NewState(5)
|
|
||||||
rs := TestResource{}
|
|
||||||
b := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0})
|
|
||||||
b = NewLine(b, HALT, nil, nil, nil)
|
|
||||||
b = NewLine(b, MOVE, []string{"foo"}, nil, nil)
|
|
||||||
var err error
|
|
||||||
b, err = Run(b, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
r := st.Where()
|
|
||||||
if r == "foo" {
|
|
||||||
t.Fatalf("Expected where-symbol not to be 'foo'")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(b[:2], []byte{0x00, MOVE}) {
|
|
||||||
t.Fatalf("Expected MOVE instruction, found '%v'", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunArg(t *testing.T) {
|
|
||||||
st := state.NewState(5)
|
|
||||||
rs := TestResource{}
|
|
||||||
|
|
||||||
input := []byte("bar")
|
|
||||||
_ = st.SetInput(input)
|
|
||||||
|
|
||||||
bi := NewLine([]byte{}, INCMP, []string{"bar", "baz"}, nil, nil)
|
|
||||||
b, err := Run(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)
|
|
||||||
}
|
|
||||||
r := st.Where()
|
|
||||||
if r != "baz" {
|
|
||||||
t.Errorf("expected where-state baz, got %v", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunInputHandler(t *testing.T) {
|
|
||||||
st := state.NewState(5)
|
|
||||||
rs := TestResource{}
|
|
||||||
|
|
||||||
_ = st.SetInput([]byte("baz"))
|
|
||||||
|
|
||||||
bi := NewLine([]byte{}, INCMP, []string{"bar", "aiee"}, nil, nil)
|
|
||||||
bi = NewLine(bi, INCMP, []string{"baz", "foo"}, nil, nil)
|
|
||||||
bi = NewLine(bi, 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
|
|
||||||
_, err = Run(bi, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
r := st.Where()
|
|
||||||
if r != "foo" {
|
|
||||||
t.Fatalf("expected where-sym 'foo', got '%v'", r)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunArgInvalid(t *testing.T) {
|
func TestParseSym(t *testing.T) {
|
||||||
st := state.NewState(5)
|
b := NewLine(nil, MAP, []string{"baz"}, nil, nil)
|
||||||
rs := TestResource{}
|
sym, b, err := ParseMap(b)
|
||||||
|
|
||||||
_ = st.SetInput([]byte("foo"))
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
b := NewLine([]byte{}, INCMP, []string{"bar", "baz"}, nil, nil)
|
|
||||||
b = NewLine(b, CATCH, []string{"_catch"}, []byte{state.FLAG_INMATCH}, []uint8{1})
|
|
||||||
|
|
||||||
b, err = Run(b, &st, &rs, context.TODO())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
l := len(b)
|
if sym != "baz" {
|
||||||
if l != 0 {
|
t.Fatalf("expected sym baz, got %v", sym)
|
||||||
t.Errorf("expected empty remainder, got length %v: %v", l, b)
|
|
||||||
}
|
}
|
||||||
r := st.Where()
|
|
||||||
if r != "_catch" {
|
b = NewLine(nil, RELOAD, []string{"xyzzy"}, nil, nil)
|
||||||
t.Errorf("expected where-state _catch, got %v", r)
|
sym, b, err = ParseReload(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sym != "xyzzy" {
|
||||||
|
t.Fatalf("expected sym xyzzy, got %v", sym)
|
||||||
|
}
|
||||||
|
|
||||||
|
b = NewLine(nil, MOVE, []string{"plugh"}, nil, nil)
|
||||||
|
sym, b, err = ParseMove(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sym != "plugh" {
|
||||||
|
t.Fatalf("expected sym plugh, got %v", sym)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTwoSym(t *testing.T) {
|
||||||
|
b := NewLine(nil, INCMP, []string{"foo", "bar"}, nil, nil)
|
||||||
|
one, two, b, err := ParseInCmp(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if one != "foo" {
|
||||||
|
t.Fatalf("expected symone foo, got %v", one)
|
||||||
|
}
|
||||||
|
if two != "bar" {
|
||||||
|
t.Fatalf("expected symtwo bar, got %v", two)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSymSig(t *testing.T) {
|
||||||
|
b := NewLine(nil, CATCH, []string{"baz"}, nil, []uint8{0x0d})
|
||||||
|
sym, n, b, err := ParseCatch(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sym != "baz" {
|
||||||
|
t.Fatalf("expected sym baz, got %v", sym)
|
||||||
|
}
|
||||||
|
if n != 13 {
|
||||||
|
t.Fatalf("expected n 13, got %v", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSymAndLen(t *testing.T) {
|
||||||
|
b := NewLine(nil, LOAD, []string{"foo"}, []byte{0x2a}, nil)
|
||||||
|
sym, n, b, err := ParseLoad(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sym != "foo" {
|
||||||
|
t.Fatalf("expected sym foo, got %v", sym)
|
||||||
|
}
|
||||||
|
if n != 42 {
|
||||||
|
t.Fatalf("expected n 42, got %v", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
b = NewLine(nil, LOAD, []string{"bar"}, []byte{0x02, 0x9a}, nil)
|
||||||
|
sym, n, b, err = ParseLoad(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sym != "bar" {
|
||||||
|
t.Fatalf("expected sym foo, got %v", sym)
|
||||||
|
}
|
||||||
|
if n != 666 {
|
||||||
|
t.Fatalf("expected n 666, got %v", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
b = NewLine(nil, LOAD, []string{"baz"}, []byte{0x0}, nil)
|
||||||
|
sym, n, b, err = ParseLoad(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if sym != "baz" {
|
||||||
|
t.Fatalf("expected sym foo, got %v", sym)
|
||||||
|
}
|
||||||
|
if n != 0 {
|
||||||
|
t.Fatalf("expected n 666, got %v", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user