1use crate::circuit::gate::circuit_gate::{CircuitGate, FrozenCircuit};
14use crate::circuit::{
15 Circuit, ClassicalExpr, ClassicalType, ClassicalVar, Instruction, Parameter, ParameterValue,
16 Qubit, StandardGate, UnitaryGate,
17};
18use crate::ir::io_errors_equivalent;
19use oq3_semantics::asg::{
20 self, ArithOp, BinaryOp, CmpOp, Expr, ForIterable, GateModifier, GateOperand, IndexOperator,
21 LValue, Literal, Stmt, TExpr, UnaryOp,
22};
23use oq3_semantics::context::Context;
24use oq3_semantics::semantic_error::{SemanticErrorKind, SemanticErrorList};
25use oq3_semantics::symbols::{SymbolId, SymbolIdResult, SymbolTable, SymbolType};
26use oq3_semantics::syntax_to_semantics;
27use oq3_semantics::types::{ArrayDims, Type};
28use regex::Regex;
29use std::cell::RefCell;
30use std::collections::{HashMap, HashSet};
31use std::fs;
32use std::io;
33use std::panic::{AssertUnwindSafe, catch_unwind};
34use std::path::{Path, PathBuf};
35use std::sync::OnceLock;
36
37const DEFAULT_MAX_RECURSION_DEPTH: usize = 100;
38
39pub fn load<P: AsRef<Path>>(path: P) -> Result<Circuit, Qasm3ParseError> {
60 let path = path.as_ref();
61 let source = fs::read_to_string(path).map_err(Qasm3ParseError::IoError)?;
62 let source = prepare_source(&source)?;
63 let source = inject_extension_gate_definitions(&source);
64 let search_paths = path.parent().map(|parent| vec![parent.to_path_buf()]);
65 let result = catch_unwind(AssertUnwindSafe(|| {
66 syntax_to_semantics::parse_source_string(&source, path.to_str(), search_paths.as_deref())
67 }))
68 .map_err(frontend_panic_error)?;
69 let has_syntax_errors = result.any_syntax_errors();
70 convert_parse_result(result.take_context(), has_syntax_errors, &source)
71}
72
73pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Circuit, Qasm3ParseError> {
78 load(path)
79}
80
81pub fn loads(source: &str) -> Result<Circuit, Qasm3ParseError> {
102 let source = prepare_source(source)?;
103 let source = inject_extension_gate_definitions(&source);
104 let result = catch_unwind(AssertUnwindSafe(|| {
105 syntax_to_semantics::parse_source_string(&source, Some("qasm3_source"), None::<&[PathBuf]>)
106 }))
107 .map_err(frontend_panic_error)?;
108 let has_syntax_errors = result.any_syntax_errors();
109 convert_parse_result(result.take_context(), has_syntax_errors, &source)
110}
111
112pub fn from_str(source: &str) -> Result<Circuit, Qasm3ParseError> {
116 loads(source)
117}
118
119fn convert_parse_result(
120 context: Context,
121 has_syntax_errors: bool,
122 source: &str,
123) -> Result<Circuit, Qasm3ParseError> {
124 if has_syntax_errors {
125 return Err(Qasm3ParseError::ParseError(
126 "OpenQASM 3 parser reported syntax errors".to_string(),
127 ));
128 }
129 let (program, semantic_errors, symbols) = context.as_tuple();
130 if semantic_errors.any_semantic_errors()
131 && !only_scalar_measurement_frontend_errors(&semantic_errors, source)
132 {
133 return Err(Qasm3ParseError::SemanticError(
134 "OpenQASM 3 parser reported semantic errors".to_string(),
135 ));
136 }
137 let mut lowering = LoweringContext::new(&symbols);
138 lowering.lower_program(&program)
139}
140
141fn frontend_panic_error(payload: Box<dyn std::any::Any + Send>) -> Qasm3ParseError {
142 let message = payload
143 .downcast_ref::<&str>()
144 .copied()
145 .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
146 .unwrap_or("unknown OpenQASM front-end failure");
147 Qasm3ParseError::ParseError(format!("OpenQASM 3 front-end failed: {message}"))
148}
149
150fn prepare_source(source: &str) -> Result<String, Qasm3ParseError> {
151 let layout = inspect_source(source)?;
152 reject_empty_pragmas(&layout.code_mask)?;
153
154 let source = normalize_single_quoted_includes(source, &layout)?;
155 let source = normalize_openqasm3_header(&source, &layout.code_mask);
156 let source = normalize_top_level_anonymous_scopes(&source)?;
157 normalize_bit_boolean_initializers(&source)
158}
159
160#[derive(Debug)]
161struct SourceLayout {
162 code_mask: String,
163 strings: Vec<StringSpan>,
164}
165
166#[derive(Clone, Copy, Debug)]
167struct StringSpan {
168 start: usize,
169 end: usize,
170 quote: u8,
171}
172
173#[derive(Clone, Copy)]
174enum ScanState {
175 Code,
176 LineComment,
177 BlockComment,
178 String { start: usize, quote: u8 },
179}
180
181fn inspect_source(source: &str) -> Result<SourceLayout, Qasm3ParseError> {
182 let bytes = source.as_bytes();
183 let mut mask = bytes.to_vec();
184 let mut strings = Vec::new();
185 let mut state = ScanState::Code;
186 let mut index = 0usize;
187
188 while index < bytes.len() {
189 match state {
190 ScanState::Code if bytes[index..].starts_with(b"//") => {
191 mask[index] = b' ';
192 mask[index + 1] = b' ';
193 state = ScanState::LineComment;
194 index += 2;
195 }
196 ScanState::Code if bytes[index..].starts_with(b"/*") => {
197 mask[index] = b' ';
198 mask[index + 1] = b' ';
199 state = ScanState::BlockComment;
200 index += 2;
201 }
202 ScanState::Code if matches!(bytes[index], b'\'' | b'"') => {
203 let quote = bytes[index];
204 mask[index] = b' ';
205 state = ScanState::String {
206 start: index,
207 quote,
208 };
209 index += 1;
210 }
211 ScanState::Code => index += 1,
212 ScanState::LineComment if matches!(bytes[index], b'\n' | b'\r') => {
213 state = ScanState::Code;
214 index += 1;
215 }
216 ScanState::LineComment => {
217 mask[index] = b' ';
218 index += 1;
219 }
220 ScanState::BlockComment if bytes[index..].starts_with(b"/*") => {
221 return Err(Qasm3ParseError::ParseError(
222 "nested block comments are not valid OpenQASM 3".to_string(),
223 ));
224 }
225 ScanState::BlockComment if bytes[index..].starts_with(b"*/") => {
226 mask[index] = b' ';
227 mask[index + 1] = b' ';
228 state = ScanState::Code;
229 index += 2;
230 }
231 ScanState::BlockComment => {
232 if !matches!(bytes[index], b'\n' | b'\r') {
233 mask[index] = b' ';
234 }
235 index += 1;
236 }
237 ScanState::String { start, quote } if bytes[index] == quote => {
238 mask[index] = b' ';
239 strings.push(StringSpan {
240 start,
241 end: index,
242 quote,
243 });
244 state = ScanState::Code;
245 index += 1;
246 }
247 ScanState::String { .. } if matches!(bytes[index], b'\n' | b'\r') => {
248 return Err(Qasm3ParseError::ParseError(
249 "unterminated string literal".to_string(),
250 ));
251 }
252 ScanState::String { .. } => {
253 mask[index] = b' ';
254 index += 1;
255 }
256 }
257 }
258
259 match state {
260 ScanState::BlockComment => {
261 return Err(Qasm3ParseError::ParseError(
262 "unterminated block comment".to_string(),
263 ));
264 }
265 ScanState::String { .. } => {
266 return Err(Qasm3ParseError::ParseError(
267 "unterminated string literal".to_string(),
268 ));
269 }
270 ScanState::Code | ScanState::LineComment => {}
271 }
272
273 let code_mask = String::from_utf8(mask).expect("source mask preserves UTF-8");
274 Ok(SourceLayout { code_mask, strings })
275}
276
277fn reject_empty_pragmas(code_mask: &str) -> Result<(), Qasm3ParseError> {
278 if code_mask
279 .lines()
280 .any(|line| matches!(line.trim(), "pragma" | "#pragma"))
281 {
282 return Err(Qasm3ParseError::ParseError(
283 "pragma requires content on the same line".to_string(),
284 ));
285 }
286 Ok(())
287}
288
289fn normalize_single_quoted_includes(
290 source: &str,
291 layout: &SourceLayout,
292) -> Result<String, Qasm3ParseError> {
293 let mut bytes = source.as_bytes().to_vec();
294 for span in layout.strings.iter().filter(|span| span.quote == b'\'') {
295 let line_start = layout.code_mask[..span.start]
296 .rfind('\n')
297 .map_or(0, |index| index + 1);
298 let prefix = layout.code_mask[line_start..span.start].trim_end();
299 if prefix.split_whitespace().last() != Some("include") {
300 continue;
301 }
302 let line_end = layout.code_mask[span.end + 1..]
303 .find('\n')
304 .map_or(layout.code_mask.len(), |offset| span.end + 1 + offset);
305 if !layout.code_mask[span.end + 1..line_end]
306 .trim_start()
307 .starts_with(';')
308 {
309 continue;
310 }
311 if source.as_bytes()[span.start + 1..span.end].contains(&b'"') {
312 return Err(Qasm3ParseError::UnsupportedFeature(
313 "single-quoted include path containing a double quote".to_string(),
314 ));
315 }
316 bytes[span.start] = b'"';
317 bytes[span.end] = b'"';
318 }
319 Ok(String::from_utf8(bytes).expect("quote normalization preserves UTF-8"))
320}
321
322fn normalize_openqasm3_header(source: &str, code_mask: &str) -> String {
323 let Some(captures) = short_header_regex().captures(code_mask) else {
324 return source.to_string();
325 };
326 let Some(major) = captures.name("major") else {
327 return source.to_string();
328 };
329 let mut normalized = source.to_string();
330 normalized.insert_str(major.end(), ".0");
331 normalized
332}
333
334fn normalize_top_level_anonymous_scopes(source: &str) -> Result<String, Qasm3ParseError> {
335 let layout = inspect_source(source)?;
336 let bytes = layout.code_mask.as_bytes();
337 let mut depth = 0usize;
338 let mut statement_start = 0usize;
339 let mut insertions = Vec::new();
340
341 for (index, byte) in bytes.iter().copied().enumerate() {
342 match byte {
343 b';' if depth == 0 => statement_start = index + 1,
344 b'{' if depth == 0 => {
345 if layout.code_mask[statement_start..index].trim().is_empty() {
346 insertions.push(index);
347 }
348 depth = 1;
349 }
350 b'{' => depth += 1,
351 b'}' if depth > 0 => {
352 depth -= 1;
353 if depth == 0 {
354 statement_start = index + 1;
355 }
356 }
357 _ => {}
358 }
359 }
360
361 let mut normalized = source.to_string();
362 for index in insertions.into_iter().rev() {
363 normalized.insert_str(index, "if (true) ");
366 }
367 Ok(normalized)
368}
369
370fn normalize_bit_boolean_initializers(source: &str) -> Result<String, Qasm3ParseError> {
371 let layout = inspect_source(source)?;
372 let mut insertions = bit_boolean_initializer_regex()
373 .captures_iter(&layout.code_mask)
374 .filter_map(|captures| captures.name("value"))
375 .flat_map(|value| [(value.start(), "bit("), (value.end(), ")")])
376 .collect::<Vec<_>>();
377 insertions.sort_unstable_by_key(|(index, _)| *index);
378
379 let mut normalized = source.to_string();
380 for (index, value) in insertions.into_iter().rev() {
381 normalized.insert_str(index, value);
382 }
383 Ok(normalized)
384}
385
386fn inject_extension_gate_definitions(source: &str) -> String {
387 let definitions = [
388 (
389 "rxx",
390 "gate rxx(theta) a,b { h a; h b; cx a,b; rz(theta) b; cx a,b; h a; h b; }",
391 ),
392 (
393 "ryy",
394 "gate ryy(theta) a,b { rx(pi/2) a; rx(pi/2) b; cx a,b; rz(theta) b; cx a,b; rx(-pi/2) a; rx(-pi/2) b; }",
395 ),
396 (
397 "rzz",
398 "gate rzz(theta) a,b { cx a,b; rz(theta) b; cx a,b; }",
399 ),
400 (
401 "rzx",
402 "gate rzx(theta) a,b { h b; cx a,b; rz(theta) b; cx a,b; h b; }",
403 ),
404 (
405 "fsim",
406 "gate fsim(theta,phi) a,b { rxx(theta) a,b; ryy(theta) a,b; gphase(-phi/4); rz(-phi/2) a; rz(-phi/2) b; rzz(phi/2) a,b; }",
407 ),
408 ];
409 let injected = definitions
410 .iter()
411 .filter_map(|(name, definition)| {
412 if has_gate_definition(source, name) || !has_gate_call(source, name) {
413 None
414 } else {
415 Some(*definition)
416 }
417 })
418 .collect::<Vec<_>>();
419 if injected.is_empty() {
420 return source.to_string();
421 }
422
423 let insertion = format!("\n{}\n", injected.join("\n\n"));
424 let mut lines = source
425 .lines()
426 .map(ToString::to_string)
427 .collect::<Vec<String>>();
428 let insert_after = lines
429 .iter()
430 .rposition(|line| line.trim() == "include \"stdgates.inc\";")
431 .or_else(|| {
432 lines
433 .iter()
434 .position(|line| line.trim_start().starts_with("OPENQASM "))
435 });
436 if let Some(index) = insert_after {
437 lines.insert(index + 1, insertion);
438 } else {
439 lines.insert(0, insertion);
440 }
441
442 let mut rewritten = lines.join("\n");
443 if source.ends_with('\n') {
444 rewritten.push('\n');
445 }
446 rewritten
447}
448
449fn has_gate_definition(source: &str, name: &str) -> bool {
450 gate_definition_regex(name).is_match(source)
451}
452
453fn has_gate_call(source: &str, name: &str) -> bool {
454 gate_call_regex(name).is_match(source)
455}
456
457fn only_scalar_measurement_frontend_errors(errors: &SemanticErrorList, source: &str) -> bool {
458 errors.include_errors().is_empty()
459 && !errors.is_empty()
460 && errors
461 .iter()
462 .all(|error| scalar_measurement_frontend_error(error, source))
463}
464
465fn scalar_measurement_frontend_error(
466 error: &oq3_semantics::semantic_error::SemanticError,
467 source: &str,
468) -> bool {
469 if !matches!(error.kind(), SemanticErrorKind::IncompatibleTypesError) {
470 return false;
471 }
472 let range = error.range();
473 let start = u32::from(range.start()) as usize;
474 let end = u32::from(range.end()) as usize;
475 let Some(snippet) = source.get(start..end) else {
476 return false;
477 };
478 let Some(captures) = scalar_measurement_error_regex().captures(snippet) else {
479 return false;
480 };
481 let Some(target) = captures.name("target") else {
482 return false;
483 };
484 scalar_bit_declaration_count(source, target.as_str()) == 1
485}
486
487fn scalar_bit_declaration_count(source: &str, identifier: &str) -> usize {
488 let Ok(layout) = inspect_source(source) else {
489 return 0;
490 };
491 let regex = Regex::new(&format!(
492 r"(?m)^[\t ]*bit[\t ]+{}[\t ]*;",
493 regex::escape(identifier)
494 ))
495 .expect("escaped identifier produces a valid regex");
496 regex.find_iter(&layout.code_mask).count()
497}
498
499fn short_header_regex() -> &'static Regex {
500 static REGEX: OnceLock<Regex> = OnceLock::new();
501 REGEX.get_or_init(|| {
502 Regex::new(r"\A\s*OPENQASM\s+(?P<major>3)[\t \r\n]*;").expect("valid short header regex")
503 })
504}
505
506fn bit_boolean_initializer_regex() -> &'static Regex {
507 static REGEX: OnceLock<Regex> = OnceLock::new();
508 REGEX.get_or_init(|| {
509 Regex::new(
510 r"(?m)^[\t ]*bit[\t ]+[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}_]*[\t ]*=[\t ]*(?P<value>true|false)[\t ]*;",
511 )
512 .expect("valid bit boolean initializer regex")
513 })
514}
515
516fn scalar_measurement_error_regex() -> &'static Regex {
517 static REGEX: OnceLock<Regex> = OnceLock::new();
518 REGEX.get_or_init(|| {
519 Regex::new(
520 r"(?s)^\s*(?P<target>[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}_]*)\s*=\s*measure\s+[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}_]*\s*\[\s*\d+\s*\]\s*;?\s*$",
521 )
522 .expect("valid scalar measurement compatibility regex")
523 })
524}
525
526fn gate_definition_regex(name: &str) -> Regex {
527 Regex::new(&format!(r"(?m)^\s*gate\s+{}\b", regex::escape(name)))
528 .expect("valid gate definition regex")
529}
530
531fn gate_call_regex(name: &str) -> Regex {
532 Regex::new(&format!(
533 r"(?m)^\s*{}\s*(?:\(|[A-Za-z_])",
534 regex::escape(name)
535 ))
536 .expect("valid gate call regex")
537}
538
539#[derive(Debug)]
545pub enum Qasm3ParseError {
546 IoError(io::Error),
548 ParseError(String),
550 SemanticError(String),
553 ConversionError(String),
555 UnsupportedFeature(String),
557 UndefinedSymbol(String),
559 UndefinedGate(String),
561 TypeError(String),
563 InvalidArgument(String),
565 MismatchedQubitCount { expected: usize, actual: usize },
567 MismatchedParameterCount { expected: usize, actual: usize },
569 RecursionLimitExceeded(String),
571 CircularGateDependency { gate: String, dependency: String },
573}
574
575impl PartialEq for Qasm3ParseError {
576 fn eq(&self, other: &Self) -> bool {
577 match (self, other) {
578 (Self::IoError(lhs), Self::IoError(rhs)) => io_errors_equivalent(lhs, rhs),
579 (Self::ParseError(lhs), Self::ParseError(rhs)) => lhs == rhs,
580 (Self::SemanticError(lhs), Self::SemanticError(rhs)) => lhs == rhs,
581 (Self::ConversionError(lhs), Self::ConversionError(rhs)) => lhs == rhs,
582 (Self::UnsupportedFeature(lhs), Self::UnsupportedFeature(rhs)) => lhs == rhs,
583 (Self::UndefinedSymbol(lhs), Self::UndefinedSymbol(rhs)) => lhs == rhs,
584 (Self::UndefinedGate(lhs), Self::UndefinedGate(rhs)) => lhs == rhs,
585 (Self::TypeError(lhs), Self::TypeError(rhs)) => lhs == rhs,
586 (Self::InvalidArgument(lhs), Self::InvalidArgument(rhs)) => lhs == rhs,
587 (
588 Self::MismatchedQubitCount {
589 expected: lhs_expected,
590 actual: lhs_actual,
591 },
592 Self::MismatchedQubitCount {
593 expected: rhs_expected,
594 actual: rhs_actual,
595 },
596 ) => lhs_expected == rhs_expected && lhs_actual == rhs_actual,
597 (
598 Self::MismatchedParameterCount {
599 expected: lhs_expected,
600 actual: lhs_actual,
601 },
602 Self::MismatchedParameterCount {
603 expected: rhs_expected,
604 actual: rhs_actual,
605 },
606 ) => lhs_expected == rhs_expected && lhs_actual == rhs_actual,
607 (Self::RecursionLimitExceeded(lhs), Self::RecursionLimitExceeded(rhs)) => lhs == rhs,
608 (
609 Self::CircularGateDependency {
610 gate: lhs_gate,
611 dependency: lhs_dependency,
612 },
613 Self::CircularGateDependency {
614 gate: rhs_gate,
615 dependency: rhs_dependency,
616 },
617 ) => lhs_gate == rhs_gate && lhs_dependency == rhs_dependency,
618 _ => false,
619 }
620 }
621}
622
623impl std::fmt::Display for Qasm3ParseError {
624 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625 match self {
626 Self::IoError(error) => write!(f, "IO error: {error}"),
627 Self::ParseError(s) => write!(f, "Parse error: {s}"),
628 Self::SemanticError(s) => write!(f, "Semantic error: {s}"),
629 Self::ConversionError(s) => write!(f, "Conversion error: {s}"),
630 Self::UnsupportedFeature(s) => write!(f, "Unsupported OpenQASM 3 feature: {s}"),
631 Self::UndefinedSymbol(s) => write!(f, "Undefined symbol: {s}"),
632 Self::UndefinedGate(s) => write!(f, "Undefined gate: {s}"),
633 Self::TypeError(s) => write!(f, "Type error: {s}"),
634 Self::InvalidArgument(s) => write!(f, "Invalid argument: {s}"),
635 Self::MismatchedQubitCount { expected, actual } => {
636 write!(
637 f,
638 "Mismatched qubit count: expected {expected}, got {actual}"
639 )
640 }
641 Self::MismatchedParameterCount { expected, actual } => {
642 write!(
643 f,
644 "Mismatched parameter count: expected {expected}, got {actual}"
645 )
646 }
647 Self::RecursionLimitExceeded(s) => write!(f, "Recursion limit exceeded: {s}"),
648 Self::CircularGateDependency { gate, dependency } => write!(
649 f,
650 "Circular gate dependency detected: '{gate}' depends on '{dependency}'"
651 ),
652 }
653 }
654}
655
656impl std::error::Error for Qasm3ParseError {
657 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
658 match self {
659 Self::IoError(error) => Some(error),
660 _ => None,
661 }
662 }
663}
664
665impl From<crate::circuit::CircuitError> for Qasm3ParseError {
666 fn from(value: crate::circuit::CircuitError) -> Self {
667 Self::ConversionError(value.to_string())
668 }
669}
670
671#[derive(Clone, Debug)]
672enum QuantumBinding {
673 Qubit(Qubit),
674 Register(Vec<Qubit>),
675}
676
677impl QuantumBinding {
678 fn qubits(&self) -> Vec<Qubit> {
679 match self {
680 Self::Qubit(q) => vec![*q],
681 Self::Register(qs) => qs.clone(),
682 }
683 }
684}
685
686#[derive(Clone, Debug)]
687enum GateDef {
688 Circuit(CircuitGate),
689 Opaque {
690 name: String,
691 qubits: usize,
692 params: usize,
693 },
694 Source(asg::GateDefinition),
695}
696
697struct LoweringContext<'a> {
698 symbols: &'a SymbolTable,
699 quantum: HashMap<SymbolId, QuantumBinding>,
700 classical: HashMap<SymbolId, ClassicalVar>,
701 gates: HashMap<SymbolId, GateDef>,
702 loop_constants: HashMap<SymbolId, u128>,
703 compiling_gates: HashSet<SymbolId>,
704 recursion_depth: usize,
705 max_recursion_depth: usize,
706}
707
708impl<'a> LoweringContext<'a> {
709 fn new(symbols: &'a SymbolTable) -> Self {
710 Self {
711 symbols,
712 quantum: HashMap::new(),
713 classical: HashMap::new(),
714 gates: HashMap::new(),
715 loop_constants: HashMap::new(),
716 compiling_gates: HashSet::new(),
717 recursion_depth: 0,
718 max_recursion_depth: DEFAULT_MAX_RECURSION_DEPTH,
719 }
720 }
721
722 fn lower_program(&mut self, program: &asg::Program) -> Result<Circuit, Qasm3ParseError> {
723 if let Some(version) = program.version()
724 && version.major() != 3
725 {
726 return Err(Qasm3ParseError::UnsupportedFeature(format!(
727 "OPENQASM major version {}",
728 version.major()
729 )));
730 }
731
732 let total_qubits = self.discover_quantum(program.stmts())?;
733 self.discover_gates(program.stmts())?;
734
735 let mut circuit = Circuit::new(total_qubits);
736
737 for stmt in program.stmts() {
738 self.lower_stmt(stmt, &mut circuit)?;
739 }
740
741 Ok(circuit)
742 }
743
744 fn discover_quantum(&mut self, stmts: &[Stmt]) -> Result<usize, Qasm3ParseError> {
745 let mut next = 0usize;
746 for stmt in stmts {
747 if let Stmt::DeclareQuantum(decl) = stmt {
748 let id = self.symbol_id(decl.name())?;
749 let ty = self.symbol_type(&id);
750 let width = match ty {
751 Type::Qubit => 1,
752 Type::QubitArray(ArrayDims::D1(width)) => *width,
753 _ => {
754 return Err(Qasm3ParseError::TypeError(format!(
755 "quantum declaration '{}' has type {:?}",
756 self.symbol_name(&id),
757 ty
758 )));
759 }
760 };
761 let qubits = (next..next + width)
762 .map(|index| Qubit::new(index as u32))
763 .collect::<Vec<_>>();
764 next += width;
765 let binding = if width == 1 {
766 QuantumBinding::Qubit(qubits[0])
767 } else {
768 QuantumBinding::Register(qubits)
769 };
770 self.quantum.insert(id, binding);
771 }
772 }
773 Ok(next)
774 }
775
776 fn discover_gates(&mut self, stmts: &[Stmt]) -> Result<(), Qasm3ParseError> {
777 for stmt in stmts {
778 match stmt {
779 Stmt::GateDefinition(def) => {
780 let id = self.symbol_id(def.name())?;
781 self.gates.insert(id, GateDef::Source(def.clone()));
782 }
783 Stmt::Include(_) => {}
784 _ => {}
785 }
786 }
787
788 for (name, id, params, qubits) in self.symbols.gates() {
789 self.gates.entry(id).or_insert_with(|| GateDef::Opaque {
790 name: name.to_string(),
791 qubits,
792 params,
793 });
794 }
795 Ok(())
796 }
797
798 fn lower_classical_declaration(
799 &mut self,
800 decl: &asg::DeclareClassical,
801 circuit: &mut Circuit,
802 ) -> Result<(), Qasm3ParseError> {
803 let id = self.symbol_id(decl.name())?;
804 let ty = self.classical_type(self.symbol_type(&id))?;
805 let var = circuit.var(ty);
806 self.classical.insert(id, var);
807 if let Some(initializer) = decl.initializer() {
808 let value = self.lower_classical_expr(initializer)?;
809 circuit.store(var, value)?;
810 }
811 Ok(())
812 }
813
814 fn lower_stmt(&mut self, stmt: &Stmt, circuit: &mut Circuit) -> Result<(), Qasm3ParseError> {
815 match stmt {
816 Stmt::DeclareQuantum(_) | Stmt::Include(_) => Ok(()),
817 Stmt::DeclareClassical(decl) => self.lower_classical_declaration(decl, circuit),
818 Stmt::GateDefinition(_) => Ok(()),
819 Stmt::GateCall(call) => self.lower_gate_call(call, circuit),
820 Stmt::GPhaseCall(call) => {
821 let phase = self.lower_angle(call.arg())?;
822 let new_phase = circuit.global_phase() + phase;
823 circuit.set_global_phase(new_phase);
824 Ok(())
825 }
826 Stmt::ModifiedGPhaseCall(_) => Err(Qasm3ParseError::UnsupportedFeature(
827 "modified gphase".to_string(),
828 )),
829 Stmt::Barrier(barrier) => {
830 let qubits = match barrier.qubits() {
831 Some(exprs) => self.expand_qubit_exprs(exprs)?,
832 None => circuit.qubits(),
833 };
834 circuit.barrier(qubits)?;
835 Ok(())
836 }
837 Stmt::Reset(reset) => {
838 for qubit in self.expand_qubit_expr(reset.gate_operand())? {
839 circuit.reset(qubit)?;
840 }
841 Ok(())
842 }
843 Stmt::Assignment(assignment) => self.lower_assignment(assignment, circuit),
844 Stmt::ExprStmt(expr) => {
845 if let Expr::MeasureExpression(measure) = expr.expression() {
846 for qubit in self.expand_qubit_expr(measure.operand())? {
847 circuit.measure(qubit)?;
848 }
849 Ok(())
850 } else {
851 Err(Qasm3ParseError::UnsupportedFeature(
852 "expression statement".to_string(),
853 ))
854 }
855 }
856 Stmt::If(op) => {
857 let condition = self.lower_condition(op.condition())?;
858 if condition.is_bool_true() {
859 return self.lower_stmt_slice(op.then_branch().statements(), circuit);
860 }
861 if condition.is_bool_false() {
862 return op.else_branch().map_or(Ok(()), |else_branch| {
863 self.lower_stmt_slice(else_branch.statements(), circuit)
864 });
865 }
866 if let Some(else_branch) = op.else_branch() {
867 let ctx = RefCell::new(self);
868 circuit.if_else(
869 condition,
870 |then_circuit| ctx.borrow_mut().lower_block(op.then_branch(), then_circuit),
871 |else_circuit| ctx.borrow_mut().lower_block(else_branch, else_circuit),
872 )?;
873 } else {
874 circuit.if_(condition, |then_circuit| {
875 self.lower_block(op.then_branch(), then_circuit)
876 })?;
877 }
878 Ok(())
879 }
880 Stmt::While(op) => {
881 let condition = self.lower_condition(op.condition())?;
882 circuit.while_(condition, |body| self.lower_block(op.loop_body(), body))?;
883 Ok(())
884 }
885 Stmt::ForStmt(op) => self.lower_for(op, circuit),
886 Stmt::SwitchCaseStmt(op) => self.lower_switch(op, circuit),
887 Stmt::Break => {
888 circuit.break_loop()?;
889 Ok(())
890 }
891 Stmt::Continue => {
892 circuit.continue_loop()?;
893 Ok(())
894 }
895 Stmt::Block(block) => {
896 self.lower_block(block, circuit)?;
897 Ok(())
898 }
899 Stmt::Pragma(_) | Stmt::AnnotatedStmt(_) => Err(Qasm3ParseError::UnsupportedFeature(
900 "pragma or annotation".to_string(),
901 )),
902 Stmt::Delay(_) => Err(Qasm3ParseError::UnsupportedFeature(
903 "delay/timing".to_string(),
904 )),
905 Stmt::DefStmt(_) => Err(Qasm3ParseError::UnsupportedFeature(
906 "subroutine def".to_string(),
907 )),
908 Stmt::Extern => Err(Qasm3ParseError::UnsupportedFeature("extern".to_string())),
909 Stmt::Cal | Stmt::DefCal => Err(Qasm3ParseError::UnsupportedFeature(
910 "calibration/defcal".to_string(),
911 )),
912 Stmt::InputDeclaration(input) => {
913 let id = self.symbol_id(input.name())?;
914 match self.symbol_type(&id) {
915 Type::Angle(_, _) | Type::Float(_, _) => Ok(()),
916 ty => Err(Qasm3ParseError::UnsupportedFeature(format!(
917 "input declaration of type {ty:?}"
918 ))),
919 }
920 }
921 Stmt::OutputDeclaration(_) => Err(Qasm3ParseError::UnsupportedFeature(
922 "output declaration".to_string(),
923 )),
924 Stmt::Alias(_) => Err(Qasm3ParseError::UnsupportedFeature("alias".to_string())),
925 Stmt::DeclareHardwareQubit(_) => Err(Qasm3ParseError::UnsupportedFeature(
926 "hardware qubit".to_string(),
927 )),
928 Stmt::Box => Err(Qasm3ParseError::UnsupportedFeature("box".to_string())),
929 Stmt::End | Stmt::NullStmt => Ok(()),
930 Stmt::OldStyleDeclaration => Err(Qasm3ParseError::UnsupportedFeature(
931 "old-style declaration".to_string(),
932 )),
933 }
934 }
935
936 fn lower_block(
937 &mut self,
938 block: &asg::Block,
939 circuit: &mut Circuit,
940 ) -> Result<(), crate::circuit::CircuitError> {
941 self.lower_stmt_slice(block.statements(), circuit)
942 .map_err(|e| crate::circuit::CircuitError::InvalidOperation(e.to_string()))
943 }
944
945 fn lower_stmt_slice(
946 &mut self,
947 stmts: &[Stmt],
948 circuit: &mut Circuit,
949 ) -> Result<(), Qasm3ParseError> {
950 for stmt in stmts {
951 self.lower_stmt(stmt, circuit)?;
952 }
953 Ok(())
954 }
955
956 fn lower_assignment(
957 &mut self,
958 assignment: &asg::Assignment,
959 circuit: &mut Circuit,
960 ) -> Result<(), Qasm3ParseError> {
961 match assignment.lvalue() {
962 LValue::Identifier(target_id_result) => {
963 let target_id = self.symbol_id(target_id_result)?;
964 let Some(target) = self.classical.get(&target_id).copied() else {
965 return Err(Qasm3ParseError::UndefinedSymbol(
966 self.symbol_name(&target_id),
967 ));
968 };
969
970 if let Expr::MeasureExpression(measure) = assignment.rvalue().expression() {
971 let qubits = self.expand_qubit_expr(measure.operand())?;
972 if qubits.len() == 1 {
973 circuit.measure_into(qubits[0], target)?;
974 } else {
975 circuit.measure_bits_into(qubits, target)?;
976 }
977 return Ok(());
978 }
979
980 let value = self.lower_classical_expr(assignment.rvalue())?;
981 circuit.store(target, value)?;
982 Ok(())
983 }
984 LValue::IndexedIdentifier(indexed) => {
985 self.lower_indexed_assignment(indexed, assignment.rvalue(), circuit)
986 }
987 }
988 }
989
990 fn lower_indexed_assignment(
991 &mut self,
992 indexed: &asg::IndexedIdentifier,
993 rvalue: &TExpr,
994 circuit: &mut Circuit,
995 ) -> Result<(), Qasm3ParseError> {
996 let target = self.indexed_classical_target(indexed)?;
997 let bit = match rvalue.expression() {
998 Expr::MeasureExpression(measure) => {
999 let qubits = self.expand_qubit_expr(measure.operand())?;
1000 if qubits.len() != 1 {
1001 return Err(Qasm3ParseError::MismatchedQubitCount {
1002 expected: 1,
1003 actual: qubits.len(),
1004 });
1005 }
1006 circuit.measure(qubits[0])?.expr()
1007 }
1008 _ => self.lower_classical_expr(rvalue)?,
1009 };
1010 self.store_indexed_classical_bit(target, bit, circuit)
1011 }
1012
1013 fn indexed_classical_target(
1014 &self,
1015 indexed: &asg::IndexedIdentifier,
1016 ) -> Result<(ClassicalVar, u32), Qasm3ParseError> {
1017 let id = self.symbol_id(indexed.identifier())?;
1018 let Some(var) = self.classical.get(&id).copied() else {
1019 return Err(Qasm3ParseError::UndefinedSymbol(self.symbol_name(&id)));
1020 };
1021 if indexed.indexes().len() != 1 {
1022 return Err(Qasm3ParseError::UnsupportedFeature(
1023 "multi-dimensional classical assignment index".to_string(),
1024 ));
1025 }
1026 let ClassicalType::BitVec(width) = var.ty() else {
1027 return Err(Qasm3ParseError::TypeError(format!(
1028 "indexed assignment target '{}' must be bit array, got {:?}",
1029 self.symbol_name(&id),
1030 var.ty()
1031 )));
1032 };
1033 let index = self.single_index(&indexed.indexes()[0])?;
1034 if index >= width.get() {
1035 return Err(Qasm3ParseError::InvalidArgument(format!(
1036 "classical index {index} out of bounds for '{}'",
1037 self.symbol_name(&id)
1038 )));
1039 }
1040 Ok((var, index))
1041 }
1042
1043 fn store_indexed_classical_bit(
1044 &self,
1045 (target, index): (ClassicalVar, u32),
1046 bit: ClassicalExpr,
1047 circuit: &mut Circuit,
1048 ) -> Result<(), Qasm3ParseError> {
1049 if bit.ty() != ClassicalType::Bit {
1050 return Err(Qasm3ParseError::TypeError(format!(
1051 "indexed bit assignment expects Bit expression, got {:?}",
1052 bit.ty()
1053 )));
1054 }
1055 let width = match target.ty() {
1056 ClassicalType::BitVec(width) => width.get(),
1057 ty => {
1058 return Err(Qasm3ParseError::TypeError(format!(
1059 "indexed assignment target must be BitVec, got {ty:?}"
1060 )));
1061 }
1062 };
1063 let current = target.expr();
1064 let bits = (0..width)
1065 .map(|bit_index| {
1066 if bit_index == index {
1067 Ok(bit.clone())
1068 } else {
1069 ClassicalExpr::extract_bit(current.clone(), bit_index)
1070 }
1071 })
1072 .collect::<Result<Vec<_>, _>>()?;
1073 let updated = ClassicalExpr::pack_bits(bits)?;
1074 circuit.store(target, updated)?;
1075 Ok(())
1076 }
1077
1078 fn lower_gate_call(
1079 &mut self,
1080 call: &asg::GateCall,
1081 circuit: &mut Circuit,
1082 ) -> Result<(), Qasm3ParseError> {
1083 let gate_id = self.symbol_id(call.name())?;
1084 let gate_name = self.symbol_name(&gate_id);
1085 let params = self.lower_params(call.params().unwrap_or(&[]))?;
1086 let mut qubits = self.expand_qubit_exprs(call.qubits())?;
1087
1088 if let Some((instruction, params)) =
1089 self.standard_gate(&gate_name, ¶ms, qubits.len())?
1090 {
1091 let instruction = self.apply_modifiers(instruction, call.modifiers())?;
1092 let (expected_qubits, expected_params) = instruction.gate_arity().ok_or_else(|| {
1093 Qasm3ParseError::UnsupportedFeature(format!(
1094 "gate '{gate_name}' has variable arity"
1095 ))
1096 })?;
1097 self.check_counts(expected_qubits, qubits.len(), expected_params, params.len())?;
1098 circuit.append(instruction, qubits, params, None)?;
1099 return Ok(());
1100 }
1101
1102 let gate = self.compile_gate_if_needed(&gate_id)?;
1103 match gate {
1104 GateDef::Circuit(circuit_gate) => {
1105 self.check_counts(
1106 circuit_gate.num_qubits(),
1107 qubits.len(),
1108 circuit_gate.num_params(),
1109 params.len(),
1110 )?;
1111 if !call.modifiers().is_empty() {
1112 return Err(Qasm3ParseError::UnsupportedFeature(
1113 "modifiers on circuit-defined gates".to_string(),
1114 ));
1115 }
1116 circuit.append(
1117 Instruction::CircuitGate(Box::new(circuit_gate)),
1118 qubits,
1119 params,
1120 None,
1121 )?;
1122 }
1123 GateDef::Opaque {
1124 name,
1125 qubits: expected_qubits,
1126 params: expected_params,
1127 } => {
1128 self.check_counts(expected_qubits, qubits.len(), expected_params, params.len())?;
1129 let mut instruction = Instruction::UnitaryGate(Box::new(UnitaryGate::new(
1130 &name,
1131 expected_qubits as u16,
1132 expected_params as u16,
1133 )));
1134 instruction = self.apply_modifiers(instruction, call.modifiers())?;
1135 let expected = instruction.gate_arity().ok_or_else(|| {
1136 Qasm3ParseError::UnsupportedFeature(format!("opaque gate '{name}' arity"))
1137 })?;
1138 if qubits.len() < expected.0 {
1139 return Err(Qasm3ParseError::MismatchedQubitCount {
1140 expected: expected.0,
1141 actual: qubits.len(),
1142 });
1143 }
1144 circuit.append(instruction, qubits.split_off(0), params, None)?;
1145 }
1146 GateDef::Source(_) => unreachable!("compile_gate_if_needed resolves source gates"),
1147 }
1148 Ok(())
1149 }
1150
1151 fn compile_gate_if_needed(&mut self, id: &SymbolId) -> Result<GateDef, Qasm3ParseError> {
1152 let Some(gate) = self.gates.get(id).cloned() else {
1153 return Err(Qasm3ParseError::UndefinedGate(self.symbol_name(id)));
1154 };
1155 if !matches!(gate, GateDef::Source(_)) {
1156 return Ok(gate);
1157 }
1158 if self.recursion_depth >= self.max_recursion_depth {
1159 return Err(Qasm3ParseError::RecursionLimitExceeded(
1160 self.symbol_name(id),
1161 ));
1162 }
1163 if !self.compiling_gates.insert(id.clone()) {
1164 return Err(Qasm3ParseError::CircularGateDependency {
1165 gate: self.symbol_name(id),
1166 dependency: self.symbol_name(id),
1167 });
1168 }
1169
1170 self.recursion_depth += 1;
1171 let result = self.build_circuit_gate(id);
1172 self.recursion_depth -= 1;
1173 self.compiling_gates.remove(id);
1174
1175 let compiled = result?;
1176 self.gates.insert(id.clone(), compiled.clone());
1177 Ok(compiled)
1178 }
1179
1180 fn build_circuit_gate(&mut self, id: &SymbolId) -> Result<GateDef, Qasm3ParseError> {
1181 let GateDef::Source(def) = self
1182 .gates
1183 .get(id)
1184 .cloned()
1185 .ok_or_else(|| Qasm3ParseError::UndefinedGate(format!("gate id {:?}", id)))?
1186 else {
1187 return self
1188 .gates
1189 .get(id)
1190 .cloned()
1191 .ok_or_else(|| Qasm3ParseError::UndefinedGate(format!("gate id {:?}", id)));
1192 };
1193
1194 let saved_quantum = self.quantum.clone();
1195 let mut gate_circuit = Circuit::new(def.qubits().len());
1196 for (index, qubit_id_result) in def.qubits().iter().enumerate() {
1197 let qubit_id = self.symbol_id(qubit_id_result)?;
1198 self.quantum
1199 .insert(qubit_id, QuantumBinding::Qubit(Qubit::new(index as u32)));
1200 }
1201
1202 for stmt in def.block().statements() {
1203 match stmt {
1204 Stmt::GateCall(_) | Stmt::GPhaseCall(_) | Stmt::Barrier(_) => {
1205 self.lower_stmt(stmt, &mut gate_circuit)?;
1206 }
1207 _ => {
1208 self.quantum = saved_quantum;
1209 return Err(Qasm3ParseError::UnsupportedFeature(format!(
1210 "statement in gate body: {stmt:?}"
1211 )));
1212 }
1213 }
1214 }
1215
1216 self.quantum = saved_quantum;
1217 let signature_params = def
1218 .params()
1219 .unwrap_or(&[])
1220 .iter()
1221 .map(|param| self.symbol_id(param).map(|id| self.symbol_name(&id)))
1222 .collect::<Result<Vec<_>, _>>()?;
1223 let gate = CircuitGate::with_signature(
1224 self.symbol_name(id),
1225 FrozenCircuit::new(gate_circuit),
1226 signature_params,
1227 )?;
1228 Ok(GateDef::Circuit(gate))
1229 }
1230
1231 fn lower_for(
1232 &mut self,
1233 op: &asg::ForStmt,
1234 circuit: &mut Circuit,
1235 ) -> Result<(), Qasm3ParseError> {
1236 let loop_id = self.symbol_id(op.loop_var())?;
1237 let ForIterable::RangeExpression(range) = op.iterable() else {
1238 return Err(Qasm3ParseError::UnsupportedFeature(
1239 "non-range for iterable".to_string(),
1240 ));
1241 };
1242 let start = self.const_u128(range.start())?;
1243 let stop = self.const_u128(range.stop())?;
1244 let step = match range.step() {
1245 Some(step) => self.const_u128(step)?,
1246 None => 1,
1247 };
1248 if step == 0 {
1249 return Err(Qasm3ParseError::InvalidArgument(
1250 "for loop step must be non-zero".to_string(),
1251 ));
1252 }
1253 let old = self.loop_constants.get(&loop_id).copied();
1254 let mut value = start;
1255 while value <= stop {
1256 self.loop_constants.insert(loop_id.clone(), value);
1257 self.lower_block(op.loop_body(), circuit)?;
1258 value = value.checked_add(step).ok_or_else(|| {
1259 Qasm3ParseError::InvalidArgument("for loop value overflow".to_string())
1260 })?;
1261 }
1262 match old {
1263 Some(value) => {
1264 self.loop_constants.insert(loop_id, value);
1265 }
1266 None => {
1267 self.loop_constants.remove(&loop_id);
1268 }
1269 }
1270 Ok(())
1271 }
1272
1273 fn lower_switch(
1274 &mut self,
1275 op: &asg::SwitchCaseStmt,
1276 circuit: &mut Circuit,
1277 ) -> Result<(), Qasm3ParseError> {
1278 let target = self.lower_classical_expr(op.control())?;
1279 let target = match target.ty() {
1280 ClassicalType::UInt(_) => target,
1281 ClassicalType::BitVec(_) => ClassicalExpr::bit_vec_to_uint(target)?,
1282 ty => {
1283 return Err(Qasm3ParseError::TypeError(format!(
1284 "switch target must be UInt or BitVec, got {ty:?}"
1285 )));
1286 }
1287 };
1288
1289 circuit.switch(target, |builder| {
1290 for case in op.cases() {
1291 for value_expr in case.control_values() {
1292 let value = self.const_u128(value_expr).map_err(|e| {
1293 crate::circuit::CircuitError::InvalidOperation(e.to_string())
1294 })?;
1295 builder.value(value, |case_circuit| {
1296 self.lower_stmt_slice(case.statements(), case_circuit)
1297 .map_err(|e| {
1298 crate::circuit::CircuitError::InvalidOperation(e.to_string())
1299 })
1300 })?;
1301 }
1302 }
1303 if let Some(default_block) = op.default_block() {
1304 builder.default(|default_circuit| {
1305 self.lower_stmt_slice(default_block, default_circuit)
1306 .map_err(|e| crate::circuit::CircuitError::InvalidOperation(e.to_string()))
1307 })?;
1308 }
1309 Ok(())
1310 })?;
1311 Ok(())
1312 }
1313
1314 fn standard_gate(
1315 &self,
1316 name: &str,
1317 params: &[ParameterValue],
1318 qubit_count: usize,
1319 ) -> Result<Option<(Instruction, Vec<ParameterValue>)>, Qasm3ParseError> {
1320 let instruction = match name {
1321 "id" | "i" => Some(Instruction::Standard(StandardGate::I)),
1322 "x" => Some(Instruction::Standard(StandardGate::X)),
1323 "y" => Some(Instruction::Standard(StandardGate::Y)),
1324 "z" => Some(Instruction::Standard(StandardGate::Z)),
1325 "h" => Some(Instruction::Standard(StandardGate::H)),
1326 "s" => Some(Instruction::Standard(StandardGate::S)),
1327 "sdg" => Some(Instruction::Standard(StandardGate::SDG)),
1328 "t" => Some(Instruction::Standard(StandardGate::T)),
1329 "tdg" => Some(Instruction::Standard(StandardGate::TDG)),
1330 "x2p" => Some(Instruction::Standard(StandardGate::X2P)),
1331 "x2m" => Some(Instruction::Standard(StandardGate::X2M)),
1332 "y2p" => Some(Instruction::Standard(StandardGate::Y2P)),
1333 "y2m" => Some(Instruction::Standard(StandardGate::Y2M)),
1334 "sx" => Some(Instruction::Standard(StandardGate::X2P)),
1335 "sxdg" => Some(Instruction::Standard(StandardGate::X2M)),
1336 "rx" => Some(Instruction::Standard(StandardGate::RX)),
1337 "ry" => Some(Instruction::Standard(StandardGate::RY)),
1338 "rz" => Some(Instruction::Standard(StandardGate::RZ)),
1339 "p" | "phase" | "u1" => Some(Instruction::Standard(StandardGate::Phase)),
1340 "u" | "U" | "u3" => Some(Instruction::Standard(StandardGate::U)),
1341 "u2" => {
1342 if params.len() != 2 {
1343 return Err(Qasm3ParseError::MismatchedParameterCount {
1344 expected: 2,
1345 actual: params.len(),
1346 });
1347 }
1348 let mut new_params = vec![ParameterValue::Param(Parameter::pi() / 2.0)];
1349 new_params.extend_from_slice(params);
1350 return Ok(Some((Instruction::Standard(StandardGate::U), new_params)));
1351 }
1352 "cx" | "CX" => Some(Instruction::Standard(StandardGate::CX)),
1353 "cy" => Some(Instruction::Standard(StandardGate::CY)),
1354 "cz" => Some(Instruction::Standard(StandardGate::CZ)),
1355 "swap" => Some(Instruction::Standard(StandardGate::SWAP)),
1356 "ccx" => Some(Instruction::Standard(StandardGate::CCX)),
1357 "rxx" => Some(Instruction::Standard(StandardGate::RXX)),
1358 "ryy" => Some(Instruction::Standard(StandardGate::RYY)),
1359 "rzz" => Some(Instruction::Standard(StandardGate::RZZ)),
1360 "rzx" => Some(Instruction::Standard(StandardGate::RZX)),
1361 "xy2p" => Some(Instruction::Standard(StandardGate::XY2P)),
1362 "xy2m" => Some(Instruction::Standard(StandardGate::XY2M)),
1363 "fsim" => Some(Instruction::Standard(StandardGate::FSIM)),
1364 "crx" => Some(Instruction::Standard(StandardGate::CRX)),
1365 "cry" => Some(Instruction::Standard(StandardGate::CRY)),
1366 "crz" => Some(Instruction::Standard(StandardGate::CRZ)),
1367 "gphase" => Some(Instruction::Standard(StandardGate::GPhase)),
1368 "ch" | "cp" | "cu" | "cswap" => {
1369 return Err(Qasm3ParseError::UnsupportedFeature(format!(
1370 "standard gate '{name}'"
1371 )));
1372 }
1373 _ => None,
1374 };
1375 if let Some(instruction) = instruction {
1376 let (expected_qubits, expected_params) = instruction.gate_arity().ok_or_else(|| {
1377 Qasm3ParseError::UnsupportedFeature(format!("gate '{name}' arity"))
1378 })?;
1379 self.check_counts(expected_qubits, qubit_count, expected_params, params.len())?;
1380 Ok(Some((instruction, params.to_vec())))
1381 } else {
1382 Ok(None)
1383 }
1384 }
1385
1386 fn apply_modifiers(
1387 &self,
1388 mut instruction: Instruction,
1389 modifiers: &[GateModifier],
1390 ) -> Result<Instruction, Qasm3ParseError> {
1391 for modifier in modifiers.iter().rev() {
1392 match modifier {
1393 GateModifier::Inv => {
1394 let params = Vec::<Parameter>::new();
1395 let Some((inverse, _)) = instruction.inverse(¶ms) else {
1396 return Err(Qasm3ParseError::UnsupportedFeature(
1397 "inverse modifier for this gate".to_string(),
1398 ));
1399 };
1400 instruction = inverse;
1401 }
1402 GateModifier::Ctrl(count) => {
1403 let count = match count {
1404 Some(expr) => self.const_u128(expr)? as usize,
1405 None => 1,
1406 };
1407 let Some(controlled) = instruction.control(count) else {
1408 return Err(Qasm3ParseError::UnsupportedFeature(
1409 "control modifier for this gate".to_string(),
1410 ));
1411 };
1412 instruction = controlled;
1413 }
1414 GateModifier::NegCtrl(_) | GateModifier::Pow(_) => {
1415 return Err(Qasm3ParseError::UnsupportedFeature(
1416 "negctrl or pow gate modifier".to_string(),
1417 ));
1418 }
1419 }
1420 }
1421 Ok(instruction)
1422 }
1423
1424 fn check_counts(
1425 &self,
1426 expected_qubits: usize,
1427 actual_qubits: usize,
1428 expected_params: usize,
1429 actual_params: usize,
1430 ) -> Result<(), Qasm3ParseError> {
1431 if expected_qubits != actual_qubits {
1432 return Err(Qasm3ParseError::MismatchedQubitCount {
1433 expected: expected_qubits,
1434 actual: actual_qubits,
1435 });
1436 }
1437 if expected_params != actual_params {
1438 return Err(Qasm3ParseError::MismatchedParameterCount {
1439 expected: expected_params,
1440 actual: actual_params,
1441 });
1442 }
1443 Ok(())
1444 }
1445
1446 fn lower_params(&self, params: &[TExpr]) -> Result<Vec<ParameterValue>, Qasm3ParseError> {
1447 params
1448 .iter()
1449 .map(|expr| {
1450 let param = self.lower_angle(expr)?;
1451 if let Ok(value) = param.evaluate(&None) {
1452 Ok(ParameterValue::Fixed(value))
1453 } else {
1454 Ok(ParameterValue::Param(param))
1455 }
1456 })
1457 .collect()
1458 }
1459
1460 fn lower_angle(&self, expr: &TExpr) -> Result<Parameter, Qasm3ParseError> {
1461 match expr.expression() {
1462 Expr::Literal(Literal::Int(value)) => {
1463 if !*value.sign() {
1464 return Err(Qasm3ParseError::InvalidArgument(
1465 "negative integer literal".to_string(),
1466 ));
1467 }
1468 Ok(Parameter::from(*value.value() as f64))
1469 }
1470 Expr::Literal(Literal::Float(value)) => value
1471 .value()
1472 .parse::<f64>()
1473 .map(Parameter::from)
1474 .map_err(|e| Qasm3ParseError::InvalidArgument(e.to_string())),
1475 Expr::Identifier(id_result) => {
1476 let id = self.symbol_id(id_result)?;
1477 let name = self.symbol_name(&id);
1478 match name.as_str() {
1479 "pi" | "π" => Ok(Parameter::pi()),
1480 "tau" | "τ" => Ok(Parameter::pi() * 2.0),
1481 "euler" | "ℇ" => Ok(Parameter::symbol("e")),
1482 _ => Ok(Parameter::symbol(&name)),
1483 }
1484 }
1485 Expr::Cast(cast) => self.lower_angle(cast.operand()),
1486 Expr::UnaryExpr(unary) => {
1487 let value = self.lower_angle(unary.operand())?;
1488 match unary.op() {
1489 UnaryOp::Minus => Ok(Parameter::from(0.0) - value),
1490 _ => Err(Qasm3ParseError::UnsupportedFeature(format!(
1491 "angle unary operator {:?}",
1492 unary.op()
1493 ))),
1494 }
1495 }
1496 Expr::BinaryExpr(binary) => {
1497 let left = self.lower_angle(binary.left())?;
1498 let right = self.lower_angle(binary.right())?;
1499 match binary.op() {
1500 BinaryOp::ArithOp(ArithOp::Add) => Ok(left + right),
1501 BinaryOp::ArithOp(ArithOp::Sub) => Ok(left - right),
1502 BinaryOp::ArithOp(ArithOp::Mul) => Ok(left * right),
1503 BinaryOp::ArithOp(ArithOp::Div) => Ok(left / right),
1504 _ => Err(Qasm3ParseError::UnsupportedFeature(format!(
1505 "angle binary operator {:?}",
1506 binary.op()
1507 ))),
1508 }
1509 }
1510 Expr::Call => Err(Qasm3ParseError::UnsupportedFeature(
1511 "function call in angle expression".to_string(),
1512 )),
1513 _ => Err(Qasm3ParseError::UnsupportedFeature(format!(
1514 "angle expression {:?}",
1515 expr.expression()
1516 ))),
1517 }
1518 }
1519
1520 fn lower_condition(&self, expr: &TExpr) -> Result<ClassicalExpr, Qasm3ParseError> {
1521 let expr = self.lower_classical_expr(expr)?;
1522 match expr.ty() {
1523 ClassicalType::Bool => Ok(expr),
1524 ClassicalType::Bit => Ok(ClassicalExpr::bit_to_bool(expr)?),
1525 ty => Err(Qasm3ParseError::TypeError(format!(
1526 "condition must be Bool or Bit, got {ty:?}"
1527 ))),
1528 }
1529 }
1530
1531 fn lower_classical_expr(&self, expr: &TExpr) -> Result<ClassicalExpr, Qasm3ParseError> {
1532 match expr.expression() {
1533 Expr::Literal(Literal::Bool(value)) => Ok(ClassicalExpr::bool_literal(*value.value())),
1534 Expr::Literal(Literal::Int(value)) => {
1535 if !*value.sign() {
1536 return Err(Qasm3ParseError::UnsupportedFeature(
1537 "signed integer classical literal".to_string(),
1538 ));
1539 }
1540 let width = expr.get_type().width().unwrap_or(128);
1541 Ok(ClassicalExpr::uint_literal(width, *value.value())?)
1542 }
1543 Expr::Literal(Literal::BitString(bits)) => {
1544 let clean = bits
1545 .value()
1546 .chars()
1547 .filter(|ch| *ch == '0' || *ch == '1')
1548 .collect::<String>();
1549 let value = u128::from_str_radix(&clean, 2)
1550 .map_err(|e| Qasm3ParseError::InvalidArgument(e.to_string()))?;
1551 Ok(ClassicalExpr::bit_vec_literal(clean.len() as u32, value)?)
1552 }
1553 Expr::Identifier(id_result) => {
1554 let id = self.symbol_id(id_result)?;
1555 if let Some(value) = self.loop_constants.get(&id) {
1556 let width = self.symbol_type(&id).width().unwrap_or(128);
1557 return Ok(ClassicalExpr::uint_literal(width, *value)?);
1558 }
1559 let Some(var) = self.classical.get(&id).copied() else {
1560 return Err(Qasm3ParseError::UndefinedSymbol(self.symbol_name(&id)));
1561 };
1562 Ok(var.expr())
1563 }
1564 Expr::Cast(cast) => {
1565 if matches!(cast.get_type(), Type::Bit(_))
1566 && let Expr::Literal(Literal::Bool(value)) = cast.operand().expression()
1567 {
1568 return Ok(ClassicalExpr::bit_literal(*value.value()));
1569 }
1570 if let Type::UInt(Some(width), _) = cast.get_type()
1571 && let Ok(value) = self.const_u128(cast.operand())
1572 {
1573 return Ok(ClassicalExpr::uint_literal(*width, value)?);
1574 }
1575 let operand = self.lower_classical_expr(cast.operand())?;
1576 match (operand.ty(), cast.get_type()) {
1577 (ClassicalType::Bit, Type::Bool(_)) => Ok(ClassicalExpr::bit_to_bool(operand)?),
1578 (ClassicalType::BitVec(_), Type::UInt(_, _)) => {
1579 Ok(ClassicalExpr::bit_vec_to_uint(operand)?)
1580 }
1581 (_, _) if self.classical_type(cast.get_type()).ok() == Some(operand.ty()) => {
1582 Ok(operand)
1583 }
1584 _ => Err(Qasm3ParseError::UnsupportedFeature(format!(
1585 "classical cast from {:?} to {:?}",
1586 operand.ty(),
1587 cast.get_type()
1588 ))),
1589 }
1590 }
1591 Expr::UnaryExpr(unary) => {
1592 let operand = self.lower_classical_expr(unary.operand())?;
1593 match unary.op() {
1594 UnaryOp::Not | UnaryOp::BitNot => Ok(ClassicalExpr::try_not(operand)?),
1595 UnaryOp::Minus => Err(Qasm3ParseError::UnsupportedFeature(
1596 "runtime unary minus".to_string(),
1597 )),
1598 }
1599 }
1600 Expr::BinaryExpr(binary) => {
1601 let left = self.lower_classical_expr(binary.left())?;
1602 let right = self.lower_classical_expr(binary.right())?;
1603 match binary.op() {
1604 BinaryOp::CmpOp(CmpOp::Eq) => Ok(ClassicalExpr::eq(left, right)?),
1605 BinaryOp::CmpOp(CmpOp::Neq) => Ok(ClassicalExpr::ne(left, right)?),
1606 BinaryOp::ArithOp(ArithOp::BitAnd) => Ok(ClassicalExpr::try_and(left, right)?),
1607 BinaryOp::ArithOp(ArithOp::BitXOr) => Ok(ClassicalExpr::try_xor(left, right)?),
1608 _ => Err(Qasm3ParseError::UnsupportedFeature(format!(
1609 "runtime classical binary operator {:?}",
1610 binary.op()
1611 ))),
1612 }
1613 }
1614 Expr::IndexedIdentifier(indexed) => {
1615 let base = self.lower_classical_indexed_base(indexed)?;
1616 if indexed.indexes().len() != 1 {
1617 return Err(Qasm3ParseError::UnsupportedFeature(
1618 "multi-dimensional classical index".to_string(),
1619 ));
1620 }
1621 let index = self.single_index(&indexed.indexes()[0])?;
1622 Ok(ClassicalExpr::extract_bit(base, index)?)
1623 }
1624 _ => Err(Qasm3ParseError::UnsupportedFeature(format!(
1625 "classical expression {:?}",
1626 expr.expression()
1627 ))),
1628 }
1629 }
1630
1631 fn lower_classical_indexed_base(
1632 &self,
1633 indexed: &asg::IndexedIdentifier,
1634 ) -> Result<ClassicalExpr, Qasm3ParseError> {
1635 let id = self.symbol_id(indexed.identifier())?;
1636 let Some(var) = self.classical.get(&id).copied() else {
1637 return Err(Qasm3ParseError::UndefinedSymbol(self.symbol_name(&id)));
1638 };
1639 Ok(var.expr())
1640 }
1641
1642 fn expand_qubit_exprs(&self, exprs: &[TExpr]) -> Result<Vec<Qubit>, Qasm3ParseError> {
1643 let mut out = Vec::new();
1644 for expr in exprs {
1645 out.extend(self.expand_qubit_expr(expr)?);
1646 }
1647 Ok(out)
1648 }
1649
1650 fn expand_qubit_expr(&self, expr: &TExpr) -> Result<Vec<Qubit>, Qasm3ParseError> {
1651 match expr.expression() {
1652 Expr::GateOperand(GateOperand::Identifier(id_result)) | Expr::Identifier(id_result) => {
1653 let id = self.symbol_id(id_result)?;
1654 self.quantum
1655 .get(&id)
1656 .map(QuantumBinding::qubits)
1657 .ok_or_else(|| Qasm3ParseError::UndefinedSymbol(self.symbol_name(&id)))
1658 }
1659 Expr::GateOperand(GateOperand::IndexedIdentifier(indexed))
1660 | Expr::IndexedIdentifier(indexed) => {
1661 self.resolve_indexed_qubit(indexed).map(|q| vec![q])
1662 }
1663 Expr::GateOperand(GateOperand::HardwareQubit(_)) | Expr::HardwareQubit(_) => Err(
1664 Qasm3ParseError::UnsupportedFeature("hardware qubit".to_string()),
1665 ),
1666 Expr::Cast(cast) => self.expand_qubit_expr(cast.operand()),
1667 _ => Err(Qasm3ParseError::TypeError(format!(
1668 "expected qubit operand, got {:?}",
1669 expr.expression()
1670 ))),
1671 }
1672 }
1673
1674 fn resolve_indexed_qubit(
1675 &self,
1676 indexed: &asg::IndexedIdentifier,
1677 ) -> Result<Qubit, Qasm3ParseError> {
1678 let id = self.symbol_id(indexed.identifier())?;
1679 if indexed.indexes().len() != 1 {
1680 return Err(Qasm3ParseError::UnsupportedFeature(
1681 "multi-dimensional qubit index".to_string(),
1682 ));
1683 }
1684 let index = self.single_index(&indexed.indexes()[0])? as usize;
1685 match self.quantum.get(&id) {
1686 Some(QuantumBinding::Qubit(q)) if index == 0 => Ok(*q),
1687 Some(QuantumBinding::Register(qs)) => qs.get(index).copied().ok_or_else(|| {
1688 Qasm3ParseError::InvalidArgument(format!(
1689 "qubit index {index} out of bounds for '{}'",
1690 self.symbol_name(&id)
1691 ))
1692 }),
1693 Some(QuantumBinding::Qubit(_)) => Err(Qasm3ParseError::InvalidArgument(format!(
1694 "qubit index {index} out of bounds for '{}'",
1695 self.symbol_name(&id)
1696 ))),
1697 None => Err(Qasm3ParseError::UndefinedSymbol(self.symbol_name(&id))),
1698 }
1699 }
1700
1701 fn single_index(&self, index: &IndexOperator) -> Result<u32, Qasm3ParseError> {
1702 match index {
1703 IndexOperator::ExpressionList(list) if list.expressions.len() == 1 => {
1704 let value = self.const_u128(&list.expressions[0])?;
1705 u32::try_from(value).map_err(|_| {
1706 Qasm3ParseError::InvalidArgument(format!("index {value} exceeds u32"))
1707 })
1708 }
1709 _ => Err(Qasm3ParseError::UnsupportedFeature(
1710 "non-scalar index".to_string(),
1711 )),
1712 }
1713 }
1714
1715 fn const_u128(&self, expr: &TExpr) -> Result<u128, Qasm3ParseError> {
1716 match expr.expression() {
1717 Expr::Literal(Literal::Int(value)) if *value.sign() => Ok(*value.value()),
1718 Expr::Cast(cast) => self.const_u128(cast.operand()),
1719 Expr::Identifier(id_result) => {
1720 let id = self.symbol_id(id_result)?;
1721 self.loop_constants
1722 .get(&id)
1723 .copied()
1724 .ok_or_else(|| Qasm3ParseError::InvalidArgument(self.symbol_name(&id)))
1725 }
1726 Expr::BinaryExpr(binary) => {
1727 let left = self.const_u128(binary.left())?;
1728 let right = self.const_u128(binary.right())?;
1729 match binary.op() {
1730 BinaryOp::ArithOp(ArithOp::Add) => left.checked_add(right).ok_or_else(|| {
1731 Qasm3ParseError::InvalidArgument("constant add overflow".to_string())
1732 }),
1733 BinaryOp::ArithOp(ArithOp::Sub) => left.checked_sub(right).ok_or_else(|| {
1734 Qasm3ParseError::InvalidArgument("constant sub underflow".to_string())
1735 }),
1736 BinaryOp::ArithOp(ArithOp::Mul) => left.checked_mul(right).ok_or_else(|| {
1737 Qasm3ParseError::InvalidArgument("constant mul overflow".to_string())
1738 }),
1739 BinaryOp::ArithOp(ArithOp::Div) => left.checked_div(right).ok_or_else(|| {
1740 Qasm3ParseError::InvalidArgument("division by zero".to_string())
1741 }),
1742 _ => Err(Qasm3ParseError::UnsupportedFeature(format!(
1743 "constant operator {:?}",
1744 binary.op()
1745 ))),
1746 }
1747 }
1748 _ => Err(Qasm3ParseError::InvalidArgument(format!(
1749 "expected unsigned integer constant, got {:?}",
1750 expr.expression()
1751 ))),
1752 }
1753 }
1754
1755 fn classical_type(&self, ty: &Type) -> Result<ClassicalType, Qasm3ParseError> {
1756 match ty {
1757 Type::Bit(_) => Ok(ClassicalType::Bit),
1758 Type::Bool(_) => Ok(ClassicalType::Bool),
1759 Type::UInt(Some(width), _) => ClassicalType::uint(*width).ok_or_else(|| {
1760 Qasm3ParseError::InvalidArgument("uint width must be non-zero".to_string())
1761 }),
1762 Type::BitArray(ArrayDims::D1(width), _) => ClassicalType::bit_vec(*width as u32)
1763 .ok_or_else(|| {
1764 Qasm3ParseError::InvalidArgument("bit array width must be non-zero".to_string())
1765 }),
1766 _ => Err(Qasm3ParseError::UnsupportedFeature(format!(
1767 "classical type {ty:?}"
1768 ))),
1769 }
1770 }
1771
1772 fn symbol_id(&self, result: &SymbolIdResult) -> Result<SymbolId, Qasm3ParseError> {
1773 result
1774 .clone()
1775 .map_err(|err| Qasm3ParseError::UndefinedSymbol(format!("{err:?}")))
1776 }
1777
1778 fn symbol_name(&self, id: &SymbolId) -> String {
1779 self.symbols[id].name().to_string()
1780 }
1781
1782 fn symbol_type(&self, id: &SymbolId) -> &Type {
1783 self.symbols[id].symbol_type()
1784 }
1785}
1786
1787#[cfg(test)]
1788#[path = "./load_test.rs"]
1789mod load_test;