Instructions and render for menu display
This commit is contained in:
parent
ac4a2bac00
commit
a0f7ad5c80
22
README.md
22
README.md
@ -17,8 +17,9 @@ The VM defines the following opcode symbols:
|
|||||||
* `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 (typically, 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.
|
||||||
* `INCMP <arg> <symbol>` - Compare registered input to `arg` and and move to `symbol` node on match. Any consecutive matches will be ignored until `HALT` is called.
|
* `INCMP <arg> <symbol>` - Compare registered input to `arg`. If match, it has the same side-effects as `MOVE`. In addition, any consecutive `INCMP` matches will be ignored until `HALT` is called.
|
||||||
|
* `MSIZE <num>` - Set max display size of menu part to `num` bytes.
|
||||||
|
* `MOUT <choice> <display>` - Add menu display entry. Each entry should have a matching `INCMP` whose `arg` matches `choice`. `display` is a descriptive text of the menu item.
|
||||||
|
|
||||||
### External code
|
### External code
|
||||||
|
|
||||||
@ -128,14 +129,15 @@ It expects all replacement symbols to be available at time of rendering, and has
|
|||||||
(Minimal, WIP)
|
(Minimal, WIP)
|
||||||
|
|
||||||
```
|
```
|
||||||
0008 03666f6f 03626172 # INCMP "foo" "bar" - move to node "bar" if input is "FOO"
|
000a 03666f6f 06746f20666f6f # MOUT "foo" "to foo" - display a menu entry for choice "foo", described by "to foo"
|
||||||
0001 0461696565 01 01 # CATCH "aiee" 1 1 - move to node "aiee" (and immediately halt) if input match flag (1) is not set (1)
|
0008 03666f6f 03626172 # INCMP "foo" "bar" - move to node "bar" if input is "FOO"
|
||||||
0003 04616263 020104 # LOAD "abc" 260 - execute code symbol "abc" with a result size limit of 260 (2 byte BE integer, 0x0104)
|
0001 0461696565 01 01 # CATCH "aiee" 1 1 - move to node "aiee" (and immediately halt) if input match flag (1) is not set (1)
|
||||||
0003 04646566 00 # LOAD "def" 0 - execute code symbol "abc" with no size limit (sink)
|
0003 04616263 020104 # LOAD "abc" 260 - execute code symbol "abc" with a result size limit of 260 (2 byte BE integer, 0x0104)
|
||||||
0005 04616263 # MAP "abc" - make "abc" available for renderer
|
0003 04646566 00 # LOAD "def" 0 - execute code symbol "abc" with no size limit (sink)
|
||||||
0007 # HALT - stop execution (require new input to continue)
|
0005 04616263 # MAP "abc" - make "abc" available for renderer
|
||||||
0006 03313233 # MOVE "123" - move to node "123" (regardless of input)
|
0007 # HALT - stop execution (require new input to continue)
|
||||||
0007 # HALT - stop execution
|
0006 03313233 # MOVE "123" - move to node "123" (regardless of input)
|
||||||
|
0007 # HALT - stop execution
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development tools
|
## Development tools
|
||||||
|
@ -32,21 +32,25 @@ func NewEngine(st *state.State, rs resource.Resource) Engine {
|
|||||||
//
|
//
|
||||||
// It makes sure bootstrapping code has been executed, and that the exposed bytecode is ready for user input.
|
// It makes sure bootstrapping code has been executed, and that the exposed bytecode is ready for user input.
|
||||||
func(en *Engine) Init(sym string, ctx context.Context) error {
|
func(en *Engine) Init(sym string, ctx context.Context) error {
|
||||||
b := vm.NewLine([]byte{}, vm.MOVE, []string{sym}, nil, nil)
|
b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil)
|
||||||
var err error
|
var err error
|
||||||
_, err = vm.Run(b, en.st, en.rs, ctx)
|
b, err = vm.Run(b, en.st, en.rs, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
location := en.st.Where()
|
// location := en.st.Where()
|
||||||
code, err := en.rs.GetCode(location)
|
// code, err := en.rs.GetCode(location)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if len(code) == 0 {
|
// if len(code) == 0 {
|
||||||
return fmt.Errorf("no code found at resource %s", en.rs)
|
// return fmt.Errorf("no code found at resource %s", en.rs)
|
||||||
}
|
// }
|
||||||
return en.st.AppendCode(code)
|
//
|
||||||
|
// code, err = vm.Run(code, en.st, en.rs, ctx)
|
||||||
|
//
|
||||||
|
en.st.SetCode(b)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec processes user input against the current state of the virtual machine environment.
|
// Exec processes user input against the current state of the virtual machine environment.
|
||||||
@ -97,6 +101,13 @@ func(en *Engine) WriteResult(w io.Writer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
m, err := en.rs.RenderMenu()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(m) > 0 {
|
||||||
|
r += "\n" + m
|
||||||
|
}
|
||||||
c, err := io.WriteString(w, r)
|
c, err := io.WriteString(w, r)
|
||||||
log.Printf("%v bytes written as result for %v", c, location)
|
log.Printf("%v bytes written as result for %v", c, location)
|
||||||
return err
|
return err
|
||||||
|
@ -39,10 +39,16 @@ func(fs FsWrapper) one(ctx context.Context) (string, error) {
|
|||||||
return "one", nil
|
return "one", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func(fs FsWrapper) inky(ctx context.Context) (string, error) {
|
||||||
|
return "tinkywinky", nil
|
||||||
|
}
|
||||||
|
|
||||||
func(fs FsWrapper) FuncFor(sym string) (resource.EntryFunc, error) {
|
func(fs FsWrapper) FuncFor(sym string) (resource.EntryFunc, error) {
|
||||||
switch sym {
|
switch sym {
|
||||||
case "one":
|
case "one":
|
||||||
return fs.one, nil
|
return fs.one, nil
|
||||||
|
case "inky":
|
||||||
|
return fs.inky, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("function for %v not found", sym)
|
return nil, fmt.Errorf("function for %v not found", sym)
|
||||||
}
|
}
|
||||||
@ -93,6 +99,20 @@ func TestEngineInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
r := st.Where()
|
r := st.Where()
|
||||||
if r != "foo" {
|
if r != "foo" {
|
||||||
t.Fatalf("expected where-string 'foo', got %v", r)
|
t.Fatalf("expected where-string 'foo', got %s", r)
|
||||||
|
}
|
||||||
|
w = bytes.NewBuffer(nil)
|
||||||
|
err = en.WriteResult(w)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b = w.Bytes()
|
||||||
|
expect := `this is in foo
|
||||||
|
|
||||||
|
it has more lines
|
||||||
|
0:to foo
|
||||||
|
1:go bar`
|
||||||
|
if !bytes.Equal(b, []byte(expect)) {
|
||||||
|
t.Fatalf("expected\n\t%s\ngot:\n\t%s\n", expect, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FsResource struct {
|
type FsResource struct {
|
||||||
|
MenuResource
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,3 +22,4 @@ func DefaultRenderTemplate(r Resource, sym string, values map[string]string) (st
|
|||||||
}
|
}
|
||||||
return b.String(), err
|
return b.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ package resource
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EntryFunc is a function signature for retrieving value for a key
|
// EntryFunc is a function signature for retrieving value for a key
|
||||||
@ -11,6 +13,47 @@ type EntryFunc func(ctx context.Context) (string, error)
|
|||||||
type Resource interface {
|
type Resource interface {
|
||||||
GetTemplate(sym string) (string, error) // Get the template for a given symbol.
|
GetTemplate(sym string) (string, error) // Get the template for a given symbol.
|
||||||
GetCode(sym string) ([]byte, error) // Get the bytecode for the given symbol.
|
GetCode(sym string) ([]byte, error) // Get the bytecode for the given symbol.
|
||||||
|
PutMenu(string, string) error // Add a menu item
|
||||||
|
ShiftMenu() (string, string, error)
|
||||||
RenderTemplate(sym string, values map[string]string) (string, error) // Render the given data map using the template of the symbol.
|
RenderTemplate(sym string, values map[string]string) (string, error) // Render the given data map using the template of the symbol.
|
||||||
|
RenderMenu() (string, error)
|
||||||
FuncFor(sym string) (EntryFunc, error) // Resolve symbol code point for.
|
FuncFor(sym string) (EntryFunc, error) // Resolve symbol code point for.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MenuResource struct {
|
||||||
|
menu [][2]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func(m *MenuResource) PutMenu(selector string, title string) error {
|
||||||
|
m.menu = append(m.menu, [2]string{selector, title})
|
||||||
|
log.Printf("menu %v", m.menu)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func(m *MenuResource) ShiftMenu() (string, string, error) {
|
||||||
|
if len(m.menu) == 0 {
|
||||||
|
return "", "", fmt.Errorf("menu is empty")
|
||||||
|
}
|
||||||
|
r := m.menu[0]
|
||||||
|
m.menu = m.menu[1:]
|
||||||
|
return r[0], r[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func(m *MenuResource) RenderMenu() (string, error) {
|
||||||
|
r := ""
|
||||||
|
for true {
|
||||||
|
l := len(r)
|
||||||
|
choice, title, err := m.ShiftMenu()
|
||||||
|
if err != nil {
|
||||||
|
//if l == 0 { // TODO: replace with EOF
|
||||||
|
// return "", err
|
||||||
|
//}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if l > 0 {
|
||||||
|
r += "\n"
|
||||||
|
}
|
||||||
|
r += fmt.Sprintf("%s:%s", choice, title)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
5
go/testdata/testdata.go
vendored
5
go/testdata/testdata.go
vendored
@ -37,6 +37,7 @@ func out(sym string, b []byte, tpl string) error {
|
|||||||
|
|
||||||
func root() error {
|
func root() error {
|
||||||
b := []byte{}
|
b := []byte{}
|
||||||
|
b = vm.NewLine(b, vm.HALT, nil, nil, nil)
|
||||||
b = vm.NewLine(b, vm.INCMP, []string{"1", "foo"}, nil, nil)
|
b = vm.NewLine(b, vm.INCMP, []string{"1", "foo"}, nil, nil)
|
||||||
b = vm.NewLine(b, vm.INCMP, []string{"2", "bar"}, nil, nil)
|
b = vm.NewLine(b, vm.INCMP, []string{"2", "bar"}, nil, nil)
|
||||||
|
|
||||||
@ -47,9 +48,11 @@ func root() error {
|
|||||||
|
|
||||||
func foo() error {
|
func foo() error {
|
||||||
b := []byte{}
|
b := []byte{}
|
||||||
|
b = vm.NewLine(b, vm.MOUT, []string{"0", "to foo"}, nil, nil)
|
||||||
|
b = vm.NewLine(b, vm.MOUT, []string{"1", "go bar"}, nil, nil)
|
||||||
b = vm.NewLine(b, vm.LOAD, []string{"inky"}, []byte{20}, nil)
|
b = vm.NewLine(b, vm.LOAD, []string{"inky"}, []byte{20}, nil)
|
||||||
b = vm.NewLine(b, vm.HALT, nil, nil, nil)
|
b = vm.NewLine(b, vm.HALT, nil, nil, nil)
|
||||||
b = vm.NewLine(b, vm.INCMP, []string{"0", "_back"}, nil, nil)
|
b = vm.NewLine(b, vm.INCMP, []string{"0", "_"}, nil, nil)
|
||||||
b = vm.NewLine(b, vm.INCMP, []string{"1", "baz"}, nil, nil)
|
b = vm.NewLine(b, vm.INCMP, []string{"1", "baz"}, nil, nil)
|
||||||
b = vm.NewLine(b, vm.CATCH, []string{"_catch"}, []byte{1}, []uint8{1})
|
b = vm.NewLine(b, vm.CATCH, []string{"_catch"}, []byte{1}, []uint8{1})
|
||||||
|
|
||||||
|
@ -83,6 +83,20 @@ func ToString(b []byte) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
case MSIZE:
|
||||||
|
r, bb, err := ParseMSize(b)
|
||||||
|
b = bb
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s = fmt.Sprintf("%s %v", s, r)
|
||||||
|
case MOUT:
|
||||||
|
r, v, bb, err := ParseMOut(b)
|
||||||
|
b = bb
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s = fmt.Sprintf("%s %s \"%s\"", s, r, v)
|
||||||
}
|
}
|
||||||
s += "\n"
|
s += "\n"
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
|
@ -15,7 +15,9 @@ const (
|
|||||||
MOVE = 6
|
MOVE = 6
|
||||||
HALT = 7
|
HALT = 7
|
||||||
INCMP = 8
|
INCMP = 8
|
||||||
_MAX = 8
|
MSIZE = 9
|
||||||
|
MOUT = 10
|
||||||
|
_MAX = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -29,5 +31,7 @@ var (
|
|||||||
MOVE: "MOVE",
|
MOVE: "MOVE",
|
||||||
HALT: "HALT",
|
HALT: "HALT",
|
||||||
INCMP: "INCMP",
|
INCMP: "INCMP",
|
||||||
|
MSIZE: "MSIZE",
|
||||||
|
MOUT: "MOUT",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
func Run(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
func Run(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
running := true
|
running := true
|
||||||
for running {
|
for running {
|
||||||
|
log.Printf("execute code %x", b)
|
||||||
op, bb, err := opSplit(b)
|
op, bb, err := opSplit(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return b, err
|
return b, err
|
||||||
@ -39,6 +40,10 @@ func Run(b []byte, st *state.State, rs resource.Resource, ctx context.Context) (
|
|||||||
b, err = RunMove(b, st, rs, ctx)
|
b, err = RunMove(b, st, rs, ctx)
|
||||||
case INCMP:
|
case INCMP:
|
||||||
b, err = RunInCmp(b, st, rs, ctx)
|
b, err = RunInCmp(b, st, rs, ctx)
|
||||||
|
case MSIZE:
|
||||||
|
b, err = RunMSize(b, st, rs, ctx)
|
||||||
|
case MOUT:
|
||||||
|
b, err = RunMOut(b, st, rs, ctx)
|
||||||
case HALT:
|
case HALT:
|
||||||
b, err = RunHalt(b, st, rs, ctx)
|
b, err = RunHalt(b, st, rs, ctx)
|
||||||
return b, err
|
return b, err
|
||||||
@ -149,6 +154,12 @@ func RunMove(b []byte, st *state.State, rs resource.Resource, ctx context.Contex
|
|||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
st.Down(sym)
|
st.Down(sym)
|
||||||
|
code, err := rs.GetCode(sym)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
log.Printf("loaded additional code: %x", code)
|
||||||
|
b = append(b, code...)
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +186,12 @@ func RunInCmp(b []byte, st *state.State, rs resource.Resource, ctx context.Conte
|
|||||||
_, err = st.SetFlag(state.FLAG_INMATCH)
|
_, err = st.SetFlag(state.FLAG_INMATCH)
|
||||||
st.Down(target)
|
st.Down(target)
|
||||||
}
|
}
|
||||||
log.Printf("b last %v", b)
|
code, err := rs.GetCode(target)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
log.Printf("loaded additional code: %x", code)
|
||||||
|
b = append(b, code...)
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,6 +207,19 @@ func RunHalt(b []byte, st *state.State, rs resource.Resource, ctx context.Contex
|
|||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunMSize
|
||||||
|
func RunMSize(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunMOut(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||||
|
choice, title, b, err := ParseMOut(b)
|
||||||
|
if err != nil {
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
err = rs.PutMenu(choice, title)
|
||||||
|
return b, 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) {
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
var dynVal = "three"
|
var dynVal = "three"
|
||||||
|
|
||||||
type TestResource struct {
|
type TestResource struct {
|
||||||
|
resource.MenuResource
|
||||||
state *state.State
|
state *state.State
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,3 +288,32 @@ func TestRunArgInvalid(t *testing.T) {
|
|||||||
t.Errorf("expected where-state _catch, got %v", r)
|
t.Errorf("expected where-state _catch, got %v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunMenu(t *testing.T) {
|
||||||
|
st := state.NewState(5)
|
||||||
|
rs := TestResource{}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
b := NewLine(nil, MOVE, []string{"foo"}, nil, nil)
|
||||||
|
b = NewLine(b, MOUT, []string{"0", "one"}, nil, nil)
|
||||||
|
b = NewLine(b, MOUT, []string{"1", "two"}, nil, nil)
|
||||||
|
|
||||||
|
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, err := rs.RenderMenu()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expect := "0:one\n1:two"
|
||||||
|
if r != expect {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
13
go/vm/vm.go
13
go/vm/vm.go
@ -47,6 +47,19 @@ func ParseInCmp(b []byte) (string, string, []byte, error) {
|
|||||||
return parseTwoSym(b)
|
return parseTwoSym(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseMSize(b []byte) (uint32, []byte, error) {
|
||||||
|
if len(b) < 1 {
|
||||||
|
return 0, b, fmt.Errorf("zero-length argument")
|
||||||
|
}
|
||||||
|
r := uint32(b[0])
|
||||||
|
b = b[1:]
|
||||||
|
return r, b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseMOut(b []byte) (string, string, []byte, error) {
|
||||||
|
return parseTwoSym(b)
|
||||||
|
}
|
||||||
|
|
||||||
func parseNoArg(b []byte) ([]byte, error) {
|
func parseNoArg(b []byte) ([]byte, error) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user