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  }