use windows_sys::Win32::Foundation::TRUE;
use windows_sys::Win32::Globalization::{CSTR_EQUAL, CompareStringOrdinal};
use wtf_string::{Wtf16Str, Wtf16String};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum CaseSensitivity {
#[default]
Sensitive,
Insensitive,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PatternToken {
Literal(Wtf16String),
AnyOne,
AnyRun,
Alternation(Vec<NamePattern>),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct NamePattern {
tokens: Vec<PatternToken>,
}
impl NamePattern {
#[must_use]
pub fn empty() -> Self {
Self { tokens: Vec::new() }
}
#[must_use]
pub fn literal(name: &Wtf16Str) -> Self {
Self {
tokens: vec![PatternToken::Literal(Wtf16String::from_units(
name.as_units(),
))],
}
}
#[must_use]
pub fn from_tokens(tokens: Vec<PatternToken>) -> Self {
Self { tokens }
}
pub fn push(&mut self, token: PatternToken) {
self.tokens.push(token);
}
#[must_use]
pub fn with(mut self, token: PatternToken) -> Self {
self.tokens.push(token);
self
}
#[must_use]
pub fn tokens(&self) -> &[PatternToken] {
&self.tokens
}
#[must_use]
pub fn matches(&self, name: &Wtf16Str, case: CaseSensitivity) -> bool {
match_tokens(&self.tokens, name.as_units(), case)
}
}
fn match_tokens(tokens: &[PatternToken], units: &[u16], case: CaseSensitivity) -> bool {
let Some((first, rest)) = tokens.split_first() else {
return units.is_empty();
};
match first {
PatternToken::Literal(literal) => {
let width = literal.len();
match_literal_prefix(units, literal, case) && match_tokens(rest, &units[width..], case)
}
PatternToken::AnyOne => match code_point_width(units) {
Some(width) => match_tokens(rest, &units[width..], case),
None => false,
},
PatternToken::AnyRun => {
let mut remaining = units;
loop {
if match_tokens(rest, remaining, case) {
return true;
}
match code_point_width(remaining) {
Some(width) => remaining = &remaining[width..],
None => return false,
}
}
}
PatternToken::Alternation(arms) => arms.iter().any(|arm| {
let mut spliced = arm.tokens.clone();
spliced.extend_from_slice(rest);
match_tokens(&spliced, units, case)
}),
}
}
fn match_literal_prefix(units: &[u16], literal: &Wtf16Str, case: CaseSensitivity) -> bool {
let width = literal.len();
if units.len() < width {
return false;
}
units_equal(&units[..width], literal.as_units(), case)
}
fn units_equal(left: &[u16], right: &[u16], case: CaseSensitivity) -> bool {
match case {
CaseSensitivity::Sensitive => left == right,
CaseSensitivity::Insensitive => ordinal_equal_ignoring_case(left, right),
}
}
fn ordinal_equal_ignoring_case(left: &[u16], right: &[u16]) -> bool {
if left.len() != right.len() {
return false;
}
if left.is_empty() {
return true;
}
let Ok(left_len) = i32::try_from(left.len()) else {
return left == right;
};
let Ok(right_len) = i32::try_from(right.len()) else {
return left == right;
};
let result =
unsafe { CompareStringOrdinal(left.as_ptr(), left_len, right.as_ptr(), right_len, TRUE) };
result == CSTR_EQUAL
}
fn code_point_width(units: &[u16]) -> Option<usize> {
let first = *units.first()?;
let is_high_surrogate = (0xD800..0xDC00).contains(&first);
let has_low_surrogate = units
.get(1)
.is_some_and(|second| (0xDC00..0xE000).contains(second));
Some(if is_high_surrogate && has_low_surrogate {
2
} else {
1
})
}
#[cfg(test)]
mod tests;