// Structural analysis of compiled glob patterns.
///|
/// Counts the syntax features in a compiled glob pattern.
pub(all) struct PatternStats {
literal_chars : Int
star_count : Int
globstar_count : Int
question_count : Int
char_class_count : Int
brace_group_count : Int
escaped_char_count : Int
} derive(Debug, Eq)
///|
fn PatternStats::zero() -> PatternStats {
{
literal_chars: 0,
star_count: 0,
globstar_count: 0,
question_count: 0,
char_class_count: 0,
brace_group_count: 0,
escaped_char_count: 0,
}
}
///|
fn add_stats(left : PatternStats, right : PatternStats) -> PatternStats {
{
literal_chars: left.literal_chars + right.literal_chars,
star_count: left.star_count + right.star_count,
globstar_count: left.globstar_count + right.globstar_count,
question_count: left.question_count + right.question_count,
char_class_count: left.char_class_count + right.char_class_count,
brace_group_count: left.brace_group_count + right.brace_group_count,
escaped_char_count: left.escaped_char_count + right.escaped_char_count,
}
}
///|
fn ast_stats(ast : AST) -> PatternStats {
match ast {
AST::Text(text) => { ..PatternStats::zero(), literal_chars: text.length() }
AST::Star => { ..PatternStats::zero(), star_count: 1 }
AST::GlobStar => { ..PatternStats::zero(), globstar_count: 1 }
AST::Question => { ..PatternStats::zero(), question_count: 1 }
AST::CharClass(_, _) => { ..PatternStats::zero(), char_class_count: 1 }
AST::Seq(nodes) => {
let mut result = PatternStats::zero()
for node in nodes {
result = add_stats(result, ast_stats(node))
}
result
}
AST::Brace(options) => {
let mut result = { ..PatternStats::zero(), brace_group_count: 1 }
for option in options {
for node in option {
result = add_stats(result, ast_stats(node))
}
}
result
}
}
}
///|
fn count_escaped_chars(pattern : String) -> Int {
let mut count = 0
let mut i = 0
while i + 1 < pattern.length() {
if pattern[i].to_int().unsafe_to_char() == '\\' {
count = count + 1
i = i + 2
} else {
i = i + 1
}
}
count
}
///|
/// Compiles and analyzes a pattern without performing any path matching.
pub fn analyze(pattern : String) -> Result[PatternStats, GlobError] {
match compile(pattern) {
Ok(ast) => {
let stats = ast_stats(ast)
Ok({ ..stats, escaped_char_count: count_escaped_chars(pattern) })
}
Err(err) => Err(err)
}
}
///|
/// Returns the number of syntax constructs that require matching logic.
pub fn PatternStats::magic_count(self : PatternStats) -> Int {
self.star_count +
self.globstar_count +
self.question_count +
self.char_class_count +
self.brace_group_count
}
///|
/// Returns true when the pattern contains no wildcard constructs.
pub fn PatternStats::is_literal(self : PatternStats) -> Bool {
self.magic_count() == 0
}
///|
/// Returns true when the pattern has at least one wildcard construct.
pub fn PatternStats::has_magic(self : PatternStats) -> Bool {
!self.is_literal()
}