1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! This crate provides two macros [from_dot_file!] and [from_dot_string!], which help parsing
//! graph files at compile time.
use std::convert::TryFrom;
extern crate proc_macro;
use dot_parser::ast::Graph;
use litrs::StringLit;
use quote::quote;

/// Imports a DOT graph contained in a file.
///
/// Notice that the import happens *at compile time*. This
/// means the provided file *must* exist at compile time. This also means that modifying the graph
/// file without recompiling has no effect. This macro generates a graph
/// declaration that corresponds to the graph in the file. All the parsing happens *at compile
/// time*. If you want to import dynamically a graph (i.e. at compile time), use
/// `dot_parser::ast::Graph::from_file` instead.
///
/// This macro expects a single literal argument which is the path of the file to read.
///
/// This macro will fail if:
///  - the number of arguments is not exactly 1; or
///  - the file can not be read; or
///  - the content of the file is not a valid DOT graph.
///
/// For example, provided that the file `/tmp/graph.dot` contains:
/// ```ignore
/// digraph {
/// A -> B
/// }
/// ```
///
/// Using:
/// ```ignore
/// # use dot_parser_macros::from_dot;
/// let graph = from_dot!("/tmp/graph.dot");
/// ```
/// is roughtly equivalent to:
/// ```
/// # use dot_parser::ast::*;
/// # use dot_parser::ast::either::Either;
/// let graph = Graph::<(&'static str, &'static str)> {
///     strict: false,
///     is_digraph: true,
///     name: Option::None,
///     stmts: StmtList {
///         stmts: vec![
///             Stmt::EdgeStmt(EdgeStmt {
///                 from: Either::Left(NodeID {
///                     id: "A".to_string(),
///                     port: Option::None,
///                 }),
///                 next: EdgeRHS {
///                     to: Either::Left(NodeID {
///                         id: "B".to_string(),
///                         port: Option::None,
///                     }),
///                     next: Option::None,
///                 },
///                 attr: Option::None,
///             }),
///         ]
///     },
/// };
/// ```
#[proc_macro]
pub fn from_dot_file(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = proc_macro2::TokenStream::from(input);
    let tokens: Vec<_> = input.into_iter().collect();
    if tokens.len() != 1 {
        let output: proc_macro2::TokenStream = {
            quote! {
                    compile_error!("Expect a single path argument")
            }
        };
        return proc_macro::TokenStream::from(output);
    }
    let token = tokens.into_iter().next().unwrap();
    let str_lit = match StringLit::try_from(token) {
        Err(e) => return e.to_compile_error(),
        Ok(lit) => lit,
    };
    let graph = match Graph::from_file(str_lit.value()) {
        Err(f) => {
            let msg = format!("{}", f);
            return quote! { compile_error!(#msg)}.into();
        }
        Ok(graph) => graph,
    };

    let output: proc_macro2::TokenStream = quote! { #graph };
    proc_macro::TokenStream::from(output)
}

/// Similar to [from_dot_file!], but reads the DOT graph from a given literal instead of reading it
/// from a file.
///
/// ```
/// use dot_parser_macros::from_dot_string;
/// use petgraph::graph::Graph as PetGraph;
/// use dot_parser::canonical::Graph as CanonicalGraph;
///
/// let petgraph: PetGraph<_, _> =
///     CanonicalGraph::from(from_dot_string!("digraph { A -> B}")).into();
/// println!("{:#?}", petgraph);
/// ```
#[proc_macro]
pub fn from_dot_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = proc_macro2::TokenStream::from(input);
    let tokens: Vec<_> = input.into_iter().collect();
    if tokens.len() != 1 {
        let output: proc_macro2::TokenStream = {
            quote! {
                    compile_error!("Expect a single argument, which must be a literal containing a DOT graph description.")
            }
        };
        return proc_macro::TokenStream::from(output);
    }
    let token = tokens.into_iter().next().unwrap();
    let str_lit = match StringLit::try_from(token) {
        Err(e) => return e.to_compile_error(),
        Ok(lit) => lit,
    };
    let graph = match Graph::try_from(str_lit.value()) {
        Err(f) => {
            let msg = format!("{}", f);
            return quote! { compile_error!(#msg)}.into();
        }
        Ok(graph) => graph.filter_map(&|(s1, s2): (&str, &str)| Some((s1.to_string(), s2.to_string()))),
    };

    let output: proc_macro2::TokenStream = quote! { #graph };
    proc_macro::TokenStream::from(output)
}