1#![recursion_limit = "512"]
2
3extern crate proc_macro;
4
5mod codegen;
6mod composite_codegen;
7#[cfg(feature = "graphviz")]
8mod diagramgen;
9mod orthogonal_codegen;
10mod parser;
11mod validation;
12
13use syn::parse_macro_input;
14
15#[proc_macro]
26pub fn sml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
27 let input = parse_macro_input!(input as parser::cpp::SmlDefinitions);
28 if input.machines.len() > 1 {
29 let machine_names = input
30 .machines
31 .iter()
32 .filter_map(|machine| machine.name.as_ref())
33 .collect::<Vec<_>>();
34 let has_composite = input.machines.iter().any(|machine| {
35 machine.transitions.iter().any(|transition| {
36 transition
37 .in_state
38 .composite
39 .as_ref()
40 .or(transition.out_state.composite.as_ref())
41 .is_some_and(|reference| machine_names.contains(&reference))
42 })
43 });
44 if has_composite {
45 return match composite_codegen::generate_code(&input.machines) {
46 Ok(code) => code.into(),
47 Err(error) => error.to_compile_error().into(),
48 };
49 }
50 let mut output = proc_macro2::TokenStream::new();
51 for machine in input.machines {
52 output.extend(proc_macro2::TokenStream::from(expand(machine)));
53 }
54 return output.into();
55 }
56 let machine = input
57 .machines
58 .into_iter()
59 .next()
60 .expect("parser requires a machine");
61 if machine
62 .transitions
63 .iter()
64 .filter(|transition| transition.in_state.start)
65 .count()
66 > 1
67 {
68 return match orthogonal_codegen::generate_code(&machine) {
69 Ok(code) => code.into(),
70 Err(error) => error.to_compile_error().into(),
71 };
72 }
73 expand(machine)
74}
75
76fn expand(input: parser::state_machine::StateMachine) -> proc_macro::TokenStream {
77 match parser::ParsedStateMachine::new(input) {
78 Ok(sm) => {
80 #[cfg(feature = "graphviz")]
81 {
82 use std::hash::{Hash, Hasher};
83 use std::io::Write;
84
85 let diagram = diagramgen::generate_diagram(&sm);
87 let diagram_name = if let Some(name) = &sm.name {
88 name.to_string()
89 } else {
90 let mut diagram_hasher = std::collections::hash_map::DefaultHasher::new();
91 diagram.hash(&mut diagram_hasher);
92 format!("sml{:010x}", diagram_hasher.finish())
93 };
94
95 let svg_name = format!("sml_{diagram_name}.svg");
99 let rendered = std::process::Command::new("dot")
100 .args(["-Tsvg", "-o", &svg_name])
101 .stdin(std::process::Stdio::piped())
102 .spawn()
103 .ok()
104 .map(|mut process| {
105 let wrote_input = process
106 .stdin
107 .as_mut()
108 .map(|stdin| stdin.write_all(diagram.as_bytes()).is_ok())
109 .unwrap_or(false);
110 wrote_input
111 && process
112 .wait()
113 .map(|status| status.success())
114 .unwrap_or(false)
115 })
116 .unwrap_or(false);
117
118 if !rendered {
119 let _ = std::fs::remove_file(svg_name);
120 let dot_name = format!("sml_{diagram_name}.dot");
121 let dot_path = std::env::var_os("OUT_DIR")
122 .map(std::path::PathBuf::from)
123 .map(|directory| directory.join(&dot_name))
124 .unwrap_or_else(|| std::env::temp_dir().join(dot_name));
125 let _ = std::fs::write(dot_path, diagram.as_bytes());
126 }
127 }
128
129 if let Err(e) = validation::validate(&sm) {
131 return e.to_compile_error().into();
132 }
133
134 codegen::generate_code(&sm).into()
135 }
136 Err(error) => error.to_compile_error().into(),
137 }
138}