mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
perf: Replaced fd with custom internals.
This commit is contained in:
203
findr/glob.odin
Normal file
203
findr/glob.odin
Normal file
@@ -0,0 +1,203 @@
|
||||
package findr
|
||||
|
||||
Range :: struct {
|
||||
lo: u8,
|
||||
hi: u8,
|
||||
}
|
||||
|
||||
Class_Data :: struct {
|
||||
negated: bool,
|
||||
ranges: [dynamic]Range,
|
||||
}
|
||||
|
||||
Token_Kind :: enum u8 { Char, Star, Globstar, Question, Class }
|
||||
|
||||
Token :: struct {
|
||||
kind: Token_Kind,
|
||||
byte: u8,
|
||||
class_idx: u16,
|
||||
}
|
||||
|
||||
GlobPattern :: struct {
|
||||
tokens: [dynamic]Token,
|
||||
classes: [dynamic]Class_Data,
|
||||
anchored: bool,
|
||||
}
|
||||
|
||||
glob_compile :: proc(pattern: string, anchored: bool) -> GlobPattern {
|
||||
gp: GlobPattern
|
||||
gp.tokens = make([dynamic]Token)
|
||||
gp.classes = make([dynamic]Class_Data)
|
||||
gp.anchored = anchored
|
||||
|
||||
i := 0
|
||||
for i < len(pattern) {
|
||||
c := pattern[i]
|
||||
|
||||
if c == '*' {
|
||||
if i + 1 < len(pattern) && pattern[i + 1] == '*' {
|
||||
prev_slash := i == 0 || pattern[i - 1] == '/'
|
||||
at_end := i + 2 >= len(pattern)
|
||||
next_slash := !at_end && pattern[i + 2] == '/'
|
||||
|
||||
if prev_slash && (next_slash || at_end) {
|
||||
append(&gp.tokens, Token{kind = .Globstar})
|
||||
if next_slash {
|
||||
i += 3
|
||||
} else {
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
append(&gp.tokens, Token{kind = .Star})
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
append(&gp.tokens, Token{kind = .Star})
|
||||
i += 1
|
||||
}
|
||||
} else if c == '?' {
|
||||
append(&gp.tokens, Token{kind = .Question})
|
||||
i += 1
|
||||
} else if c == '[' {
|
||||
i += 1
|
||||
negated := false
|
||||
if i < len(pattern) && pattern[i] == '!' {
|
||||
negated = true
|
||||
i += 1
|
||||
}
|
||||
|
||||
ranges := make([dynamic]Range)
|
||||
|
||||
if i < len(pattern) && pattern[i] == ']' {
|
||||
append(&ranges, Range{lo = ']', hi = ']'})
|
||||
i += 1
|
||||
}
|
||||
|
||||
for i < len(pattern) && pattern[i] != ']' {
|
||||
if i + 2 < len(pattern) && pattern[i + 1] == '-' && pattern[i + 2] != ']' {
|
||||
append(&ranges, Range{lo = pattern[i], hi = pattern[i + 2]})
|
||||
i += 3
|
||||
} else {
|
||||
append(&ranges, Range{lo = pattern[i], hi = pattern[i]})
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
if i < len(pattern) {
|
||||
i += 1
|
||||
}
|
||||
|
||||
class_idx := u16(len(gp.classes))
|
||||
append(&gp.classes, Class_Data{negated = negated, ranges = ranges})
|
||||
append(&gp.tokens, Token{kind = .Class, class_idx = class_idx})
|
||||
} else if c == '\\' {
|
||||
i += 1
|
||||
if i < len(pattern) {
|
||||
append(&gp.tokens, Token{kind = .Char, byte = pattern[i]})
|
||||
i += 1
|
||||
}
|
||||
} else {
|
||||
append(&gp.tokens, Token{kind = .Char, byte = c})
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
return gp
|
||||
}
|
||||
|
||||
match_tokens :: proc(tokens: []Token, classes: []Class_Data, ti: int, path: string, pi: int) -> bool {
|
||||
if ti >= len(tokens) {
|
||||
return pi == len(path)
|
||||
}
|
||||
|
||||
tok := tokens[ti]
|
||||
switch tok.kind {
|
||||
case .Char:
|
||||
if pi < len(path) && path[pi] == tok.byte {
|
||||
return match_tokens(tokens, classes, ti + 1, path, pi + 1)
|
||||
}
|
||||
return false
|
||||
|
||||
case .Question:
|
||||
if pi < len(path) && path[pi] != '/' {
|
||||
return match_tokens(tokens, classes, ti + 1, path, pi + 1)
|
||||
}
|
||||
return false
|
||||
|
||||
case .Star:
|
||||
max_end := pi
|
||||
for max_end < len(path) && path[max_end] != '/' {
|
||||
max_end += 1
|
||||
}
|
||||
for end := max_end; end >= pi; end -= 1 {
|
||||
if match_tokens(tokens, classes, ti + 1, path, end) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
case .Globstar:
|
||||
if ti + 1 >= len(tokens) {
|
||||
return true
|
||||
}
|
||||
if match_tokens(tokens, classes, ti + 1, path, pi) {
|
||||
return true
|
||||
}
|
||||
for end := pi + 1; end <= len(path); end += 1 {
|
||||
if path[end - 1] == '/' {
|
||||
if match_tokens(tokens, classes, ti + 1, path, end) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
case .Class:
|
||||
if pi >= len(path) {
|
||||
return false
|
||||
}
|
||||
cd := classes[tok.class_idx]
|
||||
ch := path[pi]
|
||||
in_range := false
|
||||
for r in cd.ranges {
|
||||
if ch >= r.lo && ch <= r.hi {
|
||||
in_range = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if in_range != cd.negated {
|
||||
return match_tokens(tokens, classes, ti + 1, path, pi + 1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
glob_match_compiled :: proc(gp: ^GlobPattern, path: string) -> bool {
|
||||
tokens := gp.tokens[:]
|
||||
classes := gp.classes[:]
|
||||
|
||||
if gp.anchored {
|
||||
return match_tokens(tokens, classes, 0, path, 0)
|
||||
}
|
||||
|
||||
if match_tokens(tokens, classes, 0, path, 0) {
|
||||
return true
|
||||
}
|
||||
for i := 1; i < len(path); i += 1 {
|
||||
if path[i - 1] == '/' {
|
||||
if match_tokens(tokens, classes, 0, path, i) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
glob_destroy :: proc(gp: ^GlobPattern) {
|
||||
for &cd in gp.classes {
|
||||
delete(cd.ranges)
|
||||
}
|
||||
delete(gp.classes)
|
||||
delete(gp.tokens)
|
||||
}
|
||||
Reference in New Issue
Block a user