github.com/jgbaldwinbrown/perf@v0.1.1/benchproc/internal/parse/filter.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package parse
     6  
     7  import "strconv"
     8  
     9  // ParseFilter parses a filter expression into a Filter tree.
    10  func ParseFilter(q string) (Filter, error) {
    11  	toks := newTokenizer(q)
    12  	p := parser{}
    13  	query, toks := p.expr(toks)
    14  	toks.end()
    15  	if toks.errt.err != nil {
    16  		return nil, toks.errt.err
    17  	}
    18  	return query, nil
    19  }
    20  
    21  type parser struct{}
    22  
    23  func (p *parser) error(toks tokenizer, msg string) tokenizer {
    24  	_, toks = toks.error(msg)
    25  	return toks
    26  }
    27  
    28  func (p *parser) expr(toks tokenizer) (Filter, tokenizer) {
    29  	var terms []Filter
    30  	for {
    31  		var q Filter
    32  		q, toks = p.andExpr(toks)
    33  		terms = append(terms, q)
    34  		op, toks2 := toks.keyOrOp()
    35  		if op.Kind != 'O' {
    36  			break
    37  		}
    38  		toks = toks2
    39  	}
    40  	if len(terms) == 1 {
    41  		return terms[0], toks
    42  	}
    43  	return &FilterOp{OpOr, terms}, toks
    44  }
    45  
    46  func (p *parser) andExpr(toks tokenizer) (Filter, tokenizer) {
    47  	var q Filter
    48  	q, toks = p.match(toks)
    49  	terms := []Filter{q}
    50  loop:
    51  	for {
    52  		op, toks2 := toks.keyOrOp()
    53  		switch op.Kind {
    54  		case 'A':
    55  			// "AND" between matches is the same as no
    56  			// operator. Skip.
    57  			toks = toks2
    58  			continue
    59  		case '(', '-', '*', 'w', 'q':
    60  			q, toks = p.match(toks)
    61  			terms = append(terms, q)
    62  		case ')', 'O', 0:
    63  			break loop
    64  		default:
    65  			return nil, p.error(toks, "unexpected "+strconv.Quote(op.Tok))
    66  		}
    67  	}
    68  	if len(terms) == 1 {
    69  		return terms[0], toks
    70  	}
    71  	return &FilterOp{OpAnd, terms}, toks
    72  }
    73  
    74  func (p *parser) match(start tokenizer) (Filter, tokenizer) {
    75  	tok, rest := start.keyOrOp()
    76  	switch tok.Kind {
    77  	case '(':
    78  		q, rest := p.expr(rest)
    79  		op, toks2 := rest.keyOrOp()
    80  		if op.Kind != ')' {
    81  			return nil, p.error(rest, "missing \")\"")
    82  		}
    83  		return q, toks2
    84  	case '-':
    85  		q, rest := p.match(rest)
    86  		q = &FilterOp{OpNot, []Filter{q}}
    87  		return q, rest
    88  	case '*':
    89  		q := &FilterOp{OpAnd, nil}
    90  		return q, rest
    91  	case 'w', 'q':
    92  		off := tok.Off
    93  		key := tok.Tok
    94  		op, toks2 := rest.keyOrOp()
    95  		if op.Kind != ':' {
    96  			// TODO: Support other operators
    97  			return nil, p.error(start, "expected key:value")
    98  		}
    99  		rest = toks2
   100  		val, rest := rest.valueOrOp()
   101  		switch val.Kind {
   102  		default:
   103  			return nil, p.error(start, "expected key:value")
   104  		case 'w', 'q', 'r':
   105  			return p.mkMatch(off, key, val), rest
   106  		case '(':
   107  			var terms []Filter
   108  			for {
   109  				val, toks2 := rest.valueOrOp()
   110  				switch val.Kind {
   111  				default:
   112  					return nil, p.error(rest, "expected value")
   113  				case 'w', 'q', 'r':
   114  					terms = append(terms, p.mkMatch(off, key, val))
   115  				}
   116  				rest = toks2
   117  
   118  				// Consume "OR" or ")"
   119  				val, toks2 = rest.valueOrOp()
   120  				switch val.Kind {
   121  				default:
   122  					return nil, p.error(rest, "value list must be separated by OR")
   123  				case ')':
   124  					return &FilterOp{OpOr, terms}, toks2
   125  				case 'O':
   126  					// Do nothing
   127  				}
   128  				rest = toks2
   129  			}
   130  		}
   131  	}
   132  	return nil, p.error(start, "expected key:value or subexpression")
   133  }
   134  
   135  func (p *parser) mkMatch(off int, key string, val tok) Filter {
   136  	switch val.Kind {
   137  	case 'w', 'q':
   138  		// Literal match.
   139  		return &FilterMatch{key, nil, val.Tok, off}
   140  	case 'r':
   141  		// Regexp match.
   142  		return &FilterMatch{key, val.Regexp, "", off}
   143  	default:
   144  		panic("non-word token")
   145  	}
   146  }