shrimple_parser/
utils.rs

1//! This module provides utility functions for locating pointers into text.
2
3extern crate alloc;
4
5use alloc::borrow::Cow;
6#[cfg(feature = "std")]
7use std::{
8    ffi::{OsStr, OsString},
9    path::{Path, PathBuf},
10};
11
12/// Create a non-zero integer from a literal.
13/// ```rust
14/// # fn main() {
15/// use shrimple_parser::nonzero;
16///
17/// assert_eq!(nonzero!(69), core::num::NonZero::new(69).unwrap())
18/// # }
19/// ```
20#[macro_export]
21macro_rules! nonzero {
22    (0 $_:ident) => {
23        compile_error!("`0` passed to `nonzero!`")
24    };
25    ($n:literal) => {
26        core::num::NonZero::new($n).unwrap()
27    };
28}
29
30/// Safety:
31/// `bytes` must come from an `str`, `OsStr` or `Path`.
32#[cfg(feature = "std")]
33pub(crate) unsafe fn bytes_as_path(bytes: &[u8]) -> &std::path::Path {
34    std::path::Path::new(std::ffi::OsStr::from_encoded_bytes_unchecked(bytes))
35}
36
37/// Effectively an alias to `move |y| &x == y`.
38pub fn eq<T: PartialEq<Other>, Other>(x: T) -> impl Fn(&Other) -> bool {
39    move |y| &x == y
40}
41
42/// Effectively an alias to `move |y| &x != y`.
43pub fn ne<T: PartialEq<Other>, Other>(x: T) -> impl Fn(&Other) -> bool {
44    move |y| &x != y
45}
46
47/// A trait that represents a sequence of bytes that can be interpreted as a path.
48/// This is better than `AsRef<Path>` for the following reasons:
49/// - Doesn't actually require [`Path`] or [`OsStr`], thus working in `#[no_std]` environments
50/// - Preserves ownership, being closer to `into Into<Cow>` in this regard.
51///
52/// # Safety
53/// Only implement this for types whose representation is the same as that of [`OsStr`]. Currently
54/// in the standard library those are [`str`], [`OsStr`] & [`Path`] (and all their varieties)
55pub unsafe trait PathLike<'data>: Sized {
56    /// Convert this to a possibly owned sequence of bytes that's guaranteed to uphold the same
57    /// guarantees as an [`OsStr`].
58    fn into_path_bytes(self) -> Cow<'data, [u8]>;
59
60    /// Convert this to a possibly owned [`Path`].
61    #[cfg(feature = "std")]
62    fn into_path(self) -> Cow<'data, Path> {
63        match self.into_path_bytes() {
64            Cow::Borrowed(x) => unsafe { bytes_as_path(x) }.into(),
65            Cow::Owned(x) => {
66                PathBuf::from(unsafe { OsString::from_encoded_bytes_unchecked(x) }).into()
67            }
68        }
69    }
70}
71
72macro_rules! impl_path_like {
73    (<$data:lifetime> for $t:ty: $self:ident => $res:expr) => {
74        unsafe impl<$data> PathLike<$data> for $t {
75            fn into_path_bytes(self) -> Cow<'data, [u8]> {let $self = self; $res.into()}
76        }
77    };
78
79    (owned $owned:ty: $self:ident => $res:expr) => {
80        unsafe impl PathLike<'static> for $owned {
81            fn into_path_bytes(self) -> Cow<'static, [u8]> {let $self = self; $res.into()}
82        }
83    };
84
85    (
86        $(for $owned:ty[$borrowed:ty]:
87            $self:ident => $res:expr;
88            box $bself:ident => $bres:expr;
89            ref $rself:ident => $rres:expr;
90        )+
91    ) => {
92        $(
93            impl_path_like!(owned $owned: $self => $res);
94            impl_path_like!(owned Box<$borrowed>: $bself => $bres);
95            impl_path_like!(<'data> for &'data $owned: $rself => $rres);
96            impl_path_like!(<'data> for &'data $borrowed: $rself => $rres);
97            impl_path_like!(<'data> for &'data Box<$borrowed>: $rself => $rres);
98            impl_path_like!(<'data> for &'data Cow<'data, $borrowed>: $rself => $rres);
99
100            unsafe impl<'data> PathLike<'data> for Cow<'data, $borrowed> {
101                fn into_path_bytes(self) -> Cow<'data, [u8]> {
102                    match self {
103                        Cow::Owned($self) => $res.into(),
104                        Cow::Borrowed($rself) => $rres.into(),
105                    }
106                }
107            }
108        )+
109    };
110}
111
112impl_path_like! {
113    for String[str]:
114        x => x.into_bytes();
115        box x => x.into_boxed_bytes().into_vec();
116        ref x => x.as_bytes();
117}
118
119#[cfg(feature = "std")]
120impl_path_like! {
121    for PathBuf[Path]:
122        x => x.into_os_string().into_encoded_bytes();
123        box x => x.into_path_buf().into_os_string().into_encoded_bytes();
124        ref x => x.as_os_str().as_encoded_bytes();
125    for OsString[OsStr]:
126        x => x.into_encoded_bytes();
127        box x => x.into_os_string().into_encoded_bytes();
128        ref x => x.as_encoded_bytes();
129}
130
131/// Make a parser/pattern that tries any of the provided paths.
132///
133/// If the last expression is prefixed with `else: `, it will be applied as a
134/// [`crate::Parser::or_map_rest`] instead of [`crate::Parser::or`]
135/// Right now it's merely syntactic sugar, but it might bring performance benefits in the future,
136/// if such possibility is found.
137#[macro_export]
138macro_rules! any {
139    ($first:expr, $($rest:expr),* $(, $(else: $map_rest:expr)?)?) => {
140        $first $(.or($rest))* $($(.or_map_rest($map_rest))?)?
141    };
142}
143
144/// Make a [mapping parser](`crate::MappingParser`) that chooses the path based on the current output of the parser.
145/// The usage
146#[macro_export]
147macro_rules! match_out {
148    {
149        $($p:pat => $e:expr),+ $(,)?
150    } => {
151        |i, o| match o {
152            $($p => $e.parse(i)),+
153        }
154    };
155}