Implement INCMP and check in nav match flag
This commit is contained in:
parent
4181fe0576
commit
b78e28622a
@ -1,6 +1,6 @@
|
||||
# festive: A Constrained Size Output Virtual Machine
|
||||
|
||||
An attempt at defining a small VM to create a stack machine for size-constrained clients and servers.
|
||||
An attempt at defining a small VM to handle menu interaction for size-constrained clients and servers.
|
||||
|
||||
Original motivation was to create a simple templating renderer for USSD clients, combined with an agnostic data-retrieval reference that may conceal any level of complexity.
|
||||
|
||||
@ -16,7 +16,7 @@ The VM defines the following opcode symbols:
|
||||
* `RELOAD <symbol>` - Execute a code symbol already loaded by `LOAD` and cache the data, constrained to the previously given `size` for the same symbol.
|
||||
* `MAP <symbol>` - Expose a code symbol previously loaded by `LOAD` to the rendering client. Roughly corresponds to the `global` directive in Python.
|
||||
* `MOVE <symbol>` - Create a new execution frame, invalidating all previous `MAP` calls. More detailed: After a `MOVE` call, a `BACK` call will return to the same execution frame, with the same symbols available, but all `MAP` calls will have to be repeated.
|
||||
* 'HALT' - Stop execution. The remaining bytecode (typicaly, the routing code for the node) is returned to the invoking function.
|
||||
* `HALT` - Stop execution. The remaining bytecode (typically, the routing code for the node) is returned to the invoking function.
|
||||
|
||||
|
||||
### External code
|
||||
@ -48,6 +48,7 @@ Signal may be set when executing of external code symbols, and may be used as a
|
||||
|
||||
The signal flag arguments should only set a single flag to be tested. If more than one flag is set, the first flag matched will be used as the trigger.
|
||||
|
||||
First 8 flags are reserved and used for internal VM operations.
|
||||
|
||||
|
||||
## Rendering
|
||||
|
@ -61,11 +61,10 @@ func(en *Engine) Init(ctx context.Context) error {
|
||||
// - no current bytecode is available
|
||||
// - input processing against bytcode failed
|
||||
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)
|
||||
err := en.st.SetInput(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
input = append([]byte{l}, input...)
|
||||
code, err := en.st.GetCode()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -73,7 +72,11 @@ func (en *Engine) Exec(input []byte, ctx context.Context) error {
|
||||
if len(code) == 0 {
|
||||
return fmt.Errorf("no code to execute")
|
||||
}
|
||||
code, err = vm.Apply(input, code, en.st, en.rs, ctx)
|
||||
err = en.st.SetInput(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
code, err = vm.Run(code, en.st, en.rs, ctx)
|
||||
en.st.SetCode(code)
|
||||
return err
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path"
|
||||
"text/template"
|
||||
"testing"
|
||||
@ -63,7 +62,6 @@ 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
|
||||
}
|
||||
|
||||
@ -87,7 +85,7 @@ func TestEngineInit(t *testing.T) {
|
||||
if !bytes.Equal(b, []byte("hello world")) {
|
||||
t.Fatalf("expected result 'hello world', got %v", b)
|
||||
}
|
||||
input := []byte("foo")
|
||||
input := []byte("bar")
|
||||
err = en.Exec(input, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
5
go/state/flag.go
Normal file
5
go/state/flag.go
Normal file
@ -0,0 +1,5 @@
|
||||
package state
|
||||
|
||||
const (
|
||||
FLAG_INMATCH = 1
|
||||
)
|
@ -19,13 +19,16 @@ import (
|
||||
//
|
||||
// Symbol keys do not count towards cache size limitations.
|
||||
//
|
||||
// 8 first flags are reserved.
|
||||
//
|
||||
// TODO factor out cache
|
||||
type State struct {
|
||||
Flags []byte // Error state
|
||||
CacheSize uint32 // Total allowed cumulative size of values in cache
|
||||
CacheUseSize uint32 // Currently used bytes by all values in cache
|
||||
CacheSize uint32 // Total allowed cumulative size of values (not code) in cache
|
||||
CacheUseSize uint32 // Currently used bytes by all values (not code) in cache
|
||||
Cache []map[string]string // All loaded cache items
|
||||
CacheMap map[string]string // Mapped
|
||||
input []byte // Last input
|
||||
code []byte // Pending bytecode to execute
|
||||
execPath []string // Command symbols stack
|
||||
arg *string // Optional argument. Nil if not set.
|
||||
@ -54,14 +57,14 @@ func getFlag(bitIndex uint32, bitField []byte) bool {
|
||||
return (b & (1 << localBitIndex)) > 0
|
||||
}
|
||||
|
||||
// NewState creates a new State object with bitSize number of error condition states.
|
||||
// NewState creates a new State object with bitSize number of error condition states in ADDITION to the 8 builtin flags.
|
||||
func NewState(bitSize uint32) State {
|
||||
st := State{
|
||||
CacheSize: 0,
|
||||
CacheUseSize: 0,
|
||||
bitSize: bitSize,
|
||||
bitSize: bitSize + 8,
|
||||
}
|
||||
byteSize := toByteSize(bitSize)
|
||||
byteSize := toByteSize(bitSize + 8)
|
||||
if byteSize > 0 {
|
||||
st.Flags = make([]byte, byteSize)
|
||||
} else {
|
||||
@ -181,26 +184,26 @@ func(st State) Where() string {
|
||||
return st.execPath[l-1]
|
||||
}
|
||||
|
||||
// PutArg adds the optional argument.
|
||||
//// PutArg adds the optional argument.
|
||||
////
|
||||
//// Fails if arg already set.
|
||||
//func(st *State) PutArg(input string) error {
|
||||
// st.arg = &input
|
||||
// if st.arg != nil {
|
||||
// return fmt.Errorf("arg already set to %s", *st.arg)
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
// Fails if arg already set.
|
||||
func(st *State) PutArg(input string) error {
|
||||
st.arg = &input
|
||||
if st.arg != nil {
|
||||
return fmt.Errorf("arg already set to %s", *st.arg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopArg retrieves the optional argument. Will be freed upon retrieval.
|
||||
//
|
||||
// Fails if arg not set (or already freed).
|
||||
func(st *State) PopArg() (string, error) {
|
||||
if st.arg == nil {
|
||||
return "", fmt.Errorf("arg is not set")
|
||||
}
|
||||
return *st.arg, nil
|
||||
}
|
||||
//// PopArg retrieves the optional argument. Will be freed upon retrieval.
|
||||
////
|
||||
//// Fails if arg not set (or already freed).
|
||||
//func(st *State) PopArg() (string, error) {
|
||||
// if st.arg == nil {
|
||||
// return "", fmt.Errorf("arg is not set")
|
||||
// }
|
||||
// return *st.arg, nil
|
||||
//}
|
||||
|
||||
// Down adds the given symbol to the command stack.
|
||||
//
|
||||
@ -391,12 +394,31 @@ func(st *State) SetCode(b []byte) {
|
||||
st.code = b
|
||||
}
|
||||
|
||||
// Get the remaning cached bytecode
|
||||
func(st *State) GetCode() ([]byte, error) {
|
||||
b := st.code
|
||||
st.code = []byte{}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// GetInput gets the most recent client input.
|
||||
func(st *State) GetInput() ([]byte, error) {
|
||||
if st.input == nil {
|
||||
return nil, fmt.Errorf("no input has been set")
|
||||
}
|
||||
return st.input, nil
|
||||
}
|
||||
|
||||
// SetInput is used to record the latest client input.
|
||||
func(st *State) SetInput(input []byte) error {
|
||||
l := len(input)
|
||||
if l > 255 {
|
||||
return fmt.Errorf("input size %v too large (limit %v)", l, 255)
|
||||
}
|
||||
st.input = input
|
||||
return 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 {
|
||||
|
@ -8,21 +8,21 @@ import (
|
||||
// Check creation
|
||||
func TestNewState(t *testing.T) {
|
||||
st := NewState(5)
|
||||
if len(st.Flags) != 1 {
|
||||
if len(st.Flags) != 2 {
|
||||
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
||||
}
|
||||
st = NewState(8)
|
||||
if len(st.Flags) != 1 {
|
||||
if len(st.Flags) != 2 {
|
||||
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
||||
}
|
||||
st = NewState(17)
|
||||
if len(st.Flags) != 3 {
|
||||
if len(st.Flags) != 4 {
|
||||
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateFlags(t *testing.T) {
|
||||
st := NewState(17)
|
||||
st := NewState(9)
|
||||
v, err := st.GetFlag(2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
1
go/testdata/bar
vendored
Normal file
1
go/testdata/bar
vendored
Normal file
@ -0,0 +1 @@
|
||||
i am in bar
|
BIN
go/testdata/bar.bin
vendored
Normal file
BIN
go/testdata/bar.bin
vendored
Normal file
Binary file not shown.
BIN
go/testdata/root.bin
vendored
Normal file
BIN
go/testdata/root.bin
vendored
Normal file
Binary file not shown.
@ -5,6 +5,7 @@ import (
|
||||
)
|
||||
const VERSION = 0
|
||||
|
||||
// Opcodes
|
||||
const (
|
||||
BACK = 0
|
||||
CATCH = 1
|
||||
@ -14,22 +15,25 @@ const (
|
||||
MAP = 5
|
||||
MOVE = 6
|
||||
HALT = 7
|
||||
_MAX = 7
|
||||
INCMP = 8
|
||||
//IN = 9
|
||||
_MAX = 8
|
||||
)
|
||||
|
||||
func NewLine(instructionList []byte, instruction uint16, args []string, post []byte, szPost []uint8) []byte {
|
||||
// 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 args {
|
||||
for _, arg := range strargs {
|
||||
b = append(b, uint8(len(arg)))
|
||||
b = append(b, []byte(arg)...)
|
||||
}
|
||||
if post != nil {
|
||||
b = append(b, uint8(len(post)))
|
||||
b = append(b, post...)
|
||||
if byteargs != nil {
|
||||
b = append(b, uint8(len(byteargs)))
|
||||
b = append(b, byteargs...)
|
||||
}
|
||||
if szPost != nil {
|
||||
b = append(b, szPost...)
|
||||
if numargs != nil {
|
||||
b = append(b, numargs...)
|
||||
}
|
||||
return append(instructionList, b...)
|
||||
}
|
||||
|
99
go/vm/vm.go
99
go/vm/vm.go
@ -7,7 +7,6 @@ import (
|
||||
"log"
|
||||
|
||||
"git.defalsify.org/festive/resource"
|
||||
"git.defalsify.org/festive/router"
|
||||
"git.defalsify.org/festive/state"
|
||||
)
|
||||
|
||||
@ -22,52 +21,6 @@ func argFromBytes(input []byte) (string, []byte, error) {
|
||||
return string(out), input[1+sz:], nil
|
||||
}
|
||||
|
||||
// Apply applies input to router bytecode to resolve the node symbol to execute.
|
||||
//
|
||||
// The execution byte code is initialized with the appropriate MOVE
|
||||
//
|
||||
// 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) ([]byte, error) {
|
||||
var err error
|
||||
|
||||
log.Printf("running input %v against instruction %v", input, instruction)
|
||||
arg, input, err := argFromBytes(input)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
|
||||
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 {
|
||||
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...)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// Each step may update the state.
|
||||
@ -96,9 +49,10 @@ func Run(instruction []byte, st *state.State, rs resource.Resource, ctx context.
|
||||
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:
|
||||
log.Printf("found HALT, stopping")
|
||||
return instruction[2:], err
|
||||
return RunHalt(instruction[2:], st, rs, ctx)
|
||||
default:
|
||||
err = fmt.Errorf("Unhandled state: %v", op)
|
||||
}
|
||||
@ -128,7 +82,18 @@ func RunCatch(instruction []byte, st *state.State, rs resource.Resource, ctx con
|
||||
bitFieldSize := tail[0]
|
||||
bitField := tail[1:1+bitFieldSize]
|
||||
tail = tail[1+bitFieldSize:]
|
||||
if st.GetIndex(bitField) {
|
||||
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{}
|
||||
@ -198,6 +163,40 @@ func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx cont
|
||||
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
|
||||
}
|
||||
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(head)
|
||||
}
|
||||
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)
|
||||
|
283
go/vm/vm_test.go
283
go/vm/vm_test.go
@ -9,7 +9,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
"git.defalsify.org/festive/resource"
|
||||
"git.defalsify.org/festive/router"
|
||||
// "git.defalsify.org/festive/router"
|
||||
"git.defalsify.org/festive/state"
|
||||
)
|
||||
|
||||
@ -35,11 +35,6 @@ type TestStatefulResolver struct {
|
||||
state *state.State
|
||||
}
|
||||
|
||||
|
||||
func (r *TestResource) getEachArg(ctx context.Context) (string, error) {
|
||||
return r.state.PopArg()
|
||||
}
|
||||
|
||||
func (r *TestResource) GetTemplate(sym string) (string, error) {
|
||||
switch sym {
|
||||
case "foo":
|
||||
@ -84,12 +79,17 @@ func (r *TestResource) FuncFor(sym string) (resource.EntryFunc, error) {
|
||||
case "dyn":
|
||||
return getDyn, nil
|
||||
case "arg":
|
||||
return r.getEachArg, nil
|
||||
return r.getInput, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid function: '%s'", sym)
|
||||
}
|
||||
|
||||
func (r *TestResource) GetCode(sym string) ([]byte, error) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -208,202 +208,6 @@ 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()...)
|
||||
var err error
|
||||
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)
|
||||
}
|
||||
r := st.Where()
|
||||
if r != "xyzzy" {
|
||||
t.Errorf("expected where-state baz, got %v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunArgInvalid(t *testing.T) {
|
||||
st := state.NewState(5)
|
||||
rt := router.NewRouter()
|
||||
rt.Add("foo", "bar")
|
||||
rt.Add("baz", "xyzzy")
|
||||
b := []byte{0x03}
|
||||
b = append(b, []byte("bar")...)
|
||||
//b = append(b, rt.ToBytes()...)
|
||||
var err error
|
||||
b, err = Apply(b, rt.ToBytes(), &st, nil, 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunArgInstructions(t *testing.T) {
|
||||
t.Skip("pending fix for separating router code from executing code")
|
||||
st := state.NewState(5)
|
||||
rs := TestResource{}
|
||||
|
||||
rt := router.NewRouter()
|
||||
rt.Add("foo", "bar")
|
||||
b := []byte{0x03}
|
||||
b = append(b, []byte("foo")...)
|
||||
|
||||
bi := NewLine(rt.ToBytes(), 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)
|
||||
}
|
||||
r, err := rs.RenderTemplate(loc, m)
|
||||
if err != nil {
|
||||
t.Fatal(err) //f("expected error to generate template")
|
||||
}
|
||||
if r != "aiee" {
|
||||
t.Fatalf("expected result 'aiee', got '%v'", r)
|
||||
}
|
||||
_, 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) {
|
||||
t.Skip("pending fix for separating router code from executing code")
|
||||
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)
|
||||
rs := TestResource{}
|
||||
rt := router.NewRouter()
|
||||
rt.Add("foo", "bar")
|
||||
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)
|
||||
_, err := Run(b, &st, &rs, context.TODO())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
r := st.Where()
|
||||
if r != "foo" {
|
||||
t.Errorf("expected where-symbol 'foo', got %v", r)
|
||||
}
|
||||
|
||||
st.SetFlag(2)
|
||||
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)
|
||||
_, err = Run(b, &st, &rs, context.TODO())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
r = st.Where()
|
||||
if r != "bar" {
|
||||
t.Errorf("expected where-symbol 'bar', got %v", r)
|
||||
}
|
||||
|
||||
st.Up()
|
||||
r = st.Where()
|
||||
if r != "foo" {
|
||||
t.Errorf("expected where-symbol 'foo', got %v", r)
|
||||
}
|
||||
err = st.Map("one")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestHalt(t *testing.T) {
|
||||
st := state.NewState(5)
|
||||
rs := TestResource{}
|
||||
@ -423,3 +227,74 @@ func TestHalt(t *testing.T) {
|
||||
t.Fatalf("Expected MOVE instruction, found '%v'", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunArg(t *testing.T) {
|
||||
st := state.NewState(5)
|
||||
rs := TestResource{}
|
||||
|
||||
input := []byte("baz")
|
||||
_ = st.SetInput(input)
|
||||
|
||||
bi := NewLine([]byte{}, INCMP, []string{"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("foo"))
|
||||
|
||||
bi := NewLine([]byte{}, INCMP, []string{"bar"}, nil, nil)
|
||||
bi = NewLine(bi, INCMP, []string{"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"}, 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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user