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 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        // oq3_semantics 0.7.0 panics on anonymous BlockExpr. A constant-true
364        // branch gives its parser the same lexical scope and execution semantics.
365        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/// Error returned while parsing or lowering OpenQASM 3 into Cqlib.
540///
541/// `ParseError` and `SemanticError` come from the OpenQASM front-end. The
542/// other variants are produced by Cqlib's lowering layer when a valid ASG
543/// cannot be represented as a [`Circuit`] without losing semantics.
544#[derive(Debug)]
545pub enum Qasm3ParseError {
546    /// File system or I/O error while reading OpenQASM source.
547    IoError(io::Error),
548    /// The OpenQASM parser reported syntax errors.
549    ParseError(String),
550    /// The OpenQASM semantic analyzer reported unresolved symbols, invalid
551    /// gate arity, or type errors before Cqlib lowering began.
552    SemanticError(String),
553    /// A Cqlib circuit construction error occurred while appending lowered IR.
554    ConversionError(String),
555    /// The source uses a valid OpenQASM 3 feature not supported by this loader.
556    UnsupportedFeature(String),
557    /// A symbol reference could not be resolved in Cqlib's lowering context.
558    UndefinedSymbol(String),
559    /// A gate reference could not be resolved as a standard or custom gate.
560    UndefinedGate(String),
561    /// A value had an incompatible OpenQASM/Cqlib type.
562    TypeError(String),
563    /// A literal, index, range, or declaration argument is invalid.
564    InvalidArgument(String),
565    /// A gate application used the wrong number of qubits.
566    MismatchedQubitCount { expected: usize, actual: usize },
567    /// A gate application used the wrong number of parameters.
568    MismatchedParameterCount { expected: usize, actual: usize },
569    /// A custom-gate dependency chain exceeded the configured recursion limit.
570    RecursionLimitExceeded(String),
571    /// A custom gate depends on itself directly or indirectly.
572    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, &params, 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(&params) 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;