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  }