1use proc_macro::Span;
2use proc_macro::TokenStream;
3
4use proc_macro2::TokenStream as TokenStream2;
5use quote::{quote, ToTokens};
6
7use std::path::Path;
8
9use ed25519_dalek::SigningKey;
10use hex::FromHex;
11
12use triblespace_core::id::fucid;
13use triblespace_core::id::Id;
14use triblespace_core::repo::pile::Pile;
15use triblespace_core::repo::Repository;
16use triblespace_core::repo::Workspace;
17use triblespace_core::trible::TribleSet;
18use triblespace_core::value::schemas::hash::Blake3;
19
20use syn::parse::Parse;
21use syn::parse::ParseStream;
22use syn::Attribute;
23use syn::Ident;
24use syn::LitStr;
25use syn::Token;
26use syn::Type;
27use syn::Visibility;
28
29use triblespace_macros_common::{
30 attributes_impl, entity_impl, path_impl, pattern_changes_impl, pattern_impl,
31 value_formatter_impl,
32};
33
34mod instrumentation_attributes {
35 pub(crate) mod attribute {
39 use triblespace_core::blob::schemas::longstring::LongString;
40 use triblespace_core::prelude::valueschemas::{Blake3, Handle, ShortString};
41 use triblespace_core_macros::attributes;
42
43 attributes! {
44 "19D4972B2DF977FA64541FC967C4B133" as invocation: ShortString;
46 "D97A427FF782B0BF08B55AC84877B486" as attribute_type: Handle<Blake3, LongString>;
48 }
49 }
50
51 pub(crate) mod invocation {
52 use triblespace_core::blob::schemas::longstring::LongString;
53 use triblespace_core::prelude::valueschemas::{Blake3, Handle, LineLocation, ShortString};
54 use triblespace_core_macros::attributes;
55
56 attributes! {
57 "1CED5213A71C9DD60AD9B3698E5548F4" as macro_kind: ShortString;
58 "E413CB09A4352D7B46B65FC635C18CCC" as manifest_dir: Handle<Blake3, LongString>;
59 "8ED33DA54C226ADEA0FFF7863563DF5F" as source_range: LineLocation;
60 "B981AEA9437561F8DB96E7EECBB94BFD" as source_tokens: Handle<Blake3, LongString>;
61 "92EF719DA3DD2405E89B953837E076A5" as crate_name: ShortString;
62 }
63 }
64}
65
66use instrumentation_attributes::attribute;
67use instrumentation_attributes::invocation;
68
69fn invocation_span(input: &TokenStream) -> Span {
70 let mut iter = input.clone().into_iter();
71 iter.next()
72 .map(|tt| tt.span())
73 .unwrap_or_else(Span::call_site)
74}
75
76fn parse_signing_key(value: &str) -> Option<[u8; 32]> {
77 <[u8; 32]>::from_hex(value).ok()
78}
79
80fn metadata_signing_key() -> Option<SigningKey> {
81 let value = std::env::var("TRIBLESPACE_METADATA_SIGNING_KEY").ok()?;
82 let bytes = parse_signing_key(&value)?;
83 Some(SigningKey::from_bytes(&bytes))
84}
85
86fn parse_branch_id(value: &str) -> Option<Id> {
87 Id::from_hex(value)
88}
89
90struct MetadataContext<'a> {
91 workspace: &'a mut Workspace<Pile<Blake3>>,
92 invocation_id: triblespace_core::id::Id,
93 input: &'a TokenStream,
94}
95
96impl<'a> MetadataContext<'a> {
97 fn workspace(&mut self) -> &mut Workspace<Pile<Blake3>> {
98 self.workspace
99 }
100
101 fn invocation_id(&self) -> triblespace_core::id::Id {
102 self.invocation_id
103 }
104
105 fn tokens(&self) -> &'a TokenStream {
106 self.input
107 }
108}
109
110fn emit_metadata<F>(kind: &str, input: &TokenStream, extra: F)
111where
112 F: FnOnce(&mut MetadataContext<'_>),
113{
114 let pile_path = match std::env::var("TRIBLESPACE_METADATA_PILE") {
115 Ok(p) if !p.trim().is_empty() => p,
116 _ => return,
117 };
118
119 let branch_value = match std::env::var("TRIBLESPACE_METADATA_BRANCH") {
120 Ok(b) if !b.trim().is_empty() => b,
121 _ => return,
122 };
123
124 let branch_id = match parse_branch_id(&branch_value) {
125 Some(id) => id,
126 None => return,
127 };
128
129 let pile = match Pile::<Blake3>::open(Path::new(&pile_path)) {
130 Ok(pile) => pile,
131 Err(_) => return,
132 };
133
134 let signing_key = match metadata_signing_key() {
135 Some(key) => key,
136 None => {
137 let _ = pile.close();
139 return;
140 }
141 };
142 let mut repo = match Repository::new(pile, signing_key, TribleSet::new()) {
143 Ok(r) => r,
144 Err(_) => return,
145 };
146
147 let mut workspace = match repo.pull(branch_id) {
148 Ok(ws) => ws,
149 Err(_) => {
150 let _ = repo.close();
151 return;
152 }
153 };
154
155 let span = invocation_span(input);
156 let mut set = TribleSet::new();
157 let entity = fucid();
158 let invocation_id = entity.id;
159
160 set += ::triblespace_core::macros::entity! {
161 &entity @
162 invocation::macro_kind: kind,
163 invocation::source_range: span
164 };
165
166 if let Ok(crate_name) = std::env::var("CARGO_PKG_NAME") {
167 set += ::triblespace_core::macros::entity! { &entity @ invocation::crate_name: crate_name };
168 }
169
170 if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
171 if !dir.trim().is_empty() {
172 let handle = workspace.put(dir);
173 set +=
174 ::triblespace_core::macros::entity! { &entity @ invocation::manifest_dir: handle };
175 }
176 }
177
178 let tokens = input.to_string();
179 if !tokens.is_empty() {
180 let handle = workspace.put(tokens);
181 set += ::triblespace_core::macros::entity! { &entity @ invocation::source_tokens: handle };
182 }
183
184 if set.is_empty() {
185 let _ = repo.close();
186 return;
187 }
188
189 workspace.commit(set, "macro invocation");
190
191 {
192 let mut context = MetadataContext {
193 workspace: &mut workspace,
194 invocation_id,
195 input,
196 };
197 extra(&mut context);
198 }
199
200 let _ = repo.push(&mut workspace);
201
202 drop(workspace);
203 let _ = repo.close();
204}
205
206struct AttributeDefinition {
207 id: LitStr,
208 name: Ident,
209 ty: Type,
210}
211
212struct AttributeDefinitions {
213 entries: Vec<AttributeDefinition>,
214}
215
216impl Parse for AttributeDefinitions {
217 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
218 let mut entries = Vec::new();
219 while !input.is_empty() {
220 let _ = input.call(Attribute::parse_outer)?;
221 if input.peek(Token![pub]) {
222 let v: Visibility = input.parse()?;
223 return Err(syn::Error::new_spanned(
224 v,
225 "visibility must appear after `as` and before the attribute name (e.g. `\"...\" as pub name: Type;`)",
226 ));
227 }
228
229 let id: LitStr = input.parse()?;
230 input.parse::<Token![as]>()?;
231 if input.peek(Token![pub]) {
232 let _: Visibility = input.parse()?;
233 }
234 let name: Ident = input.parse()?;
235 input.parse::<Token![:]>()?;
236 let ty: Type = input.parse()?;
237 input.parse::<Token![;]>()?;
238
239 entries.push(AttributeDefinition { id, name, ty });
240 }
241 Ok(AttributeDefinitions { entries })
242 }
243}
244
245fn emit_attribute_definitions(context: &mut MetadataContext<'_>) {
246 use triblespace_core::metadata;
247 use triblespace_core::prelude::ValueSchema;
248 use triblespace_core::value::schemas::genid::GenId;
249
250 let Ok(parsed) =
251 syn::parse2::<AttributeDefinitions>(TokenStream2::from(context.tokens().clone()))
252 else {
253 return;
254 };
255 if parsed.entries.is_empty() {
256 return;
257 }
258
259 let invocation_hex = format!("{:X}", context.invocation_id());
260
261 for definition in parsed.entries {
262 let entity = fucid();
263
264 let Some(attr_id) = Id::from_hex(&definition.id.value()) else {
266 continue;
267 };
268
269 let name_handle = context.workspace().put(definition.name.to_string());
270 let mut set = ::triblespace_core::macros::entity! {
271 &entity @
272 metadata::attribute: GenId::value_from(attr_id),
273 metadata::name: name_handle,
274 metadata::tag: metadata::KIND_ATTRIBUTE_USAGE,
275 attribute::invocation: invocation_hex.as_str()
276 };
277
278 let ty_tokens = definition.ty.to_token_stream().to_string();
279 if !ty_tokens.is_empty() {
280 let handle = context.workspace().put(ty_tokens);
281 set +=
282 ::triblespace_core::macros::entity! { &entity @ attribute::attribute_type: handle };
283 }
284
285 context.workspace().commit(set, "macro invocation");
286 }
287}
288
289#[proc_macro]
290pub fn attributes(input: TokenStream) -> TokenStream {
291 let clone = input.clone();
292 emit_metadata("attributes", &clone, |context| {
293 emit_attribute_definitions(context)
294 });
295 let base_path: TokenStream2 = quote!(::triblespace::core);
296 let tokens = TokenStream2::from(input);
297 match attributes_impl(tokens, &base_path) {
298 Ok(ts) => TokenStream::from(ts),
299 Err(e) => e.to_compile_error().into(),
300 }
301}
302
303#[proc_macro]
304pub fn path(input: TokenStream) -> TokenStream {
305 let clone = input.clone();
306 emit_metadata("path", &clone, |_context| {});
307 let base_path: TokenStream2 = quote!(::triblespace::core);
308 let tokens = TokenStream2::from(input);
309 match path_impl(tokens, &base_path) {
310 Ok(ts) => TokenStream::from(ts),
311 Err(e) => e.to_compile_error().into(),
312 }
313}
314
315#[proc_macro]
316pub fn pattern(input: TokenStream) -> TokenStream {
317 let clone = input.clone();
318 emit_metadata("pattern", &clone, |_context| {});
319 let base_path: TokenStream2 = quote!(::triblespace::core);
320 let tokens = TokenStream2::from(input);
321 match pattern_impl(tokens, &base_path) {
322 Ok(ts) => TokenStream::from(ts),
323 Err(e) => e.to_compile_error().into(),
324 }
325}
326
327#[proc_macro]
328pub fn pattern_changes(input: TokenStream) -> TokenStream {
329 let clone = input.clone();
330 emit_metadata("pattern_changes", &clone, |_context| {});
331 let base_path: TokenStream2 = quote!(::triblespace::core);
332 let tokens = TokenStream2::from(input);
333 match pattern_changes_impl(tokens, &base_path) {
334 Ok(ts) => TokenStream::from(ts),
335 Err(e) => e.to_compile_error().into(),
336 }
337}
338
339#[proc_macro]
340pub fn entity(input: TokenStream) -> TokenStream {
341 let clone = input.clone();
342 emit_metadata("entity", &clone, |_context| {});
343 let base_path: TokenStream2 = quote!(::triblespace::core);
344 let tokens = TokenStream2::from(input);
345 match entity_impl(tokens, &base_path) {
346 Ok(ts) => TokenStream::from(ts),
347 Err(e) => e.to_compile_error().into(),
348 }
349}
350
351#[proc_macro]
352pub fn find(input: TokenStream) -> TokenStream {
353 let clone = input.clone();
354 emit_metadata("find", &clone, |_context| {});
355 let inner = TokenStream2::from(input);
356 TokenStream::from(quote!(::triblespace::core::macros::find!(#inner)))
357}
358
359#[proc_macro]
360pub fn exists(input: TokenStream) -> TokenStream {
361 let clone = input.clone();
362 emit_metadata("exists", &clone, |_context| {});
363 let inner = TokenStream2::from(input);
364 TokenStream::from(quote!(::triblespace::core::exists!(#inner)))
365}
366
367#[proc_macro_attribute]
368pub fn value_formatter(attr: TokenStream, item: TokenStream) -> TokenStream {
369 let clone = item.clone();
370 emit_metadata("value_formatter", &clone, |_context| {});
371
372 match value_formatter_impl(TokenStream2::from(attr), TokenStream2::from(item)) {
373 Ok(tokens) => TokenStream::from(tokens),
374 Err(err) => err.to_compile_error().into(),
375 }
376}