input.go (3595B)
1 package vm 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "regexp" 8 9 "git.defalsify.org/vise.git/cache" 10 "git.defalsify.org/vise.git/state" 11 ) 12 13 var ( 14 inputRegexStr = "^[a-zA-Z0-9].*$" 15 inputRegex = regexp.MustCompile(inputRegexStr) 16 ctrlRegexStr = "^[><_^.]$" 17 ctrlRegex = regexp.MustCompile(ctrlRegexStr) 18 symRegexStr = "^[a-zA-Z0-9][a-zA-Z0-9_]+$" 19 symRegex = regexp.MustCompile(symRegexStr) 20 21 ) 22 23 // InvalidInputError indicates client input that was unhandled by the bytecode (INCMP fallthrough) 24 type InvalidInputError struct { 25 input string 26 } 27 28 // NewInvalidInputError creates a new InvalidInputError 29 func NewInvalidInputError(input string) error { 30 return InvalidInputError{input} 31 } 32 33 // Error implements error interface. 34 func(e InvalidInputError) Error() string { 35 return fmt.Sprintf("invalid input: '%s'", e.input) 36 } 37 38 // CheckInput validates the given byte string as client input. 39 func ValidInput(input []byte) error { 40 if !inputRegex.Match(input) { 41 return fmt.Errorf("Input '%s' does not match input format /%s/", input, inputRegexStr) 42 } 43 return nil 44 } 45 46 // control characters for relative navigation. 47 func validControl(input []byte) error { 48 if !ctrlRegex.Match(input) { 49 return fmt.Errorf("Input '%s' does not match 'control' format /%s/", input, ctrlRegexStr) 50 } 51 return nil 52 } 53 54 // CheckSym validates the given byte string as a node symbol. 55 func ValidSym(input []byte) error { 56 if bytes.Equal(input, []byte("_catch")) { 57 return nil 58 } 59 if !symRegex.Match(input) { 60 return fmt.Errorf("Input '%s' does not match 'sym' format /%s/", input, symRegexStr) 61 } 62 return nil 63 } 64 65 // false if target is not valid 66 func valid(target []byte) bool { 67 var ok bool 68 if len(target) == 0 { 69 return false 70 } 71 72 err := ValidSym(target) 73 if err == nil { 74 ok = true 75 } 76 77 if !ok { 78 err = validControl(target) 79 if err == nil { 80 ok = true 81 } 82 } 83 return ok 84 } 85 86 // CheckTarget tests whether the navigation state transition is available in the current state. 87 // 88 // Fails if target is formally invalid, or if navigation is unavailable. 89 func CheckTarget(target []byte, st *state.State) (bool, error) { 90 ok := valid(target) 91 if !ok { 92 return false, fmt.Errorf("invalid target: %x", target) 93 } 94 95 switch target[0] { 96 case '_': 97 topOk, err := st.Top() 98 if err != nil { 99 return false, err 100 } 101 return topOk, nil 102 case '<': 103 _, prevOk := st.Sides() 104 return prevOk, nil 105 case '>': 106 nextOk, _ := st.Sides() 107 return nextOk, nil 108 } 109 return true, nil 110 } 111 112 // route parsed target symbol to navigation state change method, 113 func applyTarget(target []byte, st *state.State, ca cache.Memory, ctx context.Context) (string, uint16, error) { 114 var err error 115 sym, idx := st.Where() 116 117 ok := valid(target) 118 if !ok { 119 return sym, idx, fmt.Errorf("invalid input: %s", target) 120 } 121 122 switch string(target) { 123 case "_": 124 sym, err = st.Up() 125 if err != nil { 126 return sym, idx, err 127 } 128 err = ca.Pop() 129 if err != nil { 130 return sym, idx, err 131 } 132 133 case ">": 134 idx, err = st.Next() 135 if err != nil { 136 return sym, idx, err 137 } 138 case "<": 139 idx, err = st.Previous() 140 if err != nil { 141 return sym, idx, err 142 } 143 case "^": 144 notTop := true 145 for notTop { 146 notTop, err := st.Top() 147 if notTop { 148 break 149 } 150 sym, err = st.Up() 151 if err != nil { 152 return sym, idx, err 153 } 154 err = ca.Pop() 155 if err != nil { 156 return sym, idx, err 157 } 158 } 159 case ".": 160 st.Same() 161 location, idx := st.Where() 162 return location, idx, nil 163 default: 164 sym = string(target) 165 err := st.Down(sym) 166 if err != nil { 167 return sym, idx, err 168 } 169 err = ca.Push() 170 if err != nil { 171 return sym, idx, err 172 } 173 idx = 0 174 } 175 return sym, idx, nil 176 }