|
3 | 3 | //! The macros here mirror the declarative macros defined in |
4 | 4 | //! [`src/namespace.rs`](https://github.com/tribles/tribles-rust/blob/main/src/namespace.rs) |
5 | 5 | //! but are implemented with `proc_macro` to allow more complex analysis and |
6 | | -//! additional features in the future. Currently the crate exposes a single |
7 | | -//! [`pattern!`] macro which expands the namespace pattern syntax into an |
8 | | -//! [`IntersectionConstraint`] of query constraints. |
| 6 | +//! additional features in the future. The crate currently exposes two macros: |
| 7 | +//! [`pattern!`], which expands namespace patterns into an |
| 8 | +//! [`IntersectionConstraint`] of query constraints, and [`entity!`], which |
| 9 | +//! constructs [`TribleSet`]s from namespace field assignments or inserts |
| 10 | +//! triples into an existing set. |
9 | 11 | //! |
10 | 12 | //! ```ignore |
11 | 13 | //! ::tribles_macros::pattern!(::tribles, my_ns, &set, [ { field: (42) } ]); |
| 14 | +//! ::tribles_macros::entity!(::tribles, my_ns, { field: 42 }); |
| 15 | +//! ::tribles_macros::entity!(::tribles, my_ns, &mut set, id, { field: 42 }); |
12 | 16 | //! ``` |
13 | 17 | //! |
14 | | -//! The macro expects the crate path, a namespace module, a dataset expression |
15 | | -//! implementing [`TriblePattern`], and a bracketed list of entity patterns. |
16 | | -//! Each entity pattern may specify an identifier using `ident @` or `(expr) @` |
17 | | -//! notation and contains `field: value` pairs. Values can either reference an |
18 | | -//! existing query variable or be written as `(expr)` to match a literal. |
| 18 | +//! The `pattern` macro expects the crate path, a namespace module, a dataset |
| 19 | +//! expression implementing [`TriblePattern`], and a bracketed list of entity |
| 20 | +//! patterns. Each entity pattern may specify an identifier using `ident @` or |
| 21 | +//! `(expr) @` notation and contains `field: value` pairs. Values can either |
| 22 | +//! reference an existing query variable or be written as `(expr)` to match a |
| 23 | +//! literal. |
| 24 | +//! |
| 25 | +//! The `entity` macro similarly starts with the crate and namespace paths and |
| 26 | +//! optionally an explicit entity ID expression before the field list. |
19 | 27 | //! |
20 | 28 | //! These macros are internal implementation details and should not be used |
21 | 29 | //! directly outside of the `tribles` codebase. |
@@ -264,3 +272,137 @@ fn pattern_impl(input: TokenStream) -> syn::Result<TokenStream> { |
264 | 272 |
|
265 | 273 | Ok(output.into()) |
266 | 274 | } |
| 275 | + |
| 276 | +/// Parsed input for the [`entity`] macro. |
| 277 | +/// |
| 278 | +/// Invocation forms: |
| 279 | +/// `crate_path, namespace_path, { field: value, ... }` |
| 280 | +/// `crate_path, namespace_path, id_expr, { field: value, ... }` |
| 281 | +/// `crate_path, namespace_path, set_expr, id_expr, { field: value, ... }` |
| 282 | +struct EntityInput { |
| 283 | + crate_path: Path, |
| 284 | + ns: Path, |
| 285 | + set: Option<Expr>, |
| 286 | + id: Option<Expr>, |
| 287 | + fields: Vec<(Ident, Expr)>, |
| 288 | +} |
| 289 | + |
| 290 | +impl Parse for EntityInput { |
| 291 | + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { |
| 292 | + let crate_path: Path = input.parse()?; |
| 293 | + input.parse::<Token![,]>()?; |
| 294 | + let ns: Path = input.parse()?; |
| 295 | + input.parse::<Token![,]>()?; |
| 296 | + |
| 297 | + let mut set = None; |
| 298 | + let mut id = None; |
| 299 | + |
| 300 | + if input.peek(syn::token::Brace) { |
| 301 | + // no id, no set |
| 302 | + } else { |
| 303 | + let expr1: Expr = input.parse()?; |
| 304 | + input.parse::<Token![,]>()?; |
| 305 | + if input.peek(syn::token::Brace) { |
| 306 | + id = Some(expr1); |
| 307 | + } else { |
| 308 | + set = Some(expr1); |
| 309 | + let id_expr: Expr = input.parse()?; |
| 310 | + input.parse::<Token![,]>()?; |
| 311 | + id = Some(id_expr); |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + let content; |
| 316 | + braced!(content in input); |
| 317 | + let mut fields = Vec::new(); |
| 318 | + while !content.is_empty() { |
| 319 | + let name: Ident = content.parse()?; |
| 320 | + content.parse::<Token![:]>()?; |
| 321 | + let value: Expr = content.parse()?; |
| 322 | + fields.push((name, value)); |
| 323 | + if content.peek(Token![,]) { |
| 324 | + content.parse::<Token![,]>()?; |
| 325 | + } |
| 326 | + } |
| 327 | + |
| 328 | + Ok(EntityInput { |
| 329 | + crate_path, |
| 330 | + ns, |
| 331 | + set, |
| 332 | + id, |
| 333 | + fields, |
| 334 | + }) |
| 335 | + } |
| 336 | +} |
| 337 | + |
| 338 | +/// Procedural implementation of the `entity!` macro. |
| 339 | +#[proc_macro] |
| 340 | +pub fn entity(input: TokenStream) -> TokenStream { |
| 341 | + match entity_impl(input) { |
| 342 | + Ok(ts) => ts, |
| 343 | + Err(e) => e.to_compile_error().into(), |
| 344 | + } |
| 345 | +} |
| 346 | + |
| 347 | +fn entity_impl(input: TokenStream) -> syn::Result<TokenStream> { |
| 348 | + let EntityInput { |
| 349 | + crate_path, |
| 350 | + ns, |
| 351 | + set, |
| 352 | + id, |
| 353 | + fields, |
| 354 | + } = syn::parse(input)?; |
| 355 | + |
| 356 | + let (set_init, set_expr) = if let Some(s) = &set { |
| 357 | + (TokenStream2::new(), quote! { #s }) |
| 358 | + } else { |
| 359 | + ( |
| 360 | + quote! { let mut set = #crate_path::trible::TribleSet::new(); }, |
| 361 | + quote! { set }, |
| 362 | + ) |
| 363 | + }; |
| 364 | + |
| 365 | + let (id_init, id_expr) = if let Some(expr) = id { |
| 366 | + ( |
| 367 | + quote! { let id_ref: &#crate_path::id::ExclusiveId = #expr; }, |
| 368 | + quote! { id_ref }, |
| 369 | + ) |
| 370 | + } else { |
| 371 | + ( |
| 372 | + quote! { |
| 373 | + let id_tmp: #crate_path::id::ExclusiveId = #crate_path::id::rngid(); |
| 374 | + let id_ref: &#crate_path::id::ExclusiveId = &id_tmp; |
| 375 | + }, |
| 376 | + quote! { id_ref }, |
| 377 | + ) |
| 378 | + }; |
| 379 | + |
| 380 | + let mut insert_tokens = TokenStream2::new(); |
| 381 | + for (field, value) in fields { |
| 382 | + let stmt = quote! { |
| 383 | + { |
| 384 | + use #ns as ns; |
| 385 | + let v: #crate_path::value::Value<ns::schemas::#field> = |
| 386 | + #crate_path::value::ToValue::to_value(#value); |
| 387 | + #set_expr.insert(&#crate_path::trible::Trible::new(#id_expr, &ns::ids::#field, &v)); |
| 388 | + } |
| 389 | + }; |
| 390 | + insert_tokens.extend(stmt); |
| 391 | + } |
| 392 | + |
| 393 | + let output = if set.is_some() { |
| 394 | + quote! {{ |
| 395 | + #id_init |
| 396 | + #insert_tokens |
| 397 | + }} |
| 398 | + } else { |
| 399 | + quote! {{ |
| 400 | + #set_init |
| 401 | + #id_init |
| 402 | + #insert_tokens |
| 403 | + set |
| 404 | + }} |
| 405 | + }; |
| 406 | + |
| 407 | + Ok(output.into()) |
| 408 | +} |
0 commit comments