#![doc(hidden)]
use std::str::FromStr;
#[doc(hidden)]
#[proc_macro]
pub fn __bitpattern_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
#[allow(clippy::useless_conversion)]
match __bitpattern_testable(input.into()) {
Ok(result) => result.into(),
Err(msg) => format!("compile_error!(\"{}\")", msg).parse().unwrap(),
}
}
#[cfg(not(test))]
use proc_macro::*;
#[cfg(test)]
use proc_macro2::*;
fn __bitpattern_testable(input: TokenStream) -> Result<TokenStream, String> {
let mut stream = flatten_tokenstream(input).into_iter();
let crate_prefix = stream.next().expect(PARSE_FAILED_MSG);
let pattern_literal = match stream.next() {
Some(TokenTree::Literal(literal)) => literal.to_string(),
_ => panic!("{}", PARSE_FAILED_MSG),
};
let declared_type = match stream.next() {
Some(TokenTree::Ident(ident)) => Some(ident.to_string()),
_ => None,
};
let pattern = pattern_literal
.strip_prefix('"')
.ok_or_else(|| LITERAL_WRONG_TYPE_MSG.to_owned())?
.strip_suffix('"')
.ok_or_else(|| LITERAL_WRONG_TYPE_MSG.to_owned())?;
let pattern = pattern
.strip_prefix("0b")
.unwrap_or(pattern)
.replace(&['_', ' '][..], "");
let set = map_bitstring(&pattern, |c| match c {
'1' => Some('1'),
'0' | '.' => Some('0'),
_ => None,
})?;
let cleared = map_bitstring(&pattern, |c| match c {
'0' => Some('1'),
'1' | '.' => Some('0'),
_ => None,
})?;
let size = [8, set.len(), cleared.len()]
.iter()
.max()
.unwrap()
.next_power_of_two();
if size > 128 {
return Err(PATTERN_TOO_LARGE_MSG.to_owned());
}
let type_suffix = match declared_type {
None => format!("u{}", size),
Some(t) => match extract_int_type_size(&t) {
Some(declared_size) if declared_size >= size => t,
_ => {
return Err(type_size_invalid_msg(size));
}
},
};
let output_fragment = format!(
"::BitPattern::<{output_type}>::set_and_cleared_const(0b{}{output_type}, 0b{}{output_type})",
set,
cleared,
output_type = type_suffix,
)
.parse()
.expect(PARSE_FAILED_MSG);
Ok(vec![
crate_prefix,
TokenTree::Group(Group::new(Delimiter::None, output_fragment)),
]
.into_iter()
.collect())
}
fn flatten_tokenstream(input: TokenStream) -> Vec<TokenTree> {
fn flatten_tokenstream_inner(input: TokenStream, mut output: Vec<TokenTree>) -> Vec<TokenTree> {
for token in input {
if let TokenTree::Group(group) = token {
output = flatten_tokenstream_inner(group.stream(), output);
} else {
output.push(token);
}
}
output
}
flatten_tokenstream_inner(input, Vec::new())
}
fn map_bitstring<F>(bitstring: &str, mapper: F) -> Result<String, String>
where
F: Fn(char) -> Option<char>,
{
let result: String = bitstring
.chars()
.map(|c| mapper(c).ok_or_else(|| num_char_invalid_msg(bitstring, c)))
.skip_while(|c| c.as_ref().map(|c| *c == '0').unwrap_or(false))
.collect::<Result<String, String>>()?;
Ok(match result.len() {
0 => "0".to_string(),
_ => result,
})
}
fn extract_int_type_size(type_name: &str) -> Option<usize> {
usize::from_str(type_name.strip_prefix(&['u', 'i'][..])?).ok()
}
const PARSE_FAILED_MSG: &str = "Failed to parse bitpattern! arguments.";
const LITERAL_WRONG_TYPE_MSG: &str =
"The pattern argument for bitpattern! must be a string literal";
const PATTERN_TOO_LARGE_MSG: &str = "A pattern cannot have a size greater than 128 bits.";
fn type_size_invalid_msg(required_size: usize) -> String {
format!("Explicit type given for bitpattern! is invalid or too small to fit the pattern. The pattern requires a type with at least {} bits.", required_size)
}
fn num_char_invalid_msg(pattern: &str, invalid_char: char) -> String {
format!(
"Failed to parse pattern {} as a number due to the invalid character {}. The pattern should contain the digits '0' and '1', with '.' used for a wildcard.",
pattern, invalid_char
)
}
#[cfg(test)]
mod tests {
use std::panic;
use quote::quote;
use crate::__bitpattern_testable;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum MacroStatus {
Panic,
Failure,
Success,
}
use MacroStatus::*;
macro_rules! bp_macro_test {
($expected_status:ident, $($test:tt)+) => {
let macro_result = panic::catch_unwind(|| __bitpattern_testable(quote!($($test)+)));
let actual_status = match macro_result {
Err(_) => Panic,
Ok(Err(_)) => Failure,
Ok(Ok(_)) => Success,
};
assert!(
actual_status == $expected_status,
"__bitpattern_testable test failed: expected status {} for input `{}`. Result was `{:?}` with status {:?}.",
stringify!($expected_status),
stringify!($($test)+),
macro_result,
actual_status,
);
};
}
#[test]
fn type_too_small() {
bp_macro_test!(Failure, test "0b1010101010" u8);
bp_macro_test!(Failure, test "0b1010101010" i8);
bp_macro_test!(Success, test "0b.....0000" u8);
bp_macro_test!(Success, test "0b.....0000" i8);
bp_macro_test!(Failure,
test
"100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
i128
);
bp_macro_test!(Success, test "10101010" u8);
bp_macro_test!(Success, test "10101010" u16);
}
#[test]
fn pattern_too_long() {
bp_macro_test!(Failure,
test
"100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
);
bp_macro_test!(Success,
test
"10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
);
}
#[test]
fn invalid_pattern_chars() {
bp_macro_test!(Failure, test "1234");
bp_macro_test!(Failure, test "/\0..10.3");
bp_macro_test!(Success, test "10010");
}
#[test]
fn wrong_literal_type() {
bp_macro_test!(Failure, test 0b100_01);
bp_macro_test!(Success, test "0b100_01");
bp_macro_test!(Failure, test 0b10..0_01 u8);
bp_macro_test!(Success, test "0b10..0_01" u8);
}
#[test]
fn wrong_input_tokens() {
bp_macro_test!(Panic, "0b101010");
bp_macro_test!(Panic, "0b101010" u8);
bp_macro_test!(Success, test "0b101010" u8);
bp_macro_test!(Panic, test u8 "0b101010");
bp_macro_test!(Panic, test u8);
bp_macro_test!(Panic, u8);
}
}