Skip to main content

cqlib_core/ir/qasm3/
load.rs

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