github.com/jgbaldwinbrown/perf@v0.1.1/benchproc/internal/parse/projection.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 ( 8 "fmt" 9 "strings" 10 ) 11 12 // A Field is one element in a projection expression. It represents 13 // extracting a single dimension of a benchmark result and applying an 14 // order to it. 15 type Field struct { 16 Key string 17 18 // Order is the sort order for this field. This can be 19 // "first", meaning to sort by order of first appearance; 20 // "fixed", meaning to use the explicit value order in Fixed; 21 // or a named sort order. 22 Order string 23 24 // Fixed gives the explicit value order for "fixed" ordering. 25 // If a record's value is not in this list, the record should 26 // be filtered out. Otherwise, values should be sorted 27 // according to their order in this list. 28 Fixed []string 29 30 // KeyOff and OrderOff give the byte offsets of the key and 31 // order, for error reporting. 32 KeyOff, OrderOff int 33 } 34 35 // String returns Projection as a valid projection expression. 36 func (p Field) String() string { 37 switch p.Order { 38 case "first": 39 return quoteWord(p.Key) 40 case "fixed": 41 words := make([]string, 0, len(p.Fixed)) 42 for _, word := range p.Fixed { 43 words = append(words, quoteWord(word)) 44 } 45 return fmt.Sprintf("%s@(%s)", quoteWord(p.Key), strings.Join(words, " ")) 46 } 47 return fmt.Sprintf("%s@%s", quoteWord(p.Key), quoteWord(p.Order)) 48 } 49 50 // ParseProjection parses a projection expression into a tuple of 51 // Fields. 52 func ParseProjection(q string) ([]Field, error) { 53 // Parse each projection field. 54 var fields []Field 55 toks := newTokenizer(q) 56 for { 57 // Peek at the next token. 58 tok, toks2 := toks.keyOrOp() 59 if tok.Kind == 0 { 60 // No more fields. 61 break 62 } else if tok.Kind == ',' && len(fields) > 0 { 63 // Consume optional separating comma. 64 toks = toks2 65 } 66 67 var f Field 68 f, toks = parseField(toks) 69 fields = append(fields, f) 70 } 71 toks.end() 72 if toks.errt.err != nil { 73 return nil, toks.errt.err 74 } 75 return fields, nil 76 } 77 78 func parseField(toks tokenizer) (Field, tokenizer) { 79 var f Field 80 81 // Consume key. 82 key, toks2 := toks.keyOrOp() 83 if !(key.Kind == 'w' || key.Kind == 'q') { 84 _, toks = toks.error("expected key") 85 return f, toks 86 } 87 toks = toks2 88 f.Key = key.Tok 89 f.KeyOff = key.Off 90 91 // Consume optional sort order. 92 f.Order = "first" 93 f.OrderOff = key.Off + len(key.Tok) 94 sep, toks2 := toks.keyOrOp() 95 if sep.Kind != '@' { 96 // No sort order. 97 return f, toks 98 } 99 toks = toks2 100 101 // Is it a named sort order? 102 order, toks2 := toks.keyOrOp() 103 f.OrderOff = order.Off 104 if order.Kind == 'w' || order.Kind == 'q' { 105 f.Order = order.Tok 106 return f, toks2 107 } 108 // Or a fixed sort order? 109 if order.Kind == '(' { 110 f.Order = "fixed" 111 toks = toks2 112 for { 113 t, toks2 := toks.keyOrOp() 114 if t.Kind == 'w' || t.Kind == 'q' { 115 toks = toks2 116 f.Fixed = append(f.Fixed, t.Tok) 117 } else if t.Kind == ')' { 118 if len(f.Fixed) == 0 { 119 _, toks = toks.error("nothing to match") 120 } else { 121 toks = toks2 122 } 123 break 124 } else { 125 _, toks = toks.error("missing )") 126 break 127 } 128 } 129 return f, toks 130 } 131 // Bad sort order syntax. 132 _, toks = toks.error("expected named sort order or parenthesized list") 133 return f, toks 134 }