2020-03-14 18:47:38 +03:00
|
|
|
package query
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
)
|
|
|
|
|
|
|
|
type token struct {
|
|
|
|
qualifier string
|
|
|
|
value string
|
|
|
|
}
|
|
|
|
|
|
|
|
// tokenize parse and break a input into tokens ready to be
|
|
|
|
// interpreted later by a parser to get the semantic.
|
|
|
|
func tokenize(query string) ([]token, error) {
|
2020-03-28 21:22:27 +03:00
|
|
|
fields, err := splitQuery(query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-03-14 18:47:38 +03:00
|
|
|
|
|
|
|
var tokens []token
|
|
|
|
for _, field := range fields {
|
|
|
|
split := strings.Split(field, ":")
|
|
|
|
if len(split) != 2 {
|
|
|
|
return nil, fmt.Errorf("can't tokenize \"%s\"", field)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(split[0]) == 0 {
|
|
|
|
return nil, fmt.Errorf("can't tokenize \"%s\": empty qualifier", field)
|
|
|
|
}
|
|
|
|
if len(split[1]) == 0 {
|
|
|
|
return nil, fmt.Errorf("empty value for qualifier \"%s\"", split[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
tokens = append(tokens, token{
|
|
|
|
qualifier: split[0],
|
|
|
|
value: removeQuote(split[1]),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return tokens, nil
|
|
|
|
}
|
|
|
|
|
2020-03-28 21:22:27 +03:00
|
|
|
func splitQuery(query string) ([]string, error) {
|
2020-03-14 18:47:38 +03:00
|
|
|
lastQuote := rune(0)
|
2020-03-28 21:22:27 +03:00
|
|
|
inQuote := false
|
|
|
|
|
|
|
|
isToken := func(r rune) bool {
|
2020-03-14 18:47:38 +03:00
|
|
|
switch {
|
2020-03-28 21:22:27 +03:00
|
|
|
case !inQuote && isQuote(r):
|
|
|
|
lastQuote = r
|
|
|
|
inQuote = true
|
|
|
|
return true
|
|
|
|
case inQuote && r == lastQuote:
|
2020-03-14 18:47:38 +03:00
|
|
|
lastQuote = rune(0)
|
2020-03-28 21:22:27 +03:00
|
|
|
inQuote = false
|
|
|
|
return true
|
|
|
|
case inQuote:
|
|
|
|
return true
|
2020-03-14 18:47:38 +03:00
|
|
|
default:
|
2020-03-28 21:22:27 +03:00
|
|
|
return !unicode.IsSpace(r)
|
2020-03-14 18:47:38 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-28 21:22:27 +03:00
|
|
|
var result []string
|
|
|
|
var token strings.Builder
|
|
|
|
for _, r := range query {
|
|
|
|
if isToken(r) {
|
|
|
|
token.WriteRune(r)
|
|
|
|
} else {
|
|
|
|
if token.Len() > 0 {
|
|
|
|
result = append(result, token.String())
|
|
|
|
token.Reset()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if inQuote {
|
|
|
|
return nil, fmt.Errorf("unmatched quote")
|
|
|
|
}
|
|
|
|
|
|
|
|
if token.Len() > 0 {
|
|
|
|
result = append(result, token.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func isQuote(r rune) bool {
|
|
|
|
return r == '"' || r == '\''
|
2020-03-14 18:47:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func removeQuote(field string) string {
|
2020-03-28 21:22:27 +03:00
|
|
|
runes := []rune(field)
|
|
|
|
if len(runes) >= 2 {
|
|
|
|
r1 := runes[0]
|
|
|
|
r2 := runes[len(runes)-1]
|
|
|
|
|
|
|
|
if r1 == r2 && isQuote(r1) {
|
|
|
|
return string(runes[1 : len(runes)-1])
|
2020-03-14 18:47:38 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return field
|
|
|
|
}
|