blob: 05de34c1d6321b5e357fe702539ce5172ede819c [file] [log] [blame]
// 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
}