Files
Vincent Bourdon 9af114e391 Initial import
2026-06-09 16:14:55 +02:00

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, &register_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"
```