ethaddr_macros/
lib.rs

1//! Procedural macro for Ethereum address literals.
2//!
3//! See [`ethaddr`](https://docs.rs/ethaddr/latest/ethaddr/macro.address.html)
4//! documentation for more information.
5
6extern crate proc_macro;
7
8mod checksum;
9#[allow(dead_code)]
10mod hex;
11
12use proc_macro::{Delimiter, Literal, Span, TokenStream, TokenTree};
13use std::fmt::Write as _;
14
15#[proc_macro]
16pub fn address(input: TokenStream) -> TokenStream {
17    match AddressLiteral::generate(input) {
18        Ok(address) => address.into_tokens(),
19        Err(err) => err.into_tokens(),
20    }
21}
22
23struct AddressLiteral([u8; 20], String);
24
25impl AddressLiteral {
26    fn generate(input: TokenStream) -> Result<Self, CompileError> {
27        let input = Input::parse(input)?;
28
29        let bytes = hex::decode(&input.value).map_err(|err| CompileError {
30            message: format!("invalid address literal: {err}"),
31            // TODO(nlordell): If the `Span` API changes to allow offseting in
32            // the future, we can add more details in the case of bad hex
33            // character errors.
34            span: Some(input.span),
35        })?;
36        if input.checksum {
37            checksum::verify(&bytes, &input.value).map_err(|checksum| {
38                let suggestion = if input.value.starts_with("0x") {
39                    checksum.as_str()
40                } else {
41                    checksum.as_str().strip_prefix("0x").unwrap()
42                };
43                CompileError {
44                    message: format!("invalid address checksum; did you mean `{suggestion}`?"),
45                    span: Some(input.span),
46                }
47            })?;
48        }
49
50        Ok(Self(bytes, input.crate_name))
51    }
52
53    fn into_tokens(self) -> TokenStream {
54        let mut buf = String::new();
55        write!(buf, "{}::Address(*b\"", self.1).unwrap();
56        for byte in self.0 {
57            write!(buf, "\\x{byte:02x}").unwrap();
58        }
59        write!(buf, "\")").unwrap();
60
61        buf.parse().unwrap()
62    }
63}
64
65struct Input {
66    checksum: bool,
67    value: String,
68    span: Span,
69    crate_name: String,
70}
71
72impl Input {
73    fn parse(input: TokenStream) -> Result<Self, CompileError> {
74        let mut result = Input {
75            checksum: true,
76            value: String::new(),
77            span: Span::call_site(),
78            crate_name: "::ethaddr".to_string(),
79        };
80        ParserState::start().input(input, &mut result)?.end()?;
81
82        Ok(result)
83    }
84}
85
86enum ParserState {
87    TildeOrString,
88    String,
89    CommaOrEof,
90    Crate,
91    EqualCrateName,
92    CrateName,
93    Eof,
94}
95
96impl ParserState {
97    fn start() -> Self {
98        Self::TildeOrString
99    }
100
101    fn input(self, input: TokenStream, result: &mut Input) -> Result<Self, CompileError> {
102        input
103            .into_iter()
104            .try_fold(self, |state, token| state.next(token, result))
105    }
106
107    fn next(self, token: TokenTree, result: &mut Input) -> Result<Self, CompileError> {
108        match (&self, &token) {
109            // Procedural macros invoked from withing `macro_rules!` expansions
110            // may be grouped with a `Ø` delimiter (which allows operator
111            // precidence to be preserved).
112            //
113            // See <https://doc.rust-lang.org/stable/proc_macro/enum.Delimiter.html#variant.None>
114            (_, TokenTree::Group(g)) if g.delimiter() == Delimiter::None => {
115                self.input(g.stream(), result)
116            }
117
118            (Self::TildeOrString, TokenTree::Punct(p)) if p.as_char() == '~' => {
119                result.checksum = false;
120                Ok(Self::String)
121            }
122            (Self::TildeOrString | Self::String, TokenTree::Literal(l)) => match parse_string(l) {
123                Some(value) => {
124                    result.value = value;
125                    result.span = token.span();
126                    Ok(Self::CommaOrEof)
127                }
128                None => Err(self.unexpected(Some(token))),
129            },
130
131            (Self::CommaOrEof, TokenTree::Punct(p)) if p.as_char() == ',' => Ok(Self::Crate),
132            (Self::Crate, TokenTree::Ident(c)) if c.to_string() == "crate" => {
133                Ok(Self::EqualCrateName)
134            }
135            (Self::EqualCrateName, TokenTree::Punct(p)) if p.as_char() == '=' => {
136                Ok(Self::CrateName)
137            }
138            (Self::CrateName, TokenTree::Literal(l)) => match parse_string(l) {
139                Some(value) => {
140                    result.crate_name = value;
141                    Ok(Self::Eof)
142                }
143                None => Err(self.unexpected(Some(token))),
144            },
145
146            _ => Err(self.unexpected(Some(token))),
147        }
148    }
149
150    fn end(self) -> Result<(), CompileError> {
151        match self {
152            Self::CommaOrEof | Self::Eof => Ok(()),
153            _ => Err(self.unexpected(None)),
154        }
155    }
156
157    fn unexpected(self, token: Option<TokenTree>) -> CompileError {
158        let expected = match self {
159            Self::TildeOrString => "`~` or string literal",
160            Self::String => "string literal",
161            Self::CommaOrEof => "`,` or <eof>",
162            Self::Crate => "`crate` identifier",
163            Self::EqualCrateName => "`=`",
164            Self::CrateName => "crate name string literal",
165            Self::Eof => "<eof>",
166        };
167        let (value, span) = match token {
168            Some(TokenTree::Group(g)) => {
169                let delim = match g.delimiter() {
170                    Delimiter::Parenthesis => "(",
171                    Delimiter::Brace => "{",
172                    Delimiter::Bracket => "[",
173                    Delimiter::None => "Ø",
174                };
175                (delim.to_string(), Some(g.span_open()))
176            }
177            Some(t) => (t.to_string(), Some(t.span())),
178            None => ("<eof>".to_owned(), None),
179        };
180
181        CompileError {
182            message: format!("expected {expected} but found `{value}`"),
183            span,
184        }
185    }
186}
187
188struct CompileError {
189    message: String,
190    span: Option<Span>,
191}
192
193impl CompileError {
194    fn into_tokens(self) -> TokenStream {
195        let error = format!("compile_error!({:?})", self.message)
196            .parse::<TokenStream>()
197            .unwrap();
198
199        match self.span {
200            Some(span) => error
201                .into_iter()
202                .map(|mut token| {
203                    token.set_span(span);
204                    token
205                })
206                .collect(),
207            None => error,
208        }
209    }
210}
211
212fn parse_string(literal: &Literal) -> Option<String> {
213    Some(
214        literal
215            .to_string()
216            .strip_prefix('"')?
217            .strip_suffix('"')?
218            .to_owned(),
219    )
220}