1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! This crate provides `hex!` macro for converting hexadecimal string literal
//! to byte array at compile time.
//!
//! It accepts the following characters in the input string:
//!
//! - `'0'...'9'`, `'a'...'f'`, `'A'...'F'` — hex characters which will be used
//!     in construction of the output byte array
//! - `' '`, `'\r'`, `'\n'`, `'\t'` — formatting characters which will be
//!     ignored
//!
//! # Examples
//! ```
//! # #[macro_use] extern crate hex_literal;
//! const DATA: [u8; 4] = hex!("01020304");
//!
//! # fn main() {
//! assert_eq!(DATA, [1, 2, 3, 4]);
//! assert_eq!(hex!("a1 b2 c3 d4"), [0xA1, 0xB2, 0xC3, 0xD4]);
//! assert_eq!(hex!("E5 E6 90 92"), [0xE5, 0xE6, 0x90, 0x92]);
//! assert_eq!(hex!("0a0B 0C0d"), [10, 11, 12, 13]);
//! let bytes = hex!("
//!     00010203 04050607
//!     08090a0b 0c0d0e0f
//! ");
//! assert_eq!(bytes, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
//! assert_eq!(hex!("0a0B // 0c0d line comments"), [10, 11]);
//! assert_eq!(hex!("0a0B // line comments
//!                  0c0d"), [10, 11, 12, 13]);
//! assert_eq!(hex!("0a0B /* block comments */ 0c0d"), [10, 11, 12, 13]);
//! assert_eq!(hex!("0a0B /* multi-line
//!                          block comments
//!                       */ 0c0d"), [10, 11, 12, 13]);
//! # }
//! ```
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
    html_root_url = "https://docs.rs/hex-literal/0.3.2"
)]

mod comments;
extern crate proc_macro;

use std::{iter::FromIterator, vec::IntoIter};

use proc_macro::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree};

use crate::comments::{Exclude, ExcludingComments};

/// Strips any outer `Delimiter::None` groups from the input,
/// returning a `TokenStream` consisting of the innermost
/// non-empty-group `TokenTree`.
/// This is used to handle a proc macro being invoked
/// by a `macro_rules!` expansion.
/// See https://github.com/rust-lang/rust/issues/72545 for background
fn ignore_groups(mut input: TokenStream) -> TokenStream {
    let mut tokens = input.clone().into_iter();
    loop {
        if let Some(TokenTree::Group(group)) = tokens.next() {
            if group.delimiter() == Delimiter::None {
                input = group.stream();
                continue;
            }
        }
        return input;
    }
}

struct TokenTreeIter {
    buf: ExcludingComments<IntoIter<u8>>,
    is_punct: bool,
}

impl TokenTreeIter {
    fn new(input: TokenStream) -> Self {
        let mut ts = ignore_groups(input).into_iter();
        let input_str = match (ts.next(), ts.next()) {
            (Some(TokenTree::Literal(literal)), None) => literal.to_string(),
            _ => panic!("expected single string literal"),
        };
        let mut buf: Vec<u8> = input_str.into();

        match buf.as_slice() {
            [b'"', .., b'"'] => (),
            _ => panic!("expected single string literal"),
        };
        buf.pop();
        let mut iter = buf.into_iter().exclude_comments();
        iter.next();
        Self {
            buf: iter,
            is_punct: false,
        }
    }

    fn next_hex_val(&mut self) -> Option<u8> {
        loop {
            let v = self.buf.next()?;
            let n = match v {
                b'0'..=b'9' => v - 48,
                b'A'..=b'F' => v - 55,
                b'a'..=b'f' => v - 87,
                b' ' | b'\r' | b'\n' | b'\t' => continue,
                _ => panic!("encountered invalid character"),
            };
            return Some(n);
        }
    }
}

impl Iterator for TokenTreeIter {
    type Item = TokenTree;

    fn next(&mut self) -> Option<TokenTree> {
        let v = if self.is_punct {
            TokenTree::Punct(Punct::new(',', Spacing::Alone))
        } else {
            let p1 = self.next_hex_val()?;
            let p2 = match self.next_hex_val() {
                Some(v) => v,
                None => panic!("expected even number of hex characters"),
            };
            let val = (p1 << 4) + p2;
            TokenTree::Literal(Literal::u8_suffixed(val))
        };
        self.is_punct = !self.is_punct;
        Some(v)
    }
}

/// Macro for converting string literal containing hex-encoded string
/// to an array containing decoded bytes
#[proc_macro]
pub fn hex(input: TokenStream) -> TokenStream {
    let ts = TokenStream::from_iter(TokenTreeIter::new(input));
    TokenStream::from(TokenTree::Group(Group::new(Delimiter::Bracket, ts)))
}