prints lines in order, highlights

This commit is contained in:
2026-01-15 17:56:47 +01:00
parent 73c53b1cba
commit 908b614d74

118
main.go
View File

@@ -5,6 +5,7 @@ import (
"flag"
"fmt"
"os"
"sort"
"sync"
)
@@ -42,6 +43,34 @@ type Component struct {
Rendered RenderedCharacterClass
}
type LineMatch struct {
Line string
Start int
End int
LineNum int
}
const (
reset = "\x1b[0m"
bold = "\x1b[1m"
red = "\x1b[31m"
green = "\x1b[32m"
yellow = "\x1b[33m"
)
func highlight(src string, start, end int, style ...string) string {
if start < 0 || end > len(src) || start >= end {
return src
}
s := ""
for _, c := range style {
s += c
}
return src[:start] + s + src[start:end] + reset + src[end:]
}
func alphabetRange(start, end rune) []rune {
if start > end {
start, end = end, start
@@ -153,33 +182,32 @@ func buildComponents(pattern string) ([]Component, error) {
return comps, nil
}
// ---------- match a single literal (with repeat) ----------
func matchLiteral(l Character, runes []rune, pos *int) bool {
func matchLiteral(l Character, runes []rune, pos *int) (bool, int) {
switch l.Repeats {
case NoRepeat:
if *pos >= len(runes) || runes[*pos] != l.Character {
return false
return false, 0
}
(*pos)++
return true
return true, *pos
case ZeroOrMore:
for *pos < len(runes) && runes[*pos] == l.Character {
(*pos)++
}
return true // zero occurrences are fine
return true, *pos
case OneOrMore:
if *pos >= len(runes) || runes[*pos] != l.Character {
return false
return false, 0
}
for *pos < len(runes) && runes[*pos] == l.Character {
(*pos)++
}
return true
return true, *pos
default:
return false
return false, 0
}
}
@@ -192,85 +220,94 @@ func inClass(rc RenderedCharacterClass, r rune) bool {
return false
}
func matchClass(rc RenderedCharacterClass, runes []rune, pos *int) bool {
func matchClass(rc RenderedCharacterClass, runes []rune, pos *int) (bool, int) {
switch rc.Repeats {
case NoRepeat:
if *pos >= len(runes) || !inClass(rc, runes[*pos]) {
return false
return false, 0
}
(*pos)++
return true
return true, *pos
case ZeroOrMore:
for *pos < len(runes) && inClass(rc, runes[*pos]) {
(*pos)++
}
return true // zero occurrences are fine
return true, *pos
case OneOrMore:
if *pos >= len(runes) || !inClass(rc, runes[*pos]) {
return false
return false, 0
}
for *pos < len(runes) && inClass(rc, runes[*pos]) {
(*pos)++
}
return true
return true, *pos
default:
return false
return false, 0
}
}
func matchFrom(components []Component, runes []rune, pos int) bool {
func matchFrom(components []Component, runes []rune, pos int) (bool, int) {
var end int
for _, c := range components {
switch c.Ctype {
case Literal:
if !matchLiteral(c.Literal, runes, &pos) {
return false
a, b := matchLiteral(c.Literal, runes, &pos)
if !a {
return false, 0
}
end = b
case Rendered:
if !matchClass(c.Rendered, runes, &pos) {
return false
a, b := matchClass(c.Rendered, runes, &pos)
if !a {
return false, 0
}
end = b
}
}
return true
return true, end
}
func matchComponents(components []Component, text string) bool {
func matchComponents(components []Component, text string) (bool, int, int) {
runes := []rune(text)
for start := 0; start <= len(runes); start++ {
if matchFrom(components, runes, start) {
return true
a, end := matchFrom(components, runes, start)
if a {
return true, start, end
}
}
return false
return false, 0, 0
}
func matchEngine(pattern string, line string, lineNum int, exp bool, components []Component) error {
func matchEngine(pattern string, line string, lineNum int, exp bool, components []Component) (bool, LineMatch, error) {
if !exp {
// sliding window approach for simple full pattern matching
for i := 0; i < len(line)-len(pattern); i++ {
slice := line[i : i+len(pattern)]
if slice == pattern {
println(lineNum, line)
return true, LineMatch{Line: line, Start: i, End: i + len(pattern), LineNum: lineNum}, nil
}
}
} else {
if matchComponents(components, line) {
println(lineNum, line)
a, s, e := matchComponents(components, line)
if a {
return true, LineMatch{Line: line, Start: s, End: e, LineNum: lineNum}, nil
}
}
return nil
return false, LineMatch{}, nil
}
func processReader(r *os.File, pattern string, exp bool) {
func processReader(r *os.File, pattern string, exp bool) []LineMatch {
var components []Component
if exp {
components, _ = buildComponents(pattern)
}
var matched []LineMatch
var wg sync.WaitGroup
scanner := bufio.NewScanner(r)
lineN := 0
@@ -280,18 +317,33 @@ func processReader(r *os.File, pattern string, exp bool) {
wg.Add(1)
go func(ln int) {
defer wg.Done()
matchEngine(pattern, text, ln, exp, components)
a, l, _ := matchEngine(pattern, text, ln, exp, components)
if a {
matched = append(matched, l)
}
}(lineN)
}
wg.Wait()
sort.Slice(matched, func(i, j int) bool { return matched[i].LineNum < matched[j].LineNum })
return matched
}
func main() {
regexModePtr := flag.Bool("ex", false, "set if using g2eb expressions, leave for full string matching")
noHighlightPtr := flag.Bool("noh", false, "no hightlght")
flag.Parse()
pattern := flag.Args()[0]
processReader(os.Stdin, pattern, *regexModePtr)
m := processReader(os.Stdin, pattern, *regexModePtr)
for _, lm := range m {
if *noHighlightPtr {
fmt.Println(lm.Line)
} else {
fmt.Println(highlight(lm.Line, lm.Start, lm.End, green))
}
}
}