blob: e162bd8c41c11a3f8d27410f592d2321b4e79e89 [file] [log] [blame]
// Copyright 2015 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.
package logparse
import (
"regexp"
"strings"
)
// A matcher implements incrementally consuming a string using
// regexps.
type matcher struct {
str string // string being matched
pos int
groups []string // match groups
// matchPos is the byte position of the beginning of the
// match in str.
matchPos int
// literals maps from literal strings to the index of the
// next occurrence of that string.
literals map[string]int
}
func newMatcher(str string) *matcher {
return &matcher{str: str, literals: map[string]int{}}
}
func (m *matcher) done() bool {
return m.pos >= len(m.str)
}
// consume searches for r in the remaining text. If found, it consumes
// up to the end of the match, fills m.groups with the matched groups,
// and returns true.
func (m *matcher) consume(r *regexp.Regexp) bool {
idx := r.FindStringSubmatchIndex(m.str[m.pos:])
if idx == nil {
m.groups = m.groups[:0]
return false
}
if len(idx)/2 <= cap(m.groups) {
m.groups = m.groups[:len(idx)/2]
} else {
m.groups = make([]string, len(idx)/2, len(idx))
}
for i := range m.groups {
if idx[i*2] >= 0 {
m.groups[i] = m.str[m.pos+idx[i*2] : m.pos+idx[i*2+1]]
} else {
m.groups[i] = ""
}
}
m.matchPos = m.pos + idx[0]
m.pos += idx[1]
return true
}
// peek returns whether r matches the remaining text.
func (m *matcher) peek(r *regexp.Regexp) bool {
return r.MatchString(m.str[m.pos:])
}
// lineHasLiteral returns whether any of literals is found before the
// end of the current line.
func (m *matcher) lineHasLiteral(literals ...string) bool {
// Find the position of the next literal.
nextLiteral := len(m.str)
for _, literal := range literals {
next, ok := m.literals[literal]
if !ok || next < m.pos {
// Update the literal position.
i := strings.Index(m.str[m.pos:], literal)
if i < 0 {
next = len(m.str)
} else {
next = m.pos + i
}
m.literals[literal] = next
}
if next < nextLiteral {
nextLiteral = next
}
}
// If the next literal comes after this line, this line
// doesn't have any of literals.
if nextLiteral != len(m.str) {
eol := strings.Index(m.str[m.pos:], "\n")
if eol >= 0 && eol+m.pos < nextLiteral {
return false
}
}
return true
}
// hasPrefix returns whether the remaining text begins with s.
func (m *matcher) hasPrefix(s string) bool {
return strings.HasPrefix(m.str[m.pos:], s)
}
// line consumes and returns the remainder of the current line, not
// including the line terminator.
func (m *matcher) line() string {
if i := strings.Index(m.str[m.pos:], "\n"); i >= 0 {
line := m.str[m.pos : m.pos+i]
m.pos += i + 1
return line
} else {
line := m.str[m.pos:]
m.pos = len(m.str)
return line
}
}
// peekLine returns the remainder of the current line, not including
// the line terminator, and the position of the beginning of the next
// line.
func (m *matcher) peekLine() (string, int) {
if i := strings.Index(m.str[m.pos:], "\n"); i >= 0 {
return m.str[m.pos : m.pos+i], m.pos + i + 1
} else {
return m.str[m.pos:], len(m.str)
}
}