Skip to content

Commit 56095ec

Browse files
Merge pull request #127 from triblespace/codex/port-entity-macro-to-procedural
Port entity_inner macro to procedural
2 parents 5b924ad + c85a898 commit 56095ec

3 files changed

Lines changed: 155 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Test coverage for `branch_from` and `pull_with_key`.
1616
- `Workspace::checkout` helper to load commit contents.
1717
- `pattern!` now implemented as a procedural macro in the new `tribles-macros` crate.
18+
- `entity!` now implemented as a procedural macro alongside `pattern!`.
19+
- `entity!` subsumes the old `entity_inner!` helper; macro invocations can
20+
optionally provide an existing `TribleSet`.
1821
- Expanded documentation for the `pattern` procedural macro to ease maintenance, including detailed comments inside the implementation.
1922
- `EntityId` variants renamed to `Var` and `Lit` for consistency with field patterns.
2023
- `Workspace::checkout` now accepts commit ranges for convenient history queries.

src/namespace.rs

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,6 @@
1515
/// Hidden by default, used internally by the `entity!` macro.
1616
pub use hex_literal;
1717

18-
#[doc(hidden)]
19-
#[macro_export]
20-
macro_rules! entity_inner {
21-
($Namespace:path, $Set:expr, $EntityId:expr, {$($FieldName:ident : $Value:expr),* $(,)?}) => {
22-
{
23-
use $Namespace as ns;
24-
$(
25-
{ let v: $crate::value::Value<ns::schemas::$FieldName> = $crate::value::ToValue::to_value($Value);
26-
$Set.insert(&$crate::trible::Trible::new($EntityId, &ns::ids::$FieldName, &v)); }
27-
)*
28-
}
29-
};
30-
}
31-
32-
pub use entity_inner;
33-
3418
/// Defines a Rust module to represent a namespace, along with convenience macros.
3519
/// The `namespace` block maps human-readable names to attribute IDs and type schemas.
3620
#[macro_export]
@@ -72,20 +56,12 @@ macro_rules! NS {
7256
macro_rules! entity {
7357
($entity:tt) => {
7458
{
75-
use $crate::namespace::entity_inner;
76-
let mut set = $crate::trible::TribleSet::new();
77-
let id: $crate::id::ExclusiveId = $crate::id::rngid();
78-
entity_inner!($mod_name, &mut set, &id, $entity);
79-
set
59+
::tribles_macros::entity!(::tribles, $mod_name, $entity)
8060
}
8161
};
8262
($entity_id:expr, $entity:tt) => {
8363
{
84-
use $crate::namespace::entity_inner;
85-
let mut set = $crate::trible::TribleSet::new();
86-
let id: &$crate::id::ExclusiveId = $entity_id;
87-
entity_inner!($mod_name, &mut set, id, $entity);
88-
set
64+
::tribles_macros::entity!(::tribles, $mod_name, $entity_id, $entity)
8965
}
9066
};
9167
}

tribles-macros/src/lib.rs

Lines changed: 150 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,27 @@
33
//! The macros here mirror the declarative macros defined in
44
//! [`src/namespace.rs`](https://github.com/tribles/tribles-rust/blob/main/src/namespace.rs)
55
//! 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.
911
//!
1012
//! ```ignore
1113
//! ::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 });
1216
//! ```
1317
//!
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.
1927
//!
2028
//! These macros are internal implementation details and should not be used
2129
//! directly outside of the `tribles` codebase.
@@ -264,3 +272,137 @@ fn pattern_impl(input: TokenStream) -> syn::Result<TokenStream> {
264272

265273
Ok(output.into())
266274
}
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

Comments
 (0)