conflagrate_macros/
lib.rs

1//! See the [conflagrate](https://docs.rs/conflagrate) documentation for details about the macros
2//! contained in this crate.
3mod dependency;
4mod funcutils;
5mod graph;
6mod nodetype;
7
8use crate::dependency::dependency_impl;
9use crate::graph::graph_impl;
10use crate::nodetype::nodetype_impl;
11use proc_macro::TokenStream;
12use syn::{parse_macro_input, ItemFn};
13
14
15/// Defines a dependency that can be accessed from any node in the graph.
16///
17/// Dependencies are resources shared amongst all nodes in a graph.  They can be interfaces to
18/// external processes or collections of data shared between disparate parts of the graph.
19/// Basically anything a node needs that isn't provided directly by the preceding node should be
20/// provided as a dependency.
21///
22/// # Providers
23///
24/// Async functions decorated with `#[dependency]` macro construct a dependency that shares the
25/// name of the function.  The first time a node that declares that dependency is executed in a
26/// graph, the graph will call the provider function and store the returned object in the
27/// dependency cache.  The cache is a hash map whose keys are the names of the provider functions
28/// and the values are the provided objects.
29///
30/// # Shared Resources
31///
32/// Dependencies are only created the first time they are needed.  Every node that names the same
33/// dependency gets the same object.  Because multiple nodes can be running simultaneously on
34/// separate threads, nodes only receive the dependency as an immutable reference.  If you need
35/// mutable access to the dependency, wrap it in a
36/// [`tokio::sync::Mutex`](https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html).
37///
38/// # Examples
39/// ```
40/// use conflagrate::{dependency, graph, nodetype};
41/// use tokio::sync::Mutex;
42///
43/// #[dependency]
44/// async fn messages() -> Mutex<Vec<String>> {
45///     Mutex::<Vec<String>>::new(Vec::<String>::new())
46/// }
47///
48/// #[nodetype]
49/// pub async fn StoreMessage(messages: &Mutex<Vec<String>>) {
50///     messages.lock().await.push(String::from("Hello mutable storage!"));
51/// }
52///
53/// #[nodetype]
54/// async fn StoreAnotherMessage(messages: &Mutex<Vec<String>>) {
55///     messages.lock().await.push(String::from("Here's another message!"));
56/// }
57///
58/// #[nodetype]
59/// pub async fn PrintMessages(messages: &Mutex<Vec<String>>) {
60///     for msg in messages.lock().await.iter() {
61///         println!("{}", msg);
62///     }
63/// }
64///
65/// graph!{
66///     digraph MessageGraph {
67///         store[type=StoreMessage, start=true];
68///         store_another[type=StoreAnotherMessage];
69///         print[type=PrintMessages];
70///
71///         store -> store_another -> print;
72///     }
73/// }
74///
75/// fn main() {
76///     MessageGraph::run(());
77/// }
78/// ```
79///
80/// # See Also
81/// * [`nodetype`](macro@nodetype) -- Macro for associating a function with a type of node.
82/// Reference types in
83/// input arguments are interpreted to be dependencies.
84#[proc_macro_attribute]
85pub fn dependency(_: TokenStream, func: TokenStream) -> TokenStream {
86    TokenStream::from(dependency_impl(parse_macro_input!(func as ItemFn)))
87}
88
89/// Defines a block of code to be associated with nodes of a certain type in a graph.
90///
91/// Each executable node in a graph is given a `type`, which is a non-standard GraphViz attribute
92/// that conflagrate associates with a function of the same name passed to the
93/// [`nodetype`](macro@nodetype) macro.
94///
95/// The input of a node is the output of the previous node in the graph (or the graph's input),
96/// and the output of the node is the input of the next node in the graph (or the return value of
97/// the graph).
98///
99/// ```no_run
100/// # use std::collections::VecDeque;
101/// # use conflagrate::nodetype;
102/// #[nodetype]
103/// async fn SplitCommand(input: String) -> VecDeque<String> {
104///     input.split_whitespace().map(String::from).collect::<VecDeque<String>>()
105/// }
106/// ```
107///
108/// # Branching Behavior
109///
110/// In a graph, when one node has more than one arrow following it pointing to different nodes,
111/// then the graph has "branched".  Branches in control flow graphs can have multiple,
112/// contradictory meanings.  In some cases, we may use branches to show conditional execution: if
113/// some condition is satisfied, follow one branch, otherwise follow another.  In other cases
114/// branching may represent parallel execution: at this point in the application, spawn N tasks
115/// and copy the data to each task.
116///
117/// To cover these possibilities, conflagrate supports tagging the nodes in a graph with a
118/// `branch` attribute to specify what the node's branching behavior should be (see [`graph`:
119/// Node Attributes](graph#node-attributes)).  Some choices of branching behavior require a
120/// `nodetype`'s output to be structured a certain way.
121///
122/// ## Parallel
123///
124/// The default branching behavior is simply to spawn a separate task for each following node in
125/// parallel.  The output from the branching node is cloned to each trailing node.
126///
127/// Parallel branching puts no constraints on the return type of the node, other than the usual
128/// requirement that each following node must accept exactly the same number and types as their
129/// input.
130///
131/// ## Matcher
132///
133/// If a node is given the `branch=matcher` attribute in the graph definition, it is specified to
134/// have the `matcher` branching behavior, where conflagrate executes only one trailing node
135/// determined by the output of the matcher node.  This puts a constraint on the form of the
136/// output of the node.
137///
138/// The return type of the node is required to be a 2-tuple of the form `(String, T)`, where the
139/// String first element is used for matching and the `T` second element is passed to the next node
140/// as its input.  Edges tagged with the `value` attribute (see
141/// [`graph`: Edge Attributes](graph#edge-attributes)) are matched using the attribute's value,
142/// and a matching edge determines the following node.  If no matches are found, an edge without a
143/// `value` attribute is used as the default. If no default edge is provided, the graph will
144/// terminate, using the matcher node's output as its output.
145///
146/// ```
147/// # use std::collections::VecDeque;
148/// # use conflagrate::nodetype;
149/// #[nodetype]
150/// async fn GetCommand(input: VecDeque<String>) -> (String, VecDeque<String>) {
151///     let cmd = input.pop_front().unwrap_or(String::from(""));
152///     (cmd, input)
153/// }
154/// ```
155///
156/// ## Result Matcher
157///
158/// Like the matcher behavior, if a node is described with the `branch=resultmatcher` attribute,
159/// the choice of trailing nodes depends on the output of the node.  In this case, the return
160/// type must be a single [`Result<T, E>`](core::result).  Edges marked with the `value=ok`
161/// attribute will match against the `Ok(T)` variant and receive `T` as their input type, and edges
162/// with `value=err` will match `Err(E)` and receive `E` as their input type.  Unlike
163/// the regular matcher type, multiple trailing nodes can be labeled with either `value=ok` or
164/// `value=err` on their edges, allowing for parallel execution as in the default parallel
165/// branching behavior.
166///
167/// ```
168/// # use std::collections::VecDeque;
169/// # use conflagrate::nodetype;
170/// #[nodetype]
171/// async fn GetCommand(input: VecDeque<String>) -> Result<(String, VecDeque<String>), String> {
172///     match input.pop_front() {
173///         Ok(cmd) => Ok((cmd, input)),
174///         Err(e) => Err(format!("unable to get command from input: {}", e.to_string())),
175///     }
176/// }
177/// ```
178///
179/// # Blocking Versus Non-Blocking
180///
181/// Conflagrate applications are built using `tokio`, so `nodetype`s are converted to async
182/// functions.  If a regular function is passed into the `nodetype` macro, conflagrate assumes
183/// it is blocking and spawns its codeblock in a separate thread.  To avoid spawning extra
184/// threads, use `async fn` wherever possible.
185///
186/// # Visibility (Public Versus Private)
187///
188/// To facilitate larger projects split into multiple modules, the `run` and `run_graph` methods
189/// of [`graph`](macro@graph)s are `pub`.  The arguments to the graph are the input arguments to
190/// the first node, and the return value of the graph is the return value of the last possible node,
191/// so consequently the `nodetype` functions of those nodes must also be `pub`.
192///
193/// # Testing
194///
195/// Under the hood, the `nodetype` function is converted to a struct implementing a trait that
196/// provides the function with a uniform call signature so that when it's used with the
197/// [`graph`](macro@graph) macro, the graph builder doesn't need to know anything about the shape of
198/// your function.  This makes testing more difficult, so your original function is also provided as
199/// a `test` static method. The `test` method has exactly the same call signature as the original
200/// definition.
201///
202/// ```
203/// # use conflagrate::{nodetype};
204/// #[nodetype]
205/// async fn BusinessLogic(value: u32) -> Result<String, String> {
206///     match value {
207///         0..=10 => Ok(String::from("good")),
208///         _ => Err(String::from("too high!"))
209///     }
210/// }
211///
212/// #[cfg(test)]
213/// mod tests {
214///     # use std::assert_eq;
215///     # use super::BusinessLogic;
216///     #[test]
217///     fn handles_good_values() {
218///         assert_eq!(BusinessLogic::test(1), Ok(String::from("good")));
219///         assert_eq!(BusinessLogic::test(10), Ok(String::from("good")));
220///     }
221///
222///     #[test]
223///     fn bails_on_bad_values() {
224///         assert_eq!(BusinessLogic::test(100), Err(String::from("too high!")));
225///     }
226/// }
227/// ```
228///
229/// # Examples
230///
231/// ## Hello World
232///
233/// A simple "hello world" graph with two nodes, `get_name` and `print_name`.  These nodes have
234/// the `nodetype`s `GetName` and `PrintGreeting`, respectively.  The graph starts at `get_name`
235/// and then follows to `print_name`.  The `GetName` `nodetype` returns a `String`, so the
236/// `nodetype` of the node that follows, `PrintGreeting` for `print_name`, must take as its input
237/// just a `String` (plus an dependencies, but in this case there are none).
238///
239/// ```no_run
240/// # use conflagrate::{graph, nodetype};
241/// #[nodetype]
242/// pub fn GetName() -> String {
243///    let mut name = String::new();
244///    println!("Hello, what is your name?");
245///    std::io::stdin().read_line(&mut name).unwrap();
246///    name.truncate(name.len() - 1);
247///    name
248/// }
249///
250/// #[nodetype]
251/// pub fn PrintGreeting(name: String) {
252///     println!("Hello, {}!", name)
253/// }
254///
255/// graph!{
256///     digraph GreetingGraph {
257///         get_name[type=GetName, start=true];
258///         print_name[type=PrintGreeting];
259///
260///         get_name -> print_name;
261///     }
262/// }
263///
264/// fn main() {
265///     GreetingGraph::run(());
266/// }
267/// ```
268///
269/// # See Also
270/// * [`dependency`](macro@dependency) -- Macro for defining an external resource or data to be used
271/// as input by a node in addition to the output of the previous node.
272/// * [`graph`](macro@graph) -- Macro for building an executable control flow graph using
273/// `nodetype`s.
274#[proc_macro_attribute]
275pub fn nodetype(_: TokenStream, func: TokenStream) -> TokenStream {
276    TokenStream::from(
277        nodetype_impl(parse_macro_input!(func as ItemFn))
278    )
279}
280
281/// Defines the control flow graph of an application.
282///
283/// The `graph!` macro defines a control flow graph that can be run as a stand-alone application
284/// or called as a subgraph from within another graph.  The syntax follows the standard
285/// [DOT language](https://graphviz.org) excepting a few new, non-standard attributes that can
286/// be applied to nodes and edges that conflagrate uses to construct the executable application
287/// logic.
288///
289/// # Node Attributes
290///
291/// * `type` -- The [`nodetype`](macro@nodetype) associated with the node the in graph, which is a
292/// block of executable code that takes as input the output from the previous node and provides as
293/// output the input to the next node.  Multiple nodes in the graph can use the same `nodetype`
294/// to facillitate more code reuse.
295/// * `start` -- Labels the node to start the graph from.  Only one node may be labeled with the
296/// `start` attribute.
297/// * `branch` -- Tells conflagrate how to handle a node that has more than one node trailing it in
298/// the graph.  May take the following values:
299///     * `parallel` (default) -- Conflagrate executes all trailing nodes simultaneously in
300/// parallel.  The return value from the node is cloned and passed separately to each tail.  If
301/// the `branch` attribute is omitted, this value is assumed.
302///     * `matcher` -- Conflagrate executes only one trailing node determined by the output of
303/// the matcher node.  This puts constraints on the required return type of the `nodetype` (see
304/// [`nodetype`: Matcher](nodetype#matcher)).
305///     * `resultmatcher` -- A variant of `matcher` that matches on a `Result` instead of a
306/// `String` (see [`nodetype`: Result Matcher](nodetype#result-matcher)).
307///
308/// # Edge Attributes
309///
310/// * `value` -- Used with nodes with the `branch=matcher` attribute (see above).  The return value
311/// of the matcher node is compared against this (string) value.  If it matches, this edge is
312/// followed to determine the next node to be executed in the graph.
313///
314/// # Examples
315///
316/// ## Trivial Graph
317///
318/// A single-node graph that just prints the text `Hello, world!`.
319/// ```
320/// # use conflagrate::{graph, nodetype};
321/// #[nodetype]
322/// pub fn HelloWorld() {
323///     println!("Hello, world!");
324/// }
325///
326/// graph!{
327///     digraph {
328///         start[type=HelloWorld, start=true];
329///     }
330/// }
331///
332/// fn main() {
333///     Graph::run(());
334/// }
335/// ```
336///
337/// ## Simple Loop
338///
339/// A simple loop that asks the user if they wish to exit (type `yes` to exit).
340/// ```no_run
341/// # use conflagrate::{graph, nodetype};
342/// #[nodetype]
343/// pub fn AskExit() -> (String, ()) {
344///     let mut response = String::new();
345///     println!("Exit loop?");
346///     std::io::stdin().read_line(&mut response).unwrap();
347///     response.truncate(response.len() - 1);
348///     (response, ())
349/// }
350///
351/// #[nodetype]
352/// pub async fn DoNothing() {}
353///
354/// graph!{
355///     digraph Loop {
356///         ask_exit[type=AskExit, branch=matcher, start=true];
357///         end[type=DoNothing];
358///
359///         ask_exit -> end [value=yes];
360///         ask_exit -> ask_exit;
361///     }
362/// }
363///
364/// fn main() {
365///     Loop::run(())
366/// }
367/// ```
368///
369/// This graph makes use of the `matcher` branching logic.  The `ask_exit` node has two tails,
370/// one that goes to the `end` node with the `value=yes` attribute, and another that goes back to
371/// the `ask_exit` node with no attributes.  The matching logic chooses which trailing node to
372/// execute next based on the first element in the tuple output from the `AskExit` nodetype
373/// function.  The second element of the tuple is then passed as input to the chosen following node.
374///
375/// # See Also
376///
377/// * [`nodetype`](macro@nodetype) -- Macro associating functions with nodes.
378/// * [`dependency`](macro@nodetype) -- Macro associating a function providing a resource with the
379/// name of the function, providing that resource to `nodetype`s that reference them.
380#[proc_macro]
381pub fn graph(graph: TokenStream) -> TokenStream {
382    TokenStream::from(graph_impl(graph.to_string()))
383}