From 8b1f91e8599c08b83911adcc278d0d19a8f42fba Mon Sep 17 00:00:00 2001 From: lash Date: Sat, 8 Apr 2023 08:54:55 +0100 Subject: [PATCH] Factor out target sym navigation handling --- go/asm/asm.go | 1 - go/state/state.go | 28 +++++++++++++++ go/vm/input.go | 86 ++++++++++++++++++++++++++++++++++------------- go/vm/runner.go | 4 ++- 4 files changed, 94 insertions(+), 25 deletions(-) diff --git a/go/asm/asm.go b/go/asm/asm.go index 40ba8b2..c63fa57 100644 --- a/go/asm/asm.go +++ b/go/asm/asm.go @@ -212,7 +212,6 @@ func parseOne(op vm.Opcode, instruction *Instruction, w io.Writer) (int, error) n, err = writeSym(b, *a.Sym) n_buf += n return flush(b, w) - } func (a Arg) String() string { diff --git a/go/state/state.go b/go/state/state.go index 6d7f525..62f3f6a 100644 --- a/go/state/state.go +++ b/go/state/state.go @@ -212,6 +212,34 @@ func(st State) Where() (string, uint16) { return st.execPath[l-1], st.sizeIdx } +// Next moves to the next sink page index. +func(st State) Next() (uint16, error) { + st.sizeIdx += 1 + return st.sizeIdx, nil +} + +// Previous moves to the next sink page index. +// +// Fails if try to move beyond index 0. +func(st *State) Previous() (uint16, error) { + if st.sizeIdx == 0 { + return 0, fmt.Errorf("already at first index") + } + st.sizeIdx -= 1 + return st.sizeIdx, nil +} + +// Sides informs the caller which index page options will currently succeed. +// +// 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) { + next := true + if st.sizeIdx == 0 { + return next, false + } + return next, true +} + // Down adds the given symbol to the command stack. // // Clears mapping and sink. diff --git a/go/vm/input.go b/go/vm/input.go index 78b4369..1fc0054 100644 --- a/go/vm/input.go +++ b/go/vm/input.go @@ -11,42 +11,82 @@ import ( var ( inputRegexStr = "^[a-zA-Z0-9].*$" inputRegex = regexp.MustCompile(inputRegexStr) - ctrlInputRegexStr = "^[<>_]$" - ctrlInputRegex = regexp.MustCompile(inputRegexStr) + ctrlRegexStr = "^[<>_]$" + ctrlRegex = regexp.MustCompile(inputRegexStr) + symRegexStr = "^[a-zA-Z0-9][a-zA-Z0-9_]+$" + symRegex = regexp.MustCompile(inputRegexStr) + ) - +// CheckInput validates the given byte string as client input. func CheckInput(input []byte) error { if !inputRegex.Match(input) { - return fmt.Errorf("Input '%s' does not match format /%s/", input, inputRegexStr) + return fmt.Errorf("Input '%s' does not match input format /%s/", input, inputRegexStr) } return nil } -func applyControlInput(input []byte, st *state.State, ctx context.Context) (string, error) { +// control characters for relative navigation. +func checkControl(input []byte) error { + if !ctrlRegex.Match(input) { + return fmt.Errorf("Input '%s' does not match 'control' format /%s/", input, ctrlRegexStr) + } + return nil +} + +// CheckSym validates the given byte string as a node symbol. +func CheckSym(input []byte) error { + if !symRegex.Match(input) { + return fmt.Errorf("Input '%s' does not match 'sym' format /%s/", input, symRegexStr) + } + return nil +} + +// route parsed target symbol to navigation state change method, +func applyTarget(target []byte, st *state.State, ctx context.Context) (string, uint16, error) { var err error + var valid bool sym, idx := st.Where() - switch input[0] { + + err = CheckInput(target) + if err == nil { + valid = true + } + + if !valid { + err = CheckSym(target) + if err == nil { + valid = true + } + } + + if !valid { + err = checkControl(target) + if err == nil { + valid = true + } + } + + switch target[0] { case '_': sym, err = st.Up() if err != nil { - return sym, err + return sym, idx, err } + case '>': + idx, err = st.Next() + if err != nil { + return sym, idx, err + } + case '<': + idx, err = st.Previous() + if err != nil { + return sym, idx, err + } + default: + sym = string(target) + st.Down(sym) + idx = 0 } - _ = idx - return sym, nil -} - -func ApplyInput(inputString string, st *state.State, ctx context.Context) (string, error) { - input := []byte(inputString) - if ctrlInputRegex.Match(input) { - return applyControlInput(input, st, ctx) - } - - err := CheckInput(input) - if err != nil { - return "", err - } - st.Down(inputString) - return inputString, nil + return sym, idx, nil } diff --git a/go/vm/runner.go b/go/vm/runner.go index c8b7ae8..9db606a 100644 --- a/go/vm/runner.go +++ b/go/vm/runner.go @@ -212,7 +212,9 @@ func RunInCmp(b []byte, st *state.State, rs resource.Resource, ctx context.Conte log.Printf("input match for '%s'", input) _, err = st.SetFlag(state.FLAG_INMATCH) - st.Down(target) + + sym, _, err = applyTarget([]byte(target), st, ctx) + code, err := rs.GetCode(target) if err != nil { return b, err