oxidate_fsm/codegen/
mod.rs1use crate::fsm::FsmDefinition;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum CodegenTarget {
25 Standard,
27 Embassy,
29 Rtic,
31}
32
33impl CodegenTarget {
34 pub fn is_available(&self) -> bool {
36 match self {
37 CodegenTarget::Standard => true,
38 CodegenTarget::Embassy | CodegenTarget::Rtic => false, }
40 }
41
42 pub fn upgrade_message(&self) -> Option<&'static str> {
44 match self {
45 CodegenTarget::Standard => None,
46 CodegenTarget::Embassy => Some(
47 "Embassy code generation is available in Oxidate Pro.\n\
48 Contact: https://github.com/JoseClaudioSJr/Oxidate/discussions"
49 ),
50 CodegenTarget::Rtic => Some(
51 "RTIC code generation is available in Oxidate Pro.\n\
52 Contact: https://github.com/JoseClaudioSJr/Oxidate/discussions"
53 ),
54 }
55 }
56}
57
58impl Default for CodegenTarget {
59 fn default() -> Self {
60 Self::Standard
61 }
62}
63
64pub fn generate_rust_code(fsm: &FsmDefinition) -> String {
66 generate_rust_code_with_target(fsm, CodegenTarget::Standard)
67}
68
69pub fn generate_rust_code_with_target(fsm: &FsmDefinition, target: CodegenTarget) -> String {
71 match target {
72 CodegenTarget::Standard => generate_standard_code(fsm),
73 CodegenTarget::Embassy => generate_premium_stub(fsm, "Embassy"),
74 CodegenTarget::Rtic => generate_premium_stub(fsm, "RTIC"),
75 }
76}
77
78fn generate_premium_stub(fsm: &FsmDefinition, target_name: &str) -> String {
80 format!(
81 "//! {} code generation requires Oxidate Pro\n\
82 //!\n\
83 //! FSM: {}\n\
84 //!\n\
85 //! To generate {} code:\n\
86 //! 1. Purchase/access: https://github.com/JoseClaudioSJr/Oxidate/discussions\n\
87 //! 2. Then use: oxidate-pro generate --target {} your_fsm.fsm\n\
88 //!\n\
89 //! Oxidate Pro includes:\n\
90 //! - Embassy async Active Object pattern\n\
91 //! - RTIC real-time event queues\n\
92 //! - Events with payload\n\
93 //! - HSM hierarchical states\n\
94 //! - Priority support\n\
95 \n\
96 compile_error!(\"This target requires Oxidate Pro. Contact: https://github.com/JoseClaudioSJr/Oxidate/discussions\");\n",
97 target_name,
98 fsm.name,
99 target_name,
100 target_name.to_lowercase()
101 )
102}
103
104fn generate_standard_code(fsm: &FsmDefinition) -> String {
109 let mut code = String::new();
110
111 code.push_str(&format!(
113 "//! Auto-generated FSM: {}\n",
114 fsm.name
115 ));
116 code.push_str("//! Generated by Oxidate\n\n");
117
118 code.push_str(&generate_state_enum(fsm));
120 code.push_str("\n");
121
122 code.push_str(&generate_event_enum(fsm));
124 code.push_str("\n");
125
126 code.push_str(&generate_fsm_struct(fsm));
128 code.push_str("\n");
129
130 code.push_str(&generate_fsm_impl(fsm));
132 code.push_str("\n");
133
134 code.push_str(&generate_action_trait(fsm));
136
137 code
138}
139
140fn generate_state_enum(fsm: &FsmDefinition) -> String {
141 let mut code = String::new();
142
143 code.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n");
144 code.push_str(&format!("pub enum {}State {{\n", fsm.name));
145
146 for state in &fsm.states {
147 if let Some(ref desc) = state.description {
148 code.push_str(&format!(" /// {}\n", desc));
149 }
150 code.push_str(&format!(" {},\n", to_pascal_case(&state.name)));
151 }
152
153 code.push_str("}\n");
154 code
155}
156
157fn generate_event_enum(fsm: &FsmDefinition) -> String {
158 let mut code = String::new();
159
160 let mut events: Vec<String> = fsm.transitions
162 .iter()
163 .filter_map(|t| t.event.as_ref().map(|e| e.name.clone()))
164 .collect();
165 events.sort();
166 events.dedup();
167
168 if events.is_empty() {
169 return String::new();
170 }
171
172 code.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\n");
173 code.push_str(&format!("pub enum {}Event {{\n", fsm.name));
174
175 for event in &events {
176 code.push_str(&format!(" {},\n", to_pascal_case(event)));
177 }
178
179 code.push_str("}\n");
180 code
181}
182
183fn generate_fsm_struct(fsm: &FsmDefinition) -> String {
184 let mut code = String::new();
185
186 code.push_str(&format!("pub struct {}<T: {}Actions> {{\n", fsm.name, fsm.name));
187 code.push_str(&format!(" state: {}State,\n", fsm.name));
188 code.push_str(" context: T,\n");
189 code.push_str("}\n");
190
191 code
192}
193
194fn generate_fsm_impl(fsm: &FsmDefinition) -> String {
195 let mut code = String::new();
196
197 let initial_state = fsm.initial_state.as_ref()
198 .map(|s| to_pascal_case(s))
199 .unwrap_or_else(|| "Unknown".to_string());
200
201 code.push_str(&format!("impl<T: {}Actions> {}<T> {{\n", fsm.name, fsm.name));
202
203 code.push_str(" pub fn new(context: T) -> Self {\n");
205 code.push_str(&format!(" Self {{\n"));
206 code.push_str(&format!(" state: {}State::{},\n", fsm.name, initial_state));
207 code.push_str(" context,\n");
208 code.push_str(" }\n");
209 code.push_str(" }\n\n");
210
211 code.push_str(&format!(" pub fn state(&self) -> {}State {{\n", fsm.name));
213 code.push_str(" self.state\n");
214 code.push_str(" }\n\n");
215
216 code.push_str(" pub fn context(&self) -> &T {\n");
218 code.push_str(" &self.context\n");
219 code.push_str(" }\n\n");
220
221 code.push_str(" pub fn context_mut(&mut self) -> &mut T {\n");
223 code.push_str(" &mut self.context\n");
224 code.push_str(" }\n\n");
225
226 code.push_str(&generate_process_event(fsm));
228
229 code.push_str("}\n");
230 code
231}
232
233fn generate_process_event(fsm: &FsmDefinition) -> String {
234 let mut code = String::new();
235
236 code.push_str(&format!(" pub fn process(&mut self, event: {}Event) {{\n", fsm.name));
237 code.push_str(" match (self.state, event) {\n");
238
239 for transition in &fsm.transitions {
240 if transition.source == "[*]" {
241 continue; }
243
244 if let Some(ref event) = transition.event {
245 let source = to_pascal_case(&transition.source);
246 let target = to_pascal_case(&transition.target);
247 let event_name = to_pascal_case(&event.name);
248
249 if let Some(ref guard) = transition.guard {
251 code.push_str(&format!(
252 " ({}State::{}, {}Event::{}) if self.context.{} => {{\n",
253 fsm.name, source, fsm.name, event_name, to_snake_case(&guard.expression)
254 ));
255 } else {
256 code.push_str(&format!(
257 " ({}State::{}, {}Event::{}) => {{\n",
258 fsm.name, source, fsm.name, event_name
259 ));
260 }
261
262 if let Some(state) = fsm.states.iter().find(|s| s.name == transition.source) {
264 if let Some(ref exit_action) = state.exit_action {
265 code.push_str(&format!(
266 " self.context.{}();\n",
267 to_snake_case(&exit_action.name)
268 ));
269 }
270 }
271
272 if let Some(ref action) = transition.action {
274 code.push_str(&format!(
275 " self.context.{}();\n",
276 to_snake_case(&action.name)
277 ));
278 }
279
280 code.push_str(&format!(
282 " self.state = {}State::{};\n",
283 fsm.name, target
284 ));
285
286 if let Some(state) = fsm.states.iter().find(|s| s.name == transition.target) {
288 if let Some(ref entry_action) = state.entry_action {
289 code.push_str(&format!(
290 " self.context.{}();\n",
291 to_snake_case(&entry_action.name)
292 ));
293 }
294 }
295
296 code.push_str(" }\n");
297 }
298 }
299
300 code.push_str(" _ => {} // No transition\n");
302 code.push_str(" }\n");
303 code.push_str(" }\n");
304
305 code
306}
307
308fn generate_action_trait(fsm: &FsmDefinition) -> String {
309 let mut code = String::new();
310
311 let mut actions: Vec<String> = Vec::new();
313 let mut guards: Vec<String> = Vec::new();
314
315 for state in &fsm.states {
316 if let Some(ref action) = state.entry_action {
317 actions.push(action.name.clone());
318 }
319 if let Some(ref action) = state.exit_action {
320 actions.push(action.name.clone());
321 }
322 }
323
324 for transition in &fsm.transitions {
325 if let Some(ref action) = transition.action {
326 actions.push(action.name.clone());
327 }
328 if let Some(ref guard) = transition.guard {
329 guards.push(guard.expression.clone());
330 }
331 }
332
333 actions.sort();
334 actions.dedup();
335 guards.sort();
336 guards.dedup();
337
338 code.push_str(&format!("pub trait {}Actions {{\n", fsm.name));
339
340 for action in &actions {
341 code.push_str(&format!(" fn {}(&mut self);\n", to_snake_case(action)));
342 }
343
344 for guard in &guards {
345 code.push_str(&format!(" fn {}(&self) -> bool;\n", to_snake_case(guard)));
346 }
347
348 code.push_str("}\n");
349
350 code
351}
352
353fn to_pascal_case(s: &str) -> String {
354 s.split('_')
355 .map(|word| {
356 let mut chars = word.chars();
357 match chars.next() {
358 None => String::new(),
359 Some(first) => first.to_uppercase().chain(chars).collect(),
360 }
361 })
362 .collect()
363}
364
365fn to_snake_case(s: &str) -> String {
366 let mut result = String::new();
367 for (i, c) in s.chars().enumerate() {
368 if c.is_uppercase() && i > 0 {
369 result.push('_');
370 }
371 result.push(c.to_lowercase().next().unwrap_or(c));
372 }
373 result.replace(' ', "_").replace('-', "_")
375}
376
377