| // Copyright 2023 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. |
| |
| // Code generated by "gen.bash" from internal/trace/v2; DO NOT EDIT. |
| |
| //go:build go1.21 |
| |
| package raw |
| |
| import ( |
| "bufio" |
| "fmt" |
| "io" |
| "strconv" |
| "strings" |
| "unicode" |
| |
| "golang.org/x/exp/trace/internal/event" |
| "golang.org/x/exp/trace/internal/version" |
| ) |
| |
| // TextReader parses a text format trace with only very basic validation |
| // into an event stream. |
| type TextReader struct { |
| v version.Version |
| specs []event.Spec |
| names map[string]event.Type |
| s *bufio.Scanner |
| } |
| |
| // NewTextReader creates a new reader for the trace text format. |
| func NewTextReader(r io.Reader) (*TextReader, error) { |
| tr := &TextReader{s: bufio.NewScanner(r)} |
| line, err := tr.nextLine() |
| if err != nil { |
| return nil, err |
| } |
| trace, line := readToken(line) |
| if trace != "Trace" { |
| return nil, fmt.Errorf("failed to parse header") |
| } |
| gover, line := readToken(line) |
| if !strings.HasPrefix(gover, "Go1.") { |
| return nil, fmt.Errorf("failed to parse header Go version") |
| } |
| rawv, err := strconv.ParseUint(gover[len("Go1."):], 10, 64) |
| if err != nil { |
| return nil, fmt.Errorf("failed to parse header Go version: %v", err) |
| } |
| v := version.Version(rawv) |
| if !v.Valid() { |
| return nil, fmt.Errorf("unknown or unsupported Go version 1.%d", v) |
| } |
| tr.v = v |
| tr.specs = v.Specs() |
| tr.names = event.Names(tr.specs) |
| for _, r := range line { |
| if !unicode.IsSpace(r) { |
| return nil, fmt.Errorf("encountered unexpected non-space at the end of the header: %q", line) |
| } |
| } |
| return tr, nil |
| } |
| |
| // Version returns the version of the trace that we're reading. |
| func (r *TextReader) Version() version.Version { |
| return r.v |
| } |
| |
| // ReadEvent reads and returns the next trace event in the text stream. |
| func (r *TextReader) ReadEvent() (Event, error) { |
| line, err := r.nextLine() |
| if err != nil { |
| return Event{}, err |
| } |
| evStr, line := readToken(line) |
| ev, ok := r.names[evStr] |
| if !ok { |
| return Event{}, fmt.Errorf("unidentified event: %s", evStr) |
| } |
| spec := r.specs[ev] |
| args, err := readArgs(line, spec.Args) |
| if err != nil { |
| return Event{}, fmt.Errorf("reading args for %s: %v", evStr, err) |
| } |
| if spec.IsStack { |
| len := int(args[1]) |
| for i := 0; i < len; i++ { |
| line, err := r.nextLine() |
| if err == io.EOF { |
| return Event{}, fmt.Errorf("unexpected EOF while reading stack: args=%v", args) |
| } |
| if err != nil { |
| return Event{}, err |
| } |
| frame, err := readArgs(line, frameFields) |
| if err != nil { |
| return Event{}, err |
| } |
| args = append(args, frame...) |
| } |
| } |
| var data []byte |
| if spec.HasData { |
| line, err := r.nextLine() |
| if err == io.EOF { |
| return Event{}, fmt.Errorf("unexpected EOF while reading data for %s: args=%v", evStr, args) |
| } |
| if err != nil { |
| return Event{}, err |
| } |
| data, err = readData(line) |
| if err != nil { |
| return Event{}, err |
| } |
| } |
| return Event{ |
| Version: r.v, |
| Ev: ev, |
| Args: args, |
| Data: data, |
| }, nil |
| } |
| |
| func (r *TextReader) nextLine() (string, error) { |
| for { |
| if !r.s.Scan() { |
| if err := r.s.Err(); err != nil { |
| return "", err |
| } |
| return "", io.EOF |
| } |
| txt := r.s.Text() |
| tok, _ := readToken(txt) |
| if tok == "" { |
| continue // Empty line or comment. |
| } |
| return txt, nil |
| } |
| } |
| |
| var frameFields = []string{"pc", "func", "file", "line"} |
| |
| func readArgs(s string, names []string) ([]uint64, error) { |
| var args []uint64 |
| for _, name := range names { |
| arg, value, rest, err := readArg(s) |
| if err != nil { |
| return nil, err |
| } |
| if arg != name { |
| return nil, fmt.Errorf("expected argument %q, but got %q", name, arg) |
| } |
| args = append(args, value) |
| s = rest |
| } |
| for _, r := range s { |
| if !unicode.IsSpace(r) { |
| return nil, fmt.Errorf("encountered unexpected non-space at the end of an event: %q", s) |
| } |
| } |
| return args, nil |
| } |
| |
| func readArg(s string) (arg string, value uint64, rest string, err error) { |
| var tok string |
| tok, rest = readToken(s) |
| if len(tok) == 0 { |
| return "", 0, s, fmt.Errorf("no argument") |
| } |
| parts := strings.SplitN(tok, "=", 2) |
| if len(parts) < 2 { |
| return "", 0, s, fmt.Errorf("malformed argument: %q", tok) |
| } |
| arg = parts[0] |
| value, err = strconv.ParseUint(parts[1], 10, 64) |
| if err != nil { |
| return arg, value, s, fmt.Errorf("failed to parse argument value %q for arg %q", parts[1], parts[0]) |
| } |
| return |
| } |
| |
| func readToken(s string) (token, rest string) { |
| tkStart := -1 |
| for i, r := range s { |
| if r == '#' { |
| return "", "" |
| } |
| if !unicode.IsSpace(r) { |
| tkStart = i |
| break |
| } |
| } |
| if tkStart < 0 { |
| return "", "" |
| } |
| tkEnd := -1 |
| for i, r := range s[tkStart:] { |
| if unicode.IsSpace(r) || r == '#' { |
| tkEnd = i + tkStart |
| break |
| } |
| } |
| if tkEnd < 0 { |
| return s[tkStart:], "" |
| } |
| return s[tkStart:tkEnd], s[tkEnd:] |
| } |
| |
| func readData(line string) ([]byte, error) { |
| parts := strings.SplitN(line, "=", 2) |
| if len(parts) < 2 || strings.TrimSpace(parts[0]) != "data" { |
| return nil, fmt.Errorf("malformed data: %q", line) |
| } |
| data, err := strconv.Unquote(strings.TrimSpace(parts[1])) |
| if err != nil { |
| return nil, fmt.Errorf("failed to parse data: %q: %v", line, err) |
| } |
| return []byte(data), nil |
| } |