Initial import
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
# Sub-plan 2 — CircuitValidator (Task 4)
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans.
|
||||
|
||||
**Goal:** Implement `CircuitValidator` wrapping `oq3_semantics`. Validates OpenQASM 3.0 syntax, qubit bounds, and our supported gate subset. Returns structured `ValidationResult` with line/column diagnostics.
|
||||
|
||||
**Starting state (after sub-plan 1):**
|
||||
- `Cargo.toml` with all deps
|
||||
- `src/main.rs` declares `mod error; mod executor; mod types;`
|
||||
- `src/error.rs` — `BridgeError`
|
||||
- `src/types.rs` — `CircuitSource`, `ValidationResult`, `ValidationDiagnostic`, `DiagnosticSeverity`
|
||||
- `src/executor.rs` — `SUPPORTED_GATES`, `MAX_LOCAL_QUBITS`, trait defs only
|
||||
- `cargo test` passes
|
||||
|
||||
**Deliverable:** `cargo test` passes with 5 validator unit tests green. One commit added.
|
||||
|
||||
**Main plan reference:** `docs/superpowers/plans/2026-04-28-quantum-bridge-mcp-v1.md` Task 4.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: CircuitValidator — oq3_semantics integration
|
||||
|
||||
**Files:**
|
||||
- Create: `src/validator.rs`
|
||||
- Modify: `src/main.rs` (add `mod validator;`)
|
||||
|
||||
### 4a — AST exploration
|
||||
|
||||
The `oq3_semantics` AST is lightly documented (6.9% rustdoc coverage). You MUST run the exploration test first to discover the real type names before implementing.
|
||||
|
||||
- [ ] **Step 1: Create src/validator.rs with exploration tests**
|
||||
|
||||
```rust
|
||||
use oq3_semantics::syntax_to_semantics::parse_source_string;
|
||||
|
||||
use crate::error::BridgeError;
|
||||
use crate::executor::SUPPORTED_GATES;
|
||||
use crate::types::{CircuitSource, DiagnosticSeverity, ValidationDiagnostic, ValidationResult};
|
||||
|
||||
pub struct CircuitValidator {
|
||||
max_qubits: usize,
|
||||
}
|
||||
|
||||
impl CircuitValidator {
|
||||
pub fn new(max_qubits: usize) -> Self {
|
||||
Self { max_qubits }
|
||||
}
|
||||
|
||||
pub fn validate(&self, source: &CircuitSource) -> Result<ValidationResult, BridgeError> {
|
||||
todo!("implement after AST exploration")
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_offset_to_line_col(source: &str, offset: usize) -> (usize, usize) {
|
||||
let safe_offset = offset.min(source.len());
|
||||
let prefix = &source[..safe_offset];
|
||||
let line = prefix.bytes().filter(|&b| b == b'\n').count() + 1;
|
||||
let col = prefix.rfind('\n').map(|p| safe_offset - p - 1).unwrap_or(safe_offset) + 1;
|
||||
(line, col)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod exploration {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn print_bell_circuit_ast() {
|
||||
let qasm = r#"OPENQASM 3.0;
|
||||
include "stdgates.inc";
|
||||
qubit[2] q;
|
||||
bit[2] c;
|
||||
h q[0];
|
||||
cx q[0], q[1];
|
||||
c = measure q;"#;
|
||||
let result = parse_source_string(qasm, Some("bell.qasm"), None::<&[&str]>);
|
||||
println!("any_errors: {}", result.any_errors());
|
||||
println!("program: {:#?}", result.program());
|
||||
for err in result.semantic_errors().iter() {
|
||||
println!("semantic_error: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_unsupported_gate_ast() {
|
||||
let qasm = r#"OPENQASM 3.0;
|
||||
include "stdgates.inc";
|
||||
qubit[1] q;
|
||||
u3(0.1, 0.2, 0.3) q[0];"#;
|
||||
let result = parse_source_string(qasm, Some("bad.qasm"), None::<&[&str]>);
|
||||
println!("any_errors: {}", result.any_errors());
|
||||
println!("program: {:#?}", result.program());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_qubit_declaration_ast() {
|
||||
let qasm = "OPENQASM 3.0;\nqubit[3] myq;\n";
|
||||
let result = parse_source_string(qasm, Some("q.qasm"), None::<&[&str]>);
|
||||
println!("program: {:#?}", result.program());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::executor::MAX_LOCAL_QUBITS;
|
||||
|
||||
fn validator() -> CircuitValidator {
|
||||
CircuitValidator::new(MAX_LOCAL_QUBITS)
|
||||
}
|
||||
|
||||
const BELL_CIRCUIT: &str = r#"OPENQASM 3.0;
|
||||
include "stdgates.inc";
|
||||
qubit[2] q;
|
||||
bit[2] c;
|
||||
h q[0];
|
||||
cx q[0], q[1];
|
||||
c = measure q;"#;
|
||||
|
||||
#[test]
|
||||
fn valid_bell_circuit_is_accepted() {
|
||||
let result = validator()
|
||||
.validate(&CircuitSource(BELL_CIRCUIT.to_string()))
|
||||
.unwrap();
|
||||
assert!(result.is_valid, "diagnostics: {:?}", result.diagnostics);
|
||||
assert!(result.diagnostics.is_empty());
|
||||
assert_eq!(result.num_qubits, Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_gate_produces_error_diagnostic() {
|
||||
let circuit = r#"OPENQASM 3.0;
|
||||
include "stdgates.inc";
|
||||
qubit[1] q;
|
||||
u3(0.1, 0.2, 0.3) q[0];"#;
|
||||
let result = validator()
|
||||
.validate(&CircuitSource(circuit.to_string()))
|
||||
.unwrap();
|
||||
assert!(!result.is_valid);
|
||||
assert!(!result.diagnostics.is_empty());
|
||||
let msg = &result.diagnostics[0].message;
|
||||
assert!(msg.to_lowercase().contains("u3"), "expected 'u3' in: {msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_qubits_produces_error_diagnostic() {
|
||||
let n = MAX_LOCAL_QUBITS + 1;
|
||||
let circuit = format!("OPENQASM 3.0;\nqubit[{n}] q;\n");
|
||||
let result = validator().validate(&CircuitSource(circuit)).unwrap();
|
||||
assert!(!result.is_valid);
|
||||
let msg = &result.diagnostics[0].message;
|
||||
assert!(msg.contains(&n.to_string()), "expected {n} in: {msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_qasm_syntax_produces_error_diagnostic() {
|
||||
let circuit = "OPENQASM 3.0;\nthis is not valid qasm;\n";
|
||||
let result = validator()
|
||||
.validate(&CircuitSource(circuit.to_string()))
|
||||
.unwrap();
|
||||
assert!(!result.is_valid);
|
||||
assert!(!result.diagnostics.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undeclared_qubit_produces_error_diagnostic() {
|
||||
let circuit = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[1] q;\nh q[5];\n";
|
||||
let result = validator()
|
||||
.validate(&CircuitSource(circuit.to_string()))
|
||||
.unwrap();
|
||||
assert!(!result.is_valid);
|
||||
assert!(!result.diagnostics.is_empty());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Declare the module**
|
||||
|
||||
Add to `src/main.rs`:
|
||||
```rust
|
||||
mod validator;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run exploration tests and read AST output**
|
||||
|
||||
```bash
|
||||
cargo test exploration -- --nocapture 2>&1 | head -300
|
||||
```
|
||||
|
||||
**Read the output carefully. Identify:**
|
||||
1. How `Program` exposes statements — look for `stmts()`, `statements()`, or `iter()`
|
||||
2. The `Stmt` enum variant name for gate calls (e.g. `GateCall`, `Gate`)
|
||||
3. The `Stmt` variant name for qubit register declarations (e.g. `QubitDeclaration`, `QubitDecl`)
|
||||
4. How to extract the gate name string from a gate call node
|
||||
5. How to extract the register size (width) from a qubit declaration node
|
||||
6. What `semantic_errors().iter()` yields — specifically the `.range()` method's return type
|
||||
|
||||
Write down the exact variant and method names before continuing.
|
||||
|
||||
- [ ] **Step 4: Confirm tests fail at todo!()**
|
||||
|
||||
```bash
|
||||
cargo test validator::tests 2>&1 | tail -10
|
||||
```
|
||||
|
||||
Expected: all 5 panic at `todo!()`.
|
||||
|
||||
### 4b — Implementation
|
||||
|
||||
- [ ] **Step 5: Implement CircuitValidator::validate**
|
||||
|
||||
Replace the `todo!()` with the full implementation. The skeleton below uses placeholder helper names — replace variant patterns with what you observed in Step 3:
|
||||
|
||||
```rust
|
||||
pub fn validate(&self, source: &CircuitSource) -> Result<ValidationResult, BridgeError> {
|
||||
let parse_result = parse_source_string(&source.0, Some("circuit.qasm"), None::<&[&str]>);
|
||||
let mut diagnostics: Vec<ValidationDiagnostic> = Vec::new();
|
||||
|
||||
// Collect oq3_semantics errors (syntax + undeclared symbols)
|
||||
for error in parse_result.semantic_errors().iter() {
|
||||
let range = error.range();
|
||||
// TextRange::start() → rowan::TextSize → u32 → usize
|
||||
let offset: usize = u32::from(range.start()) as usize;
|
||||
let (line, col) = byte_offset_to_line_col(&source.0, offset);
|
||||
diagnostics.push(ValidationDiagnostic {
|
||||
line,
|
||||
column: col,
|
||||
message: error.message(),
|
||||
severity: DiagnosticSeverity::Error,
|
||||
});
|
||||
}
|
||||
|
||||
if parse_result.any_errors() {
|
||||
return Ok(ValidationResult {
|
||||
is_valid: false,
|
||||
diagnostics,
|
||||
num_qubits: None,
|
||||
num_gates: None,
|
||||
});
|
||||
}
|
||||
|
||||
let program = parse_result.program();
|
||||
let mut total_qubits: usize = 0;
|
||||
let mut gate_count: usize = 0;
|
||||
|
||||
for stmt in program.stmts() { // ← adjust method name from exploration
|
||||
// Qubit declarations — adjust variant name from exploration output
|
||||
if let Some(n) = extract_qubit_count(stmt) {
|
||||
total_qubits += n;
|
||||
}
|
||||
|
||||
// Gate calls — adjust variant name from exploration output
|
||||
if let Some(gate_name) = extract_gate_name(stmt) {
|
||||
if gate_name == "measure" {
|
||||
continue;
|
||||
}
|
||||
gate_count += 1;
|
||||
if !SUPPORTED_GATES.contains(&gate_name.as_str()) {
|
||||
let line = extract_stmt_line(stmt, &source.0);
|
||||
let supported = SUPPORTED_GATES.join(", ");
|
||||
diagnostics.push(ValidationDiagnostic {
|
||||
line,
|
||||
column: 1,
|
||||
message: format!(
|
||||
"gate '{gate_name}' is not supported (supported: {supported})"
|
||||
),
|
||||
severity: DiagnosticSeverity::Error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total_qubits > self.max_qubits {
|
||||
diagnostics.push(ValidationDiagnostic {
|
||||
line: 1,
|
||||
column: 1,
|
||||
message: format!(
|
||||
"circuit requires {total_qubits} qubits, exceeds local simulator limit of {}",
|
||||
self.max_qubits
|
||||
),
|
||||
severity: DiagnosticSeverity::Error,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(ValidationResult {
|
||||
is_valid: diagnostics.is_empty(),
|
||||
diagnostics,
|
||||
num_qubits: if total_qubits > 0 { Some(total_qubits) } else { None },
|
||||
num_gates: if gate_count > 0 { Some(gate_count) } else { None },
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Implement the three AST helper functions**
|
||||
|
||||
Add below `validate`. Fill in match arms using the variant names from Step 3:
|
||||
|
||||
```rust
|
||||
use oq3_semantics::asg::Stmt;
|
||||
|
||||
fn extract_qubit_count(stmt: &Stmt) -> Option<usize> {
|
||||
// Fill in from exploration output, e.g.:
|
||||
// if let Stmt::QubitDeclaration(qd) = stmt {
|
||||
// return Some(qd.width().unwrap_or(1));
|
||||
// }
|
||||
todo!("fill in from AST exploration")
|
||||
}
|
||||
|
||||
fn extract_gate_name(stmt: &Stmt) -> Option<String> {
|
||||
// Fill in from exploration output, e.g.:
|
||||
// if let Stmt::GateCall(gc) = stmt {
|
||||
// return Some(gc.name().to_string().to_lowercase());
|
||||
// }
|
||||
todo!("fill in from AST exploration")
|
||||
}
|
||||
|
||||
fn extract_stmt_line(stmt: &Stmt, source: &str) -> usize {
|
||||
// Fill in from exploration output, e.g.:
|
||||
// let offset = u32::from(stmt.span().start()) as usize;
|
||||
// byte_offset_to_line_col(source, offset).0
|
||||
todo!("fill in from AST exploration")
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Run tests and iterate until all 5 pass**
|
||||
|
||||
```bash
|
||||
cargo test validator::tests 2>&1
|
||||
```
|
||||
|
||||
Common issues:
|
||||
- If `program.stmts()` doesn't exist: use `cargo doc --open` or hover in rust-analyzer to find the correct method on `Program`
|
||||
- If gate name extraction returns wrong case: add `.to_lowercase()`
|
||||
- If `too_many_qubits` test fails: `extract_qubit_count` may be returning 0 — verify the qubit declaration variant name
|
||||
|
||||
- [ ] **Step 8: Run full test suite**
|
||||
|
||||
```bash
|
||||
cargo test 2>&1
|
||||
```
|
||||
|
||||
Expected: all tests pass (exploration + validator::tests + executor::tests from sub-plan 1).
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add src/validator.rs src/main.rs
|
||||
git commit -m "feat: implement CircuitValidator with oq3_semantics"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Final verification
|
||||
|
||||
```bash
|
||||
cargo fmt --check && cargo clippy -- -D warnings && cargo test
|
||||
```
|
||||
|
||||
Expected: all green. Sub-plan 2 complete — hand off to sub-plan 3.
|
||||
Reference in New Issue
Block a user