dot_parser_macros/
lib.rs

1//! This crate provides two macros [from_dot_file!] and [from_dot_string!], which help parsing
2//! graph files at compile time.
3use std::convert::TryFrom;
4extern crate proc_macro;
5use dot_parser::ast::{Graph, ID};
6use litrs::StringLit;
7use quote::quote;
8
9/// Imports a DOT graph contained in a file.
10///
11/// Notice that the import happens *at compile time*. This
12/// means the provided file *must* exist at compile time. This also means that modifying the graph
13/// file without recompiling has no effect. This macro generates a graph
14/// declaration that corresponds to the graph in the file. All the parsing happens *at compile
15/// time*. If you want to import dynamically a graph (i.e. at compile time), use
16/// `dot_parser::ast::Graph::from_file` instead.
17///
18/// This macro expects a single literal argument which is the path of the file to read.
19///
20/// This macro will fail if:
21///  - the number of arguments is not exactly 1; or
22///  - the file can not be read; or
23///  - the content of the file is not a valid DOT graph.
24///
25/// For example, provided that the file `/tmp/graph.dot` contains:
26/// ```ignore
27/// digraph {
28/// A -> B
29/// }
30/// ```
31///
32/// Using:
33/// ```ignore
34/// # use dot_parser_macros::from_dot;
35/// let graph = from_dot!("/tmp/graph.dot");
36/// ```
37/// is roughtly equivalent to:
38/// ```
39/// # use dot_parser::ast::*;
40/// # use dot_parser::ast::either::Either;
41/// let graph = Graph::<(&'static str, &'static str)> {
42///     strict: false,
43///     is_digraph: true,
44///     name: Option::None,
45///     stmts: StmtList {
46///         stmts: vec![
47///             Stmt::EdgeStmt(EdgeStmt {
48///                 from: Either::Left(NodeID {
49///                     id: "A".to_string(),
50///                     port: Option::None,
51///                 }),
52///                 next: EdgeRHS {
53///                     to: Either::Left(NodeID {
54///                         id: "B".to_string(),
55///                         port: Option::None,
56///                     }),
57///                     next: Option::None,
58///                 },
59///                 attr: Option::None,
60///             }),
61///         ]
62///     },
63/// };
64/// ```
65#[proc_macro]
66pub fn from_dot_file(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
67    let input = proc_macro2::TokenStream::from(input);
68    let tokens: Vec<_> = input.into_iter().collect();
69    if tokens.len() != 1 {
70        let output: proc_macro2::TokenStream = {
71            quote! {
72                    compile_error!("Expect a single path argument")
73            }
74        };
75        return proc_macro::TokenStream::from(output);
76    }
77    let token = tokens.into_iter().next().unwrap();
78    let str_lit = match StringLit::try_from(token) {
79        Err(e) => return e.to_compile_error(),
80        Ok(lit) => lit,
81    };
82    let graph = match Graph::from_file(str_lit.value()) {
83        Err(f) => {
84            let msg = format!("{}", f);
85            return quote! { compile_error!(#msg)}.into();
86        }
87        Ok(graph) => graph,
88    };
89
90    let output: proc_macro2::TokenStream = quote! { #graph };
91    proc_macro::TokenStream::from(output)
92}
93
94/// Similar to [from_dot_file!], but reads the DOT graph from a given literal instead of reading it
95/// from a file.
96///
97/// ```
98/// use dot_parser_macros::from_dot_string;
99/// use dot_parser::canonical::Graph as CanonicalGraph;
100///
101/// let graph =
102///     CanonicalGraph::from(from_dot_string!("digraph { A -> B}"));
103/// println!("{:#?}", graph);
104/// ```
105#[proc_macro]
106pub fn from_dot_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
107    let input = proc_macro2::TokenStream::from(input);
108    let tokens: Vec<_> = input.into_iter().collect();
109    if tokens.len() != 1 {
110        let output: proc_macro2::TokenStream = {
111            quote! {
112                    compile_error!("Expect a single argument, which must be a literal containing a DOT graph description.")
113            }
114        };
115        return proc_macro::TokenStream::from(output);
116    }
117    let token = tokens.into_iter().next().unwrap();
118    let str_lit = match StringLit::try_from(token) {
119        Err(e) => return e.to_compile_error(),
120        Ok(lit) => lit,
121    };
122    let graph = match Graph::try_from(str_lit.value()) {
123        Err(f) => {
124            let msg = format!("{}", f);
125            return quote! { compile_error!(#msg)}.into();
126        }
127        Ok(graph) => graph.filter_map(&|(s1, s2): (ID<'_>, ID<'_>)| Some((s1.into(), s2.into()))),
128    };
129
130    let output: proc_macro2::TokenStream = quote! { #graph };
131    proc_macro::TokenStream::from(output)
132}