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, Path,
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("no_mangle") {
139 if let Some(v) = &mut parser.no_mangle {
140 **v = Some(NoMangle(attr.span()));
141 continue;
142 }
143 }
144
145 if path.is_ident("repr") {
147 match attr.parse_args::<Repr>() {
148 Ok(attr) => {
149 if let Some(v) = &mut parser.repr {
150 **v = Some(attr);
151 continue;
152 }
153 }
154 Err(err) => {
155 ctx.push(err);
156 break;
157 }
158 }
159 }
160
161 passthru.push(attr);
162 }
163 passthru
164}
165
166#[instrument(skip_all)]
170fn parse_capi_attr(ctx: &Ctx, attr: &Attribute, parser: &mut Parser<'_>) -> bool {
171 assert_eq!(attr.path().segments.len(), 2);
172 assert!(
173 attr.path().segments[0].ident == ctx.capi
174 || attr.path().segments[0].ident == "capi"
175 || attr.path().segments[0].ident == "aranya_capi_core"
176 );
177
178 let span = attr.span();
179 let ident = &attr.path().segments[1].ident;
180 if ident == "builds" {
181 match attr.parse_args_with(|attr: ParseStream<'_>| Builds::parse(attr)) {
182 Ok(builds) => {
183 if let Some(v) = &mut parser.capi_builds {
184 **v = Some(builds);
185 return true;
186 }
187 }
188 Err(err) => {
189 ctx.push(err);
190 return true;
191 }
192 }
193 } else if ident == "derive" {
194 match attr.parse_args_with(|attr: ParseStream<'_>| Derives::parse(ctx, attr)) {
195 Ok(attrs) => {
196 if let Some(derives) = &mut parser.derives {
197 derives.append(attrs);
198 return true;
199 }
200 }
201 Err(err) => {
202 ctx.push(err);
203 return true;
204 }
205 }
206 } else if ident == "error" {
207 if let Some(v) = &mut parser.capi_error {
208 **v = Some(Error(span));
209 return true;
210 }
211 } else if ident == "ext_error" {
212 if let Some(v) = &mut parser.capi_ext_error {
213 **v = Some(ExtError(span));
214 return true;
215 }
216 } else if ident == "generated" {
217 if let Some(v) = &mut parser.capi_generated {
218 **v = Some(Generated(span));
219 return true;
220 }
221 } else if ident == "opaque" {
222 match attr.parse_args_with(|attr: ParseStream<'_>| Opaque::parse(Some(ctx), attr)) {
223 Ok(attr) => {
224 if let Some(v) = &mut parser.capi_opaque {
225 **v = Some(attr);
226 return false; }
228 }
229 Err(err) => {
230 ctx.push(err);
231 return false; }
233 }
234 } else if ident == "no_ext_error" {
235 if let Some(v) = &mut parser.capi_no_ext_error {
236 **v = Some(NoExtError(span));
237 return true;
238 }
239 } else {
240 ctx.error(ident, format!("unknown `capi::` attribute: {ident}"));
241 return true;
242 }
243
244 ctx.error(ident, "invalid `capi::` attribute for context");
245 true
246}
247
248enum DocAttr {
250 Doc(LitStr),
252 Hidden,
254}
255
256fn parse_doc_attr(meta: &Meta) -> Result<DocAttr> {
260 match meta {
261 Meta::NameValue(meta) => {
262 if let Expr::Lit(expr) = &meta.value {
263 if let Lit::Str(lit) = &expr.lit {
264 return Ok(DocAttr::Doc(lit.clone()));
265 }
266 }
267 }
268 Meta::List(meta) => {
269 meta.parse_args::<kw::hidden>()?;
270 return Ok(DocAttr::Hidden);
271 }
272 Meta::Path(_) => {}
273 }
274 Err(syn::Error::new_spanned(meta, "unsupported doc attribute"))
275}
276
277#[derive(
279 Copy, Clone, Debug, Eq, PartialEq, strum::AsRefStr, strum::EnumString, strum::IntoStaticStr,
280)]
281#[strum(serialize_all = "snake_case")]
282pub enum Repr {
283 #[strum(serialize = "C")]
284 C,
285 Transparent,
286 U8,
287 U16,
288 U32,
289 U64,
290 U128,
291 Usize,
292 I8,
293 I16,
294 I32,
295 I64,
296 I128,
297 Isize,
298}
299
300impl Repr {
301 pub fn to_str(self) -> &'static str {
303 self.into()
304 }
305}
306
307impl PartialEq<Repr> for &Ident {
308 fn eq(&self, repr: &Repr) -> bool {
309 *self == repr
310 }
311}
312
313impl fmt::Display for Repr {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 write!(f, "#[repr({})]", self.to_str())
316 }
317}
318
319impl Parse for Repr {
320 fn parse(input: ParseStream<'_>) -> Result<Repr> {
321 let begin = input.cursor();
324 let ident = input.parse::<Ident>()?;
325 ident
326 .to_string()
327 .parse()
328 .map_err(|_| syn::Error::new_spanned(begin.token_stream(), "unrecognized repr"))
329 }
330}
331
332impl ToTokens for Repr {
333 fn to_tokens(&self, tokens: &mut TokenStream) {
334 let ident = format_ident!("{}", self.to_str());
335 tokens.extend(quote! {
336 #[repr(#ident)]
337 })
338 }
339}
340
341pub trait AttrsExt {
343 fn get(&self) -> &[Attribute];
345
346 #[allow(dead_code)] fn dump(&self) {
349 println!("found {} attrs:", self.get().len());
350 for attr in self.get() {
351 println!("\t{}", quote!(#attr));
352 }
353 }
354
355 fn outer(&self) -> impl Iterator<Item = &Attribute> {
357 fn is_outer(attr: &&Attribute) -> bool {
358 match attr.style {
359 AttrStyle::Outer => true,
360 AttrStyle::Inner(_) => false,
361 }
362 }
363 self.get().iter().filter(is_outer)
364 }
365
366 fn inner(&self) -> impl Iterator<Item = &Attribute> {
368 fn is_inner(attr: &&Attribute) -> bool {
369 match attr.style {
370 AttrStyle::Inner(_) => true,
371 AttrStyle::Outer => false,
372 }
373 }
374 self.get().iter().filter(is_inner)
375 }
376
377 fn repr(&self) -> Option<&Attribute> {
379 self.get().iter().find(|attr| attr.path().is_ident("repr"))
380 }
381}
382
383impl AttrsExt for Vec<Attribute> {
384 fn get(&self) -> &[Attribute] {
386 self
387 }
388}
389
390macro_rules! simple_outer_attr {
391 ($name:ident, $value:literal) => {
392 #[doc = concat!("The `#[", $value, "]` attribute.")]
393 #[derive(Clone)]
394 pub(crate) struct $name(pub Span);
395
396 impl $name {
397 #[allow(dead_code)] pub fn with_span(span: Span) -> Self {
400 Self(span)
401 }
402
403 #[allow(dead_code)] fn parse(_ctx: &Ctx, input: ParseStream<'_>) -> Result<Self> {
405 Ok(Self(input.span()))
406 }
407 }
408
409 impl Eq for $name {}
410 impl PartialEq for $name {
411 fn eq(&self, _other: &Self) -> bool {
412 true
413 }
414 }
415
416 impl fmt::Display for $name {
417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 write!(f, "{}", $value)
419 }
420 }
421
422 impl fmt::Debug for $name {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 write!(f, "{}", $value)
425 }
426 }
427
428 impl TryFrom<Attribute> for $name {
429 type Error = syn::Error;
430 fn try_from(attr: Attribute) -> Result<Self> {
431 Ok(Self(attr.span()))
432 }
433 }
434
435 impl ToTokens for $name {
436 fn to_tokens(&self, tokens: &mut TokenStream) {
437 let path = syn::parse_str::<Path>($value).unwrap();
439 tokens.extend(quote_spanned! {self.0=>
440 #[#path]
441 })
442 }
443 }
444 };
445}
446simple_outer_attr!(Error, "capi::error");
447simple_outer_attr!(ExtError, "capi::ext_error");
448simple_outer_attr!(Generated, "capi::generated");
449simple_outer_attr!(NoExtError, "capi::no_ext_error");
450simple_outer_attr!(NoMangle, "no_mangle");