tramli_plugins/hierarchy/
mod.rs1#[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#[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#[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
67pub 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
102pub 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}