use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::cmp::Ordering;
use super::parser::parse_query;
use super::{Binding, Component, Entry};
mod zip_longest {
pub(crate) fn zip_longest<'a, T>(
a: &'a [T],
b: &'a [T],
) -> impl Iterator<Item = (Option<&'a T>, Option<&'a T>)> + 'a {
ZipLongest {
a: a.iter(),
b: b.iter(),
}
}
#[derive(Debug)]
struct ZipLongest<A, B> {
a: A,
b: B,
}
impl<A, B> Iterator for ZipLongest<A, B>
where
A: Iterator,
B: Iterator,
{
type Item = (Option<A::Item>, Option<B::Item>);
fn next(&mut self) -> Option<Self::Item> {
match (self.a.next(), self.b.next()) {
(None, None) => None,
(a, b) => Some((a, b)),
}
}
}
#[cfg(test)]
mod test_zip_longest {
use alloc::vec::Vec;
use super::zip_longest;
#[test]
fn empty() {
let (a, b): ([u8; 0], [u8; 0]) = ([], []);
let res = zip_longest(&a, &b).collect::<Vec<_>>();
assert_eq!(res, []);
}
#[test]
fn same_length() {
let a = [0, 1, 2];
let b = [4, 5, 6];
let expected = [
(Some(&0), Some(&4)),
(Some(&1), Some(&5)),
(Some(&2), Some(&6)),
];
let res = zip_longest(&a, &b).collect::<Vec<_>>();
assert_eq!(res, expected);
}
#[test]
fn first_shorter() {
let a = [0, 1];
let b = [4, 5, 6, 7];
let expected = [
(Some(&0), Some(&4)),
(Some(&1), Some(&5)),
(None, Some(&6)),
(None, Some(&7)),
];
let res = zip_longest(&a, &b).collect::<Vec<_>>();
assert_eq!(res, expected);
}
#[test]
fn second_shorter() {
let a = [0, 1, 2, 3];
let b = [4, 5];
let expected = [
(Some(&0), Some(&4)),
(Some(&1), Some(&5)),
(Some(&2), None),
(Some(&3), None),
];
let res = zip_longest(&a, &b).collect::<Vec<_>>();
assert_eq!(res, expected);
}
}
}
#[derive(Debug, Copy, Clone)]
enum HowMatched {
Instance,
Class,
Wildcard,
}
#[derive(Debug, Copy, Clone)]
struct MatchComponent {
preceding_binding: Binding,
how_matched: HowMatched,
}
#[derive(Debug, Copy, Clone)]
enum MatchKind {
SkippedViaLooseBinding,
Matched(MatchComponent),
}
impl MatchKind {
fn new_match(preceding_binding: Binding, how_matched: HowMatched) -> Self {
Self::Matched(MatchComponent {
preceding_binding,
how_matched,
})
}
}
fn check_match(entry: &Entry, resource: &[String], class: &[String]) -> Vec<Vec<MatchKind>> {
#[derive(Debug, Default)]
struct MatchState {
index: usize,
history: Vec<MatchKind>,
}
impl MatchState {
fn skip_loose(&self) -> Self {
let mut history = self.history.clone();
history.push(MatchKind::SkippedViaLooseBinding);
Self {
index: self.index,
history,
}
}
fn step(mut self, kind: MatchKind) -> Self {
self.history.push(kind);
self.index += 1;
self
}
}
let mut states = vec![MatchState::default()];
for (resource, class) in zip_longest::zip_longest(resource, class) {
let mut next_states = Vec::new();
for state in states {
if state.index == entry.components.len() {
continue;
}
let binding = entry.components[state.index].0;
match binding {
Binding::Tight => {}
Binding::Loose => next_states.push(state.skip_loose()),
}
let kind = match entry.components[state.index].1 {
Component::Wildcard => Some(MatchKind::new_match(binding, HowMatched::Wildcard)),
Component::Normal(ref s) => {
if Some(s) == resource {
Some(MatchKind::new_match(binding, HowMatched::Instance))
} else if Some(s) == class {
Some(MatchKind::new_match(binding, HowMatched::Class))
} else {
None
}
}
};
if let Some(kind) = kind {
next_states.push(state.step(kind));
}
}
states = next_states;
}
states
.into_iter()
.filter(|s| s.index == entry.components.len())
.map(|s| s.history)
.collect()
}
fn compare_matches(match1: &[MatchKind], match2: &[MatchKind]) -> Ordering {
use Binding::{Loose, Tight};
use HowMatched::{Class, Instance, Wildcard};
use MatchKind::{Matched, SkippedViaLooseBinding};
fn rule1(match1: MatchKind, match2: MatchKind) -> Ordering {
if let Matched(_) = match1 {
if let SkippedViaLooseBinding = match2 {
return Ordering::Greater;
}
}
Ordering::Equal
}
fn rule2(match1: MatchKind, match2: MatchKind) -> Ordering {
if let Matched(MatchComponent {
how_matched: Instance,
preceding_binding: _,
}) = match1
{
if let Matched(MatchComponent {
how_matched: Class,
preceding_binding: _,
}) = match2
{
return Ordering::Greater;
}
if let Matched(MatchComponent {
how_matched: Wildcard,
..
}) = match2
{
return Ordering::Greater;
}
}
if let Matched(MatchComponent {
how_matched: Class, ..
}) = match1
{
if let Matched(MatchComponent {
how_matched: Wildcard,
..
}) = match2
{
return Ordering::Greater;
}
}
Ordering::Equal
}
fn rule3(match1: MatchKind, match2: MatchKind) -> Ordering {
if let Matched(MatchComponent {
preceding_binding: Tight,
..
}) = match1
{
if let Matched(MatchComponent {
preceding_binding: Loose,
..
}) = match2
{
return Ordering::Greater;
}
}
Ordering::Equal
}
assert_eq!(
match1.len(),
match2.len(),
"Both matches should have the same length (which is guaranteed by the current \
implementation of check_match())"
);
for (m1, m2) in match1.iter().zip(match2.iter()) {
let ordering = rule1(*m1, *m2)
.then_with(|| rule1(*m2, *m1).reverse())
.then_with(|| rule2(*m1, *m2))
.then_with(|| rule2(*m2, *m1).reverse())
.then_with(|| rule3(*m1, *m2))
.then_with(|| rule3(*m2, *m1).reverse());
if ordering != Ordering::Equal {
return ordering;
}
}
Ordering::Equal
}
pub(crate) fn match_entry<'a>(
database: &'a [Entry],
resource: &str,
class: &str,
) -> Option<&'a [u8]> {
let resource = parse_query(resource.as_bytes())?;
let class = parse_query(class.as_bytes())?;
database
.iter()
.filter_map(|entry| {
let matches = check_match(entry, &resource, &class);
let best_match = matches
.into_iter()
.max_by(|match1, match2| compare_matches(match1, match2));
best_match.map(|m| (entry, m))
})
.max_by(|(_, match1), (_, match2)| compare_matches(match1, match2))
.map(|(entry, _)| &entry.value[..])
}
#[cfg(test)]
mod test {
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use std::eprintln;
use super::super::parser::parse_database;
use super::match_entry;
#[test]
fn test_matches() {
let tests = [
(&b""[..], "", "", None),
(
b"First.second: 1",
"First.second",
"First.second.third",
None,
),
(b"", "First.second", "", None),
(b"First.second: 1", "First.third", "", None),
(b"First.second: 1", "First", "", None),
(b"First: 1", "First.second", "", None),
(b"First.?.fourth: 1", "First.second.third.fourth", "", None),
(b"First*?.third: 1", "First.third", "", None),
(b"First: 1", "first", "", None),
(b"First: 1", "", "first", None),
(
b"First: 1\nFirst: 2\nFirst: 3\n",
"First",
"",
Some(&b"3"[..]),
),
(
b"First: 1\nSecond: 2\nSecond: 3\nThird: 4\n",
"Second",
"",
Some(b"3"),
),
(b"First: 1", "First", "", Some(b"1")),
(b"First.second: 1", "First.second", "", Some(b"1")),
(b"?.second: 1", "First.second", "", Some(b"1")),
(b"First.?.third: 1", "First.second.third", "", Some(b"1")),
(
b"First.?.?.fourth: 1",
"First.second.third.fourth",
"",
Some(b"1"),
),
(b"*second: 1", "First.second", "", Some(b"1")),
(b".second: 1", "First.second", "", None),
(b"*third: 1", "First.second.third", "", Some(b"1")),
(b"First*second: 1", "First.second", "", Some(b"1")),
(b"First*third: 1", "First.second.third", "", Some(b"1")),
(
b"First*fourth: 1",
"First.second.third.fourth",
"",
Some(b"1"),
),
(b"First*?.third: 1", "First.second.third", "", Some(b"1")),
(b"First: 1", "Second", "First", Some(b"1")),
(
b"First.second: 1",
"First.third",
"first.second",
Some(b"1"),
),
(
b"First.second.third: 1",
"First.third.third",
"first.second.fourth",
Some(b"1"),
),
(
b"First*third*fifth: 1",
"First.second.third.fourth.third.fifth",
"",
Some(b"1"),
),
(b"First: x\\\ny", "First", "", Some(b"xy")),
(b"! First: x", "First", "", None),
(b"# First: x", "First", "", None),
(b"First:", "First", "", Some(b"")),
(b"First: ", "First", "", Some(b"")),
(b"First: \t ", "First", "", Some(b"")),
(b"*.bar: 1", "foo.foo.bar", "", Some(b"1")),
(b"...bar: 1", "foo.bar", "", None),
(b"...bar: 1", "foo.foo.foo.bar", "", None),
(b"***bar: 1", "foo.bar", "", Some(b"1")),
(b".*.bar: 1", "foo.bar", "", Some(b"1")),
(b".*.bar: 1", "foo.foo.bar", "", Some(b"1")),
(b"..*bar: 1", "foo.foo.foo.foo.bar", "", Some(b"1")),
(b"a.*.z: 1", "a.b.c.d.e.f.z", "", Some(b"1")),
(b"a...z: 1", "a.z", "", Some(b"1")),
(b"a...z: 1", "a.b.z", "", None),
(b"First: 1\nSecond: 2\n", "First", "", Some(b"1")),
(b"First: 1\nSecond: 2\n", "Second", "", Some(b"2")),
(b"a*c.e: 1", "a.b.c.d.c.e", "", Some(b"1")),
(b"a*c.e: 1", "a.b.c.c.e", "", Some(b"1")),
(b"a*?.e: 1", "a.b.c.e", "", Some(b"1")),
(b"a*c*e: 1", "a.b.c.d.c.d.e.d.e", "", Some(b"1")),
(
b"First.second.third: 1\nFirst*third: 2\n",
"First.second.third",
"",
Some(b"1"),
),
(
b"First*third: 2\nFirst.second.third: 1\n",
"First.second.third",
"",
Some(b"1"),
),
(
b"First.second.third: 1\nFirst*third: 2\n",
"x.x.x",
"First.second.third",
Some(b"1"),
),
(
b"First*third: 2\nFirst.second.third: 1\n",
"x.x.x",
"First.second.third",
Some(b"1"),
),
(
b"First.second: 1\nFirst.third: 2\n",
"First.second",
"First.third",
Some(b"1"),
),
(
b"First.third: 2\nFirst.second: 1\n",
"First.second",
"First.third",
Some(b"1"),
),
(
b"First.second.third: 1\nFirst.?.third: 2\n",
"First.second.third",
"",
Some(b"1"),
),
(
b"First.?.third: 2\nFirst.second.third: 1\n",
"First.second.third",
"",
Some(b"1"),
),
(
b"First.second.third: 1\nFirst.?.third: 2\n",
"x.x.x",
"First.second.third",
Some(b"1"),
),
(
b"First.?.third: 2\nFirst.second.third: 1\n",
"x.x.x",
"First.second.third",
Some(b"1"),
),
(
b"First.second: 1\nFirst*second: 2\n",
"First.second",
"",
Some(b"1"),
),
(
b"First*second: 2\nFirst.second: 1\n",
"First.second",
"",
Some(b"1"),
),
(
b"xmh*Paned*activeForeground: red\n\
*incorporate.Foreground: blue\n\
xmh.toc*Command*activeForeground: green\n\
xmh.toc*?.Foreground: white\n\
xmh.toc*Command.activeForeground: black",
"xmh.toc.messagefunctions.incorporate.activeForeground",
"Xmh.Paned.Box.Command.Foreground",
Some(b"black"),
),
(
b"urxvt*background: [95]#000",
"urxvt.background",
"",
Some(b"[95]#000"),
),
(
b"urxvt*scrollBar_right:true",
"urxvt.scrollBar_right",
"",
Some(b"true"),
),
(
b"urxvt*cutchars: '\"'()*<>[]{|}",
"urxvt.cutchars",
"",
Some(b"'\"'()*<>[]{|}"),
),
(
b"urxvt.keysym.Control-Shift-Up: perl:font:increment",
"urxvt.keysym.Control-Shift-Up",
"",
Some(b"perl:font:increment"),
),
(
b"rofi.normal: #000000, #000000, #000000, #000000",
"rofi.normal",
"",
Some(b"#000000, #000000, #000000, #000000"),
),
(b"*foo.bar: 1", "bar", "", None),
(
b"First.Second.Third: 1\nFirst.Second: 2",
"First.Second.Third",
"First.Second",
Some(b"1"),
),
(
b"First.Second.Third: 1\nFirst.Second: 2",
"First.Second",
"First.Second.Third",
Some(b"1"),
),
];
let mut failures = 0;
for &(data, resource, class, expected) in &tests {
let mut entries = Vec::new();
parse_database(data, &mut entries, |_, _| unreachable!()).unwrap();
let result = match_entry(&entries, resource, class);
if result != expected {
eprintln!(
"While testing resource '{resource}' and class '{class}' with the following input:"
);
eprintln!("{}", print_string(data));
eprintln!("Expected: {:?}", expected.map(print_string));
eprintln!("Got: {:?}", result.map(print_string));
eprintln!();
failures += 1;
}
}
assert!(failures == 0, "Had {failures} failures");
}
fn print_string(data: &[u8]) -> String {
core::str::from_utf8(data)
.map_or_else(|_| format!("{data:?}"), alloc::string::ToString::to_string)
}
}