input.go (4165B)
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 var ( 23 preInputRegexStr = make(map[int]*regexp.Regexp) 24 ) 25 26 // InvalidInputError indicates client input that was unhandled by the bytecode (INCMP fallthrough) 27 type InvalidInputError struct { 28 input string 29 } 30 31 // NewInvalidInputError creates a new InvalidInputError 32 func NewInvalidInputError(input string) error { 33 return InvalidInputError{input} 34 } 35 36 // Error implements the Error interface. 37 func(e InvalidInputError) Error() string { 38 return fmt.Sprintf("invalid input: '%s'", e.input) 39 } 40 41 func RegisterInputValidator(k int, v string) error { 42 var ok bool 43 var err error 44 45 _, ok = preInputRegexStr[k] 46 if ok { 47 return fmt.Errorf("input checker with key '%d' already registered", k) 48 } 49 preInputRegexStr[k], err = regexp.Compile(v) 50 return err 51 } 52 53 // CheckInput validates the given byte string as client input. 54 func ValidInput(input []byte) (int, error) { 55 if inputRegex.Match(input) { 56 return -1, nil 57 } 58 for k, v := range preInputRegexStr { 59 logg.Tracef("custom check input", "i", k, "regex", v) 60 if v.Match(input) { 61 logg.Debugf("match custom check input", "i", k, "regex", v, "input", input) 62 return k, nil 63 } 64 } 65 return -2, fmt.Errorf("Input '%s' does not match any input format (default: /%s/)", input, inputRegexStr) 66 } 67 68 // control characters for relative navigation. 69 func validControl(input []byte) error { 70 if !ctrlRegex.Match(input) { 71 return fmt.Errorf("Input '%s' does not match 'control' format /%s/", input, ctrlRegexStr) 72 } 73 return nil 74 } 75 76 // CheckSym validates the given byte string as a node symbol. 77 func ValidSym(input []byte) error { 78 if bytes.Equal(input, []byte("_catch")) { 79 return nil 80 } 81 if !symRegex.Match(input) { 82 return fmt.Errorf("Input '%s' does not match 'sym' format /%s/", input, symRegexStr) 83 } 84 return nil 85 } 86 87 // false if target is not valid 88 func valid(target []byte) bool { 89 var ok bool 90 if len(target) == 0 { 91 return false 92 } 93 94 err := ValidSym(target) 95 if err == nil { 96 ok = true 97 } 98 99 if !ok { 100 err = validControl(target) 101 if err == nil { 102 ok = true 103 } 104 } 105 return ok 106 } 107 108 // CheckTarget tests whether the navigation state transition is available in the current state. 109 // 110 // Fails if target is formally invalid, or if navigation is unavailable. 111 func CheckTarget(target []byte, st *state.State) (bool, error) { 112 ok := valid(target) 113 if !ok { 114 return false, fmt.Errorf("invalid target: %x", target) 115 } 116 117 switch target[0] { 118 case '_': 119 topOk, err := st.Top() 120 if err != nil { 121 return false, err 122 } 123 return topOk, nil 124 case '<': 125 _, prevOk := st.Sides() 126 return prevOk, nil 127 case '>': 128 nextOk, _ := st.Sides() 129 return nextOk, nil 130 } 131 return true, nil 132 } 133 134 // route parsed target symbol to navigation state change method, 135 func applyTarget(target []byte, st *state.State, ca cache.Memory, ctx context.Context) (string, uint16, error) { 136 var err error 137 sym, idx := st.Where() 138 139 ok := valid(target) 140 if !ok { 141 return sym, idx, fmt.Errorf("invalid input: %s", target) 142 } 143 144 switch string(target) { 145 case "_": 146 sym, err = st.Up() 147 if err != nil { 148 return sym, idx, err 149 } 150 err = ca.Pop() 151 if err != nil { 152 return sym, idx, err 153 } 154 155 case ">": 156 idx, err = st.Next() 157 if err != nil { 158 return sym, idx, err 159 } 160 case "<": 161 idx, err = st.Previous() 162 if err != nil { 163 return sym, idx, err 164 } 165 case "^": 166 notTop := true 167 for notTop { 168 notTop, err := st.Top() 169 if notTop { 170 break 171 } 172 sym, err = st.Up() 173 if err != nil { 174 return sym, idx, err 175 } 176 err = ca.Pop() 177 if err != nil { 178 return sym, idx, err 179 } 180 } 181 case ".": 182 st.Same() 183 location, idx := st.Where() 184 return location, idx, nil 185 default: 186 sym = string(target) 187 err := st.Down(sym) 188 if err != nil { 189 return sym, idx, err 190 } 191 err = ca.Push() 192 if err != nil { 193 return sym, idx, err 194 } 195 idx = 0 196 } 197 return sym, idx, nil 198 }