├── .gitignore ├── .travis.yml ├── LICENSE ├── bench.sh ├── cmd ├── globdraw │ └── main.go └── globtest │ └── main.go ├── compiler ├── compiler.go └── compiler_test.go ├── glob.go ├── glob_test.go ├── match ├── any.go ├── any_of.go ├── any_of_test.go ├── any_test.go ├── btree.go ├── btree_test.go ├── contains.go ├── contains_test.go ├── debug │ └── debug.go ├── every_of.go ├── every_of_test.go ├── list.go ├── list_test.go ├── match.go ├── match_test.go ├── max.go ├── max_test.go ├── min.go ├── min_test.go ├── nothing.go ├── nothing_test.go ├── prefix.go ├── prefix_any.go ├── prefix_any_test.go ├── prefix_suffix.go ├── prefix_suffix_test.go ├── prefix_test.go ├── range.go ├── range_test.go ├── row.go ├── row_test.go ├── segments.go ├── segments_test.go ├── single.go ├── single_test.go ├── suffix.go ├── suffix_any.go ├── suffix_any_test.go ├── suffix_test.go ├── super.go ├── super_test.go ├── text.go └── text_test.go ├── readme.md ├── syntax ├── ast │ ├── ast.go │ ├── parser.go │ └── parser_test.go ├── lexer │ ├── lexer.go │ ├── lexer_test.go │ └── token.go └── syntax.go └── util ├── runes ├── runes.go └── runes_test.go └── strings └── strings.go /.gitignore: -------------------------------------------------------------------------------- 1 | glob.iml 2 | .idea 3 | *.cpu 4 | *.mem 5 | *.test 6 | *.dot 7 | *.png 8 | *.svg 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.7.X" 4 | - "1.8.X" 5 | - "1.9.X" 6 | - "1.10.X" 7 | - master 8 | 9 | matrix: 10 | allow_failures: 11 | - go: master 12 | fast_finish: true 13 | 14 | script: 15 | - go test -v ./... 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sergey Kamardin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | bench() { 4 | filename="/tmp/$1-$2.bench" 5 | if test -e "${filename}"; 6 | then 7 | echo "Already exists ${filename}" 8 | else 9 | backup=`git rev-parse --abbrev-ref HEAD` 10 | git checkout $1 11 | echo -n "Creating ${filename}... " 12 | go test ./... -run=NONE -bench=$2 > "${filename}" -benchmem 13 | echo "OK" 14 | git checkout ${backup} 15 | sleep 5 16 | fi 17 | } 18 | 19 | 20 | to=$1 21 | current=`git rev-parse --abbrev-ref HEAD` 22 | 23 | bench ${to} $2 24 | bench ${current} $2 25 | 26 | benchcmp $3 "/tmp/${to}-$2.bench" "/tmp/${current}-$2.bench" 27 | -------------------------------------------------------------------------------- /cmd/globdraw/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/gobwas/glob" 7 | "github.com/gobwas/glob/match" 8 | "github.com/gobwas/glob/match/debug" 9 | "os" 10 | "strings" 11 | "unicode/utf8" 12 | ) 13 | 14 | func main() { 15 | pattern := flag.String("p", "", "pattern to draw") 16 | sep := flag.String("s", "", "comma separated list of separators characters") 17 | flag.Parse() 18 | 19 | if *pattern == "" { 20 | flag.Usage() 21 | os.Exit(1) 22 | } 23 | 24 | var separators []rune 25 | if len(*sep) > 0 { 26 | for _, c := range strings.Split(*sep, ",") { 27 | if r, w := utf8.DecodeRuneInString(c); len(c) > w { 28 | fmt.Println("only single charactered separators are allowed") 29 | os.Exit(1) 30 | } else { 31 | separators = append(separators, r) 32 | } 33 | } 34 | } 35 | 36 | glob, err := glob.Compile(*pattern, separators...) 37 | if err != nil { 38 | fmt.Println("could not compile pattern:", err) 39 | os.Exit(1) 40 | } 41 | 42 | matcher := glob.(match.Matcher) 43 | fmt.Fprint(os.Stdout, debug.Graphviz(*pattern, matcher)) 44 | } 45 | -------------------------------------------------------------------------------- /cmd/globtest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/gobwas/glob" 7 | "os" 8 | "strings" 9 | "testing" 10 | "unicode/utf8" 11 | ) 12 | 13 | func benchString(r testing.BenchmarkResult) string { 14 | nsop := r.NsPerOp() 15 | ns := fmt.Sprintf("%10d ns/op", nsop) 16 | allocs := "0" 17 | if r.N > 0 { 18 | if nsop < 100 { 19 | // The format specifiers here make sure that 20 | // the ones digits line up for all three possible formats. 21 | if nsop < 10 { 22 | ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) 23 | } else { 24 | ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) 25 | } 26 | } 27 | 28 | allocs = fmt.Sprintf("%d", r.MemAllocs/uint64(r.N)) 29 | } 30 | 31 | return fmt.Sprintf("%8d\t%s\t%s allocs", r.N, ns, allocs) 32 | } 33 | 34 | func main() { 35 | pattern := flag.String("p", "", "pattern to draw") 36 | sep := flag.String("s", "", "comma separated list of separators") 37 | fixture := flag.String("f", "", "fixture") 38 | verbose := flag.Bool("v", false, "verbose") 39 | flag.Parse() 40 | 41 | if *pattern == "" { 42 | flag.Usage() 43 | os.Exit(1) 44 | } 45 | 46 | var separators []rune 47 | for _, c := range strings.Split(*sep, ",") { 48 | if r, w := utf8.DecodeRuneInString(c); len(c) > w { 49 | fmt.Println("only single charactered separators are allowed") 50 | os.Exit(1) 51 | } else { 52 | separators = append(separators, r) 53 | } 54 | } 55 | 56 | g, err := glob.Compile(*pattern, separators...) 57 | if err != nil { 58 | fmt.Println("could not compile pattern:", err) 59 | os.Exit(1) 60 | } 61 | 62 | if !*verbose { 63 | fmt.Println(g.Match(*fixture)) 64 | return 65 | } 66 | 67 | fmt.Printf("result: %t\n", g.Match(*fixture)) 68 | 69 | cb := testing.Benchmark(func(b *testing.B) { 70 | for i := 0; i < b.N; i++ { 71 | glob.Compile(*pattern, separators...) 72 | } 73 | }) 74 | fmt.Println("compile:", benchString(cb)) 75 | 76 | mb := testing.Benchmark(func(b *testing.B) { 77 | for i := 0; i < b.N; i++ { 78 | g.Match(*fixture) 79 | } 80 | }) 81 | fmt.Println("match: ", benchString(mb)) 82 | } 83 | -------------------------------------------------------------------------------- /compiler/compiler.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | // TODO use constructor with all matchers, and to their structs private 4 | // TODO glue multiple Text nodes (like after QuoteMeta) 5 | 6 | import ( 7 | "fmt" 8 | "reflect" 9 | 10 | "github.com/gobwas/glob/match" 11 | "github.com/gobwas/glob/syntax/ast" 12 | "github.com/gobwas/glob/util/runes" 13 | ) 14 | 15 | func optimizeMatcher(matcher match.Matcher) match.Matcher { 16 | switch m := matcher.(type) { 17 | 18 | case match.Any: 19 | if len(m.Separators) == 0 { 20 | return match.NewSuper() 21 | } 22 | 23 | case match.AnyOf: 24 | if len(m.Matchers) == 1 { 25 | return m.Matchers[0] 26 | } 27 | 28 | return m 29 | 30 | case match.List: 31 | if m.Not == false && len(m.List) == 1 { 32 | return match.NewText(string(m.List)) 33 | } 34 | 35 | return m 36 | 37 | case match.BTree: 38 | m.Left = optimizeMatcher(m.Left) 39 | m.Right = optimizeMatcher(m.Right) 40 | 41 | r, ok := m.Value.(match.Text) 42 | if !ok { 43 | return m 44 | } 45 | 46 | var ( 47 | leftNil = m.Left == nil 48 | rightNil = m.Right == nil 49 | ) 50 | if leftNil && rightNil { 51 | return match.NewText(r.Str) 52 | } 53 | 54 | _, leftSuper := m.Left.(match.Super) 55 | lp, leftPrefix := m.Left.(match.Prefix) 56 | la, leftAny := m.Left.(match.Any) 57 | 58 | _, rightSuper := m.Right.(match.Super) 59 | rs, rightSuffix := m.Right.(match.Suffix) 60 | ra, rightAny := m.Right.(match.Any) 61 | 62 | switch { 63 | case leftSuper && rightSuper: 64 | return match.NewContains(r.Str, false) 65 | 66 | case leftSuper && rightNil: 67 | return match.NewSuffix(r.Str) 68 | 69 | case rightSuper && leftNil: 70 | return match.NewPrefix(r.Str) 71 | 72 | case leftNil && rightSuffix: 73 | return match.NewPrefixSuffix(r.Str, rs.Suffix) 74 | 75 | case rightNil && leftPrefix: 76 | return match.NewPrefixSuffix(lp.Prefix, r.Str) 77 | 78 | case rightNil && leftAny: 79 | return match.NewSuffixAny(r.Str, la.Separators) 80 | 81 | case leftNil && rightAny: 82 | return match.NewPrefixAny(r.Str, ra.Separators) 83 | } 84 | 85 | return m 86 | } 87 | 88 | return matcher 89 | } 90 | 91 | func compileMatchers(matchers []match.Matcher) (match.Matcher, error) { 92 | if len(matchers) == 0 { 93 | return nil, fmt.Errorf("compile error: need at least one matcher") 94 | } 95 | if len(matchers) == 1 { 96 | return matchers[0], nil 97 | } 98 | if m := glueMatchers(matchers); m != nil { 99 | return m, nil 100 | } 101 | 102 | idx := -1 103 | maxLen := -1 104 | var val match.Matcher 105 | for i, matcher := range matchers { 106 | if l := matcher.Len(); l != -1 && l >= maxLen { 107 | maxLen = l 108 | idx = i 109 | val = matcher 110 | } 111 | } 112 | 113 | if val == nil { // not found matcher with static length 114 | r, err := compileMatchers(matchers[1:]) 115 | if err != nil { 116 | return nil, err 117 | } 118 | return match.NewBTree(matchers[0], nil, r), nil 119 | } 120 | 121 | left := matchers[:idx] 122 | var right []match.Matcher 123 | if len(matchers) > idx+1 { 124 | right = matchers[idx+1:] 125 | } 126 | 127 | var l, r match.Matcher 128 | var err error 129 | if len(left) > 0 { 130 | l, err = compileMatchers(left) 131 | if err != nil { 132 | return nil, err 133 | } 134 | } 135 | 136 | if len(right) > 0 { 137 | r, err = compileMatchers(right) 138 | if err != nil { 139 | return nil, err 140 | } 141 | } 142 | 143 | return match.NewBTree(val, l, r), nil 144 | } 145 | 146 | func glueMatchers(matchers []match.Matcher) match.Matcher { 147 | if m := glueMatchersAsEvery(matchers); m != nil { 148 | return m 149 | } 150 | if m := glueMatchersAsRow(matchers); m != nil { 151 | return m 152 | } 153 | return nil 154 | } 155 | 156 | func glueMatchersAsRow(matchers []match.Matcher) match.Matcher { 157 | if len(matchers) <= 1 { 158 | return nil 159 | } 160 | 161 | var ( 162 | c []match.Matcher 163 | l int 164 | ) 165 | for _, matcher := range matchers { 166 | if ml := matcher.Len(); ml == -1 { 167 | return nil 168 | } else { 169 | c = append(c, matcher) 170 | l += ml 171 | } 172 | } 173 | return match.NewRow(l, c...) 174 | } 175 | 176 | func glueMatchersAsEvery(matchers []match.Matcher) match.Matcher { 177 | if len(matchers) <= 1 { 178 | return nil 179 | } 180 | 181 | var ( 182 | hasAny bool 183 | hasSuper bool 184 | hasSingle bool 185 | min int 186 | separator []rune 187 | ) 188 | 189 | for i, matcher := range matchers { 190 | var sep []rune 191 | 192 | switch m := matcher.(type) { 193 | case match.Super: 194 | sep = []rune{} 195 | hasSuper = true 196 | 197 | case match.Any: 198 | sep = m.Separators 199 | hasAny = true 200 | 201 | case match.Single: 202 | sep = m.Separators 203 | hasSingle = true 204 | min++ 205 | 206 | case match.List: 207 | if !m.Not { 208 | return nil 209 | } 210 | sep = m.List 211 | hasSingle = true 212 | min++ 213 | 214 | default: 215 | return nil 216 | } 217 | 218 | // initialize 219 | if i == 0 { 220 | separator = sep 221 | } 222 | 223 | if runes.Equal(sep, separator) { 224 | continue 225 | } 226 | 227 | return nil 228 | } 229 | 230 | if hasSuper && !hasAny && !hasSingle { 231 | return match.NewSuper() 232 | } 233 | 234 | if hasAny && !hasSuper && !hasSingle { 235 | return match.NewAny(separator) 236 | } 237 | 238 | if (hasAny || hasSuper) && min > 0 && len(separator) == 0 { 239 | return match.NewMin(min) 240 | } 241 | 242 | every := match.NewEveryOf() 243 | 244 | if min > 0 { 245 | every.Add(match.NewMin(min)) 246 | 247 | if !hasAny && !hasSuper { 248 | every.Add(match.NewMax(min)) 249 | } 250 | } 251 | 252 | if len(separator) > 0 { 253 | every.Add(match.NewContains(string(separator), true)) 254 | } 255 | 256 | return every 257 | } 258 | 259 | func minimizeMatchers(matchers []match.Matcher) []match.Matcher { 260 | var done match.Matcher 261 | var left, right, count int 262 | 263 | for l := 0; l < len(matchers); l++ { 264 | for r := len(matchers); r > l; r-- { 265 | if glued := glueMatchers(matchers[l:r]); glued != nil { 266 | var swap bool 267 | 268 | if done == nil { 269 | swap = true 270 | } else { 271 | cl, gl := done.Len(), glued.Len() 272 | swap = cl > -1 && gl > -1 && gl > cl 273 | swap = swap || count < r-l 274 | } 275 | 276 | if swap { 277 | done = glued 278 | left = l 279 | right = r 280 | count = r - l 281 | } 282 | } 283 | } 284 | } 285 | 286 | if done == nil { 287 | return matchers 288 | } 289 | 290 | next := append(append([]match.Matcher{}, matchers[:left]...), done) 291 | if right < len(matchers) { 292 | next = append(next, matchers[right:]...) 293 | } 294 | 295 | if len(next) == len(matchers) { 296 | return next 297 | } 298 | 299 | return minimizeMatchers(next) 300 | } 301 | 302 | // minimizeAnyOf tries to apply some heuristics to minimize number of nodes in given tree 303 | func minimizeTree(tree *ast.Node) *ast.Node { 304 | switch tree.Kind { 305 | case ast.KindAnyOf: 306 | return minimizeTreeAnyOf(tree) 307 | default: 308 | return nil 309 | } 310 | } 311 | 312 | // minimizeAnyOf tries to find common children of given node of AnyOf pattern 313 | // it searches for common children from left and from right 314 | // if any common children are found – then it returns new optimized ast tree 315 | // else it returns nil 316 | func minimizeTreeAnyOf(tree *ast.Node) *ast.Node { 317 | if !areOfSameKind(tree.Children, ast.KindPattern) { 318 | return nil 319 | } 320 | 321 | commonLeft, commonRight := commonChildren(tree.Children) 322 | commonLeftCount, commonRightCount := len(commonLeft), len(commonRight) 323 | if commonLeftCount == 0 && commonRightCount == 0 { // there are no common parts 324 | return nil 325 | } 326 | 327 | var result []*ast.Node 328 | if commonLeftCount > 0 { 329 | result = append(result, ast.NewNode(ast.KindPattern, nil, commonLeft...)) 330 | } 331 | 332 | var anyOf []*ast.Node 333 | for _, child := range tree.Children { 334 | reuse := child.Children[commonLeftCount : len(child.Children)-commonRightCount] 335 | var node *ast.Node 336 | if len(reuse) == 0 { 337 | // this pattern is completely reduced by commonLeft and commonRight patterns 338 | // so it become nothing 339 | node = ast.NewNode(ast.KindNothing, nil) 340 | } else { 341 | node = ast.NewNode(ast.KindPattern, nil, reuse...) 342 | } 343 | anyOf = appendIfUnique(anyOf, node) 344 | } 345 | switch { 346 | case len(anyOf) == 1 && anyOf[0].Kind != ast.KindNothing: 347 | result = append(result, anyOf[0]) 348 | case len(anyOf) > 1: 349 | result = append(result, ast.NewNode(ast.KindAnyOf, nil, anyOf...)) 350 | } 351 | 352 | if commonRightCount > 0 { 353 | result = append(result, ast.NewNode(ast.KindPattern, nil, commonRight...)) 354 | } 355 | 356 | return ast.NewNode(ast.KindPattern, nil, result...) 357 | } 358 | 359 | func commonChildren(nodes []*ast.Node) (commonLeft, commonRight []*ast.Node) { 360 | if len(nodes) <= 1 { 361 | return 362 | } 363 | 364 | // find node that has least number of children 365 | idx := leastChildren(nodes) 366 | if idx == -1 { 367 | return 368 | } 369 | tree := nodes[idx] 370 | treeLength := len(tree.Children) 371 | 372 | // allocate max able size for rightCommon slice 373 | // to get ability insert elements in reverse order (from end to start) 374 | // without sorting 375 | commonRight = make([]*ast.Node, treeLength) 376 | lastRight := treeLength // will use this to get results as commonRight[lastRight:] 377 | 378 | var ( 379 | breakLeft bool 380 | breakRight bool 381 | commonTotal int 382 | ) 383 | for i, j := 0, treeLength-1; commonTotal < treeLength && j >= 0 && !(breakLeft && breakRight); i, j = i+1, j-1 { 384 | treeLeft := tree.Children[i] 385 | treeRight := tree.Children[j] 386 | 387 | for k := 0; k < len(nodes) && !(breakLeft && breakRight); k++ { 388 | // skip least children node 389 | if k == idx { 390 | continue 391 | } 392 | 393 | restLeft := nodes[k].Children[i] 394 | restRight := nodes[k].Children[j+len(nodes[k].Children)-treeLength] 395 | 396 | breakLeft = breakLeft || !treeLeft.Equal(restLeft) 397 | 398 | // disable searching for right common parts, if left part is already overlapping 399 | breakRight = breakRight || (!breakLeft && j <= i) 400 | breakRight = breakRight || !treeRight.Equal(restRight) 401 | } 402 | 403 | if !breakLeft { 404 | commonTotal++ 405 | commonLeft = append(commonLeft, treeLeft) 406 | } 407 | if !breakRight { 408 | commonTotal++ 409 | lastRight = j 410 | commonRight[j] = treeRight 411 | } 412 | } 413 | 414 | commonRight = commonRight[lastRight:] 415 | 416 | return 417 | } 418 | 419 | func appendIfUnique(target []*ast.Node, val *ast.Node) []*ast.Node { 420 | for _, n := range target { 421 | if reflect.DeepEqual(n, val) { 422 | return target 423 | } 424 | } 425 | return append(target, val) 426 | } 427 | 428 | func areOfSameKind(nodes []*ast.Node, kind ast.Kind) bool { 429 | for _, n := range nodes { 430 | if n.Kind != kind { 431 | return false 432 | } 433 | } 434 | return true 435 | } 436 | 437 | func leastChildren(nodes []*ast.Node) int { 438 | min := -1 439 | idx := -1 440 | for i, n := range nodes { 441 | if idx == -1 || (len(n.Children) < min) { 442 | min = len(n.Children) 443 | idx = i 444 | } 445 | } 446 | return idx 447 | } 448 | 449 | func compileTreeChildren(tree *ast.Node, sep []rune) ([]match.Matcher, error) { 450 | var matchers []match.Matcher 451 | for _, desc := range tree.Children { 452 | m, err := compile(desc, sep) 453 | if err != nil { 454 | return nil, err 455 | } 456 | matchers = append(matchers, optimizeMatcher(m)) 457 | } 458 | return matchers, nil 459 | } 460 | 461 | func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) { 462 | switch tree.Kind { 463 | case ast.KindAnyOf: 464 | // todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go) 465 | if n := minimizeTree(tree); n != nil { 466 | return compile(n, sep) 467 | } 468 | matchers, err := compileTreeChildren(tree, sep) 469 | if err != nil { 470 | return nil, err 471 | } 472 | return match.NewAnyOf(matchers...), nil 473 | 474 | case ast.KindPattern: 475 | if len(tree.Children) == 0 { 476 | return match.NewNothing(), nil 477 | } 478 | matchers, err := compileTreeChildren(tree, sep) 479 | if err != nil { 480 | return nil, err 481 | } 482 | m, err = compileMatchers(minimizeMatchers(matchers)) 483 | if err != nil { 484 | return nil, err 485 | } 486 | 487 | case ast.KindAny: 488 | m = match.NewAny(sep) 489 | 490 | case ast.KindSuper: 491 | m = match.NewSuper() 492 | 493 | case ast.KindSingle: 494 | m = match.NewSingle(sep) 495 | 496 | case ast.KindNothing: 497 | m = match.NewNothing() 498 | 499 | case ast.KindList: 500 | l := tree.Value.(ast.List) 501 | m = match.NewList([]rune(l.Chars), l.Not) 502 | 503 | case ast.KindRange: 504 | r := tree.Value.(ast.Range) 505 | m = match.NewRange(r.Lo, r.Hi, r.Not) 506 | 507 | case ast.KindText: 508 | t := tree.Value.(ast.Text) 509 | m = match.NewText(t.Text) 510 | 511 | default: 512 | return nil, fmt.Errorf("could not compile tree: unknown node type") 513 | } 514 | 515 | return optimizeMatcher(m), nil 516 | } 517 | 518 | func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) { 519 | m, err := compile(tree, sep) 520 | if err != nil { 521 | return nil, err 522 | } 523 | 524 | return m, nil 525 | } 526 | -------------------------------------------------------------------------------- /compiler/compiler_test.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "github.com/gobwas/glob/match" 5 | "github.com/gobwas/glob/match/debug" 6 | "github.com/gobwas/glob/syntax/ast" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | var separators = []rune{'.'} 12 | 13 | func TestCommonChildren(t *testing.T) { 14 | for i, test := range []struct { 15 | nodes []*ast.Node 16 | left []*ast.Node 17 | right []*ast.Node 18 | }{ 19 | { 20 | nodes: []*ast.Node{ 21 | ast.NewNode(ast.KindNothing, nil, 22 | ast.NewNode(ast.KindText, ast.Text{"a"}), 23 | ast.NewNode(ast.KindText, ast.Text{"z"}), 24 | ast.NewNode(ast.KindText, ast.Text{"c"}), 25 | ), 26 | }, 27 | }, 28 | { 29 | nodes: []*ast.Node{ 30 | ast.NewNode(ast.KindNothing, nil, 31 | ast.NewNode(ast.KindText, ast.Text{"a"}), 32 | ast.NewNode(ast.KindText, ast.Text{"z"}), 33 | ast.NewNode(ast.KindText, ast.Text{"c"}), 34 | ), 35 | ast.NewNode(ast.KindNothing, nil, 36 | ast.NewNode(ast.KindText, ast.Text{"a"}), 37 | ast.NewNode(ast.KindText, ast.Text{"b"}), 38 | ast.NewNode(ast.KindText, ast.Text{"c"}), 39 | ), 40 | }, 41 | left: []*ast.Node{ 42 | ast.NewNode(ast.KindText, ast.Text{"a"}), 43 | }, 44 | right: []*ast.Node{ 45 | ast.NewNode(ast.KindText, ast.Text{"c"}), 46 | }, 47 | }, 48 | { 49 | nodes: []*ast.Node{ 50 | ast.NewNode(ast.KindNothing, nil, 51 | ast.NewNode(ast.KindText, ast.Text{"a"}), 52 | ast.NewNode(ast.KindText, ast.Text{"b"}), 53 | ast.NewNode(ast.KindText, ast.Text{"c"}), 54 | ast.NewNode(ast.KindText, ast.Text{"d"}), 55 | ), 56 | ast.NewNode(ast.KindNothing, nil, 57 | ast.NewNode(ast.KindText, ast.Text{"a"}), 58 | ast.NewNode(ast.KindText, ast.Text{"b"}), 59 | ast.NewNode(ast.KindText, ast.Text{"c"}), 60 | ast.NewNode(ast.KindText, ast.Text{"c"}), 61 | ast.NewNode(ast.KindText, ast.Text{"d"}), 62 | ), 63 | }, 64 | left: []*ast.Node{ 65 | ast.NewNode(ast.KindText, ast.Text{"a"}), 66 | ast.NewNode(ast.KindText, ast.Text{"b"}), 67 | }, 68 | right: []*ast.Node{ 69 | ast.NewNode(ast.KindText, ast.Text{"c"}), 70 | ast.NewNode(ast.KindText, ast.Text{"d"}), 71 | }, 72 | }, 73 | { 74 | nodes: []*ast.Node{ 75 | ast.NewNode(ast.KindNothing, nil, 76 | ast.NewNode(ast.KindText, ast.Text{"a"}), 77 | ast.NewNode(ast.KindText, ast.Text{"b"}), 78 | ast.NewNode(ast.KindText, ast.Text{"c"}), 79 | ), 80 | ast.NewNode(ast.KindNothing, nil, 81 | ast.NewNode(ast.KindText, ast.Text{"a"}), 82 | ast.NewNode(ast.KindText, ast.Text{"b"}), 83 | ast.NewNode(ast.KindText, ast.Text{"b"}), 84 | ast.NewNode(ast.KindText, ast.Text{"c"}), 85 | ), 86 | }, 87 | left: []*ast.Node{ 88 | ast.NewNode(ast.KindText, ast.Text{"a"}), 89 | ast.NewNode(ast.KindText, ast.Text{"b"}), 90 | }, 91 | right: []*ast.Node{ 92 | ast.NewNode(ast.KindText, ast.Text{"c"}), 93 | }, 94 | }, 95 | { 96 | nodes: []*ast.Node{ 97 | ast.NewNode(ast.KindNothing, nil, 98 | ast.NewNode(ast.KindText, ast.Text{"a"}), 99 | ast.NewNode(ast.KindText, ast.Text{"d"}), 100 | ), 101 | ast.NewNode(ast.KindNothing, nil, 102 | ast.NewNode(ast.KindText, ast.Text{"a"}), 103 | ast.NewNode(ast.KindText, ast.Text{"d"}), 104 | ), 105 | ast.NewNode(ast.KindNothing, nil, 106 | ast.NewNode(ast.KindText, ast.Text{"a"}), 107 | ast.NewNode(ast.KindText, ast.Text{"e"}), 108 | ), 109 | }, 110 | left: []*ast.Node{ 111 | ast.NewNode(ast.KindText, ast.Text{"a"}), 112 | }, 113 | right: []*ast.Node{}, 114 | }, 115 | } { 116 | left, right := commonChildren(test.nodes) 117 | if !nodesEqual(left, test.left) { 118 | t.Errorf("[%d] left, right := commonChildren(); left = %v; want %v", i, left, test.left) 119 | } 120 | if !nodesEqual(right, test.right) { 121 | t.Errorf("[%d] left, right := commonChildren(); right = %v; want %v", i, right, test.right) 122 | } 123 | } 124 | } 125 | 126 | func nodesEqual(a, b []*ast.Node) bool { 127 | if len(a) != len(b) { 128 | return false 129 | } 130 | for i, av := range a { 131 | if !av.Equal(b[i]) { 132 | return false 133 | } 134 | } 135 | return true 136 | } 137 | 138 | func TestGlueMatchers(t *testing.T) { 139 | for id, test := range []struct { 140 | in []match.Matcher 141 | exp match.Matcher 142 | }{ 143 | { 144 | []match.Matcher{ 145 | match.NewSuper(), 146 | match.NewSingle(nil), 147 | }, 148 | match.NewMin(1), 149 | }, 150 | { 151 | []match.Matcher{ 152 | match.NewAny(separators), 153 | match.NewSingle(separators), 154 | }, 155 | match.EveryOf{match.Matchers{ 156 | match.NewMin(1), 157 | match.NewContains(string(separators), true), 158 | }}, 159 | }, 160 | { 161 | []match.Matcher{ 162 | match.NewSingle(nil), 163 | match.NewSingle(nil), 164 | match.NewSingle(nil), 165 | }, 166 | match.EveryOf{match.Matchers{ 167 | match.NewMin(3), 168 | match.NewMax(3), 169 | }}, 170 | }, 171 | { 172 | []match.Matcher{ 173 | match.NewList([]rune{'a'}, true), 174 | match.NewAny([]rune{'a'}), 175 | }, 176 | match.EveryOf{match.Matchers{ 177 | match.NewMin(1), 178 | match.NewContains("a", true), 179 | }}, 180 | }, 181 | } { 182 | act, err := compileMatchers(test.in) 183 | if err != nil { 184 | t.Errorf("#%d convert matchers error: %s", id, err) 185 | continue 186 | } 187 | 188 | if !reflect.DeepEqual(act, test.exp) { 189 | t.Errorf("#%d unexpected convert matchers result:\nact: %#v;\nexp: %#v", id, act, test.exp) 190 | continue 191 | } 192 | } 193 | } 194 | 195 | func TestCompileMatchers(t *testing.T) { 196 | for id, test := range []struct { 197 | in []match.Matcher 198 | exp match.Matcher 199 | }{ 200 | { 201 | []match.Matcher{ 202 | match.NewSuper(), 203 | match.NewSingle(separators), 204 | match.NewText("c"), 205 | }, 206 | match.NewBTree( 207 | match.NewText("c"), 208 | match.NewBTree( 209 | match.NewSingle(separators), 210 | match.NewSuper(), 211 | nil, 212 | ), 213 | nil, 214 | ), 215 | }, 216 | { 217 | []match.Matcher{ 218 | match.NewAny(nil), 219 | match.NewText("c"), 220 | match.NewAny(nil), 221 | }, 222 | match.NewBTree( 223 | match.NewText("c"), 224 | match.NewAny(nil), 225 | match.NewAny(nil), 226 | ), 227 | }, 228 | { 229 | []match.Matcher{ 230 | match.NewRange('a', 'c', true), 231 | match.NewList([]rune{'z', 't', 'e'}, false), 232 | match.NewText("c"), 233 | match.NewSingle(nil), 234 | }, 235 | match.NewRow( 236 | 4, 237 | match.Matchers{ 238 | match.NewRange('a', 'c', true), 239 | match.NewList([]rune{'z', 't', 'e'}, false), 240 | match.NewText("c"), 241 | match.NewSingle(nil), 242 | }..., 243 | ), 244 | }, 245 | } { 246 | act, err := compileMatchers(test.in) 247 | if err != nil { 248 | t.Errorf("#%d convert matchers error: %s", id, err) 249 | continue 250 | } 251 | 252 | if !reflect.DeepEqual(act, test.exp) { 253 | t.Errorf("#%d unexpected convert matchers result:\nact: %#v\nexp: %#v", id, act, test.exp) 254 | continue 255 | } 256 | } 257 | } 258 | 259 | func TestConvertMatchers(t *testing.T) { 260 | for id, test := range []struct { 261 | in, exp []match.Matcher 262 | }{ 263 | { 264 | []match.Matcher{ 265 | match.NewRange('a', 'c', true), 266 | match.NewList([]rune{'z', 't', 'e'}, false), 267 | match.NewText("c"), 268 | match.NewSingle(nil), 269 | match.NewAny(nil), 270 | }, 271 | []match.Matcher{ 272 | match.NewRow( 273 | 4, 274 | []match.Matcher{ 275 | match.NewRange('a', 'c', true), 276 | match.NewList([]rune{'z', 't', 'e'}, false), 277 | match.NewText("c"), 278 | match.NewSingle(nil), 279 | }..., 280 | ), 281 | match.NewAny(nil), 282 | }, 283 | }, 284 | { 285 | []match.Matcher{ 286 | match.NewRange('a', 'c', true), 287 | match.NewList([]rune{'z', 't', 'e'}, false), 288 | match.NewText("c"), 289 | match.NewSingle(nil), 290 | match.NewAny(nil), 291 | match.NewSingle(nil), 292 | match.NewSingle(nil), 293 | match.NewAny(nil), 294 | }, 295 | []match.Matcher{ 296 | match.NewRow( 297 | 3, 298 | match.Matchers{ 299 | match.NewRange('a', 'c', true), 300 | match.NewList([]rune{'z', 't', 'e'}, false), 301 | match.NewText("c"), 302 | }..., 303 | ), 304 | match.NewMin(3), 305 | }, 306 | }, 307 | } { 308 | act := minimizeMatchers(test.in) 309 | if !reflect.DeepEqual(act, test.exp) { 310 | t.Errorf("#%d unexpected convert matchers 2 result:\nact: %#v\nexp: %#v", id, act, test.exp) 311 | continue 312 | } 313 | } 314 | } 315 | 316 | func TestCompiler(t *testing.T) { 317 | for id, test := range []struct { 318 | ast *ast.Node 319 | result match.Matcher 320 | sep []rune 321 | }{ 322 | { 323 | ast: ast.NewNode(ast.KindPattern, nil, 324 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 325 | ), 326 | result: match.NewText("abc"), 327 | }, 328 | { 329 | ast: ast.NewNode(ast.KindPattern, nil, 330 | ast.NewNode(ast.KindAny, nil), 331 | ), 332 | sep: separators, 333 | result: match.NewAny(separators), 334 | }, 335 | { 336 | ast: ast.NewNode(ast.KindPattern, nil, 337 | ast.NewNode(ast.KindAny, nil), 338 | ), 339 | result: match.NewSuper(), 340 | }, 341 | { 342 | ast: ast.NewNode(ast.KindPattern, nil, 343 | ast.NewNode(ast.KindSuper, nil), 344 | ), 345 | result: match.NewSuper(), 346 | }, 347 | { 348 | ast: ast.NewNode(ast.KindPattern, nil, 349 | ast.NewNode(ast.KindSingle, nil), 350 | ), 351 | sep: separators, 352 | result: match.NewSingle(separators), 353 | }, 354 | { 355 | ast: ast.NewNode(ast.KindPattern, nil, 356 | ast.NewNode(ast.KindRange, ast.Range{ 357 | Lo: 'a', 358 | Hi: 'z', 359 | Not: true, 360 | }), 361 | ), 362 | result: match.NewRange('a', 'z', true), 363 | }, 364 | { 365 | ast: ast.NewNode(ast.KindPattern, nil, 366 | ast.NewNode(ast.KindList, ast.List{ 367 | Chars: "abc", 368 | Not: true, 369 | }), 370 | ), 371 | result: match.NewList([]rune{'a', 'b', 'c'}, true), 372 | }, 373 | { 374 | ast: ast.NewNode(ast.KindPattern, nil, 375 | ast.NewNode(ast.KindAny, nil), 376 | ast.NewNode(ast.KindSingle, nil), 377 | ast.NewNode(ast.KindSingle, nil), 378 | ast.NewNode(ast.KindSingle, nil), 379 | ), 380 | sep: separators, 381 | result: match.EveryOf{Matchers: match.Matchers{ 382 | match.NewMin(3), 383 | match.NewContains(string(separators), true), 384 | }}, 385 | }, 386 | { 387 | ast: ast.NewNode(ast.KindPattern, nil, 388 | ast.NewNode(ast.KindAny, nil), 389 | ast.NewNode(ast.KindSingle, nil), 390 | ast.NewNode(ast.KindSingle, nil), 391 | ast.NewNode(ast.KindSingle, nil), 392 | ), 393 | result: match.NewMin(3), 394 | }, 395 | { 396 | ast: ast.NewNode(ast.KindPattern, nil, 397 | ast.NewNode(ast.KindAny, nil), 398 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 399 | ast.NewNode(ast.KindSingle, nil), 400 | ), 401 | sep: separators, 402 | result: match.NewBTree( 403 | match.NewRow( 404 | 4, 405 | match.Matchers{ 406 | match.NewText("abc"), 407 | match.NewSingle(separators), 408 | }..., 409 | ), 410 | match.NewAny(separators), 411 | nil, 412 | ), 413 | }, 414 | { 415 | ast: ast.NewNode(ast.KindPattern, nil, 416 | ast.NewNode(ast.KindText, ast.Text{"/"}), 417 | ast.NewNode(ast.KindAnyOf, nil, 418 | ast.NewNode(ast.KindText, ast.Text{"z"}), 419 | ast.NewNode(ast.KindText, ast.Text{"ab"}), 420 | ), 421 | ast.NewNode(ast.KindSuper, nil), 422 | ), 423 | sep: separators, 424 | result: match.NewBTree( 425 | match.NewText("/"), 426 | nil, 427 | match.NewBTree( 428 | match.NewAnyOf(match.NewText("z"), match.NewText("ab")), 429 | nil, 430 | match.NewSuper(), 431 | ), 432 | ), 433 | }, 434 | { 435 | ast: ast.NewNode(ast.KindPattern, nil, 436 | ast.NewNode(ast.KindSuper, nil), 437 | ast.NewNode(ast.KindSingle, nil), 438 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 439 | ast.NewNode(ast.KindSingle, nil), 440 | ), 441 | sep: separators, 442 | result: match.NewBTree( 443 | match.NewRow( 444 | 5, 445 | match.Matchers{ 446 | match.NewSingle(separators), 447 | match.NewText("abc"), 448 | match.NewSingle(separators), 449 | }..., 450 | ), 451 | match.NewSuper(), 452 | nil, 453 | ), 454 | }, 455 | { 456 | ast: ast.NewNode(ast.KindPattern, nil, 457 | ast.NewNode(ast.KindAny, nil), 458 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 459 | ), 460 | result: match.NewSuffix("abc"), 461 | }, 462 | { 463 | ast: ast.NewNode(ast.KindPattern, nil, 464 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 465 | ast.NewNode(ast.KindAny, nil), 466 | ), 467 | result: match.NewPrefix("abc"), 468 | }, 469 | { 470 | ast: ast.NewNode(ast.KindPattern, nil, 471 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 472 | ast.NewNode(ast.KindAny, nil), 473 | ast.NewNode(ast.KindText, ast.Text{"def"}), 474 | ), 475 | result: match.NewPrefixSuffix("abc", "def"), 476 | }, 477 | { 478 | ast: ast.NewNode(ast.KindPattern, nil, 479 | ast.NewNode(ast.KindAny, nil), 480 | ast.NewNode(ast.KindAny, nil), 481 | ast.NewNode(ast.KindAny, nil), 482 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 483 | ast.NewNode(ast.KindAny, nil), 484 | ast.NewNode(ast.KindAny, nil), 485 | ), 486 | result: match.NewContains("abc", false), 487 | }, 488 | { 489 | ast: ast.NewNode(ast.KindPattern, nil, 490 | ast.NewNode(ast.KindAny, nil), 491 | ast.NewNode(ast.KindAny, nil), 492 | ast.NewNode(ast.KindAny, nil), 493 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 494 | ast.NewNode(ast.KindAny, nil), 495 | ast.NewNode(ast.KindAny, nil), 496 | ), 497 | sep: separators, 498 | result: match.NewBTree( 499 | match.NewText("abc"), 500 | match.NewAny(separators), 501 | match.NewAny(separators), 502 | ), 503 | }, 504 | { 505 | ast: ast.NewNode(ast.KindPattern, nil, 506 | ast.NewNode(ast.KindSuper, nil), 507 | ast.NewNode(ast.KindSingle, nil), 508 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 509 | ast.NewNode(ast.KindSuper, nil), 510 | ast.NewNode(ast.KindSingle, nil), 511 | ), 512 | result: match.NewBTree( 513 | match.NewText("abc"), 514 | match.NewMin(1), 515 | match.NewMin(1), 516 | ), 517 | }, 518 | { 519 | ast: ast.NewNode(ast.KindPattern, nil, 520 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 521 | ), 522 | result: match.NewText("abc"), 523 | }, 524 | { 525 | ast: ast.NewNode(ast.KindPattern, nil, 526 | ast.NewNode(ast.KindAnyOf, nil, 527 | ast.NewNode(ast.KindPattern, nil, 528 | ast.NewNode(ast.KindAnyOf, nil, 529 | ast.NewNode(ast.KindPattern, nil, 530 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 531 | ), 532 | ), 533 | ), 534 | ), 535 | ), 536 | result: match.NewText("abc"), 537 | }, 538 | { 539 | ast: ast.NewNode(ast.KindPattern, nil, 540 | ast.NewNode(ast.KindAnyOf, nil, 541 | ast.NewNode(ast.KindPattern, nil, 542 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 543 | ast.NewNode(ast.KindSingle, nil), 544 | ), 545 | ast.NewNode(ast.KindPattern, nil, 546 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 547 | ast.NewNode(ast.KindList, ast.List{Chars: "def"}), 548 | ), 549 | ast.NewNode(ast.KindPattern, nil, 550 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 551 | ), 552 | ast.NewNode(ast.KindPattern, nil, 553 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 554 | ), 555 | ), 556 | ), 557 | result: match.NewBTree( 558 | match.NewText("abc"), 559 | nil, 560 | match.AnyOf{Matchers: match.Matchers{ 561 | match.NewSingle(nil), 562 | match.NewList([]rune{'d', 'e', 'f'}, false), 563 | match.NewNothing(), 564 | }}, 565 | ), 566 | }, 567 | { 568 | ast: ast.NewNode(ast.KindPattern, nil, 569 | ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'z'}), 570 | ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'x', Not: true}), 571 | ast.NewNode(ast.KindAny, nil), 572 | ), 573 | result: match.NewBTree( 574 | match.NewRow( 575 | 2, 576 | match.Matchers{ 577 | match.NewRange('a', 'z', false), 578 | match.NewRange('a', 'x', true), 579 | }..., 580 | ), 581 | nil, 582 | match.NewSuper(), 583 | ), 584 | }, 585 | { 586 | ast: ast.NewNode(ast.KindPattern, nil, 587 | ast.NewNode(ast.KindAnyOf, nil, 588 | ast.NewNode(ast.KindPattern, nil, 589 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 590 | ast.NewNode(ast.KindList, ast.List{Chars: "abc"}), 591 | ast.NewNode(ast.KindText, ast.Text{"ghi"}), 592 | ), 593 | ast.NewNode(ast.KindPattern, nil, 594 | ast.NewNode(ast.KindText, ast.Text{"abc"}), 595 | ast.NewNode(ast.KindList, ast.List{Chars: "def"}), 596 | ast.NewNode(ast.KindText, ast.Text{"ghi"}), 597 | ), 598 | ), 599 | ), 600 | result: match.NewRow( 601 | 7, 602 | match.Matchers{ 603 | match.NewText("abc"), 604 | match.AnyOf{Matchers: match.Matchers{ 605 | match.NewList([]rune{'a', 'b', 'c'}, false), 606 | match.NewList([]rune{'d', 'e', 'f'}, false), 607 | }}, 608 | match.NewText("ghi"), 609 | }..., 610 | ), 611 | }, 612 | } { 613 | m, err := Compile(test.ast, test.sep) 614 | if err != nil { 615 | t.Errorf("compilation error: %s", err) 616 | continue 617 | } 618 | 619 | if !reflect.DeepEqual(m, test.result) { 620 | t.Errorf("[%d] Compile():\nexp: %#v\nact: %#v\n\ngraphviz:\nexp:\n%s\nact:\n%s\n", id, test.result, m, debug.Graphviz("", test.result.(match.Matcher)), debug.Graphviz("", m.(match.Matcher))) 621 | continue 622 | } 623 | } 624 | } 625 | -------------------------------------------------------------------------------- /glob.go: -------------------------------------------------------------------------------- 1 | package glob 2 | 3 | import ( 4 | "github.com/gobwas/glob/compiler" 5 | "github.com/gobwas/glob/syntax" 6 | ) 7 | 8 | // Glob represents compiled glob pattern. 9 | type Glob interface { 10 | Match(string) bool 11 | } 12 | 13 | // Compile creates Glob for given pattern and strings (if any present after pattern) as separators. 14 | // The pattern syntax is: 15 | // 16 | // pattern: 17 | // { term } 18 | // 19 | // term: 20 | // `*` matches any sequence of non-separator characters 21 | // `**` matches any sequence of characters 22 | // `?` matches any single non-separator character 23 | // `[` [ `!` ] { character-range } `]` 24 | // character class (must be non-empty) 25 | // `{` pattern-list `}` 26 | // pattern alternatives 27 | // c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`) 28 | // `\` c matches character c 29 | // 30 | // character-range: 31 | // c matches character c (c != `\\`, `-`, `]`) 32 | // `\` c matches character c 33 | // lo `-` hi matches character c for lo <= c <= hi 34 | // 35 | // pattern-list: 36 | // pattern { `,` pattern } 37 | // comma-separated (without spaces) patterns 38 | // 39 | func Compile(pattern string, separators ...rune) (Glob, error) { 40 | ast, err := syntax.Parse(pattern) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | matcher, err := compiler.Compile(ast, separators) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return matcher, nil 51 | } 52 | 53 | // MustCompile is the same as Compile, except that if Compile returns error, this will panic 54 | func MustCompile(pattern string, separators ...rune) Glob { 55 | g, err := Compile(pattern, separators...) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | return g 61 | } 62 | 63 | // QuoteMeta returns a string that quotes all glob pattern meta characters 64 | // inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`. 65 | func QuoteMeta(s string) string { 66 | b := make([]byte, 2*len(s)) 67 | 68 | // a byte loop is correct because all meta characters are ASCII 69 | j := 0 70 | for i := 0; i < len(s); i++ { 71 | if syntax.Special(s[i]) { 72 | b[j] = '\\' 73 | j++ 74 | } 75 | b[j] = s[i] 76 | j++ 77 | } 78 | 79 | return string(b[0:j]) 80 | } 81 | -------------------------------------------------------------------------------- /glob_test.go: -------------------------------------------------------------------------------- 1 | package glob 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | pattern_all = "[a-z][!a-x]*cat*[h][!b]*eyes*" 10 | regexp_all = `^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` 11 | fixture_all_match = "my cat has very bright eyes" 12 | fixture_all_mismatch = "my dog has very bright eyes" 13 | 14 | pattern_plain = "google.com" 15 | regexp_plain = `^google\.com$` 16 | fixture_plain_match = "google.com" 17 | fixture_plain_mismatch = "gobwas.com" 18 | 19 | pattern_multiple = "https://*.google.*" 20 | regexp_multiple = `^https:\/\/.*\.google\..*$` 21 | fixture_multiple_match = "https://account.google.com" 22 | fixture_multiple_mismatch = "https://google.com" 23 | 24 | pattern_alternatives = "{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}" 25 | regexp_alternatives = `^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$` 26 | fixture_alternatives_match = "http://yahoo.com" 27 | fixture_alternatives_mismatch = "http://google.com" 28 | 29 | pattern_alternatives_suffix = "{https://*gobwas.com,http://exclude.gobwas.com}" 30 | regexp_alternatives_suffix = `^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$` 31 | fixture_alternatives_suffix_first_match = "https://safe.gobwas.com" 32 | fixture_alternatives_suffix_first_mismatch = "http://safe.gobwas.com" 33 | fixture_alternatives_suffix_second = "http://exclude.gobwas.com" 34 | 35 | pattern_prefix = "abc*" 36 | regexp_prefix = `^abc.*$` 37 | pattern_suffix = "*def" 38 | regexp_suffix = `^.*def$` 39 | pattern_prefix_suffix = "ab*ef" 40 | regexp_prefix_suffix = `^ab.*ef$` 41 | fixture_prefix_suffix_match = "abcdef" 42 | fixture_prefix_suffix_mismatch = "af" 43 | 44 | pattern_alternatives_combine_lite = "{abc*def,abc?def,abc[zte]def}" 45 | regexp_alternatives_combine_lite = `^(abc.*def|abc.def|abc[zte]def)$` 46 | fixture_alternatives_combine_lite = "abczdef" 47 | 48 | pattern_alternatives_combine_hard = "{abc*[a-c]def,abc?[d-g]def,abc[zte]?def}" 49 | regexp_alternatives_combine_hard = `^(abc.*[a-c]def|abc.[d-g]def|abc[zte].def)$` 50 | fixture_alternatives_combine_hard = "abczqdef" 51 | ) 52 | 53 | type test struct { 54 | pattern, match string 55 | should bool 56 | delimiters []rune 57 | } 58 | 59 | func glob(s bool, p, m string, d ...rune) test { 60 | return test{p, m, s, d} 61 | } 62 | 63 | func TestGlob(t *testing.T) { 64 | for _, test := range []test{ 65 | glob(true, "* ?at * eyes", "my cat has very bright eyes"), 66 | 67 | glob(true, "", ""), 68 | glob(false, "", "b"), 69 | 70 | glob(true, "*ä", "åä"), 71 | glob(true, "abc", "abc"), 72 | glob(true, "a*c", "abc"), 73 | glob(true, "a*c", "a12345c"), 74 | glob(true, "a?c", "a1c"), 75 | glob(true, "a.b", "a.b", '.'), 76 | glob(true, "a.*", "a.b", '.'), 77 | glob(true, "a.**", "a.b.c", '.'), 78 | glob(true, "a.?.c", "a.b.c", '.'), 79 | glob(true, "a.?.?", "a.b.c", '.'), 80 | glob(true, "?at", "cat"), 81 | glob(true, "?at", "fat"), 82 | glob(true, "*", "abc"), 83 | glob(true, `\*`, "*"), 84 | glob(true, "**", "a.b.c", '.'), 85 | 86 | glob(false, "?at", "at"), 87 | glob(false, "?at", "fat", 'f'), 88 | glob(false, "a.*", "a.b.c", '.'), 89 | glob(false, "a.?.c", "a.bb.c", '.'), 90 | glob(false, "*", "a.b.c", '.'), 91 | 92 | glob(true, "*test", "this is a test"), 93 | glob(true, "this*", "this is a test"), 94 | glob(true, "*is *", "this is a test"), 95 | glob(true, "*is*a*", "this is a test"), 96 | glob(true, "**test**", "this is a test"), 97 | glob(true, "**is**a***test*", "this is a test"), 98 | 99 | glob(false, "*is", "this is a test"), 100 | glob(false, "*no*", "this is a test"), 101 | glob(true, "[!a]*", "this is a test3"), 102 | 103 | glob(true, "*abc", "abcabc"), 104 | glob(true, "**abc", "abcabc"), 105 | glob(true, "???", "abc"), 106 | glob(true, "?*?", "abc"), 107 | glob(true, "?*?", "ac"), 108 | glob(false, "sta", "stagnation"), 109 | glob(true, "sta*", "stagnation"), 110 | glob(false, "sta?", "stagnation"), 111 | glob(false, "sta?n", "stagnation"), 112 | 113 | glob(true, "{abc,def}ghi", "defghi"), 114 | glob(true, "{abc,abcd}a", "abcda"), 115 | glob(true, "{a,ab}{bc,f}", "abc"), 116 | glob(true, "{*,**}{a,b}", "ab"), 117 | glob(false, "{*,**}{a,b}", "ac"), 118 | 119 | glob(true, "/{rate,[a-z][a-z][a-z]}*", "/rate"), 120 | glob(true, "/{rate,[0-9][0-9][0-9]}*", "/rate"), 121 | glob(true, "/{rate,[a-z][a-z][a-z]}*", "/usd"), 122 | 123 | glob(true, "{*.google.*,*.yandex.*}", "www.google.com", '.'), 124 | glob(true, "{*.google.*,*.yandex.*}", "www.yandex.com", '.'), 125 | glob(false, "{*.google.*,*.yandex.*}", "yandex.com", '.'), 126 | glob(false, "{*.google.*,*.yandex.*}", "google.com", '.'), 127 | 128 | glob(true, "{*.google.*,yandex.*}", "www.google.com", '.'), 129 | glob(true, "{*.google.*,yandex.*}", "yandex.com", '.'), 130 | glob(false, "{*.google.*,yandex.*}", "www.yandex.com", '.'), 131 | glob(false, "{*.google.*,yandex.*}", "google.com", '.'), 132 | 133 | glob(true, "*//{,*.}example.com", "https://www.example.com"), 134 | glob(true, "*//{,*.}example.com", "http://example.com"), 135 | glob(false, "*//{,*.}example.com", "http://example.com.net"), 136 | 137 | glob(true, pattern_all, fixture_all_match), 138 | glob(false, pattern_all, fixture_all_mismatch), 139 | 140 | glob(true, pattern_plain, fixture_plain_match), 141 | glob(false, pattern_plain, fixture_plain_mismatch), 142 | 143 | glob(true, pattern_multiple, fixture_multiple_match), 144 | glob(false, pattern_multiple, fixture_multiple_mismatch), 145 | 146 | glob(true, pattern_alternatives, fixture_alternatives_match), 147 | glob(false, pattern_alternatives, fixture_alternatives_mismatch), 148 | 149 | glob(true, pattern_alternatives_suffix, fixture_alternatives_suffix_first_match), 150 | glob(false, pattern_alternatives_suffix, fixture_alternatives_suffix_first_mismatch), 151 | glob(true, pattern_alternatives_suffix, fixture_alternatives_suffix_second), 152 | 153 | glob(true, pattern_alternatives_combine_hard, fixture_alternatives_combine_hard), 154 | 155 | glob(true, pattern_alternatives_combine_lite, fixture_alternatives_combine_lite), 156 | 157 | glob(true, pattern_prefix, fixture_prefix_suffix_match), 158 | glob(false, pattern_prefix, fixture_prefix_suffix_mismatch), 159 | 160 | glob(true, pattern_suffix, fixture_prefix_suffix_match), 161 | glob(false, pattern_suffix, fixture_prefix_suffix_mismatch), 162 | 163 | glob(true, pattern_prefix_suffix, fixture_prefix_suffix_match), 164 | glob(false, pattern_prefix_suffix, fixture_prefix_suffix_mismatch), 165 | } { 166 | t.Run("", func(t *testing.T) { 167 | g := MustCompile(test.pattern, test.delimiters...) 168 | result := g.Match(test.match) 169 | if result != test.should { 170 | t.Errorf( 171 | "pattern %q matching %q should be %v but got %v\n%s", 172 | test.pattern, test.match, test.should, result, g, 173 | ) 174 | } 175 | }) 176 | } 177 | } 178 | 179 | func TestQuoteMeta(t *testing.T) { 180 | for id, test := range []struct { 181 | in, out string 182 | }{ 183 | { 184 | in: `[foo*]`, 185 | out: `\[foo\*\]`, 186 | }, 187 | { 188 | in: `{foo*}`, 189 | out: `\{foo\*\}`, 190 | }, 191 | { 192 | in: `*?\[]{}`, 193 | out: `\*\?\\\[\]\{\}`, 194 | }, 195 | { 196 | in: `some text and *?\[]{}`, 197 | out: `some text and \*\?\\\[\]\{\}`, 198 | }, 199 | } { 200 | act := QuoteMeta(test.in) 201 | if act != test.out { 202 | t.Errorf("#%d QuoteMeta(%q) = %q; want %q", id, test.in, act, test.out) 203 | } 204 | if _, err := Compile(act); err != nil { 205 | t.Errorf("#%d _, err := Compile(QuoteMeta(%q) = %q); err = %q", id, test.in, act, err) 206 | } 207 | } 208 | } 209 | 210 | func BenchmarkParseGlob(b *testing.B) { 211 | for i := 0; i < b.N; i++ { 212 | Compile(pattern_all) 213 | } 214 | } 215 | func BenchmarkParseRegexp(b *testing.B) { 216 | for i := 0; i < b.N; i++ { 217 | regexp.MustCompile(regexp_all) 218 | } 219 | } 220 | 221 | func BenchmarkAllGlobMatch(b *testing.B) { 222 | m, _ := Compile(pattern_all) 223 | 224 | for i := 0; i < b.N; i++ { 225 | _ = m.Match(fixture_all_match) 226 | } 227 | } 228 | func BenchmarkAllGlobMatchParallel(b *testing.B) { 229 | m, _ := Compile(pattern_all) 230 | 231 | b.RunParallel(func(pb *testing.PB) { 232 | for pb.Next() { 233 | _ = m.Match(fixture_all_match) 234 | } 235 | }) 236 | } 237 | 238 | func BenchmarkAllRegexpMatch(b *testing.B) { 239 | m := regexp.MustCompile(regexp_all) 240 | f := []byte(fixture_all_match) 241 | 242 | for i := 0; i < b.N; i++ { 243 | _ = m.Match(f) 244 | } 245 | } 246 | func BenchmarkAllGlobMismatch(b *testing.B) { 247 | m, _ := Compile(pattern_all) 248 | 249 | for i := 0; i < b.N; i++ { 250 | _ = m.Match(fixture_all_mismatch) 251 | } 252 | } 253 | func BenchmarkAllGlobMismatchParallel(b *testing.B) { 254 | m, _ := Compile(pattern_all) 255 | 256 | b.RunParallel(func(pb *testing.PB) { 257 | for pb.Next() { 258 | _ = m.Match(fixture_all_mismatch) 259 | } 260 | }) 261 | } 262 | func BenchmarkAllRegexpMismatch(b *testing.B) { 263 | m := regexp.MustCompile(regexp_all) 264 | f := []byte(fixture_all_mismatch) 265 | 266 | for i := 0; i < b.N; i++ { 267 | _ = m.Match(f) 268 | } 269 | } 270 | 271 | func BenchmarkMultipleGlobMatch(b *testing.B) { 272 | m, _ := Compile(pattern_multiple) 273 | 274 | for i := 0; i < b.N; i++ { 275 | _ = m.Match(fixture_multiple_match) 276 | } 277 | } 278 | func BenchmarkMultipleRegexpMatch(b *testing.B) { 279 | m := regexp.MustCompile(regexp_multiple) 280 | f := []byte(fixture_multiple_match) 281 | 282 | for i := 0; i < b.N; i++ { 283 | _ = m.Match(f) 284 | } 285 | } 286 | func BenchmarkMultipleGlobMismatch(b *testing.B) { 287 | m, _ := Compile(pattern_multiple) 288 | 289 | for i := 0; i < b.N; i++ { 290 | _ = m.Match(fixture_multiple_mismatch) 291 | } 292 | } 293 | func BenchmarkMultipleRegexpMismatch(b *testing.B) { 294 | m := regexp.MustCompile(regexp_multiple) 295 | f := []byte(fixture_multiple_mismatch) 296 | 297 | for i := 0; i < b.N; i++ { 298 | _ = m.Match(f) 299 | } 300 | } 301 | 302 | func BenchmarkAlternativesGlobMatch(b *testing.B) { 303 | m, _ := Compile(pattern_alternatives) 304 | 305 | for i := 0; i < b.N; i++ { 306 | _ = m.Match(fixture_alternatives_match) 307 | } 308 | } 309 | func BenchmarkAlternativesGlobMismatch(b *testing.B) { 310 | m, _ := Compile(pattern_alternatives) 311 | 312 | for i := 0; i < b.N; i++ { 313 | _ = m.Match(fixture_alternatives_mismatch) 314 | } 315 | } 316 | func BenchmarkAlternativesRegexpMatch(b *testing.B) { 317 | m := regexp.MustCompile(regexp_alternatives) 318 | f := []byte(fixture_alternatives_match) 319 | 320 | for i := 0; i < b.N; i++ { 321 | _ = m.Match(f) 322 | } 323 | } 324 | func BenchmarkAlternativesRegexpMismatch(b *testing.B) { 325 | m := regexp.MustCompile(regexp_alternatives) 326 | f := []byte(fixture_alternatives_mismatch) 327 | 328 | for i := 0; i < b.N; i++ { 329 | _ = m.Match(f) 330 | } 331 | } 332 | 333 | func BenchmarkAlternativesSuffixFirstGlobMatch(b *testing.B) { 334 | m, _ := Compile(pattern_alternatives_suffix) 335 | 336 | for i := 0; i < b.N; i++ { 337 | _ = m.Match(fixture_alternatives_suffix_first_match) 338 | } 339 | } 340 | func BenchmarkAlternativesSuffixFirstGlobMismatch(b *testing.B) { 341 | m, _ := Compile(pattern_alternatives_suffix) 342 | 343 | for i := 0; i < b.N; i++ { 344 | _ = m.Match(fixture_alternatives_suffix_first_mismatch) 345 | } 346 | } 347 | func BenchmarkAlternativesSuffixSecondGlobMatch(b *testing.B) { 348 | m, _ := Compile(pattern_alternatives_suffix) 349 | 350 | for i := 0; i < b.N; i++ { 351 | _ = m.Match(fixture_alternatives_suffix_second) 352 | } 353 | } 354 | func BenchmarkAlternativesCombineLiteGlobMatch(b *testing.B) { 355 | m, _ := Compile(pattern_alternatives_combine_lite) 356 | 357 | for i := 0; i < b.N; i++ { 358 | _ = m.Match(fixture_alternatives_combine_lite) 359 | } 360 | } 361 | func BenchmarkAlternativesCombineHardGlobMatch(b *testing.B) { 362 | m, _ := Compile(pattern_alternatives_combine_hard) 363 | 364 | for i := 0; i < b.N; i++ { 365 | _ = m.Match(fixture_alternatives_combine_hard) 366 | } 367 | } 368 | func BenchmarkAlternativesSuffixFirstRegexpMatch(b *testing.B) { 369 | m := regexp.MustCompile(regexp_alternatives_suffix) 370 | f := []byte(fixture_alternatives_suffix_first_match) 371 | 372 | for i := 0; i < b.N; i++ { 373 | _ = m.Match(f) 374 | } 375 | } 376 | func BenchmarkAlternativesSuffixFirstRegexpMismatch(b *testing.B) { 377 | m := regexp.MustCompile(regexp_alternatives_suffix) 378 | f := []byte(fixture_alternatives_suffix_first_mismatch) 379 | 380 | for i := 0; i < b.N; i++ { 381 | _ = m.Match(f) 382 | } 383 | } 384 | func BenchmarkAlternativesSuffixSecondRegexpMatch(b *testing.B) { 385 | m := regexp.MustCompile(regexp_alternatives_suffix) 386 | f := []byte(fixture_alternatives_suffix_second) 387 | 388 | for i := 0; i < b.N; i++ { 389 | _ = m.Match(f) 390 | } 391 | } 392 | func BenchmarkAlternativesCombineLiteRegexpMatch(b *testing.B) { 393 | m := regexp.MustCompile(regexp_alternatives_combine_lite) 394 | f := []byte(fixture_alternatives_combine_lite) 395 | 396 | for i := 0; i < b.N; i++ { 397 | _ = m.Match(f) 398 | } 399 | } 400 | func BenchmarkAlternativesCombineHardRegexpMatch(b *testing.B) { 401 | m := regexp.MustCompile(regexp_alternatives_combine_hard) 402 | f := []byte(fixture_alternatives_combine_hard) 403 | 404 | for i := 0; i < b.N; i++ { 405 | _ = m.Match(f) 406 | } 407 | } 408 | 409 | func BenchmarkPlainGlobMatch(b *testing.B) { 410 | m, _ := Compile(pattern_plain) 411 | 412 | for i := 0; i < b.N; i++ { 413 | _ = m.Match(fixture_plain_match) 414 | } 415 | } 416 | func BenchmarkPlainRegexpMatch(b *testing.B) { 417 | m := regexp.MustCompile(regexp_plain) 418 | f := []byte(fixture_plain_match) 419 | 420 | for i := 0; i < b.N; i++ { 421 | _ = m.Match(f) 422 | } 423 | } 424 | func BenchmarkPlainGlobMismatch(b *testing.B) { 425 | m, _ := Compile(pattern_plain) 426 | 427 | for i := 0; i < b.N; i++ { 428 | _ = m.Match(fixture_plain_mismatch) 429 | } 430 | } 431 | func BenchmarkPlainRegexpMismatch(b *testing.B) { 432 | m := regexp.MustCompile(regexp_plain) 433 | f := []byte(fixture_plain_mismatch) 434 | 435 | for i := 0; i < b.N; i++ { 436 | _ = m.Match(f) 437 | } 438 | } 439 | 440 | func BenchmarkPrefixGlobMatch(b *testing.B) { 441 | m, _ := Compile(pattern_prefix) 442 | 443 | for i := 0; i < b.N; i++ { 444 | _ = m.Match(fixture_prefix_suffix_match) 445 | } 446 | } 447 | func BenchmarkPrefixRegexpMatch(b *testing.B) { 448 | m := regexp.MustCompile(regexp_prefix) 449 | f := []byte(fixture_prefix_suffix_match) 450 | 451 | for i := 0; i < b.N; i++ { 452 | _ = m.Match(f) 453 | } 454 | } 455 | func BenchmarkPrefixGlobMismatch(b *testing.B) { 456 | m, _ := Compile(pattern_prefix) 457 | 458 | for i := 0; i < b.N; i++ { 459 | _ = m.Match(fixture_prefix_suffix_mismatch) 460 | } 461 | } 462 | func BenchmarkPrefixRegexpMismatch(b *testing.B) { 463 | m := regexp.MustCompile(regexp_prefix) 464 | f := []byte(fixture_prefix_suffix_mismatch) 465 | 466 | for i := 0; i < b.N; i++ { 467 | _ = m.Match(f) 468 | } 469 | } 470 | 471 | func BenchmarkSuffixGlobMatch(b *testing.B) { 472 | m, _ := Compile(pattern_suffix) 473 | 474 | for i := 0; i < b.N; i++ { 475 | _ = m.Match(fixture_prefix_suffix_match) 476 | } 477 | } 478 | func BenchmarkSuffixRegexpMatch(b *testing.B) { 479 | m := regexp.MustCompile(regexp_suffix) 480 | f := []byte(fixture_prefix_suffix_match) 481 | 482 | for i := 0; i < b.N; i++ { 483 | _ = m.Match(f) 484 | } 485 | } 486 | func BenchmarkSuffixGlobMismatch(b *testing.B) { 487 | m, _ := Compile(pattern_suffix) 488 | 489 | for i := 0; i < b.N; i++ { 490 | _ = m.Match(fixture_prefix_suffix_mismatch) 491 | } 492 | } 493 | func BenchmarkSuffixRegexpMismatch(b *testing.B) { 494 | m := regexp.MustCompile(regexp_suffix) 495 | f := []byte(fixture_prefix_suffix_mismatch) 496 | 497 | for i := 0; i < b.N; i++ { 498 | _ = m.Match(f) 499 | } 500 | } 501 | 502 | func BenchmarkPrefixSuffixGlobMatch(b *testing.B) { 503 | m, _ := Compile(pattern_prefix_suffix) 504 | 505 | for i := 0; i < b.N; i++ { 506 | _ = m.Match(fixture_prefix_suffix_match) 507 | } 508 | } 509 | func BenchmarkPrefixSuffixRegexpMatch(b *testing.B) { 510 | m := regexp.MustCompile(regexp_prefix_suffix) 511 | f := []byte(fixture_prefix_suffix_match) 512 | 513 | for i := 0; i < b.N; i++ { 514 | _ = m.Match(f) 515 | } 516 | } 517 | func BenchmarkPrefixSuffixGlobMismatch(b *testing.B) { 518 | m, _ := Compile(pattern_prefix_suffix) 519 | 520 | for i := 0; i < b.N; i++ { 521 | _ = m.Match(fixture_prefix_suffix_mismatch) 522 | } 523 | } 524 | func BenchmarkPrefixSuffixRegexpMismatch(b *testing.B) { 525 | m := regexp.MustCompile(regexp_prefix_suffix) 526 | f := []byte(fixture_prefix_suffix_mismatch) 527 | 528 | for i := 0; i < b.N; i++ { 529 | _ = m.Match(f) 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /match/any.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gobwas/glob/util/strings" 6 | ) 7 | 8 | type Any struct { 9 | Separators []rune 10 | } 11 | 12 | func NewAny(s []rune) Any { 13 | return Any{s} 14 | } 15 | 16 | func (self Any) Match(s string) bool { 17 | return strings.IndexAnyRunes(s, self.Separators) == -1 18 | } 19 | 20 | func (self Any) Index(s string) (int, []int) { 21 | found := strings.IndexAnyRunes(s, self.Separators) 22 | switch found { 23 | case -1: 24 | case 0: 25 | return 0, segments0 26 | default: 27 | s = s[:found] 28 | } 29 | 30 | segments := acquireSegments(len(s)) 31 | for i := range s { 32 | segments = append(segments, i) 33 | } 34 | segments = append(segments, len(s)) 35 | 36 | return 0, segments 37 | } 38 | 39 | func (self Any) Len() int { 40 | return lenNo 41 | } 42 | 43 | func (self Any) String() string { 44 | return fmt.Sprintf("", string(self.Separators)) 45 | } 46 | -------------------------------------------------------------------------------- /match/any_of.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import "fmt" 4 | 5 | type AnyOf struct { 6 | Matchers Matchers 7 | } 8 | 9 | func NewAnyOf(m ...Matcher) AnyOf { 10 | return AnyOf{Matchers(m)} 11 | } 12 | 13 | func (self *AnyOf) Add(m Matcher) error { 14 | self.Matchers = append(self.Matchers, m) 15 | return nil 16 | } 17 | 18 | func (self AnyOf) Match(s string) bool { 19 | for _, m := range self.Matchers { 20 | if m.Match(s) { 21 | return true 22 | } 23 | } 24 | 25 | return false 26 | } 27 | 28 | func (self AnyOf) Index(s string) (int, []int) { 29 | index := -1 30 | 31 | segments := acquireSegments(len(s)) 32 | for _, m := range self.Matchers { 33 | idx, seg := m.Index(s) 34 | if idx == -1 { 35 | continue 36 | } 37 | 38 | if index == -1 || idx < index { 39 | index = idx 40 | segments = append(segments[:0], seg...) 41 | continue 42 | } 43 | 44 | if idx > index { 45 | continue 46 | } 47 | 48 | // here idx == index 49 | segments = appendMerge(segments, seg) 50 | } 51 | 52 | if index == -1 { 53 | releaseSegments(segments) 54 | return -1, nil 55 | } 56 | 57 | return index, segments 58 | } 59 | 60 | func (self AnyOf) Len() (l int) { 61 | l = -1 62 | for _, m := range self.Matchers { 63 | ml := m.Len() 64 | switch { 65 | case l == -1: 66 | l = ml 67 | continue 68 | 69 | case ml == -1: 70 | return -1 71 | 72 | case l != ml: 73 | return -1 74 | } 75 | } 76 | 77 | return 78 | } 79 | 80 | func (self AnyOf) String() string { 81 | return fmt.Sprintf("", self.Matchers) 82 | } 83 | -------------------------------------------------------------------------------- /match/any_of_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestAnyOfIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | matchers Matchers 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | Matchers{ 17 | NewAny(nil), 18 | NewText("b"), 19 | NewText("c"), 20 | }, 21 | "abc", 22 | 0, 23 | []int{0, 1, 2, 3}, 24 | }, 25 | { 26 | Matchers{ 27 | NewPrefix("b"), 28 | NewSuffix("c"), 29 | }, 30 | "abc", 31 | 0, 32 | []int{3}, 33 | }, 34 | { 35 | Matchers{ 36 | NewList([]rune("[def]"), false), 37 | NewList([]rune("[abc]"), false), 38 | }, 39 | "abcdef", 40 | 0, 41 | []int{1}, 42 | }, 43 | } { 44 | everyOf := NewAnyOf(test.matchers...) 45 | index, segments := everyOf.Index(test.fixture) 46 | if index != test.index { 47 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 48 | } 49 | if !reflect.DeepEqual(segments, test.segments) { 50 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /match/any_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestAnyIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | sep []rune 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | []rune{'.'}, 17 | "abc", 18 | 0, 19 | []int{0, 1, 2, 3}, 20 | }, 21 | { 22 | []rune{'.'}, 23 | "abc.def", 24 | 0, 25 | []int{0, 1, 2, 3}, 26 | }, 27 | } { 28 | p := NewAny(test.sep) 29 | index, segments := p.Index(test.fixture) 30 | if index != test.index { 31 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 32 | } 33 | if !reflect.DeepEqual(segments, test.segments) { 34 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 35 | } 36 | } 37 | } 38 | 39 | func BenchmarkIndexAny(b *testing.B) { 40 | m := NewAny(bench_separators) 41 | 42 | for i := 0; i < b.N; i++ { 43 | _, s := m.Index(bench_pattern) 44 | releaseSegments(s) 45 | } 46 | } 47 | 48 | func BenchmarkIndexAnyParallel(b *testing.B) { 49 | m := NewAny(bench_separators) 50 | 51 | b.RunParallel(func(pb *testing.PB) { 52 | for pb.Next() { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /match/btree.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "unicode/utf8" 6 | ) 7 | 8 | type BTree struct { 9 | Value Matcher 10 | Left Matcher 11 | Right Matcher 12 | ValueLengthRunes int 13 | LeftLengthRunes int 14 | RightLengthRunes int 15 | LengthRunes int 16 | } 17 | 18 | func NewBTree(Value, Left, Right Matcher) (tree BTree) { 19 | tree.Value = Value 20 | tree.Left = Left 21 | tree.Right = Right 22 | 23 | lenOk := true 24 | if tree.ValueLengthRunes = Value.Len(); tree.ValueLengthRunes == -1 { 25 | lenOk = false 26 | } 27 | 28 | if Left != nil { 29 | if tree.LeftLengthRunes = Left.Len(); tree.LeftLengthRunes == -1 { 30 | lenOk = false 31 | } 32 | } 33 | 34 | if Right != nil { 35 | if tree.RightLengthRunes = Right.Len(); tree.RightLengthRunes == -1 { 36 | lenOk = false 37 | } 38 | } 39 | 40 | if lenOk { 41 | tree.LengthRunes = tree.LeftLengthRunes + tree.ValueLengthRunes + tree.RightLengthRunes 42 | } else { 43 | tree.LengthRunes = -1 44 | } 45 | 46 | return tree 47 | } 48 | 49 | func (self BTree) Len() int { 50 | return self.LengthRunes 51 | } 52 | 53 | // todo? 54 | func (self BTree) Index(s string) (index int, segments []int) { 55 | //inputLen := len(s) 56 | //// try to cut unnecessary parts 57 | //// by knowledge of length of right and left part 58 | //offset, limit := self.offsetLimit(inputLen) 59 | //for offset < limit { 60 | // // search for matching part in substring 61 | // vi, segments := self.Value.Index(s[offset:limit]) 62 | // if index == -1 { 63 | // return -1, nil 64 | // } 65 | // if self.Left == nil { 66 | // if index != offset { 67 | // return -1, nil 68 | // } 69 | // } else { 70 | // left := s[:offset+vi] 71 | // i := self.Left.IndexSuffix(left) 72 | // if i == -1 { 73 | // return -1, nil 74 | // } 75 | // index = i 76 | // } 77 | // if self.Right != nil { 78 | // for _, seg := range segments { 79 | // right := s[:offset+vi+seg] 80 | // } 81 | // } 82 | 83 | // l := s[:offset+index] 84 | // var left bool 85 | // if self.Left != nil { 86 | // left = self.Left.Index(l) 87 | // } else { 88 | // left = l == "" 89 | // } 90 | //} 91 | 92 | return -1, nil 93 | } 94 | 95 | func (self BTree) Match(s string) bool { 96 | inputLen := len(s) 97 | // try to cut unnecessary parts 98 | // by knowledge of length of right and left part 99 | offset, limit := self.offsetLimit(inputLen) 100 | 101 | for offset < limit { 102 | // search for matching part in substring 103 | index, segments := self.Value.Index(s[offset:limit]) 104 | if index == -1 { 105 | releaseSegments(segments) 106 | return false 107 | } 108 | 109 | l := s[:offset+index] 110 | var left bool 111 | if self.Left != nil { 112 | left = self.Left.Match(l) 113 | } else { 114 | left = l == "" 115 | } 116 | 117 | if left { 118 | for i := len(segments) - 1; i >= 0; i-- { 119 | length := segments[i] 120 | 121 | var right bool 122 | var r string 123 | // if there is no string for the right branch 124 | if inputLen <= offset+index+length { 125 | r = "" 126 | } else { 127 | r = s[offset+index+length:] 128 | } 129 | 130 | if self.Right != nil { 131 | right = self.Right.Match(r) 132 | } else { 133 | right = r == "" 134 | } 135 | 136 | if right { 137 | releaseSegments(segments) 138 | return true 139 | } 140 | } 141 | } 142 | 143 | _, step := utf8.DecodeRuneInString(s[offset+index:]) 144 | offset += index + step 145 | 146 | releaseSegments(segments) 147 | } 148 | 149 | return false 150 | } 151 | 152 | func (self BTree) offsetLimit(inputLen int) (offset int, limit int) { 153 | // self.Length, self.RLen and self.LLen are values meaning the length of runes for each part 154 | // here we manipulating byte length for better optimizations 155 | // but these checks still works, cause minLen of 1-rune string is 1 byte. 156 | if self.LengthRunes != -1 && self.LengthRunes > inputLen { 157 | return 0, 0 158 | } 159 | if self.LeftLengthRunes >= 0 { 160 | offset = self.LeftLengthRunes 161 | } 162 | if self.RightLengthRunes >= 0 { 163 | limit = inputLen - self.RightLengthRunes 164 | } else { 165 | limit = inputLen 166 | } 167 | return offset, limit 168 | } 169 | 170 | func (self BTree) String() string { 171 | const n string = "" 172 | var l, r string 173 | if self.Left == nil { 174 | l = n 175 | } else { 176 | l = self.Left.String() 177 | } 178 | if self.Right == nil { 179 | r = n 180 | } else { 181 | r = self.Right.String() 182 | } 183 | 184 | return fmt.Sprintf("%s]>", l, self.Value, r) 185 | } 186 | -------------------------------------------------------------------------------- /match/btree_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBTree(t *testing.T) { 8 | for id, test := range []struct { 9 | tree BTree 10 | str string 11 | exp bool 12 | }{ 13 | { 14 | NewBTree(NewText("abc"), NewSuper(), NewSuper()), 15 | "abc", 16 | true, 17 | }, 18 | { 19 | NewBTree(NewText("a"), NewSingle(nil), NewSingle(nil)), 20 | "aaa", 21 | true, 22 | }, 23 | { 24 | NewBTree(NewText("b"), NewSingle(nil), nil), 25 | "bbb", 26 | false, 27 | }, 28 | { 29 | NewBTree( 30 | NewText("c"), 31 | NewBTree( 32 | NewSingle(nil), 33 | NewSuper(), 34 | nil, 35 | ), 36 | nil, 37 | ), 38 | "abc", 39 | true, 40 | }, 41 | } { 42 | act := test.tree.Match(test.str) 43 | if act != test.exp { 44 | t.Errorf("#%d match %q error: act: %t; exp: %t", id, test.str, act, test.exp) 45 | continue 46 | } 47 | } 48 | } 49 | 50 | type fakeMatcher struct { 51 | len int 52 | name string 53 | } 54 | 55 | func (f *fakeMatcher) Match(string) bool { 56 | return true 57 | } 58 | 59 | var i = 3 60 | 61 | func (f *fakeMatcher) Index(s string) (int, []int) { 62 | seg := make([]int, 0, i) 63 | for x := 0; x < i; x++ { 64 | seg = append(seg, x) 65 | } 66 | return 0, seg 67 | } 68 | func (f *fakeMatcher) Len() int { 69 | return f.len 70 | } 71 | func (f *fakeMatcher) String() string { 72 | return f.name 73 | } 74 | 75 | func BenchmarkMatchBTree(b *testing.B) { 76 | l := &fakeMatcher{4, "left_fake"} 77 | r := &fakeMatcher{4, "right_fake"} 78 | v := &fakeMatcher{2, "value_fake"} 79 | 80 | // must be <= len(l + r + v) 81 | fixture := "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij" 82 | 83 | bt := NewBTree(v, l, r) 84 | 85 | b.RunParallel(func(pb *testing.PB) { 86 | for pb.Next() { 87 | bt.Match(fixture) 88 | } 89 | }) 90 | } 91 | -------------------------------------------------------------------------------- /match/contains.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Contains struct { 9 | Needle string 10 | Not bool 11 | } 12 | 13 | func NewContains(needle string, not bool) Contains { 14 | return Contains{needle, not} 15 | } 16 | 17 | func (self Contains) Match(s string) bool { 18 | return strings.Contains(s, self.Needle) != self.Not 19 | } 20 | 21 | func (self Contains) Index(s string) (int, []int) { 22 | var offset int 23 | 24 | idx := strings.Index(s, self.Needle) 25 | 26 | if !self.Not { 27 | if idx == -1 { 28 | return -1, nil 29 | } 30 | 31 | offset = idx + len(self.Needle) 32 | if len(s) <= offset { 33 | return 0, []int{offset} 34 | } 35 | s = s[offset:] 36 | } else if idx != -1 { 37 | s = s[:idx] 38 | } 39 | 40 | segments := acquireSegments(len(s) + 1) 41 | for i := range s { 42 | segments = append(segments, offset+i) 43 | } 44 | 45 | return 0, append(segments, offset+len(s)) 46 | } 47 | 48 | func (self Contains) Len() int { 49 | return lenNo 50 | } 51 | 52 | func (self Contains) String() string { 53 | var not string 54 | if self.Not { 55 | not = "!" 56 | } 57 | return fmt.Sprintf("", not, self.Needle) 58 | } 59 | -------------------------------------------------------------------------------- /match/contains_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestContainsIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | prefix string 11 | not bool 12 | fixture string 13 | index int 14 | segments []int 15 | }{ 16 | { 17 | "ab", 18 | false, 19 | "abc", 20 | 0, 21 | []int{2, 3}, 22 | }, 23 | { 24 | "ab", 25 | false, 26 | "fffabfff", 27 | 0, 28 | []int{5, 6, 7, 8}, 29 | }, 30 | { 31 | "ab", 32 | true, 33 | "abc", 34 | 0, 35 | []int{0}, 36 | }, 37 | { 38 | "ab", 39 | true, 40 | "fffabfff", 41 | 0, 42 | []int{0, 1, 2, 3}, 43 | }, 44 | } { 45 | p := NewContains(test.prefix, test.not) 46 | index, segments := p.Index(test.fixture) 47 | if index != test.index { 48 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 49 | } 50 | if !reflect.DeepEqual(segments, test.segments) { 51 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 52 | } 53 | } 54 | } 55 | 56 | func BenchmarkIndexContains(b *testing.B) { 57 | m := NewContains(string(bench_separators), true) 58 | 59 | for i := 0; i < b.N; i++ { 60 | _, s := m.Index(bench_pattern) 61 | releaseSegments(s) 62 | } 63 | } 64 | 65 | func BenchmarkIndexContainsParallel(b *testing.B) { 66 | m := NewContains(string(bench_separators), true) 67 | 68 | b.RunParallel(func(pb *testing.PB) { 69 | for pb.Next() { 70 | _, s := m.Index(bench_pattern) 71 | releaseSegments(s) 72 | } 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /match/debug/debug.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/gobwas/glob/match" 7 | "math/rand" 8 | ) 9 | 10 | func Graphviz(pattern string, m match.Matcher) string { 11 | return fmt.Sprintf(`digraph G {graph[label="%s"];%s}`, pattern, graphviz_internal(m, fmt.Sprintf("%x", rand.Int63()))) 12 | } 13 | 14 | func graphviz_internal(m match.Matcher, id string) string { 15 | buf := &bytes.Buffer{} 16 | 17 | switch matcher := m.(type) { 18 | case match.BTree: 19 | fmt.Fprintf(buf, `"%s"[label="%s"];`, id, matcher.Value.String()) 20 | for _, m := range []match.Matcher{matcher.Left, matcher.Right} { 21 | switch n := m.(type) { 22 | case nil: 23 | rnd := rand.Int63() 24 | fmt.Fprintf(buf, `"%x"[label=""];`, rnd) 25 | fmt.Fprintf(buf, `"%s"->"%x";`, id, rnd) 26 | 27 | default: 28 | sub := fmt.Sprintf("%x", rand.Int63()) 29 | fmt.Fprintf(buf, `"%s"->"%s";`, id, sub) 30 | fmt.Fprintf(buf, graphviz_internal(n, sub)) 31 | } 32 | } 33 | 34 | case match.AnyOf: 35 | fmt.Fprintf(buf, `"%s"[label="AnyOf"];`, id) 36 | for _, m := range matcher.Matchers { 37 | rnd := rand.Int63() 38 | fmt.Fprintf(buf, graphviz_internal(m, fmt.Sprintf("%x", rnd))) 39 | fmt.Fprintf(buf, `"%s"->"%x";`, id, rnd) 40 | } 41 | 42 | case match.EveryOf: 43 | fmt.Fprintf(buf, `"%s"[label="EveryOf"];`, id) 44 | for _, m := range matcher.Matchers { 45 | rnd := rand.Int63() 46 | fmt.Fprintf(buf, graphviz_internal(m, fmt.Sprintf("%x", rnd))) 47 | fmt.Fprintf(buf, `"%s"->"%x";`, id, rnd) 48 | } 49 | 50 | default: 51 | fmt.Fprintf(buf, `"%s"[label="%s"];`, id, m.String()) 52 | } 53 | 54 | return buf.String() 55 | } 56 | -------------------------------------------------------------------------------- /match/every_of.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type EveryOf struct { 8 | Matchers Matchers 9 | } 10 | 11 | func NewEveryOf(m ...Matcher) EveryOf { 12 | return EveryOf{Matchers(m)} 13 | } 14 | 15 | func (self *EveryOf) Add(m Matcher) error { 16 | self.Matchers = append(self.Matchers, m) 17 | return nil 18 | } 19 | 20 | func (self EveryOf) Len() (l int) { 21 | for _, m := range self.Matchers { 22 | if ml := m.Len(); l > 0 { 23 | l += ml 24 | } else { 25 | return -1 26 | } 27 | } 28 | 29 | return 30 | } 31 | 32 | func (self EveryOf) Index(s string) (int, []int) { 33 | var index int 34 | var offset int 35 | 36 | // make `in` with cap as len(s), 37 | // cause it is the maximum size of output segments values 38 | next := acquireSegments(len(s)) 39 | current := acquireSegments(len(s)) 40 | 41 | sub := s 42 | for i, m := range self.Matchers { 43 | idx, seg := m.Index(sub) 44 | if idx == -1 { 45 | releaseSegments(next) 46 | releaseSegments(current) 47 | return -1, nil 48 | } 49 | 50 | if i == 0 { 51 | // we use copy here instead of `current = seg` 52 | // cause seg is a slice from reusable buffer `in` 53 | // and it could be overwritten in next iteration 54 | current = append(current, seg...) 55 | } else { 56 | // clear the next 57 | next = next[:0] 58 | 59 | delta := index - (idx + offset) 60 | for _, ex := range current { 61 | for _, n := range seg { 62 | if ex+delta == n { 63 | next = append(next, n) 64 | } 65 | } 66 | } 67 | 68 | if len(next) == 0 { 69 | releaseSegments(next) 70 | releaseSegments(current) 71 | return -1, nil 72 | } 73 | 74 | current = append(current[:0], next...) 75 | } 76 | 77 | index = idx + offset 78 | sub = s[index:] 79 | offset += idx 80 | } 81 | 82 | releaseSegments(next) 83 | 84 | return index, current 85 | } 86 | 87 | func (self EveryOf) Match(s string) bool { 88 | for _, m := range self.Matchers { 89 | if !m.Match(s) { 90 | return false 91 | } 92 | } 93 | 94 | return true 95 | } 96 | 97 | func (self EveryOf) String() string { 98 | return fmt.Sprintf("", self.Matchers) 99 | } 100 | -------------------------------------------------------------------------------- /match/every_of_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestEveryOfIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | matchers Matchers 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | Matchers{ 17 | NewAny(nil), 18 | NewText("b"), 19 | NewText("c"), 20 | }, 21 | "dbc", 22 | -1, 23 | nil, 24 | }, 25 | { 26 | Matchers{ 27 | NewAny(nil), 28 | NewPrefix("b"), 29 | NewSuffix("c"), 30 | }, 31 | "abc", 32 | 1, 33 | []int{2}, 34 | }, 35 | } { 36 | everyOf := NewEveryOf(test.matchers...) 37 | index, segments := everyOf.Index(test.fixture) 38 | if index != test.index { 39 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 40 | } 41 | if !reflect.DeepEqual(segments, test.segments) { 42 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /match/list.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gobwas/glob/util/runes" 6 | "unicode/utf8" 7 | ) 8 | 9 | type List struct { 10 | List []rune 11 | Not bool 12 | } 13 | 14 | func NewList(list []rune, not bool) List { 15 | return List{list, not} 16 | } 17 | 18 | func (self List) Match(s string) bool { 19 | r, w := utf8.DecodeRuneInString(s) 20 | if len(s) > w { 21 | return false 22 | } 23 | 24 | inList := runes.IndexRune(self.List, r) != -1 25 | return inList == !self.Not 26 | } 27 | 28 | func (self List) Len() int { 29 | return lenOne 30 | } 31 | 32 | func (self List) Index(s string) (int, []int) { 33 | for i, r := range s { 34 | if self.Not == (runes.IndexRune(self.List, r) == -1) { 35 | return i, segmentsByRuneLength[utf8.RuneLen(r)] 36 | } 37 | } 38 | 39 | return -1, nil 40 | } 41 | 42 | func (self List) String() string { 43 | var not string 44 | if self.Not { 45 | not = "!" 46 | } 47 | 48 | return fmt.Sprintf("", not, string(self.List)) 49 | } 50 | -------------------------------------------------------------------------------- /match/list_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestListIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | list []rune 11 | not bool 12 | fixture string 13 | index int 14 | segments []int 15 | }{ 16 | { 17 | []rune("ab"), 18 | false, 19 | "abc", 20 | 0, 21 | []int{1}, 22 | }, 23 | { 24 | []rune("ab"), 25 | true, 26 | "fffabfff", 27 | 0, 28 | []int{1}, 29 | }, 30 | } { 31 | p := NewList(test.list, test.not) 32 | index, segments := p.Index(test.fixture) 33 | if index != test.index { 34 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 35 | } 36 | if !reflect.DeepEqual(segments, test.segments) { 37 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 38 | } 39 | } 40 | } 41 | 42 | func BenchmarkIndexList(b *testing.B) { 43 | m := NewList([]rune("def"), false) 44 | 45 | for i := 0; i < b.N; i++ { 46 | m.Index(bench_pattern) 47 | } 48 | } 49 | 50 | func BenchmarkIndexListParallel(b *testing.B) { 51 | m := NewList([]rune("def"), false) 52 | 53 | b.RunParallel(func(pb *testing.PB) { 54 | for pb.Next() { 55 | m.Index(bench_pattern) 56 | } 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /match/match.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | // todo common table of rune's length 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | const lenOne = 1 11 | const lenZero = 0 12 | const lenNo = -1 13 | 14 | type Matcher interface { 15 | Match(string) bool 16 | Index(string) (int, []int) 17 | Len() int 18 | String() string 19 | } 20 | 21 | type Matchers []Matcher 22 | 23 | func (m Matchers) String() string { 24 | var s []string 25 | for _, matcher := range m { 26 | s = append(s, fmt.Sprint(matcher)) 27 | } 28 | 29 | return fmt.Sprintf("%s", strings.Join(s, ",")) 30 | } 31 | 32 | // appendMerge merges and sorts given already SORTED and UNIQUE segments. 33 | func appendMerge(target, sub []int) []int { 34 | lt, ls := len(target), len(sub) 35 | out := make([]int, 0, lt+ls) 36 | 37 | for x, y := 0, 0; x < lt || y < ls; { 38 | if x >= lt { 39 | out = append(out, sub[y:]...) 40 | break 41 | } 42 | 43 | if y >= ls { 44 | out = append(out, target[x:]...) 45 | break 46 | } 47 | 48 | xValue := target[x] 49 | yValue := sub[y] 50 | 51 | switch { 52 | 53 | case xValue == yValue: 54 | out = append(out, xValue) 55 | x++ 56 | y++ 57 | 58 | case xValue < yValue: 59 | out = append(out, xValue) 60 | x++ 61 | 62 | case yValue < xValue: 63 | out = append(out, yValue) 64 | y++ 65 | 66 | } 67 | } 68 | 69 | target = append(target[:0], out...) 70 | 71 | return target 72 | } 73 | 74 | func reverseSegments(input []int) { 75 | l := len(input) 76 | m := l / 2 77 | 78 | for i := 0; i < m; i++ { 79 | input[i], input[l-i-1] = input[l-i-1], input[i] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /match/match_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "unicode/utf8" 7 | ) 8 | 9 | var bench_separators = []rune{'.'} 10 | 11 | const bench_pattern = "abcdefghijklmnopqrstuvwxyz0123456789" 12 | 13 | func TestAppendMerge(t *testing.T) { 14 | for id, test := range []struct { 15 | segments [2][]int 16 | exp []int 17 | }{ 18 | { 19 | [2][]int{ 20 | {0, 6, 7}, 21 | {0, 1, 3}, 22 | }, 23 | []int{0, 1, 3, 6, 7}, 24 | }, 25 | { 26 | [2][]int{ 27 | {0, 1, 3, 6, 7}, 28 | {0, 1, 10}, 29 | }, 30 | []int{0, 1, 3, 6, 7, 10}, 31 | }, 32 | } { 33 | act := appendMerge(test.segments[0], test.segments[1]) 34 | if !reflect.DeepEqual(act, test.exp) { 35 | t.Errorf("#%d merge sort segments unexpected:\nact: %v\nexp:%v", id, act, test.exp) 36 | continue 37 | } 38 | } 39 | } 40 | 41 | func BenchmarkAppendMerge(b *testing.B) { 42 | s1 := []int{0, 1, 3, 6, 7} 43 | s2 := []int{0, 1, 3} 44 | 45 | for i := 0; i < b.N; i++ { 46 | appendMerge(s1, s2) 47 | } 48 | } 49 | 50 | func BenchmarkAppendMergeParallel(b *testing.B) { 51 | s1 := []int{0, 1, 3, 6, 7} 52 | s2 := []int{0, 1, 3} 53 | 54 | b.RunParallel(func(pb *testing.PB) { 55 | for pb.Next() { 56 | appendMerge(s1, s2) 57 | } 58 | }) 59 | } 60 | 61 | func BenchmarkReverse(b *testing.B) { 62 | for i := 0; i < b.N; i++ { 63 | reverseSegments([]int{1, 2, 3, 4}) 64 | } 65 | } 66 | 67 | func getTable() []int { 68 | table := make([]int, utf8.MaxRune+1) 69 | for i := 0; i <= utf8.MaxRune; i++ { 70 | table[i] = utf8.RuneLen(rune(i)) 71 | } 72 | 73 | return table 74 | } 75 | 76 | var table = getTable() 77 | 78 | const runeToLen = 'q' 79 | 80 | func BenchmarkRuneLenFromTable(b *testing.B) { 81 | for i := 0; i < b.N; i++ { 82 | _ = table[runeToLen] 83 | } 84 | } 85 | 86 | func BenchmarkRuneLenFromUTF8(b *testing.B) { 87 | for i := 0; i < b.N; i++ { 88 | _ = utf8.RuneLen(runeToLen) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /match/max.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "unicode/utf8" 6 | ) 7 | 8 | type Max struct { 9 | Limit int 10 | } 11 | 12 | func NewMax(l int) Max { 13 | return Max{l} 14 | } 15 | 16 | func (self Max) Match(s string) bool { 17 | var l int 18 | for range s { 19 | l += 1 20 | if l > self.Limit { 21 | return false 22 | } 23 | } 24 | 25 | return true 26 | } 27 | 28 | func (self Max) Index(s string) (int, []int) { 29 | segments := acquireSegments(self.Limit + 1) 30 | segments = append(segments, 0) 31 | var count int 32 | for i, r := range s { 33 | count++ 34 | if count > self.Limit { 35 | break 36 | } 37 | segments = append(segments, i+utf8.RuneLen(r)) 38 | } 39 | 40 | return 0, segments 41 | } 42 | 43 | func (self Max) Len() int { 44 | return lenNo 45 | } 46 | 47 | func (self Max) String() string { 48 | return fmt.Sprintf("", self.Limit) 49 | } 50 | -------------------------------------------------------------------------------- /match/max_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestMaxIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | limit int 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | 3, 17 | "abc", 18 | 0, 19 | []int{0, 1, 2, 3}, 20 | }, 21 | { 22 | 3, 23 | "abcdef", 24 | 0, 25 | []int{0, 1, 2, 3}, 26 | }, 27 | } { 28 | p := NewMax(test.limit) 29 | index, segments := p.Index(test.fixture) 30 | if index != test.index { 31 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 32 | } 33 | if !reflect.DeepEqual(segments, test.segments) { 34 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 35 | } 36 | } 37 | } 38 | 39 | func BenchmarkIndexMax(b *testing.B) { 40 | m := NewMax(10) 41 | 42 | for i := 0; i < b.N; i++ { 43 | _, s := m.Index(bench_pattern) 44 | releaseSegments(s) 45 | } 46 | } 47 | 48 | func BenchmarkIndexMaxParallel(b *testing.B) { 49 | m := NewMax(10) 50 | 51 | b.RunParallel(func(pb *testing.PB) { 52 | for pb.Next() { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /match/min.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "unicode/utf8" 6 | ) 7 | 8 | type Min struct { 9 | Limit int 10 | } 11 | 12 | func NewMin(l int) Min { 13 | return Min{l} 14 | } 15 | 16 | func (self Min) Match(s string) bool { 17 | var l int 18 | for range s { 19 | l += 1 20 | if l >= self.Limit { 21 | return true 22 | } 23 | } 24 | 25 | return false 26 | } 27 | 28 | func (self Min) Index(s string) (int, []int) { 29 | var count int 30 | 31 | c := len(s) - self.Limit + 1 32 | if c <= 0 { 33 | return -1, nil 34 | } 35 | 36 | segments := acquireSegments(c) 37 | for i, r := range s { 38 | count++ 39 | if count >= self.Limit { 40 | segments = append(segments, i+utf8.RuneLen(r)) 41 | } 42 | } 43 | 44 | if len(segments) == 0 { 45 | return -1, nil 46 | } 47 | 48 | return 0, segments 49 | } 50 | 51 | func (self Min) Len() int { 52 | return lenNo 53 | } 54 | 55 | func (self Min) String() string { 56 | return fmt.Sprintf("", self.Limit) 57 | } 58 | -------------------------------------------------------------------------------- /match/min_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestMinIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | limit int 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | 1, 17 | "abc", 18 | 0, 19 | []int{1, 2, 3}, 20 | }, 21 | { 22 | 3, 23 | "abcd", 24 | 0, 25 | []int{3, 4}, 26 | }, 27 | } { 28 | p := NewMin(test.limit) 29 | index, segments := p.Index(test.fixture) 30 | if index != test.index { 31 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 32 | } 33 | if !reflect.DeepEqual(segments, test.segments) { 34 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 35 | } 36 | } 37 | } 38 | 39 | func BenchmarkIndexMin(b *testing.B) { 40 | m := NewMin(10) 41 | 42 | for i := 0; i < b.N; i++ { 43 | _, s := m.Index(bench_pattern) 44 | releaseSegments(s) 45 | } 46 | } 47 | 48 | func BenchmarkIndexMinParallel(b *testing.B) { 49 | m := NewMin(10) 50 | 51 | b.RunParallel(func(pb *testing.PB) { 52 | for pb.Next() { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /match/nothing.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Nothing struct{} 8 | 9 | func NewNothing() Nothing { 10 | return Nothing{} 11 | } 12 | 13 | func (self Nothing) Match(s string) bool { 14 | return len(s) == 0 15 | } 16 | 17 | func (self Nothing) Index(s string) (int, []int) { 18 | return 0, segments0 19 | } 20 | 21 | func (self Nothing) Len() int { 22 | return lenZero 23 | } 24 | 25 | func (self Nothing) String() string { 26 | return fmt.Sprintf("") 27 | } 28 | -------------------------------------------------------------------------------- /match/nothing_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNothingIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | fixture string 11 | index int 12 | segments []int 13 | }{ 14 | { 15 | "abc", 16 | 0, 17 | []int{0}, 18 | }, 19 | { 20 | "", 21 | 0, 22 | []int{0}, 23 | }, 24 | } { 25 | p := NewNothing() 26 | index, segments := p.Index(test.fixture) 27 | if index != test.index { 28 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 29 | } 30 | if !reflect.DeepEqual(segments, test.segments) { 31 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 32 | } 33 | } 34 | } 35 | 36 | func BenchmarkIndexNothing(b *testing.B) { 37 | m := NewNothing() 38 | 39 | for i := 0; i < b.N; i++ { 40 | _, s := m.Index(bench_pattern) 41 | releaseSegments(s) 42 | } 43 | } 44 | 45 | func BenchmarkIndexNothingParallel(b *testing.B) { 46 | m := NewNothing() 47 | 48 | b.RunParallel(func(pb *testing.PB) { 49 | for pb.Next() { 50 | _, s := m.Index(bench_pattern) 51 | releaseSegments(s) 52 | } 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /match/prefix.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode/utf8" 7 | ) 8 | 9 | type Prefix struct { 10 | Prefix string 11 | } 12 | 13 | func NewPrefix(p string) Prefix { 14 | return Prefix{p} 15 | } 16 | 17 | func (self Prefix) Index(s string) (int, []int) { 18 | idx := strings.Index(s, self.Prefix) 19 | if idx == -1 { 20 | return -1, nil 21 | } 22 | 23 | length := len(self.Prefix) 24 | var sub string 25 | if len(s) > idx+length { 26 | sub = s[idx+length:] 27 | } else { 28 | sub = "" 29 | } 30 | 31 | segments := acquireSegments(len(sub) + 1) 32 | segments = append(segments, length) 33 | for i, r := range sub { 34 | segments = append(segments, length+i+utf8.RuneLen(r)) 35 | } 36 | 37 | return idx, segments 38 | } 39 | 40 | func (self Prefix) Len() int { 41 | return lenNo 42 | } 43 | 44 | func (self Prefix) Match(s string) bool { 45 | return strings.HasPrefix(s, self.Prefix) 46 | } 47 | 48 | func (self Prefix) String() string { 49 | return fmt.Sprintf("", self.Prefix) 50 | } 51 | -------------------------------------------------------------------------------- /match/prefix_any.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode/utf8" 7 | 8 | sutil "github.com/gobwas/glob/util/strings" 9 | ) 10 | 11 | type PrefixAny struct { 12 | Prefix string 13 | Separators []rune 14 | } 15 | 16 | func NewPrefixAny(s string, sep []rune) PrefixAny { 17 | return PrefixAny{s, sep} 18 | } 19 | 20 | func (self PrefixAny) Index(s string) (int, []int) { 21 | idx := strings.Index(s, self.Prefix) 22 | if idx == -1 { 23 | return -1, nil 24 | } 25 | 26 | n := len(self.Prefix) 27 | sub := s[idx+n:] 28 | i := sutil.IndexAnyRunes(sub, self.Separators) 29 | if i > -1 { 30 | sub = sub[:i] 31 | } 32 | 33 | seg := acquireSegments(len(sub) + 1) 34 | seg = append(seg, n) 35 | for i, r := range sub { 36 | seg = append(seg, n+i+utf8.RuneLen(r)) 37 | } 38 | 39 | return idx, seg 40 | } 41 | 42 | func (self PrefixAny) Len() int { 43 | return lenNo 44 | } 45 | 46 | func (self PrefixAny) Match(s string) bool { 47 | if !strings.HasPrefix(s, self.Prefix) { 48 | return false 49 | } 50 | return sutil.IndexAnyRunes(s[len(self.Prefix):], self.Separators) == -1 51 | } 52 | 53 | func (self PrefixAny) String() string { 54 | return fmt.Sprintf("", self.Prefix, string(self.Separators)) 55 | } 56 | -------------------------------------------------------------------------------- /match/prefix_any_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPrefixAnyIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | prefix string 11 | separators []rune 12 | fixture string 13 | index int 14 | segments []int 15 | }{ 16 | { 17 | "ab", 18 | []rune{'.'}, 19 | "ab", 20 | 0, 21 | []int{2}, 22 | }, 23 | { 24 | "ab", 25 | []rune{'.'}, 26 | "abc", 27 | 0, 28 | []int{2, 3}, 29 | }, 30 | { 31 | "ab", 32 | []rune{'.'}, 33 | "qw.abcd.efg", 34 | 3, 35 | []int{2, 3, 4}, 36 | }, 37 | } { 38 | p := NewPrefixAny(test.prefix, test.separators) 39 | index, segments := p.Index(test.fixture) 40 | if index != test.index { 41 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 42 | } 43 | if !reflect.DeepEqual(segments, test.segments) { 44 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /match/prefix_suffix.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type PrefixSuffix struct { 9 | Prefix, Suffix string 10 | } 11 | 12 | func NewPrefixSuffix(p, s string) PrefixSuffix { 13 | return PrefixSuffix{p, s} 14 | } 15 | 16 | func (self PrefixSuffix) Index(s string) (int, []int) { 17 | prefixIdx := strings.Index(s, self.Prefix) 18 | if prefixIdx == -1 { 19 | return -1, nil 20 | } 21 | 22 | suffixLen := len(self.Suffix) 23 | if suffixLen <= 0 { 24 | return prefixIdx, []int{len(s) - prefixIdx} 25 | } 26 | 27 | if (len(s) - prefixIdx) <= 0 { 28 | return -1, nil 29 | } 30 | 31 | segments := acquireSegments(len(s) - prefixIdx) 32 | for sub := s[prefixIdx:]; ; { 33 | suffixIdx := strings.LastIndex(sub, self.Suffix) 34 | if suffixIdx == -1 { 35 | break 36 | } 37 | 38 | segments = append(segments, suffixIdx+suffixLen) 39 | sub = sub[:suffixIdx] 40 | } 41 | 42 | if len(segments) == 0 { 43 | releaseSegments(segments) 44 | return -1, nil 45 | } 46 | 47 | reverseSegments(segments) 48 | 49 | return prefixIdx, segments 50 | } 51 | 52 | func (self PrefixSuffix) Len() int { 53 | return lenNo 54 | } 55 | 56 | func (self PrefixSuffix) Match(s string) bool { 57 | return strings.HasPrefix(s, self.Prefix) && strings.HasSuffix(s, self.Suffix) 58 | } 59 | 60 | func (self PrefixSuffix) String() string { 61 | return fmt.Sprintf("", self.Prefix, self.Suffix) 62 | } 63 | -------------------------------------------------------------------------------- /match/prefix_suffix_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPrefixSuffixIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | prefix string 11 | suffix string 12 | fixture string 13 | index int 14 | segments []int 15 | }{ 16 | { 17 | "a", 18 | "c", 19 | "abc", 20 | 0, 21 | []int{3}, 22 | }, 23 | { 24 | "f", 25 | "f", 26 | "fffabfff", 27 | 0, 28 | []int{1, 2, 3, 6, 7, 8}, 29 | }, 30 | { 31 | "ab", 32 | "bc", 33 | "abc", 34 | 0, 35 | []int{3}, 36 | }, 37 | } { 38 | p := NewPrefixSuffix(test.prefix, test.suffix) 39 | index, segments := p.Index(test.fixture) 40 | if index != test.index { 41 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 42 | } 43 | if !reflect.DeepEqual(segments, test.segments) { 44 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 45 | } 46 | } 47 | } 48 | 49 | func BenchmarkIndexPrefixSuffix(b *testing.B) { 50 | m := NewPrefixSuffix("qew", "sqw") 51 | 52 | for i := 0; i < b.N; i++ { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | } 57 | 58 | func BenchmarkIndexPrefixSuffixParallel(b *testing.B) { 59 | m := NewPrefixSuffix("qew", "sqw") 60 | 61 | b.RunParallel(func(pb *testing.PB) { 62 | for pb.Next() { 63 | _, s := m.Index(bench_pattern) 64 | releaseSegments(s) 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /match/prefix_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPrefixIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | prefix string 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | "ab", 17 | "abc", 18 | 0, 19 | []int{2, 3}, 20 | }, 21 | { 22 | "ab", 23 | "fffabfff", 24 | 3, 25 | []int{2, 3, 4, 5}, 26 | }, 27 | } { 28 | p := NewPrefix(test.prefix) 29 | index, segments := p.Index(test.fixture) 30 | if index != test.index { 31 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 32 | } 33 | if !reflect.DeepEqual(segments, test.segments) { 34 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 35 | } 36 | } 37 | } 38 | 39 | func BenchmarkIndexPrefix(b *testing.B) { 40 | m := NewPrefix("qew") 41 | 42 | for i := 0; i < b.N; i++ { 43 | _, s := m.Index(bench_pattern) 44 | releaseSegments(s) 45 | } 46 | } 47 | 48 | func BenchmarkIndexPrefixParallel(b *testing.B) { 49 | m := NewPrefix("qew") 50 | 51 | b.RunParallel(func(pb *testing.PB) { 52 | for pb.Next() { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /match/range.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "unicode/utf8" 6 | ) 7 | 8 | type Range struct { 9 | Lo, Hi rune 10 | Not bool 11 | } 12 | 13 | func NewRange(lo, hi rune, not bool) Range { 14 | return Range{lo, hi, not} 15 | } 16 | 17 | func (self Range) Len() int { 18 | return lenOne 19 | } 20 | 21 | func (self Range) Match(s string) bool { 22 | r, w := utf8.DecodeRuneInString(s) 23 | if len(s) > w { 24 | return false 25 | } 26 | 27 | inRange := r >= self.Lo && r <= self.Hi 28 | 29 | return inRange == !self.Not 30 | } 31 | 32 | func (self Range) Index(s string) (int, []int) { 33 | for i, r := range s { 34 | if self.Not != (r >= self.Lo && r <= self.Hi) { 35 | return i, segmentsByRuneLength[utf8.RuneLen(r)] 36 | } 37 | } 38 | 39 | return -1, nil 40 | } 41 | 42 | func (self Range) String() string { 43 | var not string 44 | if self.Not { 45 | not = "!" 46 | } 47 | return fmt.Sprintf("", not, string(self.Lo), string(self.Hi)) 48 | } 49 | -------------------------------------------------------------------------------- /match/range_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestRangeIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | lo, hi rune 11 | not bool 12 | fixture string 13 | index int 14 | segments []int 15 | }{ 16 | { 17 | 'a', 'z', 18 | false, 19 | "abc", 20 | 0, 21 | []int{1}, 22 | }, 23 | { 24 | 'a', 'c', 25 | false, 26 | "abcd", 27 | 0, 28 | []int{1}, 29 | }, 30 | { 31 | 'a', 'c', 32 | true, 33 | "abcd", 34 | 3, 35 | []int{1}, 36 | }, 37 | } { 38 | m := NewRange(test.lo, test.hi, test.not) 39 | index, segments := m.Index(test.fixture) 40 | if index != test.index { 41 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 42 | } 43 | if !reflect.DeepEqual(segments, test.segments) { 44 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 45 | } 46 | } 47 | } 48 | 49 | func BenchmarkIndexRange(b *testing.B) { 50 | m := NewRange('0', '9', false) 51 | 52 | for i := 0; i < b.N; i++ { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | } 57 | 58 | func BenchmarkIndexRangeParallel(b *testing.B) { 59 | m := NewRange('0', '9', false) 60 | 61 | b.RunParallel(func(pb *testing.PB) { 62 | for pb.Next() { 63 | _, s := m.Index(bench_pattern) 64 | releaseSegments(s) 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /match/row.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Row struct { 8 | Matchers Matchers 9 | RunesLength int 10 | Segments []int 11 | } 12 | 13 | func NewRow(len int, m ...Matcher) Row { 14 | return Row{ 15 | Matchers: Matchers(m), 16 | RunesLength: len, 17 | Segments: []int{len}, 18 | } 19 | } 20 | 21 | func (self Row) matchAll(s string) bool { 22 | var idx int 23 | for _, m := range self.Matchers { 24 | length := m.Len() 25 | 26 | var next, i int 27 | for next = range s[idx:] { 28 | i++ 29 | if i == length { 30 | break 31 | } 32 | } 33 | 34 | if i < length || !m.Match(s[idx:idx+next+1]) { 35 | return false 36 | } 37 | 38 | idx += next + 1 39 | } 40 | 41 | return true 42 | } 43 | 44 | func (self Row) lenOk(s string) bool { 45 | var i int 46 | for range s { 47 | i++ 48 | if i > self.RunesLength { 49 | return false 50 | } 51 | } 52 | return self.RunesLength == i 53 | } 54 | 55 | func (self Row) Match(s string) bool { 56 | return self.lenOk(s) && self.matchAll(s) 57 | } 58 | 59 | func (self Row) Len() (l int) { 60 | return self.RunesLength 61 | } 62 | 63 | func (self Row) Index(s string) (int, []int) { 64 | for i := range s { 65 | if len(s[i:]) < self.RunesLength { 66 | break 67 | } 68 | if self.matchAll(s[i:]) { 69 | return i, self.Segments 70 | } 71 | } 72 | return -1, nil 73 | } 74 | 75 | func (self Row) String() string { 76 | return fmt.Sprintf("", self.RunesLength, self.Matchers) 77 | } 78 | -------------------------------------------------------------------------------- /match/row_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestRowIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | matchers Matchers 11 | length int 12 | fixture string 13 | index int 14 | segments []int 15 | }{ 16 | { 17 | Matchers{ 18 | NewText("abc"), 19 | NewText("def"), 20 | NewSingle(nil), 21 | }, 22 | 7, 23 | "qweabcdefghij", 24 | 3, 25 | []int{7}, 26 | }, 27 | { 28 | Matchers{ 29 | NewText("abc"), 30 | NewText("def"), 31 | NewSingle(nil), 32 | }, 33 | 7, 34 | "abcd", 35 | -1, 36 | nil, 37 | }, 38 | } { 39 | p := NewRow(test.length, test.matchers...) 40 | index, segments := p.Index(test.fixture) 41 | if index != test.index { 42 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 43 | } 44 | if !reflect.DeepEqual(segments, test.segments) { 45 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 46 | } 47 | } 48 | } 49 | 50 | func BenchmarkRowIndex(b *testing.B) { 51 | m := NewRow( 52 | 7, 53 | Matchers{ 54 | NewText("abc"), 55 | NewText("def"), 56 | NewSingle(nil), 57 | }..., 58 | ) 59 | 60 | for i := 0; i < b.N; i++ { 61 | _, s := m.Index(bench_pattern) 62 | releaseSegments(s) 63 | } 64 | } 65 | 66 | func BenchmarkIndexRowParallel(b *testing.B) { 67 | m := NewRow( 68 | 7, 69 | Matchers{ 70 | NewText("abc"), 71 | NewText("def"), 72 | NewSingle(nil), 73 | }..., 74 | ) 75 | 76 | b.RunParallel(func(pb *testing.PB) { 77 | for pb.Next() { 78 | _, s := m.Index(bench_pattern) 79 | releaseSegments(s) 80 | } 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /match/segments.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type SomePool interface { 8 | Get() []int 9 | Put([]int) 10 | } 11 | 12 | var segmentsPools [1024]sync.Pool 13 | 14 | func toPowerOfTwo(v int) int { 15 | v-- 16 | v |= v >> 1 17 | v |= v >> 2 18 | v |= v >> 4 19 | v |= v >> 8 20 | v |= v >> 16 21 | v++ 22 | 23 | return v 24 | } 25 | 26 | const ( 27 | cacheFrom = 16 28 | cacheToAndHigher = 1024 29 | cacheFromIndex = 15 30 | cacheToAndHigherIndex = 1023 31 | ) 32 | 33 | var ( 34 | segments0 = []int{0} 35 | segments1 = []int{1} 36 | segments2 = []int{2} 37 | segments3 = []int{3} 38 | segments4 = []int{4} 39 | ) 40 | 41 | var segmentsByRuneLength [5][]int = [5][]int{ 42 | 0: segments0, 43 | 1: segments1, 44 | 2: segments2, 45 | 3: segments3, 46 | 4: segments4, 47 | } 48 | 49 | func init() { 50 | for i := cacheToAndHigher; i >= cacheFrom; i >>= 1 { 51 | func(i int) { 52 | segmentsPools[i-1] = sync.Pool{New: func() interface{} { 53 | return make([]int, 0, i) 54 | }} 55 | }(i) 56 | } 57 | } 58 | 59 | func getTableIndex(c int) int { 60 | p := toPowerOfTwo(c) 61 | switch { 62 | case p >= cacheToAndHigher: 63 | return cacheToAndHigherIndex 64 | case p <= cacheFrom: 65 | return cacheFromIndex 66 | default: 67 | return p - 1 68 | } 69 | } 70 | 71 | func acquireSegments(c int) []int { 72 | // make []int with less capacity than cacheFrom 73 | // is faster than acquiring it from pool 74 | if c < cacheFrom { 75 | return make([]int, 0, c) 76 | } 77 | 78 | return segmentsPools[getTableIndex(c)].Get().([]int)[:0] 79 | } 80 | 81 | func releaseSegments(s []int) { 82 | c := cap(s) 83 | 84 | // make []int with less capacity than cacheFrom 85 | // is faster than acquiring it from pool 86 | if c < cacheFrom { 87 | return 88 | } 89 | 90 | segmentsPools[getTableIndex(c)].Put(s) 91 | } 92 | -------------------------------------------------------------------------------- /match/segments_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func benchPool(i int, b *testing.B) { 9 | pool := sync.Pool{New: func() interface{} { 10 | return make([]int, 0, i) 11 | }} 12 | 13 | b.RunParallel(func(pb *testing.PB) { 14 | for pb.Next() { 15 | s := pool.Get().([]int)[:0] 16 | pool.Put(s) 17 | } 18 | }) 19 | } 20 | 21 | func benchMake(i int, b *testing.B) { 22 | b.RunParallel(func(pb *testing.PB) { 23 | for pb.Next() { 24 | _ = make([]int, 0, i) 25 | } 26 | }) 27 | } 28 | 29 | func BenchmarkSegmentsPool_1(b *testing.B) { 30 | benchPool(1, b) 31 | } 32 | func BenchmarkSegmentsPool_2(b *testing.B) { 33 | benchPool(2, b) 34 | } 35 | func BenchmarkSegmentsPool_4(b *testing.B) { 36 | benchPool(4, b) 37 | } 38 | func BenchmarkSegmentsPool_8(b *testing.B) { 39 | benchPool(8, b) 40 | } 41 | func BenchmarkSegmentsPool_16(b *testing.B) { 42 | benchPool(16, b) 43 | } 44 | func BenchmarkSegmentsPool_32(b *testing.B) { 45 | benchPool(32, b) 46 | } 47 | func BenchmarkSegmentsPool_64(b *testing.B) { 48 | benchPool(64, b) 49 | } 50 | func BenchmarkSegmentsPool_128(b *testing.B) { 51 | benchPool(128, b) 52 | } 53 | func BenchmarkSegmentsPool_256(b *testing.B) { 54 | benchPool(256, b) 55 | } 56 | 57 | func BenchmarkSegmentsMake_1(b *testing.B) { 58 | benchMake(1, b) 59 | } 60 | func BenchmarkSegmentsMake_2(b *testing.B) { 61 | benchMake(2, b) 62 | } 63 | func BenchmarkSegmentsMake_4(b *testing.B) { 64 | benchMake(4, b) 65 | } 66 | func BenchmarkSegmentsMake_8(b *testing.B) { 67 | benchMake(8, b) 68 | } 69 | func BenchmarkSegmentsMake_16(b *testing.B) { 70 | benchMake(16, b) 71 | } 72 | func BenchmarkSegmentsMake_32(b *testing.B) { 73 | benchMake(32, b) 74 | } 75 | func BenchmarkSegmentsMake_64(b *testing.B) { 76 | benchMake(64, b) 77 | } 78 | func BenchmarkSegmentsMake_128(b *testing.B) { 79 | benchMake(128, b) 80 | } 81 | func BenchmarkSegmentsMake_256(b *testing.B) { 82 | benchMake(256, b) 83 | } 84 | -------------------------------------------------------------------------------- /match/single.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gobwas/glob/util/runes" 6 | "unicode/utf8" 7 | ) 8 | 9 | // single represents ? 10 | type Single struct { 11 | Separators []rune 12 | } 13 | 14 | func NewSingle(s []rune) Single { 15 | return Single{s} 16 | } 17 | 18 | func (self Single) Match(s string) bool { 19 | r, w := utf8.DecodeRuneInString(s) 20 | if len(s) > w { 21 | return false 22 | } 23 | 24 | return runes.IndexRune(self.Separators, r) == -1 25 | } 26 | 27 | func (self Single) Len() int { 28 | return lenOne 29 | } 30 | 31 | func (self Single) Index(s string) (int, []int) { 32 | for i, r := range s { 33 | if runes.IndexRune(self.Separators, r) == -1 { 34 | return i, segmentsByRuneLength[utf8.RuneLen(r)] 35 | } 36 | } 37 | 38 | return -1, nil 39 | } 40 | 41 | func (self Single) String() string { 42 | return fmt.Sprintf("", string(self.Separators)) 43 | } 44 | -------------------------------------------------------------------------------- /match/single_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSingleIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | separators []rune 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | []rune{'.'}, 17 | ".abc", 18 | 1, 19 | []int{1}, 20 | }, 21 | { 22 | []rune{'.'}, 23 | ".", 24 | -1, 25 | nil, 26 | }, 27 | } { 28 | p := NewSingle(test.separators) 29 | index, segments := p.Index(test.fixture) 30 | if index != test.index { 31 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 32 | } 33 | if !reflect.DeepEqual(segments, test.segments) { 34 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 35 | } 36 | } 37 | } 38 | 39 | func BenchmarkIndexSingle(b *testing.B) { 40 | m := NewSingle(bench_separators) 41 | 42 | for i := 0; i < b.N; i++ { 43 | _, s := m.Index(bench_pattern) 44 | releaseSegments(s) 45 | } 46 | } 47 | 48 | func BenchmarkIndexSingleParallel(b *testing.B) { 49 | m := NewSingle(bench_separators) 50 | 51 | b.RunParallel(func(pb *testing.PB) { 52 | for pb.Next() { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /match/suffix.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Suffix struct { 9 | Suffix string 10 | } 11 | 12 | func NewSuffix(s string) Suffix { 13 | return Suffix{s} 14 | } 15 | 16 | func (self Suffix) Len() int { 17 | return lenNo 18 | } 19 | 20 | func (self Suffix) Match(s string) bool { 21 | return strings.HasSuffix(s, self.Suffix) 22 | } 23 | 24 | func (self Suffix) Index(s string) (int, []int) { 25 | idx := strings.Index(s, self.Suffix) 26 | if idx == -1 { 27 | return -1, nil 28 | } 29 | 30 | return 0, []int{idx + len(self.Suffix)} 31 | } 32 | 33 | func (self Suffix) String() string { 34 | return fmt.Sprintf("", self.Suffix) 35 | } 36 | -------------------------------------------------------------------------------- /match/suffix_any.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | sutil "github.com/gobwas/glob/util/strings" 8 | ) 9 | 10 | type SuffixAny struct { 11 | Suffix string 12 | Separators []rune 13 | } 14 | 15 | func NewSuffixAny(s string, sep []rune) SuffixAny { 16 | return SuffixAny{s, sep} 17 | } 18 | 19 | func (self SuffixAny) Index(s string) (int, []int) { 20 | idx := strings.Index(s, self.Suffix) 21 | if idx == -1 { 22 | return -1, nil 23 | } 24 | 25 | i := sutil.LastIndexAnyRunes(s[:idx], self.Separators) + 1 26 | 27 | return i, []int{idx + len(self.Suffix) - i} 28 | } 29 | 30 | func (self SuffixAny) Len() int { 31 | return lenNo 32 | } 33 | 34 | func (self SuffixAny) Match(s string) bool { 35 | if !strings.HasSuffix(s, self.Suffix) { 36 | return false 37 | } 38 | return sutil.IndexAnyRunes(s[:len(s)-len(self.Suffix)], self.Separators) == -1 39 | } 40 | 41 | func (self SuffixAny) String() string { 42 | return fmt.Sprintf("", string(self.Separators), self.Suffix) 43 | } 44 | -------------------------------------------------------------------------------- /match/suffix_any_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSuffixAnyIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | suffix string 11 | separators []rune 12 | fixture string 13 | index int 14 | segments []int 15 | }{ 16 | { 17 | "ab", 18 | []rune{'.'}, 19 | "ab", 20 | 0, 21 | []int{2}, 22 | }, 23 | { 24 | "ab", 25 | []rune{'.'}, 26 | "cab", 27 | 0, 28 | []int{3}, 29 | }, 30 | { 31 | "ab", 32 | []rune{'.'}, 33 | "qw.cdab.efg", 34 | 3, 35 | []int{4}, 36 | }, 37 | } { 38 | p := NewSuffixAny(test.suffix, test.separators) 39 | index, segments := p.Index(test.fixture) 40 | if index != test.index { 41 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 42 | } 43 | if !reflect.DeepEqual(segments, test.segments) { 44 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /match/suffix_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSuffixIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | prefix string 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | "ab", 17 | "abc", 18 | 0, 19 | []int{2}, 20 | }, 21 | { 22 | "ab", 23 | "fffabfff", 24 | 0, 25 | []int{5}, 26 | }, 27 | } { 28 | p := NewSuffix(test.prefix) 29 | index, segments := p.Index(test.fixture) 30 | if index != test.index { 31 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 32 | } 33 | if !reflect.DeepEqual(segments, test.segments) { 34 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 35 | } 36 | } 37 | } 38 | 39 | func BenchmarkIndexSuffix(b *testing.B) { 40 | m := NewSuffix("qwe") 41 | 42 | for i := 0; i < b.N; i++ { 43 | _, s := m.Index(bench_pattern) 44 | releaseSegments(s) 45 | } 46 | } 47 | 48 | func BenchmarkIndexSuffixParallel(b *testing.B) { 49 | m := NewSuffix("qwe") 50 | 51 | b.RunParallel(func(pb *testing.PB) { 52 | for pb.Next() { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /match/super.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Super struct{} 8 | 9 | func NewSuper() Super { 10 | return Super{} 11 | } 12 | 13 | func (self Super) Match(s string) bool { 14 | return true 15 | } 16 | 17 | func (self Super) Len() int { 18 | return lenNo 19 | } 20 | 21 | func (self Super) Index(s string) (int, []int) { 22 | segments := acquireSegments(len(s) + 1) 23 | for i := range s { 24 | segments = append(segments, i) 25 | } 26 | segments = append(segments, len(s)) 27 | 28 | return 0, segments 29 | } 30 | 31 | func (self Super) String() string { 32 | return fmt.Sprintf("") 33 | } 34 | -------------------------------------------------------------------------------- /match/super_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSuperIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | fixture string 11 | index int 12 | segments []int 13 | }{ 14 | { 15 | "abc", 16 | 0, 17 | []int{0, 1, 2, 3}, 18 | }, 19 | { 20 | "", 21 | 0, 22 | []int{0}, 23 | }, 24 | } { 25 | p := NewSuper() 26 | index, segments := p.Index(test.fixture) 27 | if index != test.index { 28 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 29 | } 30 | if !reflect.DeepEqual(segments, test.segments) { 31 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 32 | } 33 | } 34 | } 35 | 36 | func BenchmarkIndexSuper(b *testing.B) { 37 | m := NewSuper() 38 | 39 | for i := 0; i < b.N; i++ { 40 | _, s := m.Index(bench_pattern) 41 | releaseSegments(s) 42 | } 43 | } 44 | 45 | func BenchmarkIndexSuperParallel(b *testing.B) { 46 | m := NewSuper() 47 | 48 | b.RunParallel(func(pb *testing.PB) { 49 | for pb.Next() { 50 | _, s := m.Index(bench_pattern) 51 | releaseSegments(s) 52 | } 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /match/text.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode/utf8" 7 | ) 8 | 9 | // raw represents raw string to match 10 | type Text struct { 11 | Str string 12 | RunesLength int 13 | BytesLength int 14 | Segments []int 15 | } 16 | 17 | func NewText(s string) Text { 18 | return Text{ 19 | Str: s, 20 | RunesLength: utf8.RuneCountInString(s), 21 | BytesLength: len(s), 22 | Segments: []int{len(s)}, 23 | } 24 | } 25 | 26 | func (self Text) Match(s string) bool { 27 | return self.Str == s 28 | } 29 | 30 | func (self Text) Len() int { 31 | return self.RunesLength 32 | } 33 | 34 | func (self Text) Index(s string) (int, []int) { 35 | index := strings.Index(s, self.Str) 36 | if index == -1 { 37 | return -1, nil 38 | } 39 | 40 | return index, self.Segments 41 | } 42 | 43 | func (self Text) String() string { 44 | return fmt.Sprintf("", self.Str) 45 | } 46 | -------------------------------------------------------------------------------- /match/text_test.go: -------------------------------------------------------------------------------- 1 | package match 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestTextIndex(t *testing.T) { 9 | for id, test := range []struct { 10 | text string 11 | fixture string 12 | index int 13 | segments []int 14 | }{ 15 | { 16 | "b", 17 | "abc", 18 | 1, 19 | []int{1}, 20 | }, 21 | { 22 | "f", 23 | "abcd", 24 | -1, 25 | nil, 26 | }, 27 | } { 28 | m := NewText(test.text) 29 | index, segments := m.Index(test.fixture) 30 | if index != test.index { 31 | t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) 32 | } 33 | if !reflect.DeepEqual(segments, test.segments) { 34 | t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) 35 | } 36 | } 37 | } 38 | 39 | func BenchmarkIndexText(b *testing.B) { 40 | m := NewText("foo") 41 | 42 | for i := 0; i < b.N; i++ { 43 | _, s := m.Index(bench_pattern) 44 | releaseSegments(s) 45 | } 46 | } 47 | 48 | func BenchmarkIndexTextParallel(b *testing.B) { 49 | m := NewText("foo") 50 | 51 | b.RunParallel(func(pb *testing.PB) { 52 | for pb.Next() { 53 | _, s := m.Index(bench_pattern) 54 | releaseSegments(s) 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # glob.[go](https://golang.org) 2 | 3 | [![GoDoc][godoc-image]][godoc-url] [![Build Status][travis-image]][travis-url] 4 | 5 | > Go Globbing Library. 6 | 7 | ## Install 8 | 9 | ```shell 10 | go get github.com/gobwas/glob 11 | ``` 12 | 13 | ## Example 14 | 15 | ```go 16 | 17 | package main 18 | 19 | import "github.com/gobwas/glob" 20 | 21 | func main() { 22 | var g glob.Glob 23 | 24 | // create simple glob 25 | g = glob.MustCompile("*.github.com") 26 | g.Match("api.github.com") // true 27 | 28 | // quote meta characters and then create simple glob 29 | g = glob.MustCompile(glob.QuoteMeta("*.github.com")) 30 | g.Match("*.github.com") // true 31 | 32 | // create new glob with set of delimiters as ["."] 33 | g = glob.MustCompile("api.*.com", '.') 34 | g.Match("api.github.com") // true 35 | g.Match("api.gi.hub.com") // false 36 | 37 | // create new glob with set of delimiters as ["."] 38 | // but now with super wildcard 39 | g = glob.MustCompile("api.**.com", '.') 40 | g.Match("api.github.com") // true 41 | g.Match("api.gi.hub.com") // true 42 | 43 | // create glob with single symbol wildcard 44 | g = glob.MustCompile("?at") 45 | g.Match("cat") // true 46 | g.Match("fat") // true 47 | g.Match("at") // false 48 | 49 | // create glob with single symbol wildcard and delimiters ['f'] 50 | g = glob.MustCompile("?at", 'f') 51 | g.Match("cat") // true 52 | g.Match("fat") // false 53 | g.Match("at") // false 54 | 55 | // create glob with character-list matchers 56 | g = glob.MustCompile("[abc]at") 57 | g.Match("cat") // true 58 | g.Match("bat") // true 59 | g.Match("fat") // false 60 | g.Match("at") // false 61 | 62 | // create glob with character-list matchers 63 | g = glob.MustCompile("[!abc]at") 64 | g.Match("cat") // false 65 | g.Match("bat") // false 66 | g.Match("fat") // true 67 | g.Match("at") // false 68 | 69 | // create glob with character-range matchers 70 | g = glob.MustCompile("[a-c]at") 71 | g.Match("cat") // true 72 | g.Match("bat") // true 73 | g.Match("fat") // false 74 | g.Match("at") // false 75 | 76 | // create glob with character-range matchers 77 | g = glob.MustCompile("[!a-c]at") 78 | g.Match("cat") // false 79 | g.Match("bat") // false 80 | g.Match("fat") // true 81 | g.Match("at") // false 82 | 83 | // create glob with pattern-alternatives list 84 | g = glob.MustCompile("{cat,bat,[fr]at}") 85 | g.Match("cat") // true 86 | g.Match("bat") // true 87 | g.Match("fat") // true 88 | g.Match("rat") // true 89 | g.Match("at") // false 90 | g.Match("zat") // false 91 | } 92 | 93 | ``` 94 | 95 | ## Performance 96 | 97 | This library is created for compile-once patterns. This means, that compilation could take time, but 98 | strings matching is done faster, than in case when always parsing template. 99 | 100 | If you will not use compiled `glob.Glob` object, and do `g := glob.MustCompile(pattern); g.Match(...)` every time, then your code will be much more slower. 101 | 102 | Run `go test -bench=.` from source root to see the benchmarks: 103 | 104 | Pattern | Fixture | Match | Speed (ns/op) 105 | --------|---------|-------|-------------- 106 | `[a-z][!a-x]*cat*[h][!b]*eyes*` | `my cat has very bright eyes` | `true` | 432 107 | `[a-z][!a-x]*cat*[h][!b]*eyes*` | `my dog has very bright eyes` | `false` | 199 108 | `https://*.google.*` | `https://account.google.com` | `true` | 96 109 | `https://*.google.*` | `https://google.com` | `false` | 66 110 | `{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://yahoo.com` | `true` | 163 111 | `{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://google.com` | `false` | 197 112 | `{https://*gobwas.com,http://exclude.gobwas.com}` | `https://safe.gobwas.com` | `true` | 22 113 | `{https://*gobwas.com,http://exclude.gobwas.com}` | `http://safe.gobwas.com` | `false` | 24 114 | `abc*` | `abcdef` | `true` | 8.15 115 | `abc*` | `af` | `false` | 5.68 116 | `*def` | `abcdef` | `true` | 8.84 117 | `*def` | `af` | `false` | 5.74 118 | `ab*ef` | `abcdef` | `true` | 15.2 119 | `ab*ef` | `af` | `false` | 10.4 120 | 121 | The same things with `regexp` package: 122 | 123 | Pattern | Fixture | Match | Speed (ns/op) 124 | --------|---------|-------|-------------- 125 | `^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my cat has very bright eyes` | `true` | 2553 126 | `^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my dog has very bright eyes` | `false` | 1383 127 | `^https:\/\/.*\.google\..*$` | `https://account.google.com` | `true` | 1205 128 | `^https:\/\/.*\.google\..*$` | `https://google.com` | `false` | 767 129 | `^(https:\/\/.*\.google\..*\|.*yandex\..*\|.*yahoo\..*\|.*mail\.ru)$` | `http://yahoo.com` | `true` | 1435 130 | `^(https:\/\/.*\.google\..*\|.*yandex\..*\|.*yahoo\..*\|.*mail\.ru)$` | `http://google.com` | `false` | 1674 131 | `^(https:\/\/.*gobwas\.com\|http://exclude.gobwas.com)$` | `https://safe.gobwas.com` | `true` | 1039 132 | `^(https:\/\/.*gobwas\.com\|http://exclude.gobwas.com)$` | `http://safe.gobwas.com` | `false` | 272 133 | `^abc.*$` | `abcdef` | `true` | 237 134 | `^abc.*$` | `af` | `false` | 100 135 | `^.*def$` | `abcdef` | `true` | 464 136 | `^.*def$` | `af` | `false` | 265 137 | `^ab.*ef$` | `abcdef` | `true` | 375 138 | `^ab.*ef$` | `af` | `false` | 145 139 | 140 | [godoc-image]: https://godoc.org/github.com/gobwas/glob?status.svg 141 | [godoc-url]: https://godoc.org/github.com/gobwas/glob 142 | [travis-image]: https://travis-ci.org/gobwas/glob.svg?branch=master 143 | [travis-url]: https://travis-ci.org/gobwas/glob 144 | 145 | ## Syntax 146 | 147 | Syntax is inspired by [standard wildcards](http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm), 148 | except that `**` is aka super-asterisk, that do not sensitive for separators. 149 | -------------------------------------------------------------------------------- /syntax/ast/ast.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | type Node struct { 9 | Parent *Node 10 | Children []*Node 11 | Value interface{} 12 | Kind Kind 13 | } 14 | 15 | func NewNode(k Kind, v interface{}, ch ...*Node) *Node { 16 | n := &Node{ 17 | Kind: k, 18 | Value: v, 19 | } 20 | for _, c := range ch { 21 | Insert(n, c) 22 | } 23 | return n 24 | } 25 | 26 | func (a *Node) Equal(b *Node) bool { 27 | if a.Kind != b.Kind { 28 | return false 29 | } 30 | if a.Value != b.Value { 31 | return false 32 | } 33 | if len(a.Children) != len(b.Children) { 34 | return false 35 | } 36 | for i, c := range a.Children { 37 | if !c.Equal(b.Children[i]) { 38 | return false 39 | } 40 | } 41 | return true 42 | } 43 | 44 | func (a *Node) String() string { 45 | var buf bytes.Buffer 46 | buf.WriteString(a.Kind.String()) 47 | if a.Value != nil { 48 | buf.WriteString(" =") 49 | buf.WriteString(fmt.Sprintf("%v", a.Value)) 50 | } 51 | if len(a.Children) > 0 { 52 | buf.WriteString(" [") 53 | for i, c := range a.Children { 54 | if i > 0 { 55 | buf.WriteString(", ") 56 | } 57 | buf.WriteString(c.String()) 58 | } 59 | buf.WriteString("]") 60 | } 61 | return buf.String() 62 | } 63 | 64 | func Insert(parent *Node, children ...*Node) { 65 | parent.Children = append(parent.Children, children...) 66 | for _, ch := range children { 67 | ch.Parent = parent 68 | } 69 | } 70 | 71 | type List struct { 72 | Not bool 73 | Chars string 74 | } 75 | 76 | type Range struct { 77 | Not bool 78 | Lo, Hi rune 79 | } 80 | 81 | type Text struct { 82 | Text string 83 | } 84 | 85 | type Kind int 86 | 87 | const ( 88 | KindNothing Kind = iota 89 | KindPattern 90 | KindList 91 | KindRange 92 | KindText 93 | KindAny 94 | KindSuper 95 | KindSingle 96 | KindAnyOf 97 | ) 98 | 99 | func (k Kind) String() string { 100 | switch k { 101 | case KindNothing: 102 | return "Nothing" 103 | case KindPattern: 104 | return "Pattern" 105 | case KindList: 106 | return "List" 107 | case KindRange: 108 | return "Range" 109 | case KindText: 110 | return "Text" 111 | case KindAny: 112 | return "Any" 113 | case KindSuper: 114 | return "Super" 115 | case KindSingle: 116 | return "Single" 117 | case KindAnyOf: 118 | return "AnyOf" 119 | default: 120 | return "" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /syntax/ast/parser.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/gobwas/glob/syntax/lexer" 7 | "unicode/utf8" 8 | ) 9 | 10 | type Lexer interface { 11 | Next() lexer.Token 12 | } 13 | 14 | type parseFn func(*Node, Lexer) (parseFn, *Node, error) 15 | 16 | func Parse(lexer Lexer) (*Node, error) { 17 | var parser parseFn 18 | 19 | root := NewNode(KindPattern, nil) 20 | 21 | var ( 22 | tree *Node 23 | err error 24 | ) 25 | for parser, tree = parserMain, root; parser != nil; { 26 | parser, tree, err = parser(tree, lexer) 27 | if err != nil { 28 | return nil, err 29 | } 30 | } 31 | 32 | return root, nil 33 | } 34 | 35 | func parserMain(tree *Node, lex Lexer) (parseFn, *Node, error) { 36 | for { 37 | token := lex.Next() 38 | switch token.Type { 39 | case lexer.EOF: 40 | return nil, tree, nil 41 | 42 | case lexer.Error: 43 | return nil, tree, errors.New(token.Raw) 44 | 45 | case lexer.Text: 46 | Insert(tree, NewNode(KindText, Text{token.Raw})) 47 | return parserMain, tree, nil 48 | 49 | case lexer.Any: 50 | Insert(tree, NewNode(KindAny, nil)) 51 | return parserMain, tree, nil 52 | 53 | case lexer.Super: 54 | Insert(tree, NewNode(KindSuper, nil)) 55 | return parserMain, tree, nil 56 | 57 | case lexer.Single: 58 | Insert(tree, NewNode(KindSingle, nil)) 59 | return parserMain, tree, nil 60 | 61 | case lexer.RangeOpen: 62 | return parserRange, tree, nil 63 | 64 | case lexer.TermsOpen: 65 | a := NewNode(KindAnyOf, nil) 66 | Insert(tree, a) 67 | 68 | p := NewNode(KindPattern, nil) 69 | Insert(a, p) 70 | 71 | return parserMain, p, nil 72 | 73 | case lexer.Separator: 74 | p := NewNode(KindPattern, nil) 75 | Insert(tree.Parent, p) 76 | 77 | return parserMain, p, nil 78 | 79 | case lexer.TermsClose: 80 | return parserMain, tree.Parent.Parent, nil 81 | 82 | default: 83 | return nil, tree, fmt.Errorf("unexpected token: %s", token) 84 | } 85 | } 86 | return nil, tree, fmt.Errorf("unknown error") 87 | } 88 | 89 | func parserRange(tree *Node, lex Lexer) (parseFn, *Node, error) { 90 | var ( 91 | not bool 92 | lo rune 93 | hi rune 94 | chars string 95 | ) 96 | for { 97 | token := lex.Next() 98 | switch token.Type { 99 | case lexer.EOF: 100 | return nil, tree, errors.New("unexpected end") 101 | 102 | case lexer.Error: 103 | return nil, tree, errors.New(token.Raw) 104 | 105 | case lexer.Not: 106 | not = true 107 | 108 | case lexer.RangeLo: 109 | r, w := utf8.DecodeRuneInString(token.Raw) 110 | if len(token.Raw) > w { 111 | return nil, tree, fmt.Errorf("unexpected length of lo character") 112 | } 113 | lo = r 114 | 115 | case lexer.RangeBetween: 116 | // 117 | 118 | case lexer.RangeHi: 119 | r, w := utf8.DecodeRuneInString(token.Raw) 120 | if len(token.Raw) > w { 121 | return nil, tree, fmt.Errorf("unexpected length of lo character") 122 | } 123 | 124 | hi = r 125 | 126 | if hi < lo { 127 | return nil, tree, fmt.Errorf("hi character '%s' should be greater than lo '%s'", string(hi), string(lo)) 128 | } 129 | 130 | case lexer.Text: 131 | chars = token.Raw 132 | 133 | case lexer.RangeClose: 134 | isRange := lo != 0 && hi != 0 135 | isChars := chars != "" 136 | 137 | if isChars == isRange { 138 | return nil, tree, fmt.Errorf("could not parse range") 139 | } 140 | 141 | if isRange { 142 | Insert(tree, NewNode(KindRange, Range{ 143 | Lo: lo, 144 | Hi: hi, 145 | Not: not, 146 | })) 147 | } else { 148 | Insert(tree, NewNode(KindList, List{ 149 | Chars: chars, 150 | Not: not, 151 | })) 152 | } 153 | 154 | return parserMain, tree, nil 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /syntax/ast/parser_test.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/gobwas/glob/syntax/lexer" 8 | ) 9 | 10 | type stubLexer struct { 11 | tokens []lexer.Token 12 | pos int 13 | } 14 | 15 | func (s *stubLexer) Next() (ret lexer.Token) { 16 | if s.pos == len(s.tokens) { 17 | return lexer.Token{lexer.EOF, ""} 18 | } 19 | ret = s.tokens[s.pos] 20 | s.pos++ 21 | return 22 | } 23 | 24 | func TestParseString(t *testing.T) { 25 | for id, test := range []struct { 26 | tokens []lexer.Token 27 | tree *Node 28 | }{ 29 | { 30 | //pattern: "abc", 31 | tokens: []lexer.Token{ 32 | {lexer.Text, "abc"}, 33 | {lexer.EOF, ""}, 34 | }, 35 | tree: NewNode(KindPattern, nil, 36 | NewNode(KindText, Text{Text: "abc"}), 37 | ), 38 | }, 39 | { 40 | //pattern: "a*c", 41 | tokens: []lexer.Token{ 42 | {lexer.Text, "a"}, 43 | {lexer.Any, "*"}, 44 | {lexer.Text, "c"}, 45 | {lexer.EOF, ""}, 46 | }, 47 | tree: NewNode(KindPattern, nil, 48 | NewNode(KindText, Text{Text: "a"}), 49 | NewNode(KindAny, nil), 50 | NewNode(KindText, Text{Text: "c"}), 51 | ), 52 | }, 53 | { 54 | //pattern: "a**c", 55 | tokens: []lexer.Token{ 56 | {lexer.Text, "a"}, 57 | {lexer.Super, "**"}, 58 | {lexer.Text, "c"}, 59 | {lexer.EOF, ""}, 60 | }, 61 | tree: NewNode(KindPattern, nil, 62 | NewNode(KindText, Text{Text: "a"}), 63 | NewNode(KindSuper, nil), 64 | NewNode(KindText, Text{Text: "c"}), 65 | ), 66 | }, 67 | { 68 | //pattern: "a?c", 69 | tokens: []lexer.Token{ 70 | {lexer.Text, "a"}, 71 | {lexer.Single, "?"}, 72 | {lexer.Text, "c"}, 73 | {lexer.EOF, ""}, 74 | }, 75 | tree: NewNode(KindPattern, nil, 76 | NewNode(KindText, Text{Text: "a"}), 77 | NewNode(KindSingle, nil), 78 | NewNode(KindText, Text{Text: "c"}), 79 | ), 80 | }, 81 | { 82 | //pattern: "[!a-z]", 83 | tokens: []lexer.Token{ 84 | {lexer.RangeOpen, "["}, 85 | {lexer.Not, "!"}, 86 | {lexer.RangeLo, "a"}, 87 | {lexer.RangeBetween, "-"}, 88 | {lexer.RangeHi, "z"}, 89 | {lexer.RangeClose, "]"}, 90 | {lexer.EOF, ""}, 91 | }, 92 | tree: NewNode(KindPattern, nil, 93 | NewNode(KindRange, Range{Lo: 'a', Hi: 'z', Not: true}), 94 | ), 95 | }, 96 | { 97 | //pattern: "[az]", 98 | tokens: []lexer.Token{ 99 | {lexer.RangeOpen, "["}, 100 | {lexer.Text, "az"}, 101 | {lexer.RangeClose, "]"}, 102 | {lexer.EOF, ""}, 103 | }, 104 | tree: NewNode(KindPattern, nil, 105 | NewNode(KindList, List{Chars: "az"}), 106 | ), 107 | }, 108 | { 109 | //pattern: "{a,z}", 110 | tokens: []lexer.Token{ 111 | {lexer.TermsOpen, "{"}, 112 | {lexer.Text, "a"}, 113 | {lexer.Separator, ","}, 114 | {lexer.Text, "z"}, 115 | {lexer.TermsClose, "}"}, 116 | {lexer.EOF, ""}, 117 | }, 118 | tree: NewNode(KindPattern, nil, 119 | NewNode(KindAnyOf, nil, 120 | NewNode(KindPattern, nil, 121 | NewNode(KindText, Text{Text: "a"}), 122 | ), 123 | NewNode(KindPattern, nil, 124 | NewNode(KindText, Text{Text: "z"}), 125 | ), 126 | ), 127 | ), 128 | }, 129 | { 130 | //pattern: "/{z,ab}*", 131 | tokens: []lexer.Token{ 132 | {lexer.Text, "/"}, 133 | {lexer.TermsOpen, "{"}, 134 | {lexer.Text, "z"}, 135 | {lexer.Separator, ","}, 136 | {lexer.Text, "ab"}, 137 | {lexer.TermsClose, "}"}, 138 | {lexer.Any, "*"}, 139 | {lexer.EOF, ""}, 140 | }, 141 | tree: NewNode(KindPattern, nil, 142 | NewNode(KindText, Text{Text: "/"}), 143 | NewNode(KindAnyOf, nil, 144 | NewNode(KindPattern, nil, 145 | NewNode(KindText, Text{Text: "z"}), 146 | ), 147 | NewNode(KindPattern, nil, 148 | NewNode(KindText, Text{Text: "ab"}), 149 | ), 150 | ), 151 | NewNode(KindAny, nil), 152 | ), 153 | }, 154 | { 155 | //pattern: "{a,{x,y},?,[a-z],[!qwe]}", 156 | tokens: []lexer.Token{ 157 | {lexer.TermsOpen, "{"}, 158 | {lexer.Text, "a"}, 159 | {lexer.Separator, ","}, 160 | {lexer.TermsOpen, "{"}, 161 | {lexer.Text, "x"}, 162 | {lexer.Separator, ","}, 163 | {lexer.Text, "y"}, 164 | {lexer.TermsClose, "}"}, 165 | {lexer.Separator, ","}, 166 | {lexer.Single, "?"}, 167 | {lexer.Separator, ","}, 168 | {lexer.RangeOpen, "["}, 169 | {lexer.RangeLo, "a"}, 170 | {lexer.RangeBetween, "-"}, 171 | {lexer.RangeHi, "z"}, 172 | {lexer.RangeClose, "]"}, 173 | {lexer.Separator, ","}, 174 | {lexer.RangeOpen, "["}, 175 | {lexer.Not, "!"}, 176 | {lexer.Text, "qwe"}, 177 | {lexer.RangeClose, "]"}, 178 | {lexer.TermsClose, "}"}, 179 | {lexer.EOF, ""}, 180 | }, 181 | tree: NewNode(KindPattern, nil, 182 | NewNode(KindAnyOf, nil, 183 | NewNode(KindPattern, nil, 184 | NewNode(KindText, Text{Text: "a"}), 185 | ), 186 | NewNode(KindPattern, nil, 187 | NewNode(KindAnyOf, nil, 188 | NewNode(KindPattern, nil, 189 | NewNode(KindText, Text{Text: "x"}), 190 | ), 191 | NewNode(KindPattern, nil, 192 | NewNode(KindText, Text{Text: "y"}), 193 | ), 194 | ), 195 | ), 196 | NewNode(KindPattern, nil, 197 | NewNode(KindSingle, nil), 198 | ), 199 | NewNode(KindPattern, nil, 200 | NewNode(KindRange, Range{Lo: 'a', Hi: 'z', Not: false}), 201 | ), 202 | NewNode(KindPattern, nil, 203 | NewNode(KindList, List{Chars: "qwe", Not: true}), 204 | ), 205 | ), 206 | ), 207 | }, 208 | } { 209 | lexer := &stubLexer{tokens: test.tokens} 210 | result, err := Parse(lexer) 211 | if err != nil { 212 | t.Errorf("[%d] unexpected error: %s", id, err) 213 | } 214 | if !reflect.DeepEqual(test.tree, result) { 215 | t.Errorf("[%d] Parse():\nact:\t%s\nexp:\t%s\n", id, result, test.tree) 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /syntax/lexer/lexer.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/gobwas/glob/util/runes" 7 | "unicode/utf8" 8 | ) 9 | 10 | const ( 11 | char_any = '*' 12 | char_comma = ',' 13 | char_single = '?' 14 | char_escape = '\\' 15 | char_range_open = '[' 16 | char_range_close = ']' 17 | char_terms_open = '{' 18 | char_terms_close = '}' 19 | char_range_not = '!' 20 | char_range_between = '-' 21 | ) 22 | 23 | var specials = []byte{ 24 | char_any, 25 | char_single, 26 | char_escape, 27 | char_range_open, 28 | char_range_close, 29 | char_terms_open, 30 | char_terms_close, 31 | } 32 | 33 | func Special(c byte) bool { 34 | return bytes.IndexByte(specials, c) != -1 35 | } 36 | 37 | type tokens []Token 38 | 39 | func (i *tokens) shift() (ret Token) { 40 | ret = (*i)[0] 41 | copy(*i, (*i)[1:]) 42 | *i = (*i)[:len(*i)-1] 43 | return 44 | } 45 | 46 | func (i *tokens) push(v Token) { 47 | *i = append(*i, v) 48 | } 49 | 50 | func (i *tokens) empty() bool { 51 | return len(*i) == 0 52 | } 53 | 54 | var eof rune = 0 55 | 56 | type lexer struct { 57 | data string 58 | pos int 59 | err error 60 | 61 | tokens tokens 62 | termsLevel int 63 | 64 | lastRune rune 65 | lastRuneSize int 66 | hasRune bool 67 | } 68 | 69 | func NewLexer(source string) *lexer { 70 | l := &lexer{ 71 | data: source, 72 | tokens: tokens(make([]Token, 0, 4)), 73 | } 74 | return l 75 | } 76 | 77 | func (l *lexer) Next() Token { 78 | if l.err != nil { 79 | return Token{Error, l.err.Error()} 80 | } 81 | if !l.tokens.empty() { 82 | return l.tokens.shift() 83 | } 84 | 85 | l.fetchItem() 86 | return l.Next() 87 | } 88 | 89 | func (l *lexer) peek() (r rune, w int) { 90 | if l.pos == len(l.data) { 91 | return eof, 0 92 | } 93 | 94 | r, w = utf8.DecodeRuneInString(l.data[l.pos:]) 95 | if r == utf8.RuneError { 96 | l.errorf("could not read rune") 97 | r = eof 98 | w = 0 99 | } 100 | 101 | return 102 | } 103 | 104 | func (l *lexer) read() rune { 105 | if l.hasRune { 106 | l.hasRune = false 107 | l.seek(l.lastRuneSize) 108 | return l.lastRune 109 | } 110 | 111 | r, s := l.peek() 112 | l.seek(s) 113 | 114 | l.lastRune = r 115 | l.lastRuneSize = s 116 | 117 | return r 118 | } 119 | 120 | func (l *lexer) seek(w int) { 121 | l.pos += w 122 | } 123 | 124 | func (l *lexer) unread() { 125 | if l.hasRune { 126 | l.errorf("could not unread rune") 127 | return 128 | } 129 | l.seek(-l.lastRuneSize) 130 | l.hasRune = true 131 | } 132 | 133 | func (l *lexer) errorf(f string, v ...interface{}) { 134 | l.err = fmt.Errorf(f, v...) 135 | } 136 | 137 | func (l *lexer) inTerms() bool { 138 | return l.termsLevel > 0 139 | } 140 | 141 | func (l *lexer) termsEnter() { 142 | l.termsLevel++ 143 | } 144 | 145 | func (l *lexer) termsLeave() { 146 | l.termsLevel-- 147 | } 148 | 149 | var inTextBreakers = []rune{char_single, char_any, char_range_open, char_terms_open} 150 | var inTermsBreakers = append(inTextBreakers, char_terms_close, char_comma) 151 | 152 | func (l *lexer) fetchItem() { 153 | r := l.read() 154 | switch { 155 | case r == eof: 156 | l.tokens.push(Token{EOF, ""}) 157 | 158 | case r == char_terms_open: 159 | l.termsEnter() 160 | l.tokens.push(Token{TermsOpen, string(r)}) 161 | 162 | case r == char_comma && l.inTerms(): 163 | l.tokens.push(Token{Separator, string(r)}) 164 | 165 | case r == char_terms_close && l.inTerms(): 166 | l.tokens.push(Token{TermsClose, string(r)}) 167 | l.termsLeave() 168 | 169 | case r == char_range_open: 170 | l.tokens.push(Token{RangeOpen, string(r)}) 171 | l.fetchRange() 172 | 173 | case r == char_single: 174 | l.tokens.push(Token{Single, string(r)}) 175 | 176 | case r == char_any: 177 | if l.read() == char_any { 178 | l.tokens.push(Token{Super, string(r) + string(r)}) 179 | } else { 180 | l.unread() 181 | l.tokens.push(Token{Any, string(r)}) 182 | } 183 | 184 | default: 185 | l.unread() 186 | 187 | var breakers []rune 188 | if l.inTerms() { 189 | breakers = inTermsBreakers 190 | } else { 191 | breakers = inTextBreakers 192 | } 193 | l.fetchText(breakers) 194 | } 195 | } 196 | 197 | func (l *lexer) fetchRange() { 198 | var wantHi bool 199 | var wantClose bool 200 | var seenNot bool 201 | for { 202 | r := l.read() 203 | if r == eof { 204 | l.errorf("unexpected end of input") 205 | return 206 | } 207 | 208 | if wantClose { 209 | if r != char_range_close { 210 | l.errorf("expected close range character") 211 | } else { 212 | l.tokens.push(Token{RangeClose, string(r)}) 213 | } 214 | return 215 | } 216 | 217 | if wantHi { 218 | l.tokens.push(Token{RangeHi, string(r)}) 219 | wantClose = true 220 | continue 221 | } 222 | 223 | if !seenNot && r == char_range_not { 224 | l.tokens.push(Token{Not, string(r)}) 225 | seenNot = true 226 | continue 227 | } 228 | 229 | if n, w := l.peek(); n == char_range_between { 230 | l.seek(w) 231 | l.tokens.push(Token{RangeLo, string(r)}) 232 | l.tokens.push(Token{RangeBetween, string(n)}) 233 | wantHi = true 234 | continue 235 | } 236 | 237 | l.unread() // unread first peek and fetch as text 238 | l.fetchText([]rune{char_range_close}) 239 | wantClose = true 240 | } 241 | } 242 | 243 | func (l *lexer) fetchText(breakers []rune) { 244 | var data []rune 245 | var escaped bool 246 | 247 | reading: 248 | for { 249 | r := l.read() 250 | if r == eof { 251 | break 252 | } 253 | 254 | if !escaped { 255 | if r == char_escape { 256 | escaped = true 257 | continue 258 | } 259 | 260 | if runes.IndexRune(breakers, r) != -1 { 261 | l.unread() 262 | break reading 263 | } 264 | } 265 | 266 | escaped = false 267 | data = append(data, r) 268 | } 269 | 270 | if len(data) > 0 { 271 | l.tokens.push(Token{Text, string(data)}) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /syntax/lexer/lexer_test.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestLexGood(t *testing.T) { 8 | for id, test := range []struct { 9 | pattern string 10 | items []Token 11 | }{ 12 | { 13 | pattern: "", 14 | items: []Token{ 15 | {EOF, ""}, 16 | }, 17 | }, 18 | { 19 | pattern: "hello", 20 | items: []Token{ 21 | {Text, "hello"}, 22 | {EOF, ""}, 23 | }, 24 | }, 25 | { 26 | pattern: "/{rate,[0-9]]}*", 27 | items: []Token{ 28 | {Text, "/"}, 29 | {TermsOpen, "{"}, 30 | {Text, "rate"}, 31 | {Separator, ","}, 32 | {RangeOpen, "["}, 33 | {RangeLo, "0"}, 34 | {RangeBetween, "-"}, 35 | {RangeHi, "9"}, 36 | {RangeClose, "]"}, 37 | {Text, "]"}, 38 | {TermsClose, "}"}, 39 | {Any, "*"}, 40 | {EOF, ""}, 41 | }, 42 | }, 43 | { 44 | pattern: "hello,world", 45 | items: []Token{ 46 | {Text, "hello,world"}, 47 | {EOF, ""}, 48 | }, 49 | }, 50 | { 51 | pattern: "hello\\,world", 52 | items: []Token{ 53 | {Text, "hello,world"}, 54 | {EOF, ""}, 55 | }, 56 | }, 57 | { 58 | pattern: "hello\\{world", 59 | items: []Token{ 60 | {Text, "hello{world"}, 61 | {EOF, ""}, 62 | }, 63 | }, 64 | { 65 | pattern: "hello?", 66 | items: []Token{ 67 | {Text, "hello"}, 68 | {Single, "?"}, 69 | {EOF, ""}, 70 | }, 71 | }, 72 | { 73 | pattern: "hellof*", 74 | items: []Token{ 75 | {Text, "hellof"}, 76 | {Any, "*"}, 77 | {EOF, ""}, 78 | }, 79 | }, 80 | { 81 | pattern: "hello**", 82 | items: []Token{ 83 | {Text, "hello"}, 84 | {Super, "**"}, 85 | {EOF, ""}, 86 | }, 87 | }, 88 | { 89 | pattern: "[日-語]", 90 | items: []Token{ 91 | {RangeOpen, "["}, 92 | {RangeLo, "日"}, 93 | {RangeBetween, "-"}, 94 | {RangeHi, "語"}, 95 | {RangeClose, "]"}, 96 | {EOF, ""}, 97 | }, 98 | }, 99 | { 100 | pattern: "[!日-語]", 101 | items: []Token{ 102 | {RangeOpen, "["}, 103 | {Not, "!"}, 104 | {RangeLo, "日"}, 105 | {RangeBetween, "-"}, 106 | {RangeHi, "語"}, 107 | {RangeClose, "]"}, 108 | {EOF, ""}, 109 | }, 110 | }, 111 | { 112 | pattern: "[日本語]", 113 | items: []Token{ 114 | {RangeOpen, "["}, 115 | {Text, "日本語"}, 116 | {RangeClose, "]"}, 117 | {EOF, ""}, 118 | }, 119 | }, 120 | { 121 | pattern: "[!日本語]", 122 | items: []Token{ 123 | {RangeOpen, "["}, 124 | {Not, "!"}, 125 | {Text, "日本語"}, 126 | {RangeClose, "]"}, 127 | {EOF, ""}, 128 | }, 129 | }, 130 | { 131 | pattern: "{a,b}", 132 | items: []Token{ 133 | {TermsOpen, "{"}, 134 | {Text, "a"}, 135 | {Separator, ","}, 136 | {Text, "b"}, 137 | {TermsClose, "}"}, 138 | {EOF, ""}, 139 | }, 140 | }, 141 | { 142 | pattern: "/{z,ab}*", 143 | items: []Token{ 144 | {Text, "/"}, 145 | {TermsOpen, "{"}, 146 | {Text, "z"}, 147 | {Separator, ","}, 148 | {Text, "ab"}, 149 | {TermsClose, "}"}, 150 | {Any, "*"}, 151 | {EOF, ""}, 152 | }, 153 | }, 154 | { 155 | pattern: "{[!日-語],*,?,{a,b,\\c}}", 156 | items: []Token{ 157 | {TermsOpen, "{"}, 158 | {RangeOpen, "["}, 159 | {Not, "!"}, 160 | {RangeLo, "日"}, 161 | {RangeBetween, "-"}, 162 | {RangeHi, "語"}, 163 | {RangeClose, "]"}, 164 | {Separator, ","}, 165 | {Any, "*"}, 166 | {Separator, ","}, 167 | {Single, "?"}, 168 | {Separator, ","}, 169 | {TermsOpen, "{"}, 170 | {Text, "a"}, 171 | {Separator, ","}, 172 | {Text, "b"}, 173 | {Separator, ","}, 174 | {Text, "c"}, 175 | {TermsClose, "}"}, 176 | {TermsClose, "}"}, 177 | {EOF, ""}, 178 | }, 179 | }, 180 | } { 181 | lexer := NewLexer(test.pattern) 182 | for i, exp := range test.items { 183 | act := lexer.Next() 184 | if act.Type != exp.Type { 185 | t.Errorf("#%d %q: wrong %d-th item type: exp: %q; act: %q\n\t(%s vs %s)", id, test.pattern, i, exp.Type, act.Type, exp, act) 186 | } 187 | if act.Raw != exp.Raw { 188 | t.Errorf("#%d %q: wrong %d-th item contents: exp: %q; act: %q\n\t(%s vs %s)", id, test.pattern, i, exp.Raw, act.Raw, exp, act) 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /syntax/lexer/token.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import "fmt" 4 | 5 | type TokenType int 6 | 7 | const ( 8 | EOF TokenType = iota 9 | Error 10 | Text 11 | Char 12 | Any 13 | Super 14 | Single 15 | Not 16 | Separator 17 | RangeOpen 18 | RangeClose 19 | RangeLo 20 | RangeHi 21 | RangeBetween 22 | TermsOpen 23 | TermsClose 24 | ) 25 | 26 | func (tt TokenType) String() string { 27 | switch tt { 28 | case EOF: 29 | return "eof" 30 | 31 | case Error: 32 | return "error" 33 | 34 | case Text: 35 | return "text" 36 | 37 | case Char: 38 | return "char" 39 | 40 | case Any: 41 | return "any" 42 | 43 | case Super: 44 | return "super" 45 | 46 | case Single: 47 | return "single" 48 | 49 | case Not: 50 | return "not" 51 | 52 | case Separator: 53 | return "separator" 54 | 55 | case RangeOpen: 56 | return "range_open" 57 | 58 | case RangeClose: 59 | return "range_close" 60 | 61 | case RangeLo: 62 | return "range_lo" 63 | 64 | case RangeHi: 65 | return "range_hi" 66 | 67 | case RangeBetween: 68 | return "range_between" 69 | 70 | case TermsOpen: 71 | return "terms_open" 72 | 73 | case TermsClose: 74 | return "terms_close" 75 | 76 | default: 77 | return "undef" 78 | } 79 | } 80 | 81 | type Token struct { 82 | Type TokenType 83 | Raw string 84 | } 85 | 86 | func (t Token) String() string { 87 | return fmt.Sprintf("%v<%q>", t.Type, t.Raw) 88 | } 89 | -------------------------------------------------------------------------------- /syntax/syntax.go: -------------------------------------------------------------------------------- 1 | package syntax 2 | 3 | import ( 4 | "github.com/gobwas/glob/syntax/ast" 5 | "github.com/gobwas/glob/syntax/lexer" 6 | ) 7 | 8 | func Parse(s string) (*ast.Node, error) { 9 | return ast.Parse(lexer.NewLexer(s)) 10 | } 11 | 12 | func Special(b byte) bool { 13 | return lexer.Special(b) 14 | } 15 | -------------------------------------------------------------------------------- /util/runes/runes.go: -------------------------------------------------------------------------------- 1 | package runes 2 | 3 | func Index(s, needle []rune) int { 4 | ls, ln := len(s), len(needle) 5 | 6 | switch { 7 | case ln == 0: 8 | return 0 9 | case ln == 1: 10 | return IndexRune(s, needle[0]) 11 | case ln == ls: 12 | if Equal(s, needle) { 13 | return 0 14 | } 15 | return -1 16 | case ln > ls: 17 | return -1 18 | } 19 | 20 | head: 21 | for i := 0; i < ls && ls-i >= ln; i++ { 22 | for y := 0; y < ln; y++ { 23 | if s[i+y] != needle[y] { 24 | continue head 25 | } 26 | } 27 | 28 | return i 29 | } 30 | 31 | return -1 32 | } 33 | 34 | func LastIndex(s, needle []rune) int { 35 | ls, ln := len(s), len(needle) 36 | 37 | switch { 38 | case ln == 0: 39 | if ls == 0 { 40 | return 0 41 | } 42 | return ls 43 | case ln == 1: 44 | return IndexLastRune(s, needle[0]) 45 | case ln == ls: 46 | if Equal(s, needle) { 47 | return 0 48 | } 49 | return -1 50 | case ln > ls: 51 | return -1 52 | } 53 | 54 | head: 55 | for i := ls - 1; i >= 0 && i >= ln; i-- { 56 | for y := ln - 1; y >= 0; y-- { 57 | if s[i-(ln-y-1)] != needle[y] { 58 | continue head 59 | } 60 | } 61 | 62 | return i - ln + 1 63 | } 64 | 65 | return -1 66 | } 67 | 68 | // IndexAny returns the index of the first instance of any Unicode code point 69 | // from chars in s, or -1 if no Unicode code point from chars is present in s. 70 | func IndexAny(s, chars []rune) int { 71 | if len(chars) > 0 { 72 | for i, c := range s { 73 | for _, m := range chars { 74 | if c == m { 75 | return i 76 | } 77 | } 78 | } 79 | } 80 | return -1 81 | } 82 | 83 | func Contains(s, needle []rune) bool { 84 | return Index(s, needle) >= 0 85 | } 86 | 87 | func Max(s []rune) (max rune) { 88 | for _, r := range s { 89 | if r > max { 90 | max = r 91 | } 92 | } 93 | 94 | return 95 | } 96 | 97 | func Min(s []rune) rune { 98 | min := rune(-1) 99 | for _, r := range s { 100 | if min == -1 { 101 | min = r 102 | continue 103 | } 104 | 105 | if r < min { 106 | min = r 107 | } 108 | } 109 | 110 | return min 111 | } 112 | 113 | func IndexRune(s []rune, r rune) int { 114 | for i, c := range s { 115 | if c == r { 116 | return i 117 | } 118 | } 119 | return -1 120 | } 121 | 122 | func IndexLastRune(s []rune, r rune) int { 123 | for i := len(s) - 1; i >= 0; i-- { 124 | if s[i] == r { 125 | return i 126 | } 127 | } 128 | 129 | return -1 130 | } 131 | 132 | func Equal(a, b []rune) bool { 133 | if len(a) == len(b) { 134 | for i := 0; i < len(a); i++ { 135 | if a[i] != b[i] { 136 | return false 137 | } 138 | } 139 | 140 | return true 141 | } 142 | 143 | return false 144 | } 145 | 146 | // HasPrefix tests whether the string s begins with prefix. 147 | func HasPrefix(s, prefix []rune) bool { 148 | return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix) 149 | } 150 | 151 | // HasSuffix tests whether the string s ends with suffix. 152 | func HasSuffix(s, suffix []rune) bool { 153 | return len(s) >= len(suffix) && Equal(s[len(s)-len(suffix):], suffix) 154 | } 155 | -------------------------------------------------------------------------------- /util/runes/runes_test.go: -------------------------------------------------------------------------------- 1 | package runes 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | type indexTest struct { 9 | s []rune 10 | sep []rune 11 | out int 12 | } 13 | 14 | type equalTest struct { 15 | a []rune 16 | b []rune 17 | out bool 18 | } 19 | 20 | func newIndexTest(s, sep string, out int) indexTest { 21 | return indexTest{[]rune(s), []rune(sep), out} 22 | } 23 | func newEqualTest(s, sep string, out bool) equalTest { 24 | return equalTest{[]rune(s), []rune(sep), out} 25 | } 26 | 27 | var dots = "1....2....3....4" 28 | 29 | var indexTests = []indexTest{ 30 | newIndexTest("", "", 0), 31 | newIndexTest("", "a", -1), 32 | newIndexTest("", "foo", -1), 33 | newIndexTest("fo", "foo", -1), 34 | newIndexTest("foo", "foo", 0), 35 | newIndexTest("oofofoofooo", "f", 2), 36 | newIndexTest("oofofoofooo", "foo", 4), 37 | newIndexTest("barfoobarfoo", "foo", 3), 38 | newIndexTest("foo", "", 0), 39 | newIndexTest("foo", "o", 1), 40 | newIndexTest("abcABCabc", "A", 3), 41 | // cases with one byte strings - test special case in Index() 42 | newIndexTest("", "a", -1), 43 | newIndexTest("x", "a", -1), 44 | newIndexTest("x", "x", 0), 45 | newIndexTest("abc", "a", 0), 46 | newIndexTest("abc", "b", 1), 47 | newIndexTest("abc", "c", 2), 48 | newIndexTest("abc", "x", -1), 49 | } 50 | 51 | var lastIndexTests = []indexTest{ 52 | newIndexTest("", "", 0), 53 | newIndexTest("", "a", -1), 54 | newIndexTest("", "foo", -1), 55 | newIndexTest("fo", "foo", -1), 56 | newIndexTest("foo", "foo", 0), 57 | newIndexTest("foo", "f", 0), 58 | newIndexTest("oofofoofooo", "f", 7), 59 | newIndexTest("oofofoofooo", "foo", 7), 60 | newIndexTest("barfoobarfoo", "foo", 9), 61 | newIndexTest("foo", "", 3), 62 | newIndexTest("foo", "o", 2), 63 | newIndexTest("abcABCabc", "A", 3), 64 | newIndexTest("abcABCabc", "a", 6), 65 | } 66 | 67 | var indexAnyTests = []indexTest{ 68 | newIndexTest("", "", -1), 69 | newIndexTest("", "a", -1), 70 | newIndexTest("", "abc", -1), 71 | newIndexTest("a", "", -1), 72 | newIndexTest("a", "a", 0), 73 | newIndexTest("aaa", "a", 0), 74 | newIndexTest("abc", "xyz", -1), 75 | newIndexTest("abc", "xcz", 2), 76 | newIndexTest("a☺b☻c☹d", "uvw☻xyz", 3), 77 | newIndexTest("aRegExp*", ".(|)*+?^$[]", 7), 78 | newIndexTest(dots+dots+dots, " ", -1), 79 | } 80 | 81 | // Execute f on each test case. funcName should be the name of f; it's used 82 | // in failure reports. 83 | func runIndexTests(t *testing.T, f func(s, sep []rune) int, funcName string, testCases []indexTest) { 84 | for _, test := range testCases { 85 | actual := f(test.s, test.sep) 86 | if actual != test.out { 87 | t.Errorf("%s(%q,%q) = %v; want %v", funcName, test.s, test.sep, actual, test.out) 88 | } 89 | } 90 | } 91 | 92 | func TestIndex(t *testing.T) { runIndexTests(t, Index, "Index", indexTests) } 93 | func TestLastIndex(t *testing.T) { runIndexTests(t, LastIndex, "LastIndex", lastIndexTests) } 94 | func TestIndexAny(t *testing.T) { runIndexTests(t, IndexAny, "IndexAny", indexAnyTests) } 95 | 96 | var equalTests = []equalTest{ 97 | newEqualTest("a", "a", true), 98 | newEqualTest("a", "b", false), 99 | newEqualTest("a☺b☻c☹d", "uvw☻xyz", false), 100 | newEqualTest("a☺b☻c☹d", "a☺b☻c☹d", true), 101 | } 102 | 103 | func TestEqual(t *testing.T) { 104 | for _, test := range equalTests { 105 | actual := Equal(test.a, test.b) 106 | if actual != test.out { 107 | t.Errorf("Equal(%q,%q) = %v; want %v", test.a, test.b, actual, test.out) 108 | } 109 | } 110 | } 111 | 112 | func BenchmarkLastIndexRunes(b *testing.B) { 113 | r := []rune("abcdef") 114 | n := []rune("cd") 115 | 116 | for i := 0; i < b.N; i++ { 117 | LastIndex(r, n) 118 | } 119 | } 120 | func BenchmarkLastIndexStrings(b *testing.B) { 121 | r := "abcdef" 122 | n := "cd" 123 | 124 | for i := 0; i < b.N; i++ { 125 | strings.LastIndex(r, n) 126 | } 127 | } 128 | 129 | func BenchmarkIndexAnyRunes(b *testing.B) { 130 | s := []rune("...b...") 131 | c := []rune("abc") 132 | 133 | for i := 0; i < b.N; i++ { 134 | IndexAny(s, c) 135 | } 136 | } 137 | func BenchmarkIndexAnyStrings(b *testing.B) { 138 | s := "...b..." 139 | c := "abc" 140 | 141 | for i := 0; i < b.N; i++ { 142 | strings.IndexAny(s, c) 143 | } 144 | } 145 | 146 | func BenchmarkIndexRuneRunes(b *testing.B) { 147 | s := []rune("...b...") 148 | r := 'b' 149 | 150 | for i := 0; i < b.N; i++ { 151 | IndexRune(s, r) 152 | } 153 | } 154 | func BenchmarkIndexRuneStrings(b *testing.B) { 155 | s := "...b..." 156 | r := 'b' 157 | 158 | for i := 0; i < b.N; i++ { 159 | strings.IndexRune(s, r) 160 | } 161 | } 162 | 163 | func BenchmarkIndexRunes(b *testing.B) { 164 | r := []rune("abcdef") 165 | n := []rune("cd") 166 | 167 | for i := 0; i < b.N; i++ { 168 | Index(r, n) 169 | } 170 | } 171 | func BenchmarkIndexStrings(b *testing.B) { 172 | r := "abcdef" 173 | n := "cd" 174 | 175 | for i := 0; i < b.N; i++ { 176 | strings.Index(r, n) 177 | } 178 | } 179 | 180 | func BenchmarkEqualRunes(b *testing.B) { 181 | x := []rune("abc") 182 | y := []rune("abc") 183 | 184 | for i := 0; i < b.N; i++ { 185 | if Equal(x, y) { 186 | continue 187 | } 188 | } 189 | } 190 | 191 | func BenchmarkEqualStrings(b *testing.B) { 192 | x := "abc" 193 | y := "abc" 194 | 195 | for i := 0; i < b.N; i++ { 196 | if x == y { 197 | continue 198 | } 199 | } 200 | } 201 | 202 | func BenchmarkNotEqualRunes(b *testing.B) { 203 | x := []rune("abc") 204 | y := []rune("abcd") 205 | 206 | for i := 0; i < b.N; i++ { 207 | if Equal(x, y) { 208 | continue 209 | } 210 | } 211 | } 212 | 213 | func BenchmarkNotEqualStrings(b *testing.B) { 214 | x := "abc" 215 | y := "abcd" 216 | 217 | for i := 0; i < b.N; i++ { 218 | if x == y { 219 | continue 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /util/strings/strings.go: -------------------------------------------------------------------------------- 1 | package strings 2 | 3 | import ( 4 | "strings" 5 | "unicode/utf8" 6 | ) 7 | 8 | func IndexAnyRunes(s string, rs []rune) int { 9 | for _, r := range rs { 10 | if i := strings.IndexRune(s, r); i != -1 { 11 | return i 12 | } 13 | } 14 | 15 | return -1 16 | } 17 | 18 | func LastIndexAnyRunes(s string, rs []rune) int { 19 | for _, r := range rs { 20 | i := -1 21 | if 0 <= r && r < utf8.RuneSelf { 22 | i = strings.LastIndexByte(s, byte(r)) 23 | } else { 24 | sub := s 25 | for len(sub) > 0 { 26 | j := strings.IndexRune(s, r) 27 | if j == -1 { 28 | break 29 | } 30 | i = j 31 | sub = sub[i+1:] 32 | } 33 | } 34 | if i != -1 { 35 | return i 36 | } 37 | } 38 | return -1 39 | } 40 | --------------------------------------------------------------------------------