use core::mem::transmute;
use super::query::{Condition, FzfQuery, Pattern};
use crate::utils;
#[derive(Clone)]
pub struct FzfParser {
chars: Vec<char>,
patterns: Vec<Pattern<'static>>,
conditions: Vec<Condition<'static>>,
}
impl Default for FzfParser {
#[inline]
fn default() -> Self {
Self {
chars: vec![char::default(); 64],
patterns: vec![Pattern::default(); 64],
conditions: vec![Condition::default(); 64],
}
}
}
impl core::fmt::Debug for FzfParser {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("FzfParser").finish_non_exhaustive()
}
}
impl FzfParser {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn parse<'a>(&'a mut self, query: &str) -> FzfQuery<'a> {
let max_chars = query.len();
if self.chars.len() < max_chars {
self.chars.resize(max_chars, char::default());
}
let max_conditions = query.len() / 2 + 1;
if self.conditions.len() < max_conditions {
self.conditions.resize(max_conditions, Condition::default());
}
if self.patterns.len() < max_conditions {
self.patterns.resize(max_conditions, Pattern::default());
}
let patterns: &'a mut [Pattern<'static>] =
self.patterns.as_mut_slice();
let patterns = unsafe {
transmute::<&'a mut [Pattern<'static>], &'a mut [Pattern<'a>]>(
patterns,
)
};
let mut num_conditions = 0;
for condition in
Patterns::new(patterns, &mut self.chars, query).map(Condition::new)
{
let condition = unsafe {
transmute::<Condition, Condition<'static>>(condition)
};
self.conditions[num_conditions] = condition;
num_conditions += 1;
}
FzfQuery::new_extended(&self.conditions[..num_conditions])
}
#[inline]
pub fn parse_not_extended<'a>(&'a mut self, query: &str) -> FzfQuery<'a> {
let mut char_len = 0;
for ch in query.chars() {
self.chars[char_len] = ch;
char_len += 1;
}
FzfQuery::new_not_extended(&self.chars[..char_len])
}
}
const OR_BLOCK_SEPARATOR: &[char] = &['|'];
struct Patterns<'buf, 's> {
buf: &'buf mut [Pattern<'buf>],
allocated: usize,
words: Words<'buf, 's>,
next: Option<Pattern<'buf>>,
}
impl<'buf, 's> Patterns<'buf, 's> {
#[inline]
fn alloc(&mut self, pattern: Pattern<'buf>) {
self.buf[self.allocated] = pattern;
self.allocated += 1;
}
#[inline]
fn new(
patterns_buf: &'buf mut [Pattern<'buf>],
char_buf: &'buf mut [char],
s: &'s str,
) -> Self {
Self {
buf: patterns_buf,
allocated: 0,
words: Words::new(char_buf, s),
next: None,
}
}
}
impl<'buf, 's> Iterator for Patterns<'buf, 's> {
type Item = &'buf [Pattern<'buf>];
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let prev_allocated = self.allocated;
let mut looking_for_or;
if let Some(first_pattern) = self.next.take() {
self.alloc(first_pattern);
looking_for_or = true;
} else {
looking_for_or = false;
}
loop {
let Some(word) = self.words.next() else {
break;
};
let word_is_condition = word != OR_BLOCK_SEPARATOR;
if word_is_condition {
let Some(word) = Pattern::parse(word) else { continue };
if looking_for_or {
self.next = Some(word);
break;
} else {
self.alloc(word);
looking_for_or = true;
continue;
}
}
looking_for_or = false;
}
if self.allocated == prev_allocated {
return None;
}
let patterns = &self.buf[prev_allocated..self.allocated];
let patterns =
unsafe { transmute::<&[Pattern], &'buf [Pattern]>(patterns) };
Some(patterns)
}
}
#[doc(hidden)]
pub struct Words<'buf, 'sentence> {
buf: &'buf mut [char],
allocated: usize,
s: &'sentence str,
}
impl<'buf, 'sentence> Words<'buf, 'sentence> {
#[inline]
fn alloc(&mut self, s: &str) {
for ch in s.chars() {
self.buf[self.allocated] = ch;
self.allocated += 1;
}
}
#[inline]
fn new(buf: &'buf mut [char], s: &'sentence str) -> Self {
Self { buf, s: utils::strip_leading_spaces(s), allocated: 0 }
}
}
impl<'buf> Iterator for Words<'buf, '_> {
type Item = &'buf [char];
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
if self.s.is_empty() {
return None;
}
let prev_allocated = self.allocated;
let mut word_byte_end = 0;
let mut s = self.s;
loop {
match memchr::memchr(b' ', s.as_bytes()) {
Some(0) => break,
Some(offset) if s.as_bytes()[offset - 1] == b'\\' => {
self.alloc(&s[..offset - 1]);
self.alloc(" ");
s = &s[offset + 1..];
word_byte_end += offset + 1;
},
Some(offset) => {
let s = &s[..offset];
self.alloc(s);
word_byte_end += s.len();
break;
},
None => {
self.alloc(s);
word_byte_end += s.len();
break;
},
}
}
self.s = utils::strip_leading_spaces(&self.s[word_byte_end..]);
let word = &self.buf[prev_allocated..self.allocated];
let word = unsafe { transmute::<&[char], &'buf [char]>(word) };
Some(word)
}
}
#[cfg(feature = "__tests")]
#[doc(hidden)]
pub fn parse(s: &str) -> FzfQuery<'static> {
let parser = Box::leak(Box::new(FzfParser::new()));
parser.parse(s)
}
#[cfg(test)]
mod parse_tests {
use super::super::query::*;
use super::*;
#[test]
fn parse_query_empty() {
assert!(parse("").is_empty());
}
#[test]
fn parse_query_single_fuzzy() {
let query = parse("foo");
let SearchMode::NotExtended(pattern) = query.search_mode else {
panic!();
};
assert_eq!(pattern.into_string(), "foo");
assert_eq!(pattern.match_type, MatchType::Fuzzy);
}
#[test]
fn parse_query_upstream_extended() {
let query = parse(
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | \
zzz$ | !ZZZ |",
);
let SearchMode::Extended(conditions) = query.search_mode else {
panic!();
};
assert_eq!(conditions.len(), 9);
let pattern = conditions[0].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::Fuzzy);
assert!(!pattern.is_inverse);
let pattern = conditions[1].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::Exact);
assert!(!pattern.is_inverse);
let pattern = conditions[2].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::PrefixExact);
assert!(!pattern.is_inverse);
let pattern = conditions[3].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::SuffixExact);
assert!(!pattern.is_inverse);
let pattern = conditions[4].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::Exact);
assert!(pattern.is_inverse);
let pattern = conditions[5].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::Fuzzy);
assert!(pattern.is_inverse);
let pattern = conditions[6].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::PrefixExact);
assert!(pattern.is_inverse);
let pattern = conditions[7].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::SuffixExact);
assert!(pattern.is_inverse);
let pattern = conditions[7].or_patterns()[1];
assert_eq!(pattern.match_type, MatchType::EqualExact);
assert!(!pattern.is_inverse);
let pattern = conditions[8].or_patterns()[0];
assert_eq!(pattern.match_type, MatchType::PrefixExact);
assert!(!pattern.is_inverse);
let pattern = conditions[8].or_patterns()[1];
assert_eq!(pattern.match_type, MatchType::Exact);
assert!(!pattern.is_inverse);
let pattern = conditions[8].or_patterns()[2];
assert_eq!(pattern.match_type, MatchType::SuffixExact);
assert!(!pattern.is_inverse);
let pattern = conditions[8].or_patterns()[3];
assert_eq!(pattern.match_type, MatchType::Exact);
assert!(pattern.is_inverse);
}
}
#[cfg(test)]
mod patterns_tests {
use super::*;
fn patterns(
s: &str,
) -> impl Iterator<Item = &'static [Pattern<'static>]> + '_ {
let patterns_buf = vec![Pattern::default(); s.len() / 2 + 1].leak();
let char_buf = vec![char::default(); s.len()].leak();
Patterns::new(patterns_buf, char_buf, s)
}
fn pattern(s: &str) -> Pattern<'static> {
Pattern::parse(s.chars().collect::<Vec<_>>().leak()).unwrap()
}
#[test]
fn patterns_empty() {
let mut blocks = patterns("");
assert!(blocks.next().is_none());
}
#[test]
fn patterns_single() {
let mut blocks = patterns("foo");
assert_eq!(blocks.next().unwrap(), [pattern("foo")]);
assert_eq!(blocks.next(), None);
}
#[test]
fn patterns_multiple_ors() {
let mut blocks = patterns("foo | bar | baz");
assert_eq!(
blocks.next().unwrap(),
[pattern("foo"), pattern("bar"), pattern("baz")]
);
assert_eq!(blocks.next(), None);
}
#[test]
fn patterns_multiple_ands() {
let mut blocks = patterns("foo bar baz");
assert_eq!(blocks.next().unwrap(), [pattern("foo")]);
assert_eq!(blocks.next().unwrap(), [pattern("bar")]);
assert_eq!(blocks.next().unwrap(), [pattern("baz")]);
assert_eq!(blocks.next(), None);
}
#[test]
fn patterns_empty_between_ors() {
let mut blocks = patterns("foo | | bar");
assert_eq!(blocks.next().unwrap(), [pattern("foo"), pattern("bar")]);
assert_eq!(blocks.next(), None);
}
#[test]
fn patterns_multiple_ors_multiple_ands() {
let mut blocks = patterns("foo | bar baz qux | quux | corge");
assert_eq!(blocks.next().unwrap(), [pattern("foo"), pattern("bar")]);
assert_eq!(blocks.next().unwrap(), [pattern("baz")]);
assert_eq!(
blocks.next().unwrap(),
[pattern("qux"), pattern("quux"), pattern("corge")]
);
assert_eq!(blocks.next(), None);
}
}
#[cfg(feature = "__tests")]
#[doc(hidden)]
pub fn words(s: &str) -> impl Iterator<Item = String> {
let mut buf = Vec::new();
buf.resize(s.len(), char::default());
Words::new(&mut buf, s)
.map(String::from_iter)
.collect::<Vec<_>>()
.into_iter()
}
#[cfg(test)]
mod word_tests {
use super::*;
#[test]
fn words_empty() {
let mut words = words("");
assert!(words.next().is_none());
}
#[test]
fn words_single() {
let mut words = words("foo");
assert_eq!(words.next().as_deref(), Some("foo"));
assert_eq!(words.next(), None);
}
#[test]
fn words_escaped_escape_escaped_space() {
let mut words = words("\\\\ ");
assert_eq!(words.next().as_deref(), Some("\\ "));
assert_eq!(words.next(), None);
}
#[test]
fn words_multiple() {
let mut words = words("foo bar");
assert_eq!(words.next().as_deref(), Some("foo"));
assert_eq!(words.next().as_deref(), Some("bar"));
assert_eq!(words.next(), None);
}
#[test]
fn words_multiple_leading_trailing_spaces() {
let mut words = words(" foo bar ");
assert_eq!(words.next().as_deref(), Some("foo"));
assert_eq!(words.next().as_deref(), Some("bar"));
assert_eq!(words.next(), None);
}
#[test]
fn words_multiple_escaped_spaces() {
let mut words = words("foo\\ bar\\ baz");
assert_eq!(words.next().as_deref(), Some("foo bar baz"));
assert_eq!(words.next(), None);
}
#[test]
fn words_multiple_standalone_escaped_spaces() {
let mut words = words(" \\ foo \\ bar \\ ");
assert_eq!(words.next().as_deref(), Some(" "));
assert_eq!(words.next().as_deref(), Some("foo"));
assert_eq!(words.next().as_deref(), Some(" bar"));
assert_eq!(words.next().as_deref(), Some(" "));
assert_eq!(words.next(), None);
}
#[test]
fn words_single_escaped_spaces() {
let mut words = words("\\ ");
assert_eq!(words.next().as_deref(), Some(" "));
assert_eq!(words.next(), None);
}
#[test]
fn words_consecutive_escaped_spaces() {
let mut words = words(" \\ \\ \\ ");
assert_eq!(words.next().as_deref(), Some(" "));
assert_eq!(words.next(), None);
}
}