commit 8b1f91e8599c08b83911adcc278d0d19a8f42fba
parent 6221e1dce2fb16c9518e452ab9d3ad027c8047eb
Author: lash <dev@holbrook.no>
Date: Sat, 8 Apr 2023 08:54:55 +0100
Factor out target sym navigation handling
Diffstat:
4 files changed, 93 insertions(+), 24 deletions(-)
diff --git 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
@@ -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
@@ -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] {
- case '_':
- sym, err = st.Up()
- if err != nil {
- return sym, err
+
+ err = CheckInput(target)
+ if err == nil {
+ valid = true
+ }
+
+ if !valid {
+ err = CheckSym(target)
+ if err == nil {
+ valid = true
}
}
- _ = 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)
+ if !valid {
+ err = checkControl(target)
+ if err == nil {
+ valid = true
+ }
}
- err := CheckInput(input)
- if err != nil {
- return "", err
+ switch target[0] {
+ case '_':
+ sym, err = st.Up()
+ if err != nil {
+ 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
}
- st.Down(inputString)
- return inputString, nil
+ return sym, idx, nil
}
diff --git 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