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 }