#![macro_use]
#![cfg_attr(not(feature = "read"), allow(unused_macros))]
#[cfg(feature = "names")]
use core::ops::{BitAnd, Not};
pub trait Wrap {
type Inner;
fn from_inner(inner: Self::Inner) -> Self;
fn into_inner(self) -> Self::Inner;
}
macro_rules! wrap {
($outer:ident, $inner:ident) => {
impl crate::constants::Wrap for $outer {
type Inner = $inner;
fn into_inner(self) -> $inner {
self.0
}
fn from_inner(inner: $inner) -> Self {
Self(inner)
}
}
};
($primitive:ident) => {
impl crate::constants::Wrap for $primitive {
type Inner = $primitive;
fn into_inner(self) -> $primitive {
self
}
fn from_inner(inner: $primitive) -> Self {
inner
}
}
};
}
wrap!(u8);
wrap!(u16);
wrap!(u32);
wrap!(u64);
wrap!(usize);
wrap!(i16);
wrap!(i32);
wrap!(i64);
#[cfg(feature = "names")]
#[derive(Debug, Default)]
pub struct ConstantNames<T: Wrap + 'static> {
pub(crate) next: Option<&'static ConstantNames<T>>,
pub(crate) entries: &'static [(T::Inner, &'static str)],
}
#[cfg(feature = "names")]
impl<T: Wrap> ConstantNames<T> {
pub fn name(&self, value: T) -> Option<&'static str>
where
T::Inner: PartialEq,
{
let value = value.into_inner();
let mut next = Some(self);
while let Some(names) = next {
for entry in names.entries {
if entry.0 == value {
return Some(entry.1);
}
}
next = names.next;
}
None
}
}
#[cfg(feature = "names")]
pub(crate) struct FlagGroup<T: Wrap> {
pub(crate) mask: T::Inner,
pub(crate) name: fn(T) -> Option<&'static str>,
}
#[cfg(feature = "names")]
impl<T: Wrap> core::fmt::Debug for FlagGroup<T>
where
T::Inner: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("FlagGroup")
.field("mask", &self.mask)
.field("name", &self.name)
.finish()
}
}
#[cfg(feature = "names")]
#[derive(Default)]
pub struct FlagNames<T: Wrap + 'static> {
pub(crate) next: Option<&'static FlagNames<T>>,
pub(crate) bits: &'static [(T::Inner, &'static str)],
pub(crate) groups: &'static [FlagGroup<T>],
}
#[cfg(feature = "names")]
impl<T: Wrap> core::fmt::Debug for FlagNames<T>
where
T::Inner: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("FlagNames")
.field("next", &self.next)
.field("bits", &self.bits)
.field("groups", &self.groups)
.finish()
}
}
#[cfg(feature = "names")]
impl<T: Wrap> FlagNames<T>
where
T::Inner: Copy + PartialEq + BitAnd<Output = T::Inner> + Not<Output = T::Inner>,
{
pub fn try_names<F, E>(&self, value: T, mut f: F) -> Result<T::Inner, E>
where
F: FnMut(T::Inner, &'static str) -> Result<(), E>,
{
let mut unmatched = value.into_inner();
let mut next = Some(self);
while let Some(names) = next {
for group in names.groups {
let masked = unmatched & group.mask;
if let Some(name) = (group.name)(T::from_inner(masked)) {
f(masked, name)?;
unmatched = unmatched & !group.mask;
}
}
next = names.next;
}
self.try_bit_names(T::from_inner(unmatched), f)
}
pub fn try_bit_names<F, E>(&self, value: T, mut f: F) -> Result<T::Inner, E>
where
F: FnMut(T::Inner, &'static str) -> Result<(), E>,
{
let mut unmatched = value.into_inner();
let mut next = Some(self);
while let Some(names) = next {
for &(bit, name) in names.bits {
if unmatched & bit == bit {
f(bit, name)?;
unmatched = unmatched & !bit;
}
}
next = names.next;
}
Ok(unmatched)
}
pub fn names<F>(&self, value: T, mut f: F) -> T::Inner
where
F: FnMut(T::Inner, &'static str),
{
unwrap_infallible(self.try_names(value, |v, n| {
f(v, n);
Ok(())
}))
}
pub fn bit_names<F>(&self, value: T, mut f: F) -> T::Inner
where
F: FnMut(T::Inner, &'static str),
{
unwrap_infallible(self.try_bit_names(value, |v, n| {
f(v, n);
Ok(())
}))
}
pub fn name(&self, value: T) -> Option<(T, &'static str)> {
self.try_names(value, |v, n| Err((T::from_inner(v), n)))
.err()
}
}
#[cfg(feature = "names")]
fn unwrap_infallible<T>(r: Result<T, core::convert::Infallible>) -> T {
match r {
Ok(v) => v,
Err(e) => match e {},
}
}
#[cfg_attr(not(feature = "read"), allow(dead_code))]
#[cfg(feature = "names")]
pub(crate) fn flag_debug<T>(
value: T,
f: &mut core::fmt::Formatter<'_>,
names: &FlagNames<T>,
) -> core::fmt::Result
where
T: Wrap,
T::Inner: core::fmt::LowerHex
+ Default
+ Copy
+ PartialEq
+ BitAnd<Output = T::Inner>
+ Not<Output = T::Inner>,
{
let mut first = true;
let unmatched = names.try_names(value, |_, name| {
if !first {
f.write_str(" | ")?;
}
first = false;
f.write_str(name)
})?;
if unmatched != T::Inner::default() {
if !first {
f.write_str(" | ")?;
}
write!(f, "0x{:x}", unmatched)?;
} else if first {
write!(f, "0")?;
}
Ok(())
}
macro_rules! names {
($(#[$meta:meta])* struct $struct:ident$(($parent:ident))?;
$(
$(#[$varname_meta:meta])*
$kind:ident $method:ident
$(= $vis:vis $varname:ident)?
$(: $outer:ident$(($inner:ident))? = $body:tt)?;
)*
) => {
#[cfg(feature = "names")]
$(#[$meta])*
#[derive(Debug, Clone, Copy)]
struct $struct;
#[cfg(feature = "names")]
impl $struct {
const fn names_value() -> Names {
#[allow(clippy::needless_update)]
Names {
$($method: names!(@ref $method ($($varname)?) ($($body)?)),)*
$(..$parent::names_value())?
}
}
#[allow(unused)]
const fn names() -> &'static Names {
static C: Names = $struct::names_value();
&C
}
names! { @impl_methods ($($parent)?) $(
$kind $method ($($varname)?) ($($outer $($inner)?)?) ($($body)?)
)* }
}
names! { @statics ($($parent)?) $(
$kind $method ($(#[$varname_meta])*) ($($vis $varname)?) ($($outer $($inner)?)?) ($($body)?)
)* }
$(names! { @consts $kind ($($outer $($inner)?)?) ($($body)?) })*
};
(@impl_methods $parent:tt $($kind:ident $method:ident $varname:tt $type:tt $body:tt)*) => {
$(names! { @impl_method $kind $method $varname $type (names!(@impl_next $method $parent)) $body })*
};
(@impl_method $kind:ident $method:ident ($varname:ident) $type:tt $next:tt $body:tt) => {};
(@impl_method consts $method:ident () $type:tt $next:tt ($body:tt)) => {
const fn $method() -> &'static crate::constants::ConstantNames<newtype!(@type $type)> {
constant_names! { @static () NAMES $type $next $body }
&NAMES
}
};
(@impl_method flags $method:ident () $type:tt $next:tt ($body:tt)) => {
const fn $method() -> &'static crate::constants::FlagNames<newtype!(@type $type)> {
flag_names! { @static () NAMES $type $next $body }
&NAMES
}
};
(@statics $parent:tt $($kind:ident $method:ident $meta:tt $varname:tt $type:tt $body:tt)*) => {
$(names! { @static $kind $meta $varname $type (names!(@impl_next $method $parent)) $body })*
};
(@static $kind:ident () () $type:tt $next:tt $body:tt) => {};
(@static $kind:ident () $varname:tt $type:tt $next:tt ()) => {};
(@static consts $meta:tt ($vis:vis $varname:ident) $type:tt $next:tt ($body:tt)) => {
constant_names! { @static $meta $vis $varname $type $next $body }
};
(@static flags $meta:tt ($vis:vis $varname:ident) $type:tt $next:tt ($body:tt)) => {
flag_names! { @static $meta $vis $varname $type $next $body }
};
(@impl_next $method:ident ()) => { None };
(@impl_next $method:ident ($parent:ident)) => { Some($parent::names_value().$method) };
(@ref $method:ident ($varname:ident) $body:tt) => { &$varname };
(@ref $method:ident () ($body:tt)) => { Self::$method() };
(@ref $method:ident () ()) => {
compile_error!(concat!(
"`names!`: `", stringify!($method),
"` must specify either a body `= { ... }` or a reference `= NAME`"
))
};
(@consts $kind:tt $type:tt ()) => {};
(@consts consts $type:tt ($body:tt)) => { constant_names! { @consts $type $body } };
(@consts flags $type:tt ($body:tt)) => { flag_names! { @consts $type $body } };
}
macro_rules! constant_names {
($(#[$meta:meta])* $vis:vis $varname:ident: $outer:ident$(($inner:ident))? = $($next:ident +)? { $($body:tt)* }) => {
constant_names! { @static ($(#[$meta])*) $vis $varname ($outer $($inner)?) (constant_names!(@next $($next)?)) { $($body)* } }
constant_names! { @consts ($outer $($inner)?) { $($body)* } }
};
(@next) => { None };
(@next $next:ident) => { Some(&$next) };
(@static ($(#[$meta:meta])*) $vis:vis $varname:ident $type:tt ($next:expr) {
$($(#[$entry_meta:meta])* $name:ident = $value:expr),* $(,)?
}) => {
$(#[$meta])*
#[cfg(feature = "names")]
$vis static $varname: crate::constants::ConstantNames<newtype!(@type $type)> = crate::constants::ConstantNames {
next: $next,
entries: &[$(($value, stringify!($name)),)*],
};
};
(@consts $type:tt {
$($(#[$meta:meta])* $name:ident = $value:expr),* $(,)?
}) => {
$($(#[$meta])* pub const $name: newtype!(@type $type) = newtype!(@value $type $value);)*
};
}
macro_rules! flag_names {
($(#[$meta:meta])* $vis:vis $varname:ident: $outer:ident$(($inner:ident))? = $($next:ident +)? { $($body:tt)* }) => {
flag_names! { @static ($(#[$meta])*) $vis $varname ($outer $($inner)?) (flag_names!(@next $($next)?)) { $($body)* } }
flag_names! { @consts ($outer $($inner)?) { $($body)* } }
};
(@next) => { None };
(@next $next:ident) => { Some(&$next) };
(@static ($(#[$meta:meta])*) $vis:vis $varname:ident $type:tt ($next:expr) { $($body:tt)* }) => {
$(#[$meta])*
#[cfg(feature = "names")]
$vis static $varname: crate::constants::FlagNames<newtype!(@type $type)> = flag_names! {
@build_static ($type $next) [] [] $($body)*
};
};
(@consts $type:tt { $($body:tt)* }) => {
flag_names! { @build_consts $type $($body)* }
};
(@build_static ($type:tt $next:expr) [$($bits:tt)*] [$($groups:tt)*]) => {
crate::constants::FlagNames {
next: $next,
bits: &[$($bits)*],
groups: &[$($groups)*],
}
};
(@build_static ($type:tt $next:tt) [$($bits:tt)*] [$($groups:tt)*]
$(#[$_meta:meta])* $name:ident = $value:expr,
$($rest:tt)*
) => {
flag_names! {
@build_static ($type $next)
[$($bits)* ($value, stringify!($name)),]
[$($groups)*]
$($rest)*
}
};
(@build_static ($type:tt $next:tt) [$($bits:tt)*] [$($groups:tt)*]
$(#[$_meta:meta])* $name:ident = $value:expr => $entry:expr,
$($rest:tt)*
) => {
flag_names! {
@build_static ($type $next)
[$($bits)*]
[$($groups)* (flag_names!(@flaggroup $value => $entry)),]
$($rest)*
}
};
(@build_static ($type:tt $next:tt) [$($bits:tt)*] [$($groups:tt)*]
$(#[$_meta:meta])* _ = $value:expr => $entry:expr,
$($rest:tt)*
) => {
flag_names! {
@build_static ($type $next)
[$($bits)*]
[$($groups)* (flag_names!(@flaggroup $value => $entry)),]
$($rest)*
}
};
(@flaggroup $value:expr => $entry:expr) => {
crate::constants::FlagGroup {
mask: $value,
name: |v| $entry.name(v.into()),
}
};
(@build_consts $type:tt) => {};
(@build_consts $type:tt
$(#[$meta:meta])* $name:ident = $value:expr,
$($rest:tt)*
) => {
$(#[$meta])* pub const $name: newtype!(@type $type) = newtype!(@value $type $value);
flag_names! { @build_consts $type $($rest)* }
};
(@build_consts $type:tt
$(#[$meta:meta])* $name:ident = $value:expr => $entry:expr,
$($rest:tt)*
) => {
$(#[$meta])* pub const $name: newtype!(@inner $type) = $value;
flag_names! { @build_consts $type $($rest)* }
};
(@build_consts $type:tt
_ = $value:expr => $entry:expr,
$($rest:tt)*
) => {
flag_names! { @build_consts $type $($rest)* }
};
}
macro_rules! newtype {
($(#[$meta:meta])* struct $outer:ident($inner:ident);) => {
$(#[$meta])*
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $outer(pub $inner);
impl core::fmt::LowerHex for $outer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::LowerHex::fmt(&self.0, f)
}
}
impl core::fmt::UpperHex for $outer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::UpperHex::fmt(&self.0, f)
}
}
wrap!($outer, $inner);
};
(@type ($outer:ident $inner:ident)) => { $outer };
(@type ($type:ident)) => { $type };
(@inner ($outer:ident $inner:ident)) => { $inner };
(@inner ($type:ident)) => { $type };
(@value ($outer:ident $inner:ident) $value:expr) => { $outer($value) };
(@value ($type:ident) $value:expr) => { $value };
}
macro_rules! newtype_consts {
($type:ident = {
$($(#[$meta:meta])* $name:ident = $value:expr),* $(,)?
}) => {
$($(#[$meta])* pub const $name: $type = $type($value);)*
};
}
macro_rules! newtype_constant_names {
($outer:ident($inner:ident) = {}) => {
newtype_constant_names!(@impl $outer($inner));
impl core::fmt::Debug for $outer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
};
($varname:ident: $outer:ident($inner:ident) = $($next:ident +)? { $($body:tt)+ }) => {
constant_names!($varname: $outer($inner) = $($next +)? { $($body)+ });
newtype_constant_names!(@impl $outer($inner));
#[cfg(feature = "names")]
impl $outer {
pub const NAMES: &'static crate::constants::ConstantNames<$outer> = &$varname;
pub fn name(self) -> Option<&'static str> {
$varname.name(self)
}
}
impl core::fmt::Debug for $outer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[cfg(feature = "names")]
if let Some(name) = $varname.name(*self) {
return f.write_str(name);
}
self.0.fmt(f)
}
}
};
(@impl $outer:ident($inner:ident)) => {
impl core::fmt::Display for $outer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}
};
}
macro_rules! newtype_flag_names {
($outer:ident($inner:ident) = {}) => {
newtype_flag_names!(@impl $outer($inner));
impl core::fmt::Debug for $outer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.0 == 0 {
write!(f, "0")
} else {
write!(f, "0x{:x}", self.0)
}
}
}
};
($varname:ident: $outer:ident($inner:ident) = $($next:ident +)? { $($body:tt)+ }) => {
flag_names!($varname: $outer($inner) = $($next +)? { $($body)+ });
newtype_flag_names!(@impl $outer($inner));
#[cfg(feature = "names")]
impl $outer {
pub const NAMES: &'static crate::constants::FlagNames<$outer> = &$varname;
}
impl core::fmt::Debug for $outer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[cfg(feature = "names")]
if true {
return crate::constants::flag_debug(*self, f, &$varname);
}
if self.0 == 0 {
write!(f, "0")
} else {
write!(f, "0x{:x}", self.0)
}
}
}
};
(@impl $outer:ident($inner:ident)) => {
impl core::fmt::Display for $outer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}
impl core::ops::BitAnd for $outer {
type Output = $outer;
fn bitand(self, rhs: $outer) -> $outer {
$outer(self.0 & rhs.0)
}
}
impl core::ops::BitAndAssign for $outer {
fn bitand_assign(&mut self, rhs: $outer) {
self.0 &= rhs.0;
}
}
impl core::ops::BitOr for $outer {
type Output = $outer;
fn bitor(self, rhs: $outer) -> $outer {
$outer(self.0 | rhs.0)
}
}
impl core::ops::BitOrAssign for $outer {
fn bitor_assign(&mut self, rhs: $outer) {
self.0 |= rhs.0;
}
}
impl core::ops::BitXor for $outer {
type Output = $outer;
fn bitxor(self, rhs: $outer) -> $outer {
$outer(self.0 ^ rhs.0)
}
}
impl core::ops::BitXorAssign for $outer {
fn bitxor_assign(&mut self, rhs: $outer) {
self.0 ^= rhs.0;
}
}
impl $outer {
pub fn contains(self, other: $outer) -> bool {
self.0 & other.0 == other.0
}
pub fn intersects(self, other: $outer) -> bool {
self.0 & other.0 != 0
}
pub const fn with(self, other: $outer) -> Self {
Self(self.0 | other.0)
}
pub const fn without(self, other: $outer) -> Self {
Self(self.0 & !other.0)
}
pub fn insert(&mut self, other: $outer) {
self.0 |= other.0;
}
pub fn remove(&mut self, other: $outer) {
self.0 &= !other.0;
}
}
};
}
#[cfg(test)]
mod tests {
#[cfg(feature = "names")]
use super::{ConstantNames, FlagNames};
#[test]
#[allow(unused)]
fn macros() {
newtype!(
struct Foo(u32);
);
newtype_constant_names!(FOO: Foo(u32) = {
FOO_A = 0,
FOO_B = 1,
});
newtype!(
struct Bar(u32);
);
newtype_flag_names!(BAR: Bar(u32) = {
BIT_0 = 0x1,
BIT_1 = 0x2,
BIT_3 = 0x4,
BAR_MASK = 0xf0 => BAR_FIELD,
_ = 0xff00 => BAZ_FIELD,
});
constant_names!(BAR_FIELD_BASE: Bar(u32) = {
BAR_A = 0x10,
BAR_B = 0x20,
BAR_C = 0x30,
});
constant_names!(BAR_FIELD: Bar(u32) = BAR_FIELD_BASE + {
BAR_D = 0x40,
});
newtype!(
struct Baz(u8);
);
newtype_constant_names!(BAZ_FIELD: Baz(u8) = {
BAZ_A = 0x1,
BAZ_B = 0x2,
});
impl From<Baz> for Bar {
fn from(value: Baz) -> Self {
Bar(u32::from(value.0) << 8)
}
}
impl From<Bar> for Baz {
fn from(value: Bar) -> Self {
Baz((value.0 >> 8) as u8)
}
}
#[cfg(feature = "names")]
struct Names {
foo: &'static ConstantNames<Foo>,
bar: &'static FlagNames<Bar>,
quux: &'static ConstantNames<u64>,
}
names! {
struct Base;
consts foo = FOO;
flags bar = BAR;
consts quux: u64 = {
QUUX_A = 1,
QUUX_B = 1,
};
}
names! {
struct Arch(Base);
consts foo = FOO_ARCH;
flags bar = BAR_ARCH: Bar(u32) = {
BIT_4 = 0x8,
};
}
constant_names!(FOO_ARCH: Foo(u32) = FOO + {
FOO_ARCH_A = 100,
});
#[cfg(feature = "names")]
{
let names = Arch::names();
assert_eq!(names.foo.name(FOO_A), Some("FOO_A"));
assert_eq!(names.foo.name(FOO_ARCH_A), Some("FOO_ARCH_A"));
assert_eq!(names.bar.name(BIT_1), Some((BIT_1, "BIT_1")));
assert_eq!(names.bar.name(BIT_4), Some((BIT_4, "BIT_4")));
assert_eq!(names.bar.name(BIT_1 | BIT_4), Some((BIT_4, "BIT_4")));
assert_eq!(names.bar.name(BAR_B), Some((BAR_B, "BAR_B")));
assert_eq!(names.bar.name(BAR_D), Some((BAR_D, "BAR_D")));
assert_eq!(names.bar.name(BAZ_B.into()), Some((BAZ_B.into(), "BAZ_B")));
}
}
}