ethnum_macros/
lib.rs

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