mirror of
https://github.com/junegunn/fzf.git
synced 2026-08-01 21:50:28 +08:00
Add fast path for two-character patterns in FuzzyMatchV2
For two ASCII characters, rows 0 and 1 of the score matrix collapse to scalar running state, so Phase 2 and Phase 3 fuse into one pass with no score arrays. withPos stores the two rows for the backtrace. Up to 1.4x on two-char queries, the most common multi-char length. Verify both fast paths against the general algorithm with exhaustive (every short string over a class-complete alphabet) and fuzz tests, runnable via the new make fuzz target.
This commit is contained in:
+4
-2
@@ -3,8 +3,10 @@ CHANGELOG
|
|||||||
|
|
||||||
0.74.2
|
0.74.2
|
||||||
------
|
------
|
||||||
- Single-character queries are up to 2.4x faster
|
- Performance optimizations for short queries
|
||||||
- This is the most latency-sensitive case; the first keystroke scans the entire list before the result cache can help
|
- The most critical case: fzf narrows results as you type, so short queries scan the largest candidate sets and the first keystroke scans the whole input.
|
||||||
|
- Single-character queries are up to 2.4x faster
|
||||||
|
- Two-character queries are up to 1.4x faster
|
||||||
- Faster sorting of search results; the default two-criteria ranking skips redundant radix passes
|
- Faster sorting of search results; the default two-criteria ranking skips redundant radix passes
|
||||||
- Fixed nondeterministic match highlight positions
|
- Fixed nondeterministic match highlight positions
|
||||||
- fzf now detects terminal resize on Windows in `--height` mode (#4790) (@Cyrus580529)
|
- fzf now detects terminal resize on Windows in `--height` mode (#4790) (@Cyrus580529)
|
||||||
|
|||||||
@@ -97,6 +97,16 @@ test: $(SOURCES)
|
|||||||
itest:
|
itest:
|
||||||
ruby test/runner.rb
|
ruby test/runner.rb
|
||||||
|
|
||||||
|
# Actively fuzz the matcher fast paths against the general algorithm.
|
||||||
|
# Go fuzzes one target at a time, so iterate. Override duration with
|
||||||
|
# FUZZTIME (e.g. make fuzz FUZZTIME=5m).
|
||||||
|
FUZZTIME ?= 30s
|
||||||
|
fuzz:
|
||||||
|
@for t in FuzzFuzzyMatchV2Single FuzzFuzzyMatchV2Two; do \
|
||||||
|
echo "== $$t =="; \
|
||||||
|
$(GO) test -run '^$$' -fuzz "^$$t$$" -fuzztime $(FUZZTIME) ./src/algo || exit 1; \
|
||||||
|
done
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
||||||
|
|
||||||
|
|||||||
+181
-1
@@ -463,6 +463,179 @@ func fuzzyMatchV2Single(caseSensitive bool, forward bool, input *util.Chars, b b
|
|||||||
return result, &pos
|
return result, &pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test hooks: force the general path instead of a fast path, so the two can
|
||||||
|
// be compared for equivalence.
|
||||||
|
var (
|
||||||
|
disableSingle bool
|
||||||
|
disableTwo bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// fuzzyMatchV2Two is a fused fast path for a two-character ASCII pattern on
|
||||||
|
// ASCII input. It replicates Phase 2 (row 0) and Phase 3 (row 1) of
|
||||||
|
// FuzzyMatchV2 in a single pass, carrying the row-0 diagonal/left values and
|
||||||
|
// the row-1 left value as scalars instead of materializing score arrays.
|
||||||
|
// When withPos is set, the two DP rows are stored so the backtrace can
|
||||||
|
// recover the matched character positions, exactly as the general Phase 4.
|
||||||
|
func fuzzyMatchV2Two(caseSensitive bool, forward bool, input *util.Chars, pchar0 byte, pchar1 byte, minIdx int, maxIdx int, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
|
sl := input.Bytes()
|
||||||
|
N := maxIdx - minIdx
|
||||||
|
|
||||||
|
// Row storage, only needed for the backtrace
|
||||||
|
var H0, C0, H1, C1 []int16
|
||||||
|
if withPos {
|
||||||
|
o := 0
|
||||||
|
o, H0 = alloc16(o, slab, N)
|
||||||
|
o, C0 = alloc16(o, slab, N)
|
||||||
|
o, H1 = alloc16(o, slab, N)
|
||||||
|
_, C1 = alloc16(o, slab, N)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxScore, maxScorePos := int16(0), 0
|
||||||
|
prevClass := initialCharClass
|
||||||
|
|
||||||
|
// Subsequence tracking (equivalent to F[0], F[1] in Phase 2). The scope
|
||||||
|
// from asciiFuzzyIndex ends exactly at the last pchar1, so row 1's upper
|
||||||
|
// bound (Phase 3 lastIdx) is the final loop position; no separate var.
|
||||||
|
f0, f1 := -1, -1
|
||||||
|
|
||||||
|
// Row 0 running state at the previous position
|
||||||
|
var h0Prev, c0Prev, bPrev int16
|
||||||
|
inGap0 := false
|
||||||
|
|
||||||
|
// Row 1 running state
|
||||||
|
var h1Prev int16
|
||||||
|
inGap1 := false
|
||||||
|
|
||||||
|
for off := range N {
|
||||||
|
pos := minIdx + off
|
||||||
|
b := sl[pos]
|
||||||
|
class := asciiCharClasses[b]
|
||||||
|
lb := b
|
||||||
|
if !caseSensitive && b >= 'A' && b <= 'Z' {
|
||||||
|
lb = b + 32
|
||||||
|
}
|
||||||
|
bonus := bonusMatrix[prevClass][class]
|
||||||
|
prevClass = class
|
||||||
|
|
||||||
|
// Subsequence advance: pchar0 then pchar1
|
||||||
|
if f0 < 0 {
|
||||||
|
if lb == pchar0 {
|
||||||
|
f0 = off
|
||||||
|
}
|
||||||
|
} else if lb == pchar1 && f1 < 0 {
|
||||||
|
f1 = off
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 0 (pchar0)
|
||||||
|
var h0Cur, c0Cur int16
|
||||||
|
if lb == pchar0 {
|
||||||
|
h0Cur = scoreMatch + bonus*bonusFirstCharMultiplier
|
||||||
|
c0Cur = 1
|
||||||
|
inGap0 = false
|
||||||
|
} else {
|
||||||
|
if inGap0 {
|
||||||
|
h0Cur = max(h0Prev+scoreGapExtension, 0)
|
||||||
|
} else {
|
||||||
|
h0Cur = max(h0Prev+scoreGapStart, 0)
|
||||||
|
}
|
||||||
|
c0Cur = 0
|
||||||
|
inGap0 = true
|
||||||
|
}
|
||||||
|
if withPos {
|
||||||
|
H0[off], C0[off] = h0Cur, c0Cur
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 1 (pchar1), only within [f1, lastIdx]
|
||||||
|
if f1 >= 0 && off >= f1 {
|
||||||
|
var s1, s2, consecutive int16
|
||||||
|
hleft := h1Prev
|
||||||
|
if off == f1 {
|
||||||
|
hleft = 0
|
||||||
|
}
|
||||||
|
if inGap1 {
|
||||||
|
s2 = hleft + scoreGapExtension
|
||||||
|
} else {
|
||||||
|
s2 = hleft + scoreGapStart
|
||||||
|
}
|
||||||
|
if lb == pchar1 {
|
||||||
|
s1 = h0Prev + scoreMatch
|
||||||
|
bb := bonus
|
||||||
|
consecutive = c0Prev + 1
|
||||||
|
if consecutive > 1 {
|
||||||
|
fb := bPrev
|
||||||
|
if bb >= bonusBoundary && bb > fb {
|
||||||
|
consecutive = 1
|
||||||
|
} else {
|
||||||
|
bb = max(bb, bonusConsecutive, fb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s1+bb < s2 {
|
||||||
|
s1 += bonus
|
||||||
|
consecutive = 0
|
||||||
|
} else {
|
||||||
|
s1 += bb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inGap1 = s1 < s2
|
||||||
|
score := max(s1, s2, 0)
|
||||||
|
if forward && score > maxScore || !forward && score >= maxScore {
|
||||||
|
maxScore, maxScorePos = score, off
|
||||||
|
}
|
||||||
|
h1Prev = score
|
||||||
|
if withPos {
|
||||||
|
H1[off], C1[off] = score, consecutive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h0Prev, c0Prev, bPrev = h0Cur, c0Cur, bonus
|
||||||
|
}
|
||||||
|
|
||||||
|
if f1 < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
if !withPos {
|
||||||
|
return Result{minIdx + f0, minIdx + maxScorePos + 1, int(maxScore)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 4 backtrace, specialized to two rows. Mirrors the general loop:
|
||||||
|
// record a cell when it dominates its diagonal and left neighbors, then
|
||||||
|
// step up a row; otherwise step left. preferMatch breaks score ties and
|
||||||
|
// must not read row 1 left of f1 (unwritten, possibly stale slab data).
|
||||||
|
pos := posArray(true, 2)
|
||||||
|
i := 1
|
||||||
|
j := maxScorePos
|
||||||
|
preferMatch := true
|
||||||
|
for {
|
||||||
|
var s, s1, s2, cCur int16
|
||||||
|
if i == 1 {
|
||||||
|
s, cCur = H1[j], C1[j]
|
||||||
|
if j >= f1 {
|
||||||
|
s1 = H0[j-1]
|
||||||
|
}
|
||||||
|
if j > f1 {
|
||||||
|
s2 = H1[j-1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s, cCur = H0[j], C0[j]
|
||||||
|
if j > f0 {
|
||||||
|
s2 = H0[j-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row := i
|
||||||
|
if s > s1 && (s > s2 || s == s2 && preferMatch) {
|
||||||
|
*pos = append(*pos, j+minIdx)
|
||||||
|
if i == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
preferMatch = cCur > 1 ||
|
||||||
|
row == 0 && j < N-1 && j+1 >= f1 && C1[j+1] > 0
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
return Result{minIdx + j, minIdx + maxScorePos + 1, int(maxScore)}, pos
|
||||||
|
}
|
||||||
|
|
||||||
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
// Assume that pattern is given in lowercase if case-insensitive.
|
// Assume that pattern is given in lowercase if case-insensitive.
|
||||||
// First check if there's a match and calculate bonus for each position.
|
// First check if there's a match and calculate bonus for each position.
|
||||||
@@ -487,7 +660,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
|
|
||||||
// Single-character ASCII pattern needs neither the prefilter nor the
|
// Single-character ASCII pattern needs neither the prefilter nor the
|
||||||
// score matrix
|
// score matrix
|
||||||
if M == 1 && input.IsBytes() && pattern[0] < utf8.RuneSelf {
|
if !disableSingle && M == 1 && input.IsBytes() && pattern[0] < utf8.RuneSelf {
|
||||||
return fuzzyMatchV2Single(caseSensitive, forward, input, byte(pattern[0]), withPos)
|
return fuzzyMatchV2Single(caseSensitive, forward, input, byte(pattern[0]), withPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,6 +672,13 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
// fmt.Println(N, maxIdx, idx, maxIdx-idx, input.ToString())
|
// fmt.Println(N, maxIdx, idx, maxIdx-idx, input.ToString())
|
||||||
N = maxIdx - minIdx
|
N = maxIdx - minIdx
|
||||||
|
|
||||||
|
// Two-character ASCII pattern: rows 0 and 1 collapse to scalar running
|
||||||
|
// state, so the general score arrays are unnecessary
|
||||||
|
if !disableTwo && M == 2 && input.IsBytes() &&
|
||||||
|
pattern[0] < utf8.RuneSelf && pattern[1] < utf8.RuneSelf {
|
||||||
|
return fuzzyMatchV2Two(caseSensitive, forward, input, byte(pattern[0]), byte(pattern[1]), minIdx, maxIdx, withPos, slab)
|
||||||
|
}
|
||||||
|
|
||||||
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
||||||
offset16 := 0
|
offset16 := 0
|
||||||
offset32 := 0
|
offset32 := 0
|
||||||
|
|||||||
@@ -241,3 +241,75 @@ func TestResultPositionsWithReusedSlab(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFuzzyMatchV2TwoEquivalence verifies that the two-character fast path
|
||||||
|
// produces the same Result and positions as the general algorithm across
|
||||||
|
// case sensitivity, direction, and withPos, using a reused slab to surface
|
||||||
|
// any stale-data reads in the backtrace.
|
||||||
|
func TestFuzzyMatchV2TwoEquivalence(t *testing.T) {
|
||||||
|
words := []string{"src", "main", "core", "config", "parser", "render", "server",
|
||||||
|
"client", "index", "handler", "util", "list", "cache", "reader"}
|
||||||
|
exts := []string{".go", ".rb", ".py", ".md", ".c", ".txt"}
|
||||||
|
// Deterministic corpus (LCG), plus adversarial short/repeated items
|
||||||
|
corpus := []util.Chars{}
|
||||||
|
seed := uint32(12345)
|
||||||
|
next := func(n int) int { seed = seed*1664525 + 1013904223; return int(seed>>8) % n }
|
||||||
|
for i := 0; i < 4000; i++ {
|
||||||
|
depth := 2 + next(4)
|
||||||
|
s := ""
|
||||||
|
for d := 0; d < depth; d++ {
|
||||||
|
if d > 0 {
|
||||||
|
s += "/"
|
||||||
|
}
|
||||||
|
s += words[next(len(words))]
|
||||||
|
if next(5) == 0 {
|
||||||
|
s += "_" + words[next(len(words))]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += exts[next(len(exts))]
|
||||||
|
corpus = append(corpus, util.ToChars([]byte(s)))
|
||||||
|
}
|
||||||
|
for _, s := range []string{"", "a", "ab", "aa", "aXb", "a/b", "//", "..", "abcabc",
|
||||||
|
"AaBb", "x.y.z", "a_b_c", "CoreCore", " co"} {
|
||||||
|
corpus = append(corpus, util.ToChars([]byte(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pats := []string{"co", "ab", "aa", "//", "..", "sr", "a/", "_c", "oo", "Ab",
|
||||||
|
"z.", "b.", "1a", "ll", "re", "er", "Co"}
|
||||||
|
slab := util.MakeSlab(100*1024, 2048)
|
||||||
|
for _, cs := range []bool{false, true} {
|
||||||
|
for _, fwd := range []bool{true, false} {
|
||||||
|
for _, wp := range []bool{false, true} {
|
||||||
|
for _, p := range pats {
|
||||||
|
pattern := []rune(p)
|
||||||
|
for j := range corpus {
|
||||||
|
disableTwo = true
|
||||||
|
rg, pg := FuzzyMatchV2(cs, false, fwd, &corpus[j], pattern, wp, slab)
|
||||||
|
disableTwo = false
|
||||||
|
rt, pt := FuzzyMatchV2(cs, false, fwd, &corpus[j], pattern, wp, slab)
|
||||||
|
if rg != rt {
|
||||||
|
t.Fatalf("Result cs=%v fwd=%v wp=%v pat=%q item=%q: general %v vs two %v",
|
||||||
|
cs, fwd, wp, p, corpus[j].ToString(), rg, rt)
|
||||||
|
}
|
||||||
|
if (pg == nil) != (pt == nil) || (pg != nil && !equalInts(*pg, *pt)) {
|
||||||
|
t.Fatalf("Pos cs=%v fwd=%v wp=%v pat=%q item=%q: general %v vs two %v",
|
||||||
|
cs, fwd, wp, p, corpus[j].ToString(), pg, pt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func equalInts(a, b []int) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package algo
|
||||||
|
|
||||||
|
// Equivalence tests for the single- and two-character fast paths against the
|
||||||
|
// general FuzzyMatchV2 algorithm, which serves as the oracle.
|
||||||
|
//
|
||||||
|
// Two complementary strategies:
|
||||||
|
// - Exhaustive: every string up to a fixed length over an alphabet that
|
||||||
|
// covers all ASCII character classes, so every local scoring branch is
|
||||||
|
// exercised (not merely sampled).
|
||||||
|
// - Fuzz: coverage-guided, arbitrary length, to reach cases the bounded
|
||||||
|
// exhaustive sweep cannot (e.g. long gaps where a high score decays).
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// One char of each ASCII class that affects scoring: lower, upper, delimiter,
|
||||||
|
// number, non-word, whitespace.
|
||||||
|
var equivAlphabet = []byte{'a', 'B', '/', '1', '_', ' '}
|
||||||
|
|
||||||
|
func samePos(a, b *[]int) bool {
|
||||||
|
if (a == nil) != (b == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if a == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return equalInts(*a, *b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareFastPath runs a single input/pattern/params pair through both the
|
||||||
|
// fast path and the general algorithm and fails on any difference.
|
||||||
|
func compareFastPath(t *testing.T, chars *util.Chars, pattern []rune, cs, fwd, wp bool, disable *bool, slab *util.Slab) {
|
||||||
|
t.Helper()
|
||||||
|
*disable = true
|
||||||
|
rg, pg := FuzzyMatchV2(cs, false, fwd, chars, pattern, wp, slab)
|
||||||
|
*disable = false
|
||||||
|
rt, pt := FuzzyMatchV2(cs, false, fwd, chars, pattern, wp, slab)
|
||||||
|
if rg != rt || !samePos(pg, pt) {
|
||||||
|
t.Fatalf("mismatch item=%q pat=%q cs=%v fwd=%v wp=%v: general %v %v vs fast %v %v",
|
||||||
|
chars.ToString(), string(pattern), cs, fwd, wp, rg, pg, rt, pt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lowerPattern lowercases the pattern for case-insensitive search, matching
|
||||||
|
// the Algo contract (the pattern is pre-lowercased by BuildPattern).
|
||||||
|
func lowerPattern(p []rune, cs bool) []rune {
|
||||||
|
if cs {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
out := make([]rune, len(p))
|
||||||
|
for i, c := range p {
|
||||||
|
if c >= 'A' && c <= 'Z' {
|
||||||
|
c += 32
|
||||||
|
}
|
||||||
|
out[i] = c
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func runExhaustive(t *testing.T, patLen, maxLen int, disable *bool) {
|
||||||
|
// All patterns of length patLen over the alphabet
|
||||||
|
var pats [][]rune
|
||||||
|
var genPat func(cur []rune)
|
||||||
|
genPat = func(cur []rune) {
|
||||||
|
if len(cur) == patLen {
|
||||||
|
pats = append(pats, append([]rune(nil), cur...))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, c := range equivAlphabet {
|
||||||
|
genPat(append(cur, rune(c)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
genPat(nil)
|
||||||
|
|
||||||
|
slab := util.MakeSlab(100*1024, 2048)
|
||||||
|
buf := make([]byte, 0, maxLen)
|
||||||
|
var rec func(depth int)
|
||||||
|
rec = func(depth int) {
|
||||||
|
chars := util.ToChars(append([]byte(nil), buf...))
|
||||||
|
for _, cs := range []bool{false, true} {
|
||||||
|
for _, fwd := range []bool{true, false} {
|
||||||
|
for _, wp := range []bool{false, true} {
|
||||||
|
for _, p := range pats {
|
||||||
|
compareFastPath(t, &chars, lowerPattern(p, cs), cs, fwd, wp, disable, slab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if depth == maxLen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, c := range equivAlphabet {
|
||||||
|
buf = append(buf, c)
|
||||||
|
rec(depth + 1)
|
||||||
|
buf = buf[:len(buf)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rec(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuzzyMatchV2SingleExhaustive(t *testing.T) {
|
||||||
|
runExhaustive(t, 1, 6, &disableSingle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuzzyMatchV2TwoExhaustive(t *testing.T) {
|
||||||
|
runExhaustive(t, 2, 6, &disableTwo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuzzFastPath(f *testing.F, patLen int, disable *bool) {
|
||||||
|
f.Add("core_color/view/server.txt", "co")
|
||||||
|
f.Add("a b", "ab")
|
||||||
|
f.Add("XyZ/123_abc.def", "z1")
|
||||||
|
f.Add("aaaaaaaaaaaaaaaaaaaaaaaa", "aa")
|
||||||
|
slab := util.MakeSlab(200*1024, 4096)
|
||||||
|
f.Fuzz(func(t *testing.T, input, pat string) {
|
||||||
|
r := []rune(pat)
|
||||||
|
if len(r) != patLen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, c := range r {
|
||||||
|
if c >= 128 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chars := util.ToChars([]byte(input))
|
||||||
|
if !chars.IsBytes() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, cs := range []bool{false, true} {
|
||||||
|
for _, fwd := range []bool{true, false} {
|
||||||
|
for _, wp := range []bool{false, true} {
|
||||||
|
compareFastPath(t, &chars, lowerPattern(r, cs), cs, fwd, wp, disable, slab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzFuzzyMatchV2Single(f *testing.F) { fuzzFastPath(f, 1, &disableSingle) }
|
||||||
|
func FuzzFuzzyMatchV2Two(f *testing.F) { fuzzFastPath(f, 2, &disableTwo) }
|
||||||
Reference in New Issue
Block a user