use std::{
path::{Path, PathBuf},
sync::atomic::{AtomicUsize, Ordering},
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{parse::Parse, Error, LitStr};
pub(crate) struct CodegenArgs {
subplot_lit: LitStr,
subplot_path: PathBuf,
}
impl CodegenArgs {
fn new(subplot_lit: LitStr) -> syn::Result<Self> {
let manifest = std::env::var_os("CARGO_MANIFEST_DIR").ok_or_else(|| {
syn::Error::new_spanned(&subplot_lit, "Cannot find CARGO_MANIFEST_DIR")
})?;
let subplot_path = Path::new(&manifest).join(subplot_lit.value());
if let Err(e) = std::fs::metadata(&subplot_path) {
return Err(syn::Error::new_spanned(
&subplot_lit,
format!("{}: {}", subplot_path.display(), e),
));
}
let subplot_path = subplot_path.canonicalize().map_err(|e| {
syn::Error::new_spanned(&subplot_lit, format!("Failure canonicalising: {e}"))
})?;
Ok(Self {
subplot_lit,
subplot_path,
})
}
fn basedir(&self) -> syn::Result<&Path> {
self.subplot_path.parent().ok_or_else(|| {
syn::Error::new_spanned(
&self.subplot_lit,
"Cannot determine directory for subplot file",
)
})
}
}
impl Parse for CodegenArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let inputfile: LitStr = input.parse()?;
Self::new(inputfile)
}
}
struct ItemList {
items: Vec<syn::Item>,
}
impl Parse for ItemList {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut items = Vec::new();
while !input.is_empty() {
items.push(input.parse()?);
}
Ok(Self { items })
}
}
impl ToTokens for ItemList {
fn to_tokens(&self, tokens: &mut TokenStream) {
for item in &self.items {
item.to_tokens(tokens);
}
}
}
static UNIQ_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub(crate) fn do_codegen(input: CodegenArgs) -> Result<TokenStream, Error> {
let code = subplot::codegen_to_memory(&input.subplot_path, Some("rust"))
.map_err(|e| syn::Error::new_spanned(&input.subplot_lit, e))?;
let items: ItemList = syn::parse_str(&code.code)?;
let basedir = input.basedir()?;
let docimpl = code.doc.meta().document_impl("rust").ok_or_else(|| {
syn::Error::new_spanned(
&input.subplot_lit,
"Could not find rust impl in subplot document",
)
})?;
let files = code
.doc
.meta()
.markdown_filenames()
.iter()
.map(|md| basedir.join(md))
.chain(
code.doc
.meta()
.bindings_filenames()
.iter()
.map(|b| basedir.join(b)),
)
.chain(docimpl.functions_filenames().map(|f| basedir.join(f)))
.chain(Some(input.subplot_path.clone()))
.enumerate()
.map(|(i, p)| {
let ctr = UNIQ_COUNTER.fetch_add(1, Ordering::SeqCst);
let uniq_name =
syn::Ident::new(&format!("_SUBPLOT_INPUT_{i}_{ctr}"), Span::call_site());
let pp = format!("{}", p.display());
if std::fs::metadata(&p).is_ok() {
quote! {
const #uniq_name: (&str, &[u8]) = (#pp, include_bytes!(#pp));
}
} else {
quote! {
const #uniq_name: (&str, &[u8]) = (#pp, b"(builtin)");
}
}
})
.collect::<Vec<_>>();
Ok(quote! {
#(#files)*
#items
})
}