Add menu browser choices handling
This commit is contained in:
parent
856bbdeb63
commit
06938a9628
34
README.md
34
README.md
@ -18,8 +18,10 @@ The VM defines the following opcode symbols:
|
||||
* `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.
|
||||
* `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.
|
||||
* `MSIZE <max> <min>` - Set min and 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.
|
||||
* `MSEP <choice> <display> <direction>` - Define how to display a menu separator, when browsing menus that are too long. Direction `0` is forward, `>0` is backward. If a `>0` value is not defined, no previous step will be available.
|
||||
|
||||
|
||||
### External code
|
||||
|
||||
@ -53,6 +55,25 @@ The signal flag arguments should only set a single flag to be tested. If more th
|
||||
First 8 flags are reserved and used for internal VM operations.
|
||||
|
||||
|
||||
### Avoid duplicate menu items
|
||||
|
||||
The vm execution should overwrite duplicate `MOUT` directives with the last definition between `HALT` instructions.
|
||||
|
||||
The assembler should detect duplicate `INCMP` and `MOUT` (or menu batch code) selectors, and fail to compile. `MSEP` should be included in duplication detection.
|
||||
|
||||
|
||||
## Menus
|
||||
|
||||
A menu has both a display and a input processing part. They are on either side of a `HALT` instruction.
|
||||
|
||||
To assist with menu creation, a few batch operation symbols have been made available for use with the assembly language.
|
||||
|
||||
* `DOWN <choice> <display> <symbol>` descend to next frame
|
||||
* `UP <choice> <display>` return to the previous frame
|
||||
* `NEXT <choice> <display>` include pagination advance
|
||||
* `PREVIOUS <choice> <display>` include pagination return. If `NEXT` has not been defined this will not be rendered.
|
||||
|
||||
|
||||
## Rendering
|
||||
|
||||
The fixed-size output is generated using a templating language, and a combination of one or more _max size_ properties, and an optional _sink_ property that will attempt to consume all remaining capacity of the rendered template.
|
||||
@ -68,6 +89,17 @@ For example, in this example
|
||||
The renderer may use up to `256 - 120 - 5 - 12 = 119` bytes from the _sink_ when rendering the output.
|
||||
|
||||
|
||||
### Menu rendering
|
||||
|
||||
The menu is appended to the template output.
|
||||
|
||||
A max size can be set for the menu, which will count towards the space available for the _template sink_.
|
||||
|
||||
Menus too long for a single screen should be browseable through separate screens. How the browse choice is displayed is defined using the `MSEP` definition. The browse choice counts towards the menu size capacity.
|
||||
|
||||
When browsing additional menu pages, the template output should not be included.
|
||||
|
||||
|
||||
### Multipage support
|
||||
|
||||
Multipage outputs, like listings, are handled using the _sink_ output constraints:
|
||||
|
@ -13,8 +13,9 @@ type EntryFunc func(ctx context.Context) (string, error)
|
||||
type Resource interface {
|
||||
GetTemplate(sym string) (string, error) // Get the template for a 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)
|
||||
PutMenu(string, string) error // Add a menu item.
|
||||
ShiftMenu() (string, string, error) // Remove and return the first menu item in list.
|
||||
SetMenuBrowse(string, string, bool) error // Set menu browser display details.
|
||||
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.
|
||||
@ -22,6 +23,18 @@ type Resource interface {
|
||||
|
||||
type MenuResource struct {
|
||||
menu [][2]string
|
||||
next [2]string
|
||||
prev [2]string
|
||||
}
|
||||
|
||||
func(m *MenuResource) SetMenuBrowse(selector string, title string, back bool) error {
|
||||
entry := [2]string{selector, title}
|
||||
if back {
|
||||
m.prev = entry
|
||||
} else {
|
||||
m.next = entry
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func(m *MenuResource) PutMenu(selector string, title string) error {
|
||||
@ -45,9 +58,6 @@ func(m *MenuResource) RenderMenu() (string, error) {
|
||||
l := len(r)
|
||||
choice, title, err := m.ShiftMenu()
|
||||
if err != nil {
|
||||
//if l == 0 { // TODO: replace with EOF
|
||||
// return "", err
|
||||
//}
|
||||
break
|
||||
}
|
||||
if l > 0 {
|
||||
|
@ -97,6 +97,20 @@ func ToString(b []byte) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
s = fmt.Sprintf("%s %s \"%s\"", s, r, v)
|
||||
case MNEXT:
|
||||
r, v, bb, err := ParseMNext(b)
|
||||
b = bb
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s = fmt.Sprintf("%s %s \"%s\"", s, r, v)
|
||||
case MPREV:
|
||||
r, v, bb, err := ParseMPrev(b)
|
||||
b = bb
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s = fmt.Sprintf("%s %s \"%s\"", s, r, v)
|
||||
}
|
||||
s += "\n"
|
||||
if len(b) == 0 {
|
||||
|
@ -90,6 +90,46 @@ func TestToString(t *testing.T) {
|
||||
if r != expect {
|
||||
t.Fatalf("expected:\n\t%v\ngot:\n\t%v", expect, r)
|
||||
}
|
||||
|
||||
b = NewLine(nil, MNEXT, []string{"11", "nextmenu"}, nil, nil)
|
||||
r, err = ToString(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expect = "MNEXT 11 \"nextmenu\"\n"
|
||||
if r != expect {
|
||||
t.Fatalf("expected:\n\t%v\ngot:\n\t%v", expect, r)
|
||||
}
|
||||
|
||||
b = NewLine(nil, MPREV, []string{"222", "previous menu item"}, nil, nil)
|
||||
r, err = ToString(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expect = "MPREV 222 \"previous menu item\"\n"
|
||||
if r != expect {
|
||||
t.Fatalf("expected:\n\t%v\ngot:\n\t%v", expect, r)
|
||||
}
|
||||
|
||||
b = NewLine(nil, MOUT, []string{"1", "foo"}, nil, nil)
|
||||
r, err = ToString(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expect = "MOUT 1 \"foo\"\n"
|
||||
if r != expect {
|
||||
t.Fatalf("expected:\n\t%v\ngot:\n\t%v", expect, r)
|
||||
}
|
||||
|
||||
b = NewLine(nil, MSIZE, nil, nil, []uint8{0x42})
|
||||
r, err = ToString(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expect = "MSIZE 66\n"
|
||||
if r != expect {
|
||||
t.Fatalf("expected:\n\t%v\ngot:\n\t%v", expect, r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToStringMultiple(t *testing.T) {
|
||||
|
@ -17,7 +17,9 @@ const (
|
||||
INCMP = 8
|
||||
MSIZE = 9
|
||||
MOUT = 10
|
||||
_MAX = 10
|
||||
MNEXT = 11
|
||||
MPREV = 12
|
||||
_MAX = 12
|
||||
)
|
||||
|
||||
var (
|
||||
@ -33,5 +35,7 @@ var (
|
||||
INCMP: "INCMP",
|
||||
MSIZE: "MSIZE",
|
||||
MOUT: "MOUT",
|
||||
MNEXT: "MNEXT",
|
||||
MPREV: "MPREV",
|
||||
}
|
||||
)
|
||||
|
@ -44,6 +44,10 @@ func Run(b []byte, st *state.State, rs resource.Resource, ctx context.Context) (
|
||||
b, err = RunMSize(b, st, rs, ctx)
|
||||
case MOUT:
|
||||
b, err = RunMOut(b, st, rs, ctx)
|
||||
case MNEXT:
|
||||
b, err = RunMNext(b, st, rs, ctx)
|
||||
case MPREV:
|
||||
b, err = RunMPrev(b, st, rs, ctx)
|
||||
case HALT:
|
||||
b, err = RunHalt(b, st, rs, ctx)
|
||||
return b, err
|
||||
@ -105,15 +109,6 @@ func RunCroak(b []byte, st *state.State, rs resource.Resource, ctx context.Conte
|
||||
|
||||
// RunLoad executes the LOAD opcode
|
||||
func RunLoad(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||
// head, tail, err := instructionSplit(b)
|
||||
// if err != nil {
|
||||
// return b, err
|
||||
// }
|
||||
// if !st.Check(head) {
|
||||
// return b, fmt.Errorf("key %v already loaded", head)
|
||||
// }
|
||||
// sz := uint16(tail[0])
|
||||
// tail = tail[1:]
|
||||
sym, sz, b, err := ParseLoad(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
@ -129,10 +124,6 @@ func RunLoad(b []byte, st *state.State, rs resource.Resource, ctx context.Contex
|
||||
|
||||
// RunLoad executes the RELOAD opcode
|
||||
func RunReload(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||
// head, tail, err := instructionSplit(b)
|
||||
// if err != nil {
|
||||
// return b, err
|
||||
// }
|
||||
sym, b, err := ParseReload(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
@ -149,7 +140,6 @@ func RunReload(b []byte, st *state.State, rs resource.Resource, ctx context.Cont
|
||||
// RunLoad executes the MOVE opcode
|
||||
func RunMove(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||
sym, b, err := ParseMove(b)
|
||||
// head, tail, err := instructionSplit(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
@ -165,7 +155,6 @@ func RunMove(b []byte, st *state.State, rs resource.Resource, ctx context.Contex
|
||||
|
||||
// RunIncmp executes the INCMP opcode
|
||||
func RunInCmp(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||
//head, tail, err := instructionSplit(b)
|
||||
sym, target, b, err := ParseInCmp(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
@ -209,9 +198,30 @@ func RunHalt(b []byte, st *state.State, rs resource.Resource, ctx context.Contex
|
||||
|
||||
// RunMSize
|
||||
func RunMSize(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||
log.Printf("WARNING MSIZE not yet implemented")
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// RunMSize
|
||||
func RunMNext(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||
selector, display, b, err := ParseMNext(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
err = rs.SetMenuBrowse(selector, display, false)
|
||||
return b, err
|
||||
}
|
||||
|
||||
// RunMSize
|
||||
func RunMPrev(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||
selector, display, b, err := ParseMPrev(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
err = rs.SetMenuBrowse(selector, display, false)
|
||||
return b, err
|
||||
}
|
||||
|
||||
func RunMOut(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) {
|
||||
choice, title, b, err := ParseMOut(b)
|
||||
if err != nil {
|
||||
|
@ -317,3 +317,36 @@ func TestRunMenu(t *testing.T) {
|
||||
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestRunMenuBrowse(t *testing.T) {
|
||||
log.Printf("This test is incomplete, it must check the output of a menu browser once one is implemented. For now it only checks whether it can execute the runner endpoints for the instrucitons.")
|
||||
st := state.NewState(5)
|
||||
rs := TestResource{}
|
||||
|
||||
var err error
|
||||
|
||||
b := NewLine(nil, MOVE, []string{"foo"}, nil, nil)
|
||||
b = NewLine(b, MNEXT, []string{"11", "two"}, nil, nil)
|
||||
b = NewLine(b, MPREV, []string{"22", "two"}, 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)
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,14 @@ func ParseInCmp(b []byte) (string, string, []byte, error) {
|
||||
return parseTwoSym(b)
|
||||
}
|
||||
|
||||
func ParseMPrev(b []byte) (string, string, []byte, error) {
|
||||
return parseTwoSym(b)
|
||||
}
|
||||
|
||||
func ParseMNext(b []byte) (string, string, []byte, error) {
|
||||
return parseTwoSym(b)
|
||||
}
|
||||
|
||||
func ParseMSize(b []byte) (uint32, []byte, error) {
|
||||
if len(b) < 1 {
|
||||
return 0, b, fmt.Errorf("zero-length argument")
|
||||
|
Loading…
Reference in New Issue
Block a user