Skip to main content

tramli_plugins/hierarchy/
mod.rs

1/// Hierarchical state specification.
2#[derive(Debug, Clone)]
3pub struct HierarchicalStateSpec {
4    pub name: String,
5    pub initial: bool,
6    pub terminal: bool,
7    pub entry_produces: Vec<String>,
8    pub exit_produces: Vec<String>,
9    pub children: Vec<HierarchicalStateSpec>,
10}
11
12impl HierarchicalStateSpec {
13    pub fn new(name: &str, initial: bool, terminal: bool) -> Self {
14        Self {
15            name: name.to_string(),
16            initial,
17            terminal,
18            entry_produces: Vec::new(),
19            exit_produces: Vec::new(),
20            children: Vec::new(),
21        }
22    }
23}
24
25/// Hierarchical transition specification.
26#[derive(Debug, Clone)]
27pub struct HierarchicalTransitionSpec {
28    pub from: String,
29    pub to: String,
30    pub trigger: String,
31    pub requires: Vec<String>,
32    pub produces: Vec<String>,
33}
34
35impl HierarchicalTransitionSpec {
36    pub fn new(from: &str, to: &str, trigger: &str) -> Self {
37        Self {
38            from: from.to_string(),
39            to: to.to_string(),
40            trigger: trigger.to_string(),
41            requires: Vec::new(),
42            produces: Vec::new(),
43        }
44    }
45}
46
47/// Hierarchical flow specification.
48#[derive(Debug, Clone)]
49pub struct HierarchicalFlowSpec {
50    pub flow_name: String,
51    pub enum_name: String,
52    pub root_states: Vec<HierarchicalStateSpec>,
53    pub transitions: Vec<HierarchicalTransitionSpec>,
54}
55
56impl HierarchicalFlowSpec {
57    pub fn new(flow_name: &str, enum_name: &str) -> Self {
58        Self {
59            flow_name: flow_name.to_string(),
60            enum_name: enum_name.to_string(),
61            root_states: Vec::new(),
62            transitions: Vec::new(),
63        }
64    }
65}
66
67/// Entry/exit compiler — synthesizes transitions from hierarchical specs.
68pub struct EntryExitCompiler;
69
70impl EntryExitCompiler {
71    pub fn synthesize(spec: &HierarchicalFlowSpec) -> Vec<HierarchicalTransitionSpec> {
72        let mut generated = Vec::new();
73        for state in &spec.root_states {
74            Self::walk(state, &mut generated, None);
75        }
76        generated
77    }
78
79    fn walk(state: &HierarchicalStateSpec, out: &mut Vec<HierarchicalTransitionSpec>, parent: Option<&str>) {
80        if !state.entry_produces.is_empty() {
81            let from = parent.map(|p| p.to_string())
82                .unwrap_or_else(|| format!("{}__ENTRY_START", state.name));
83            let mut t = HierarchicalTransitionSpec::new(&from, &state.name, &format!("__entry__{}", state.name));
84            t.produces = state.entry_produces.clone();
85            out.push(t);
86        }
87        if !state.exit_produces.is_empty() {
88            let mut t = HierarchicalTransitionSpec::new(
89                &state.name,
90                &format!("{}__EXIT_END", state.name),
91                &format!("__exit__{}", state.name),
92            );
93            t.produces = state.exit_produces.clone();
94            out.push(t);
95        }
96        for child in &state.children {
97            Self::walk(child, out, Some(&state.name));
98        }
99    }
100}
101
102/// Hierarchy code generator — generates Rust source from hierarchical specs.
103pub struct HierarchyCodeGenerator;
104
105impl HierarchyCodeGenerator {
106    pub fn generate_enum_source(spec: &HierarchicalFlowSpec) -> String {
107        let mut flat = Vec::new();
108        Self::flatten(&spec.root_states, "", &mut flat);
109
110        let mut lines = Vec::new();
111        lines.push(format!("use tramli::FlowState;"));
112        lines.push(String::new());
113        lines.push(format!("#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]"));
114        lines.push(format!("pub enum {} {{", spec.enum_name));
115        for (name, _, _) in &flat {
116            lines.push(format!("    {},", name));
117        }
118        lines.push("}".to_string());
119        lines.push(String::new());
120        lines.push(format!("impl FlowState for {} {{", spec.enum_name));
121        lines.push("    fn is_terminal(&self) -> bool {".to_string());
122        lines.push("        matches!(self,".to_string());
123        let terminals: Vec<&str> = flat.iter()
124            .filter(|(_, terminal, _)| *terminal)
125            .map(|(name, _, _)| name.as_str())
126            .collect();
127        if terminals.is_empty() {
128            lines.push("            _ => false,".to_string());
129        } else {
130            for (i, t) in terminals.iter().enumerate() {
131                let sep = if i + 1 < terminals.len() { " |" } else { "" };
132                lines.push(format!("            Self::{}{}", t, sep));
133            }
134        }
135        lines.push("        )".to_string());
136        lines.push("    }".to_string());
137        lines.push("    fn is_initial(&self) -> bool {".to_string());
138        let initials: Vec<&str> = flat.iter()
139            .filter(|(_, _, initial)| *initial)
140            .map(|(name, _, _)| name.as_str())
141            .collect();
142        if initials.is_empty() {
143            lines.push("        false".to_string());
144        } else {
145            lines.push("        matches!(self,".to_string());
146            for (i, init) in initials.iter().enumerate() {
147                let sep = if i + 1 < initials.len() { " |" } else { "" };
148                lines.push(format!("            Self::{}{}", init, sep));
149            }
150            lines.push("        )".to_string());
151        }
152        lines.push("    }".to_string());
153        lines.push(format!("    fn all_states() -> &'static [Self] {{"));
154        lines.push(format!("        &["));
155        for (name, _, _) in &flat {
156            lines.push(format!("            Self::{},", name));
157        }
158        lines.push("        ]".to_string());
159        lines.push("    }".to_string());
160        lines.push("}".to_string());
161
162        lines.join("\n")
163    }
164
165    pub fn generate_builder_skeleton(spec: &HierarchicalFlowSpec) -> String {
166        let mut lines = Vec::new();
167        lines.push(format!("use std::sync::Arc;"));
168        lines.push(format!("use tramli::{{Builder, FlowDefinition}};"));
169        lines.push(String::new());
170        lines.push(format!("pub fn build_{}() -> Result<FlowDefinition<{}>, tramli::FlowError> {{", spec.flow_name.to_lowercase(), spec.enum_name));
171        lines.push(format!("    let b = Builder::<{}>::new(\"{}\");", spec.enum_name, spec.flow_name));
172        for t in &spec.transitions {
173            lines.push(format!("    // {}: {} -> {} requires {:?} produces {:?}", t.trigger, t.from, t.to, t.requires, t.produces));
174        }
175        lines.push("    b.build()".to_string());
176        lines.push("}".to_string());
177        lines.join("\n")
178    }
179
180    fn flatten(states: &[HierarchicalStateSpec], prefix: &str, out: &mut Vec<(String, bool, bool)>) {
181        for state in states {
182            let flat = if prefix.is_empty() {
183                state.name.to_uppercase()
184            } else {
185                format!("{}_{}", prefix, state.name.to_uppercase())
186            };
187            out.push((flat.clone(), state.terminal, state.initial));
188            Self::flatten(&state.children, &flat, out);
189        }
190    }
191}