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