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 mod #mod_hidden {
127 #[allow(clippy::wildcard_imports)]
128 use super::*;
129
130 #hidden
131 }
132 };
133
134 dump(&code, "/tmp/expand-capi-codegen.rs");
135
136 Ok(code)
137}
138
139pub fn format(tokens: &TokenStream) -> String {
141 let mut data = tokens.to_string();
142 if let Ok(file) = syn::parse_file(&data) {
143 data = prettyplease::unparse(&file);
144 }
145 data
146}
147
148#[doc(hidden)]
149#[allow(clippy::panic, reason = "This is debugging `build.rs` code")]
150pub fn dump(code: &TokenStream, path: &str) {
151 if !cfg!(feature = "debug") {
152 return;
153 }
154 let data = format(code);
155 File::create(path)
156 .unwrap_or_else(|_| panic!("unable to create `{path}`"))
157 .write_all(data.as_bytes())
158 .unwrap_or_else(|_| panic!("unable to write all data to `{path}`"));
159}