1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::spanned::Spanned;
4use syn::{Item, Result, parse};
5
6pub(crate) use attributes::*;
7
8use crate::function::AnnotatedFunction;
9use crate::module::AnnotatedModule;
10
11mod attributes;
12mod function;
13mod module;
14
15pub(crate) fn symbol_source_path(path: &str) -> String {
16 path.replace('/', "$")
17}
18
19#[proc_macro_attribute]
20pub fn pragma(
21 attr: proc_macro::TokenStream,
22 item: proc_macro::TokenStream,
23) -> proc_macro::TokenStream {
24 let expanded = expand(attr, item).unwrap_or_else(|error| error.to_compile_error());
25 expanded.into()
26}
27
28#[proc_macro]
29pub fn environment(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
30 let expanded = expand_environment(input).unwrap_or_else(|error| error.to_compile_error());
31 expanded.into()
32}
33
34fn expand(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> Result<TokenStream> {
35 let source_path = source_path_of(item.clone());
36 let attributes = Attributes::parse(attr)?;
37 let item = parse::<Item>(item)?;
38
39 let expanded = match item {
40 Item::Fn(item_fn) => AnnotatedFunction::new(item_fn, attributes, source_path).expand(),
41 Item::Mod(item_mod) => AnnotatedModule::new(item_mod, attributes, source_path).expand(),
42 _ => syn::Error::new(
43 item.span(),
44 "Pragmas are not supported for this type of construct",
45 )
46 .to_compile_error(),
47 };
48
49 Ok(expanded)
50}
51
52fn expand_environment(input: proc_macro::TokenStream) -> Result<TokenStream> {
53 let annotate_path = if input.is_empty() {
54 None
55 } else {
56 Some(parse::<syn::Path>(input)?)
57 };
58 let generated_path = environment_source_path(proc_macro::Span::call_site());
59 let generated_path = syn::LitStr::new(generated_path.as_str(), proc_macro2::Span::call_site());
60
61 let expanded = if let Some(annotate_path) = annotate_path {
62 quote! {
63 mod __annotate {
64 use #annotate_path;
65 include!(concat!(env!("OUT_DIR"), "/annotate/", #generated_path));
66 pub const fn environment() -> &'static #annotate_path::Environment {
67 &__annotate::ENVIRONMENT
68 }
69 }
70
71 #[doc(hidden)]
72 #[inline(never)]
73 pub fn __ensure_linked() {
74 __annotate::__ensure_linked();
75 }
76 }
77 } else {
78 quote! {
79 #[macro_use]
80 extern crate annotate;
81 extern crate alloc;
82
83 include!(concat!(env!("OUT_DIR"), "/annotate/", #generated_path));
84 #[doc(hidden)]
85 #[inline(never)]
86 pub fn __ensure_linked() {
87 __annotate::__ensure_linked();
88 }
89 pub const fn environment() -> &'static annotate::Environment {
90 &__annotate::ENVIRONMENT
91 }
92 }
93 };
94
95 Ok(expanded)
96}
97
98fn environment_source_path(span: proc_macro::Span) -> String {
99 let source_path = std::path::PathBuf::from(span.file());
100 let manifest_root = std::path::PathBuf::from(
101 std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
102 .file_name()
103 .unwrap(),
104 );
105
106 if source_path.is_absolute()
107 && let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR")
108 {
109 let manifest_dir = std::path::PathBuf::from(manifest_dir);
110 if let Ok(relative_path) = source_path.strip_prefix(&manifest_dir) {
111 return manifest_root
112 .join(relative_path)
113 .to_string_lossy()
114 .replace('\\', "/");
115 }
116 }
117
118 if source_path
119 .components()
120 .next()
121 .map(|component| component.as_os_str() == manifest_root.as_os_str())
122 .unwrap_or(false)
123 {
124 return source_path.to_string_lossy().replace('\\', "/");
125 }
126
127 manifest_root
128 .join(source_path)
129 .to_string_lossy()
130 .replace('\\', "/")
131}
132
133fn source_path_of(stream: proc_macro::TokenStream) -> String {
134 let span = stream
135 .into_iter()
136 .next()
137 .map(|token| token.span())
138 .unwrap_or_else(proc_macro::Span::call_site);
139 environment_source_path(span)
140}