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}