| // Copyright 2022 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package parse | |
| import ( | |
| "fmt" | |
| "strings" | |
| ) | |
| // A Field is one element in a projection expression. It represents | |
| // extracting a single dimension of a benchmark result and applying an | |
| // order to it. | |
| type Field struct { | |
| Key string | |
| // Order is the sort order for this field. This can be | |
| // "first", meaning to sort by order of first appearance; | |
| // "fixed", meaning to use the explicit value order in Fixed; | |
| // or a named sort order. | |
| Order string | |
| // Fixed gives the explicit value order for "fixed" ordering. | |
| // If a record's value is not in this list, the record should | |
| // be filtered out. Otherwise, values should be sorted | |
| // according to their order in this list. | |
| Fixed []string | |
| // KeyOff and OrderOff give the byte offsets of the key and | |
| // order, for error reporting. | |
| KeyOff, OrderOff int | |
| } | |
| // String returns Projection as a valid projection expression. | |
| func (p Field) String() string { | |
| switch p.Order { | |
| case "first": | |
| return quoteWord(p.Key) | |
| case "fixed": | |
| words := make([]string, 0, len(p.Fixed)) | |
| for _, word := range p.Fixed { | |
| words = append(words, quoteWord(word)) | |
| } | |
| return fmt.Sprintf("%s@(%s)", quoteWord(p.Key), strings.Join(words, " ")) | |
| } | |
| return fmt.Sprintf("%s@%s", quoteWord(p.Key), quoteWord(p.Order)) | |
| } | |
| // ParseProjection parses a projection expression into a tuple of | |
| // Fields. | |
| func ParseProjection(q string) ([]Field, error) { | |
| // Parse each projection field. | |
| var fields []Field | |
| toks := newTokenizer(q) | |
| for { | |
| // Peek at the next token. | |
| tok, toks2 := toks.keyOrOp() | |
| if tok.Kind == 0 { | |
| // No more fields. | |
| break | |
| } else if tok.Kind == ',' && len(fields) > 0 { | |
| // Consume optional separating comma. | |
| toks = toks2 | |
| } | |
| var f Field | |
| f, toks = parseField(toks) | |
| fields = append(fields, f) | |
| } | |
| toks.end() | |
| if toks.errt.err != nil { | |
| return nil, toks.errt.err | |
| } | |
| return fields, nil | |
| } | |
| func parseField(toks tokenizer) (Field, tokenizer) { | |
| var f Field | |
| // Consume key. | |
| key, toks2 := toks.keyOrOp() | |
| if !(key.Kind == 'w' || key.Kind == 'q') { | |
| _, toks = toks.error("expected key") | |
| return f, toks | |
| } | |
| toks = toks2 | |
| f.Key = key.Tok | |
| f.KeyOff = key.Off | |
| // Consume optional sort order. | |
| f.Order = "first" | |
| f.OrderOff = key.Off + len(key.Tok) | |
| sep, toks2 := toks.keyOrOp() | |
| if sep.Kind != '@' { | |
| // No sort order. | |
| return f, toks | |
| } | |
| toks = toks2 | |
| // Is it a named sort order? | |
| order, toks2 := toks.keyOrOp() | |
| f.OrderOff = order.Off | |
| if order.Kind == 'w' || order.Kind == 'q' { | |
| f.Order = order.Tok | |
| return f, toks2 | |
| } | |
| // Or a fixed sort order? | |
| if order.Kind == '(' { | |
| f.Order = "fixed" | |
| toks = toks2 | |
| for { | |
| t, toks2 := toks.keyOrOp() | |
| if t.Kind == 'w' || t.Kind == 'q' { | |
| toks = toks2 | |
| f.Fixed = append(f.Fixed, t.Tok) | |
| } else if t.Kind == ')' { | |
| if len(f.Fixed) == 0 { | |
| _, toks = toks.error("nothing to match") | |
| } else { | |
| toks = toks2 | |
| } | |
| break | |
| } else { | |
| _, toks = toks.error("missing )") | |
| break | |
| } | |
| } | |
| return f, toks | |
| } | |
| // Bad sort order syntax. | |
| _, toks = toks.error("expected named sort order or parenthesized list") | |
| return f, toks | |
| } |