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
|
# 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.
|
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.
|
* `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.
|
* `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.
|
* `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
|
### 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.
|
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
|
## Rendering
|
||||||
|
@ -61,11 +61,10 @@ func(en *Engine) Init(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) error {
|
func (en *Engine) Exec(input []byte, ctx context.Context) error {
|
||||||
l := uint8(len(input))
|
err := en.st.SetInput(input)
|
||||||
if l > 255 {
|
if err != nil {
|
||||||
return fmt.Errorf("input too long (%v)", l)
|
return err
|
||||||
}
|
}
|
||||||
input = append([]byte{l}, input...)
|
|
||||||
code, err := en.st.GetCode()
|
code, err := en.st.GetCode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -73,7 +72,11 @@ func (en *Engine) Exec(input []byte, ctx context.Context) error {
|
|||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
return fmt.Errorf("no code to execute")
|
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)
|
en.st.SetCode(code)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"path"
|
"path"
|
||||||
"text/template"
|
"text/template"
|
||||||
"testing"
|
"testing"
|
||||||
@ -63,7 +62,6 @@ func(fs FsWrapper) GetCode(sym string) ([]byte, error) {
|
|||||||
sym += ".bin"
|
sym += ".bin"
|
||||||
fp := path.Join(fs.Path, sym)
|
fp := path.Join(fs.Path, sym)
|
||||||
r, err := ioutil.ReadFile(fp)
|
r, err := ioutil.ReadFile(fp)
|
||||||
log.Printf("getcode for %v %v", fp, r)
|
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +85,7 @@ func TestEngineInit(t *testing.T) {
|
|||||||
if !bytes.Equal(b, []byte("hello world")) {
|
if !bytes.Equal(b, []byte("hello world")) {
|
||||||
t.Fatalf("expected result 'hello world', got %v", b)
|
t.Fatalf("expected result 'hello world', got %v", b)
|
||||||
}
|
}
|
||||||
input := []byte("foo")
|
input := []byte("bar")
|
||||||
err = en.Exec(input, ctx)
|
err = en.Exec(input, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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.
|
// Symbol keys do not count towards cache size limitations.
|
||||||
//
|
//
|
||||||
|
// 8 first flags are reserved.
|
||||||
|
//
|
||||||
// TODO factor out cache
|
// TODO factor out cache
|
||||||
type State struct {
|
type State struct {
|
||||||
Flags []byte // Error state
|
Flags []byte // Error state
|
||||||
CacheSize uint32 // Total allowed cumulative size of values in cache
|
CacheSize uint32 // Total allowed cumulative size of values (not code) in cache
|
||||||
CacheUseSize uint32 // Currently used bytes by all values in cache
|
CacheUseSize uint32 // Currently used bytes by all values (not code) in cache
|
||||||
Cache []map[string]string // All loaded cache items
|
Cache []map[string]string // All loaded cache items
|
||||||
CacheMap map[string]string // Mapped
|
CacheMap map[string]string // Mapped
|
||||||
|
input []byte // Last input
|
||||||
code []byte // Pending bytecode to execute
|
code []byte // Pending bytecode to execute
|
||||||
execPath []string // Command symbols stack
|
execPath []string // Command symbols stack
|
||||||
arg *string // Optional argument. Nil if not set.
|
arg *string // Optional argument. Nil if not set.
|
||||||
@ -54,14 +57,14 @@ func getFlag(bitIndex uint32, bitField []byte) bool {
|
|||||||
return (b & (1 << localBitIndex)) > 0
|
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 {
|
func NewState(bitSize uint32) State {
|
||||||
st := State{
|
st := State{
|
||||||
CacheSize: 0,
|
CacheSize: 0,
|
||||||
CacheUseSize: 0,
|
CacheUseSize: 0,
|
||||||
bitSize: bitSize,
|
bitSize: bitSize + 8,
|
||||||
}
|
}
|
||||||
byteSize := toByteSize(bitSize)
|
byteSize := toByteSize(bitSize + 8)
|
||||||
if byteSize > 0 {
|
if byteSize > 0 {
|
||||||
st.Flags = make([]byte, byteSize)
|
st.Flags = make([]byte, byteSize)
|
||||||
} else {
|
} else {
|
||||||
@ -181,26 +184,26 @@ func(st State) Where() string {
|
|||||||
return st.execPath[l-1]
|
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.
|
//// PopArg retrieves the optional argument. Will be freed upon retrieval.
|
||||||
func(st *State) PutArg(input string) error {
|
////
|
||||||
st.arg = &input
|
//// Fails if arg not set (or already freed).
|
||||||
if st.arg != nil {
|
//func(st *State) PopArg() (string, error) {
|
||||||
return fmt.Errorf("arg already set to %s", *st.arg)
|
// if st.arg == nil {
|
||||||
}
|
// return "", fmt.Errorf("arg is not set")
|
||||||
return nil
|
// }
|
||||||
}
|
// 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.
|
// Down adds the given symbol to the command stack.
|
||||||
//
|
//
|
||||||
@ -391,12 +394,31 @@ func(st *State) SetCode(b []byte) {
|
|||||||
st.code = b
|
st.code = b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the remaning cached bytecode
|
||||||
func(st *State) GetCode() ([]byte, error) {
|
func(st *State) GetCode() ([]byte, error) {
|
||||||
b := st.code
|
b := st.code
|
||||||
st.code = []byte{}
|
st.code = []byte{}
|
||||||
return b, nil
|
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
|
// return 0-indexed frame number where key is defined. -1 if not defined
|
||||||
func(st *State) frameOf(key string) int {
|
func(st *State) frameOf(key string) int {
|
||||||
for i, m := range st.Cache {
|
for i, m := range st.Cache {
|
||||||
|
@ -8,21 +8,21 @@ import (
|
|||||||
// Check creation
|
// Check creation
|
||||||
func TestNewState(t *testing.T) {
|
func TestNewState(t *testing.T) {
|
||||||
st := NewState(5)
|
st := NewState(5)
|
||||||
if len(st.Flags) != 1 {
|
if len(st.Flags) != 2 {
|
||||||
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
||||||
}
|
}
|
||||||
st = NewState(8)
|
st = NewState(8)
|
||||||
if len(st.Flags) != 1 {
|
if len(st.Flags) != 2 {
|
||||||
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
||||||
}
|
}
|
||||||
st = NewState(17)
|
st = NewState(17)
|
||||||
if len(st.Flags) != 3 {
|
if len(st.Flags) != 4 {
|
||||||
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
t.Errorf("invalid state flag length: %v", len(st.Flags))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateFlags(t *testing.T) {
|
func TestStateFlags(t *testing.T) {
|
||||||
st := NewState(17)
|
st := NewState(9)
|
||||||
v, err := st.GetFlag(2)
|
v, err := st.GetFlag(2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
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
|
const VERSION = 0
|
||||||
|
|
||||||
|
// Opcodes
|
||||||
const (
|
const (
|
||||||
BACK = 0
|
BACK = 0
|
||||||
CATCH = 1
|
CATCH = 1
|
||||||
@ -14,22 +15,25 @@ const (
|
|||||||
MAP = 5
|
MAP = 5
|
||||||
MOVE = 6
|
MOVE = 6
|
||||||
HALT = 7
|
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}
|
b := []byte{0x00, 0x00}
|
||||||
binary.BigEndian.PutUint16(b, instruction)
|
binary.BigEndian.PutUint16(b, instruction)
|
||||||
for _, arg := range args {
|
for _, arg := range strargs {
|
||||||
b = append(b, uint8(len(arg)))
|
b = append(b, uint8(len(arg)))
|
||||||
b = append(b, []byte(arg)...)
|
b = append(b, []byte(arg)...)
|
||||||
}
|
}
|
||||||
if post != nil {
|
if byteargs != nil {
|
||||||
b = append(b, uint8(len(post)))
|
b = append(b, uint8(len(byteargs)))
|
||||||
b = append(b, post...)
|
b = append(b, byteargs...)
|
||||||
}
|
}
|
||||||
if szPost != nil {
|
if numargs != nil {
|
||||||
b = append(b, szPost...)
|
b = append(b, numargs...)
|
||||||
}
|
}
|
||||||
return append(instructionList, b...)
|
return append(instructionList, b...)
|
||||||
}
|
}
|
||||||
|
99
go/vm/vm.go
99
go/vm/vm.go
@ -7,7 +7,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.defalsify.org/festive/resource"
|
"git.defalsify.org/festive/resource"
|
||||||
"git.defalsify.org/festive/router"
|
|
||||||
"git.defalsify.org/festive/state"
|
"git.defalsify.org/festive/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,52 +21,6 @@ func argFromBytes(input []byte) (string, []byte, error) {
|
|||||||
return string(out), input[1+sz:], nil
|
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.
|
// Run extracts individual op codes and arguments and executes them.
|
||||||
//
|
//
|
||||||
// Each step may update the state.
|
// 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)
|
instruction, err = RunMove(instruction[2:], st, rs, ctx)
|
||||||
case BACK:
|
case BACK:
|
||||||
instruction, err = RunBack(instruction[2:], st, rs, ctx)
|
instruction, err = RunBack(instruction[2:], st, rs, ctx)
|
||||||
|
case INCMP:
|
||||||
|
instruction, err = RunIncmp(instruction[2:], st, rs, ctx)
|
||||||
case HALT:
|
case HALT:
|
||||||
log.Printf("found HALT, stopping")
|
return RunHalt(instruction[2:], st, rs, ctx)
|
||||||
return instruction[2:], err
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("Unhandled state: %v", op)
|
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]
|
bitFieldSize := tail[0]
|
||||||
bitField := tail[1:1+bitFieldSize]
|
bitField := tail[1:1+bitFieldSize]
|
||||||
tail = tail[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)
|
log.Printf("catch at flag %v, moving to %v", bitField, head)
|
||||||
st.Down(head)
|
st.Down(head)
|
||||||
tail = []byte{}
|
tail = []byte{}
|
||||||
@ -198,6 +163,40 @@ func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx cont
|
|||||||
return instruction, nil
|
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
|
// retrieve data for key
|
||||||
func refresh(key string, rs resource.Resource, ctx context.Context) (string, error) {
|
func refresh(key string, rs resource.Resource, ctx context.Context) (string, error) {
|
||||||
fn, err := rs.FuncFor(key)
|
fn, err := rs.FuncFor(key)
|
||||||
|
281
go/vm/vm_test.go
281
go/vm/vm_test.go
@ -9,7 +9,7 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"git.defalsify.org/festive/resource"
|
"git.defalsify.org/festive/resource"
|
||||||
"git.defalsify.org/festive/router"
|
// "git.defalsify.org/festive/router"
|
||||||
"git.defalsify.org/festive/state"
|
"git.defalsify.org/festive/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,11 +35,6 @@ type TestStatefulResolver struct {
|
|||||||
state *state.State
|
state *state.State
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (r *TestResource) getEachArg(ctx context.Context) (string, error) {
|
|
||||||
return r.state.PopArg()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *TestResource) GetTemplate(sym string) (string, error) {
|
func (r *TestResource) GetTemplate(sym string) (string, error) {
|
||||||
switch sym {
|
switch sym {
|
||||||
case "foo":
|
case "foo":
|
||||||
@ -84,11 +79,16 @@ func (r *TestResource) FuncFor(sym string) (resource.EntryFunc, error) {
|
|||||||
case "dyn":
|
case "dyn":
|
||||||
return getDyn, nil
|
return getDyn, nil
|
||||||
case "arg":
|
case "arg":
|
||||||
return r.getEachArg, nil
|
return r.getInput, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("invalid function: '%s'", sym)
|
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) {
|
func(r *TestResource) GetCode(sym string) ([]byte, error) {
|
||||||
return []byte{}, nil
|
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) {
|
func TestHalt(t *testing.T) {
|
||||||
st := state.NewState(5)
|
st := state.NewState(5)
|
||||||
rs := TestResource{}
|
rs := TestResource{}
|
||||||
@ -423,3 +227,74 @@ func TestHalt(t *testing.T) {
|
|||||||
t.Fatalf("Expected MOVE instruction, found '%v'", b)
|
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