Skip to main content

telltale_runtime/effects/
algebra_program_analysis.rs

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