xso/dynxso/linktime.rs
1use alloc::boxed::Box;
2use core::{cmp::Ordering, marker::PhantomData};
3
4use crate::{
5 dynxso::MayContain,
6 error::{Error, FromEventsError},
7 fromxml::XmlNameMatcher,
8 Context, FromEventsBuilder, FromXml,
9};
10
11/// Dynamic XSO traits using a link-time registry
12///
13/// This macro is used for (a) declaring [`DynXso`][`crate::dynxso::DynXso`]
14/// traits using a registry filled at link-time and (b) for registering types
15/// with a trait.
16///
17/// ## Declaring a link-time `DynXso` trait
18///
19/// To declare a trait as a link-time `DynXso` trait, wrap its declaration in
20/// a `linktime!` invocation.
21///
22/// ### Example
23///
24/// ```rust
25/// use xso::{dynxso::linktime, asxml::AsXmlDyn};
26///
27/// linktime! {
28/// pub trait MyPayload: AsXmlDyn {}
29/// }
30/// ```
31///
32/// ### Usage notes
33///
34/// - A bound on [`Any`][`core::any::Any`] is automatically added, because it
35/// is a requirement for implementing [`DynXso`][`crate::dynxso::DynXso`]
36/// on `dyn Trait`.
37/// - If you want to specify more than one trait bound, you have to separate
38/// them with `,` instead of `+`, due to rustc limitations with declarative
39/// macros.
40/// - A trait bound on [`AsXmlDyn`][`crate::asxml::AsXmlDyn`] is required (but
41/// has to be added manually, as seen in the above example) for
42/// [`Xso<dyn Trait>`][`crate::dynxso::Xso`] to implement
43/// [`AsXml`][`crate::AsXml`].
44/// - This macro automatically calls [`crate::derive_dyn_traits!`], but does
45/// not use the default `BuilderRegistry`, removing the `std` dependency at
46/// the cost of being unable to register more types at runtime.
47///
48/// ## Implement a `DynXso` trait
49///
50/// To implement a trait declared with `linktime!`, wrap the `impl` block in
51/// a `linktime!` invocation.
52///
53/// Doing so allows a [`Xso<dyn Trait>`][`crate::dynxso::Xso`]'s [`FromXml`]
54/// implementation to parse the registered type.
55///
56/// ### Example
57///
58#[cfg_attr(
59 not(feature = "macros"),
60 doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
61)]
62#[cfg_attr(feature = "macros", doc = "\n```\n")]
63/// use xso::{dynxso::{linktime, Xso}, FromXml, AsXml, asxml::AsXmlDyn, from_bytes};
64///
65/// linktime! {
66/// trait Foo: AsXmlDyn {}
67/// }
68///
69/// #[derive(FromXml, AsXml, Debug, Clone, Copy)]
70/// #[xml(namespace = "urn:example", name = "a")]
71/// struct A {}
72///
73/// #[derive(FromXml, AsXml, Debug, Clone, Copy)]
74/// #[xml(namespace = "urn:example", name = "b")]
75/// struct B {}
76///
77/// linktime! {
78/// impl Foo for A {}
79/// impl Foo for B {}
80/// }
81///
82/// let foo: Xso<dyn Foo> = from_bytes(b"<a xmlns='urn:example'/>").unwrap();
83/// let foo: Xso<dyn Foo> = from_bytes(b"<b xmlns='urn:example'/>").unwrap();
84/// ```
85///
86/// ### Usage notes
87///
88/// - `linktime!` can be placed anywhere; unlike the `Xso::register_type`
89/// calls needed with the
90/// [`BuilderRegistry`][`crate::dynxso::BuilderRegistry`], registrations are
91/// handled at compile-time (or more precisely: at link-time, with a minimal
92/// startup overhead of postprocessing the list generated by the linker).
93#[doc(hidden)]
94#[macro_export]
95macro_rules! __linktime {
96 // First variant: declaring a trait.
97 (
98 $(
99 $(#[$meta:meta])*
100 $vis:vis trait $trait:ident $(: $bound:path $(, $bound2:path)*)? {
101 $($item:item)*
102 }
103 )*
104 ) => {
105 $(
106 // Just "copy" the trait decl over, but add the mandatory bound on
107 // Any.
108 $(#[$meta])*
109 $vis trait $trait: core::any::Any $(+ $bound $(+ $bound2)*)? {
110 $($item)*
111 }
112
113 $crate::exports::paste::paste! {
114 #[doc = concat!("Link-time type registry for ", stringify!($trait), ".\n\nGenerated by the `linktime!` macro in the `xso` crate.")]
115 #[doc(hidden)]
116 $vis struct [<$trait LinktimeRegistry>]();
117
118 $crate::exports::scattered_collect::declarative::gather! {
119 #[gather]
120 #[doc = concat!("Link-time type registry data for ", stringify!($trait), ".\n\nGenerated by the `linktime!` macro in the `xso` crate.")]
121 #[doc(hidden)]
122 $vis static [<$trait:upper _LINKTIME_REGISTRY_DATA>]: $crate::exports::scattered_collect::ScatteredSortedSlice<
123 $crate::dynxso::linktime::BuilderRegistryEntry<dyn $trait>
124 >;
125 }
126
127 impl $crate::dynxso::registry::DynXsoRegistryLookup<dyn $trait> for [<$trait LinktimeRegistry>] {
128 fn make_builder(
129 &self,
130 name: $crate::exports::rxml::QName,
131 attrs: $crate::exports::rxml::AttrMap,
132 ctx: &$crate::Context<'_>,
133 ) -> Result<
134 Box<dyn $crate::FromEventsBuilder<Output = Box<dyn $trait>>>,
135 $crate::error::FromEventsError,
136 > {
137 let registry = &[<$trait:upper _LINKTIME_REGISTRY_DATA>][..];
138 $crate::dynxso::linktime::make_builder(registry, name, attrs, ctx)
139 }
140 }
141
142 // This is a fun trick!
143 //
144 // You'd think that that wouldn't work, because statics and traits
145 // cannot share the same name, but in fact, they can! And the
146 // particularly good thing about this is that macros can, too,
147 // because what is *actually* relevant is the macro, not the
148 // static: the scatter! macro needs access to a hidden macro
149 // generated by the gather! trait and named exactly like the
150 // registry static.
151 $vis use [<$trait:upper _LINKTIME_REGISTRY_DATA>] as $trait;
152
153 $crate::derive_dyn_traits!(
154 $trait
155 use [<$trait LinktimeRegistry>] = [<$trait LinktimeRegistry>]()
156 );
157 }
158 )*
159 };
160
161 // Second variant: Implementing a trait.
162 (
163 $(
164 $(#[$meta:meta])*
165 impl $trait_seg1:ident $(:: $trait_segn:ident)* for $ty:ty {
166 $($item:item)*
167 }
168 )*
169 ) => {
170 $(
171 // Just "copy" the `impl` block over.
172 impl $trait_seg1 $(:: $trait_segn)* for $ty {
173 $($item)*
174 }
175
176 $crate::exports::scattered_collect::declarative::scatter! {
177 // Register the type using linker magic.
178 //
179 // The `scatter!` macro invocation needs us to pass the
180 // `static` we declared above with the `gather!` macro (well,
181 // in fact it needs the macro which overloads the `static`).
182 #[scatter($trait_seg1 $(:: $trait_segn)*)]
183 const _: $crate::dynxso::linktime::BuilderRegistryEntry<dyn $trait_seg1 $(:: $trait_segn)*> = $crate::dynxso::linktime::BuilderRegistryEntry {
184 matcher: <$ty as $crate::FromXml>::XML_NAME_MATCHER,
185 builder: $crate::dynxso::linktime::build_boxed::<$ty, dyn $trait_seg1 $(:: $trait_segn)*>,
186 };
187 }
188 )*
189 }
190}
191
192/// A function to attempt to construct a specific XSO.
193///
194/// This can be used as a type-erased [`FromXml::from_events`] if `T` is a
195/// `dyn Trait`.
196///
197/// **NOT** part of the public API -- only exposed for use by macros.
198#[doc(hidden)]
199pub type Builder<T> = fn(
200 rxml::QName,
201 rxml::AttrMap,
202 &Context<'_>,
203) -> Result<Box<dyn FromEventsBuilder<Output = Box<T>>>, FromEventsError>;
204
205/// Entry in a linktime registry of builder functions for `T`.
206///
207/// **NOT** part of the public API -- only exposed for use by macros.
208#[doc(hidden)]
209pub struct BuilderRegistryEntry<T: ?Sized> {
210 pub matcher: XmlNameMatcher<'static>,
211 pub builder: Builder<T>,
212}
213
214impl<T: ?Sized> PartialEq for BuilderRegistryEntry<T> {
215 fn eq(&self, other: &Self) -> bool {
216 (self.matcher, self.builder) == (other.matcher, other.builder)
217 }
218}
219
220impl<T: ?Sized> Eq for BuilderRegistryEntry<T> {}
221
222impl<T: ?Sized> Ord for BuilderRegistryEntry<T> {
223 fn cmp(&self, other: &Self) -> Ordering {
224 (&self.matcher, self.builder).cmp(&(&other.matcher, other.builder))
225 }
226}
227
228impl<T: ?Sized> PartialOrd for BuilderRegistryEntry<T> {
229 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
230 Some(self.cmp(other))
231 }
232}
233
234/// Look up builders matching the result of `matcher_builder` in `registry`
235/// and try initialising them in the order they are in the registry.
236///
237/// If a builder returns [`FromEventsError::Invalid`], that error is propagated
238/// immediately. If a builder returns [`FromEventsError::Mismatch`], the next
239/// matching builder is tried. If there are no more matching builders, the
240/// function returns the `Mismatch` error.
241///
242/// `matcher_builder` is a closure instead of an argument, because we want to
243/// borrow from the `name`, which must be passed by value to `from_events`.
244fn try_build<T: ?Sized>(
245 registry: &[BuilderRegistryEntry<T>],
246 mut name: rxml::QName,
247 mut attrs: rxml::AttrMap,
248 ctx: &Context<'_>,
249 matcher_builder: impl for<'x> FnOnce(&'x rxml::QName) -> XmlNameMatcher<'x>,
250) -> Result<Box<dyn FromEventsBuilder<Output = Box<T>>>, FromEventsError> {
251 let matcher = matcher_builder(&name);
252 let start_scan_at = registry.partition_point(|entry| entry.matcher < matcher);
253 let end_scan_at = registry.partition_point(|entry| entry.matcher >= matcher);
254
255 for entry in ®istry[start_scan_at..end_scan_at] {
256 if !entry.matcher.matches(&name) {
257 return Err(FromEventsError::Mismatch { name, attrs });
258 }
259
260 match (entry.builder)(name, attrs, ctx) {
261 Ok(v) => return Ok(v),
262 Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)),
263 Err(FromEventsError::Mismatch {
264 name: new_name,
265 attrs: new_attrs,
266 }) => {
267 name = new_name;
268 attrs = new_attrs;
269 }
270 }
271 }
272
273 Err(FromEventsError::Mismatch { name, attrs })
274}
275
276/// Find and start a builder matching the given `name` and `attrs` from the
277/// `registry`.
278///
279/// **NOT** part of the public API -- only exposed for use by macros.
280#[doc(hidden)]
281pub fn make_builder<T: ?Sized>(
282 registry: &[BuilderRegistryEntry<T>],
283 name: rxml::QName,
284 attrs: rxml::AttrMap,
285 ctx: &Context<'_>,
286) -> Result<Box<dyn FromEventsBuilder<Output = Box<T>>>, FromEventsError> {
287 let (name, attrs) = match try_build(registry, name, attrs, ctx, |qname| {
288 XmlNameMatcher::Specific(qname.0.as_str(), qname.1.as_str())
289 }) {
290 Ok(v) => return Ok(v),
291 Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)),
292 Err(FromEventsError::Mismatch { name, attrs }) => (name, attrs),
293 };
294
295 let (name, attrs) = match try_build(registry, name, attrs, ctx, |qname| {
296 XmlNameMatcher::InNamespace(qname.0.as_str())
297 }) {
298 Ok(v) => return Ok(v),
299 Err(FromEventsError::Invalid(e)) => return Err(FromEventsError::Invalid(e)),
300 Err(FromEventsError::Mismatch { name, attrs }) => (name, attrs),
301 };
302
303 try_build(registry, name, attrs, ctx, |_| XmlNameMatcher::Any)
304}
305
306/// Build a `U` using its [`FromXml`] implementation and return it as `Box<T>`.
307///
308/// **NOT** part of the public API -- only exposed for use by macros.
309#[doc(hidden)]
310pub fn build_boxed<U: FromXml + 'static, T: ?Sized + 'static>(
311 name: rxml::QName,
312 attrs: rxml::AttrMap,
313 ctx: &Context<'_>,
314) -> Result<Box<dyn crate::FromEventsBuilder<Output = Box<T>>>, FromEventsError>
315where
316 T: super::MayContain<U>,
317{
318 struct Wrapper<B, X: ?Sized> {
319 inner: B,
320 output: PhantomData<X>,
321 }
322
323 impl<X: ?Sized, O, B: FromEventsBuilder<Output = O>> FromEventsBuilder for Wrapper<B, X>
324 where
325 X: MayContain<O>,
326 {
327 type Output = Box<X>;
328
329 fn feed(
330 &mut self,
331 ev: rxml::Event,
332 ctx: &Context<'_>,
333 ) -> Result<Option<Self::Output>, Error> {
334 self.inner
335 .feed(ev, ctx)
336 .map(|x| x.map(|x| <X as MayContain<O>>::upcast_into(x)))
337 }
338 }
339
340 <U as crate::FromXml>::from_events(name, attrs, ctx).map(|builder| {
341 Box::new(Wrapper {
342 inner: builder,
343 output: PhantomData,
344 }) as Box<dyn FromEventsBuilder<Output = Box<T>>>
345 })
346}