1extern 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 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 (_, 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}