Skip to main content

wyrd/authoring/
macros.rs

1//! Authoring macros: [`pattern!`](crate::pattern), [`weave!`](crate::weave),
2//! and hidden expand helpers.
3//!
4//! The public surface lowers declarative definitions into the existing validated
5//! authoring types while retaining compile-time binding names.
6
7/// Builds and validates a reusable [`Pattern`](crate::Pattern).
8///
9/// The declaration order is `id`, optional `numeric`, `knots`, `exports`, then
10/// `threads`. Exports name input and output ports that the enclosing
11/// [`weave!`](crate::weave) can connect through `in(name)` and `out(name)`.
12/// Knot aliases control the authored ids used by exports and threads.
13///
14/// The macro evaluates each expression once in source order and delegates all
15/// structural and export validation to [`Pattern::try_from`](crate::Pattern).
16/// It returns `Result<Pattern, BuildError>`.
17///
18/// ```
19/// use wyrd::{pattern, KnotKind, Pattern};
20///
21/// fn pulse() -> Result<Pattern, wyrd::BuildError> {
22///     pattern! {
23///         id: "pulse";
24///         knots {
25///             edge as "edge.detect" = KnotKind::rising_from_zero();
26///             timer = KnotKind::timer(wyrd::TimerMode::PulseHold, 2);
27///         }
28///         exports {
29///             input start = edge.in;
30///             output active = timer.active;
31///         }
32///         threads {
33///             edge.out -> timer.start;
34///         }
35///     }
36/// }
37/// ```
38#[macro_export]
39macro_rules! pattern {
40    {
41        id: $id:expr;
42        numeric: $numeric:expr;
43        knots {
44            $( $knot:ident $(as $alias:literal)? = $kind:expr; )*
45        }
46        exports {
47            $( input $input_name:ident = $input_knot:ident . $input_port:ident; )*
48            $( output $output_name:ident = $output_knot:ident . $output_port:ident; )*
49        }
50        threads {
51            $( $from:ident . $from_port:ident -> $to:ident . $to_port:ident; )*
52        }
53    } => {
54        $crate::__pattern_expand! {
55            id: $id;
56            numeric: $numeric;
57            knots { $( $knot $(as $alias)? = $kind; )* }
58            exports {
59                $( input $input_name = $input_knot . $input_port; )*
60                $( output $output_name = $output_knot . $output_port; )*
61            }
62            threads { $( $from . $from_port -> $to . $to_port; )* }
63        }
64    };
65
66    {
67        id: $id:expr;
68        knots {
69            $( $knot:ident $(as $alias:literal)? = $kind:expr; )*
70        }
71        exports {
72            $( input $input_name:ident = $input_knot:ident . $input_port:ident; )*
73            $( output $output_name:ident = $output_knot:ident . $output_port:ident; )*
74        }
75        threads {
76            $( $from:ident . $from_port:ident -> $to:ident . $to_port:ident; )*
77        }
78    } => {
79        $crate::__pattern_expand! {
80            id: $id;
81            numeric: $crate::NumericPath::compiled();
82            knots { $( $knot $(as $alias)? = $kind; )* }
83            exports {
84                $( input $input_name = $input_knot . $input_port; )*
85                $( output $output_name = $output_knot . $output_port; )*
86            }
87            threads { $( $from . $from_port -> $to . $to_port; )* }
88        }
89    };
90}
91
92/// Implementation detail for [`pattern!`](crate::pattern).
93#[doc(hidden)]
94#[macro_export]
95macro_rules! __pattern_expand {
96    {
97        id: $id:expr;
98        numeric: $numeric:expr;
99        knots {
100            $( $knot:ident $(as $alias:literal)? = $kind:expr; )*
101        }
102        exports {
103            $( input $input_name:ident = $input_knot:ident . $input_port:ident; )*
104            $( output $output_name:ident = $output_knot:ident . $output_port:ident; )*
105        }
106        threads {
107            $( $from:ident . $from_port:ident -> $to:ident . $to_port:ident; )*
108        }
109    } => {{
110        (|| -> ::core::result::Result<$crate::Pattern, $crate::BuildError> {
111            // Duplicate bindings become duplicate struct fields, which makes
112            // this authoring mistake a compile error instead of shadowing.
113            #[allow(dead_code, non_camel_case_types)]
114            struct __PatternBindingNames {
115                $( $knot: (), )*
116            }
117
118            $( let $knot = $crate::__weave_author_id!($knot $(as $alias)?); )*
119            let __id: $crate::__private::String = ($id).into();
120            let mut __inner_id = __id.clone();
121            __inner_id.push_str(".inner");
122            let __numeric = $numeric;
123            Ok(<$crate::Pattern as ::core::convert::TryFrom<$crate::PatternDef>>::try_from(
124                $crate::PatternDef {
125                    id: __id.clone(),
126                    inner: $crate::WeaveDef {
127                        id: __inner_id,
128                        numeric: __numeric,
129                        knots: $crate::__private::Vec::from([
130                            $( $crate::KnotDef {
131                                id: $knot.into(),
132                                kind: $kind,
133                            }, )*
134                        ]),
135                        threads: $crate::__private::Vec::from([
136                            $( $crate::ThreadDef {
137                                from: $crate::PortRefDef::new($from, ::core::stringify!($from_port)),
138                                to: $crate::PortRefDef::new($to, ::core::stringify!($to_port)),
139                            }, )*
140                        ]),
141                    },
142                    inputs: $crate::__private::Vec::from([
143                        $( $crate::PatternExportDef::new(
144                            ::core::stringify!($input_name),
145                            $input_knot,
146                            ::core::stringify!($input_port),
147                        ), )*
148                    ]),
149                    outputs: $crate::__private::Vec::from([
150                        $( $crate::PatternExportDef::new(
151                            ::core::stringify!($output_name),
152                            $output_knot,
153                            ::core::stringify!($output_port),
154                        ), )*
155                    ]),
156                },
157            )?)
158        })()
159    }};
160}
161
162/// Builds and validates a [`Weave`](crate::Weave) with the typed graph API.
163///
164/// The declaration order is `id`, optional `numeric`, `knots`, optional
165/// `patterns`, then `threads`. Knot ports use identifier syntax (`source.out`),
166/// while pattern exports use `out(expr)` and `in(expr)`.
167///
168/// The macro is deliberately only syntax sugar: expressions are evaluated once
169/// in source order and all endpoint and graph checks are performed by
170/// [`WeaveBuilder`](crate::WeaveBuilder).
171///
172/// ```
173/// use wyrd::{weave, BuildError, KnotKind, SignalDomain, Weave};
174///
175/// fn graph() -> Result<Weave, BuildError> {
176///     weave! {
177///         id: "inverter";
178///         knots {
179///             source = KnotKind::signal_in(SignalDomain::Bool);
180///             invert = KnotKind::not();
181///             sink as "debug.inverted" = KnotKind::signal_out("debug.inverted", SignalDomain::Bool);
182///         }
183///         threads {
184///             source.out -> invert.in;
185///             invert.out -> sink.in;
186///         }
187///     }
188/// }
189/// ```
190#[macro_export]
191macro_rules! weave {
192    {
193        id: $id:expr;
194        numeric: $numeric:expr;
195        knots {
196            $( $knot:ident $(as $alias:literal)? = $kind:expr; )*
197        }
198        patterns {
199            $( $pattern_binding:ident = ($instance_id:expr, $pattern:expr); )*
200        }
201        threads { $($threads:tt)* }
202    } => {
203        $crate::__weave_expand! {
204            id: $id;
205            numeric: [$numeric];
206            knots { $( $knot $(as $alias)? = $kind; )* }
207            patterns { $( $pattern_binding = ($instance_id, $pattern); )* }
208            threads { $($threads)* }
209        }
210    };
211
212    {
213        id: $id:expr;
214        numeric: $numeric:expr;
215        knots {
216            $( $knot:ident $(as $alias:literal)? = $kind:expr; )*
217        }
218        threads { $($threads:tt)* }
219    } => {
220        $crate::__weave_expand! {
221            id: $id;
222            numeric: [$numeric];
223            knots { $( $knot $(as $alias)? = $kind; )* }
224            patterns { }
225            threads { $($threads)* }
226        }
227    };
228
229    {
230        id: $id:expr;
231        knots {
232            $( $knot:ident $(as $alias:literal)? = $kind:expr; )*
233        }
234        patterns {
235            $( $pattern_binding:ident = ($instance_id:expr, $pattern:expr); )*
236        }
237        threads { $($threads:tt)* }
238    } => {
239        $crate::__weave_expand! {
240            id: $id;
241            numeric: [];
242            knots { $( $knot $(as $alias)? = $kind; )* }
243            patterns { $( $pattern_binding = ($instance_id, $pattern); )* }
244            threads { $($threads)* }
245        }
246    };
247
248    {
249        id: $id:expr;
250        knots {
251            $( $knot:ident $(as $alias:literal)? = $kind:expr; )*
252        }
253        threads { $($threads:tt)* }
254    } => {
255        $crate::__weave_expand! {
256            id: $id;
257            numeric: [];
258            knots { $( $knot $(as $alias)? = $kind; )* }
259            patterns { }
260            threads { $($threads)* }
261        }
262    };
263}
264
265/// Implementation detail for [`weave!`](crate::weave).
266#[doc(hidden)]
267#[macro_export]
268macro_rules! __weave_expand {
269    {
270        id: $id:expr;
271        numeric: [$($numeric:expr)?];
272        knots {
273            $( $knot:ident $(as $alias:literal)? = $kind:expr; )*
274        }
275        patterns {
276            $( $pattern_binding:ident = ($instance_id:expr, $pattern:expr); )*
277        }
278        threads { $($threads:tt)* }
279    } => {{
280        (|| -> ::core::result::Result<$crate::Weave, $crate::BuildError> {
281            // Duplicate bindings become duplicate struct fields, which makes
282            // this authoring mistake a compile error instead of shadowing.
283            #[allow(dead_code, non_camel_case_types)]
284            struct __WeaveBindingNames {
285                $( $knot: (), )*
286                $( $pattern_binding: (), )*
287            }
288
289            let mut __builder = $crate::WeaveBuilder::new($id)?;
290            $( __builder.set_numeric($numeric)?; )?
291            $(
292                let $knot = __builder.knot(
293                    $crate::__weave_author_id!($knot $(as $alias)?),
294                    $kind,
295                )?;
296            )*
297            $(
298                let $pattern_binding = __builder.include($instance_id, $pattern)?;
299            )*
300            $crate::__weave_threads!(__builder; $($threads)*);
301            Ok(__builder.build()?)
302        })()
303    }};
304}
305
306/// Implementation detail for [`weave!`](crate::weave).
307#[doc(hidden)]
308#[macro_export]
309macro_rules! __weave_author_id {
310    ($binding:ident as $alias:literal) => {
311        $alias
312    };
313    ($binding:ident) => {
314        ::core::stringify!($binding)
315    };
316}
317
318/// Implementation detail for [`weave!`](crate::weave).
319#[doc(hidden)]
320#[macro_export]
321macro_rules! __weave_threads {
322    ($builder:ident;) => {};
323
324    ($builder:ident;
325        $from:ident . out($from_export:expr) ->
326        $to:ident . in($to_export:expr);
327        $($rest:tt)*
328    ) => {
329        let __from = $from.output($from_export)?;
330        let __to = $to.input($to_export)?;
331        $builder.connect(__from, __to)?;
332        $crate::__weave_threads!($builder; $($rest)*);
333    };
334
335    ($builder:ident;
336        $from:ident . out($from_export:expr) ->
337        $to:ident . $to_port:ident;
338        $($rest:tt)*
339    ) => {
340        let __from = $from.output($from_export)?;
341        let __to = $builder.input(&$to, ::core::stringify!($to_port))?;
342        $builder.connect(__from, __to)?;
343        $crate::__weave_threads!($builder; $($rest)*);
344    };
345
346    ($builder:ident;
347        $from:ident . $from_port:ident ->
348        $to:ident . in($to_export:expr);
349        $($rest:tt)*
350    ) => {
351        let __from = $builder.output(&$from, ::core::stringify!($from_port))?;
352        let __to = $to.input($to_export)?;
353        $builder.connect(__from, __to)?;
354        $crate::__weave_threads!($builder; $($rest)*);
355    };
356
357    ($builder:ident;
358        $from:ident . $from_port:ident ->
359        $to:ident . $to_port:ident;
360        $($rest:tt)*
361    ) => {
362        let __from = $builder.output(&$from, ::core::stringify!($from_port))?;
363        let __to = $builder.input(&$to, ::core::stringify!($to_port))?;
364        $builder.connect(__from, __to)?;
365        $crate::__weave_threads!($builder; $($rest)*);
366    };
367}