aranya_capi_codegen/
generate.rs1use std::{fs::File, io::Write as _};
2
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use syn::{Ident, Path, parse_quote};
6use tracing::{debug, info, instrument};
7
8use super::{
9 ast::Ast,
10 ctx::Ctx,
11 error::BuildError,
12 syntax::{Fn, Item, Node, Trimmed},
13};
14
15#[derive(Clone, Debug)]
17pub struct Config {
18 pub err_ty: Path,
22 pub ext_err_ty: Path,
26 pub fn_prefix: Ident,
30 pub ty_prefix: Ident,
34 pub defs: Path,
36 pub target: String,
38}
39
40impl Config {
41 #[instrument(skip_all)]
43 pub fn generate(self, source: &str) -> Result<TokenStream, BuildError> {
44 let file = syn::parse_file(source)?;
45 generate(self, file.items.into_iter().map(Item::from).collect())
46 }
47}
48
49fn generate(cfg: Config, items: Vec<Item>) -> Result<TokenStream, BuildError> {
50 info!(target = cfg.target, items = items.len(), "generating C API");
51
52 let capi = format_ident!("__capi");
53 let mut ctx = Ctx {
54 capi: capi.clone(),
55 conv: parse_quote!(#capi::internal::conv),
56 util: parse_quote!(#capi::internal::util),
57 error: parse_quote!(#capi::internal::error),
58 err_ty: cfg.err_ty,
59 ext_err_ty: cfg.ext_err_ty,
60 ty_prefix: cfg.ty_prefix,
61 fn_prefix: cfg.fn_prefix,
62 defs: cfg.defs,
63 hidden: format_ident!("__hidden"),
64 imports: format_ident!("__imports"),
65 errs: Default::default(),
66 };
67 let ast = Ast::parse(&mut ctx, items)?;
68 ctx.propagate()?;
69
70 let capi = &ctx.capi;
71 let err_ty = &ctx.err_ty;
72 let ext_err_ty = &ctx.ext_err_ty;
73
74 debug!(
75 capi = %Trimmed(capi),
76 err_ty = %Trimmed(err_ty),
77 ext_err_ty = %Trimmed(ext_err_ty),
78 ty_prefix = %ctx.ty_prefix,
79 fn_prefix = %ctx.fn_prefix,
80 "generating code",
81 );
82
83 let mut imports = Vec::new();
85 let mut defs = Vec::new();
87 let mut fns = Vec::<Fn>::new();
89 let mut other = Vec::new();
91
92 for node in ast.nodes {
93 match node {
94 n @ (Node::Alias(_) | Node::Enum(_) | Node::Struct(_) | Node::Union(_)) => defs.push(n),
95 Node::FfiFn(f) => fns.push(f.into()),
96 Node::RustFn(f) => fns.push(f.into()),
97 Node::Other(Item::Use(mut v)) => {
98 v.vis = parse_quote!(pub(super));
99 imports.push(v);
100 }
101 Node::Other(v) => other.push(v),
102 }
103 }
104 let hidden = &ast.hidden;
105
106 let mod_imports = &ctx.imports;
107 let mod_hidden = &ctx.hidden;
108
109 let code = quote! {
110 const _: () = ();
112
113 extern crate aranya_capi_core as #capi;
114
115 use #capi::Builder;
116 use #capi::internal::tracing;
117
118 mod #mod_imports {
119 #(#imports)*
120 }
121
122 #(#defs)*
123 #(#fns)*
124 #(#other)*
125
126 #[allow(deprecated)]
127 mod #mod_hidden {
128 #[allow(clippy::wildcard_imports)]
129 use super::*;
130
131 #hidden
132 }
133 };
134
135 dump(&code, "/tmp/expand-capi-codegen.rs");
136
137 Ok(code)
138}
139
140pub fn format(tokens: &TokenStream) -> String {
142 let mut data = tokens.to_string();
143 if let Ok(file) = syn::parse_file(&data) {
144 data = prettyplease::unparse(&file);
145 }
146 data
147}
148
149#[doc(hidden)]
150#[allow(clippy::panic, reason = "This is debugging `build.rs` code")]
151pub fn dump(code: &TokenStream, path: &str) {
152 if !cfg!(feature = "debug") {
153 return;
154 }
155 let data = format(code);
156 File::create(path)
157 .unwrap_or_else(|_| panic!("unable to create `{path}`"))
158 .write_all(data.as_bytes())
159 .unwrap_or_else(|_| panic!("unable to write all data to `{path}`"));
160}