mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
135 lines
2.8 KiB
Go
135 lines
2.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/crufter/lexer"
|
|
)
|
|
|
|
var quoteEscape = fmt.Sprint(0x10FFFF)
|
|
|
|
const (
|
|
itemIgnore = iota
|
|
|
|
// nouns
|
|
itemAnd
|
|
itemInt
|
|
itemFieldName
|
|
itemString
|
|
itemBoolTrue
|
|
itemBoolFalse
|
|
|
|
// ops
|
|
itemEquals
|
|
itemNotEquals
|
|
itemLessThan
|
|
itemGreaterThan
|
|
itemLessThanEquals
|
|
itemGreaterThanEquals
|
|
)
|
|
|
|
var opToString = map[int]string{
|
|
itemEquals: "==",
|
|
itemNotEquals: "!=",
|
|
itemLessThan: "<",
|
|
itemGreaterThan: ">",
|
|
itemLessThanEquals: "<=",
|
|
itemGreaterThanEquals: ">=",
|
|
}
|
|
|
|
var expressions = []lexer.TokenExpr{
|
|
{`[ ]+`, itemIgnore}, // Whitespace
|
|
{`==`, itemEquals},
|
|
{`!=`, itemNotEquals},
|
|
{`false`, itemBoolFalse},
|
|
{`true`, itemBoolTrue},
|
|
{`and`, itemAnd},
|
|
{`<=`, itemLessThanEquals},
|
|
{`>=`, itemGreaterThanEquals},
|
|
{`<`, itemLessThan},
|
|
{`>`, itemGreaterThan},
|
|
{`[0-9]+`, itemInt},
|
|
{`"(?:[^"\\]|\\.)*"`, itemString},
|
|
{`[\<\>\!\=\+\-\|\&\*\/A-Za-z][A-Za-z0-9_]*`, itemFieldName},
|
|
}
|
|
|
|
type Query struct {
|
|
Field string
|
|
Op int
|
|
Value interface{}
|
|
}
|
|
|
|
func Parse(q string) ([]Query, error) {
|
|
if strings.Contains(q, quoteEscape) {
|
|
return nil, errors.New("query contains illegal max rune")
|
|
}
|
|
q = strings.Replace(q, `""`, quoteEscape, -1)
|
|
tokens, err := lexer.Lex(q, expressions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
queries := []Query{}
|
|
current := Query{}
|
|
for i, token := range tokens {
|
|
// and tokens should trigger a query
|
|
// save and reset
|
|
if token.Typ == itemAnd {
|
|
queries = append(queries, current)
|
|
current = Query{}
|
|
continue
|
|
}
|
|
|
|
// is an op
|
|
if token.Typ >= itemEquals {
|
|
current.Op = token.Typ
|
|
continue
|
|
}
|
|
|
|
// is a value
|
|
switch token.Typ {
|
|
case itemFieldName:
|
|
current.Field = token.Text
|
|
case itemString:
|
|
switch current.Op {
|
|
case itemEquals, itemNotEquals:
|
|
default:
|
|
return nil, fmt.Errorf("operator '%v' can't be used with strings", opToString[token.Typ])
|
|
}
|
|
|
|
if len(token.Text) < 2 {
|
|
return nil, fmt.Errorf("string literal too short: '%v'", token.Text)
|
|
}
|
|
current.Value = strings.Replace(token.Text[1:len(token.Text)-1], quoteEscape, `"`, -1)
|
|
case itemBoolTrue:
|
|
switch current.Op {
|
|
case itemEquals, itemNotEquals:
|
|
default:
|
|
return nil, fmt.Errorf("operator '%v' can't be used with bools", opToString[token.Typ])
|
|
}
|
|
current.Value = true
|
|
case itemBoolFalse:
|
|
switch current.Op {
|
|
case itemEquals, itemNotEquals:
|
|
default:
|
|
return nil, fmt.Errorf("operator '%v' can't be used with bools", opToString[token.Typ])
|
|
}
|
|
current.Value = false
|
|
case itemInt:
|
|
num, err := strconv.ParseInt(token.Text, 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
current.Value = num
|
|
}
|
|
|
|
// if we are at last position, save last query
|
|
if i == len(tokens)-1 {
|
|
queries = append(queries, current)
|
|
}
|
|
}
|
|
return queries, nil
|
|
}
|