aranya_capi_codegen/syntax/
attrs.rs1use std::fmt;
2
3use proc_macro2::{Span, TokenStream};
4use quote::{ToTokens, format_ident, quote, quote_spanned};
5use syn::{
6 AttrStyle, Attribute, Expr, Ident, Lit, LitStr, Meta,
7 parse::{Parse, ParseStream, Result},
8 spanned::Spanned as _,
9};
10use tracing::{debug, instrument};
11
12use super::{builds::Builds, derive::Derives, doc::Doc, opaque::Opaque, util::Trimmed};
13use crate::ctx::Ctx;
14
15mod kw {
16 use syn::custom_keyword;
17
18 custom_keyword!(Init);
19 custom_keyword!(Cleanup);
20 custom_keyword!(hidden);
21}
22
23#[derive(Default)]
29pub(crate) struct Parser<'a> {
30 pub doc: Option<&'a mut Doc>,
32 pub repr: Option<&'a mut Option<Repr>>,
34 pub derives: Option<&'a mut Derives>,
36 pub no_mangle: Option<&'a mut Option<NoMangle>>,
38 pub capi_builds: Option<&'a mut Option<Builds>>,
40 pub capi_opaque: Option<&'a mut Option<Opaque>>,
42 pub capi_error: Option<&'a mut Option<Error>>,
44 pub capi_no_ext_error: Option<&'a mut Option<NoExtError>>,
46 pub capi_generated: Option<&'a mut Option<Generated>>,
48}
49
50#[instrument(skip_all)]
74pub(crate) fn parse(ctx: &Ctx, attrs: Vec<Attribute>, mut parser: Parser<'_>) -> Vec<Attribute> {
75 let n = attrs.len();
76
77 let mut passthru = Vec::new();
78 for (i, attr) in attrs.into_iter().enumerate() {
79 let path = attr.path();
80
81 debug!(
82 path = %Trimmed(path),
83 "parsing attr {}/{n}",
84 i.saturating_add(1)
85 );
86
87 if path.segments.len() == 2
90 && (path.segments[0].ident == ctx.capi
91 || path.segments[0].ident == "aranya_capi_core"
92 || path.segments[0].ident == "capi")
93 {
94 if !parse_capi_attr(ctx, &attr, &mut parser) {
95 passthru.push(attr);
96 }
97 continue;
98 }
99
100 if path.is_ident("derive") {
102 match attr.parse_args_with(|attr: ParseStream<'_>| Derives::parse(ctx, attr)) {
103 Ok(attrs) => {
104 if let Some(derives) = &mut parser.derives {
105 derives.append(attrs);
106 continue;
107 }
108 }
109 Err(err) => {
110 ctx.push(err);
111 break;
112 }
113 }
114 }
115
116 if path.is_ident("doc") {
118 match parse_doc_attr(&attr.meta) {
119 Ok(attr) => {
120 if let Some(doc) = &mut parser.doc {
121 match attr {
122 DocAttr::Doc(lit) => doc.push(lit),
123 DocAttr::Hidden => doc.hidden = true,
124 }
125 continue;
126 }
127 }
128 Err(err) => {
129 ctx.push(err);
130 break;
131 }
132 }
133 }
134
135 if path.is_ident("unsafe") {
137 let attr = match attr.parse_args::<Ident>() {
138 Ok(attr) => attr,
139 Err(err) => {
140 ctx.push(err);
141 break;
142 }
143 };
144
145 if attr == "no_mangle"
147 && let Some(v) = &mut parser.no_mangle
148 {
149 **v = Some(NoMangle(attr.span()));
150 continue;
151 }
152 }
153
154 if path.is_ident("repr") {
156 match attr.parse_args::<Repr>() {
157 Ok(attr) => {
158 if let Some(v) = &mut parser.repr {
159 **v = Some(attr);
160 continue;
161 }
162 }
163 Err(err) => {
164 ctx.push(err);
165 break;
166 }
167 }
168 }
169
170 passthru.push(attr);
171 }
172 passthru
173}
174
175#[instrument(skip_all)]
179fn parse_capi_attr(ctx: &Ctx, attr: &Attribute, parser: &mut Parser<'_>) -> bool {
180 assert_eq!(attr.path().segments.len(), 2);
181 assert!(
182 attr.path().segments[0].ident == ctx.capi
183 || attr.path().segments[0].ident == "capi"
184 || attr.path().segments[0].ident == "aranya_capi_core"
185 );
186
187 let span = attr.span();
188 let ident = &attr.path().segments[1].ident;
189 if ident == "builds" {
190 match attr.parse_args_with(|attr: ParseStream<'_>| Builds::parse(attr)) {
191 Ok(builds) => {
192 if let Some(v) = &mut parser.capi_builds {
193 **v = Some(builds);
194 return true;
195 }
196 }
197 Err(err) => {
198 ctx.push(err);
199 return true;
200 }
201 }
202 } else if ident == "derive" {
203 match attr.parse_args_with(|attr: ParseStream<'_>| Derives::parse(ctx, attr)) {
204 Ok(attrs) => {
205 if let Some(derives) = &mut parser.derives {
206 derives.append(attrs);
207 return true;
208 }
209 }
210 Err(err) => {
211 ctx.push(err);
212 return true;
213 }
214 }
215 } else if ident == "error" {
216 if let Some(v) = &mut parser.capi_error {
217 **v = Some(Error(span));
218 return true;
219 }
220 } else if ident == "generated" {
221 if let Some(v) = &mut parser.capi_generated {
222 **v = Some(Generated(span));
223 return true;
224 }
225 } else if ident == "opaque" {
226 match attr.parse_args_with(|attr: ParseStream<'_>| Opaque::parse(Some(ctx), attr)) {
227 Ok(attr) => {
228 if let Some(v) = &mut parser.capi_opaque {
229 **v = Some(attr);
230 return true;
231 }
232 }
233 Err(err) => {
234 ctx.push(err);
235 return false; }
237 }
238 } else if ident == "no_ext_error" {
239 if let Some(v) = &mut parser.capi_no_ext_error {
240 **v = Some(NoExtError(span));
241 return true;
242 }
243 } else {
244 ctx.error(ident, format!("unknown `capi::` attribute: {ident}"));
245 return true;
246 }
247
248 ctx.error(ident, "invalid `capi::` attribute for context");
249 true
250}
251
252enum DocAttr {
254 Doc(LitStr),
256 Hidden,
258}
259
260fn parse_doc_attr(meta: &Meta) -> Result<DocAttr> {
264 match meta {
265 Meta::NameValue(meta) => {
266 if let Expr::Lit(expr) = &meta.value
267 && let Lit::Str(lit) = &expr.lit
268 {
269 return Ok(DocAttr::Doc(lit.clone()));
270 }
271 }
272 Meta::List(meta) => {
273 meta.parse_args::<kw::hidden>()?;
274 return Ok(DocAttr::Hidden);
275 }
276 Meta::Path(_) => {}
277 }
278 Err(syn::Error::new_spanned(meta, "unsupported doc attribute"))
279}
280
281#[derive(
283 Copy, Clone, Debug, Eq, PartialEq, strum::AsRefStr, strum::EnumString, strum::IntoStaticStr,
284)]
285#[strum(serialize_all = "snake_case")]
286pub enum Repr {
287 #[strum(serialize = "C")]
288 C,
289 Transparent,
290 U8,
291 U16,
292 U32,
293 U64,
294 U128,
295 Usize,
296 I8,
297 I16,
298 I32,
299 I64,
300 I128,
301 Isize,
302}
303
304impl Repr {
305 pub fn to_str(self) -> &'static str {
307 self.into()
308 }
309}
310
311impl PartialEq<Repr> for &Ident {
312 fn eq(&self, repr: &Repr) -> bool {
313 *self == repr
314 }
315}
316
317impl fmt::Display for Repr {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 write!(f, "#[repr({})]", self.to_str())
320 }
321}
322
323impl Parse for Repr {
324 fn parse(input: ParseStream<'_>) -> Result<Self> {
325 let begin = input.cursor();
328 let ident = input.parse::<Ident>()?;
329 ident
330 .to_string()
331 .parse()
332 .map_err(|_| syn::Error::new_spanned(begin.token_stream(), "unrecognized repr"))
333 }
334}
335
336impl ToTokens for Repr {
337 fn to_tokens(&self, tokens: &mut TokenStream) {
338 let ident = format_ident!("{}", self.to_str());
339 tokens.extend(quote! {
340 #[repr(#ident)]
341 });
342 }
343}
344
345pub trait AttrsExt {
347 fn get(&self) -> &[Attribute];
349
350 #[allow(dead_code)] fn dump(&self) {
353 println!("found {} attrs:", self.get().len());
354 for attr in self.get() {
355 println!("\t{}", quote!(#attr));
356 }
357 }
358
359 fn outer(&self) -> impl Iterator<Item = &Attribute> {
361 fn is_outer(attr: &&Attribute) -> bool {
362 match attr.style {
363 AttrStyle::Outer => true,
364 AttrStyle::Inner(_) => false,
365 }
366 }
367 self.get().iter().filter(is_outer)
368 }
369
370 fn inner(&self) -> impl Iterator<Item = &Attribute> {
372 fn is_inner(attr: &&Attribute) -> bool {
373 match attr.style {
374 AttrStyle::Inner(_) => true,
375 AttrStyle::Outer => false,
376 }
377 }
378 self.get().iter().filter(is_inner)
379 }
380
381 fn repr(&self) -> Option<&Attribute> {
383 self.get().iter().find(|attr| attr.path().is_ident("repr"))
384 }
385}
386
387impl AttrsExt for Vec<Attribute> {
388 fn get(&self) -> &[Attribute] {
390 self
391 }
392}
393
394macro_rules! simple_outer_attr {
395 ($name:ident, $value:literal) => {
396 #[doc = concat!("The `#[", $value, "]` attribute.")]
397 #[derive(Clone)]
398 pub(crate) struct $name(pub Span);
399
400 impl $name {
401 #[allow(dead_code)] pub fn with_span(span: Span) -> Self {
404 Self(span)
405 }
406
407 #[allow(dead_code)] fn parse(_ctx: &Ctx, input: ParseStream<'_>) -> Result<Self> {
409 Ok(Self(input.span()))
410 }
411 }
412
413 impl Eq for $name {}
414 impl PartialEq for $name {
415 fn eq(&self, _other: &Self) -> bool {
416 true
417 }
418 }
419
420 impl fmt::Display for $name {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 write!(f, "{}", $value)
423 }
424 }
425
426 impl fmt::Debug for $name {
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 write!(f, "{}", $value)
429 }
430 }
431
432 impl TryFrom<Attribute> for $name {
433 type Error = syn::Error;
434 fn try_from(attr: Attribute) -> Result<Self> {
435 Ok(Self(attr.span()))
436 }
437 }
438
439 impl ToTokens for $name {
440 fn to_tokens(&self, tokens: &mut TokenStream) {
441 let path = syn::parse_str::<Meta>($value).unwrap();
443 tokens.extend(quote_spanned! {self.0=>
444 #[#path]
445 })
446 }
447 }
448 };
449}
450simple_outer_attr!(Error, "capi::error");
451simple_outer_attr!(Generated, "capi::generated");
452simple_outer_attr!(NoExtError, "capi::no_ext_error");
453simple_outer_attr!(NoMangle, "unsafe(no_mangle)");