158 lines
3.0 KiB
Go
158 lines
3.0 KiB
Go
package vm
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
|
|
"git.defalsify.org/vise/cache"
|
|
"git.defalsify.org/vise/state"
|
|
)
|
|
|
|
var (
|
|
inputRegexStr = "^[a-zA-Z0-9].*$"
|
|
inputRegex = regexp.MustCompile(inputRegexStr)
|
|
ctrlRegexStr = "^[><_^.]$"
|
|
ctrlRegex = regexp.MustCompile(ctrlRegexStr)
|
|
symRegexStr = "^[a-zA-Z0-9][a-zA-Z0-9_]+$"
|
|
symRegex = regexp.MustCompile(symRegexStr)
|
|
|
|
)
|
|
|
|
// CheckInput validates the given byte string as client input.
|
|
func ValidInput(input []byte) error {
|
|
if !inputRegex.Match(input) {
|
|
return fmt.Errorf("Input '%s' does not match input format /%s/", input, inputRegexStr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// control characters for relative navigation.
|
|
func validControl(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 ValidSym(input []byte) error {
|
|
if !symRegex.Match(input) {
|
|
return fmt.Errorf("Input '%s' does not match 'sym' format /%s/", input, symRegexStr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// false if target is not valid
|
|
func valid(target []byte) bool {
|
|
var ok bool
|
|
if len(target) == 0 {
|
|
return false
|
|
}
|
|
|
|
err := ValidSym(target)
|
|
if err == nil {
|
|
ok = true
|
|
}
|
|
|
|
if !ok {
|
|
err = validControl(target)
|
|
if err == nil {
|
|
ok = true
|
|
}
|
|
}
|
|
return ok
|
|
}
|
|
|
|
// CheckTarget tests whether the navigation state transition is available in the current state.
|
|
//
|
|
// Fails if target is formally invalid, or if navigation is unavailable.
|
|
func CheckTarget(target []byte, st *state.State) (bool, error) {
|
|
ok := valid(target)
|
|
if !ok {
|
|
return false, fmt.Errorf("invalid target: %x", target)
|
|
}
|
|
|
|
switch target[0] {
|
|
case '_':
|
|
topOk, err := st.Top()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return topOk, nil
|
|
case '<':
|
|
_, prevOk := st.Sides()
|
|
return prevOk, nil
|
|
case '>':
|
|
nextOk, _ := st.Sides()
|
|
return nextOk, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// route parsed target symbol to navigation state change method,
|
|
func applyTarget(target []byte, st *state.State, ca cache.Memory, ctx context.Context) (string, uint16, error) {
|
|
var err error
|
|
sym, idx := st.Where()
|
|
|
|
ok := valid(target)
|
|
if !ok {
|
|
return sym, idx, fmt.Errorf("invalid input: %x", target)
|
|
}
|
|
|
|
switch target[0] {
|
|
case '_':
|
|
sym, err = st.Up()
|
|
if err != nil {
|
|
return sym, idx, err
|
|
}
|
|
err = ca.Pop()
|
|
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
|
|
}
|
|
case '^':
|
|
notTop := true
|
|
for notTop {
|
|
notTop, err := st.Top()
|
|
if notTop {
|
|
break
|
|
}
|
|
sym, err = st.Up()
|
|
if err != nil {
|
|
return sym, idx, err
|
|
}
|
|
err = ca.Pop()
|
|
if err != nil {
|
|
return sym, idx, err
|
|
}
|
|
}
|
|
case '.':
|
|
st.Same()
|
|
location, idx := st.Where()
|
|
return location, idx, nil
|
|
default:
|
|
sym = string(target)
|
|
err := st.Down(sym)
|
|
if err != nil {
|
|
return sym, idx, err
|
|
}
|
|
err = ca.Push()
|
|
if err != nil {
|
|
return sym, idx, err
|
|
}
|
|
idx = 0
|
|
}
|
|
return sym, idx, nil
|
|
}
|