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 if let Some(machine) = input
46 .machines
47 .iter()
48 .find(|machine| !machine.event_generics.params.is_empty())
49 {
50 return syn::Error::new(
51 machine.name.as_ref().map_or_else(
52 proc_macro2::Span::call_site,
53 proc_macro2::Ident::span,
54 ),
55 "generic event declarations are currently supported by flat `sml!` tables; composite tables do not yet have dispatch-scoped generic event enums",
56 )
57 .to_compile_error()
58 .into();
59 }
60 return match composite_codegen::generate_code(&input.machines) {
61 Ok(code) => code.into(),
62 Err(error) => error.to_compile_error().into(),
63 };
64 }
65 let mut output = proc_macro2::TokenStream::new();
66 for machine in input.machines {
67 output.extend(proc_macro2::TokenStream::from(expand(machine)));
68 }
69 return output.into();
70 }
71 let machine = input
72 .machines
73 .into_iter()
74 .next()
75 .expect("parser requires a machine");
76 if machine
77 .transitions
78 .iter()
79 .filter(|transition| transition.in_state.start)
80 .count()
81 > 1
82 {
83 if !machine.event_generics.params.is_empty() {
84 return syn::Error::new(
85 machine.name.as_ref().map_or_else(
86 proc_macro2::Span::call_site,
87 proc_macro2::Ident::span,
88 ),
89 "generic event declarations are currently supported by flat `sml!` tables; orthogonal tables do not yet have dispatch-scoped generic event enums",
90 )
91 .to_compile_error()
92 .into();
93 }
94 return match orthogonal_codegen::generate_code(&machine) {
95 Ok(code) => code.into(),
96 Err(error) => error.to_compile_error().into(),
97 };
98 }
99 expand(machine)
100}
101
102fn expand(input: parser::state_machine::StateMachine) -> proc_macro::TokenStream {
103 match parser::ParsedStateMachine::new(input) {
104 Ok(sm) => {
106 #[cfg(feature = "graphviz")]
107 {
108 use std::hash::{Hash, Hasher};
109 use std::io::Write;
110
111 let diagram = diagramgen::generate_diagram(&sm);
113 let diagram_name = if let Some(name) = &sm.name {
114 name.to_string()
115 } else {
116 let mut diagram_hasher = std::collections::hash_map::DefaultHasher::new();
117 diagram.hash(&mut diagram_hasher);
118 format!("sml{:010x}", diagram_hasher.finish())
119 };
120
121 let svg_name = format!("sml_{diagram_name}.svg");
125 let rendered = std::process::Command::new("dot")
126 .args(["-Tsvg", "-o", &svg_name])
127 .stdin(std::process::Stdio::piped())
128 .spawn()
129 .ok()
130 .map(|mut process| {
131 let wrote_input = process
132 .stdin
133 .as_mut()
134 .map(|stdin| stdin.write_all(diagram.as_bytes()).is_ok())
135 .unwrap_or(false);
136 wrote_input
137 && process
138 .wait()
139 .map(|status| status.success())
140 .unwrap_or(false)
141 })
142 .unwrap_or(false);
143
144 if !rendered {
145 let _ = std::fs::remove_file(svg_name);
146 let dot_name = format!("sml_{diagram_name}.dot");
147 let dot_path = std::env::var_os("OUT_DIR")
148 .map(std::path::PathBuf::from)
149 .map(|directory| directory.join(&dot_name))
150 .unwrap_or_else(|| std::env::temp_dir().join(dot_name));
151 let _ = std::fs::write(dot_path, diagram.as_bytes());
152 }
153 }
154
155 if let Err(e) = validation::validate(&sm) {
157 return e.to_compile_error().into();
158 }
159
160 codegen::generate_code(&sm).into()
161 }
162 Err(error) => error.to_compile_error().into(),
163 }
164}