github.com/goplus/gop@v1.2.6/x/jsonrpc2/internal/stack/parse.go (about)

     1  // Copyright 2020 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 stack
     6  
     7  import (
     8  	"bufio"
     9  	"io"
    10  	"regexp"
    11  	"strconv"
    12  )
    13  
    14  var (
    15  	reBlank     = regexp.MustCompile(`^\s*$`)
    16  	reGoroutine = regexp.MustCompile(`^\s*goroutine (\d+) \[([^\]]*)\]:\s*$`)
    17  	reCall      = regexp.MustCompile(`^\s*` +
    18  		`(created by )?` + //marker
    19  		`(([\w/.]+/)?[\w]+)\.` + //package
    20  		`(\(([^:.)]*)\)\.)?` + //optional type
    21  		`([\w\.]+)` + //function
    22  		`(\(.*\))?` + // args
    23  		`\s*$`)
    24  	rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`)
    25  )
    26  
    27  // Scanner splits an input stream into lines in a way that is consumable by
    28  // the parser.
    29  type Scanner struct {
    30  	lines *bufio.Scanner
    31  	done  bool
    32  }
    33  
    34  // NewScanner creates a scanner on top of a reader.
    35  func NewScanner(r io.Reader) *Scanner {
    36  	s := &Scanner{
    37  		lines: bufio.NewScanner(r),
    38  	}
    39  	s.Skip() // prefill
    40  	return s
    41  }
    42  
    43  // Peek returns the next line without consuming it.
    44  func (s *Scanner) Peek() string {
    45  	if s.done {
    46  		return ""
    47  	}
    48  	return s.lines.Text()
    49  }
    50  
    51  // Skip consumes the next line without looking at it.
    52  // Normally used after it has already been looked at using Peek.
    53  func (s *Scanner) Skip() {
    54  	if !s.lines.Scan() {
    55  		s.done = true
    56  	}
    57  }
    58  
    59  // Next consumes and returns the next line.
    60  func (s *Scanner) Next() string {
    61  	line := s.Peek()
    62  	s.Skip()
    63  	return line
    64  }
    65  
    66  // Done returns true if the scanner has reached the end of the underlying
    67  // stream.
    68  func (s *Scanner) Done() bool {
    69  	return s.done
    70  }
    71  
    72  // Err returns true if the scanner has reached the end of the underlying
    73  // stream.
    74  func (s *Scanner) Err() error {
    75  	return s.lines.Err()
    76  }
    77  
    78  // Match returns the submatchs of the regular expression against the next line.
    79  // If it matched the line is also consumed.
    80  func (s *Scanner) Match(re *regexp.Regexp) []string {
    81  	if s.done {
    82  		return nil
    83  	}
    84  	match := re.FindStringSubmatch(s.Peek())
    85  	if match != nil {
    86  		s.Skip()
    87  	}
    88  	return match
    89  }
    90  
    91  // SkipBlank skips any number of pure whitespace lines.
    92  func (s *Scanner) SkipBlank() {
    93  	for !s.done {
    94  		line := s.Peek()
    95  		if len(line) != 0 && !reBlank.MatchString(line) {
    96  			return
    97  		}
    98  		s.Skip()
    99  	}
   100  }
   101  
   102  // Parse the current contiguous block of goroutine stack traces until the
   103  // scanned content no longer matches.
   104  func Parse(scanner *Scanner) (Dump, error) {
   105  	dump := Dump{}
   106  	for {
   107  		gr, ok := parseGoroutine(scanner)
   108  		if !ok {
   109  			return dump, nil
   110  		}
   111  		dump = append(dump, gr)
   112  	}
   113  }
   114  
   115  func parseGoroutine(scanner *Scanner) (Goroutine, bool) {
   116  	match := scanner.Match(reGoroutine)
   117  	if match == nil {
   118  		return Goroutine{}, false
   119  	}
   120  	id, _ := strconv.ParseInt(match[1], 0, 32)
   121  	gr := Goroutine{
   122  		ID:    int(id),
   123  		State: match[2],
   124  	}
   125  	for {
   126  		frame, ok := parseFrame(scanner)
   127  		if !ok {
   128  			scanner.SkipBlank()
   129  			return gr, true
   130  		}
   131  		if frame.Position.Filename != "" {
   132  			gr.Stack = append(gr.Stack, frame)
   133  		}
   134  	}
   135  }
   136  
   137  func parseFrame(scanner *Scanner) (Frame, bool) {
   138  	fun, ok := parseFunction(scanner)
   139  	if !ok {
   140  		return Frame{}, false
   141  	}
   142  	frame := Frame{
   143  		Function: fun,
   144  	}
   145  	frame.Position, ok = parsePosition(scanner)
   146  	// if ok is false, then this is a broken state.
   147  	// we got the func but not the file that must follow
   148  	// the consumed line can be recovered from the frame
   149  	//TODO: push back the fun raw
   150  	return frame, ok
   151  }
   152  
   153  func parseFunction(scanner *Scanner) (Function, bool) {
   154  	match := scanner.Match(reCall)
   155  	if match == nil {
   156  		return Function{}, false
   157  	}
   158  	return Function{
   159  		Package: match[2],
   160  		Type:    match[5],
   161  		Name:    match[6],
   162  	}, true
   163  }
   164  
   165  func parsePosition(scanner *Scanner) (Position, bool) {
   166  	match := scanner.Match(rePos)
   167  	if match == nil {
   168  		return Position{}, false
   169  	}
   170  	line, _ := strconv.ParseInt(match[2], 0, 32)
   171  	return Position{Filename: match[1], Line: int(line)}, true
   172  }