Reinstatate MNEXT, MPREV

This commit is contained in:
lash 2023-04-08 09:31:32 +01:00
parent 8b1f91e859
commit 9e4205e6e8
Signed by untrusted user who does not match committer: lash
GPG Key ID: 21D2E7BB88C2A746
9 changed files with 148 additions and 30 deletions

View File

@ -14,7 +14,6 @@ const (
MENU_UP = _MENU_OFFSET + 1 MENU_UP = _MENU_OFFSET + 1
MENU_NEXT = _MENU_OFFSET + 2 MENU_NEXT = _MENU_OFFSET + 2
MENU_PREVIOUS = _MENU_OFFSET + 3 MENU_PREVIOUS = _MENU_OFFSET + 3
//MENU_BROWSE = _MENU_OFFSET + 4
) )
var ( var (
@ -23,7 +22,6 @@ var (
"UP": MENU_UP, "UP": MENU_UP,
"NEXT": MENU_NEXT, "NEXT": MENU_NEXT,
"PREVIOUS": MENU_PREVIOUS, "PREVIOUS": MENU_PREVIOUS,
//"BROWSE": MENU_BROWSE,
} }
) )
@ -66,15 +64,18 @@ func (mp *MenuProcessor) ToLines() []byte {
postLines := []byte{} postLines := []byte{}
for _, v := range mp.items { for _, v := range mp.items {
preLines = vm.NewLine(preLines, vm.MOUT, []string{v.choice, v.display}, nil, nil)
switch v.code { switch v.code {
case MENU_UP: case MENU_UP:
preLines = vm.NewLine(preLines, vm.MOUT, []string{v.choice, v.display}, nil, nil)
postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, "_"}, nil, nil) postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, "_"}, nil, nil)
case MENU_NEXT: case MENU_NEXT:
preLines = vm.NewLine(preLines, vm.MNEXT, []string{v.choice, v.display}, nil, nil)
postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, ">"}, nil, nil) postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, ">"}, nil, nil)
case MENU_PREVIOUS: case MENU_PREVIOUS:
preLines = vm.NewLine(preLines, vm.MPREV, []string{v.choice, v.display}, nil, nil)
postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, "<"}, nil, nil) postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, "<"}, nil, nil)
default: default:
preLines = vm.NewLine(preLines, vm.MOUT, []string{v.choice, v.display}, nil, nil)
postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, v.target}, nil, nil) postLines = vm.NewLine(postLines, vm.INCMP, []string{v.choice, v.target}, nil, nil)
} }
} }

View File

@ -35,8 +35,8 @@ func TestMenuInterpreter(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expect := `MOUT 0 "inky" expect := `MOUT 0 "inky"
MOUT 1 "pinky" MNEXT 1 "pinky"
MOUT 2 "blinky clyde" MPREV 2 "blinky clyde"
MOUT 99 "tinky-winky" MOUT 99 "tinky-winky"
HALT HALT
INCMP 0 foo INCMP 0 foo

View File

@ -56,7 +56,7 @@ func(en *Engine) Init(sym string, 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) (bool, error) { func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
err := vm.CheckInput(input) err := vm.ValidInput(input)
if err != nil { if err != nil {
return true, err return true, err
} }

View File

@ -214,6 +214,9 @@ func(st State) Where() (string, uint16) {
// Next moves to the next sink page index. // Next moves to the next sink page index.
func(st State) Next() (uint16, error) { func(st State) Next() (uint16, error) {
if len(st.execPath) == 0 {
return 0, fmt.Errorf("state root node not yet defined")
}
st.sizeIdx += 1 st.sizeIdx += 1
return st.sizeIdx, nil return st.sizeIdx, nil
} }
@ -222,6 +225,9 @@ func(st State) Next() (uint16, error) {
// //
// Fails if try to move beyond index 0. // Fails if try to move beyond index 0.
func(st *State) Previous() (uint16, error) { func(st *State) Previous() (uint16, error) {
if len(st.execPath) == 0 {
return 0, fmt.Errorf("state root node not yet defined")
}
if st.sizeIdx == 0 { if st.sizeIdx == 0 {
return 0, fmt.Errorf("already at first index") return 0, fmt.Errorf("already at first index")
} }
@ -233,6 +239,9 @@ func(st *State) Previous() (uint16, error) {
// //
// Two values are returned, for the "next" and "previous" options in that order. A false value means the option is not available in the current state. // Two values are returned, for the "next" and "previous" options in that order. A false value means the option is not available in the current state.
func(st *State) Sides() (bool, bool) { func(st *State) Sides() (bool, bool) {
if len(st.execPath) == 0 {
return false, false
}
next := true next := true
if st.sizeIdx == 0 { if st.sizeIdx == 0 {
return next, false return next, false
@ -240,6 +249,16 @@ func(st *State) Sides() (bool, bool) {
return next, true return next, true
} }
// Top returns true if currently at topmode node.
//
// Fails if first Down() was never called.
func(st *State) Top() (bool, error) {
if len(st.execPath) == 0 {
return false, fmt.Errorf("state root node not yet defined")
}
return len(st.execPath) == 1, nil
}
// Down adds the given symbol to the command stack. // Down adds the given symbol to the command stack.
// //
// Clears mapping and sink. // Clears mapping and sink.
@ -259,10 +278,10 @@ func(st *State) Down(input string) {
// //
// Fails if called at top frame. // Fails if called at top frame.
func(st *State) Up() (string, error) { func(st *State) Up() (string, error) {
l := len(st.Cache) if len(st.execPath) == 0 {
if l == 0 {
return "", fmt.Errorf("exit called beyond top frame") return "", fmt.Errorf("exit called beyond top frame")
} }
l := len(st.Cache)
l -= 1 l -= 1
m := st.Cache[l] m := st.Cache[l]
for k, v := range m { for k, v := range m {

View File

@ -129,6 +129,22 @@ func ParseAll(b []byte, w io.Writer) (int, error) {
rs = fmt.Sprintf("%s %s \"%s\"\n", s, r, v) rs = fmt.Sprintf("%s %s \"%s\"\n", s, r, v)
} }
} }
case MNEXT:
r, v, bb, err := ParseMNext(b)
b = bb
if err == nil {
if w != nil {
rs = fmt.Sprintf("%s %s \"%s\"\n", s, r, v)
}
}
case MPREV:
r, v, bb, err := ParseMPrev(b)
b = bb
if err == nil {
if w != nil {
rs = fmt.Sprintf("%s %s \"%s\"\n", s, r, v)
}
}
} }
if err != nil { if err != nil {
return rn, err return rn, err

View File

@ -91,6 +91,26 @@ func TestToString(t *testing.T) {
t.Fatalf("expected:\n\t%v\ngot:\n\t%v", expect, r) 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) b = NewLine(nil, MOUT, []string{"1", "foo"}, nil, nil)
r, err = ToString(b) r, err = ToString(b)
if err != nil { if err != nil {

View File

@ -19,7 +19,7 @@ var (
) )
// CheckInput validates the given byte string as client input. // CheckInput validates the given byte string as client input.
func CheckInput(input []byte) error { func ValidInput(input []byte) error {
if !inputRegex.Match(input) { if !inputRegex.Match(input) {
return fmt.Errorf("Input '%s' does not match input format /%s/", input, inputRegexStr) return fmt.Errorf("Input '%s' does not match input format /%s/", input, inputRegexStr)
} }
@ -27,7 +27,7 @@ func CheckInput(input []byte) error {
} }
// control characters for relative navigation. // control characters for relative navigation.
func checkControl(input []byte) error { func validControl(input []byte) error {
if !ctrlRegex.Match(input) { if !ctrlRegex.Match(input) {
return fmt.Errorf("Input '%s' does not match 'control' format /%s/", input, ctrlRegexStr) return fmt.Errorf("Input '%s' does not match 'control' format /%s/", input, ctrlRegexStr)
} }
@ -35,36 +35,68 @@ func checkControl(input []byte) error {
} }
// CheckSym validates the given byte string as a node symbol. // CheckSym validates the given byte string as a node symbol.
func CheckSym(input []byte) error { func ValidSym(input []byte) error {
if !symRegex.Match(input) { if !symRegex.Match(input) {
return fmt.Errorf("Input '%s' does not match 'sym' format /%s/", input, symRegexStr) return fmt.Errorf("Input '%s' does not match 'sym' format /%s/", input, symRegexStr)
} }
return nil return nil
} }
// false if target is not valid
func valid(target []byte) bool {
var ok bool
if len(target) == 0 {
return false
}
err := ValidSym(target)
if err == nil {
ok = true
}
if !ok {
err = validControl(target)
if err == nil {
ok = true
}
}
return ok
}
// CheckTarget tests whether the navigation state transition is available in the current state.
//
// Fails if target is formally invalid, or if navigation is unavailable.
func CheckTarget(target []byte, st *state.State) (bool, error) {
ok := valid(target)
if !ok {
return false, fmt.Errorf("invalid target: %x", target)
}
switch target[0] {
case '_':
topOk, err := st.Top()
if err!= nil {
return false, err
}
return topOk, nil
case '<':
_, prevOk := st.Sides()
return prevOk, nil
case '>':
nextOk, _ := st.Sides()
return nextOk, nil
}
return true, nil
}
// route parsed target symbol to navigation state change method, // route parsed target symbol to navigation state change method,
func applyTarget(target []byte, st *state.State, ctx context.Context) (string, uint16, error) { func applyTarget(target []byte, st *state.State, ctx context.Context) (string, uint16, error) {
var err error var err error
var valid bool
sym, idx := st.Where() sym, idx := st.Where()
err = CheckInput(target) ok := valid(target)
if err == nil { if !ok {
valid = true return sym, idx, fmt.Errorf("invalid input: %x", target)
}
if !valid {
err = CheckSym(target)
if err == nil {
valid = true
}
}
if !valid {
err = checkControl(target)
if err == nil {
valid = true
}
} }
switch target[0] { switch target[0] {

View File

@ -17,7 +17,9 @@ const (
INCMP = 8 INCMP = 8
MSIZE = 9 MSIZE = 9
MOUT = 10 MOUT = 10
_MAX = 10 MNEXT = 11
MPREV = 12
_MAX = 12
) )
var ( var (
@ -33,6 +35,8 @@ var (
INCMP: "INCMP", INCMP: "INCMP",
MSIZE: "MSIZE", MSIZE: "MSIZE",
MOUT: "MOUT", MOUT: "MOUT",
MNEXT: "MNEXT",
MPREV: "MPREV",
} }
OpcodeIndex = map[string]Opcode { OpcodeIndex = map[string]Opcode {
@ -47,6 +51,8 @@ var (
"INCMP": INCMP, "INCMP": INCMP,
"MSIZE": MSIZE, "MSIZE": MSIZE,
"MOUT": MOUT, "MOUT": MOUT,
"MNEXT": MNEXT,
"MPREV": MPREV,
} }
) )

View File

@ -44,6 +44,10 @@ func Run(b []byte, st *state.State, rs resource.Resource, ctx context.Context) (
b, err = RunMSize(b, st, rs, ctx) b, err = RunMSize(b, st, rs, ctx)
case MOUT: case MOUT:
b, err = RunMOut(b, st, rs, ctx) 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: case HALT:
b, err = RunHalt(b, st, rs, ctx) b, err = RunHalt(b, st, rs, ctx)
return b, err return b, err
@ -253,6 +257,26 @@ func RunMOut(b []byte, st *state.State, rs resource.Resource, ctx context.Contex
return b, err return b, err
} }
// RunMNext executes the MNEXT opcode
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
}
// RunMPrev executes the MPREV opcode
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
}
// 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)