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)
}