github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/flameql/parse.go (about) 1 package flameql 2 3 import ( 4 "regexp" 5 "sort" 6 "strings" 7 ) 8 9 // ParseQuery parses a string of $app_name<{<$tag_matchers>}> form. 10 func ParseQuery(s string) (*Query, error) { 11 s = strings.TrimSpace(s) 12 q := Query{q: s} 13 14 for offset, c := range s { 15 switch c { 16 case '{': 17 if offset == 0 { 18 return nil, ErrAppNameIsRequired 19 } 20 if s[len(s)-1] != '}' { 21 return nil, newErr(ErrInvalidQuerySyntax, "expected } at the end") 22 } 23 m, err := ParseMatchers(s[offset+1 : len(s)-1]) 24 if err != nil { 25 return nil, err 26 } 27 q.AppName = s[:offset] 28 q.Matchers = m 29 return &q, nil 30 default: 31 if !IsAppNameRuneAllowed(c) { 32 return nil, newErr(ErrInvalidAppName, s[:offset+1]) 33 } 34 } 35 } 36 37 if len(s) == 0 { 38 return nil, ErrAppNameIsRequired 39 } 40 41 q.AppName = s 42 return &q, nil 43 } 44 45 // ParseMatchers parses a string of $tag_matcher<,$tag_matchers> form. 46 func ParseMatchers(s string) ([]*TagMatcher, error) { 47 var matchers []*TagMatcher 48 for _, t := range split(s) { 49 if t == "" { 50 continue 51 } 52 m, err := ParseMatcher(strings.TrimSpace(t)) 53 if err != nil { 54 return nil, err 55 } 56 matchers = append(matchers, m) 57 } 58 if len(matchers) == 0 && len(s) != 0 { 59 return nil, newErr(ErrInvalidMatchersSyntax, s) 60 } 61 sort.Sort(ByPriority(matchers)) 62 return matchers, nil 63 } 64 65 // ParseMatcher parses a string of $tag_key$op"$tag_value" form, 66 // where $op is one of the supported match operators. 67 func ParseMatcher(s string) (*TagMatcher, error) { 68 var tm TagMatcher 69 var offset int 70 var c rune 71 72 loop: 73 for offset, c = range s { 74 r := len(s) - (offset + 1) 75 switch c { 76 case '=': 77 switch { 78 case r <= 2: 79 return nil, newErr(ErrInvalidTagValueSyntax, s) 80 case s[offset+1] == '"': 81 tm.Op = OpEqual 82 case s[offset+1] == '~': 83 if r <= 3 { 84 return nil, newErr(ErrInvalidTagValueSyntax, s) 85 } 86 tm.Op = OpEqualRegex 87 default: 88 // Just for more meaningful error message. 89 if s[offset+2] != '"' { 90 return nil, newErr(ErrInvalidTagValueSyntax, s) 91 } 92 return nil, newErr(ErrUnknownOp, s) 93 } 94 break loop 95 case '!': 96 if r <= 3 { 97 return nil, newErr(ErrInvalidTagValueSyntax, s) 98 } 99 switch s[offset+1] { 100 case '=': 101 tm.Op = OpNotEqual 102 case '~': 103 tm.Op = OpNotEqualRegex 104 default: 105 return nil, newErr(ErrUnknownOp, s) 106 } 107 break loop 108 default: 109 if !IsTagKeyRuneAllowed(c) { 110 return nil, newInvalidTagKeyRuneError(s, c) 111 } 112 } 113 } 114 115 k := s[:offset] 116 if IsTagKeyReserved(k) { 117 return nil, newErr(ErrTagKeyReserved, k) 118 } 119 120 var v string 121 var ok bool 122 switch tm.Op { 123 default: 124 return nil, newErr(ErrMatchOperatorIsRequired, s) 125 case OpEqual: 126 v, ok = unquote(s[offset+1:]) 127 case OpNotEqual, OpEqualRegex, OpNotEqualRegex: 128 v, ok = unquote(s[offset+2:]) 129 } 130 if !ok { 131 return nil, newErr(ErrInvalidTagValueSyntax, v) 132 } 133 134 // Compile regex, if applicable. 135 switch tm.Op { 136 case OpEqualRegex, OpNotEqualRegex: 137 r, err := regexp.Compile(v) 138 if err != nil { 139 return nil, newErr(err, v) 140 } 141 tm.R = r 142 } 143 144 tm.Key = k 145 tm.Value = v 146 return &tm, nil 147 } 148 149 func unquote(s string) (string, bool) { 150 if s[0] != '"' || s[len(s)-1] != '"' { 151 return s, false 152 } 153 return s[1 : len(s)-1], true 154 } 155 156 func split(s string) []string { 157 var r []string 158 var x int 159 var y bool 160 for i := 0; i < len(s); i++ { 161 switch { 162 case s[i] == ',' && !y: 163 r = append(r, s[x:i]) 164 x = i + 1 165 case s[i] == '"': 166 if y && i > 0 && s[i-1] != '\\' { 167 y = false 168 continue 169 } 170 y = true 171 } 172 } 173 return append(r, s[x:]) 174 }