smlang_macros/
lib.rs

1#![recursion_limit = "512"]
2
3extern crate proc_macro;
4
5mod codegen;
6#[cfg(feature = "graphviz")]
7mod diagramgen;
8mod parser;
9mod validation;
10
11use syn::parse_macro_input;
12
13// dot -Tsvg statemachine.gv -o statemachine.svg
14
15#[proc_macro]
16pub fn statemachine(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
17    // Parse the syntax into structures
18    let input = parse_macro_input!(input as parser::state_machine::StateMachine);
19
20    // Validate syntax
21    match parser::ParsedStateMachine::new(input) {
22        // Generate code and hand the output tokens back to the compiler
23        Ok(sm) => {
24            #[cfg(feature = "graphviz")]
25            {
26                use std::hash::{Hash, Hasher};
27                use std::io::Write;
28
29                // Generate dot syntax for the statemachine.
30                let diagram = diagramgen::generate_diagram(&sm);
31                let diagram_name = if let Some(name) = &sm.name {
32                    name.to_string()
33                } else {
34                    let mut diagram_hasher = std::collections::hash_map::DefaultHasher::new();
35                    diagram.hash(&mut diagram_hasher);
36                    format!("smlang{:010x}", diagram_hasher.finish())
37                };
38
39                // Start the 'dot' process.
40                let mut process = std::process::Command::new("dot")
41                    .args(["-Tsvg", "-o", &format!("statemachine_{diagram_name}.svg")])
42                    .stdin(std::process::Stdio::piped())
43                    .spawn()
44                    .expect("Failed to execute 'dot'. Are you sure graphviz is installed?");
45
46                // Write the dot syntax string to the 'dot' process stdin.
47                process
48                    .stdin
49                    .as_mut()
50                    .map(|s| s.write_all(diagram.as_bytes()));
51
52                // Check the graphviz return status to see if it was successful.
53                match process.wait() {
54                    Ok(status) => {
55                        if !status.success() {
56                            panic!("'dot' failed to run. Are you sure graphviz is installed?");
57                        }
58                    }
59                    Err(_) => panic!("'dot' failed to run. Are you sure graphviz is installed?"),
60                }
61            }
62
63            // Validate the parsed state machine before generating code.
64            if let Err(e) = validation::validate(&sm) {
65                return e.to_compile_error().into();
66            }
67
68            codegen::generate_code(&sm).into()
69        }
70        Err(error) => error.to_compile_error().into(),
71    }
72}