Skip to main content

telltale_runtime/effects/
algebra_program_analysis.rs

1//! Analysis utilities for free-algebra effect programs.
2
3use super::{Effect, Program};
4use crate::effects::RoleId;
5use std::collections::HashSet;
6
7/// Program analysis utilities
8impl<R: RoleId, M> Program<R, M> {
9    /// Get all roles involved in this program
10    #[must_use]
11    pub fn roles_involved(&self) -> HashSet<R> {
12        let mut roles = HashSet::new();
13        self.collect_roles(&mut roles);
14        roles
15    }
16
17    fn collect_roles(&self, roles: &mut HashSet<R>) {
18        for effect in &self.effects {
19            match effect {
20                Effect::Send { to, .. } => {
21                    roles.insert(*to);
22                }
23                Effect::Recv { from, .. } => {
24                    roles.insert(*from);
25                }
26                Effect::Choose { at, .. } => {
27                    roles.insert(*at);
28                }
29                Effect::Offer { from } => {
30                    roles.insert(*from);
31                }
32                Effect::Branch {
33                    choosing_role,
34                    branches,
35                } => {
36                    roles.insert(*choosing_role);
37                    for (_, prog) in branches {
38                        prog.collect_roles(roles);
39                    }
40                }
41                Effect::Loop { body, .. } => {
42                    body.collect_roles(roles);
43                }
44                Effect::Timeout {
45                    at,
46                    body,
47                    on_timeout,
48                    ..
49                } => {
50                    roles.insert(*at);
51                    body.collect_roles(roles);
52                    if let Some(timeout_body) = on_timeout {
53                        timeout_body.collect_roles(roles);
54                    }
55                }
56                Effect::Parallel { programs } => {
57                    for prog in programs {
58                        prog.collect_roles(roles);
59                    }
60                }
61                Effect::Extension(ext) => {
62                    for role in ext.participating_roles() {
63                        roles.insert(role);
64                    }
65                }
66                Effect::End => {}
67            }
68        }
69    }
70
71    /// Count the number of send operations
72    #[must_use]
73    pub fn send_count(&self) -> usize {
74        self.effects
75            .iter()
76            .map(|e| match e {
77                Effect::Send { .. } => 1,
78                Effect::Branch { branches, .. } => branches
79                    .iter()
80                    .map(|(_, p)| p.send_count())
81                    .max()
82                    .unwrap_or(0),
83                Effect::Loop { body, .. } => body.send_count(),
84                Effect::Timeout {
85                    body, on_timeout, ..
86                } => {
87                    let body_count = body.send_count();
88                    let timeout_count = on_timeout.as_ref().map_or(0, |p| p.send_count());
89                    body_count.max(timeout_count)
90                }
91                Effect::Parallel { programs } => programs.iter().map(Program::send_count).sum(),
92                _ => 0,
93            })
94            .sum()
95    }
96
97    /// Count the number of receive operations
98    #[must_use]
99    pub fn recv_count(&self) -> usize {
100        self.effects
101            .iter()
102            .map(|e| match e {
103                Effect::Recv { .. } => 1,
104                Effect::Branch { branches, .. } => branches
105                    .iter()
106                    .map(|(_, p)| p.recv_count())
107                    .max()
108                    .unwrap_or(0),
109                Effect::Loop { body, .. } => body.recv_count(),
110                Effect::Timeout {
111                    body, on_timeout, ..
112                } => {
113                    let body_count = body.recv_count();
114                    let timeout_count = on_timeout.as_ref().map_or(0, |p| p.recv_count());
115                    body_count.max(timeout_count)
116                }
117                Effect::Parallel { programs } => programs.iter().map(Program::recv_count).sum(),
118                _ => 0,
119            })
120            .sum()
121    }
122
123    /// Check if the program has any timeout effects
124    #[must_use]
125    pub fn has_timeouts(&self) -> bool {
126        self.effects
127            .iter()
128            .any(|e| matches!(e, Effect::Timeout { .. }))
129    }
130
131    /// Check if the program has any parallel effects
132    #[must_use]
133    pub fn has_parallel(&self) -> bool {
134        self.effects
135            .iter()
136            .any(|e| matches!(e, Effect::Parallel { .. }))
137    }
138
139    /// Validate that the program is well-formed
140    pub fn validate(&self) -> Result<(), ProgramError> {
141        for (idx, effect) in self.effects.iter().enumerate() {
142            match effect {
143                Effect::Branch { branches, .. } => {
144                    if branches.is_empty() {
145                        return Err(ProgramError::InvalidStructure(
146                            "Branch must have at least one branch".to_string(),
147                        ));
148                    }
149                    for (_, prog) in branches {
150                        prog.validate()?;
151                    }
152                }
153                Effect::Loop { body, .. } => body.validate()?,
154                Effect::Timeout {
155                    body, on_timeout, ..
156                } => {
157                    body.validate()?;
158                    if let Some(timeout_body) = on_timeout {
159                        timeout_body.validate()?;
160                    }
161                }
162                Effect::Parallel { programs } => {
163                    if programs.is_empty() {
164                        return Err(ProgramError::InvalidStructure(
165                            "Parallel must contain at least one program".to_string(),
166                        ));
167                    }
168                    for prog in programs {
169                        prog.validate()?;
170                    }
171                }
172                Effect::Extension(_) => {
173                    // Extensions are always valid - validation happens at runtime
174                }
175                Effect::End if idx + 1 != self.effects.len() => {
176                    return Err(ProgramError::InvalidStructure(
177                        "End must be the final effect".to_string(),
178                    ));
179                }
180                Effect::End => {}
181                _ => {}
182            }
183        }
184        Ok(())
185    }
186}
187
188/// Errors that can occur during program construction or analysis
189#[derive(Debug, Clone, PartialEq)]
190pub enum ProgramError {
191    /// Program contains invalid structure
192    InvalidStructure(String),
193
194    /// Program has unbalanced send/receive operations
195    UnbalancedCommunication,
196
197    /// Program contains unreachable effects
198    UnreachableCode,
199}
200
201impl std::fmt::Display for ProgramError {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        match self {
204            ProgramError::InvalidStructure(msg) => write!(f, "Invalid program structure: {msg}"),
205            ProgramError::UnbalancedCommunication => {
206                write!(f, "Unbalanced send/receive operations")
207            }
208            ProgramError::UnreachableCode => write!(f, "Program contains unreachable code"),
209        }
210    }
211}
212
213impl std::error::Error for ProgramError {}
214
215/// Result of interpreting a program
216#[derive(Debug, Clone)]
217pub struct InterpretResult<M> {
218    /// Messages received during execution
219    pub received_values: Vec<M>,
220
221    /// Final state of the interpreter
222    pub final_state: InterpreterState,
223}
224
225/// State of the program interpreter
226#[derive(Debug, Clone, PartialEq)]
227pub enum InterpreterState {
228    /// Program completed successfully
229    Completed,
230
231    /// Program was interrupted by timeout
232    Timeout,
233
234    /// Program failed with an error
235    Failed(String),
236}
237
238/// Type alias for any message type that can be used in programs
239pub trait ProgramMessage: Clone + Send + Sync + std::fmt::Debug {}
240impl<T: Clone + Send + Sync + std::fmt::Debug> ProgramMessage for T {}