hex_literal/
lib.rs

1//! This crate provides the `hex!` macro for converting hexadecimal string literals
2//! to a byte array at compile time.
3//!
4//! It accepts the following characters in the input string:
5//!
6//! - `'0'...'9'`, `'a'...'f'`, `'A'...'F'` — hex characters which will be used
7//!     in construction of the output byte array
8//! - `' '`, `'\r'`, `'\n'`, `'\t'` — formatting characters which will be
9//!     ignored
10//!
11//! Additionally it accepts line (`//`) and block (`/* .. */`) comments. Characters
12//! inside of those are ignored.
13//!
14//! # Examples
15//! ```
16//! # #[macro_use] extern crate hex_literal;
17//! // the macro can be used in const context
18//! const DATA: [u8; 4] = hex!("01020304");
19//! # fn main() {
20//! assert_eq!(DATA, [1, 2, 3, 4]);
21//!
22//! // it understands both upper and lower hex values
23//! assert_eq!(hex!("a1 b2 c3 d4"), [0xA1, 0xB2, 0xC3, 0xD4]);
24//! assert_eq!(hex!("E5 E6 90 92"), [0xE5, 0xE6, 0x90, 0x92]);
25//! assert_eq!(hex!("0a0B 0C0d"), [10, 11, 12, 13]);
26//! let bytes = hex!("
27//!     00010203 04050607
28//!     08090a0b 0c0d0e0f
29//! ");
30//! assert_eq!(bytes, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
31//!
32//! // it's possible to use several literals (results will be concatenated)
33//! let bytes2 = hex!(
34//!     "00010203 04050607" // first half
35//!     "08090a0b 0c0d0e0f" // second hald
36//! );
37//! assert_eq!(bytes2, bytes);
38//!
39//! // comments can be also included inside literals
40//! assert_eq!(hex!("0a0B // 0c0d line comments"), [10, 11]);
41//! assert_eq!(hex!("0a0B // line comments
42//!                  0c0d"), [10, 11, 12, 13]);
43//! assert_eq!(hex!("0a0B /* block comments */ 0c0d"), [10, 11, 12, 13]);
44//! assert_eq!(hex!("0a0B /* multi-line
45//!                          block comments
46//!                       */ 0c0d"), [10, 11, 12, 13]);
47//! # }
48//! ```
49#![doc(
50    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
51    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
52    html_root_url = "https://docs.rs/hex-literal/0.3.4"
53)]
54
55mod comments;
56extern crate proc_macro;
57
58use std::vec::IntoIter;
59
60use proc_macro::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree};
61
62use crate::comments::{Exclude, ExcludingComments};
63
64/// Strips any outer `Delimiter::None` groups from the input,
65/// returning a `TokenStream` consisting of the innermost
66/// non-empty-group `TokenTree`.
67/// This is used to handle a proc macro being invoked
68/// by a `macro_rules!` expansion.
69/// See https://github.com/rust-lang/rust/issues/72545 for background
70fn ignore_groups(mut input: TokenStream) -> TokenStream {
71    let mut tokens = input.clone().into_iter();
72    loop {
73        if let Some(TokenTree::Group(group)) = tokens.next() {
74            if group.delimiter() == Delimiter::None {
75                input = group.stream();
76                continue;
77            }
78        }
79        return input;
80    }
81}
82
83struct TokenTreeIter {
84    buf: ExcludingComments<IntoIter<u8>>,
85    is_punct: bool,
86}
87
88impl TokenTreeIter {
89    /// Constructs a new `TokenTreeIter` from a given `proc_macro::Literal`.
90    ///
91    /// # Panics
92    /// This panics if the given `Literal` is not a string literal.
93    fn new(input: Literal) -> Self {
94        let mut buf: Vec<u8> = input.to_string().into();
95
96        match buf.as_slice() {
97            [b'"', .., b'"'] => (),
98            _ => panic!("expected string literal, got `{}`", input),
99        };
100        buf.pop();
101        let mut iter = buf.into_iter().exclude_comments();
102        iter.next();
103        Self {
104            buf: iter,
105            is_punct: false,
106        }
107    }
108
109    /// Parses a single hex character (a-f/A-F/0-9) as a `u8` from the `TokenTreeIter`'s
110    /// internal buffer, ignoring whitespace.
111    ///
112    /// # Panics
113    /// This panics if a non-hex, non-whitespace character is encountered.
114    fn next_hex_val(&mut self) -> Option<u8> {
115        loop {
116            let v = self.buf.next()?;
117            let n = match v {
118                b'0'..=b'9' => v - 48,
119                b'A'..=b'F' => v - 55,
120                b'a'..=b'f' => v - 87,
121                b' ' | b'\r' | b'\n' | b'\t' => continue,
122                0..=127 => panic!("encountered invalid character: `{}`", v as char),
123                _ => panic!("encountered invalid non-ASCII character"),
124            };
125            return Some(n);
126        }
127    }
128}
129
130impl Iterator for TokenTreeIter {
131    type Item = TokenTree;
132
133    /// Produces hex values (as `u8` literals) parsed from the `TokenTreeIter`'s
134    /// internal buffer, alternating with commas to separate the elements of the
135    /// generated array of bytes.
136    ///
137    /// # Panics
138    /// This panics if the internal buffer contains an odd number of hex
139    /// characters.
140    fn next(&mut self) -> Option<TokenTree> {
141        let v = if self.is_punct {
142            TokenTree::Punct(Punct::new(',', Spacing::Alone))
143        } else {
144            let p1 = self.next_hex_val()?;
145            let p2 = match self.next_hex_val() {
146                Some(v) => v,
147                None => panic!("expected even number of hex characters"),
148            };
149            let val = (p1 << 4) + p2;
150            TokenTree::Literal(Literal::u8_suffixed(val))
151        };
152        self.is_punct = !self.is_punct;
153        Some(v)
154    }
155}
156
157/// Macro for converting sequence of string literals containing hex-encoded data
158/// into an array of bytes.
159#[proc_macro]
160pub fn hex(input: TokenStream) -> TokenStream {
161    let mut out_ts = TokenStream::new();
162    for tt in ignore_groups(input) {
163        let iter = match tt {
164            TokenTree::Literal(literal) => TokenTreeIter::new(literal),
165            unexpected => panic!("expected string literal, got `{}`", unexpected),
166        };
167        out_ts.extend(iter);
168    }
169    TokenStream::from(TokenTree::Group(Group::new(Delimiter::Bracket, out_ts)))
170}