Search code examples
rustrust-macrosrust-crates

How can I emit an integer literal with a non-decimal base using the quote crate?


I'm using quote to generate code to decode assembly operations. The instruction manual for my chip uses binary values to describe the operations, so I'd like my generated code to also express literals as binary values to make it easier for me to spot-check correctness.

I cannot find a way to specify this. proc_macro2::Literal offers a number of ways of controlling the suffix of a literal (u8, i32, etc.), but I don't see anything to control the base of the literal.

My ideal format would be in base 2, use an underscore every four bits, and end in the appropriate suffix, but only the base is required.

use quote::quote; // 1.0.6

fn main() {
    let value = 0b0101_0101_u8;

    let code = format!("{}", quote! { #value });
    
    assert_eq!("0b0101_0101_u8", code);
}
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `"0b0101_0101_u8"`,
 right: `"85u8"`', src/main.rs:8:5

Solution

  • You can format the number as a string using format! and then construct a TokenStream from that, which you can then use in quote!:

    use proc_macro2::TokenStream;
    use quote::quote; // 1.0.6
    use std::str::FromStr;
    
    fn main() {
        let value = 0b0101_0101_u8;
        let value_formatted = TokenStream::from_str(&format!("0b{:08b}_u8", value)).unwrap();
        let code = format!(
            "{}",
            quote! {
                #value_formatted
            }
        );
    
        assert_eq!("0b01010101_u8", code);
    }
    

    The format! macro doesn't support underscores, but this could be easily extended to insert them with a custom formatter.