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 }