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

3.6 KiB

Task 4 — CircuitAnalyzer (AST-based gate listing)

Index: README. Spec: design.

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
//! 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.

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
pub mod circuit_analyzer;
  • Step 4: Run tests
cargo test circuit_analyzer::tests 2>&1 | grep -E "test result|FAILED"

Expected: test result: ok. 3 passed.

  • Step 5: Commit
git add src/circuit_analyzer.rs src/executor.rs src/lib.rs
git commit -m "feat: add CircuitAnalyzer for AST-based gate enumeration"