120 lines
3.6 KiB
Markdown
120 lines
3.6 KiB
Markdown
# 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<f64>,
|
|
pub qubits: Vec<usize>,
|
|
}
|
|
|
|
pub struct CircuitAnalyzer;
|
|
|
|
impl CircuitAnalyzer {
|
|
pub fn list_gates(source: &CircuitSource) -> Result<Vec<GateCallInfo>, 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<Vec<GateCallInfo>, 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"
|
|
```
|