/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/path/glob.nv
136 строк
5 KB
Evgeniy Golovin
docs(endocs): mop-up — translate residual prose in /// docs
01 авг 2026, 04:18
01 авг 2026, 04:18
fd2d14e
Код
Авторство
О чём код?
// stdlib/glob.nv — POSIX glob pattern matching. // // Поддержка: // * — любые символы кроме / (single-segment wildcard) // ** — любые символы включая / (recursive wildcard) // ? — один любой символ кроме / // [abc] — character class // [a-z] — character range // [!abc] — negated class // // API: // Glob.matches(pattern str, path str) -> bool module path.glob /// Match path against glob pattern. POSIX-style: /// `*` (one segment), `**` (recursive), `?` (single char), `[abc]` / `[a-z]` / `[!abc]`. /// /// # Examples /// ```nova /// assert(Glob.matches("*.nv", "hello.nv")) /// assert(Glob.matches("src/**/*.nv", "src/admin/audit.nv")) /// assert(!Glob.matches("*.nv", "src/hello.nv")) // * does not cross / /// ``` #stable(since = "0.1") export fn Glob.matches(pattern str, path str) -> bool => match_at(collect_chars(pattern), 0, collect_chars(path), 0) // Push-loop materialisation of `s.chars()` (not `.collect()` — crashes // codegen on CharsIter, Block 2 precedent). `match_at`/`match_class` recurse // heavily with random-access lookahead (`pi+1`, `pi+2`, ...) — precomputing // ONCE at the `Glob.matches` entry point and threading `[]char` through the // recursion replaces the ретрактированный `chars().nth(i)` O(n)-per-call scan // (D260-амендмент) with O(1) `Vec[char] @get(i)`, restoring the intended // linear-ish complexity instead of the O(n²) footgun. fn collect_chars(s str) -> []char { mut out []char = []char.new(cap: s.byte_len()) for c in s.chars() { out.push(c) } out } fn match_at(pattern []char, pi int, path []char, si int) -> bool { if pi == pattern.len() { return si == path.len() } ro p = match pattern.get(pi) { Some(c) => c, None => return false } if p == '*' { // Check for ** (recursive) if pi + 1 < pattern.len() && pattern.get(pi + 1) == Some('*') { // Skip extra * and any following / mut next_pi = pi + 2 if next_pi < pattern.len() && pattern.get(next_pi) == Some('/') { next_pi += 1 } // ** matches anything including / mut try_si = si while try_si <= path.len() { if match_at(pattern, next_pi, path, try_si) { return true } try_si += 1 } return false } // Single * — matches anything except / ro next_pi = pi + 1 mut try_si = si while try_si <= path.len() { if match_at(pattern, next_pi, path, try_si) { return true } // Останавливаемся на / if try_si < path.len() && path.get(try_si) == Some('/') { return match_at(pattern, next_pi, path, try_si) } try_si += 1 } return false } if p == '?' { if si == path.len() { return false } if path.get(si) == Some('/') { return false } return match_at(pattern, pi + 1, path, si + 1) } if p == '[' { if si == path.len() { return false } ro c = match path.get(si) { Some(ch) => ch, None => return false } ro (matched, next_pi) = match_class(pattern, pi + 1, c) if !matched { return false } return match_at(pattern, next_pi, path, si + 1) } // Literal char if si == path.len() { return false } if path.get(si) != Some(p) { return false } match_at(pattern, pi + 1, path, si + 1) } fn match_class(pattern []char, start int, c char) -> (bool, int) { mut pi = start mut negate = false if pi < pattern.len() && pattern.get(pi) == Some('!') { negate = true pi += 1 } mut found = false while pi < pattern.len() { match pattern.get(pi) { Some(']') => { ro result = if negate { !found } else { found } return (result, pi + 1) } Some(ch) => { // Check for range a-z if pi + 2 < pattern.len() && pattern.get(pi + 1) == Some('-') && pattern.get(pi + 2) != Some(']') { ro from_c = ch ro to_c = match pattern.get(pi + 2) { Some(x) => x, None => return (false, pi) } if (c as int) >= (from_c as int) && (c as int) <= (to_c as int) { found = true } pi += 3 } else { if c == ch { found = true } pi += 1 } } None => return (false, pi) } } (false, pi) // unclosed [ } // Тесты — см. peer-файл glob_test.nv (module path.glob_test).