github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/internal/loganal/matcher.go (about)

     1  // Copyright 2015 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 loganal
     6  
     7  import (
     8  	"regexp"
     9  	"strings"
    10  )
    11  
    12  // A matcher implements incrementally consuming a string using
    13  // regexps.
    14  type matcher struct {
    15  	str    string // string being matched
    16  	pos    int
    17  	groups []string // match groups
    18  
    19  	// matchPos is the byte position of the beginning of the
    20  	// match in str.
    21  	matchPos int
    22  
    23  	// literals maps from literal strings to the index of the
    24  	// next occurrence of that string.
    25  	literals map[string]int
    26  }
    27  
    28  func newMatcher(str string) *matcher {
    29  	return &matcher{str: str, literals: map[string]int{}}
    30  }
    31  
    32  func (m *matcher) done() bool {
    33  	return m.pos >= len(m.str)
    34  }
    35  
    36  // consume searches for r in the remaining text. If found, it consumes
    37  // up to the end of the match, fills m.groups with the matched groups,
    38  // and returns true.
    39  func (m *matcher) consume(r *regexp.Regexp) bool {
    40  	idx := r.FindStringSubmatchIndex(m.str[m.pos:])
    41  	if idx == nil {
    42  		m.groups = m.groups[:0]
    43  		return false
    44  	}
    45  	if len(idx)/2 <= cap(m.groups) {
    46  		m.groups = m.groups[:len(idx)/2]
    47  	} else {
    48  		m.groups = make([]string, len(idx)/2, len(idx))
    49  	}
    50  	for i := range m.groups {
    51  		if idx[i*2] >= 0 {
    52  			m.groups[i] = m.str[m.pos+idx[i*2] : m.pos+idx[i*2+1]]
    53  		} else {
    54  			m.groups[i] = ""
    55  		}
    56  	}
    57  	m.matchPos = m.pos + idx[0]
    58  	m.pos += idx[1]
    59  	return true
    60  }
    61  
    62  // peek returns whether r matches the remaining text.
    63  func (m *matcher) peek(r *regexp.Regexp) bool {
    64  	return r.MatchString(m.str[m.pos:])
    65  }
    66  
    67  // lineHasLiteral returns whether any of literals is found before the
    68  // end of the current line.
    69  func (m *matcher) lineHasLiteral(literals ...string) bool {
    70  	// Find the position of the next literal.
    71  	nextLiteral := len(m.str)
    72  	for _, literal := range literals {
    73  		next, ok := m.literals[literal]
    74  
    75  		if !ok || next < m.pos {
    76  			// Update the literal position.
    77  			i := strings.Index(m.str[m.pos:], literal)
    78  			if i < 0 {
    79  				next = len(m.str)
    80  			} else {
    81  				next = m.pos + i
    82  			}
    83  			m.literals[literal] = next
    84  		}
    85  
    86  		if next < nextLiteral {
    87  			nextLiteral = next
    88  		}
    89  	}
    90  	// If the next literal comes after this line, this line
    91  	// doesn't have any of literals.
    92  	if nextLiteral != len(m.str) {
    93  		eol := strings.Index(m.str[m.pos:], "\n")
    94  		if eol >= 0 && eol+m.pos < nextLiteral {
    95  			return false
    96  		}
    97  	}
    98  	return true
    99  }
   100  
   101  // hasPrefix returns whether the remaining text begins with s.
   102  func (m *matcher) hasPrefix(s string) bool {
   103  	return strings.HasPrefix(m.str[m.pos:], s)
   104  }
   105  
   106  // line consumes and returns the remainder of the current line, not
   107  // including the line terminator.
   108  func (m *matcher) line() string {
   109  	if i := strings.Index(m.str[m.pos:], "\n"); i >= 0 {
   110  		line := m.str[m.pos : m.pos+i]
   111  		m.pos += i + 1
   112  		return line
   113  	} else {
   114  		line := m.str[m.pos:]
   115  		m.pos = len(m.str)
   116  		return line
   117  	}
   118  }
   119  
   120  // peekLine returns the remainder of the current line, not including
   121  // the line terminator, and the position of the beginning of the next
   122  // line.
   123  func (m *matcher) peekLine() (string, int) {
   124  	if i := strings.Index(m.str[m.pos:], "\n"); i >= 0 {
   125  		return m.str[m.pos : m.pos+i], m.pos + i + 1
   126  	} else {
   127  		return m.str[m.pos:], len(m.str)
   128  	}
   129  }