1use super::ast::{
4 BinaryOp, ClassicalRef, Condition, Declaration, Expression, Literal, Measurement, QasmGate,
5 QasmProgram, QasmStatement, QubitRef, UnaryOp,
6};
7use std::collections::{HashMap, HashSet};
8use thiserror::Error;
9
10#[derive(Debug, Error)]
12pub enum ValidationError {
13 #[error("Undefined register: {0}")]
14 UndefinedRegister(String),
15
16 #[error("Undefined gate: {0}")]
17 UndefinedGate(String),
18
19 #[error("Undefined variable: {0}")]
20 UndefinedVariable(String),
21
22 #[error("Type mismatch: expected {expected}, found {found}")]
23 TypeMismatch { expected: String, found: String },
24
25 #[error(
26 "Index out of bounds: register {register} has size {size}, but index {index} was used"
27 )]
28 IndexOutOfBounds {
29 register: String,
30 size: usize,
31 index: usize,
32 },
33
34 #[error("Parameter count mismatch: gate {gate} expects {expected} parameters, but {found} were provided")]
35 ParameterCountMismatch {
36 gate: String,
37 expected: usize,
38 found: usize,
39 },
40
41 #[error(
42 "Qubit count mismatch: gate {gate} expects {expected} qubits, but {found} were provided"
43 )]
44 QubitCountMismatch {
45 gate: String,
46 expected: usize,
47 found: usize,
48 },
49
50 #[error("Invalid slice: start index {start} is greater than end index {end}")]
51 InvalidSlice { start: usize, end: usize },
52
53 #[error("Duplicate declaration: {0}")]
54 DuplicateDeclaration(String),
55
56 #[error("Invalid control: {0}")]
57 InvalidControl(String),
58
59 #[error("Semantic error: {0}")]
60 SemanticError(String),
61}
62
63#[derive(Debug, Clone)]
65enum Symbol {
66 QuantumRegister {
67 size: usize,
68 },
69 ClassicalRegister {
70 size: usize,
71 },
72 Gate {
73 params: Vec<String>,
74 qubits: Vec<String>,
75 },
76 Variable {
77 typ: ValueType,
78 },
79 Constant {
80 typ: ValueType,
81 },
82}
83
84#[derive(Debug, Clone, PartialEq)]
86enum ValueType {
87 Bool,
88 Int,
89 Float,
90 Angle,
91 Duration,
92 Qubit,
93 Bit,
94 String,
95}
96
97pub struct QasmValidator {
99 symbols: HashMap<String, Symbol>,
101 standard_gates: HashMap<String, (usize, usize)>,
103 scope_stack: Vec<HashMap<String, Symbol>>,
105}
106
107impl Default for QasmValidator {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113impl QasmValidator {
114 #[must_use]
116 pub fn new() -> Self {
117 let mut standard_gates = HashMap::new();
118
119 standard_gates.insert("id".to_string(), (0, 1));
121 standard_gates.insert("x".to_string(), (0, 1));
122 standard_gates.insert("y".to_string(), (0, 1));
123 standard_gates.insert("z".to_string(), (0, 1));
124 standard_gates.insert("h".to_string(), (0, 1));
125 standard_gates.insert("s".to_string(), (0, 1));
126 standard_gates.insert("sdg".to_string(), (0, 1));
127 standard_gates.insert("t".to_string(), (0, 1));
128 standard_gates.insert("tdg".to_string(), (0, 1));
129 standard_gates.insert("sx".to_string(), (0, 1));
130 standard_gates.insert("sxdg".to_string(), (0, 1));
131
132 standard_gates.insert("rx".to_string(), (1, 1));
134 standard_gates.insert("ry".to_string(), (1, 1));
135 standard_gates.insert("rz".to_string(), (1, 1));
136 standard_gates.insert("p".to_string(), (1, 1));
137 standard_gates.insert("u1".to_string(), (1, 1));
138 standard_gates.insert("u2".to_string(), (2, 1));
139 standard_gates.insert("u3".to_string(), (3, 1));
140 standard_gates.insert("u".to_string(), (3, 1));
141
142 standard_gates.insert("cx".to_string(), (0, 2));
144 standard_gates.insert("cy".to_string(), (0, 2));
145 standard_gates.insert("cz".to_string(), (0, 2));
146 standard_gates.insert("ch".to_string(), (0, 2));
147 standard_gates.insert("swap".to_string(), (0, 2));
148 standard_gates.insert("iswap".to_string(), (0, 2));
149 standard_gates.insert("ecr".to_string(), (0, 2));
150 standard_gates.insert("dcx".to_string(), (0, 2));
151
152 standard_gates.insert("crx".to_string(), (1, 2));
154 standard_gates.insert("cry".to_string(), (1, 2));
155 standard_gates.insert("crz".to_string(), (1, 2));
156 standard_gates.insert("cp".to_string(), (1, 2));
157 standard_gates.insert("cu1".to_string(), (1, 2));
158 standard_gates.insert("rxx".to_string(), (1, 2));
159 standard_gates.insert("ryy".to_string(), (1, 2));
160 standard_gates.insert("rzz".to_string(), (1, 2));
161 standard_gates.insert("rzx".to_string(), (1, 2));
162
163 standard_gates.insert("ccx".to_string(), (0, 3));
165 standard_gates.insert("cswap".to_string(), (0, 3));
166
167 Self {
168 symbols: HashMap::new(),
169 standard_gates,
170 scope_stack: vec![],
171 }
172 }
173
174 pub fn validate(&mut self, program: &QasmProgram) -> Result<(), ValidationError> {
176 self.symbols.clear();
178 self.scope_stack.clear();
179
180 self.symbols.insert(
182 "pi".to_string(),
183 Symbol::Constant {
184 typ: ValueType::Float,
185 },
186 );
187 self.symbols.insert(
188 "e".to_string(),
189 Symbol::Constant {
190 typ: ValueType::Float,
191 },
192 );
193 self.symbols.insert(
194 "tau".to_string(),
195 Symbol::Constant {
196 typ: ValueType::Float,
197 },
198 );
199
200 for decl in &program.declarations {
202 self.validate_declaration(decl)?;
203 }
204
205 for stmt in &program.statements {
207 self.validate_statement(stmt)?;
208 }
209
210 Ok(())
211 }
212
213 fn validate_declaration(&mut self, decl: &Declaration) -> Result<(), ValidationError> {
215 match decl {
216 Declaration::QuantumRegister(reg) => {
217 if self.symbols.contains_key(®.name) {
218 return Err(ValidationError::DuplicateDeclaration(reg.name.clone()));
219 }
220
221 if reg.size == 0 {
222 return Err(ValidationError::SemanticError(
223 "Register size must be greater than 0".to_string(),
224 ));
225 }
226
227 self.symbols
228 .insert(reg.name.clone(), Symbol::QuantumRegister { size: reg.size });
229 }
230 Declaration::ClassicalRegister(reg) => {
231 if self.symbols.contains_key(®.name) {
232 return Err(ValidationError::DuplicateDeclaration(reg.name.clone()));
233 }
234
235 if reg.size == 0 {
236 return Err(ValidationError::SemanticError(
237 "Register size must be greater than 0".to_string(),
238 ));
239 }
240
241 self.symbols.insert(
242 reg.name.clone(),
243 Symbol::ClassicalRegister { size: reg.size },
244 );
245 }
246 Declaration::GateDefinition(def) => {
247 if self.symbols.contains_key(&def.name) {
248 return Err(ValidationError::DuplicateDeclaration(def.name.clone()));
249 }
250
251 self.push_scope();
253
254 for param in &def.params {
256 self.add_to_current_scope(
257 param.clone(),
258 Symbol::Variable {
259 typ: ValueType::Angle,
260 },
261 );
262 }
263
264 for qubit in &def.qubits {
266 self.add_to_current_scope(
267 qubit.clone(),
268 Symbol::Variable {
269 typ: ValueType::Qubit,
270 },
271 );
272 }
273
274 for stmt in &def.body {
276 self.validate_statement(stmt)?;
277 }
278
279 self.pop_scope();
281
282 self.symbols.insert(
284 def.name.clone(),
285 Symbol::Gate {
286 params: def.params.clone(),
287 qubits: def.qubits.clone(),
288 },
289 );
290 }
291 Declaration::Constant(name, expr) => {
292 if self.symbols.contains_key(name) {
293 return Err(ValidationError::DuplicateDeclaration(name.clone()));
294 }
295
296 let typ = self.validate_expression(expr)?;
297
298 self.symbols.insert(name.clone(), Symbol::Constant { typ });
299 }
300 }
301
302 Ok(())
303 }
304
305 fn validate_statement(&mut self, stmt: &QasmStatement) -> Result<(), ValidationError> {
307 match stmt {
308 QasmStatement::Gate(gate) => self.validate_gate(gate),
309 QasmStatement::Measure(meas) => self.validate_measure(meas),
310 QasmStatement::Reset(qubits) => {
311 for qubit in qubits {
312 self.validate_qubit_ref(qubit)?;
313 }
314 Ok(())
315 }
316 QasmStatement::Barrier(qubits) => {
317 for qubit in qubits {
318 self.validate_qubit_ref(qubit)?;
319 }
320 Ok(())
321 }
322 QasmStatement::Assignment(var, expr) => {
323 let typ = self.validate_expression(expr)?;
324
325 if let Some(symbol) = self.lookup_symbol(var) {
327 match symbol {
328 Symbol::Variable { typ: var_typ } => {
329 if !self.types_compatible(var_typ, &typ) {
330 return Err(ValidationError::TypeMismatch {
331 expected: format!("{var_typ:?}"),
332 found: format!("{typ:?}"),
333 });
334 }
335 }
336 _ => {
337 return Err(ValidationError::SemanticError(format!(
338 "{var} is not a variable"
339 )))
340 }
341 }
342 } else {
343 self.add_to_current_scope(var.clone(), Symbol::Variable { typ });
345 }
346
347 Ok(())
348 }
349 QasmStatement::If(cond, stmt) => {
350 self.validate_condition(cond)?;
351 self.validate_statement(stmt)
352 }
353 QasmStatement::For(for_loop) => {
354 self.push_scope();
355
356 self.add_to_current_scope(
358 for_loop.variable.clone(),
359 Symbol::Variable {
360 typ: ValueType::Int,
361 },
362 );
363
364 let start_typ = self.validate_expression(&for_loop.start)?;
366 let end_typ = self.validate_expression(&for_loop.end)?;
367
368 if start_typ != ValueType::Int || end_typ != ValueType::Int {
369 return Err(ValidationError::TypeMismatch {
370 expected: "int".to_string(),
371 found: "non-int".to_string(),
372 });
373 }
374
375 if let Some(step) = &for_loop.step {
376 let step_typ = self.validate_expression(step)?;
377 if step_typ != ValueType::Int {
378 return Err(ValidationError::TypeMismatch {
379 expected: "int".to_string(),
380 found: format!("{step_typ:?}"),
381 });
382 }
383 }
384
385 for stmt in &for_loop.body {
387 self.validate_statement(stmt)?;
388 }
389
390 self.pop_scope();
391 Ok(())
392 }
393 QasmStatement::While(cond, body) => {
394 self.validate_condition(cond)?;
395
396 self.push_scope();
397 for stmt in body {
398 self.validate_statement(stmt)?;
399 }
400 self.pop_scope();
401
402 Ok(())
403 }
404 QasmStatement::Call(name, args) => {
405 for arg in args {
407 self.validate_expression(arg)?;
408 }
409 Ok(())
410 }
411 QasmStatement::Delay(duration, qubits) => {
412 let dur_typ = self.validate_expression(duration)?;
413 if dur_typ != ValueType::Duration && dur_typ != ValueType::Float {
414 return Err(ValidationError::TypeMismatch {
415 expected: "duration".to_string(),
416 found: format!("{dur_typ:?}"),
417 });
418 }
419
420 for qubit in qubits {
421 self.validate_qubit_ref(qubit)?;
422 }
423
424 Ok(())
425 }
426 }
427 }
428
429 fn validate_gate(&self, gate: &QasmGate) -> Result<(), ValidationError> {
431 let (expected_params, expected_qubits) =
433 if let Some(&(p, q)) = self.standard_gates.get(&gate.name) {
434 (p, q)
435 } else if let Some(symbol) = self.symbols.get(&gate.name) {
436 match symbol {
437 Symbol::Gate { params, qubits } => (params.len(), qubits.len()),
438 _ => return Err(ValidationError::UndefinedGate(gate.name.clone())),
439 }
440 } else {
441 return Err(ValidationError::UndefinedGate(gate.name.clone()));
442 };
443
444 if gate.params.len() != expected_params {
446 return Err(ValidationError::ParameterCountMismatch {
447 gate: gate.name.clone(),
448 expected: expected_params,
449 found: gate.params.len(),
450 });
451 }
452
453 for param in &gate.params {
455 let typ = self.validate_expression(param)?;
456 if typ != ValueType::Float && typ != ValueType::Angle && typ != ValueType::Int {
457 return Err(ValidationError::TypeMismatch {
458 expected: "numeric".to_string(),
459 found: format!("{typ:?}"),
460 });
461 }
462 }
463
464 let actual_qubits = gate.qubits.len();
466 let required_qubits = expected_qubits + gate.control.unwrap_or(0);
467
468 if actual_qubits != required_qubits {
469 return Err(ValidationError::QubitCountMismatch {
470 gate: gate.name.clone(),
471 expected: required_qubits,
472 found: actual_qubits,
473 });
474 }
475
476 for qubit in &gate.qubits {
478 self.validate_qubit_ref(qubit)?;
479 }
480
481 Ok(())
482 }
483
484 fn validate_measure(&self, meas: &Measurement) -> Result<(), ValidationError> {
486 if meas.qubits.len() != meas.targets.len() {
487 return Err(ValidationError::SemanticError(
488 "Measurement must have equal number of qubits and classical bits".to_string(),
489 ));
490 }
491
492 for qubit in &meas.qubits {
493 self.validate_qubit_ref(qubit)?;
494 }
495
496 for target in &meas.targets {
497 self.validate_classical_ref(target)?;
498 }
499
500 Ok(())
501 }
502
503 fn validate_qubit_ref(&self, qubit_ref: &QubitRef) -> Result<(), ValidationError> {
505 match qubit_ref {
506 QubitRef::Single { register, index } => {
507 match self.lookup_symbol(register) {
508 Some(Symbol::QuantumRegister { size }) => {
509 if *index >= *size {
510 return Err(ValidationError::IndexOutOfBounds {
511 register: register.clone(),
512 size: *size,
513 index: *index,
514 });
515 }
516 }
517 Some(Symbol::Variable {
518 typ: ValueType::Qubit,
519 }) => {
520 }
522 _ => return Err(ValidationError::UndefinedRegister(register.clone())),
523 }
524 }
525 QubitRef::Slice {
526 register,
527 start,
528 end,
529 } => match self.lookup_symbol(register) {
530 Some(Symbol::QuantumRegister { size }) => {
531 if *start >= *size || *end > *size {
532 return Err(ValidationError::IndexOutOfBounds {
533 register: register.clone(),
534 size: *size,
535 index: (*start).max(*end),
536 });
537 }
538 if *start >= *end {
539 return Err(ValidationError::InvalidSlice {
540 start: *start,
541 end: *end,
542 });
543 }
544 }
545 _ => return Err(ValidationError::UndefinedRegister(register.clone())),
546 },
547 QubitRef::Register(name) => match self.lookup_symbol(name) {
548 Some(Symbol::QuantumRegister { .. }) => {}
549 Some(Symbol::Variable {
550 typ: ValueType::Qubit,
551 }) => {}
552 _ => return Err(ValidationError::UndefinedRegister(name.clone())),
553 },
554 }
555
556 Ok(())
557 }
558
559 fn validate_classical_ref(&self, classical_ref: &ClassicalRef) -> Result<(), ValidationError> {
561 match classical_ref {
562 ClassicalRef::Single { register, index } => {
563 match self.lookup_symbol(register) {
564 Some(Symbol::ClassicalRegister { size }) => {
565 if *index >= *size {
566 return Err(ValidationError::IndexOutOfBounds {
567 register: register.clone(),
568 size: *size,
569 index: *index,
570 });
571 }
572 }
573 Some(Symbol::Variable {
574 typ: ValueType::Bit,
575 }) => {
576 }
578 _ => return Err(ValidationError::UndefinedRegister(register.clone())),
579 }
580 }
581 ClassicalRef::Slice {
582 register,
583 start,
584 end,
585 } => match self.lookup_symbol(register) {
586 Some(Symbol::ClassicalRegister { size }) => {
587 if *start >= *size || *end > *size {
588 return Err(ValidationError::IndexOutOfBounds {
589 register: register.clone(),
590 size: *size,
591 index: (*start).max(*end),
592 });
593 }
594 if *start >= *end {
595 return Err(ValidationError::InvalidSlice {
596 start: *start,
597 end: *end,
598 });
599 }
600 }
601 _ => return Err(ValidationError::UndefinedRegister(register.clone())),
602 },
603 ClassicalRef::Register(name) => match self.lookup_symbol(name) {
604 Some(Symbol::ClassicalRegister { .. }) => {}
605 Some(Symbol::Variable {
606 typ: ValueType::Bit,
607 }) => {}
608 _ => return Err(ValidationError::UndefinedRegister(name.clone())),
609 },
610 }
611
612 Ok(())
613 }
614
615 fn validate_expression(&self, expr: &Expression) -> Result<ValueType, ValidationError> {
617 match expr {
618 Expression::Literal(lit) => Ok(match lit {
619 Literal::Integer(_) => ValueType::Int,
620 Literal::Float(_) | Literal::Pi | Literal::Euler | Literal::Tau => ValueType::Float,
621 Literal::Bool(_) => ValueType::Bool,
622 Literal::String(_) => ValueType::String,
623 }),
624 Expression::Variable(name) => match self.lookup_symbol(name) {
625 Some(Symbol::Variable { typ }) => Ok(typ.clone()),
626 Some(Symbol::Constant { typ }) => Ok(typ.clone()),
627 _ => Err(ValidationError::UndefinedVariable(name.clone())),
628 },
629 Expression::Binary(op, left, right) => {
630 let left_typ = self.validate_expression(left)?;
631 let right_typ = self.validate_expression(right)?;
632
633 match op {
635 BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div => {
636 if (left_typ == ValueType::Int || left_typ == ValueType::Float)
637 && (right_typ == ValueType::Int || right_typ == ValueType::Float)
638 {
639 Ok(ValueType::Float)
640 } else {
641 Err(ValidationError::TypeMismatch {
642 expected: "numeric".to_string(),
643 found: format!("{left_typ:?} and {right_typ:?}"),
644 })
645 }
646 }
647 BinaryOp::Mod
648 | BinaryOp::BitAnd
649 | BinaryOp::BitOr
650 | BinaryOp::BitXor
651 | BinaryOp::Shl
652 | BinaryOp::Shr => {
653 if left_typ == ValueType::Int && right_typ == ValueType::Int {
654 Ok(ValueType::Int)
655 } else {
656 Err(ValidationError::TypeMismatch {
657 expected: "int".to_string(),
658 found: format!("{left_typ:?} and {right_typ:?}"),
659 })
660 }
661 }
662 BinaryOp::And | BinaryOp::Or | BinaryOp::Xor => {
663 if left_typ == ValueType::Bool && right_typ == ValueType::Bool {
664 Ok(ValueType::Bool)
665 } else {
666 Err(ValidationError::TypeMismatch {
667 expected: "bool".to_string(),
668 found: format!("{left_typ:?} and {right_typ:?}"),
669 })
670 }
671 }
672 BinaryOp::Eq
673 | BinaryOp::Ne
674 | BinaryOp::Lt
675 | BinaryOp::Le
676 | BinaryOp::Gt
677 | BinaryOp::Ge => Ok(ValueType::Bool),
678 BinaryOp::Pow => {
679 if (left_typ == ValueType::Int || left_typ == ValueType::Float)
680 && (right_typ == ValueType::Int || right_typ == ValueType::Float)
681 {
682 Ok(ValueType::Float)
683 } else {
684 Err(ValidationError::TypeMismatch {
685 expected: "numeric".to_string(),
686 found: format!("{left_typ:?} and {right_typ:?}"),
687 })
688 }
689 }
690 }
691 }
692 Expression::Unary(op, expr) => {
693 let typ = self.validate_expression(expr)?;
694
695 match op {
696 UnaryOp::Neg => {
697 if typ == ValueType::Int || typ == ValueType::Float {
698 Ok(typ)
699 } else {
700 Err(ValidationError::TypeMismatch {
701 expected: "numeric".to_string(),
702 found: format!("{typ:?}"),
703 })
704 }
705 }
706 UnaryOp::Not => {
707 if typ == ValueType::Bool {
708 Ok(ValueType::Bool)
709 } else {
710 Err(ValidationError::TypeMismatch {
711 expected: "bool".to_string(),
712 found: format!("{typ:?}"),
713 })
714 }
715 }
716 UnaryOp::BitNot => {
717 if typ == ValueType::Int {
718 Ok(ValueType::Int)
719 } else {
720 Err(ValidationError::TypeMismatch {
721 expected: "int".to_string(),
722 found: format!("{typ:?}"),
723 })
724 }
725 }
726 UnaryOp::Sin
727 | UnaryOp::Cos
728 | UnaryOp::Tan
729 | UnaryOp::Asin
730 | UnaryOp::Acos
731 | UnaryOp::Atan
732 | UnaryOp::Exp
733 | UnaryOp::Ln
734 | UnaryOp::Sqrt => {
735 if typ == ValueType::Int || typ == ValueType::Float {
736 Ok(ValueType::Float)
737 } else {
738 Err(ValidationError::TypeMismatch {
739 expected: "numeric".to_string(),
740 found: format!("{typ:?}"),
741 })
742 }
743 }
744 }
745 }
746 Expression::Function(name, args) => {
747 for arg in args {
749 self.validate_expression(arg)?;
750 }
751
752 Ok(ValueType::Float)
754 }
755 Expression::Index(name, index) => {
756 let idx_typ = self.validate_expression(index)?;
758 if idx_typ != ValueType::Int {
759 return Err(ValidationError::TypeMismatch {
760 expected: "int".to_string(),
761 found: format!("{idx_typ:?}"),
762 });
763 }
764
765 match self.lookup_symbol(name) {
767 Some(Symbol::Variable { typ }) => Ok(typ.clone()),
768 _ => Err(ValidationError::UndefinedVariable(name.clone())),
769 }
770 }
771 }
772 }
773
774 fn validate_condition(&self, cond: &Condition) -> Result<(), ValidationError> {
776 let left_typ = self.validate_expression(&cond.left)?;
777 let right_typ = self.validate_expression(&cond.right)?;
778
779 if !self.types_compatible(&left_typ, &right_typ) {
781 return Err(ValidationError::TypeMismatch {
782 expected: format!("{left_typ:?}"),
783 found: format!("{right_typ:?}"),
784 });
785 }
786
787 Ok(())
788 }
789
790 fn types_compatible(&self, typ1: &ValueType, typ2: &ValueType) -> bool {
792 match (typ1, typ2) {
793 (ValueType::Int, ValueType::Float) | (ValueType::Float, ValueType::Int) => true,
794 (ValueType::Angle, ValueType::Float) | (ValueType::Float, ValueType::Angle) => true,
795 (ValueType::Duration, ValueType::Float) | (ValueType::Float, ValueType::Duration) => {
796 true
797 }
798 (t1, t2) => t1 == t2,
799 }
800 }
801
802 fn push_scope(&mut self) {
804 self.scope_stack.push(HashMap::new());
805 }
806
807 fn pop_scope(&mut self) {
809 self.scope_stack.pop();
810 }
811
812 fn add_to_current_scope(&mut self, name: String, symbol: Symbol) {
814 if let Some(scope) = self.scope_stack.last_mut() {
815 scope.insert(name, symbol);
816 } else {
817 self.symbols.insert(name, symbol);
818 }
819 }
820
821 fn lookup_symbol(&self, name: &str) -> Option<&Symbol> {
823 for scope in self.scope_stack.iter().rev() {
825 if let Some(symbol) = scope.get(name) {
826 return Some(symbol);
827 }
828 }
829
830 self.symbols.get(name)
832 }
833}
834
835pub fn validate_qasm3(program: &QasmProgram) -> Result<(), ValidationError> {
837 let mut validator = QasmValidator::new();
838 validator.validate(program)
839}
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844 use crate::qasm::parser::parse_qasm3;
845
846 #[test]
847 fn test_validate_simple_circuit() {
848 let input = r"
849OPENQASM 3.0;
850
851qubit[2] q;
852bit[2] c;
853
854h q[0];
855cx q[0], q[1];
856measure q -> c;
857";
858
859 let program = parse_qasm3(input).expect("parse_qasm3 should succeed for valid circuit");
860 let result = validate_qasm3(&program);
861 assert!(result.is_ok());
862 }
863
864 #[test]
865 fn test_validate_undefined_register() {
866 let input = r"
867OPENQASM 3.0;
868
869qubit[2] q;
870
871h q[0];
872cx q[0], r[1]; // r is undefined
873";
874
875 let program =
876 parse_qasm3(input).expect("parse_qasm3 should succeed for undefined register test");
877 let result = validate_qasm3(&program);
878 assert!(matches!(result, Err(ValidationError::UndefinedRegister(_))));
879 }
880
881 #[test]
882 fn test_validate_index_out_of_bounds() {
883 let input = r"
884OPENQASM 3.0;
885
886qubit[2] q;
887
888h q[5]; // Index 5 is out of bounds
889";
890
891 let program =
892 parse_qasm3(input).expect("parse_qasm3 should succeed for out of bounds test");
893 let result = validate_qasm3(&program);
894 assert!(matches!(
895 result,
896 Err(ValidationError::IndexOutOfBounds { .. })
897 ));
898 }
899
900 #[test]
901 fn test_validate_gate_parameters() {
902 let input = r"
903OPENQASM 3.0;
904
905qubit q;
906
907rx(pi/2) q; // Correct
908rx q; // Missing parameter
909";
910
911 let program =
912 parse_qasm3(input).expect("parse_qasm3 should succeed for gate parameter test");
913 let result = validate_qasm3(&program);
914 assert!(matches!(
915 result,
916 Err(ValidationError::ParameterCountMismatch { .. })
917 ));
918 }
919}