oxidate_fsm/codegen/
mod.rs

1//! Code Generator Module
2//! 
3//! Generates Rust code from FSM definitions.
4//! 
5//! ## Available Targets
6//! 
7//! - **Standard** (MIT): Basic Rust FSM with states, events, and transitions
8//! 
9//! ### Premium Targets (Oxidate Pro)
10//! 
11//! The following targets are available in Oxidate Pro (available separately):
12//! 
13//! - **Embassy**: Async embedded with Active Object pattern
14//! - **RTIC**: Real-time embedded with event queues
15//! 
16//! Contact:
17//! - Issues: https://github.com/JoseClaudioSJr/Oxidate/issues
18//! - Discussions: https://github.com/JoseClaudioSJr/Oxidate/discussions
19
20use crate::fsm::FsmDefinition;
21
22/// Code generation target
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum CodegenTarget {
25    /// Standard Rust (std) - MIT licensed
26    Standard,
27    /// Embassy async (no_std, embedded) - Premium
28    Embassy,
29    /// RTIC (no_std, embedded) - Premium
30    Rtic,
31}
32
33impl CodegenTarget {
34    /// Check if target is available (premium features)
35    pub fn is_available(&self) -> bool {
36        match self {
37            CodegenTarget::Standard => true,
38            CodegenTarget::Embassy | CodegenTarget::Rtic => false, // Premium
39        }
40    }
41    
42    /// Get upgrade message for premium targets
43    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
64/// Generate Rust code from an FSM definition
65pub fn generate_rust_code(fsm: &FsmDefinition) -> String {
66    generate_rust_code_with_target(fsm, CodegenTarget::Standard)
67}
68
69/// Generate Rust code with specific target
70pub 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
78/// Generate stub for premium features
79fn 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
104// ============================================================================
105// STANDARD CODE GENERATION
106// ============================================================================
107
108fn generate_standard_code(fsm: &FsmDefinition) -> String {
109    let mut code = String::new();
110    
111    // Header
112    code.push_str(&format!(
113        "//! Auto-generated FSM: {}\n",
114        fsm.name
115    ));
116    code.push_str("//! Generated by Oxidate\n\n");
117    
118    // Generate state enum
119    code.push_str(&generate_state_enum(fsm));
120    code.push_str("\n");
121    
122    // Generate event enum
123    code.push_str(&generate_event_enum(fsm));
124    code.push_str("\n");
125    
126    // Generate FSM struct
127    code.push_str(&generate_fsm_struct(fsm));
128    code.push_str("\n");
129    
130    // Generate implementation
131    code.push_str(&generate_fsm_impl(fsm));
132    code.push_str("\n");
133    
134    // Generate action trait
135    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    // Collect unique events
161    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    // Constructor
204    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    // State getter
212    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    // Context getter
217    code.push_str("    pub fn context(&self) -> &T {\n");
218    code.push_str("        &self.context\n");
219    code.push_str("    }\n\n");
220    
221    // Context mutable getter
222    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    // Process event
227    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; // Skip initial transitions
242        }
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            // Check for guard
250            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            // Exit action
263            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            // Transition action
273            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            // State change
281            code.push_str(&format!(
282                "                self.state = {}State::{};\n",
283                fsm.name, target
284            ));
285            
286            // Entry action
287            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    // Default case - no transition
301    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    // Collect all actions
312    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    // Replace spaces and special chars
374    result.replace(' ', "_").replace('-', "_")
375}
376
377// ============================================================================
378// PREMIUM TARGETS (Embassy, RTIC)
379// ============================================================================
380// Available in Oxidate Pro (separately): https://github.com/JoseClaudioSJr/Oxidate/discussions
381// 
382// Features include:
383// - Embassy Active Object pattern (async embedded)
384// - RTIC real-time event queues
385// - Events with typed payloads
386// - HSM hierarchical states
387// - Software timers
388// - ISR-safe event posting
389// ============================================================================