From e934293d83a819845be732e7cda6b27b17ced34d Mon Sep 17 00:00:00 2001 From: mkwcat Date: Tue, 6 Feb 2024 17:51:52 -0500 Subject: [PATCH] ServerBrowser: Add LIKE operator --- serverbrowser/filter/eval.go | 68 ++++++++++++++++++++++++++++++++- serverbrowser/filter/parse.go | 11 +++++- serverbrowser/filter/scanner.go | 6 +-- serverbrowser/filter/tokens.go | 4 +- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/serverbrowser/filter/eval.go b/serverbrowser/filter/eval.go index ad747da..0e44261 100644 --- a/serverbrowser/filter/eval.go +++ b/serverbrowser/filter/eval.go @@ -3,6 +3,7 @@ package filter import ( "errors" + "regexp" "strconv" "strings" ) @@ -55,7 +56,7 @@ func (this *expression) switchOther(node *TreeNode) { func (this *expression) switchFunction(node *TreeNode) int64 { val1 := node.Value.(*OperatorToken) - switch val1.Operator { + switch strings.ToLower(val1.Operator) { case "=": return this.evalEquals(node.Items()) case "==": @@ -85,6 +86,9 @@ func (this *expression) switchFunction(node *TreeNode) int64 { case "||": return this.evalOr(node.Items()) + case "like": + return this.evalLike(node.Items()) + default: panic("function not supported: " + val1.Operator) } @@ -288,6 +292,68 @@ func (this *expression) evalMathLessOrEqual(val1, val2 int64) int64 { return 0 } +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] diff --git a/serverbrowser/filter/parse.go b/serverbrowser/filter/parse.go index 978d004..ac06271 100644 --- a/serverbrowser/filter/parse.go +++ b/serverbrowser/filter/parse.go @@ -4,6 +4,8 @@ package filter import ( "errors" "fmt" + "strings" + "unicode" ) type stateFn func(*parser) stateFn @@ -93,8 +95,15 @@ func (this *parser) parseCloseBracket() stateFn { func (this *parser) AcceptOperator() bool { scan := this.scan + state := scan.SaveState() for _, op := range operatorList { - if scan.Prefix(op) { + 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 } } diff --git a/serverbrowser/filter/scanner.go b/serverbrowser/filter/scanner.go index e73753f..6bba162 100644 --- a/serverbrowser/filter/scanner.go +++ b/serverbrowser/filter/scanner.go @@ -16,7 +16,7 @@ func (p Pos) Position() Pos { const eof = -1 -//Scanner, Iterates through a string. +// Scanner, Iterates through a string. type Scanner struct { input string start Pos @@ -52,7 +52,7 @@ func (this *Scanner) Commit() string { return r1 } -//IsEOF check if the end of the current string has been reached. +// IsEOF check if the end of the current string has been reached. func (this *Scanner) IsEOF() bool { return int(this.pos) >= len(this.input) } @@ -65,7 +65,7 @@ func (this *Scanner) MoveStart(pos int) { this.start = this.start + Pos(pos) } -//Next returns the next rune in the input. +// Next returns the next rune in the input. func (this *Scanner) Next() rune { this.safebackup = true if this.IsEOF() { diff --git a/serverbrowser/filter/tokens.go b/serverbrowser/filter/tokens.go index 03f7d4f..3b37ede 100644 --- a/serverbrowser/filter/tokens.go +++ b/serverbrowser/filter/tokens.go @@ -153,7 +153,7 @@ type OperatorPrecedence [][]string func (this OperatorPrecedence) Level(operator string) int { for level, operators := range this { for _, op := range operators { - if op == operator { + if op == strings.ToLower(operator) { return 5 - level } } @@ -177,7 +177,7 @@ var operators = OperatorPrecedence{ {"+", "-"}, {"==", "=", "!=", ">=", "<=", ">", "<"}, {"&&", "and"}, - {"||", "or"}, + {"||", "or", "like"}, } var operatorList = operators.All()