telltale_runtime/effects/
algebra_program_analysis.rs1use super::{Effect, Program};
2use crate::effects::RoleId;
3use std::collections::HashSet;
4
5impl<R: RoleId, M> Program<R, M> {
7 #[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 #[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 #[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 #[must_use]
123 pub fn has_timeouts(&self) -> bool {
124 self.effects
125 .iter()
126 .any(|e| matches!(e, Effect::Timeout { .. }))
127 }
128
129 #[must_use]
131 pub fn has_parallel(&self) -> bool {
132 self.effects
133 .iter()
134 .any(|e| matches!(e, Effect::Parallel { .. }))
135 }
136
137 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 }
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#[derive(Debug, Clone, PartialEq)]
189pub enum ProgramError {
190 InvalidStructure(String),
192
193 UnbalancedCommunication,
195
196 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#[derive(Debug, Clone)]
216pub struct InterpretResult<M> {
217 pub received_values: Vec<M>,
219
220 pub final_state: InterpreterState,
222}
223
224#[derive(Debug, Clone, PartialEq)]
226pub enum InterpreterState {
227 Completed,
229
230 Timeout,
232
233 Failed(String),
235}
236
237pub trait ProgramMessage: Clone + Send + Sync + std::fmt::Debug {}
239impl<T: Clone + Send + Sync + std::fmt::Debug> ProgramMessage for T {}