1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
//! [`std::concat!`] with support for `const` variables and expressions.
//!
//! Works on stable Rust ✨.
//!
//! # 🚀 Getting started
//!
//! Add `constcat` to your Cargo manifest.
//!
//! ```sh
//! cargo add constcat
//! ```
//!
//! Import the macro using the following.
//!
//! ```
//! use constcat::concat;
//! ```
//!
//! # 🤸 Usage
//!
//! [`concat!`] works exactly like [`std::concat!`] except you can
//! now pass variables and constant expressions. For example:
//!
//! ```
//! # use constcat::concat;
//! #
//! const CRATE_NAME: &str = env!("CARGO_PKG_NAME");
//! const CRATE_VERSION: &str = env!("CARGO_PKG_VERSION");
//! const fn tada() -> &'static str { "🎉" }
//! const VERSION: &str = concat!(CRATE_NAME, " ", CRATE_VERSION, tada());
//! #
//! # assert_eq!(
//! # VERSION,
//! # std::concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), "🎉"),
//! # );
//! ```
//!
//! [`concat_bytes!`] works similarly except it yields a static byte slice. For
//! example:
//!
//! ```
//! # use constcat::concat_bytes;
//! #
//! const VERSION: u32 = 1;
//! const fn entries() -> &'static [u8] { b"example" }
//! const HEADER: &[u8] = concat_bytes!(&VERSION.to_le_bytes(), entries());
//! ```
//!
//! [`std::concat!`]: core::concat
//! [`std::concat_bytes!`]: core::concat_bytes
#![no_std]
#[doc(hidden)]
pub use core;
////////////////////////////////////////////////////////////////////////////////
// concat!
////////////////////////////////////////////////////////////////////////////////
/// Concatenate `const` [`&str`][str] expressions and literals into a static
/// string slice.
///
/// This macro takes any number of comma-separated literals or constant
/// expressions and yields an expression of type [`&'static str`][str] which
/// represents all of the literals and expressions concatenated left-to-right.
/// Integer, floating point, and boolean literals are stringified in order to be
/// concatenated. Finally, each expression is converted to a byte slice and
/// concatenated using [`concat_slices!`].
///
/// See the [crate documentation][crate] for examples.
#[macro_export]
macro_rules! concat {
($($e:expr),* $(,)?) => {{
$crate::_concat!($($e),*)
}}
}
#[doc(hidden)]
#[macro_export]
macro_rules! _concat {
() => { "" };
($($maybe:expr),+) => {{
$crate::_concat!(@impl $($crate::_maybe_std_concat!($maybe)),+)
}};
(@impl $($s:expr),+) => {{
$(
const _: &str = $s; // require str constants
)*
let slice: &[u8] = $crate::concat_slices!([u8]: $($s.as_bytes()),+);
// SAFETY: The original constants were asserted to be &str's
// so the resultant bytes are valid UTF-8.
unsafe { $crate::core::str::from_utf8_unchecked(slice) }
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! _maybe_std_concat {
($e:literal) => {
$crate::core::concat!($e)
};
($e:expr) => {
$e
};
}
////////////////////////////////////////////////////////////////////////////////
// concat_bytes!
////////////////////////////////////////////////////////////////////////////////
/// Concatenate `const` [`&[u8]`][slice] expressions and literals into a static
/// byte slice.
///
/// This macro takes any number of comma-separated literals or constant
/// expressions and yields an expression of type [`&'static [u8]`][slice] which
/// represents all of the literals and expressions concatenated left-to-right.
/// Literals are converted using [`core::concat_bytes!`] and then each
/// expression is concatenated using [`concat_slices!`].
///
/// See the [crate documentation][crate] for examples.
///
/// # Stability note
///
/// 🔬 This macro uses a nightly-only experimental API, [`core::concat_bytes`],
/// for processing byte literals, until it is stabilized you will need to add
/// the following to the root of your crate.
///
/// ```
/// #![feature(concat_bytes)]
/// ```
///
/// Unlike the standard library macro this macro does not accept byte array
/// literals directly like `[b'A', 32, b'B']` instead you have to pass a slice
/// like `&[b'A', 32, b'B']`.
#[macro_export]
#[cfg(feature = "bytes")]
macro_rules! concat_bytes {
($($e:expr),* $(,)?) => {{
$crate::_concat_bytes!($($e),*)
}}
}
#[doc(hidden)]
#[macro_export]
#[cfg(feature = "bytes")]
macro_rules! _concat_bytes {
() => { b"" };
($($maybe:expr),+) => {{
$crate::_concat_bytes!(@impl $($crate::_maybe_std_concat_bytes!($maybe)),+)
}};
(@impl $($s:expr),+) => {{
$crate::concat_slices!([u8]: $($s),+)
}};
}
#[doc(hidden)]
#[macro_export]
#[cfg(feature = "bytes")]
macro_rules! _maybe_std_concat_bytes {
($e:literal) => {
$crate::core::concat_bytes!($e)
};
($e:expr) => {
$e
};
}
////////////////////////////////////////////////////////////////////////////////
// concat_slices!
////////////////////////////////////////////////////////////////////////////////
/// Concatenate `const` [`&[T]`][slice] expressions into a static slice.
///
/// - This macro takes any number of comma-separated [`&[T]`][slice] expressions
/// and yields an expression of type [`&'static [T]`][slice] which represents
/// all of the expressions concatenated left-to-right.
/// - The macro requires that type of slice be specified, e.g. `[usize]` or
/// `[u8]` before the comma separate expressions.
/// - You can optionally provide an initializer for non-integer types, e.g.
/// `[0.0; f32]` for floating point numbers, `[false; bool]` for `bool`s, or
/// `['\x00'; char]` for `char`s. This also works for custom types as long as
/// the type and initializer expression is able to be specified in an array
/// initializer expression.
///
/// # Examples
///
/// Basic usage with integers:
///
/// ```
/// # use constcat::concat_slices;
/// #
/// const fn more() -> &'static [i32] { &[4, 5, 6] }
/// const EXAMPLE: &[i32] = concat_slices!([i32]: &[1, 2, 3], more());
/// assert_eq!(EXAMPLE, [1, 2, 3, 4, 5, 6])
/// ```
///
/// With a constant initializer:
///
/// ```
/// # use constcat::concat_slices;
/// #
/// const fn more() -> &'static [f32] { &[4.0, 5.0, 6.0] }
/// const EXAMPLE: &[f32] = concat_slices!([0.0; f32]: &[1.0, 2.0, 3.0], more());
/// assert_eq!(EXAMPLE, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
/// ```
#[macro_export]
macro_rules! concat_slices {
([$init:expr; $T:ty]: $($s:expr),+ $(,)?) => {{
$(
const _: &[$T] = $s; // require constants
)*
const LEN: usize = $( $s.len() + )* 0;
const ARR: [$T; LEN] = {
let mut arr: [$T; LEN] = [$init; LEN];
let mut base: usize = 0;
$({
let mut i = 0;
while i < $s.len() {
arr[base + i] = $s[i];
i += 1;
}
base += $s.len();
})*
if base != LEN { panic!("invalid length"); }
arr
};
&ARR
}};
([char]: $($s:expr),+ $(,)?) => {
$crate::concat_slices!(['\x00'; char]: $($s),+)
};
([f32]: $($s:expr),+ $(,)?) => {
$crate::concat_slices!([0.0; f32]: $($s),+)
};
([f64]: $($s:expr),+ $(,)?) => {
$crate::concat_slices!([0.0; f64]: $($s),+)
};
([$T:ty]: $($s:expr),+ $(,)?) => {
$crate::concat_slices!([0; $T]: $($s),+)
};
}