Files
services/db/handler/parse.go
Janos Dobronszki 7157db9aaa DB service (#132)
2021-06-02 15:29:52 +01:00

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
}