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}