floxide_macros_support/lib.rs
1pub fn add(left: u64, right: u64) -> u64 {
2 left + right
3}
4
5#[cfg(test)]
6mod tests {
7 use super::*;
8
9 #[test]
10 fn it_works() {
11 let result = add(2, 2);
12 assert_eq!(result, 4);
13 }
14}
15
16/// AST and parsing logic for floxide-macros
17use syn::{
18 braced, bracketed,
19 parse::{Parse, ParseStream},
20 Generics, Ident, Result, Token, Type, Visibility,
21};
22
23#[derive(Debug, Clone)]
24pub struct CompositeArm {
25 pub action_path: Ident,
26 pub variant: Ident,
27 pub binding: Option<Ident>,
28 pub is_wildcard: bool,
29 pub guard: Option<syn::Expr>,
30 pub succs: Vec<Ident>,
31}
32
33#[derive(Debug, Clone)]
34pub enum EdgeKind {
35 Direct {
36 succs: Vec<Ident>,
37 on_failure: Option<Vec<Ident>>,
38 },
39 Composite(Vec<CompositeArm>),
40}
41
42#[derive(Debug, Clone)]
43pub struct WorkflowDef {
44 pub vis: Visibility,
45 pub name: Ident,
46 pub generics: Generics,
47 pub fields: Vec<(Ident, Type, Option<Ident>)>,
48 pub start: Ident,
49 pub context: Type,
50 pub edges: Vec<(Ident, EdgeKind)>,
51}
52
53// Parsing logic for WorkflowDef (copy from floxide-macros/src/workflow.rs, adapted to use these types)
54impl Parse for WorkflowDef {
55 fn parse(input: ParseStream) -> Result<Self> {
56 // parse optional visibility
57 let vis: Visibility = if input.peek(Token![pub]) {
58 input.parse()? // pub or pub(...) etc
59 } else {
60 Visibility::Inherited
61 };
62 // parse struct definition
63 input.parse::<Token![struct]>()?;
64 let name: Ident = input.parse()?;
65 let generics: Generics = input.parse()?;
66 // parse struct fields
67 let content;
68 braced!(content in input);
69 let mut fields = Vec::new();
70 while !content.is_empty() {
71 // Optional retry annotation: #[retry = policy]
72 let mut retry_policy: Option<Ident> = None;
73 if content.peek(Token![#]) {
74 content.parse::<Token![#]>()?;
75 let inner;
76 bracketed!(inner in content);
77 let attr_name: Ident = inner.parse()?;
78 if attr_name == "retry" {
79 inner.parse::<Token![=]>()?;
80 let pol: Ident = inner.parse()?;
81 retry_policy = Some(pol);
82 } else {
83 return Err(inner.error("unknown attribute, expected `retry`"));
84 }
85 }
86 // Field name and type
87 let fld: Ident = content.parse()?;
88 content.parse::<Token![:]>()?;
89 let ty: Type = content.parse()?;
90 if content.peek(Token![,]) {
91 content.parse::<Token![,]>()?;
92 }
93 fields.push((fld, ty, retry_policy));
94 }
95 // Now parse the rest in any order: start, context, edges
96 let mut start: Option<Ident> = None;
97 let mut context: Option<Type> = None;
98 let mut edges: Option<Vec<(Ident, EdgeKind)>> = None;
99 let mut seen = std::collections::HashSet::new();
100 while !input.is_empty() {
101 if input.peek(Ident) {
102 let fork = input.fork();
103 let kw = fork.parse::<Ident>()?;
104 match kw.to_string().as_str() {
105 "start" => {
106 if seen.contains("start") {
107 return Err(
108 input.error("Duplicate 'start' field in workflow definition.")
109 );
110 }
111 input.parse::<Ident>()?; // start
112 input.parse::<Token![=]>()?;
113 let s: Ident = input.parse()?;
114 input.parse::<Token![;]>()?;
115 start = Some(s);
116 seen.insert("start");
117 }
118 "context" => {
119 if seen.contains("context") {
120 return Err(
121 input.error("Duplicate 'context' field in workflow definition.")
122 );
123 }
124 input.parse::<Ident>()?; // context
125 input.parse::<Token![=]>()?;
126 let ty: Type = input.parse()?;
127 input.parse::<Token![;]>()?;
128 context = Some(ty);
129 seen.insert("context");
130 }
131 "edges" => {
132 if seen.contains("edges") {
133 return Err(
134 input.error("Duplicate 'edges' field in workflow definition.")
135 );
136 }
137 input.parse::<Ident>()?; // edges
138 let edges_content;
139 braced!(edges_content in input);
140 // Collect direct-success, direct-failure, and composite arms
141 let mut direct_success =
142 std::collections::HashMap::<Ident, Vec<Ident>>::new();
143 let mut direct_failure =
144 std::collections::HashMap::<Ident, Vec<Ident>>::new();
145 let mut composite_map =
146 std::collections::HashMap::<Ident, Vec<CompositeArm>>::new();
147 while !edges_content.is_empty() {
148 let src: Ident = edges_content.parse()?;
149 if edges_content.peek(Ident) {
150 // on_failure clause
151 let kw: Ident = edges_content.parse()?;
152 if kw == "on_failure" {
153 edges_content.parse::<Token![=>]>()?;
154 let nested;
155 braced!(nested in edges_content);
156 // expect bracketed fallback list
157 let succs_content;
158 bracketed!(succs_content in nested);
159 let fails: Vec<Ident> = succs_content
160 .parse_terminated(Ident::parse, Token![,])?
161 .into_iter()
162 .collect();
163 edges_content.parse::<Token![;]>()?;
164 direct_failure.insert(src.clone(), fails);
165 continue;
166 } else {
167 return Err(edges_content.error(
168 "Unexpected identifier. Expected `on_failure` or `=>`.",
169 ));
170 }
171 }
172 // success or composite entry
173 edges_content.parse::<Token![=>]>()?;
174 if edges_content.peek(syn::token::Bracket) {
175 // direct edge: foo => [bar];
176 let succs_content;
177 bracketed!(succs_content in edges_content);
178 let succs: Vec<Ident> = succs_content
179 .parse_terminated(Ident::parse, Token![,])?
180 .into_iter()
181 .collect();
182 edges_content.parse::<Token![;]>()?;
183 direct_success.insert(src.clone(), succs);
184 } else if edges_content.peek(syn::token::Brace) {
185 // direct or composite edge: foo => { ... };
186 let nested;
187 braced!(nested in edges_content);
188 // Empty braces => direct edge with no successors
189 if nested.is_empty() {
190 edges_content.parse::<Token![;]>()?;
191 direct_success.insert(src.clone(), Vec::new());
192 } else if nested.peek(syn::token::Bracket) {
193 // legacy: foo => {[bar]};
194 let succs_content;
195 bracketed!(succs_content in nested);
196 let succs: Vec<Ident> = succs_content
197 .parse_terminated(Ident::parse, Token![,])?
198 .into_iter()
199 .collect();
200 edges_content.parse::<Token![;]>()?;
201 direct_success.insert(src.clone(), succs);
202 } else {
203 // composite arms
204 let mut arms = Vec::new();
205 while !nested.is_empty() {
206 let action_path: Ident = nested.parse()?;
207 nested.parse::<Token![::]>()?;
208 let variant: Ident = nested.parse()?;
209 let mut binding = None;
210 let mut is_wildcard = false;
211 if nested.peek(syn::token::Paren) {
212 let inner;
213 syn::parenthesized!(inner in nested);
214 if inner.peek(Token![_]) {
215 inner.parse::<Token![_]>()?;
216 is_wildcard = true;
217 } else if inner.peek(Ident) {
218 binding = Some(inner.parse()?);
219 } else {
220 return Err(inner.error(
221 "Expected identifier or _ in variant binding",
222 ));
223 }
224 } else {
225 // No parens: always treat as wildcard for macro ergonomics
226 is_wildcard = true;
227 binding = None;
228 }
229 // Optional guard
230 let guard = if nested.peek(Token![if]) {
231 nested.parse::<Token![if]>()?;
232 Some(nested.parse()?)
233 } else {
234 None
235 };
236 nested.parse::<Token![=>]>()?;
237 let succs_content;
238 bracketed!(succs_content in nested);
239 let succs: Vec<Ident> = succs_content
240 .parse_terminated(Ident::parse, Token![,])?
241 .into_iter()
242 .collect();
243 nested.parse::<Token![;]>()?;
244 let arm = CompositeArm {
245 action_path: action_path.clone(),
246 variant: variant.clone(),
247 binding: binding.clone(),
248 is_wildcard,
249 guard: guard.clone(),
250 succs: succs.clone(),
251 };
252 // Debug log removed: parsed arm
253 arms.push(arm);
254 }
255 edges_content.parse::<Token![;]>()?;
256 // After parsing all arms for a composite edge, print them for debugging
257 // Debug log removed: parsed composite arms for source
258 composite_map.insert(src.clone(), arms);
259 }
260 } else {
261 return Err(edges_content
262 .error("Expected [ or { after => in edge definition"));
263 }
264 }
265 // Merge into final edges vector
266 let mut edges_vec = Vec::new();
267 // direct-success entries
268 for (src, succs) in direct_success.into_iter() {
269 let failure = direct_failure.remove(&src);
270 edges_vec.push((
271 src,
272 EdgeKind::Direct {
273 succs,
274 on_failure: failure,
275 },
276 ));
277 }
278 // direct-failure-only entries
279 for (src, fails) in direct_failure.into_iter() {
280 edges_vec.push((
281 src,
282 EdgeKind::Direct {
283 succs: Vec::new(),
284 on_failure: Some(fails),
285 },
286 ));
287 }
288 // composite entries
289 for (src, arms) in composite_map.into_iter() {
290 edges_vec.push((src, EdgeKind::Composite(arms)));
291 }
292 edges = Some(edges_vec);
293 seen.insert("edges");
294 }
295 other => {
296 return Err(input.error(format!(
297 "Unexpected identifier '{}'. Expected one of: start, context, edges.",
298 other
299 )));
300 }
301 }
302 } else {
303 return Err(input.error("Unexpected token in workflow definition. Expected 'start', 'context', or 'edges'."));
304 }
305 }
306 // Check required fields
307 let start = start
308 .ok_or_else(|| input.error("Missing required 'start' field in workflow definition."))?;
309 let context = context.unwrap_or_else(|| syn::parse_quote! { () });
310 let edges = edges
311 .ok_or_else(|| input.error("Missing required 'edges' field in workflow definition."))?;
312 Ok(WorkflowDef {
313 vis,
314 name,
315 generics,
316 fields,
317 start,
318 context,
319 edges,
320 })
321 }
322}