1use sim_lib_control::{FrameError, FrameLimits, ResumableFrame, ResumePacket, ResumeResult};
4
5pub struct PythonIterator<T> {
7 values: std::vec::IntoIter<T>,
8}
9impl<T> PythonIterator<T> {
10 pub fn new(values: Vec<T>) -> Self {
12 Self {
13 values: values.into_iter(),
14 }
15 }
16 pub fn next_checked(&mut self) -> Option<T> {
18 self.values.next()
19 }
20}
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct PythonException {
25 pub class: String,
27 pub message: String,
29 pub cause: Option<Box<PythonException>>,
31 pub context: Option<Box<PythonException>>,
33 pub suppress_context: bool,
35}
36
37impl PythonException {
38 pub fn new(class: impl Into<String>, message: impl Into<String>) -> Self {
40 Self {
41 class: class.into(),
42 message: message.into(),
43 cause: None,
44 context: None,
45 suppress_context: false,
46 }
47 }
48 pub fn with_cause(mut self, cause: PythonException) -> Self {
50 self.cause = Some(Box::new(cause));
51 self.suppress_context = true;
52 self
53 }
54 pub fn with_context(mut self, context: PythonException) -> Self {
56 self.context = Some(Box::new(context));
57 self
58 }
59}
60
61#[derive(Clone, Debug, Eq, PartialEq)]
63pub struct PythonExceptionGroup {
64 pub message: String,
66 pub exceptions: Vec<PythonException>,
68}
69impl PythonExceptionGroup {
70 pub fn new(
72 message: impl Into<String>,
73 exceptions: Vec<PythonException>,
74 ) -> Result<Self, PythonException> {
75 if exceptions.is_empty() {
76 Err(PythonException::new(
77 "ValueError",
78 "exception group must be non-empty",
79 ))
80 } else {
81 Ok(Self {
82 message: message.into(),
83 exceptions,
84 })
85 }
86 }
87 pub fn split(self, class: &str) -> (Option<Self>, Option<Self>) {
89 let (matched, rest): (Vec<_>, Vec<_>) = self
90 .exceptions
91 .into_iter()
92 .partition(|error| error.class == class);
93 let make = |exceptions: Vec<_>| {
94 (!exceptions.is_empty()).then(|| Self {
95 message: self.message.clone(),
96 exceptions,
97 })
98 };
99 (make(matched), make(rest))
100 }
101}
102
103pub trait ContextManager<T> {
105 fn enter(&mut self) -> Result<T, PythonException>;
107 fn exit(&mut self, error: Option<&PythonException>) -> Result<bool, PythonException>;
109}
110
111pub fn run_with_context<T, R>(
113 manager: &mut impl ContextManager<T>,
114 body: impl FnOnce(T) -> Result<R, PythonException>,
115) -> Result<Option<R>, PythonException> {
116 let entered = manager.enter()?;
117 match body(entered) {
118 Ok(value) => {
119 manager.exit(None)?;
120 Ok(Some(value))
121 }
122 Err(error) => {
123 if manager.exit(Some(&error))? {
124 Ok(None)
125 } else {
126 Err(error)
127 }
128 }
129 }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq)]
134pub enum PythonGeneratorStep<T> {
135 Yielded(T),
137 Returned(T),
139}
140
141#[derive(Clone, Debug, Eq, PartialEq)]
143pub enum PythonGeneratorError {
144 Frame(FrameError),
146 Raised(PythonException),
148}
149
150pub struct PythonGenerator<T, D> {
152 frame: ResumableFrame<D>,
153 _value: std::marker::PhantomData<T>,
154}
155impl<T, D> PythonGenerator<T, D>
156where
157 D: FnMut(
158 ResumePacket<T, PythonException>,
159 &mut sim_lib_control::StepBudget,
160 ) -> Result<ResumeResult<T, T, PythonException>, FrameError>,
161{
162 pub fn new(limits: FrameLimits, driver: D) -> Self {
164 Self {
165 frame: ResumableFrame::new(limits, driver),
166 _value: std::marker::PhantomData,
167 }
168 }
169 pub fn start(&mut self) -> Result<PythonGeneratorStep<T>, PythonGeneratorError> {
171 self.resume(ResumePacket::Start)
172 }
173 pub fn send(&mut self, value: T) -> Result<PythonGeneratorStep<T>, PythonGeneratorError> {
175 self.resume(ResumePacket::Send(value))
176 }
177 pub fn throw(
179 &mut self,
180 error: PythonException,
181 ) -> Result<PythonGeneratorStep<T>, PythonGeneratorError> {
182 self.resume(ResumePacket::Throw(error))
183 }
184 pub fn close(&mut self) -> Result<PythonGeneratorStep<T>, PythonGeneratorError> {
186 self.resume(ResumePacket::Close)
187 }
188 fn resume(
189 &mut self,
190 packet: ResumePacket<T, PythonException>,
191 ) -> Result<PythonGeneratorStep<T>, PythonGeneratorError> {
192 match self
193 .frame
194 .resume(packet)
195 .map_err(PythonGeneratorError::Frame)?
196 {
197 ResumeResult::Yielded(value) => Ok(PythonGeneratorStep::Yielded(value)),
198 ResumeResult::Returned(value) => Ok(PythonGeneratorStep::Returned(value)),
199 ResumeResult::Failed(error) => Err(PythonGeneratorError::Raised(error)),
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use std::{cell::RefCell, rc::Rc};
208 #[test]
209 fn generator_composes_start_send_throw_and_close() {
210 let mut iterator = PythonIterator::new(vec![1, 2]);
211 assert_eq!(iterator.next_checked(), Some(1));
212 assert_eq!(iterator.next_checked(), Some(2));
213 assert_eq!(iterator.next_checked(), None);
214 let cleaned = Rc::new(RefCell::new(false));
215 let mark = cleaned.clone();
216 let mut generator =
217 PythonGenerator::new(FrameLimits { depth: 4, work: 8 }, move |packet, budget| {
218 budget.charge_work()?;
219 Ok(match packet {
220 ResumePacket::Start => ResumeResult::Yielded(1),
221 ResumePacket::Send(value) => ResumeResult::Yielded(value + 1),
222 ResumePacket::Throw(error) => ResumeResult::Failed(error),
223 ResumePacket::Close => {
224 *mark.borrow_mut() = true;
225 ResumeResult::Returned(0)
226 }
227 })
228 });
229 assert_eq!(generator.start(), Ok(PythonGeneratorStep::Yielded(1)));
230 assert_eq!(generator.send(4), Ok(PythonGeneratorStep::Yielded(5)));
231 assert_eq!(generator.close(), Ok(PythonGeneratorStep::Returned(0)));
232 assert!(*cleaned.borrow());
233
234 let mut throwing = PythonGenerator::new(FrameLimits { depth: 1, work: 1 }, |packet, _| {
235 Ok(match packet {
236 ResumePacket::Start => ResumeResult::Yielded(0),
237 ResumePacket::Throw(error) => ResumeResult::Failed(error),
238 ResumePacket::Send(value) => ResumeResult::Yielded(value),
239 ResumePacket::Close => ResumeResult::Returned(0),
240 })
241 });
242 throwing.start().unwrap();
243 assert!(matches!(
244 throwing.throw(PythonException::new("KeyError", "x")),
245 Err(PythonGeneratorError::Raised(_))
246 ));
247 }
248
249 #[test]
250 fn chaining_groups_and_context_cleanup_are_checked() {
251 let root = PythonException::new("OSError", "root");
252 let chained = PythonException::new("RuntimeError", "outer")
253 .with_context(root.clone())
254 .with_cause(root);
255 assert!(chained.suppress_context);
256 let group = PythonExceptionGroup::new(
257 "many",
258 vec![chained, PythonException::new("TypeError", "bad")],
259 )
260 .unwrap();
261 assert_eq!(group.split("TypeError").0.unwrap().exceptions.len(), 1);
262 struct Manager(bool);
263 impl ContextManager<i32> for Manager {
264 fn enter(&mut self) -> Result<i32, PythonException> {
265 Ok(42)
266 }
267 fn exit(&mut self, _: Option<&PythonException>) -> Result<bool, PythonException> {
268 self.0 = true;
269 Ok(false)
270 }
271 }
272 let mut manager = Manager(false);
273 assert_eq!(run_with_context(&mut manager, Ok), Ok(Some(42)));
274 assert!(manager.0);
275 }
276}