diff --git a/.gitignore b/.gitignore index fa92975..7a0a004 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -*.out \ No newline at end of file +*.out +.antlr/ + + +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index fb81c92..a7130ce 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ * [Pagination](#pagination) * [Sorting](#sorting) * [Projection](#projection) + * [Advanced queries](#advanced-queries) * [Available options](#available-options) * [Customize limit value](#customize-limit-value) * [Specify casting per param keys](#specify-casting-per-param-keys) @@ -204,6 +205,21 @@ fields=firstname,lastname,phone,email **Note:** * The `_id` field (returned by default). +### Advanced queries + +For more advanced usage (`and`, `or` logic operations), pass query `filter` as string with the logical operations, for example: + +```json +filter=(country=Mexico OR country=Spain) and gender=female +```` + +##### What operations are possible? + +* Filtering operations. +* The `AND/and` operator. +* The `OR/or` operator. +* Parenthesis can be used for grouping. + ## Available options You can use advanced options: diff --git a/antlr4/Query.g4 b/antlr4/Query.g4 new file mode 100644 index 0000000..cdeaa80 --- /dev/null +++ b/antlr4/Query.g4 @@ -0,0 +1,162 @@ +grammar Query; + +input + : query EOF + ; + +query + : left=query logicalOp=(AND | OR) right=query #opQuery + | LPAREN query RPAREN #priorityQuery + | criteria #atomQuery + ; + +criteria + : key op value + | key + ; + +key + : IDENTIFIER + | NEG_IDENTIFIER + ; + +value + : IDENTIFIER + | STRING + | ENCODED_STRING + ; + +op + : EQ + | NE + | GT + | GTE + | LT + | LTE + ; + +STRING + : '\'' StringCharacter* '\'' + ; + +fragment StringCharacter + : ~["\\\r\n] + | '\\' EscapeSequence + | LineContinuation + ; + +fragment EscapeSequence + : CharacterEscapeSequence + | HexEscapeSequence + | UnicodeEscapeSequence + ; + +fragment CharacterEscapeSequence + : SingleEscapeCharacter + | NonEscapeCharacter + ; + +fragment HexEscapeSequence + : 'x' HexDigit HexDigit + ; + +fragment UnicodeEscapeSequence + : 'u' HexDigit HexDigit HexDigit HexDigit + ; + +fragment SingleEscapeCharacter + : ['"\\bfnrtv] + ; + +fragment NonEscapeCharacter + : ~['"\\bfnrtv0-9xu\r\n] + ; + +fragment EscapeCharacter + : SingleEscapeCharacter + | DecimalDigit + | [xu] + ; + +fragment LineContinuation + : '\\' LineTerminatorSequence + ; + +fragment LineTerminatorSequence + : '\r\n' + | LineTerminator + ; + +fragment DecimalDigit + : [0-9] + ; + +fragment HexDigit + : [0-9a-fA-F] + ; + +fragment OctalDigit + : [0-7] + ; + +AND + : 'AND' + | 'and' + ; + +OR + : 'OR' + | 'or' + ; + +LPAREN + : '(' + ; + +RPAREN + : ')' + ; + +EQ + : '=' + ; + +NE + : '!=' + ; + +GT + : '>' + ; + +GTE + : '>=' + ; + +LT + : '<' + ; + +LTE + : '<=' + ; + +IDENTIFIER + : [A-Za-z0-9.:_-]+ + ; + +NEG_IDENTIFIER + : '!'[A-Za-z0-9.:_-]+ + ; + +ENCODED_STRING + : ~([ \\[\]<>!=()])+ + ; + +LineTerminator + : [\r\n\u2028\u2029] -> channel(HIDDEN) + ; + +WS + : [ \t\r\n]+ -> skip + ; \ No newline at end of file diff --git a/convert.go b/convert.go index 82e00e5..7154f88 100644 --- a/convert.go +++ b/convert.go @@ -5,9 +5,10 @@ import ( ) // Convert converts the criteria value to a MongoDB query -func Convert(criteria SearchCriteria, filter map[string]interface{}) { +func Convert(criteria SearchCriteria) map[string]interface{} { value := ParseValue(criteria.Value, criteria.Caster) + filter := make(map[string]interface{}) switch criteria.Operation { case EQUAL: @@ -39,6 +40,8 @@ func Convert(criteria SearchCriteria, filter map[string]interface{}) { case EXISTS: filter[criteria.Key] = buildMongoQuery("$exists", !criteria.Prefix) } + + return filter } func buildMongoQuery(operator string, value interface{}) map[string]interface{} { diff --git a/convert_test.go b/convert_test.go index be376e6..e50ddc1 100644 --- a/convert_test.go +++ b/convert_test.go @@ -8,7 +8,6 @@ import ( var converetTests = []struct { Criteria SearchCriteria - Filter map[string]interface{} Expected map[string]interface{} }{ { @@ -18,7 +17,6 @@ var converetTests = []struct { Operation: EQUAL, Value: "Jhon", }, - map[string]interface{}{}, map[string]interface{}{"name": "Jhon"}, }, { @@ -28,7 +26,6 @@ var converetTests = []struct { Operation: EQUAL, Value: "QUEUED,DEQUEUED", }, - map[string]interface{}{}, map[string]interface{}{"status": map[string]interface{}{"$in": []interface{}{"QUEUED", "DEQUEUED"}}}, }, { @@ -38,7 +35,6 @@ var converetTests = []struct { Operation: EQUAL, Value: "/@gmail\\.com$/", }, - map[string]interface{}{}, map[string]interface{}{"email": map[string]interface{}{"$regex": "@gmail\\.com$", "$options": ""}}, }, { @@ -48,7 +44,6 @@ var converetTests = []struct { Operation: NOT_EQUAL, Value: "SENT", }, - map[string]interface{}{}, map[string]interface{}{"status": map[string]interface{}{"$ne": "SENT"}}, }, { @@ -58,7 +53,6 @@ var converetTests = []struct { Operation: NOT_EQUAL, Value: "QUEUED,DEQUEUED", }, - map[string]interface{}{}, map[string]interface{}{"status": map[string]interface{}{"$nin": []interface{}{"QUEUED", "DEQUEUED"}}}, }, { @@ -68,7 +62,6 @@ var converetTests = []struct { Operation: NOT_EQUAL, Value: "/^58/", }, - map[string]interface{}{}, map[string]interface{}{"phone": map[string]interface{}{"$not": map[string]interface{}{"$regex": "^58", "$options": ""}}}, }, { @@ -78,7 +71,6 @@ var converetTests = []struct { Operation: GREATER_THAN, Value: "5", }, - map[string]interface{}{}, map[string]interface{}{"price": map[string]interface{}{"$gt": int64(5)}}, }, { @@ -88,7 +80,6 @@ var converetTests = []struct { Operation: GREATER_THAN_EQUAL, Value: "5", }, - map[string]interface{}{}, map[string]interface{}{"price": map[string]interface{}{"$gte": int64(5)}}, }, { @@ -98,7 +89,6 @@ var converetTests = []struct { Operation: LESS_THAN, Value: "5", }, - map[string]interface{}{}, map[string]interface{}{"price": map[string]interface{}{"$lt": int64(5)}}, }, { @@ -108,7 +98,6 @@ var converetTests = []struct { Operation: LESS_THAN_EQUAL, Value: "5", }, - map[string]interface{}{}, map[string]interface{}{"price": map[string]interface{}{"$lte": int64(5)}}, }, { @@ -118,14 +107,13 @@ var converetTests = []struct { Operation: EXISTS, Value: "", }, - map[string]interface{}{}, map[string]interface{}{"email": map[string]interface{}{"$exists": false}}, }, } func TestShouldConvertFromSearchCriteria(t *testing.T) { for _, test := range converetTests { - Convert(test.Criteria, test.Filter) - assert.Equal(t, test.Expected, test.Filter) + result := Convert(test.Criteria) + assert.Equal(t, test.Expected, result) } } diff --git a/go.mod b/go.mod index 5b37c67..96ef28e 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,15 @@ module github.com/ajclopez/mgs go 1.21.2 +require ( + github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 + github.com/antlr4-go/antlr/v4 v4.13.0 + github.com/stretchr/testify v1.8.4 +) + require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8cf6655..c0c3638 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,16 @@ +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mgs.go b/mgs.go index 070c174..d89faf7 100644 --- a/mgs.go +++ b/mgs.go @@ -4,6 +4,9 @@ import ( "errors" "net/url" "strings" + + "github.com/ajclopez/mgs/parser" + "github.com/antlr4-go/antlr/v4" ) var ( @@ -12,6 +15,8 @@ var ( // ErrValueNoMatch is returned when the converter cannot match a string to // an unsigned integer. ErrValueNoMatch = errors.New("value does not match") + // ErrQueryVisitor + ErrQueryVisitor = errors.New("context does not exists") ) // MongoGoSearch converts query into a MongoDB query object. @@ -30,12 +35,13 @@ func MongoGoSearch(query string, opts *FindOptions) (Query, error) { return res, ErrUnescapeCharacters } - filter := make(map[string]interface{}) + var filterAdvanced map[string][]interface{} + var filters []interface{} for _, criteria := range Parse(query, opts.Caster) { switch criteria.Key { case "filter": - // advanced queries + filterAdvanced = parseFilterAdvanced(criteria.Value, opts.Caster) case "skip": err = parseSkip(&res, criteria.Value) case "limit": @@ -45,7 +51,7 @@ func MongoGoSearch(query string, opts *FindOptions) (Query, error) { case "fields": parseFields(&res, criteria.Value) default: - Convert(criteria, filter) + filters = append(filters, Convert(criteria)) } if err != nil { @@ -53,11 +59,25 @@ func MongoGoSearch(query string, opts *FindOptions) (Query, error) { } } - res.Filter = filter + parseDefaultFilter(&res, filters, filterAdvanced) return res, err } +func parseDefaultFilter(res *Query, filters []interface{}, filterAdvanced map[string][]interface{}) { + if filterAdvanced != nil { + res.Filter = filterAdvanced + } else { + if filters == nil { + res.Filter = map[string][]interface{}{} + } else { + res.Filter = map[string][]interface{}{ + "$and": filters, + } + } + } +} + func parseSkip(res *Query, value string) error { skip, err := parseIntValueToInt(value) if err != nil { @@ -116,3 +136,25 @@ func parseFields(res *Query, value string) { res.Projection = projection } + +func parseFilterAdvanced(value string, caster *map[string]CastType) map[string][]interface{} { + + is := antlr.NewInputStream(value) + lexer := parser.NewQueryLexer(is) + tokens := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel) + parser := parser.NewQueryParser(tokens) + visitor := NewGeneratorVisitor(caster) + + tree := parser.Input() + result := visitor.Visit(tree) + + if _, ok := result.(map[string]interface{}); ok { + return map[string][]interface{}{ + "$and": { + result.(map[string]interface{}), + }, + } + } else { + return result.(map[string][]interface{}) + } +} diff --git a/mgs_test.go b/mgs_test.go index f1e3daa..a8dbaee 100644 --- a/mgs_test.go +++ b/mgs_test.go @@ -19,7 +19,7 @@ var tests = []struct { "skip=10", result{ Query{ - Filter: map[string]interface{}{}, + Filter: map[string][]interface{}{}, Sort: map[string]int(nil), Limit: 0, Skip: 10, @@ -32,7 +32,7 @@ var tests = []struct { "skip=number", result{ Query{ - Filter: map[string]interface{}(nil), + Filter: map[string][]interface{}(nil), Sort: map[string]int(nil), Limit: 0, Skip: 0, @@ -45,7 +45,7 @@ var tests = []struct { "limit=25", result{ Query{ - Filter: map[string]interface{}{}, + Filter: map[string][]interface{}{}, Sort: map[string]int(nil), Limit: 25, Skip: 0, @@ -58,7 +58,7 @@ var tests = []struct { "limit=number", result{ Query{ - Filter: map[string]interface{}(nil), + Filter: map[string][]interface{}(nil), Sort: map[string]int(nil), Limit: 0, Skip: 0, @@ -71,7 +71,7 @@ var tests = []struct { "sort=+date,-age,name", result{ Query{ - Filter: map[string]interface{}{}, + Filter: map[string][]interface{}{}, Sort: map[string]int{ "date": 1, "age": -1, @@ -88,7 +88,7 @@ var tests = []struct { "fields=firstname,lastname,email", result{ Query{ - Filter: map[string]interface{}{}, + Filter: map[string][]interface{}{}, Sort: map[string]int(nil), Limit: 0, Skip: 0, @@ -105,10 +105,64 @@ var tests = []struct { "city=Madrid&age>=18", result{ Query{ - Filter: map[string]interface{}{ - "city": "Madrid", - "age": map[string]interface{}{ - "$gte": int64(18), + Filter: map[string][]interface{}{ + "$and": { + map[string]interface{}{ + "city": "Madrid", + }, + map[string]interface{}{ + "age": map[string]interface{}{ + "$gte": int64(18), + }, + }, + }, + }, + Sort: map[string]int(nil), + Limit: 0, + Skip: 0, + Projection: map[string]int(nil), + }, + nil, + }, + }, + { + "filter=gender = female", + result{ + Query{ + Filter: map[string][]interface{}{ + "$and": { + map[string]interface{}{ + "gender": "female", + }, + }, + }, + Sort: map[string]int(nil), + Limit: 0, + Skip: 0, + Projection: map[string]int(nil), + }, + nil, + }, + }, + { + "filter=(city = Madrid or city = Santiago) and gender = female", + result{ + Query{ + Filter: map[string][]interface{}{ + "$and": { + map[string][]interface{}{ + "$or": { + map[string]interface{}{ + "city": "Madrid", + }, + map[string]interface{}{ + "city": "Santiago", + }, + }, + }, + map[string]interface{}{ + "gender": "female", + }, }, }, Sort: map[string]int(nil), @@ -161,8 +215,12 @@ func TestReturnQueryUseFindOptionsWithCaster(t *testing.T) { expected := result{ Query{ - Filter: map[string]interface{}{ - "mobile": "+56900000000", + Filter: map[string][]interface{}{ + "$and": { + map[string]interface{}{ + "mobile": "+56900000000", + }, + }, }, Sort: map[string]int(nil), Limit: 0, @@ -185,7 +243,7 @@ func TestReturnQueryUseFindOptionsWithDefaultLimit(t *testing.T) { expected := result{ Query{ - Filter: map[string]interface{}{}, + Filter: map[string][]interface{}{}, Sort: map[string]int(nil), Limit: 10, Skip: 0, @@ -207,7 +265,7 @@ func TestReturnQueryUseFindOptionsWithMaxLimit(t *testing.T) { expected := result{ Query{ - Filter: map[string]interface{}{}, + Filter: map[string][]interface{}{}, Sort: map[string]int(nil), Limit: 500, Skip: 0, diff --git a/parser.go b/parser.go index 6bdc92d..a03212d 100644 --- a/parser.go +++ b/parser.go @@ -12,12 +12,13 @@ const DATE_FORMAT = "2006-01-02T15:04:05.000Z" func Parse(query string, caster *map[string]CastType) []SearchCriteria { criteria := []SearchCriteria{} for _, condition := range strings.Split(query, "&") { - criteria = append(criteria, criteriaParser(condition, caster)) + criteria = append(criteria, CriteriaParser(condition, caster)) } return criteria } -func criteriaParser(condition string, caster *map[string]CastType) SearchCriteria { +// CriteriaParser build criteria from a condition expression +func CriteriaParser(condition string, caster *map[string]CastType) SearchCriteria { pattern := GetOperatorPattern() match := pattern.FindStringSubmatch(condition) diff --git a/parser/query_base_listener.go b/parser/query_base_listener.go new file mode 100644 index 0000000..e2734e8 --- /dev/null +++ b/parser/query_base_listener.go @@ -0,0 +1,69 @@ +// Code generated from Query.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package parser // Query +import "github.com/antlr4-go/antlr/v4" + +// BaseQueryListener is a complete listener for a parse tree produced by QueryParser. +type BaseQueryListener struct{} + +var _ QueryListener = &BaseQueryListener{} + +// VisitTerminal is called when a terminal node is visited. +func (s *BaseQueryListener) VisitTerminal(node antlr.TerminalNode) {} + +// VisitErrorNode is called when an error node is visited. +func (s *BaseQueryListener) VisitErrorNode(node antlr.ErrorNode) {} + +// EnterEveryRule is called when any rule is entered. +func (s *BaseQueryListener) EnterEveryRule(ctx antlr.ParserRuleContext) {} + +// ExitEveryRule is called when any rule is exited. +func (s *BaseQueryListener) ExitEveryRule(ctx antlr.ParserRuleContext) {} + +// EnterInput is called when production input is entered. +func (s *BaseQueryListener) EnterInput(ctx *InputContext) {} + +// ExitInput is called when production input is exited. +func (s *BaseQueryListener) ExitInput(ctx *InputContext) {} + +// EnterAtomQuery is called when production atomQuery is entered. +func (s *BaseQueryListener) EnterAtomQuery(ctx *AtomQueryContext) {} + +// ExitAtomQuery is called when production atomQuery is exited. +func (s *BaseQueryListener) ExitAtomQuery(ctx *AtomQueryContext) {} + +// EnterPriorityQuery is called when production priorityQuery is entered. +func (s *BaseQueryListener) EnterPriorityQuery(ctx *PriorityQueryContext) {} + +// ExitPriorityQuery is called when production priorityQuery is exited. +func (s *BaseQueryListener) ExitPriorityQuery(ctx *PriorityQueryContext) {} + +// EnterOpQuery is called when production opQuery is entered. +func (s *BaseQueryListener) EnterOpQuery(ctx *OpQueryContext) {} + +// ExitOpQuery is called when production opQuery is exited. +func (s *BaseQueryListener) ExitOpQuery(ctx *OpQueryContext) {} + +// EnterCriteria is called when production criteria is entered. +func (s *BaseQueryListener) EnterCriteria(ctx *CriteriaContext) {} + +// ExitCriteria is called when production criteria is exited. +func (s *BaseQueryListener) ExitCriteria(ctx *CriteriaContext) {} + +// EnterKey is called when production key is entered. +func (s *BaseQueryListener) EnterKey(ctx *KeyContext) {} + +// ExitKey is called when production key is exited. +func (s *BaseQueryListener) ExitKey(ctx *KeyContext) {} + +// EnterValue is called when production value is entered. +func (s *BaseQueryListener) EnterValue(ctx *ValueContext) {} + +// ExitValue is called when production value is exited. +func (s *BaseQueryListener) ExitValue(ctx *ValueContext) {} + +// EnterOp is called when production op is entered. +func (s *BaseQueryListener) EnterOp(ctx *OpContext) {} + +// ExitOp is called when production op is exited. +func (s *BaseQueryListener) ExitOp(ctx *OpContext) {} diff --git a/parser/query_base_visitor.go b/parser/query_base_visitor.go new file mode 100644 index 0000000..99e7caa --- /dev/null +++ b/parser/query_base_visitor.go @@ -0,0 +1,40 @@ +// Code generated from Query.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package parser // Query +import "github.com/antlr4-go/antlr/v4" + +type BaseQueryVisitor struct { + *antlr.BaseParseTreeVisitor +} + +func (v *BaseQueryVisitor) VisitInput(ctx *InputContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseQueryVisitor) VisitAtomQuery(ctx *AtomQueryContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseQueryVisitor) VisitPriorityQuery(ctx *PriorityQueryContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseQueryVisitor) VisitOpQuery(ctx *OpQueryContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseQueryVisitor) VisitCriteria(ctx *CriteriaContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseQueryVisitor) VisitKey(ctx *KeyContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseQueryVisitor) VisitValue(ctx *ValueContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseQueryVisitor) VisitOp(ctx *OpContext) interface{} { + return v.VisitChildren(ctx) +} diff --git a/parser/query_lexer.go b/parser/query_lexer.go new file mode 100644 index 0000000..d0aed53 --- /dev/null +++ b/parser/query_lexer.go @@ -0,0 +1,200 @@ +// Code generated from Query.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package parser + +import ( + "fmt" + "github.com/antlr4-go/antlr/v4" + "sync" + "unicode" +) + +// Suppress unused import error +var _ = fmt.Printf +var _ = sync.Once{} +var _ = unicode.IsLetter + +type QueryLexer struct { + *antlr.BaseLexer + channelNames []string + modeNames []string + // TODO: EOF string +} + +var QueryLexerLexerStaticData struct { + once sync.Once + serializedATN []int32 + ChannelNames []string + ModeNames []string + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func querylexerLexerInit() { + staticData := &QueryLexerLexerStaticData + staticData.ChannelNames = []string{ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + } + staticData.ModeNames = []string{ + "DEFAULT_MODE", + } + staticData.LiteralNames = []string{ + "", "", "", "", "'('", "')'", "'='", "'!='", "'>'", "'>='", "'<'", "'<='", + } + staticData.SymbolicNames = []string{ + "", "STRING", "AND", "OR", "LPAREN", "RPAREN", "EQ", "NE", "GT", "GTE", + "LT", "LTE", "IDENTIFIER", "NEG_IDENTIFIER", "ENCODED_STRING", "LineTerminator", + "WS", + } + staticData.RuleNames = []string{ + "STRING", "StringCharacter", "EscapeSequence", "CharacterEscapeSequence", + "HexEscapeSequence", "UnicodeEscapeSequence", "SingleEscapeCharacter", + "NonEscapeCharacter", "EscapeCharacter", "LineContinuation", "LineTerminatorSequence", + "DecimalDigit", "HexDigit", "OctalDigit", "AND", "OR", "LPAREN", "RPAREN", + "EQ", "NE", "GT", "GTE", "LT", "LTE", "IDENTIFIER", "NEG_IDENTIFIER", + "ENCODED_STRING", "LineTerminator", "WS", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 0, 16, 176, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, + 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, + 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, + 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, + 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, + 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 1, 0, 1, 0, 5, 0, 62, 8, 0, 10, + 0, 12, 0, 65, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 73, 8, 1, + 1, 2, 1, 2, 1, 2, 3, 2, 78, 8, 2, 1, 3, 1, 3, 3, 3, 82, 8, 3, 1, 4, 1, + 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, + 7, 1, 8, 1, 8, 1, 8, 3, 8, 101, 8, 8, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, + 10, 3, 10, 109, 8, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, + 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 123, 8, 14, 1, 15, 1, 15, 1, + 15, 1, 15, 3, 15, 129, 8, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, + 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, + 23, 1, 23, 1, 23, 1, 24, 4, 24, 151, 8, 24, 11, 24, 12, 24, 152, 1, 25, + 1, 25, 4, 25, 157, 8, 25, 11, 25, 12, 25, 158, 1, 26, 4, 26, 162, 8, 26, + 11, 26, 12, 26, 163, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 4, 28, 171, 8, + 28, 11, 28, 12, 28, 172, 1, 28, 1, 28, 0, 0, 29, 1, 1, 3, 0, 5, 0, 7, 0, + 9, 0, 11, 0, 13, 0, 15, 0, 17, 0, 19, 0, 21, 0, 23, 0, 25, 0, 27, 0, 29, + 2, 31, 3, 33, 4, 35, 5, 37, 6, 39, 7, 41, 8, 43, 9, 45, 10, 47, 11, 49, + 12, 51, 13, 53, 14, 55, 15, 57, 16, 1, 0, 11, 4, 0, 10, 10, 13, 13, 34, + 34, 92, 92, 9, 0, 34, 34, 39, 39, 92, 92, 98, 98, 102, 102, 110, 110, 114, + 114, 116, 116, 118, 118, 12, 0, 10, 10, 13, 13, 34, 34, 39, 39, 48, 57, + 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 118, 120, 120, 2, 0, + 117, 117, 120, 120, 1, 0, 48, 57, 3, 0, 48, 57, 65, 70, 97, 102, 1, 0, + 48, 55, 5, 0, 45, 46, 48, 58, 65, 90, 95, 95, 97, 122, 4, 0, 32, 33, 40, + 41, 60, 62, 91, 93, 3, 0, 10, 10, 13, 13, 8232, 8233, 3, 0, 9, 10, 13, + 13, 32, 32, 177, 0, 1, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, + 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, + 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, + 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, + 0, 0, 0, 0, 57, 1, 0, 0, 0, 1, 59, 1, 0, 0, 0, 3, 72, 1, 0, 0, 0, 5, 77, + 1, 0, 0, 0, 7, 81, 1, 0, 0, 0, 9, 83, 1, 0, 0, 0, 11, 87, 1, 0, 0, 0, 13, + 93, 1, 0, 0, 0, 15, 95, 1, 0, 0, 0, 17, 100, 1, 0, 0, 0, 19, 102, 1, 0, + 0, 0, 21, 108, 1, 0, 0, 0, 23, 110, 1, 0, 0, 0, 25, 112, 1, 0, 0, 0, 27, + 114, 1, 0, 0, 0, 29, 122, 1, 0, 0, 0, 31, 128, 1, 0, 0, 0, 33, 130, 1, + 0, 0, 0, 35, 132, 1, 0, 0, 0, 37, 134, 1, 0, 0, 0, 39, 136, 1, 0, 0, 0, + 41, 139, 1, 0, 0, 0, 43, 141, 1, 0, 0, 0, 45, 144, 1, 0, 0, 0, 47, 146, + 1, 0, 0, 0, 49, 150, 1, 0, 0, 0, 51, 154, 1, 0, 0, 0, 53, 161, 1, 0, 0, + 0, 55, 165, 1, 0, 0, 0, 57, 170, 1, 0, 0, 0, 59, 63, 5, 39, 0, 0, 60, 62, + 3, 3, 1, 0, 61, 60, 1, 0, 0, 0, 62, 65, 1, 0, 0, 0, 63, 61, 1, 0, 0, 0, + 63, 64, 1, 0, 0, 0, 64, 66, 1, 0, 0, 0, 65, 63, 1, 0, 0, 0, 66, 67, 5, + 39, 0, 0, 67, 2, 1, 0, 0, 0, 68, 73, 8, 0, 0, 0, 69, 70, 5, 92, 0, 0, 70, + 73, 3, 5, 2, 0, 71, 73, 3, 19, 9, 0, 72, 68, 1, 0, 0, 0, 72, 69, 1, 0, + 0, 0, 72, 71, 1, 0, 0, 0, 73, 4, 1, 0, 0, 0, 74, 78, 3, 7, 3, 0, 75, 78, + 3, 9, 4, 0, 76, 78, 3, 11, 5, 0, 77, 74, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0, + 77, 76, 1, 0, 0, 0, 78, 6, 1, 0, 0, 0, 79, 82, 3, 13, 6, 0, 80, 82, 3, + 15, 7, 0, 81, 79, 1, 0, 0, 0, 81, 80, 1, 0, 0, 0, 82, 8, 1, 0, 0, 0, 83, + 84, 5, 120, 0, 0, 84, 85, 3, 25, 12, 0, 85, 86, 3, 25, 12, 0, 86, 10, 1, + 0, 0, 0, 87, 88, 5, 117, 0, 0, 88, 89, 3, 25, 12, 0, 89, 90, 3, 25, 12, + 0, 90, 91, 3, 25, 12, 0, 91, 92, 3, 25, 12, 0, 92, 12, 1, 0, 0, 0, 93, + 94, 7, 1, 0, 0, 94, 14, 1, 0, 0, 0, 95, 96, 8, 2, 0, 0, 96, 16, 1, 0, 0, + 0, 97, 101, 3, 13, 6, 0, 98, 101, 3, 23, 11, 0, 99, 101, 7, 3, 0, 0, 100, + 97, 1, 0, 0, 0, 100, 98, 1, 0, 0, 0, 100, 99, 1, 0, 0, 0, 101, 18, 1, 0, + 0, 0, 102, 103, 5, 92, 0, 0, 103, 104, 3, 21, 10, 0, 104, 20, 1, 0, 0, + 0, 105, 106, 5, 13, 0, 0, 106, 109, 5, 10, 0, 0, 107, 109, 3, 55, 27, 0, + 108, 105, 1, 0, 0, 0, 108, 107, 1, 0, 0, 0, 109, 22, 1, 0, 0, 0, 110, 111, + 7, 4, 0, 0, 111, 24, 1, 0, 0, 0, 112, 113, 7, 5, 0, 0, 113, 26, 1, 0, 0, + 0, 114, 115, 7, 6, 0, 0, 115, 28, 1, 0, 0, 0, 116, 117, 5, 65, 0, 0, 117, + 118, 5, 78, 0, 0, 118, 123, 5, 68, 0, 0, 119, 120, 5, 97, 0, 0, 120, 121, + 5, 110, 0, 0, 121, 123, 5, 100, 0, 0, 122, 116, 1, 0, 0, 0, 122, 119, 1, + 0, 0, 0, 123, 30, 1, 0, 0, 0, 124, 125, 5, 79, 0, 0, 125, 129, 5, 82, 0, + 0, 126, 127, 5, 111, 0, 0, 127, 129, 5, 114, 0, 0, 128, 124, 1, 0, 0, 0, + 128, 126, 1, 0, 0, 0, 129, 32, 1, 0, 0, 0, 130, 131, 5, 40, 0, 0, 131, + 34, 1, 0, 0, 0, 132, 133, 5, 41, 0, 0, 133, 36, 1, 0, 0, 0, 134, 135, 5, + 61, 0, 0, 135, 38, 1, 0, 0, 0, 136, 137, 5, 33, 0, 0, 137, 138, 5, 61, + 0, 0, 138, 40, 1, 0, 0, 0, 139, 140, 5, 62, 0, 0, 140, 42, 1, 0, 0, 0, + 141, 142, 5, 62, 0, 0, 142, 143, 5, 61, 0, 0, 143, 44, 1, 0, 0, 0, 144, + 145, 5, 60, 0, 0, 145, 46, 1, 0, 0, 0, 146, 147, 5, 60, 0, 0, 147, 148, + 5, 61, 0, 0, 148, 48, 1, 0, 0, 0, 149, 151, 7, 7, 0, 0, 150, 149, 1, 0, + 0, 0, 151, 152, 1, 0, 0, 0, 152, 150, 1, 0, 0, 0, 152, 153, 1, 0, 0, 0, + 153, 50, 1, 0, 0, 0, 154, 156, 5, 33, 0, 0, 155, 157, 7, 7, 0, 0, 156, + 155, 1, 0, 0, 0, 157, 158, 1, 0, 0, 0, 158, 156, 1, 0, 0, 0, 158, 159, + 1, 0, 0, 0, 159, 52, 1, 0, 0, 0, 160, 162, 8, 8, 0, 0, 161, 160, 1, 0, + 0, 0, 162, 163, 1, 0, 0, 0, 163, 161, 1, 0, 0, 0, 163, 164, 1, 0, 0, 0, + 164, 54, 1, 0, 0, 0, 165, 166, 7, 9, 0, 0, 166, 167, 1, 0, 0, 0, 167, 168, + 6, 27, 0, 0, 168, 56, 1, 0, 0, 0, 169, 171, 7, 10, 0, 0, 170, 169, 1, 0, + 0, 0, 171, 172, 1, 0, 0, 0, 172, 170, 1, 0, 0, 0, 172, 173, 1, 0, 0, 0, + 173, 174, 1, 0, 0, 0, 174, 175, 6, 28, 1, 0, 175, 58, 1, 0, 0, 0, 13, 0, + 63, 72, 77, 81, 100, 108, 122, 128, 152, 158, 163, 172, 2, 0, 1, 0, 6, + 0, 0, + } + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// QueryLexerInit initializes any static state used to implement QueryLexer. By default the +// static state used to implement the lexer is lazily initialized during the first call to +// NewQueryLexer(). You can call this function if you wish to initialize the static state ahead +// of time. +func QueryLexerInit() { + staticData := &QueryLexerLexerStaticData + staticData.once.Do(querylexerLexerInit) +} + +// NewQueryLexer produces a new lexer instance for the optional input antlr.CharStream. +func NewQueryLexer(input antlr.CharStream) *QueryLexer { + QueryLexerInit() + l := new(QueryLexer) + l.BaseLexer = antlr.NewBaseLexer(input) + staticData := &QueryLexerLexerStaticData + l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + l.channelNames = staticData.ChannelNames + l.modeNames = staticData.ModeNames + l.RuleNames = staticData.RuleNames + l.LiteralNames = staticData.LiteralNames + l.SymbolicNames = staticData.SymbolicNames + l.GrammarFileName = "Query.g4" + // TODO: l.EOF = antlr.TokenEOF + + return l +} + +// QueryLexer tokens. +const ( + QueryLexerSTRING = 1 + QueryLexerAND = 2 + QueryLexerOR = 3 + QueryLexerLPAREN = 4 + QueryLexerRPAREN = 5 + QueryLexerEQ = 6 + QueryLexerNE = 7 + QueryLexerGT = 8 + QueryLexerGTE = 9 + QueryLexerLT = 10 + QueryLexerLTE = 11 + QueryLexerIDENTIFIER = 12 + QueryLexerNEG_IDENTIFIER = 13 + QueryLexerENCODED_STRING = 14 + QueryLexerLineTerminator = 15 + QueryLexerWS = 16 +) diff --git a/parser/query_listener.go b/parser/query_listener.go new file mode 100644 index 0000000..38483bd --- /dev/null +++ b/parser/query_listener.go @@ -0,0 +1,57 @@ +// Code generated from Query.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package parser // Query +import "github.com/antlr4-go/antlr/v4" + +// QueryListener is a complete listener for a parse tree produced by QueryParser. +type QueryListener interface { + antlr.ParseTreeListener + + // EnterInput is called when entering the input production. + EnterInput(c *InputContext) + + // EnterAtomQuery is called when entering the atomQuery production. + EnterAtomQuery(c *AtomQueryContext) + + // EnterPriorityQuery is called when entering the priorityQuery production. + EnterPriorityQuery(c *PriorityQueryContext) + + // EnterOpQuery is called when entering the opQuery production. + EnterOpQuery(c *OpQueryContext) + + // EnterCriteria is called when entering the criteria production. + EnterCriteria(c *CriteriaContext) + + // EnterKey is called when entering the key production. + EnterKey(c *KeyContext) + + // EnterValue is called when entering the value production. + EnterValue(c *ValueContext) + + // EnterOp is called when entering the op production. + EnterOp(c *OpContext) + + // ExitInput is called when exiting the input production. + ExitInput(c *InputContext) + + // ExitAtomQuery is called when exiting the atomQuery production. + ExitAtomQuery(c *AtomQueryContext) + + // ExitPriorityQuery is called when exiting the priorityQuery production. + ExitPriorityQuery(c *PriorityQueryContext) + + // ExitOpQuery is called when exiting the opQuery production. + ExitOpQuery(c *OpQueryContext) + + // ExitCriteria is called when exiting the criteria production. + ExitCriteria(c *CriteriaContext) + + // ExitKey is called when exiting the key production. + ExitKey(c *KeyContext) + + // ExitValue is called when exiting the value production. + ExitValue(c *ValueContext) + + // ExitOp is called when exiting the op production. + ExitOp(c *OpContext) +} diff --git a/parser/query_parser.go b/parser/query_parser.go new file mode 100644 index 0000000..4a7b162 --- /dev/null +++ b/parser/query_parser.go @@ -0,0 +1,1247 @@ +// Code generated from Query.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package parser // Query +import ( + "fmt" + "strconv" + "sync" + + "github.com/antlr4-go/antlr/v4" +) + +// Suppress unused import errors +var _ = fmt.Printf +var _ = strconv.Itoa +var _ = sync.Once{} + +type QueryParser struct { + *antlr.BaseParser +} + +var QueryParserStaticData struct { + once sync.Once + serializedATN []int32 + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func queryParserInit() { + staticData := &QueryParserStaticData + staticData.LiteralNames = []string{ + "", "", "", "", "'('", "')'", "'='", "'!='", "'>'", "'>='", "'<'", "'<='", + } + staticData.SymbolicNames = []string{ + "", "STRING", "AND", "OR", "LPAREN", "RPAREN", "EQ", "NE", "GT", "GTE", + "LT", "LTE", "IDENTIFIER", "NEG_IDENTIFIER", "ENCODED_STRING", "LineTerminator", + "WS", + } + staticData.RuleNames = []string{ + "input", "query", "criteria", "key", "value", "op", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 1, 16, 45, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, + 4, 2, 5, 7, 5, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 1, 22, 8, 1, 1, 1, 1, 1, 1, 1, 5, 1, 27, 8, 1, 10, 1, 12, 1, 30, 9, 1, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 37, 8, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, + 5, 1, 5, 1, 5, 0, 1, 2, 6, 0, 2, 4, 6, 8, 10, 0, 4, 1, 0, 2, 3, 1, 0, 12, + 13, 3, 0, 1, 1, 12, 12, 14, 14, 1, 0, 6, 11, 41, 0, 12, 1, 0, 0, 0, 2, + 21, 1, 0, 0, 0, 4, 36, 1, 0, 0, 0, 6, 38, 1, 0, 0, 0, 8, 40, 1, 0, 0, 0, + 10, 42, 1, 0, 0, 0, 12, 13, 3, 2, 1, 0, 13, 14, 5, 0, 0, 1, 14, 1, 1, 0, + 0, 0, 15, 16, 6, 1, -1, 0, 16, 17, 5, 4, 0, 0, 17, 18, 3, 2, 1, 0, 18, + 19, 5, 5, 0, 0, 19, 22, 1, 0, 0, 0, 20, 22, 3, 4, 2, 0, 21, 15, 1, 0, 0, + 0, 21, 20, 1, 0, 0, 0, 22, 28, 1, 0, 0, 0, 23, 24, 10, 3, 0, 0, 24, 25, + 7, 0, 0, 0, 25, 27, 3, 2, 1, 4, 26, 23, 1, 0, 0, 0, 27, 30, 1, 0, 0, 0, + 28, 26, 1, 0, 0, 0, 28, 29, 1, 0, 0, 0, 29, 3, 1, 0, 0, 0, 30, 28, 1, 0, + 0, 0, 31, 32, 3, 6, 3, 0, 32, 33, 3, 10, 5, 0, 33, 34, 3, 8, 4, 0, 34, + 37, 1, 0, 0, 0, 35, 37, 3, 6, 3, 0, 36, 31, 1, 0, 0, 0, 36, 35, 1, 0, 0, + 0, 37, 5, 1, 0, 0, 0, 38, 39, 7, 1, 0, 0, 39, 7, 1, 0, 0, 0, 40, 41, 7, + 2, 0, 0, 41, 9, 1, 0, 0, 0, 42, 43, 7, 3, 0, 0, 43, 11, 1, 0, 0, 0, 3, + 21, 28, 36, + } + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// QueryParserInit initializes any static state used to implement QueryParser. By default the +// static state used to implement the parser is lazily initialized during the first call to +// NewQueryParser(). You can call this function if you wish to initialize the static state ahead +// of time. +func QueryParserInit() { + staticData := &QueryParserStaticData + staticData.once.Do(queryParserInit) +} + +// NewQueryParser produces a new parser instance for the optional input antlr.TokenStream. +func NewQueryParser(input antlr.TokenStream) *QueryParser { + QueryParserInit() + this := new(QueryParser) + this.BaseParser = antlr.NewBaseParser(input) + staticData := &QueryParserStaticData + this.Interpreter = antlr.NewParserATNSimulator(this, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + this.RuleNames = staticData.RuleNames + this.LiteralNames = staticData.LiteralNames + this.SymbolicNames = staticData.SymbolicNames + this.GrammarFileName = "Query.g4" + + return this +} + +// QueryParser tokens. +const ( + QueryParserEOF = antlr.TokenEOF + QueryParserSTRING = 1 + QueryParserAND = 2 + QueryParserOR = 3 + QueryParserLPAREN = 4 + QueryParserRPAREN = 5 + QueryParserEQ = 6 + QueryParserNE = 7 + QueryParserGT = 8 + QueryParserGTE = 9 + QueryParserLT = 10 + QueryParserLTE = 11 + QueryParserIDENTIFIER = 12 + QueryParserNEG_IDENTIFIER = 13 + QueryParserENCODED_STRING = 14 + QueryParserLineTerminator = 15 + QueryParserWS = 16 +) + +// QueryParser rules. +const ( + QueryParserRULE_input = 0 + QueryParserRULE_query = 1 + QueryParserRULE_criteria = 2 + QueryParserRULE_key = 3 + QueryParserRULE_value = 4 + QueryParserRULE_op = 5 +) + +// IInputContext is an interface to support dynamic dispatch. +type IInputContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Query() IQueryContext + EOF() antlr.TerminalNode + + // IsInputContext differentiates from other interfaces. + IsInputContext() +} + +type InputContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyInputContext() *InputContext { + var p = new(InputContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_input + return p +} + +func InitEmptyInputContext(p *InputContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_input +} + +func (*InputContext) IsInputContext() {} + +func NewInputContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *InputContext { + var p = new(InputContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = QueryParserRULE_input + + return p +} + +func (s *InputContext) GetParser() antlr.Parser { return s.parser } + +func (s *InputContext) Query() IQueryContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQueryContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IQueryContext) +} + +func (s *InputContext) EOF() antlr.TerminalNode { + return s.GetToken(QueryParserEOF, 0) +} + +func (s *InputContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *InputContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *InputContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.EnterInput(s) + } +} + +func (s *InputContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.ExitInput(s) + } +} + +func (s *InputContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case QueryVisitor: + return t.VisitInput(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *QueryParser) Input() (localctx IInputContext) { + localctx = NewInputContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 0, QueryParserRULE_input) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(12) + p.query(0) + } + { + p.SetState(13) + p.Match(QueryParserEOF) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IQueryContext is an interface to support dynamic dispatch. +type IQueryContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + // IsQueryContext differentiates from other interfaces. + IsQueryContext() +} + +type QueryContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyQueryContext() *QueryContext { + var p = new(QueryContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_query + return p +} + +func InitEmptyQueryContext(p *QueryContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_query +} + +func (*QueryContext) IsQueryContext() {} + +func NewQueryContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *QueryContext { + var p = new(QueryContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = QueryParserRULE_query + + return p +} + +func (s *QueryContext) GetParser() antlr.Parser { return s.parser } + +func (s *QueryContext) CopyAll(ctx *QueryContext) { + s.CopyFrom(&ctx.BaseParserRuleContext) +} + +func (s *QueryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *QueryContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +type AtomQueryContext struct { + QueryContext +} + +func NewAtomQueryContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *AtomQueryContext { + var p = new(AtomQueryContext) + + InitEmptyQueryContext(&p.QueryContext) + p.parser = parser + p.CopyAll(ctx.(*QueryContext)) + + return p +} + +func (s *AtomQueryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *AtomQueryContext) Criteria() ICriteriaContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICriteriaContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ICriteriaContext) +} + +func (s *AtomQueryContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.EnterAtomQuery(s) + } +} + +func (s *AtomQueryContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.ExitAtomQuery(s) + } +} + +func (s *AtomQueryContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case QueryVisitor: + return t.VisitAtomQuery(s) + + default: + return t.VisitChildren(s) + } +} + +type PriorityQueryContext struct { + QueryContext +} + +func NewPriorityQueryContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *PriorityQueryContext { + var p = new(PriorityQueryContext) + + InitEmptyQueryContext(&p.QueryContext) + p.parser = parser + p.CopyAll(ctx.(*QueryContext)) + + return p +} + +func (s *PriorityQueryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PriorityQueryContext) LPAREN() antlr.TerminalNode { + return s.GetToken(QueryParserLPAREN, 0) +} + +func (s *PriorityQueryContext) Query() IQueryContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQueryContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IQueryContext) +} + +func (s *PriorityQueryContext) RPAREN() antlr.TerminalNode { + return s.GetToken(QueryParserRPAREN, 0) +} + +func (s *PriorityQueryContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.EnterPriorityQuery(s) + } +} + +func (s *PriorityQueryContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.ExitPriorityQuery(s) + } +} + +func (s *PriorityQueryContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case QueryVisitor: + return t.VisitPriorityQuery(s) + + default: + return t.VisitChildren(s) + } +} + +type OpQueryContext struct { + QueryContext + left IQueryContext + logicalOp antlr.Token + right IQueryContext +} + +func NewOpQueryContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *OpQueryContext { + var p = new(OpQueryContext) + + InitEmptyQueryContext(&p.QueryContext) + p.parser = parser + p.CopyAll(ctx.(*QueryContext)) + + return p +} + +func (s *OpQueryContext) GetLogicalOp() antlr.Token { return s.logicalOp } + +func (s *OpQueryContext) SetLogicalOp(v antlr.Token) { s.logicalOp = v } + +func (s *OpQueryContext) GetLeft() IQueryContext { return s.left } + +func (s *OpQueryContext) GetRight() IQueryContext { return s.right } + +func (s *OpQueryContext) SetLeft(v IQueryContext) { s.left = v } + +func (s *OpQueryContext) SetRight(v IQueryContext) { s.right = v } + +func (s *OpQueryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OpQueryContext) AllQuery() []IQueryContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IQueryContext); ok { + len++ + } + } + + tst := make([]IQueryContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IQueryContext); ok { + tst[i] = t.(IQueryContext) + i++ + } + } + + return tst +} + +func (s *OpQueryContext) Query(i int) IQueryContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IQueryContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IQueryContext) +} + +func (s *OpQueryContext) AND() antlr.TerminalNode { + return s.GetToken(QueryParserAND, 0) +} + +func (s *OpQueryContext) OR() antlr.TerminalNode { + return s.GetToken(QueryParserOR, 0) +} + +func (s *OpQueryContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.EnterOpQuery(s) + } +} + +func (s *OpQueryContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.ExitOpQuery(s) + } +} + +func (s *OpQueryContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case QueryVisitor: + return t.VisitOpQuery(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *QueryParser) Query() (localctx IQueryContext) { + return p.query(0) +} + +func (p *QueryParser) query(_p int) (localctx IQueryContext) { + var _parentctx antlr.ParserRuleContext = p.GetParserRuleContext() + + _parentState := p.GetState() + localctx = NewQueryContext(p, p.GetParserRuleContext(), _parentState) + var _prevctx IQueryContext = localctx + var _ antlr.ParserRuleContext = _prevctx // TODO: To prevent unused variable warning. + _startState := 2 + p.EnterRecursionRule(localctx, 2, QueryParserRULE_query, _p) + var _la int + + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(21) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case QueryParserLPAREN: + localctx = NewPriorityQueryContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + + { + p.SetState(16) + p.Match(QueryParserLPAREN) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(17) + p.query(0) + } + { + p.SetState(18) + p.Match(QueryParserRPAREN) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case QueryParserIDENTIFIER, QueryParserNEG_IDENTIFIER: + localctx = NewAtomQueryContext(p, localctx) + p.SetParserRuleContext(localctx) + _prevctx = localctx + { + p.SetState(20) + p.Criteria() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + p.GetParserRuleContext().SetStop(p.GetTokenStream().LT(-1)) + p.SetState(28) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 1, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + if p.GetParseListeners() != nil { + p.TriggerExitRuleEvent() + } + _prevctx = localctx + localctx = NewOpQueryContext(p, NewQueryContext(p, _parentctx, _parentState)) + localctx.(*OpQueryContext).left = _prevctx + + p.PushNewRecursionContext(localctx, _startState, QueryParserRULE_query) + p.SetState(23) + + if !(p.Precpred(p.GetParserRuleContext(), 3)) { + p.SetError(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 3)", "")) + goto errorExit + } + { + p.SetState(24) + + var _lt = p.GetTokenStream().LT(1) + + localctx.(*OpQueryContext).logicalOp = _lt + + _la = p.GetTokenStream().LA(1) + + if !(_la == QueryParserAND || _la == QueryParserOR) { + var _ri = p.GetErrorHandler().RecoverInline(p) + + localctx.(*OpQueryContext).logicalOp = _ri + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(25) + + var _x = p.query(4) + + localctx.(*OpQueryContext).right = _x + } + + } + p.SetState(30) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 1, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.UnrollRecursionContexts(_parentctx) + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ICriteriaContext is an interface to support dynamic dispatch. +type ICriteriaContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Key() IKeyContext + Op() IOpContext + Value() IValueContext + + // IsCriteriaContext differentiates from other interfaces. + IsCriteriaContext() +} + +type CriteriaContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCriteriaContext() *CriteriaContext { + var p = new(CriteriaContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_criteria + return p +} + +func InitEmptyCriteriaContext(p *CriteriaContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_criteria +} + +func (*CriteriaContext) IsCriteriaContext() {} + +func NewCriteriaContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CriteriaContext { + var p = new(CriteriaContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = QueryParserRULE_criteria + + return p +} + +func (s *CriteriaContext) GetParser() antlr.Parser { return s.parser } + +func (s *CriteriaContext) Key() IKeyContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IKeyContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IKeyContext) +} + +func (s *CriteriaContext) Op() IOpContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOpContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOpContext) +} + +func (s *CriteriaContext) Value() IValueContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IValueContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IValueContext) +} + +func (s *CriteriaContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CriteriaContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *CriteriaContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.EnterCriteria(s) + } +} + +func (s *CriteriaContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.ExitCriteria(s) + } +} + +func (s *CriteriaContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case QueryVisitor: + return t.VisitCriteria(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *QueryParser) Criteria() (localctx ICriteriaContext) { + localctx = NewCriteriaContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 4, QueryParserRULE_criteria) + p.SetState(36) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 2, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(31) + p.Key() + } + { + p.SetState(32) + p.Op() + } + { + p.SetState(33) + p.Value() + } + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(35) + p.Key() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IKeyContext is an interface to support dynamic dispatch. +type IKeyContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IDENTIFIER() antlr.TerminalNode + NEG_IDENTIFIER() antlr.TerminalNode + + // IsKeyContext differentiates from other interfaces. + IsKeyContext() +} + +type KeyContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyKeyContext() *KeyContext { + var p = new(KeyContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_key + return p +} + +func InitEmptyKeyContext(p *KeyContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_key +} + +func (*KeyContext) IsKeyContext() {} + +func NewKeyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *KeyContext { + var p = new(KeyContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = QueryParserRULE_key + + return p +} + +func (s *KeyContext) GetParser() antlr.Parser { return s.parser } + +func (s *KeyContext) IDENTIFIER() antlr.TerminalNode { + return s.GetToken(QueryParserIDENTIFIER, 0) +} + +func (s *KeyContext) NEG_IDENTIFIER() antlr.TerminalNode { + return s.GetToken(QueryParserNEG_IDENTIFIER, 0) +} + +func (s *KeyContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *KeyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *KeyContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.EnterKey(s) + } +} + +func (s *KeyContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.ExitKey(s) + } +} + +func (s *KeyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case QueryVisitor: + return t.VisitKey(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *QueryParser) Key() (localctx IKeyContext) { + localctx = NewKeyContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 6, QueryParserRULE_key) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(38) + _la = p.GetTokenStream().LA(1) + + if !(_la == QueryParserIDENTIFIER || _la == QueryParserNEG_IDENTIFIER) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IValueContext is an interface to support dynamic dispatch. +type IValueContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IDENTIFIER() antlr.TerminalNode + STRING() antlr.TerminalNode + ENCODED_STRING() antlr.TerminalNode + + // IsValueContext differentiates from other interfaces. + IsValueContext() +} + +type ValueContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyValueContext() *ValueContext { + var p = new(ValueContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_value + return p +} + +func InitEmptyValueContext(p *ValueContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_value +} + +func (*ValueContext) IsValueContext() {} + +func NewValueContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ValueContext { + var p = new(ValueContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = QueryParserRULE_value + + return p +} + +func (s *ValueContext) GetParser() antlr.Parser { return s.parser } + +func (s *ValueContext) IDENTIFIER() antlr.TerminalNode { + return s.GetToken(QueryParserIDENTIFIER, 0) +} + +func (s *ValueContext) STRING() antlr.TerminalNode { + return s.GetToken(QueryParserSTRING, 0) +} + +func (s *ValueContext) ENCODED_STRING() antlr.TerminalNode { + return s.GetToken(QueryParserENCODED_STRING, 0) +} + +func (s *ValueContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ValueContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ValueContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.EnterValue(s) + } +} + +func (s *ValueContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.ExitValue(s) + } +} + +func (s *ValueContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case QueryVisitor: + return t.VisitValue(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *QueryParser) Value() (localctx IValueContext) { + localctx = NewValueContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 8, QueryParserRULE_value) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(40) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&20482) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IOpContext is an interface to support dynamic dispatch. +type IOpContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EQ() antlr.TerminalNode + NE() antlr.TerminalNode + GT() antlr.TerminalNode + GTE() antlr.TerminalNode + LT() antlr.TerminalNode + LTE() antlr.TerminalNode + + // IsOpContext differentiates from other interfaces. + IsOpContext() +} + +type OpContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOpContext() *OpContext { + var p = new(OpContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_op + return p +} + +func InitEmptyOpContext(p *OpContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = QueryParserRULE_op +} + +func (*OpContext) IsOpContext() {} + +func NewOpContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OpContext { + var p = new(OpContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = QueryParserRULE_op + + return p +} + +func (s *OpContext) GetParser() antlr.Parser { return s.parser } + +func (s *OpContext) EQ() antlr.TerminalNode { + return s.GetToken(QueryParserEQ, 0) +} + +func (s *OpContext) NE() antlr.TerminalNode { + return s.GetToken(QueryParserNE, 0) +} + +func (s *OpContext) GT() antlr.TerminalNode { + return s.GetToken(QueryParserGT, 0) +} + +func (s *OpContext) GTE() antlr.TerminalNode { + return s.GetToken(QueryParserGTE, 0) +} + +func (s *OpContext) LT() antlr.TerminalNode { + return s.GetToken(QueryParserLT, 0) +} + +func (s *OpContext) LTE() antlr.TerminalNode { + return s.GetToken(QueryParserLTE, 0) +} + +func (s *OpContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OpContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *OpContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.EnterOp(s) + } +} + +func (s *OpContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(QueryListener); ok { + listenerT.ExitOp(s) + } +} + +func (s *OpContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case QueryVisitor: + return t.VisitOp(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *QueryParser) Op() (localctx IOpContext) { + localctx = NewOpContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 10, QueryParserRULE_op) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(42) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&4032) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +func (p *QueryParser) Sempred(localctx antlr.RuleContext, ruleIndex, predIndex int) bool { + switch ruleIndex { + case 1: + var t *QueryContext = nil + if localctx != nil { + t = localctx.(*QueryContext) + } + return p.Query_Sempred(t, predIndex) + + default: + panic("No predicate with index: " + fmt.Sprint(ruleIndex)) + } +} + +func (p *QueryParser) Query_Sempred(localctx antlr.RuleContext, predIndex int) bool { + switch predIndex { + case 0: + return p.Precpred(p.GetParserRuleContext(), 3) + + default: + panic("No predicate with index: " + fmt.Sprint(predIndex)) + } +} diff --git a/parser/query_visitor.go b/parser/query_visitor.go new file mode 100644 index 0000000..28d569a --- /dev/null +++ b/parser/query_visitor.go @@ -0,0 +1,33 @@ +// Code generated from Query.g4 by ANTLR 4.13.1. DO NOT EDIT. + +package parser // Query +import "github.com/antlr4-go/antlr/v4" + +// A complete Visitor for a parse tree produced by QueryParser. +type QueryVisitor interface { + antlr.ParseTreeVisitor + + // Visit a parse tree produced by QueryParser#input. + VisitInput(ctx *InputContext) interface{} + + // Visit a parse tree produced by QueryParser#atomQuery. + VisitAtomQuery(ctx *AtomQueryContext) interface{} + + // Visit a parse tree produced by QueryParser#priorityQuery. + VisitPriorityQuery(ctx *PriorityQueryContext) interface{} + + // Visit a parse tree produced by QueryParser#opQuery. + VisitOpQuery(ctx *OpQueryContext) interface{} + + // Visit a parse tree produced by QueryParser#criteria. + VisitCriteria(ctx *CriteriaContext) interface{} + + // Visit a parse tree produced by QueryParser#key. + VisitKey(ctx *KeyContext) interface{} + + // Visit a parse tree produced by QueryParser#value. + VisitValue(ctx *ValueContext) interface{} + + // Visit a parse tree produced by QueryParser#op. + VisitOp(ctx *OpContext) interface{} +} diff --git a/structs.go b/structs.go index 4d16345..9fa8f38 100644 --- a/structs.go +++ b/structs.go @@ -3,7 +3,7 @@ package mgs // Query is a structure that holds information about DB request. type Query struct { // Filter is a document containing query operators. - Filter map[string]interface{} + Filter map[string][]interface{} // Sort is a document specifying the order in which documents should be returned. Sort map[string]int // Limit is the maximum number of documents to return. diff --git a/visitor.go b/visitor.go new file mode 100644 index 0000000..0dda3a5 --- /dev/null +++ b/visitor.go @@ -0,0 +1,100 @@ +package mgs + +import ( + "fmt" + "strings" + + "github.com/ajclopez/mgs/parser" + "github.com/antlr4-go/antlr/v4" +) + +type Visitor struct { + *parser.BaseQueryVisitor + Caster *map[string]CastType +} + +func NewGeneratorVisitor(caster *map[string]CastType) *Visitor { + return &Visitor{ + Caster: caster, + } +} + +func (v *Visitor) Visit(tree antlr.ParseTree) interface{} { + return tree.Accept(v) +} + +func (v *Visitor) VisitInput(ctx *parser.InputContext) interface{} { + if ctx == nil { + return ErrQueryVisitor + } + return v.Visit(ctx.Query()) +} + +func (v *Visitor) VisitPriorityQuery(ctx *parser.PriorityQueryContext) interface{} { + if ctx == nil { + return ErrQueryVisitor + } + return v.Visit(ctx.Query()) +} + +func (v *Visitor) VisitAtomQuery(ctx *parser.AtomQueryContext) interface{} { + if ctx == nil { + return ErrQueryVisitor + } + return v.Visit(ctx.Criteria()) +} + +func (v *Visitor) VisitOpQuery(ctx *parser.OpQueryContext) interface{} { + + if ctx == nil { + return ErrQueryVisitor + } + + left := v.Visit(ctx.GetLeft()) + right := v.Visit(ctx.GetRight()) + op := ctx.GetLogicalOp().GetText() + var operator string + + switch op { + case "and", "AND": + operator = "$and" + case "or", "OR": + operator = "$or" + default: + operator = "$and" + } + + return map[string][]interface{}{ + operator: { + left, + right, + }, + } +} + +func (v *Visitor) VisitCriteria(ctx *parser.CriteriaContext) interface{} { + + if ctx == nil { + return ErrQueryVisitor + } + + key := ctx.Key() + if key == nil { + return ErrQueryVisitor + } + + op := ctx.Op() + if op == nil { + return ErrQueryVisitor + } + + value := ctx.Value() + if value == nil { + return ErrQueryVisitor + } + + expression := fmt.Sprintf("%s%s%s", strings.TrimSpace(key.GetText()), strings.TrimSpace(op.GetText()), strings.TrimSpace(value.GetText())) + criteria := CriteriaParser(expression, v.Caster) + + return Convert(criteria) +} diff --git a/visitor_test.go b/visitor_test.go new file mode 100644 index 0000000..b2aa4ba --- /dev/null +++ b/visitor_test.go @@ -0,0 +1,62 @@ +package mgs + +import ( + "testing" + + "github.com/ajclopez/mgs/parser" + "github.com/antlr4-go/antlr/v4" + + "github.com/stretchr/testify/assert" +) + +func TestShouldReturnErrForVisitInputWhenContextIsNil(t *testing.T) { + caster := map[string]CastType{} + visitor := NewGeneratorVisitor(&caster) + result := visitor.VisitInput(nil) + + assert.Equal(t, ErrQueryVisitor, result) +} + +func TestShouldReturnErrForVisitPriorityQueryWhenContextIsNil(t *testing.T) { + caster := map[string]CastType{} + visitor := NewGeneratorVisitor(&caster) + result := visitor.VisitPriorityQuery(nil) + + assert.Equal(t, ErrQueryVisitor, result) +} + +func TestShouldReturnErrForVisitAtomQueryWhenContextIsNil(t *testing.T) { + caster := map[string]CastType{} + visitor := NewGeneratorVisitor(&caster) + result := visitor.VisitAtomQuery(nil) + + assert.Equal(t, ErrQueryVisitor, result) +} + +func TestShouldReturnErrForVisitOpQueryWhenContextIsNil(t *testing.T) { + caster := map[string]CastType{} + visitor := NewGeneratorVisitor(&caster) + result := visitor.VisitOpQuery(nil) + + assert.Equal(t, ErrQueryVisitor, result) +} + +func TestShouldReturnErrForVisitCriteriaWhenContextIsNil(t *testing.T) { + caster := map[string]CastType{} + visitor := NewGeneratorVisitor(&caster) + result := visitor.VisitCriteria(nil) + + assert.Equal(t, ErrQueryVisitor, result) +} + +func TestShouldReturnErrForVisitCriteriaWhenKeyContextIsNil(t *testing.T) { + caster := map[string]CastType{} + visitor := NewGeneratorVisitor(&caster) + + criteriaContext := &parser.CriteriaContext{ + BaseParserRuleContext: *antlr.NewBaseParserRuleContext(nil, -1), + } + result := visitor.VisitCriteria(criteriaContext) + + assert.Equal(t, ErrQueryVisitor, result) +}