Skip to content

Commit

Permalink
std: add predicate pattern support to OsStr
Browse files Browse the repository at this point in the history
Due to technical limitations adding support for predicate as patterns
on OsStr slices must be done via core::pattern::Predicate wrapper type.
This isn’t ideal but for the time being it’s the best option I’ve came
up with.

The core of the issue (as I understand it) is that FnMut is a foreign
type in std crate where OsStr is defined.

Using predicate as a pattern on OsStr is the final piece which now
allows parsing command line arguments.
  • Loading branch information
mina86 committed May 5, 2023
1 parent 237606c commit e7fe1a2
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 1 deletion.
98 changes: 98 additions & 0 deletions library/std/src/ffi/os_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,104 @@ unsafe impl<'hs> ReverseSearcher<&'hs OsStr> for CharSearcher<'hs> {
#[unstable(feature = "pattern", issue = "27721")]
impl<'hs> DoubleEndedSearcher<&'hs OsStr> for CharSearcher<'hs> {}

#[unstable(feature = "pattern", issue = "27721")]
// FIXME: Using Predicate because of:
// error[E0210]: type parameter `F` must be covered by another type when it
// appears before the first local type (`OsStr`)
// --> library/std/src/ffi/os_str.rs:1697:11
// |
// 1697 | impl<'hs, F: FnMut(char) -> bool> core::pattern::Pattern<&'hs OsStr> for F {
// | ^ type parameter `F` must be covered by another type
// when it appears before the first local type (`OsStr`)
impl<'hs, F: FnMut(char) -> bool> core::pattern::Pattern<&'hs OsStr>
for core::pattern::Predicate<F>
{
type Searcher = PredicateSearcher<'hs, F>;

fn into_searcher(self, haystack: &'hs OsStr) -> Self::Searcher {
Self::Searcher::new(haystack, self.into_fn())
}

fn is_contained_in(self, haystack: &'hs OsStr) -> bool {
self.into_fn().is_contained_in(core::str_bytes::Bytes::from(haystack))
}

fn is_prefix_of(self, haystack: &'hs OsStr) -> bool {
self.into_fn().is_prefix_of(core::str_bytes::Bytes::from(haystack))
}

fn is_suffix_of(self, haystack: &'hs OsStr) -> bool {
self.into_fn().is_suffix_of(core::str_bytes::Bytes::from(haystack))
}

/// Removes the pattern from the front of haystack, if it matches.
fn strip_prefix_of(self, haystack: &'hs OsStr) -> Option<&'hs OsStr> {
self.into_fn()
.strip_prefix_of(core::str_bytes::Bytes::from(haystack))
.map(|bytes| bytes.into())
}

/// Removes the pattern from the back of haystack, if it matches.
fn strip_suffix_of(self, haystack: &'hs OsStr) -> Option<&'hs OsStr>
where
Self::Searcher: ReverseSearcher<&'hs OsStr>,
{
self.into_fn()
.strip_suffix_of(core::str_bytes::Bytes::from(haystack))
.map(|bytes| bytes.into())
}
}

#[derive(Clone, Debug)]
#[unstable(feature = "pattern", issue = "27721")]
pub struct PredicateSearcher<'hs, P>(core::str_bytes::PredicateSearcher<'hs, BytesFlavour, P>);

impl<'hs, P> PredicateSearcher<'hs, P> {
fn new(haystack: &'hs OsStr, pred: P) -> PredicateSearcher<'hs, P> {
Self(core::str_bytes::PredicateSearcher::new(haystack.into(), pred))
}
}

#[unstable(feature = "pattern", issue = "27721")]
unsafe impl<'hs, P: FnMut(char) -> bool> Searcher<&'hs OsStr> for PredicateSearcher<'hs, P> {
#[inline(always)]
fn haystack(&self) -> &'hs OsStr {
self.0.haystack().into()
}

#[inline(always)]
fn next(&mut self) -> SearchStep {
self.0.next()
}
#[inline(always)]
fn next_match(&mut self) -> Option<(usize, usize)> {
self.0.next_match()
}
#[inline(always)]
fn next_reject(&mut self) -> Option<(usize, usize)> {
self.0.next_reject()
}
}

#[unstable(feature = "pattern", issue = "27721")]
unsafe impl<'hs, P: FnMut(char) -> bool> ReverseSearcher<&'hs OsStr> for PredicateSearcher<'hs, P> {
#[inline(always)]
fn next_back(&mut self) -> SearchStep {
self.0.next_back()
}
#[inline(always)]
fn next_match_back(&mut self) -> Option<(usize, usize)> {
self.0.next_match_back()
}
#[inline(always)]
fn next_reject_back(&mut self) -> Option<(usize, usize)> {
self.0.next_reject_back()
}
}

#[unstable(feature = "pattern", issue = "27721")]
impl<'hs, P: FnMut(char) -> bool> DoubleEndedSearcher<&'hs OsStr> for PredicateSearcher<'hs, P> {}

#[unstable(feature = "pattern", issue = "27721")]
impl<'hs, 'p> core::pattern::Pattern<&'hs OsStr> for &'p str {
type Searcher = StrSearcher<'hs, 'p>;
Expand Down
39 changes: 38 additions & 1 deletion library/std/tests/os_str.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![feature(associated_type_bounds, pattern)]

use core::pattern::{Pattern, Searcher, ReverseSearcher};
use core::pattern::{Pattern, Searcher, ReverseSearcher, predicate};
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};

Expand Down Expand Up @@ -111,6 +111,23 @@ fn do_test_short_flag(valid: bool) {
assert_eq!(Some(&*os("shórt")), arg.strip_prefix('-'));
assert_eq!(Some(&*os("shórt")), arg.strip_prefix("-"));
assert_eq!(None, arg.strip_prefix("--"));

// A bit awkward but closure can be used to test short options character
// by character.
let mut switch = '\0';
let mut check_switch = |chr| {
switch = chr;
chr == 's' || chr == 'h'
};
assert_eq!(
Some(&*os("hórt")),
os("shórt").strip_prefix(predicate(&mut check_switch))
);
assert_eq!(
Some(&*os("órt")),
os("hórt").strip_prefix(predicate(&mut check_switch))
);
assert_eq!(None, os("órt").strip_prefix(predicate(&mut check_switch)));
}

#[test]
Expand Down Expand Up @@ -157,15 +174,21 @@ fn test_le() {
#[test]
fn test_find() {
assert_eq!(find("hello", 'l'), Some(2));
assert_eq!(find("hello", predicate(|c: char| c == 'o')), Some(4));
assert!(find("hello", 'x').is_none());
assert!(find("hello", predicate(|c: char| c == 'x')).is_none());
assert_eq!(find("ประเทศไทย中华Việt Nam", '华'), Some(30));
assert_eq!(find("ประเทศไทย中华Việt Nam", predicate(|c: char| c == '华')), Some(30));
}

#[test]
fn test_rfind() {
assert_eq!(rfind("hello", 'l'), Some(3));
assert_eq!(rfind("hello", predicate(|c: char| c == 'o')), Some(4));
assert!(rfind("hello", 'x').is_none());
assert!(rfind("hello", predicate(|c: char| c == 'x')).is_none());
assert_eq!(rfind("ประเทศไทย中华Việt Nam", '华'), Some(30));
assert_eq!(rfind("ประเทศไทย中华Việt Nam", predicate(|c: char| c == '华')), Some(30));
}

/*
Expand Down Expand Up @@ -1832,13 +1855,27 @@ fn test_split_char_iterator() {
rsplit.reverse();
assert_eq!(rsplit, ["\nMäry", "häd", "ä", "little", "lämb\nLittle", "lämb\n"]);

let split: Vec<&OsStr> = os(data).split(predicate(|c: char| c == ' ')).collect();
assert_eq!(split, ["\nMäry", "häd", "ä", "little", "lämb\nLittle", "lämb\n"]);

let mut rsplit: Vec<&OsStr> = os(data).split(predicate(|c: char| c == ' ')).rev().collect();
rsplit.reverse();
assert_eq!(rsplit, ["\nMäry", "häd", "ä", "little", "lämb\nLittle", "lämb\n"]);

// Unicode
let split: Vec<&OsStr> = os(data).split('ä').collect();
assert_eq!(split, ["\nM", "ry h", "d ", " little l", "mb\nLittle l", "mb\n"]);

let mut rsplit: Vec<&OsStr> = os(data).split('ä').rev().collect();
rsplit.reverse();
assert_eq!(rsplit, ["\nM", "ry h", "d ", " little l", "mb\nLittle l", "mb\n"]);

let split: Vec<&OsStr> = os(data).split(predicate(|c: char| c == 'ä')).collect();
assert_eq!(split, ["\nM", "ry h", "d ", " little l", "mb\nLittle l", "mb\n"]);

let mut rsplit: Vec<&OsStr> = os(data).split(predicate(|c: char| c == 'ä')).rev().collect();
rsplit.reverse();
assert_eq!(rsplit, ["\nM", "ry h", "d ", " little l", "mb\nLittle l", "mb\n"]);
}

#[test]
Expand Down

0 comments on commit e7fe1a2

Please sign in to comment.