telltale_runtime/effects/
algebra_program_analysis.rs1use super::{Effect, Program};
4use crate::effects::RoleId;
5use std::collections::HashSet;
6
7impl<R: RoleId, M> Program<R, M> {
9 #[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 #[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 #[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 #[must_use]
125 pub fn has_timeouts(&self) -> bool {
126 self.effects
127 .iter()
128 .any(|e| matches!(e, Effect::Timeout { .. }))
129 }
130
131 #[must_use]
133 pub fn has_parallel(&self) -> bool {
134 self.effects
135 .iter()
136 .any(|e| matches!(e, Effect::Parallel { .. }))
137 }
138
139 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 }
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#[derive(Debug, Clone, PartialEq)]
190pub enum ProgramError {
191 InvalidStructure(String),
193
194 UnbalancedCommunication,
196
197 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#[derive(Debug, Clone)]
217pub struct InterpretResult<M> {
218 pub received_values: Vec<M>,
220
221 pub final_state: InterpreterState,
223}
224
225#[derive(Debug, Clone, PartialEq)]
227pub enum InterpreterState {
228 Completed,
230
231 Timeout,
233
234 Failed(String),
236}
237
238pub trait ProgramMessage: Clone + Send + Sync + std::fmt::Debug {}
240impl<T: Clone + Send + Sync + std::fmt::Debug> ProgramMessage for T {}