1use bloq_utils::{Direction, PauliBasis};
4use glam::IVec3;
5use std::collections::BTreeSet;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum BinaryOp {
10 Xor,
12 And,
14 Or,
16}
17
18impl BinaryOp {
19 pub const fn precedence(&self) -> u8 {
21 match self {
22 BinaryOp::Or => 1,
23 BinaryOp::Xor => 2,
24 BinaryOp::And => 3,
25 }
26 }
27
28 fn symbol(&self) -> &'static str {
29 match self {
30 BinaryOp::Xor => "^",
31 BinaryOp::And => "&",
32 BinaryOp::Or => "|",
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub enum Expr {
40 Var(String),
42 Not(Box<Expr>),
44 Binary(BinaryOp, Box<Expr>, Box<Expr>),
46}
47
48impl Expr {
49 pub(crate) fn negated(self) -> Self {
50 match self {
51 Self::Not(inner) => *inner,
52 expr => Self::Not(Box::new(expr)),
53 }
54 }
55}
56
57impl std::fmt::Display for Expr {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Expr::Var(name) => write!(f, "{}", name),
61 Expr::Not(expr) => {
62 if matches!(expr.as_ref(), Expr::Binary(..)) {
63 write!(f, "!({})", expr)
64 } else {
65 write!(f, "!{}", expr)
66 }
67 }
68 Expr::Binary(op, lhs, rhs) => {
69 let prec = op.precedence();
70 if needs_parens(lhs, prec, true) {
72 write!(f, "({})", lhs)?;
73 } else {
74 write!(f, "{}", lhs)?;
75 }
76 write!(f, " {} ", op.symbol())?;
77 if needs_parens(rhs, prec, false) {
79 write!(f, "({})", rhs)
80 } else {
81 write!(f, "{}", rhs)
82 }
83 }
84 }
85 }
86}
87
88fn needs_parens(expr: &Expr, parent_prec: u8, is_left: bool) -> bool {
93 if let Expr::Binary(child_op, _, _) = expr {
94 let child_prec = child_op.precedence();
95 if is_left {
96 child_prec < parent_prec
97 } else {
98 child_prec <= parent_prec
99 }
100 } else {
101 false
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub struct FeedbackTarget {
108 pub pauli: PauliBasis,
110 pub target: IVec3,
112 pub direction: Option<Direction>,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120pub enum MeasureTarget {
121 Node(IVec3),
123 Edge {
125 src: IVec3,
127 dir: Direction,
129 },
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
138pub enum Action {
139 Let {
141 name: String,
143 expr: Expr,
145 },
146 Measure {
148 target: MeasureTarget,
150 name: String,
152 },
153 DiscardIf(Expr),
155 Resolve {
157 target: IVec3,
159 condition: Expr,
161 },
162 Branch {
166 target: IVec3,
168 condition: Expr,
170 },
171 Feedback {
173 targets: Vec<FeedbackTarget>,
175 condition: Option<Expr>,
177 },
178}
179
180impl Action {
181 pub fn referenced_names(&self) -> BTreeSet<&str> {
183 fn collect<'a>(expr: &'a Expr, names: &mut BTreeSet<&'a str>) {
184 match expr {
185 Expr::Var(name) => {
186 names.insert(name);
187 }
188 Expr::Not(expr) => collect(expr, names),
189 Expr::Binary(_, left, right) => {
190 collect(left, names);
191 collect(right, names);
192 }
193 }
194 }
195 let mut names = BTreeSet::new();
196 let expression = match self {
197 Self::Let { expr, .. } | Self::DiscardIf(expr) => Some(expr),
198 Self::Resolve { condition, .. } | Self::Branch { condition, .. } => Some(condition),
199 Self::Feedback { condition, .. } => condition.as_ref(),
200 Self::Measure { .. } => None,
201 };
202 if let Some(expression) = expression {
203 collect(expression, &mut names);
204 }
205 names
206 }
207
208 pub(crate) fn try_with_orientation(
209 &self,
210 orientation: crate::ModuleOrientation,
211 ) -> Result<Self, crate::BlockGraphError> {
212 self.try_map_targets(
213 |position| orientation.try_rotate_position(position),
214 |direction| orientation.rotate_direction(direction),
215 )
216 }
217
218 pub fn with_shift(&self, offset: impl Into<IVec3>) -> Self {
225 self.try_with_shift(offset)
226 .expect("shifted action positions must fit in the i32 coordinate range")
227 }
228
229 pub fn try_with_shift(&self, offset: impl Into<IVec3>) -> Result<Self, crate::BlockGraphError> {
236 let offset = offset.into();
237 self.try_map_targets(
238 |position| crate::checked_add_position(position, offset),
239 |direction| direction,
240 )
241 }
242
243 fn try_map_targets(
244 &self,
245 position: impl Fn(IVec3) -> Result<IVec3, crate::BlockGraphError>,
246 direction: impl Fn(Direction) -> Direction,
247 ) -> Result<Self, crate::BlockGraphError> {
248 let mut action = self.clone();
249 match &mut action {
250 Action::Resolve { target, .. } | Action::Branch { target, .. } => {
251 *target = position(*target)?;
252 }
253 Action::Feedback { targets, .. } => {
254 for target in targets {
255 target.target = position(target.target)?;
256 target.direction = target.direction.map(&direction);
257 if let Some(dir) = target.direction {
258 crate::checked_add_position(target.target, dir.to_ivec3())?;
259 }
260 }
261 }
262 Action::Measure { target, .. } => match target {
263 MeasureTarget::Node(target) => *target = position(*target)?,
264 MeasureTarget::Edge { src, dir } => {
265 *src = position(*src)?;
266 *dir = direction(*dir);
267 crate::checked_add_position(*src, dir.to_ivec3())?;
268 }
269 },
270 Action::Let { .. } | Action::DiscardIf(..) => {}
271 }
272 Ok(action)
273 }
274
275 pub(crate) fn display_with_ids(&self, p2i: impl Fn(IVec3) -> Option<u32>) -> String {
276 let target_str = |target| {
277 if let Some(id) = p2i(target) {
278 return id.to_string();
279 }
280 target.to_string()
281 };
282 match self {
283 Action::Let { name, expr } => format!("{name} = {expr}"),
284 Action::Measure { target, name } => {
285 let target_str_val = match target {
286 MeasureTarget::Node(pos) => target_str(*pos),
287 MeasureTarget::Edge { src, dir } => {
288 format!("{} -> {}", target_str(*src), dir)
289 }
290 };
291 format!("{name} = measure {target_str_val}")
292 }
293 Action::DiscardIf(expr) => format!("discard if {expr}"),
294 Action::Resolve { target, condition } | Action::Branch { target, condition } => {
295 format!("resolve {} if {}", target_str(*target), condition)
296 }
297 Action::Feedback { targets, condition } => {
298 let ts = targets
299 .iter()
300 .map(|t| match t.direction {
301 Some(dir) => format!("{} {} -> {}", t.pauli, target_str(t.target), dir),
302 None => format!("{} {}", t.pauli, target_str(t.target)),
303 })
304 .collect::<Vec<_>>()
305 .join(", ");
306 if let Some(cond) = condition {
307 format!("feedback {} if {}", ts, cond)
308 } else {
309 format!("feedback {}", ts)
310 }
311 }
312 }
313 }
314}
315
316impl std::fmt::Display for Action {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 f.write_str(&self.display_with_ids(|_| None))
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn checked_shift_rejects_overflowing_measurement_edge() {
328 let action = Action::Measure {
329 target: MeasureTarget::Edge {
330 src: IVec3::new(i32::MAX - 1, 0, 0),
331 dir: Direction::XPLUS,
332 },
333 name: "m".into(),
334 };
335
336 assert!(matches!(
337 action.try_with_shift(IVec3::X),
338 Err(crate::BlockGraphError::CoordinateOverflow { .. })
339 ));
340 }
341
342 #[test]
343 fn expression_display_preserves_precedence_and_associativity() -> Result<(), crate::ParseError>
344 {
345 for (source, expected) in [
346 ("a | b & c", "a | b & c"),
347 ("(a | b) & c", "(a | b) & c"),
348 ("!(a | b)", "!(a | b)"),
349 ("!a", "!a"),
350 ("a ^ (b ^ c)", "a ^ (b ^ c)"),
351 ("(a ^ b) ^ c", "a ^ b ^ c"),
352 ] {
353 let actions = crate::parse_actions(&format!("result = {source}"), |_, _| None)?;
354 let [Action::Let { expr, .. }] = actions.as_slice() else {
355 unreachable!()
356 };
357 assert_eq!(expr.to_string(), expected, "{source}");
358 }
359 Ok::<_, crate::ParseError>(())
360 }
361
362 #[test]
363 fn branch_display_and_shift_preserve_condition() {
364 let action = Action::Branch {
365 target: IVec3::new(1, 2, 3),
366 condition: Expr::Var("m".into()),
367 };
368
369 assert_eq!(action.to_string(), "resolve [1, 2, 3] if m");
370 assert_eq!(
371 action.with_shift(IVec3::new(4, 5, 6)),
372 Action::Branch {
373 target: IVec3::new(5, 7, 9),
374 condition: Expr::Var("m".into()),
375 }
376 );
377 }
378}