naked_function_macro/
asm.rs
1use quote::ToTokens;
2use syn::{
3 ext::IdentExt,
4 parenthesized,
5 parse::{Parse, ParseStream},
6 punctuated::Punctuated,
7 token::Paren,
8 Expr, Ident, ItemFn, Result, Stmt, Token,
9};
10
11pub mod kw {
12 syn::custom_keyword!(sym);
13 syn::custom_keyword!(options);
14 syn::custom_keyword!(out);
15 syn::custom_keyword!(lateout);
16 syn::custom_keyword!(inout);
17 syn::custom_keyword!(inlateout);
18 syn::custom_keyword!(clobber_abi);
19}
20
21pub enum AsmOperand {
23 Template(Expr),
24 Const {
25 name: Option<(Ident, Token![=])>,
26 token: Token![const],
27 expr: Expr,
28 },
29 Sym {
30 name: Option<(Ident, Token![=])>,
31 token: kw::sym,
32 expr: Expr,
33 },
34 Options {
35 token: kw::options,
36 paren_token: Paren,
37 options: Punctuated<Ident, Token![,]>,
38 },
39}
40
41impl Parse for AsmOperand {
42 fn parse(input: ParseStream) -> Result<Self> {
43 if input.peek(kw::options) {
44 let token = input.parse::<kw::options>()?;
45 let content;
46 let paren_token = parenthesized!(content in input);
47 let options = content.parse_terminated(Ident::parse, Token![,])?;
48 return Ok(Self::Options {
49 token,
50 paren_token,
51 options,
52 });
53 }
54
55 let mut name = None;
56 if input.peek(Ident::peek_any) && input.peek2(Token![=]) {
57 let ident = input.call(Ident::parse_any)?;
58 let token = input.parse()?;
59 name = Some((ident, token));
60 }
61
62 if input.peek(kw::sym) {
63 let token = input.parse()?;
64 let expr = input.parse()?;
65 return Ok(Self::Sym { name, token, expr });
66 }
67
68 if input.peek(Token![const]) {
69 let token = input.parse()?;
70 let expr = input.parse()?;
71 return Ok(Self::Const { name, token, expr });
72 }
73
74 if input.peek(Token![in])
75 || input.peek(kw::out)
76 || input.peek(kw::lateout)
77 || input.peek(kw::inout)
78 || input.peek(kw::inlateout)
79 {
80 return Err(syn::Error::new(
81 input.span(),
82 "only `const` and `sym` operands may be used in naked functions",
83 ));
84 }
85
86 if input.peek(kw::clobber_abi) {
87 return Err(syn::Error::new(
88 input.span(),
89 "`clobber_abi` cannot be used in naked functions",
90 ));
91 }
92
93 if let Some((ident, _token)) = name {
96 bail!(ident, "invalid asm! syntax");
97 }
98 Ok(Self::Template(input.parse()?))
99 }
100}
101
102impl ToTokens for AsmOperand {
103 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
104 match self {
105 AsmOperand::Template(expr) => expr.to_tokens(tokens),
106 AsmOperand::Const { name, token, expr } => {
107 if let Some((ident, token)) = name {
108 ident.to_tokens(tokens);
109 token.to_tokens(tokens);
110 }
111 token.to_tokens(tokens);
112 expr.to_tokens(tokens);
113 }
114 AsmOperand::Sym { name, token, expr } => {
115 if let Some((ident, token)) = name {
116 ident.to_tokens(tokens);
117 token.to_tokens(tokens);
118 }
119 token.to_tokens(tokens);
120 expr.to_tokens(tokens);
121 }
122 AsmOperand::Options {
123 token,
124 paren_token,
125 options,
126 } => {
127 token.to_tokens(tokens);
128 paren_token.surround(tokens, |tokens| {
129 options.to_tokens(tokens);
130 })
131 }
132 }
133 }
134}
135
136pub fn extract_asm(func: &ItemFn) -> Result<Punctuated<AsmOperand, Token![,]>> {
138 if func.block.stmts.len() != 1 {
139 bail!(
140 func,
141 "naked functions may only contain a single asm! statement"
142 );
143 }
144 let (mac, attrs) = match &func.block.stmts[0] {
145 Stmt::Macro(macro_) => (¯o_.mac, ¯o_.attrs),
146 Stmt::Expr(Expr::Macro(macro_), _) => (¯o_.mac, ¯o_.attrs),
147 _ => bail!(
148 func,
149 "naked functions may only contain a single asm! statement"
150 ),
151 };
152 if !attrs.is_empty() || !mac.path.is_ident("asm") {
153 bail!(
154 func,
155 "naked functions may only contain a single asm! statement"
156 );
157 }
158 mac.parse_body_with(Punctuated::parse_terminated)
159}