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

Viewed 294

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
2 Answers

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.

This is the wrapper type I made, utilizing the implementation outlined by Peter Hall with the underscore suggestion from E_net4:

use quote::quote; // 1.0.6

fn main() {
    let value = AsBits(0b0101_0101_u8);

    let code = format!("{}", quote! { #value });

    assert_eq!("0b0101_0101_u8", code);
}
use proc_macro2::TokenStream; // 1.0.17
use quote::ToTokens; // 1.0.6
use std::str::FromStr;

struct AsBits<T>(T);

impl ToTokens for AsBits<u8> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let b = format!(
            "0b{:04b}_{:04b}_u8",
            (self.0 & 0xF0) >> 4,
            (self.0 & 0x0F) >> 0,
        );

        tokens.extend(TokenStream::from_str(&b).unwrap());
    }
}
Related