go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

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 }