quil_rs/instruction/
control_flow.rs

1use std::sync::Arc;
2
3#[cfg(not(feature = "python"))]
4use optipy::strip_pyo3;
5#[cfg(feature = "stubs")]
6use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyclass_complex_enum, gen_stub_pymethods};
7
8use super::MemoryReference;
9use crate::{
10    pickleable_new,
11    quil::{Quil, ToQuilError},
12};
13
14#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
16#[cfg_attr(
17    feature = "python",
18    pyo3::pyclass(module = "quil.instructions", eq, frozen, hash, get_all, subclass)
19)]
20pub struct Label {
21    pub target: Target,
22}
23
24pickleable_new! {
25    impl Label {
26        pub fn new(target: Target);
27    }
28}
29
30impl Quil for Label {
31    fn write(
32        &self,
33        writer: &mut impl std::fmt::Write,
34        fall_back_to_debug: bool,
35    ) -> crate::quil::ToQuilResult<()> {
36        write!(writer, "LABEL ")?;
37        self.target.write(writer, fall_back_to_debug)
38    }
39}
40
41#[derive(Clone, Debug, PartialEq, Eq, Hash, strum::EnumTryAs)]
42#[cfg_attr(feature = "stubs", gen_stub_pyclass_complex_enum)]
43#[cfg_attr(
44    feature = "python",
45    pyo3::pyclass(module = "quil.instructions", eq, frozen, hash)
46)]
47pub enum Target {
48    Fixed(String),
49    Placeholder(TargetPlaceholder),
50}
51
52impl Target {
53    pub(crate) fn resolve_placeholder<R>(&mut self, resolver: R)
54    where
55        R: Fn(&TargetPlaceholder) -> Option<String>,
56    {
57        if let Target::Placeholder(placeholder) = self {
58            if let Some(resolved) = resolver(placeholder) {
59                *self = Target::Fixed(resolved);
60            }
61        }
62    }
63}
64
65impl Quil for Target {
66    fn write(
67        &self,
68        writer: &mut impl std::fmt::Write,
69        fall_back_to_debug: bool,
70    ) -> crate::quil::ToQuilResult<()> {
71        match self {
72            Target::Fixed(label) => write!(writer, "@{label}").map_err(Into::into),
73            Target::Placeholder(_) => {
74                if fall_back_to_debug {
75                    write!(writer, "@{self:?}").map_err(Into::into)
76                } else {
77                    Err(ToQuilError::UnresolvedLabelPlaceholder)
78                }
79            }
80        }
81    }
82}
83
84type TargetPlaceholderInner = Arc<String>;
85
86/// An opaque placeholder for a label whose index may be assigned
87/// at a later time.
88#[derive(Clone, Debug, Eq)]
89#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
90#[cfg_attr(
91    feature = "python",
92    pyo3::pyclass(module = "quil.instructions", eq, frozen, hash, ord, subclass)
93)]
94pub struct TargetPlaceholder(TargetPlaceholderInner);
95
96#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
97#[cfg_attr(feature = "python", pyo3::pymethods)]
98#[cfg_attr(not(feature = "python"), strip_pyo3)]
99impl TargetPlaceholder {
100    #[new]
101    pub fn new(base_label: String) -> Self {
102        Self(Arc::new(base_label))
103    }
104
105    #[getter(base_label)]
106    pub fn as_inner(&self) -> &str {
107        &self.0
108    }
109}
110
111impl TargetPlaceholder {
112    fn address(&self) -> usize {
113        &*self.0 as *const _ as usize
114    }
115}
116
117impl std::hash::Hash for TargetPlaceholder {
118    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
119        self.address().hash(state);
120    }
121}
122
123impl PartialOrd for TargetPlaceholder {
124    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
125        Some(self.cmp(other))
126    }
127}
128
129impl Ord for TargetPlaceholder {
130    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
131        self.address().cmp(&other.address())
132    }
133}
134
135impl PartialEq for TargetPlaceholder {
136    fn eq(&self, other: &Self) -> bool {
137        Arc::<std::string::String>::ptr_eq(&self.0, &other.0)
138    }
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
142#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
143#[cfg_attr(
144    feature = "python",
145    pyo3::pyclass(module = "quil.instructions", eq, get_all, set_all, subclass)
146)]
147pub struct Jump {
148    pub target: Target,
149}
150
151impl Quil for Jump {
152    fn write(
153        &self,
154        writer: &mut impl std::fmt::Write,
155        fall_back_to_debug: bool,
156    ) -> Result<(), crate::quil::ToQuilError> {
157        write!(writer, "JUMP ")?;
158        self.target.write(writer, fall_back_to_debug)?;
159        Ok(())
160    }
161}
162
163pickleable_new! {
164    impl Jump {
165        pub fn new(target: Target);
166    }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
170#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
171#[cfg_attr(
172    feature = "python",
173    pyo3::pyclass(module = "quil.instructions", eq, get_all, set_all, subclass)
174)]
175pub struct JumpWhen {
176    pub target: Target,
177    pub condition: MemoryReference,
178}
179
180pickleable_new! {
181    impl JumpWhen {
182        pub fn new(target: Target, condition: MemoryReference);
183    }
184}
185
186impl Quil for JumpWhen {
187    fn write(
188        &self,
189        writer: &mut impl std::fmt::Write,
190        fall_back_to_debug: bool,
191    ) -> Result<(), crate::quil::ToQuilError> {
192        write!(writer, "JUMP-WHEN ")?;
193        self.target.write(writer, fall_back_to_debug)?;
194        write!(writer, " {}", self.condition)?;
195        Ok(())
196    }
197}
198
199#[derive(Clone, Debug, PartialEq, Eq)]
200#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
201#[cfg_attr(
202    feature = "python",
203    pyo3::pyclass(module = "quil.instructions", eq, get_all, set_all, subclass)
204)]
205pub struct JumpUnless {
206    pub target: Target,
207    pub condition: MemoryReference,
208}
209
210pickleable_new! {
211    impl JumpUnless {
212        pub fn new(target: Target, condition: MemoryReference);
213    }
214}
215
216impl Quil for JumpUnless {
217    fn write(
218        &self,
219        writer: &mut impl std::fmt::Write,
220        fall_back_to_debug: bool,
221    ) -> Result<(), crate::quil::ToQuilError> {
222        write!(writer, "JUMP-UNLESS ")?;
223        self.target.write(writer, fall_back_to_debug)?;
224        write!(writer, " {}", self.condition)?;
225        Ok(())
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232    use rstest::rstest;
233
234    #[test]
235    fn resolve_placeholder() {
236        let mut label = Target::Placeholder(TargetPlaceholder::new("base".to_string()));
237        label.resolve_placeholder(|_| Some("test".to_string()));
238        assert_eq!(label, Target::Fixed("test".to_string()))
239    }
240
241    #[rstest]
242    #[case(Target::Fixed(String::from("test")), Ok("@test"), "@test")]
243    #[case(
244        Target::Placeholder(TargetPlaceholder::new(String::from("test-placeholder"))),
245        Err(ToQuilError::UnresolvedLabelPlaceholder),
246        "@Placeholder(TargetPlaceholder(\"test-placeholder\"))"
247    )]
248    fn quil_format(
249        #[case] input: Target,
250        #[case] expected_quil: crate::quil::ToQuilResult<&str>,
251        #[case] expected_debug: &str,
252    ) {
253        assert_eq!(input.to_quil(), expected_quil.map(|s| s.to_string()));
254        assert_eq!(input.to_quil_or_debug(), expected_debug);
255    }
256}