1use proc_macro::TokenStream;
25
26#[proc_macro]
62pub fn asm_bytes(input: TokenStream) -> TokenStream {
63 match asm_bytes_impl(input) {
64 Ok(ts) => ts,
65 Err(err) => err.into_compile_error(),
66 }
67}
68
69#[proc_macro]
89pub fn asm_array(input: TokenStream) -> TokenStream {
90 match asm_array_impl(input) {
91 Ok(ts) => ts,
92 Err(err) => err.into_compile_error(),
93 }
94}
95
96struct MacroInput {
99 arch: asm_rs::Arch,
100 base_addr: u64,
101 source: String,
102 source_span: proc_macro::Span,
104}
105
106fn parse_input(input: TokenStream) -> Result<MacroInput, syn_free::Error> {
107 let mut tokens = input.into_iter().peekable();
108
109 let arch_tt = tokens.next().ok_or_else(|| {
111 syn_free::Error::new(
112 "expected architecture identifier (x86_64, aarch64, arm, thumb, rv32, rv64)",
113 )
114 })?;
115 let arch = parse_arch(&arch_tt)?;
116
117 expect_comma(&mut tokens)?;
119
120 let base_addr;
122 let source;
123 let source_span;
124
125 if let Some(tt) = tokens.peek() {
126 if is_integer_literal(tt) {
127 let tt = tokens.next().unwrap();
128 base_addr = parse_integer_literal(&tt)?;
129 expect_comma(&mut tokens)?;
130 let (src, span) = parse_string_literal(&mut tokens)?;
131 source = src;
132 source_span = span;
133 } else {
134 base_addr = 0;
135 let (src, span) = parse_string_literal(&mut tokens)?;
136 source = src;
137 source_span = span;
138 }
139 } else {
140 return Err(syn_free::Error::new("expected assembly source string"));
141 }
142
143 if tokens.next().is_some() {
145 return Err(syn_free::Error::new(
146 "unexpected extra tokens after source string",
147 ));
148 }
149
150 Ok(MacroInput {
151 arch,
152 base_addr,
153 source,
154 source_span,
155 })
156}
157
158fn asm_bytes_impl(input: TokenStream) -> Result<TokenStream, syn_free::Error> {
159 let mi = parse_input(input)?;
160 let bytes = do_assemble(&mi)?;
161 Ok(bytes_to_slice_expr(&bytes))
162}
163
164fn asm_array_impl(input: TokenStream) -> Result<TokenStream, syn_free::Error> {
165 let mi = parse_input(input)?;
166 let bytes = do_assemble(&mi)?;
167 Ok(bytes_to_array_expr(&bytes))
168}
169
170fn do_assemble(mi: &MacroInput) -> Result<Vec<u8>, syn_free::Error> {
171 let result = if mi.base_addr != 0 {
172 asm_rs::assemble_at(&mi.source, mi.arch, mi.base_addr)
173 } else {
174 asm_rs::assemble(&mi.source, mi.arch)
175 };
176
177 result.map_err(|e| syn_free::Error::with_span(mi.source_span, &format!("assembly error: {e}")))
178}
179
180fn parse_arch(tt: &proc_macro::TokenTree) -> Result<asm_rs::Arch, syn_free::Error> {
181 let ident = match tt {
182 proc_macro::TokenTree::Ident(id) => id.to_string(),
183 _ => {
184 return Err(syn_free::Error::new(
185 "expected architecture identifier (x86, x86_64, aarch64, arm, thumb, rv32, rv64)",
186 ));
187 }
188 };
189 match ident.as_str() {
190 "x86" => Ok(asm_rs::Arch::X86),
191 "x86_64" => Ok(asm_rs::Arch::X86_64),
192 "arm" => Ok(asm_rs::Arch::Arm),
193 "thumb" => Ok(asm_rs::Arch::Thumb),
194 "aarch64" => Ok(asm_rs::Arch::Aarch64),
195 "rv32" => Ok(asm_rs::Arch::Rv32),
196 "rv64" => Ok(asm_rs::Arch::Rv64),
197 _ => Err(syn_free::Error::with_span(
198 tt.span(),
199 &format!(
200 "unknown architecture `{ident}`, expected: x86, x86_64, arm, thumb, aarch64, rv32, rv64"
201 ),
202 )),
203 }
204}
205
206fn expect_comma(
207 tokens: &mut std::iter::Peekable<proc_macro::token_stream::IntoIter>,
208) -> Result<(), syn_free::Error> {
209 match tokens.next() {
210 Some(proc_macro::TokenTree::Punct(p)) if p.as_char() == ',' => Ok(()),
211 Some(other) => Err(syn_free::Error::with_span(other.span(), "expected `,`")),
212 None => Err(syn_free::Error::new("expected `,`")),
213 }
214}
215
216fn is_integer_literal(tt: &proc_macro::TokenTree) -> bool {
217 matches!(tt, proc_macro::TokenTree::Literal(lit) if {
218 let s = lit.to_string();
219 s.starts_with(|c: char| c.is_ascii_digit())
220 && !s.starts_with('"')
221 && !s.starts_with('\'')
222 })
223}
224
225fn parse_integer_literal(tt: &proc_macro::TokenTree) -> Result<u64, syn_free::Error> {
226 let proc_macro::TokenTree::Literal(lit) = tt else {
227 return Err(syn_free::Error::with_span(
228 tt.span(),
229 "expected integer literal",
230 ));
231 };
232 let s = lit.to_string();
233 let val = if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
234 u64::from_str_radix(hex, 16)
235 } else {
236 s.parse::<u64>()
237 };
238 val.map_err(|_| syn_free::Error::with_span(tt.span(), "invalid integer literal"))
239}
240
241fn parse_string_literal(
242 tokens: &mut std::iter::Peekable<proc_macro::token_stream::IntoIter>,
243) -> Result<(String, proc_macro::Span), syn_free::Error> {
244 let tt = tokens
245 .next()
246 .ok_or_else(|| syn_free::Error::new("expected string literal"))?;
247 let proc_macro::TokenTree::Literal(lit) = &tt else {
248 return Err(syn_free::Error::with_span(
249 tt.span(),
250 "expected string literal",
251 ));
252 };
253 let raw = lit.to_string();
254 let content = if raw.starts_with("r#\"") {
256 raw.strip_prefix("r#\"")
257 .and_then(|s| s.strip_suffix("\"#"))
258 .ok_or_else(|| syn_free::Error::with_span(tt.span(), "malformed raw string"))?
259 } else if raw.starts_with("r\"") {
260 raw.strip_prefix("r\"")
261 .and_then(|s| s.strip_suffix('"'))
262 .ok_or_else(|| syn_free::Error::with_span(tt.span(), "malformed raw string"))?
263 } else if raw.starts_with('"') {
264 let inner = raw
266 .strip_prefix('"')
267 .and_then(|s| s.strip_suffix('"'))
268 .ok_or_else(|| syn_free::Error::with_span(tt.span(), "malformed string literal"))?;
269 return Ok((unescape_string(inner), tt.span()));
270 } else {
271 return Err(syn_free::Error::with_span(
272 tt.span(),
273 "expected string literal",
274 ));
275 };
276 Ok((content.to_string(), tt.span()))
277}
278
279fn unescape_string(s: &str) -> String {
280 let mut out = String::with_capacity(s.len());
281 let mut chars = s.chars();
282 while let Some(c) = chars.next() {
283 if c == '\\' {
284 match chars.next() {
285 Some('n') => out.push('\n'),
286 Some('r') => out.push('\r'),
287 Some('t') => out.push('\t'),
288 Some('\\') => out.push('\\'),
289 Some('"') => out.push('"'),
290 Some('0') => out.push('\0'),
291 Some(other) => {
292 out.push('\\');
293 out.push(other);
294 }
295 None => out.push('\\'),
296 }
297 } else {
298 out.push(c);
299 }
300 }
301 out
302}
303
304fn bytes_to_slice_expr(bytes: &[u8]) -> TokenStream {
305 let byte_strs: Vec<String> = bytes.iter().map(|b| format!("{b:#04X}u8")).collect();
306 let inner = byte_strs.join(", ");
307 let code = format!("{{ const BYTES: &[u8] = &[{inner}]; BYTES }}");
308 code.parse().expect("generated code should parse")
309}
310
311fn bytes_to_array_expr(bytes: &[u8]) -> TokenStream {
312 let len = bytes.len();
313 let byte_strs: Vec<String> = bytes.iter().map(|b| format!("{b:#04X}u8")).collect();
314 let inner = byte_strs.join(", ");
315 let code = format!("{{ const BYTES: [u8; {len}] = [{inner}]; BYTES }}");
316 code.parse().expect("generated code should parse")
317}
318
319mod syn_free {
324 use proc_macro::{Span, TokenStream};
325
326 pub struct Error {
327 message: String,
328 span: Option<Span>,
329 }
330
331 impl Error {
332 pub fn new(msg: &str) -> Self {
333 Self {
334 message: msg.to_string(),
335 span: None,
336 }
337 }
338
339 pub fn with_span(span: Span, msg: &str) -> Self {
340 Self {
341 message: msg.to_string(),
342 span: Some(span),
343 }
344 }
345
346 pub fn into_compile_error(self) -> TokenStream {
347 let msg = self.message.replace('"', "\\\"");
348 let code = format!("compile_error!(\"{msg}\")");
349 if let Some(span) = self.span {
351 let ts: TokenStream = code.parse().unwrap();
352 ts.into_iter()
353 .map(|mut tt| {
354 tt.set_span(span);
355 tt
356 })
357 .collect()
358 } else {
359 code.parse().unwrap()
360 }
361 }
362 }
363}