# Task 4 — `CircuitAnalyzer` (AST-based gate listing) > **Index:** [README](README.md). **Spec:** [design](../../specs/2026-04-29-quantum-tutor-design.md). ## Goal Expose AST-based gate enumeration as a public utility so `explain_result` (Task 8) and any other consumer can iterate gates without re-parsing or string-matching the QASM source. Wraps the AST traversal already present in `executor::extract_gate_ops`. ## Prerequisites - Task 0 merged. ## Files - Create: `src/circuit_analyzer.rs` - Modify: `src/executor.rs` (add public `list_gate_calls`) - Modify: `src/lib.rs` ## Steps - [ ] **Step 1: Create `src/circuit_analyzer.rs` with failing tests** ```rust //! Pure AST-based gate listing for OpenQASM 3.0 circuits. use crate::error::BridgeError; use crate::types::CircuitSource; #[derive(Debug, Clone, PartialEq)] pub struct GateCallInfo { pub name: String, pub params: Vec, pub qubits: Vec, } pub struct CircuitAnalyzer; impl CircuitAnalyzer { pub fn list_gates(source: &CircuitSource) -> Result, BridgeError> { crate::executor::list_gate_calls(source) } } #[cfg(test)] mod tests { use super::*; const BELL: &str = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;"; #[test] fn list_gates_for_bell_returns_h_then_cx() { let gates = CircuitAnalyzer::list_gates(&CircuitSource(BELL.into())).unwrap(); assert_eq!(gates.len(), 2); assert_eq!(gates[0].name, "h"); assert_eq!(gates[0].qubits, vec![0]); assert_eq!(gates[1].name, "cx"); assert_eq!(gates[1].qubits, vec![0, 1]); } #[test] fn list_gates_ignores_measure() { let gates = CircuitAnalyzer::list_gates(&CircuitSource(BELL.into())).unwrap(); assert!(gates.iter().all(|g| g.name != "measure")); } #[test] fn list_gates_returns_empty_for_circuit_with_no_gates() { let identity = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[1] q;\nbit[1] c;\nc = measure q;"; let gates = CircuitAnalyzer::list_gates(&CircuitSource(identity.into())).unwrap(); assert!(gates.is_empty()); } } ``` - [ ] **Step 2: Expose `list_gate_calls` in `src/executor.rs`** Add the public function near the existing `extract_gate_ops` helper (or below it). Reuses the same parser path as `LocalSimulator::run`. ```rust use crate::circuit_analyzer::GateCallInfo; /// Public AST-based gate enumeration. Reuses the parser path of `run`. pub fn list_gate_calls(source: &CircuitSource) -> Result, BridgeError> { let parse_result = parse_source_string(&source.0, Some("circuit.qasm"), None::<&[&str]>); if parse_result.any_syntax_errors() { return Err(BridgeError::Simulation("circuit contains syntax errors".into())); } let context = parse_result.take_context(); let symbol_table = context.symbol_table(); let program = context.program(); let (_, register_offsets) = build_register_map(program, symbol_table); let ops = extract_gate_ops(program, symbol_table, ®ister_offsets)?; Ok(ops .into_iter() .map(|(name, params, qubits)| GateCallInfo { name, params, qubits }) .collect()) } ``` - [ ] **Step 3: Register `circuit_analyzer` in `src/lib.rs`** ```rust pub mod circuit_analyzer; ``` - [ ] **Step 4: Run tests** ```bash cargo test circuit_analyzer::tests 2>&1 | grep -E "test result|FAILED" ``` Expected: `test result: ok. 3 passed`. - [ ] **Step 5: Commit** ```bash git add src/circuit_analyzer.rs src/executor.rs src/lib.rs git commit -m "feat: add CircuitAnalyzer for AST-based gate enumeration" ```