package cpe //go:generate stringer -linecomment -type=tokenType import ( "errors" "fmt" ) // token type type tokenType byte const ( anyToken tokenType = iota // any naToken // na valToken // val ) // token type token struct { Type tokenType // token type Val string // token value } // parse buffer into token. func newToken(val []byte) token { if len(val) > 0 { switch val[0] { case '*': return token { Type: anyToken } case '-': return token { Type: naToken } default: return token { Type: valToken, Val: string(val) } } } else { // empty value return token { Type: valToken } } } // unterminated escape error var unterminatedEsc = errors.New("unterminated escape at end of buffer") // Parse buffer into list of tokens. func tokenize(buf []byte) ([]token, error) { // build result var r []token // current token and escape state var curr []byte esc := false // build result for _, b := range(buf) { if esc { switch b { // valid escaped characters case '\\', '*', '-', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '+', ',', '/', ':', ';', '<', '=', '>', '@', '[', ']', '^', '`', '{', '|', '}', '~': curr = append(curr, b) esc = false default: return r, fmt.Errorf("invalid escape byte: 0x%02x", b) } } else { switch b { case '\\': esc = true case ':': // push token, clear buffer r = append(r, newToken(curr)) curr = nil case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.', '_', '*', '?': curr = append(curr, b) default: return r, fmt.Errorf("invalid byte: 0x%02x", b) } } } // check for unterminated escape if esc { return r, unterminatedEsc } if len(curr) > 0 { // push token, clear buffer r = append(r, newToken(curr)) curr = nil } // return success return r, nil }