mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-09-14 03:25:52 -05:00
database: Support SAKE filter expression
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
package serverbrowser
|
||||
|
||||
import (
|
||||
"wwfc/filter"
|
||||
"wwfc/logging"
|
||||
"wwfc/serverbrowser/filter"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
// Modified from github.com/zdebeer99/goexpression
|
||||
package filter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type expression struct {
|
||||
ast *TreeNode
|
||||
context map[string]string
|
||||
queryGame string
|
||||
}
|
||||
|
||||
// Bug(zdebeer): functions is eval from right to left instead from left to right.
|
||||
func Eval(basenode *TreeNode, context map[string]string, queryGame string) (value int64, err error) {
|
||||
defer func() {
|
||||
if str := recover(); str != nil {
|
||||
value = 0
|
||||
err = errors.New(str.(string))
|
||||
}
|
||||
}()
|
||||
|
||||
this := &expression{basenode, context, queryGame}
|
||||
return this.eval(basenode), nil
|
||||
}
|
||||
|
||||
func (this *expression) eval(basenode *TreeNode) int64 {
|
||||
for _, node := range basenode.items {
|
||||
switch node.Value.Category() {
|
||||
case CatFunction:
|
||||
return this.switchFunction(node)
|
||||
|
||||
case CatValue:
|
||||
return this.getNumber(node)
|
||||
|
||||
case CatOther:
|
||||
return this.switchOther(node)
|
||||
}
|
||||
}
|
||||
panic("eval failed")
|
||||
}
|
||||
|
||||
func (this *expression) switchOther(node *TreeNode) int64 {
|
||||
switch v1 := node.Value.(type) {
|
||||
case *GroupToken:
|
||||
if v1.GroupType == "()" {
|
||||
return this.eval(node)
|
||||
}
|
||||
}
|
||||
panic("invalid node " + node.String())
|
||||
}
|
||||
|
||||
func (this *expression) switchFunction(node *TreeNode) int64 {
|
||||
val1 := node.Value.(*OperatorToken)
|
||||
switch strings.ToLower(val1.Operator) {
|
||||
case "=":
|
||||
return this.evalEquals(node.Items())
|
||||
case "==":
|
||||
return this.evalEquals(node.Items())
|
||||
case "!=":
|
||||
return this.evalNotEquals(node.Items())
|
||||
|
||||
case ">":
|
||||
return this.evalMathOperator(this.evalMathGreater, node.Items())
|
||||
case "<":
|
||||
return this.evalMathOperator(this.evalMathLess, node.Items())
|
||||
case ">=":
|
||||
return this.evalMathOperator(this.evalMathGreaterOrEqual, node.Items())
|
||||
case "<=":
|
||||
return this.evalMathOperator(this.evalMathLessOrEqual, node.Items())
|
||||
case "+":
|
||||
return this.evalMathOperator(this.evalMathPlus, node.Items())
|
||||
case "-":
|
||||
return this.evalMathOperator(this.evalMathMinus, node.Items())
|
||||
case "&":
|
||||
return this.evalMathOperator(this.evalMathAnd, node.Items())
|
||||
case "|":
|
||||
return this.evalMathOperator(this.evalMathOr, node.Items())
|
||||
case "^":
|
||||
return this.evalMathOperator(this.evalMathXor, node.Items())
|
||||
case "<<":
|
||||
return this.evalMathOperator(this.evalMathLShift, node.Items())
|
||||
case ">>":
|
||||
return this.evalMathOperator(this.evalMathRShift, node.Items())
|
||||
|
||||
case "and":
|
||||
return this.evalAnd(node.Items())
|
||||
case "or":
|
||||
return this.evalOr(node.Items())
|
||||
case "&&":
|
||||
return this.evalAnd(node.Items())
|
||||
case "||":
|
||||
return this.evalOr(node.Items())
|
||||
|
||||
case "like":
|
||||
return this.evalLike(node.Items())
|
||||
|
||||
default:
|
||||
panic("function not supported: " + val1.Operator)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *expression) getString(node *TreeNode) string {
|
||||
switch v := node.Value.(type) {
|
||||
case *NumberToken:
|
||||
return strconv.FormatInt(v.Value, 10)
|
||||
case *IdentityToken:
|
||||
return this.getValue(v)
|
||||
case *OperatorToken:
|
||||
return strconv.FormatInt(this.switchFunction(node), 10)
|
||||
case *GroupToken:
|
||||
if v.GroupType == "()" {
|
||||
return strconv.FormatInt(this.eval(node), 10)
|
||||
}
|
||||
panic("unexpected grouping type: " + node.String())
|
||||
case *TextToken:
|
||||
return node.Value.(*TextToken).Text
|
||||
|
||||
default:
|
||||
panic("unexpected value: " + node.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (this *expression) evalEquals(args []*TreeNode) int64 {
|
||||
cnt := len(args)
|
||||
switch {
|
||||
case cnt < 2:
|
||||
panic("operator missing arguments")
|
||||
case cnt == 2:
|
||||
if n, ok := args[0].Value.(*IdentityToken); ok {
|
||||
if n.Name == "rk" && this.queryGame == "mariokartwii" {
|
||||
return this.evalEqualsRK(this.getString(args[1]))
|
||||
}
|
||||
}
|
||||
|
||||
if this.getString(args[0]) == this.getString(args[1]) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
arg := this.getString(args[0])
|
||||
for i := 1; i < cnt; i++ {
|
||||
if arg != this.getString(args[i]) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Operator override
|
||||
func (this *expression) evalEqualsRK(value string) int64 {
|
||||
rk := this.context["rk"]
|
||||
// Check and remove regional searches due to the limited player count
|
||||
// China (ID 6) gets a pass because it was never released
|
||||
if len(rk) == 4 && (strings.HasPrefix(rk, "vs_") || strings.HasPrefix(rk, "bt_")) && rk[3] >= '0' && rk[3] < '6' {
|
||||
rk = rk[:2]
|
||||
}
|
||||
|
||||
if len(value) == 4 && (strings.HasPrefix(value, "vs_") || strings.HasPrefix(value, "bt_")) && value[3] >= '0' && value[3] < '6' {
|
||||
value = value[:2]
|
||||
}
|
||||
|
||||
if rk == value {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (this *expression) evalNotEquals(args []*TreeNode) int64 {
|
||||
cnt := len(args)
|
||||
switch {
|
||||
case cnt < 2:
|
||||
panic("operator missing arguments")
|
||||
case cnt == 2:
|
||||
if this.getString(args[0]) != this.getString(args[1]) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
arg := this.getString(args[0])
|
||||
for i := 1; i < cnt; i++ {
|
||||
if arg == this.getString(args[i]) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func (this *expression) evalAnd(args []*TreeNode) int64 {
|
||||
cnt := len(args)
|
||||
if cnt < 2 {
|
||||
panic("operator missing arguments")
|
||||
}
|
||||
|
||||
for i := 0; i < cnt; i++ {
|
||||
if this.getString(args[i]) == "0" {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (this *expression) evalOr(args []*TreeNode) int64 {
|
||||
cnt := len(args)
|
||||
if cnt < 2 {
|
||||
panic("operator missing arguments")
|
||||
}
|
||||
|
||||
for i := 0; i < cnt; i++ {
|
||||
if this.getString(args[i]) != "0" {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (this *expression) getNumber(node *TreeNode) int64 {
|
||||
switch v := node.Value.(type) {
|
||||
case *NumberToken:
|
||||
return v.Value
|
||||
case *IdentityToken:
|
||||
r1 := this.getValue(v)
|
||||
return this.toInt64(r1)
|
||||
case *OperatorToken:
|
||||
return this.switchFunction(node)
|
||||
case *GroupToken:
|
||||
if v.GroupType == "()" {
|
||||
return this.eval(node)
|
||||
}
|
||||
panic("unexpected grouping type: " + node.String())
|
||||
case *TextToken:
|
||||
return this.toInt64(node.Value.(*TextToken).Text)
|
||||
|
||||
default:
|
||||
panic("unexpected value: " + node.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (this *expression) evalMathOperator(fn func(int64, int64) int64, args []*TreeNode) int64 {
|
||||
cnt := len(args)
|
||||
switch {
|
||||
case cnt < 2:
|
||||
panic("operator missing arguments")
|
||||
case cnt == 2:
|
||||
if n, ok := args[0].Value.(*IdentityToken); ok {
|
||||
// Remove VR search due to the limited player count
|
||||
if (n.Name == "ev" || n.Name == "eb") && this.queryGame == "mariokartwii" {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return fn(this.getNumber(args[0]), this.getNumber(args[1]))
|
||||
default:
|
||||
answ := fn(this.getNumber(args[0]), this.getNumber(args[1]))
|
||||
for i := 2; i < cnt; i++ {
|
||||
answ = fn(answ, this.getNumber(args[i]))
|
||||
}
|
||||
return answ
|
||||
}
|
||||
}
|
||||
|
||||
func (this *expression) evalMathPlus(val1, val2 int64) int64 {
|
||||
return val1 + val2
|
||||
}
|
||||
|
||||
func (this *expression) evalMathMinus(val1, val2 int64) int64 {
|
||||
return val1 - val2
|
||||
}
|
||||
|
||||
func (this *expression) evalMathGreater(val1, val2 int64) int64 {
|
||||
if val1 > val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (this *expression) evalMathLess(val1, val2 int64) int64 {
|
||||
if val1 < val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (this *expression) evalMathGreaterOrEqual(val1, val2 int64) int64 {
|
||||
if val1 >= val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (this *expression) evalMathLessOrEqual(val1, val2 int64) int64 {
|
||||
if val1 <= val2 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (this *expression) evalMathAnd(val1, val2 int64) int64 {
|
||||
return val1 & val2
|
||||
}
|
||||
|
||||
func (this *expression) evalMathOr(val1, val2 int64) int64 {
|
||||
return val1 | val2
|
||||
}
|
||||
|
||||
func (this *expression) evalMathXor(val1, val2 int64) int64 {
|
||||
return val1 ^ val2
|
||||
}
|
||||
|
||||
func (this *expression) evalMathLShift(val1, val2 int64) int64 {
|
||||
return val1 << val2
|
||||
}
|
||||
|
||||
func (this *expression) evalMathRShift(val1, val2 int64) int64 {
|
||||
return val1 >> val2
|
||||
}
|
||||
|
||||
func (this *expression) evalLike(args []*TreeNode) int64 {
|
||||
cnt := len(args)
|
||||
switch {
|
||||
case cnt < 2:
|
||||
panic("operator missing arguments")
|
||||
case cnt == 2:
|
||||
return this.evalLikeSingle(args[0], args[1])
|
||||
default:
|
||||
panic("operator like does not support multiple arguments")
|
||||
}
|
||||
}
|
||||
|
||||
func (this *expression) evalLikeSingle(arg1, arg2 *TreeNode) int64 {
|
||||
val1 := this.getString(arg1)
|
||||
val2 := this.getString(arg2)
|
||||
|
||||
allowedCharacters := `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_%\`
|
||||
|
||||
regexString := "^"
|
||||
|
||||
// Convert SQL like pattern to regex
|
||||
for i, c := range val2 {
|
||||
if strings.IndexRune(allowedCharacters, c) < 0 {
|
||||
panic("invalid character in like pattern: " + string(c))
|
||||
}
|
||||
|
||||
if i != 0 && val2[i-1] == '\\' {
|
||||
if c == '\\' {
|
||||
regexString += "\\\\"
|
||||
continue
|
||||
}
|
||||
|
||||
regexString += string(c)
|
||||
continue
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '%':
|
||||
regexString += ".*"
|
||||
|
||||
case '_':
|
||||
regexString += "."
|
||||
|
||||
case '\\':
|
||||
// Do nothing
|
||||
|
||||
default:
|
||||
regexString += string(c)
|
||||
}
|
||||
}
|
||||
|
||||
regexString += "$"
|
||||
|
||||
if matched, err := regexp.MatchString(regexString, val1); err != nil {
|
||||
panic(err)
|
||||
} else if matched {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Get a value from the context.
|
||||
func (this *expression) getValue(token *IdentityToken) string {
|
||||
return this.context[token.Name]
|
||||
}
|
||||
|
||||
func (this *expression) toInt64(value string) int64 {
|
||||
val, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
// Modified from github.com/zdebeer99/goexpression
|
||||
package filter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type stateFn func(*parser) stateFn
|
||||
|
||||
type parser struct {
|
||||
scan *Scanner
|
||||
root *TreeNode
|
||||
curr *TreeNode
|
||||
err error
|
||||
state stateFn
|
||||
}
|
||||
|
||||
func Parse(input string) (root *TreeNode, err error) {
|
||||
defer func() {
|
||||
if str := recover(); str != nil {
|
||||
root = nil
|
||||
err = errors.New(str.(string))
|
||||
}
|
||||
}()
|
||||
|
||||
root = NewTreeNode(NewEmptyToken())
|
||||
parse := &parser{NewScanner(input), root, root, nil, nil}
|
||||
parse.parse()
|
||||
err = parse.err
|
||||
return root, err
|
||||
}
|
||||
|
||||
func (this *parser) getCurr() Token {
|
||||
if this.curr != nil {
|
||||
return this.curr.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *parser) parse() {
|
||||
this.pumpExpression()
|
||||
}
|
||||
|
||||
func (this *parser) add(token Token) *TreeNode {
|
||||
return this.curr.Add(token)
|
||||
}
|
||||
|
||||
func (this *parser) push(token Token) *TreeNode {
|
||||
return this.curr.Push(token)
|
||||
}
|
||||
|
||||
func (this *parser) lastNode() *TreeNode {
|
||||
return this.curr.LastElement()
|
||||
}
|
||||
|
||||
func (this *parser) parentNode() *TreeNode {
|
||||
return this.curr.Parent()
|
||||
}
|
||||
|
||||
func (this *parser) commit() string {
|
||||
return this.scan.Commit()
|
||||
}
|
||||
|
||||
// parseOpenBracket
|
||||
func (this *parser) parseOpenBracket() bool {
|
||||
this.curr = this.add(NewGroupToken("()"))
|
||||
this.commit()
|
||||
return true
|
||||
}
|
||||
|
||||
// parseCloseBracket
|
||||
func (this *parser) parseCloseBracket() stateFn {
|
||||
for {
|
||||
v1, ok := this.curr.Value.(*GroupToken)
|
||||
if ok && v1.GroupType == "()" {
|
||||
this.commit()
|
||||
this.curr = this.curr.Parent()
|
||||
return branchExpressionOperatorPart
|
||||
}
|
||||
if ok && v1.GroupType == "" {
|
||||
//must be a bracket part of a parent loop, exit this sub loop.
|
||||
this.scan.Backup()
|
||||
return nil
|
||||
}
|
||||
if this.curr.Parent() == nil {
|
||||
panic("brackets not closed")
|
||||
}
|
||||
this.curr = this.curr.Parent()
|
||||
}
|
||||
panic("unreachable code")
|
||||
}
|
||||
|
||||
func (this *parser) AcceptOperator() bool {
|
||||
scan := this.scan
|
||||
state := scan.SaveState()
|
||||
for _, op := range operatorList {
|
||||
if scan.Prefix(op) || scan.Prefix(strings.ToUpper(op)) {
|
||||
p := scan.Peek()
|
||||
if unicode.IsLetter(rune(op[0])) && (unicode.IsLetter(p) || unicode.IsNumber(p) || strings.IndexRune(charValidString, p) >= 0) {
|
||||
// this is a prefix of a longer word
|
||||
scan.LoadState(state)
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseOperator
|
||||
func (this *parser) parseOperator() bool {
|
||||
operator := this.commit()
|
||||
lastnode := this.lastNode()
|
||||
onode, ok := this.getCurr().(*OperatorToken)
|
||||
//push excisting operator up in tree structure
|
||||
if ok {
|
||||
//operator is the same current operator ignore
|
||||
if onode.Operator == operator {
|
||||
return true
|
||||
}
|
||||
//change order for */ presedence
|
||||
//fmt.Println(onode, operator, onode.Precedence(operator))
|
||||
if onode.Precedence(operator) > 0 {
|
||||
if lastnode != nil {
|
||||
this.curr = lastnode.Push(NewOperatorToken(operator))
|
||||
return true
|
||||
}
|
||||
}
|
||||
//after */ presedence fallback and continue pushing +- operators from the bottom.
|
||||
if onode.Precedence(operator) < 0 {
|
||||
for {
|
||||
v1, ok := this.curr.Parent().Value.(*OperatorToken)
|
||||
//if ok && strings.Index("+-", v1.Name) >= 0 {
|
||||
if ok && operators.Level(v1.Operator) >= 0 {
|
||||
this.curr = this.curr.Parent()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
//standard operator push
|
||||
this.curr = this.push(NewOperatorToken(operator))
|
||||
return true
|
||||
}
|
||||
//set previous found value as argument of the operator
|
||||
if lastnode != nil {
|
||||
this.curr = lastnode.Push(NewOperatorToken(operator))
|
||||
} else {
|
||||
this.state = nil
|
||||
panic(fmt.Sprintf("expecting a value before operator %q", operator))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// parseLRFunc
|
||||
func (this *parser) parseLRFunc() bool {
|
||||
lrfunc := this.commit()
|
||||
lastnode := this.lastNode()
|
||||
if lastnode != nil {
|
||||
this.curr = lastnode.Push(NewLRFuncToken(lrfunc))
|
||||
} else {
|
||||
this.state = nil
|
||||
panic(fmt.Sprintf("expecting a value before operator %q", lrfunc))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *parser) ParseText() string {
|
||||
scan := this.scan
|
||||
r := scan.Next()
|
||||
if r == '"' || r == '\'' {
|
||||
scan.Ignore()
|
||||
endqoute := r
|
||||
for {
|
||||
r = scan.Next()
|
||||
if r == endqoute {
|
||||
scan.Backup()
|
||||
txt := scan.Commit()
|
||||
scan.Next()
|
||||
scan.Ignore()
|
||||
return txt
|
||||
}
|
||||
if scan.IsEOF() {
|
||||
panic("missing quote and end of text")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// Modified from github.com/zdebeer99/goexpression
|
||||
package filter
|
||||
|
||||
func (this *parser) pumpExpression() {
|
||||
this.state = branchExpressionValuePart
|
||||
for this.state != nil {
|
||||
if this.err != nil {
|
||||
break
|
||||
}
|
||||
this.state = this.state(this)
|
||||
}
|
||||
endo := this.commit()
|
||||
if len(endo) > 0 || !this.scan.IsEOF() {
|
||||
panic("unexpected end of expression '" + endo + "' not parsed")
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
parse expressions
|
||||
[value part][operator part] repeat
|
||||
*/
|
||||
|
||||
func branchExpressionValuePart(this *parser) stateFn {
|
||||
scan := this.scan
|
||||
scan.SkipSpaces()
|
||||
if scan.IsEOF() {
|
||||
return nil
|
||||
}
|
||||
if scan.ScanNumber() {
|
||||
this.add(NewNumberToken(scan.Commit()))
|
||||
return branchExpressionOperatorPart
|
||||
}
|
||||
if scan.ScanWord() {
|
||||
return branchExpressionAfterWord
|
||||
}
|
||||
c := scan.Next()
|
||||
switch c {
|
||||
case '"', '\'':
|
||||
scan.Backup()
|
||||
txt := this.ParseText()
|
||||
this.add(NewTextToken(txt))
|
||||
return branchExpressionOperatorPart
|
||||
case '(':
|
||||
this.parseOpenBracket()
|
||||
return branchExpressionValuePart
|
||||
}
|
||||
|
||||
panic("unexpected token: " + string(c))
|
||||
}
|
||||
|
||||
func branchExpressionAfterWord(this *parser) stateFn {
|
||||
scan := this.scan
|
||||
switch scan.Peek() {
|
||||
case '(':
|
||||
this.curr = this.add(NewFuncToken(scan.Commit()))
|
||||
return branchFunctionArguments
|
||||
}
|
||||
this.add(NewIdentityToken(scan.Commit()))
|
||||
return branchExpressionOperatorPart
|
||||
}
|
||||
|
||||
func branchFunctionArguments(this *parser) stateFn {
|
||||
scan := this.scan
|
||||
r := scan.Next()
|
||||
if r != '(' {
|
||||
panic("expecting '(' before arguments")
|
||||
}
|
||||
ftoken, ok := this.curr.Value.(*FuncToken)
|
||||
if !ok {
|
||||
panic("expecting function token to add arguments to")
|
||||
return nil
|
||||
}
|
||||
state := branchExpressionValuePart
|
||||
currnode := this.curr
|
||||
for {
|
||||
this.curr = NewTreeNode(NewGroupToken(""))
|
||||
for state != nil {
|
||||
state = state(this)
|
||||
}
|
||||
r = scan.Next()
|
||||
switch r {
|
||||
case ' ':
|
||||
scan.Ignore()
|
||||
continue
|
||||
case ',':
|
||||
ftoken.AddArgument(this.curr.Root())
|
||||
state = branchExpressionValuePart
|
||||
scan.Ignore()
|
||||
continue
|
||||
case ')':
|
||||
ftoken.AddArgument(this.curr.Root())
|
||||
this.curr = currnode.parent
|
||||
scan.Ignore()
|
||||
return branchExpressionOperatorPart
|
||||
}
|
||||
this.curr = currnode
|
||||
if scan.IsEOF() {
|
||||
panic("arguments missing end bracket")
|
||||
return nil
|
||||
}
|
||||
panic("invalid char '" + string(r) + "' in arguments")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func branchExpressionOperatorPart(this *parser) stateFn {
|
||||
scan := this.scan
|
||||
scan.SkipSpaces()
|
||||
|
||||
if scan.IsEOF() {
|
||||
return nil
|
||||
}
|
||||
if this.AcceptOperator() {
|
||||
this.parseOperator()
|
||||
return branchExpressionValuePart
|
||||
}
|
||||
if scan.Accept("=") {
|
||||
this.parseLRFunc()
|
||||
this.curr = this.add(NewGroupToken(""))
|
||||
return branchExpressionValuePart
|
||||
}
|
||||
switch scan.Next() {
|
||||
case ')':
|
||||
return this.parseCloseBracket()
|
||||
}
|
||||
scan.Rollback()
|
||||
return nil
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
// Modified from github.com/zdebeer99/goexpression
|
||||
package filter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Pos represents a byte position in the original input text from which
|
||||
// this template was parsed.
|
||||
type Pos int
|
||||
|
||||
func (p Pos) Position() Pos {
|
||||
return p
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
// Scanner, Iterates through a string.
|
||||
type Scanner struct {
|
||||
input string
|
||||
start Pos
|
||||
pos Pos
|
||||
width Pos
|
||||
curr rune
|
||||
prevState ScannerState
|
||||
safebackup bool //insure backup is called only once after next.
|
||||
}
|
||||
|
||||
// NewScanner Creates a New Scanner pointer.
|
||||
func NewScanner(template string) *Scanner {
|
||||
return &Scanner{input: template}
|
||||
}
|
||||
|
||||
func (this *Scanner) StartPosition() int {
|
||||
return int(this.start)
|
||||
}
|
||||
|
||||
func (this *Scanner) SetPosition(pos int) {
|
||||
this.pos = Pos(pos)
|
||||
}
|
||||
|
||||
func (this *Scanner) SetStartPosition(pos int) {
|
||||
this.start = Pos(pos)
|
||||
}
|
||||
|
||||
// Token return the current selected text and move the start position to the current position
|
||||
func (this *Scanner) Commit() string {
|
||||
r1 := this.input[this.start:this.pos]
|
||||
this.start = this.pos
|
||||
this.prevState = this.SaveState()
|
||||
return r1
|
||||
}
|
||||
|
||||
// IsEOF check if the end of the current string has been reached.
|
||||
func (this *Scanner) IsEOF() bool {
|
||||
return int(this.pos) >= len(this.input)
|
||||
}
|
||||
|
||||
func (this *Scanner) Size() int {
|
||||
return len(this.input)
|
||||
}
|
||||
|
||||
func (this *Scanner) MoveStart(pos int) {
|
||||
this.start = this.start + Pos(pos)
|
||||
}
|
||||
|
||||
// Next returns the next rune in the input.
|
||||
func (this *Scanner) Next() rune {
|
||||
this.safebackup = true
|
||||
if this.IsEOF() {
|
||||
this.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(this.input[this.pos:])
|
||||
this.width = Pos(w)
|
||||
this.pos += this.width
|
||||
this.curr = r
|
||||
return r
|
||||
}
|
||||
|
||||
func (this *Scanner) Skip() {
|
||||
this.Next()
|
||||
this.Commit()
|
||||
}
|
||||
|
||||
// Peek returns but does not consume the next rune in the input.
|
||||
func (this *Scanner) Peek() rune {
|
||||
r := this.Next()
|
||||
this.Backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// Backup steps back one rune. Can only be called once per call of next.
|
||||
func (this *Scanner) Backup() {
|
||||
this.pos -= this.width
|
||||
}
|
||||
|
||||
// Rollback move the curr pos back to the start pos.
|
||||
func (this *Scanner) Rollback() {
|
||||
this.LoadState(this.prevState)
|
||||
}
|
||||
|
||||
// Ignore skips over the pending input before this point.
|
||||
func (this *Scanner) Ignore() {
|
||||
this.start = this.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (this *Scanner) Accept(valid string) bool {
|
||||
if strings.IndexRune(valid, this.Next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
this.Backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (this *Scanner) AcceptRun(valid string) (found int) {
|
||||
for strings.IndexRune(valid, this.Next()) >= 0 {
|
||||
found++
|
||||
}
|
||||
this.Backup()
|
||||
return found
|
||||
}
|
||||
|
||||
// runTo consumes a run of runes until an item in the valid set is found.
|
||||
func (this *Scanner) RunTo(valid string) rune {
|
||||
for {
|
||||
r := this.Next()
|
||||
if r == eof {
|
||||
return r
|
||||
}
|
||||
if strings.IndexRune(valid, r) >= 0 {
|
||||
return r
|
||||
}
|
||||
}
|
||||
this.Backup()
|
||||
return eof
|
||||
}
|
||||
|
||||
func (this *Scanner) Prefix(pre string) bool {
|
||||
if strings.HasPrefix(this.input[this.pos:], pre) {
|
||||
this.pos += Pos(len(pre))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *Scanner) SkipSpaces() {
|
||||
for IsSpace(this.Next()) {
|
||||
}
|
||||
this.Backup()
|
||||
this.Ignore()
|
||||
}
|
||||
|
||||
func (this *Scanner) SkipToNewLine() {
|
||||
for {
|
||||
r := this.Next()
|
||||
if this.IsEOF() {
|
||||
break
|
||||
}
|
||||
if r == '\n' {
|
||||
break
|
||||
}
|
||||
}
|
||||
this.Ignore()
|
||||
return
|
||||
}
|
||||
|
||||
// lineNumber reports which line we're on, based on the position of
|
||||
// the previous item returned by nextItem. Doing it this way
|
||||
// means we don't have to worry about peek double counting.
|
||||
func (this *Scanner) LineNumber() int {
|
||||
return 1 + strings.Count(this.input[:this.pos], "\n")
|
||||
}
|
||||
|
||||
type ScannerState struct {
|
||||
start Pos
|
||||
pos Pos
|
||||
width Pos
|
||||
}
|
||||
|
||||
func (this *Scanner) SaveState() ScannerState {
|
||||
return ScannerState{start: this.start, pos: this.pos, width: this.width}
|
||||
}
|
||||
|
||||
func (this *Scanner) LoadState(state ScannerState) {
|
||||
this.start, this.pos, this.width = state.start, state.pos, state.width
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
// Modified from github.com/zdebeer99/goexpression
|
||||
package filter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const charValidString string = "_"
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
func IsSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
func IsNumber(r rune) bool {
|
||||
return unicode.IsDigit(r) || r == '.'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func IsAlphaNumeric(r rune) bool {
|
||||
return unicode.IsLetter(r) || unicode.IsDigit(r) || strings.IndexRune(charValidString, r) >= 0
|
||||
}
|
||||
|
||||
func IsQoute(r rune) bool {
|
||||
return strings.IndexRune("\"'", r) >= 0
|
||||
}
|
||||
|
||||
func HasChar(r rune, accept string) bool {
|
||||
return strings.IndexRune(accept, r) >= 0
|
||||
}
|
||||
|
||||
func (this *Scanner) Scan(valid func(r rune) bool) bool {
|
||||
var isvalid bool
|
||||
for valid(this.Next()) {
|
||||
isvalid = true
|
||||
}
|
||||
this.Backup()
|
||||
return isvalid
|
||||
}
|
||||
|
||||
//scan upto to the end of a word, returns true if a word was scanned.
|
||||
//a word must start with a letter or '_' and can contain numbers after the first character.
|
||||
func (this *Scanner) ScanWord() bool {
|
||||
r := this.Next()
|
||||
if unicode.IsLetter(r) || strings.IndexRune(charValidString, r) >= 0 {
|
||||
for {
|
||||
r = this.Next()
|
||||
if IsAlphaNumeric(r) {
|
||||
continue
|
||||
} else {
|
||||
this.Backup()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
this.Backup()
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *Scanner) ScanNumber() bool {
|
||||
state := this.SaveState()
|
||||
r := this.Next()
|
||||
isdigit := unicode.IsDigit(r)
|
||||
if !isdigit && (r == '-' || r == '.') {
|
||||
//if the first char is '-' or '.' the next char must be a digit.
|
||||
if !unicode.IsDigit(this.Next()) {
|
||||
this.LoadState(state)
|
||||
return false
|
||||
} else {
|
||||
isdigit = true
|
||||
}
|
||||
} else if !isdigit {
|
||||
this.Backup()
|
||||
return false
|
||||
}
|
||||
if this.Scan(IsNumber) || isdigit {
|
||||
return true
|
||||
} else {
|
||||
this.LoadState(state)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
// Modified from github.com/zdebeer99/goexpression
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Token interface {
|
||||
Category() TokenCategory
|
||||
SetError(err error)
|
||||
Error() error
|
||||
String() string
|
||||
}
|
||||
|
||||
type TokenCategory int
|
||||
|
||||
const (
|
||||
CatOther TokenCategory = iota
|
||||
CatFunction
|
||||
CatValue
|
||||
)
|
||||
|
||||
type EmptyToken struct {
|
||||
tokencat TokenCategory
|
||||
err error
|
||||
}
|
||||
|
||||
func NewEmptyToken() *EmptyToken {
|
||||
return &EmptyToken{CatOther, nil}
|
||||
}
|
||||
|
||||
func (this *EmptyToken) Category() TokenCategory {
|
||||
return this.tokencat
|
||||
}
|
||||
|
||||
func (this *EmptyToken) Error() error {
|
||||
return this.err
|
||||
}
|
||||
|
||||
func (this *EmptyToken) SetError(err error) {
|
||||
this.err = err
|
||||
}
|
||||
|
||||
func (this *EmptyToken) String() string {
|
||||
return "Base()"
|
||||
}
|
||||
|
||||
type ErrorToken struct {
|
||||
EmptyToken
|
||||
}
|
||||
|
||||
func NewErrorToken(err string) *ErrorToken {
|
||||
return &ErrorToken{EmptyToken{CatOther, fmt.Errorf(err)}}
|
||||
}
|
||||
|
||||
type NumberToken struct {
|
||||
EmptyToken
|
||||
Value int64
|
||||
}
|
||||
|
||||
func NewNumberToken(value string) *NumberToken {
|
||||
node := &NumberToken{EmptyToken{CatValue, nil}, 0}
|
||||
val1, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
panic("number node failed to parse string to number (" + value + ")")
|
||||
return node
|
||||
}
|
||||
node.Value = val1
|
||||
return node
|
||||
}
|
||||
|
||||
func (this *NumberToken) String() string {
|
||||
return fmt.Sprintf("Number(%v)", this.Value)
|
||||
}
|
||||
|
||||
type IdentityToken struct {
|
||||
EmptyToken
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewIdentityToken(name string) *IdentityToken {
|
||||
return &IdentityToken{EmptyToken{CatValue, nil}, name}
|
||||
}
|
||||
|
||||
func (this *IdentityToken) String() string {
|
||||
return fmt.Sprintf("Identity(%s)", this.Name)
|
||||
}
|
||||
|
||||
type FuncToken struct {
|
||||
EmptyToken
|
||||
Name string
|
||||
Arguments []*TreeNode
|
||||
}
|
||||
|
||||
func NewFuncToken(name string) *FuncToken {
|
||||
return &FuncToken{EmptyToken{CatFunction, nil}, name, make([]*TreeNode, 0)}
|
||||
}
|
||||
|
||||
func (this *FuncToken) AddArgument(arg *TreeNode) {
|
||||
this.Arguments = append(this.Arguments, arg)
|
||||
}
|
||||
|
||||
func (this *FuncToken) String() string {
|
||||
args := make([]string, len(this.Arguments))
|
||||
for i, v := range this.Arguments {
|
||||
args[i] = fmt.Sprintf("%s", strings.Replace(v.String(), "\n", ",", -1))
|
||||
}
|
||||
return fmt.Sprintf("Func %s(%s)", this.Name, args)
|
||||
}
|
||||
|
||||
type OperatorToken struct {
|
||||
EmptyToken
|
||||
Operator string
|
||||
lvl int
|
||||
}
|
||||
|
||||
func NewOperatorToken(operator string) *OperatorToken {
|
||||
op := &OperatorToken{EmptyToken{CatFunction, nil}, "", -1}
|
||||
op.SetOperator(operator)
|
||||
return op
|
||||
}
|
||||
|
||||
func (this *OperatorToken) SetOperator(operator string) {
|
||||
this.Operator = operator
|
||||
this.lvl = operators.Level(operator)
|
||||
if this.lvl < 0 {
|
||||
panic(fmt.Sprintf("invalid operator %q", operator))
|
||||
}
|
||||
}
|
||||
|
||||
// OperatorPrecedence return true if the operator argument is lower than the current operator.
|
||||
func (this *OperatorToken) Precedence(operator string) int {
|
||||
lvl := operators.Level(operator)
|
||||
switch {
|
||||
case lvl == this.lvl:
|
||||
return 0
|
||||
case lvl > this.lvl:
|
||||
return 1
|
||||
case lvl < this.lvl:
|
||||
return -1
|
||||
}
|
||||
panic("unreachable code")
|
||||
}
|
||||
|
||||
func (this *OperatorToken) String() string {
|
||||
return fmt.Sprintf("Func(%s)", this.Operator)
|
||||
}
|
||||
|
||||
type OperatorPrecedence [][]string
|
||||
|
||||
func (this OperatorPrecedence) Level(operator string) int {
|
||||
for level, operators := range this {
|
||||
for _, op := range operators {
|
||||
if op == strings.ToLower(operator) {
|
||||
return 5 - level
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (this OperatorPrecedence) All() []string {
|
||||
out := make([]string, 0)
|
||||
for _, operators := range this {
|
||||
for _, op := range operators {
|
||||
out = append(out, op)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var operators = OperatorPrecedence{
|
||||
{"*", "/", "%"},
|
||||
{"+", "-", "&", "^", "|"},
|
||||
{"==", "=", "!=", ">=", "<=", ">", "<"},
|
||||
{"&&", "and"},
|
||||
{"||", "or", "like"},
|
||||
}
|
||||
|
||||
var operatorList = operators.All()
|
||||
|
||||
type LRFuncToken struct {
|
||||
EmptyToken
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewLRFuncToken(name string) *LRFuncToken {
|
||||
return &LRFuncToken{EmptyToken{CatFunction, nil}, name}
|
||||
}
|
||||
|
||||
func (this *LRFuncToken) String() string {
|
||||
return fmt.Sprintf("Func(%s)", this.Name)
|
||||
}
|
||||
|
||||
type GroupToken struct {
|
||||
EmptyToken
|
||||
GroupType string
|
||||
}
|
||||
|
||||
func NewGroupToken(group string) *GroupToken {
|
||||
return &GroupToken{EmptyToken{CatOther, nil}, group}
|
||||
}
|
||||
|
||||
func (this *GroupToken) String() string {
|
||||
return fmt.Sprintf("Group(%s)", this.GroupType)
|
||||
}
|
||||
|
||||
type TextToken struct {
|
||||
EmptyToken
|
||||
Text string
|
||||
}
|
||||
|
||||
func NewTextToken(text string) *TextToken {
|
||||
return &TextToken{EmptyToken{CatValue, nil}, text}
|
||||
}
|
||||
|
||||
func (this *TextToken) String() string {
|
||||
return fmt.Sprintf("%q", this.Text)
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// Modified from github.com/zdebeer99/goexpression
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TreeNode struct {
|
||||
Value Token
|
||||
parent *TreeNode
|
||||
items []*TreeNode
|
||||
}
|
||||
|
||||
// NewTreeElement Creates a new TreeElement.
|
||||
func NewTreeNode(value Token) *TreeNode {
|
||||
return &TreeNode{value, nil, make([]*TreeNode, 0)}
|
||||
}
|
||||
|
||||
// Parent Returns the current element parent
|
||||
func (this *TreeNode) Parent() *TreeNode {
|
||||
return this.parent
|
||||
}
|
||||
|
||||
func (this *TreeNode) Root() *TreeNode {
|
||||
p := this
|
||||
for p.parent != nil {
|
||||
p = p.parent
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// setParent sets the current nodes parent value.
|
||||
// Warning: does not add the node as a child
|
||||
func (this *TreeNode) setParent(element *TreeNode) {
|
||||
if this.parent != nil {
|
||||
panic("TreeNode already attached to a parent node")
|
||||
}
|
||||
this.parent = element
|
||||
}
|
||||
|
||||
func (this *TreeNode) LastElement() *TreeNode {
|
||||
if len(this.items) == 0 {
|
||||
return nil
|
||||
}
|
||||
return this.items[len(this.items)-1]
|
||||
}
|
||||
|
||||
func (this *TreeNode) Last() Token {
|
||||
last := this.LastElement()
|
||||
if last != nil {
|
||||
return last.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *TreeNode) Items() []*TreeNode {
|
||||
return this.items
|
||||
}
|
||||
|
||||
// Add adds a TreeElement to the end of the children items of the current node.
|
||||
func (this *TreeNode) AddElement(element *TreeNode) *TreeNode {
|
||||
element.setParent(this)
|
||||
this.items = append(this.items, element)
|
||||
return element
|
||||
}
|
||||
|
||||
// Add adds a value to the end of the children items of the current node.
|
||||
func (this *TreeNode) Add(value Token) *TreeNode {
|
||||
element := NewTreeNode(value)
|
||||
return this.AddElement(element)
|
||||
}
|
||||
|
||||
// Push, removes the current element from its current parent, place the new value
|
||||
// in its place and add the current element to the new element. there by pushing the current
|
||||
// element down the hierachy.
|
||||
// Example:
|
||||
// tree: A(B)
|
||||
// B.Push(C)
|
||||
// tree: A(C(B))
|
||||
func (this *TreeNode) PushElement(element *TreeNode) *TreeNode {
|
||||
parent := this.Parent()
|
||||
if parent != nil {
|
||||
//replace the current node with the new node
|
||||
index := parent.indexOf(this)
|
||||
parent.items[index] = element
|
||||
element.setParent(parent)
|
||||
this.parent = nil
|
||||
}
|
||||
//add the current node to the new node
|
||||
element.AddElement(this)
|
||||
return element
|
||||
}
|
||||
|
||||
func (this *TreeNode) Push(value Token) *TreeNode {
|
||||
return this.PushElement(NewTreeNode(value))
|
||||
}
|
||||
|
||||
// FindChildElement Finds a child element in the current nodes children
|
||||
func (this *TreeNode) indexOf(element *TreeNode) int {
|
||||
for i, v := range this.items {
|
||||
if v == element {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (this *TreeNode) StringContent() string {
|
||||
lines := make([]string, len(this.items))
|
||||
for i, v := range this.items {
|
||||
lines[i] = v.String()
|
||||
}
|
||||
if this.Value.Error() != nil {
|
||||
return fmt.Sprintf("[ERROR: %s]", this.Value.Error())
|
||||
} else if len(lines) > 0 {
|
||||
return fmt.Sprintf("%s", strings.Join(lines, ","))
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (this *TreeNode) String() string {
|
||||
if this.StringContent() == "" {
|
||||
return this.Value.String()
|
||||
}
|
||||
return fmt.Sprintf("[%s:%s]", this.Value.String(), this.StringContent())
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package serverbrowser
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"wwfc/serverbrowser/filter"
|
||||
"wwfc/filter"
|
||||
)
|
||||
|
||||
func parseFilter(t *testing.T, expression string) error {
|
||||
|
||||
Reference in New Issue
Block a user