• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 24428588749

14 Apr 2026 11:15PM UTC coverage: 87.474% (-0.006%) from 87.48%
24428588749

push

github

web-flow
Update QPY format version history table (#16034)

With the 2.4.0 release pending this commit updates the version history
table to include the missing 2.3.1 and the future 2.4.0. Right now the
table only shows up to 2.3.0 which is not the current release. We should
add this to the release process checklist so we don't forget in future
release to update this table.

104532 of 119500 relevant lines covered (87.47%)

979030.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.21
/crates/qasm2/src/parse.rs
1
// This code is part of Qiskit.
2
//
3
// (C) Copyright IBM 2023
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 https://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

13
//! The core of the parsing algorithm.  This module contains the core logic for the
14
//! recursive-descent parser, which handles all statements of OpenQASM 2.  In places where we have
15
//! to evaluate a mathematical expression on parameters, we instead swap to a short-lived
16
//! operator-precedence parser.
17

18
use hashbrown::{HashMap, HashSet};
19
use num_bigint::BigUint;
20
use pyo3::prelude::*;
21

22
use crate::bytecode::InternalBytecode;
23
use crate::error::{
24
    Position, QASM2ParseError, message_bad_eof, message_generic, message_incorrect_requirement,
25
};
26
use crate::expr::{Expr, ExprParser};
27
use crate::lex::{Token, TokenContext, TokenStream, TokenType, Version};
28
use crate::{CustomClassical, CustomInstruction};
29

30
/// The number of gates that are built in to the OpenQASM 2 language.  This is U and CX.
31
const N_BUILTIN_GATES: usize = 2;
32
/// The "qelib1.inc" special include.  The elements of the tuple are the gate name, the number of
33
/// parameters it takes, and the number of qubits it acts on.
34
const QELIB1: [(&str, usize, usize); 23] = [
35
    ("u3", 3, 1),
36
    ("u2", 2, 1),
37
    ("u1", 1, 1),
38
    ("cx", 0, 2),
39
    ("id", 0, 1),
40
    ("x", 0, 1),
41
    ("y", 0, 1),
42
    ("z", 0, 1),
43
    ("h", 0, 1),
44
    ("s", 0, 1),
45
    ("sdg", 0, 1),
46
    ("t", 0, 1),
47
    ("tdg", 0, 1),
48
    ("rx", 1, 1),
49
    ("ry", 1, 1),
50
    ("rz", 1, 1),
51
    ("cz", 0, 2),
52
    ("cy", 0, 2),
53
    ("ch", 0, 2),
54
    ("ccx", 0, 3),
55
    ("crz", 1, 2),
56
    ("cu1", 1, 2),
57
    ("cu3", 3, 2),
58
];
59

60
const BUILTIN_CLASSICAL: [&str; 6] = ["cos", "exp", "ln", "sin", "sqrt", "tan"];
61

62
/// Define a simple newtype that just has a single non-public `usize` field, has a `new`
63
/// constructor, and implements `Copy` and `IntoPy`.  The first argument is the name of the type,
64
/// the second is whether to also define addition to make offsetting the newtype easier.
65
macro_rules! newtype_id {
66
    ($id:ident, false) => {
67
        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPyObject, IntoPyObjectRef)]
68
        pub struct $id(usize);
69

70
        impl $id {
71
            pub fn new(value: usize) -> Self {
26,990✔
72
                Self(value)
26,990✔
73
            }
26,990✔
74
        }
75
    };
76

77
    ($id:ident, true) => {
78
        newtype_id!($id, false);
79

80
        impl std::ops::Add<usize> for $id {
81
            type Output = Self;
82

83
            fn add(self, rhs: usize) -> Self {
6,336✔
84
                Self::new(self.0 + rhs)
6,336✔
85
            }
6,336✔
86
        }
87
    };
88
}
89

90
newtype_id!(GateId, false);
91
newtype_id!(CregId, false);
92
newtype_id!(ParamId, false);
93
newtype_id!(QubitId, true);
94
newtype_id!(ClbitId, true);
95

96
/// A symbol in the global symbol table.  Parameters and individual qubits can't be in the global
97
/// symbol table, as there is no way for them to be defined.
98
pub enum GlobalSymbol {
99
    Qreg {
100
        size: usize,
101
        start: QubitId,
102
    },
103
    Creg {
104
        size: usize,
105
        start: ClbitId,
106
        index: CregId,
107
    },
108
    Gate {
109
        num_params: usize,
110
        num_qubits: usize,
111
        index: GateId,
112
    },
113
    Classical {
114
        callable: Py<PyAny>,
115
        num_params: usize,
116
    },
117
}
118

119
impl GlobalSymbol {
120
    pub fn describe(&self) -> &'static str {
42✔
121
        match self {
42✔
122
            Self::Qreg { .. } => "a quantum register",
10✔
123
            Self::Creg { .. } => "a classical register",
12✔
124
            Self::Gate { .. } => "a gate",
14✔
125
            Self::Classical { .. } => "a custom classical function",
6✔
126
        }
127
    }
42✔
128
}
129

130
/// Information about a gate that permits a new definition in the OQ3 file only in the case that
131
/// the number of parameters and qubits matches.  This is used when the user specifies custom gates
132
/// that are not
133
struct OverridableGate {
134
    num_params: usize,
135
    num_qubits: usize,
136
    index: GateId,
137
}
138

139
impl From<OverridableGate> for GlobalSymbol {
140
    fn from(value: OverridableGate) -> Self {
3,424✔
141
        Self::Gate {
3,424✔
142
            num_params: value.num_params,
3,424✔
143
            num_qubits: value.num_qubits,
3,424✔
144
            index: value.index,
3,424✔
145
        }
3,424✔
146
    }
3,424✔
147
}
148

149
/// A symbol in the scope of a single gate definition.  This only includes the symbols that are
150
/// specifically gate-scoped.  The rest are part of [GlobalSymbol].
151
pub enum GateSymbol {
152
    Qubit { index: QubitId },
153
    Parameter { index: ParamId },
154
}
155

156
impl GateSymbol {
157
    pub fn describe(&self) -> &'static str {
6✔
158
        match self {
6✔
159
            Self::Qubit { .. } => "a qubit",
2✔
160
            Self::Parameter { .. } => "a parameter",
4✔
161
        }
162
    }
6✔
163
}
164

165
/// An operand for an instruction.  This can be both quantum or classical.  Classical operands only
166
/// occur in the `measure` operation.  `Single` variants are what we mostly expect to see; these
167
/// happen in gate definitions (always), and in regular applications when registers are indexed.
168
/// The `Range` operand only occurs when a register is used as an operation target.
169
enum Operand<T> {
170
    Single(T),
171
    Range(usize, T),
172
}
173

174
/// The available types for the arrays of parameters in a gate call.  The `Constant` variant is for
175
/// general applications, whereas the more general `Expression` variant is for gate bodies, where
176
/// there might be mathematics occurring on symbolic parameters.  We have the special case for the
177
/// far more common `Constant` form; in this case we immediately unwrap the result of the
178
/// `ExprParser`, and we won't have to make a new vector with the conversion later.
179
enum GateParameters {
180
    Constant(Vec<f64>),
181
    Expression(Vec<Expr>),
182
}
183

184
/// An equality condition from an `if` statement.  These can condition gate applications, measures
185
/// and resets, although in practice they're basically only ever used on gates.
186
#[derive(Clone)]
187
struct Condition {
188
    creg: CregId,
189
    value: BigUint,
190
}
191

192
/// Find the first match for the partial [filename] in the directories along [path].  Returns
193
/// `None` if the cannot be found.
194
fn find_include_path(
20✔
195
    filename: &std::path::Path,
20✔
196
    path: &[std::path::PathBuf],
20✔
197
) -> Option<std::path::PathBuf> {
20✔
198
    for directory in path.iter() {
24✔
199
        let mut absolute_path = directory.clone();
24✔
200
        absolute_path.push(filename);
24✔
201
        if absolute_path.is_file() {
24✔
202
            return Some(absolute_path);
18✔
203
        }
6✔
204
    }
205
    None
2✔
206
}
20✔
207

208
/// The state of the parser (but not its output).  This struct is opaque to the rest of the
209
/// program; only its associated functions ever need to modify its internals.  The counts of
210
/// qubits, clbits, classical registers and gates are necessary to efficiently assign index keys to
211
/// new symbols as they arise.  We don't need to track quantum registers like this because no part
212
/// of the output instruction set requires a reference to a quantum register, since we resolve any
213
/// broadcast gate applications from within Rust.
214
pub struct State {
215
    tokens: Vec<TokenStream>,
216
    /// The context object that owns all the text strings that back the tokens seen so far.  This
217
    /// needs to be given as a read-only reference to the [Token] methods that extract information
218
    /// based on the text they came from.
219
    context: TokenContext,
220
    include_path: Vec<std::path::PathBuf>,
221
    /// Mapping of name to global-scoped symbols.
222
    symbols: HashMap<String, GlobalSymbol>,
223
    /// Mapping of name to gate-scoped symbols.  This object only logically lasts for the duration
224
    /// of the parsing of one gate definition, but we can save allocations by re-using the same
225
    /// object between calls.
226
    gate_symbols: HashMap<String, GateSymbol>,
227
    /// Gate names that can accept a definition, even if they are already in the symbol table as
228
    /// gates. For example, if a user defines a custom gate as a `builtin`, we don't error out if
229
    /// we see a compatible definition later.  Regardless of whether the symbol was already in the
230
    /// symbol table or not, any gate-based definition of this symbol must match the signature we
231
    /// expect for it.
232
    overridable_gates: HashMap<String, OverridableGate>,
233
    num_qubits: usize,
234
    num_clbits: usize,
235
    num_cregs: usize,
236
    num_gates: usize,
237
    /// Whether a version statement is allowed in this position.
238
    allow_version: bool,
239
    /// Whether we're in strict mode or (the default) more permissive parse.
240
    strict: bool,
241
}
242

243
impl State {
244
    /// Create and initialise a state for the parser.
245
    pub fn new(
1,172✔
246
        tokens: TokenStream,
1,172✔
247
        include_path: Vec<std::path::PathBuf>,
1,172✔
248
        custom_instructions: &[CustomInstruction],
1,172✔
249
        custom_classical: &[CustomClassical],
1,172✔
250
        strict: bool,
1,172✔
251
    ) -> PyResult<Self> {
1,172✔
252
        let mut state = State {
1,172✔
253
            tokens: vec![tokens],
1,172✔
254
            context: TokenContext::new(),
1,172✔
255
            include_path,
1,172✔
256
            // For Qiskit-created circuits, all files will have the builtin gates and `qelib1.inc`,
1,172✔
257
            // so we allocate with that in mind.  There may well be overlap between libraries and
1,172✔
258
            // custom instructions, but this is small-potatoes allocation and we'd rather not have
1,172✔
259
            // to reallocate.
1,172✔
260
            symbols: HashMap::with_capacity(
1,172✔
261
                N_BUILTIN_GATES + QELIB1.len() + custom_instructions.len() + custom_classical.len(),
1,172✔
262
            ),
1,172✔
263
            gate_symbols: HashMap::new(),
1,172✔
264
            overridable_gates: HashMap::new(),
1,172✔
265
            num_qubits: 0,
1,172✔
266
            num_clbits: 0,
1,172✔
267
            num_cregs: 0,
1,172✔
268
            num_gates: 0,
1,172✔
269
            allow_version: true,
1,172✔
270
            strict,
1,172✔
271
        };
1,172✔
272
        for inst in custom_instructions {
7,128✔
273
            if state.symbols.contains_key(&inst.name)
7,128✔
274
                || state.overridable_gates.contains_key(&inst.name)
7,128✔
275
            {
276
                return Err(QASM2ParseError::new_err(message_generic(
2✔
277
                    None,
2✔
278
                    &format!("duplicate custom instruction '{}'", inst.name),
2✔
279
                )));
2✔
280
            }
7,126✔
281
            state.overridable_gates.insert(
7,126✔
282
                inst.name.clone(),
7,126✔
283
                OverridableGate {
7,126✔
284
                    num_params: inst.num_params,
7,126✔
285
                    num_qubits: inst.num_qubits,
7,126✔
286
                    index: GateId::new(state.num_gates),
7,126✔
287
                },
7,126✔
288
            );
289
            if inst.builtin {
7,126✔
290
                state.symbols.insert(
3,154✔
291
                    inst.name.clone(),
3,154✔
292
                    GlobalSymbol::Gate {
3,154✔
293
                        num_params: inst.num_params,
3,154✔
294
                        num_qubits: inst.num_qubits,
3,154✔
295
                        index: GateId::new(state.num_gates),
3,154✔
296
                    },
3,154✔
297
                );
3,154✔
298
            }
3,972✔
299
            state.num_gates += 1;
7,126✔
300
        }
301
        state.define_gate(None, "U".to_owned(), 3, 1)?;
1,170✔
302
        state.define_gate(None, "CX".to_owned(), 0, 2)?;
1,168✔
303
        for classical in custom_classical {
1,166✔
304
            if BUILTIN_CLASSICAL.contains(&&*classical.name) {
508✔
305
                return Err(QASM2ParseError::new_err(message_generic(
12✔
306
                    None,
12✔
307
                    &format!(
12✔
308
                        "cannot override builtin classical function '{}'",
12✔
309
                        &classical.name
12✔
310
                    ),
12✔
311
                )));
12✔
312
            }
496✔
313
            match state.symbols.insert(
496✔
314
                classical.name.clone(),
496✔
315
                GlobalSymbol::Classical {
496✔
316
                    num_params: classical.num_params,
496✔
317
                    callable: classical.callable.clone(),
496✔
318
                },
496✔
319
            ) {
320
                Some(GlobalSymbol::Gate { .. }) => {
321
                    let message = match classical.name.as_str() {
4✔
322
                        "U" | "CX" => format!(
4✔
323
                            "custom classical instructions cannot shadow built-in gates, but got '{}'",
324
                            &classical.name,
2✔
325
                        ),
326
                        _ => format!(
2✔
327
                            "custom classical instruction '{}' has a naming clash with a custom gate",
328
                            &classical.name,
2✔
329
                        ),
330
                    };
331
                    return Err(QASM2ParseError::new_err(message_generic(None, &message)));
4✔
332
                }
333
                Some(GlobalSymbol::Classical { .. }) => {
334
                    return Err(QASM2ParseError::new_err(message_generic(
2✔
335
                        None,
2✔
336
                        &format!("duplicate custom classical function '{}'", &classical.name,),
2✔
337
                    )));
2✔
338
                }
339
                _ => (),
490✔
340
            }
341
        }
342
        Ok(state)
1,148✔
343
    }
1,172✔
344

345
    /// Get the next token available in the stack of token streams, popping and removing any
346
    /// complete streams, except the base case.  Will only return `None` once all streams are
347
    /// exhausted.
348
    fn next_token(&mut self) -> PyResult<Option<Token>> {
46,692✔
349
        let mut pointer = self.tokens.len() - 1;
46,692✔
350
        while pointer > 0 {
46,694✔
351
            let out = self.tokens[pointer].next(&mut self.context)?;
140✔
352
            if out.is_some() {
140✔
353
                return Ok(out);
138✔
354
            }
2✔
355
            self.tokens.pop();
2✔
356
            pointer -= 1;
2✔
357
        }
358
        self.tokens[0].next(&mut self.context)
46,554✔
359
    }
46,692✔
360

361
    /// Peek the next token in the stack of token streams.  This does not remove any complete
362
    /// streams yet.  Will only return `None` once all streams are exhausted.
363
    fn peek_token(&mut self) -> PyResult<Option<&Token>> {
30,974✔
364
        let mut pointer = self.tokens.len() - 1;
30,974✔
365
        while pointer > 0 && self.tokens[pointer].peek(&mut self.context)?.is_none() {
30,992✔
366
            pointer -= 1;
18✔
367
        }
18✔
368
        self.tokens[pointer].peek(&mut self.context)
30,974✔
369
    }
30,974✔
370

371
    /// Get the filename associated with the currently active token stream.
372
    fn current_filename(&self) -> &std::ffi::OsStr {
494✔
373
        &self.tokens[self.tokens.len() - 1].filename
494✔
374
    }
494✔
375

376
    /// Take a token from the stream that is known to be present and correct, generally because it
377
    /// has already been peeked.  Panics if the token type is not correct.
378
    fn expect_known(&mut self, expected: TokenType) -> Token {
7,024✔
379
        let out = self.next_token().unwrap().unwrap();
7,024✔
380
        if out.ttype != expected {
7,024✔
381
            panic!(
×
382
                "expected '{}' but got '{}'",
383
                expected.describe(),
×
384
                out.ttype.describe()
×
385
            )
386
        }
7,024✔
387
        out
7,024✔
388
    }
7,024✔
389

390
    /// Take the next token from the stream, expecting that it is of a particular type because it
391
    /// is required to be in order for the input program to be valid OpenQASM 2.  This returns the
392
    /// token if successful, and a suitable error message if the token type is incorrect, or the
393
    /// end of the file is reached.
394
    fn expect(&mut self, expected: TokenType, required: &str, cause: &Token) -> PyResult<Token> {
25,852✔
395
        let token = match self.next_token()? {
25,852✔
396
            None => {
397
                return Err(QASM2ParseError::new_err(message_bad_eof(
114✔
398
                    Some(&Position::new(
114✔
399
                        self.current_filename(),
114✔
400
                        cause.line,
114✔
401
                        cause.col,
114✔
402
                    )),
114✔
403
                    required,
114✔
404
                )));
114✔
405
            }
406
            Some(token) => token,
25,720✔
407
        };
408
        if token.ttype == expected {
25,720✔
409
            Ok(token)
25,658✔
410
        } else {
411
            Err(QASM2ParseError::new_err(message_incorrect_requirement(
62✔
412
                required,
62✔
413
                &token,
62✔
414
                self.current_filename(),
62✔
415
            )))
62✔
416
        }
417
    }
25,852✔
418

419
    /// Take the next token from the stream, if it is of the correct type.  Returns `None` and
420
    /// leaves the next token in the underlying iterator if it does not match.
421
    fn accept(&mut self, expected: TokenType) -> PyResult<Option<Token>> {
20,460✔
422
        let peeked = self.peek_token()?;
20,460✔
423
        if peeked.is_some() && peeked.unwrap().ttype == expected {
20,460✔
424
            self.next_token()
13,784✔
425
        } else {
426
            Ok(None)
6,676✔
427
        }
428
    }
20,460✔
429

430
    /// True if the next token in the stream matches the given type, and false if it doesn't.
431
    fn next_is(&mut self, expected: TokenType) -> PyResult<bool> {
1,996✔
432
        let peeked = self.peek_token()?;
1,996✔
433
        Ok(peeked.is_some() && peeked.unwrap().ttype == expected)
1,992✔
434
    }
1,996✔
435

436
    /// If in `strict` mode, and we have a trailing comma, emit a suitable error message.
437
    fn check_trailing_comma(&self, comma: Option<&Token>) -> PyResult<()> {
4,044✔
438
        match (self.strict, comma) {
4,044✔
439
            (true, Some(token)) => Err(QASM2ParseError::new_err(message_generic(
14✔
440
                Some(&Position::new(
14✔
441
                    self.current_filename(),
14✔
442
                    token.line,
14✔
443
                    token.col,
14✔
444
                )),
14✔
445
                "[strict] trailing commas in parameter and qubit lists are forbidden",
14✔
446
            ))),
14✔
447
            _ => Ok(()),
4,030✔
448
        }
449
    }
4,044✔
450

451
    /// Take a complete quantum argument from the token stream, if the next token is an identifier.
452
    /// This includes resolving any following subscript operation.  Returns an error variant if the
453
    /// next token _is_ an identifier, but the symbol represents something other than a quantum
454
    /// register, or isn't defined.  This can also be an error if the subscript is opened, but
455
    /// cannot be completely resolved due to a typing error or other invalid parse.  `Ok(None)` is
456
    /// returned if the next token in the stream does not match a possible quantum argument.
457
    fn accept_qarg(&mut self) -> PyResult<Option<Operand<QubitId>>> {
4,314✔
458
        let (name, name_token) = match self.accept(TokenType::Id)? {
4,314✔
459
            None => return Ok(None),
28✔
460
            Some(token) => (token.id(&self.context), token),
4,286✔
461
        };
462
        let (register_size, register_start) = match self.symbols.get(&name) {
4,286✔
463
            Some(GlobalSymbol::Qreg { size, start }) => (*size, *start),
4,264✔
464
            Some(symbol) => {
18✔
465
                return Err(QASM2ParseError::new_err(message_generic(
18✔
466
                    Some(&Position::new(
18✔
467
                        self.current_filename(),
18✔
468
                        name_token.line,
18✔
469
                        name_token.col,
18✔
470
                    )),
18✔
471
                    &format!(
18✔
472
                        "'{}' is {}, not a quantum register",
18✔
473
                        name,
18✔
474
                        symbol.describe()
18✔
475
                    ),
18✔
476
                )));
18✔
477
            }
478
            None => {
479
                return Err(QASM2ParseError::new_err(message_generic(
4✔
480
                    Some(&Position::new(
4✔
481
                        self.current_filename(),
4✔
482
                        name_token.line,
4✔
483
                        name_token.col,
4✔
484
                    )),
4✔
485
                    &format!("'{name}' is not defined in this scope"),
4✔
486
                )));
4✔
487
            }
488
        };
489
        self.complete_operand(&name, register_size, register_start)
4,264✔
490
            .map(Some)
4,264✔
491
    }
4,314✔
492

493
    /// Take a complete quantum argument from the stream, if it matches.  This is for use within
494
    /// gates, and so the only valid type of quantum argument is a single qubit.
495
    fn accept_qarg_gate(&mut self) -> PyResult<Option<Operand<QubitId>>> {
468✔
496
        let (name, name_token) = match self.accept(TokenType::Id)? {
468✔
497
            None => return Ok(None),
×
498
            Some(token) => (token.id(&self.context), token),
468✔
499
        };
500
        match self.gate_symbols.get(&name) {
468✔
501
            Some(GateSymbol::Qubit { index }) => Ok(Some(Operand::Single(*index))),
456✔
502
            Some(GateSymbol::Parameter { .. }) => Err(QASM2ParseError::new_err(message_generic(
2✔
503
                Some(&Position::new(
2✔
504
                    self.current_filename(),
2✔
505
                    name_token.line,
2✔
506
                    name_token.col,
2✔
507
                )),
2✔
508
                &format!("'{name}' is a parameter, not a qubit"),
2✔
509
            ))),
2✔
510
            None => match self.symbols.get(&name) {
10✔
511
                Some(symbol) => Err(QASM2ParseError::new_err(message_generic(
8✔
512
                    Some(&Position::new(
8✔
513
                        self.current_filename(),
8✔
514
                        name_token.line,
8✔
515
                        name_token.col,
8✔
516
                    )),
8✔
517
                    &format!("'{}' is {}, not a qubit", name, symbol.describe()),
8✔
518
                ))),
8✔
519
                _ => Err(QASM2ParseError::new_err(message_generic(
2✔
520
                    Some(&Position::new(
2✔
521
                        self.current_filename(),
2✔
522
                        name_token.line,
2✔
523
                        name_token.col,
2✔
524
                    )),
2✔
525
                    &format!("'{name}' is not defined in this scope"),
2✔
526
                ))),
2✔
527
            },
528
        }
529
    }
468✔
530

531
    /// Take a complete quantum argument from the token stream, returning an error message if one
532
    /// is not present.
533
    fn require_qarg(&mut self, instruction: &Token) -> PyResult<Operand<QubitId>> {
466✔
534
        match self.peek_token()?.map(|tok| tok.ttype) {
466✔
535
            Some(TokenType::Id) => self.accept_qarg().map(Option::unwrap),
462✔
536
            Some(_) => {
537
                let token = self.next_token()?;
×
538
                Err(QASM2ParseError::new_err(message_incorrect_requirement(
×
539
                    "a quantum argument",
×
540
                    &token.unwrap(),
×
541
                    self.current_filename(),
×
542
                )))
×
543
            }
544
            None => Err(QASM2ParseError::new_err(message_bad_eof(
4✔
545
                Some(&Position::new(
4✔
546
                    self.current_filename(),
4✔
547
                    instruction.line,
4✔
548
                    instruction.col,
4✔
549
                )),
4✔
550
                "a quantum argument",
4✔
551
            ))),
4✔
552
        }
553
    }
466✔
554

555
    /// Take a complete classical argument from the token stream, if the next token is an
556
    /// identifier.  This includes resolving any following subscript operation.  Returns an error
557
    /// variant if the next token _is_ an identifier, but the symbol represents something other
558
    /// than a classical register, or isn't defined.  This can also be an error if the subscript is
559
    /// opened, but cannot be completely resolved due to a typing error or other invalid parse.
560
    /// `Ok(None)` is returned if the next token in the stream does not match a possible classical
561
    /// argument.
562
    fn accept_carg(&mut self) -> PyResult<Option<Operand<ClbitId>>> {
408✔
563
        let (name, name_token) = match self.accept(TokenType::Id)? {
408✔
564
            None => return Ok(None),
×
565
            Some(token) => (token.id(&self.context), token),
408✔
566
        };
567
        let (register_size, register_start) = match self.symbols.get(&name) {
408✔
568
            Some(GlobalSymbol::Creg { size, start, .. }) => (*size, *start),
400✔
569
            Some(symbol) => {
6✔
570
                return Err(QASM2ParseError::new_err(message_generic(
6✔
571
                    Some(&Position::new(
6✔
572
                        self.current_filename(),
6✔
573
                        name_token.line,
6✔
574
                        name_token.col,
6✔
575
                    )),
6✔
576
                    &format!(
6✔
577
                        "'{}' is {}, not a classical register",
6✔
578
                        name,
6✔
579
                        symbol.describe()
6✔
580
                    ),
6✔
581
                )));
6✔
582
            }
583
            None => {
584
                return Err(QASM2ParseError::new_err(message_generic(
2✔
585
                    Some(&Position::new(
2✔
586
                        self.current_filename(),
2✔
587
                        name_token.line,
2✔
588
                        name_token.col,
2✔
589
                    )),
2✔
590
                    &format!("'{name}' is not defined in this scope"),
2✔
591
                )));
2✔
592
            }
593
        };
594
        self.complete_operand(&name, register_size, register_start)
400✔
595
            .map(Some)
400✔
596
    }
408✔
597

598
    /// Take a complete classical argument from the token stream, returning an error message if one
599
    /// is not present.
600
    fn require_carg(&mut self, instruction: &Token) -> PyResult<Operand<ClbitId>> {
410✔
601
        match self.peek_token()?.map(|tok| tok.ttype) {
410✔
602
            Some(TokenType::Id) => self.accept_carg().map(Option::unwrap),
408✔
603
            Some(_) => {
604
                let token = self.next_token()?;
×
605
                Err(QASM2ParseError::new_err(message_incorrect_requirement(
×
606
                    "a classical argument",
×
607
                    &token.unwrap(),
×
608
                    self.current_filename(),
×
609
                )))
×
610
            }
611
            None => Err(QASM2ParseError::new_err(message_bad_eof(
2✔
612
                Some(&Position::new(
2✔
613
                    self.current_filename(),
2✔
614
                    instruction.line,
2✔
615
                    instruction.col,
2✔
616
                )),
2✔
617
                "a classical argument",
2✔
618
            ))),
2✔
619
        }
620
    }
410✔
621

622
    /// Evaluate a possible subscript on a register into a final [Operand], consuming the tokens
623
    /// (if present) from the stream.  Can return error variants if the subscript cannot be
624
    /// completed or if there is a parse error while reading the subscript.
625
    fn complete_operand<T>(
4,664✔
626
        &mut self,
4,664✔
627
        name: &str,
4,664✔
628
        register_size: usize,
4,664✔
629
        register_start: T,
4,664✔
630
    ) -> PyResult<Operand<T>>
4,664✔
631
    where
4,664✔
632
        T: std::ops::Add<usize, Output = T>,
4,664✔
633
    {
634
        let lbracket_token = match self.accept(TokenType::LBracket)? {
4,664✔
635
            Some(token) => token,
3,826✔
636
            None => return Ok(Operand::Range(register_size, register_start)),
838✔
637
        };
638
        let index_token = self.expect(TokenType::Integer, "an integer index", &lbracket_token)?;
3,826✔
639
        let index = index_token.int(&self.context);
3,816✔
640
        self.expect(TokenType::RBracket, "a closing bracket", &lbracket_token)?;
3,816✔
641
        if index < register_size {
3,804✔
642
            Ok(Operand::Single(register_start + index))
3,798✔
643
        } else {
644
            Err(QASM2ParseError::new_err(message_generic(
6✔
645
                Some(&Position::new(
6✔
646
                    self.current_filename(),
6✔
647
                    index_token.line,
6✔
648
                    index_token.col,
6✔
649
                )),
6✔
650
                &format!(
6✔
651
                    "index {index} is out-of-range for register '{name}' of size {register_size}"
6✔
652
                ),
6✔
653
            )))
6✔
654
        }
655
    }
4,664✔
656

657
    /// Parse an `OPENQASM <version>;` statement completely.  This function does not need to take
658
    /// the bytecode stream because the version information has no actionable effects for Qiskit
659
    /// to care about.  We simply error if the version supplied by the file is not the version of
660
    /// OpenQASM that we are able to support.  This assumes that the `OPENQASM` token is still in
661
    /// the stream.
662
    fn parse_version(&mut self) -> PyResult<usize> {
504✔
663
        let openqasm_token = self.expect_known(TokenType::OpenQASM);
504✔
664
        let version_token = self.expect(TokenType::Version, "version number", &openqasm_token)?;
504✔
665
        match version_token.version(&self.context) {
490✔
666
            Version {
667
                major: 2,
668
                minor: Some(0) | None,
669
            } => Ok(()),
486✔
670
            _ => Err(QASM2ParseError::new_err(message_generic(
4✔
671
                Some(&Position::new(
4✔
672
                    self.current_filename(),
4✔
673
                    version_token.line,
4✔
674
                    version_token.col,
4✔
675
                )),
4✔
676
                &format!(
4✔
677
                    "can only handle OpenQASM 2.0, but given {}",
4✔
678
                    version_token.text(&self.context),
4✔
679
                ),
4✔
680
            ))),
4✔
681
        }?;
4✔
682
        self.expect(TokenType::Semicolon, ";", &openqasm_token)?;
486✔
683
        Ok(0)
482✔
684
    }
504✔
685

686
    /// Parse a complete gate definition (including the body of the definition).  This assumes that
687
    /// the `gate` token is still in the scheme.  This function will likely result in many
688
    /// instructions being pushed onto the bytecode stream; one for the start and end of the gate
689
    /// definition, and then one instruction each for the gate applications in the body.
690
    fn parse_gate_definition(&mut self, bc: &mut Vec<Option<InternalBytecode>>) -> PyResult<usize> {
292✔
691
        let gate_token = self.expect_known(TokenType::Gate);
292✔
692
        let name_token = self.expect(TokenType::Id, "an identifier", &gate_token)?;
292✔
693
        let name = name_token.id(&self.context);
290✔
694
        // Parse the gate parameters (if any) into the symbol take.
695
        let mut num_params = 0usize;
290✔
696
        if let Some(lparen_token) = self.accept(TokenType::LParen)? {
290✔
697
            let mut comma = None;
152✔
698
            while let Some(param_token) = self.accept(TokenType::Id)? {
264✔
699
                let param_name = param_token.id(&self.context);
248✔
700
                if let Some(symbol) = self.gate_symbols.insert(
248✔
701
                    param_name.to_owned(),
248✔
702
                    GateSymbol::Parameter {
248✔
703
                        index: ParamId::new(num_params),
248✔
704
                    },
248✔
705
                ) {
248✔
706
                    return Err(QASM2ParseError::new_err(message_generic(
2✔
707
                        Some(&Position::new(
2✔
708
                            self.current_filename(),
2✔
709
                            param_token.line,
2✔
710
                            param_token.col,
2✔
711
                        )),
2✔
712
                        &format!(
2✔
713
                            "'{}' is already defined as {}",
2✔
714
                            param_name,
2✔
715
                            symbol.describe()
2✔
716
                        ),
2✔
717
                    )));
2✔
718
                }
246✔
719
                num_params += 1;
246✔
720
                comma = self.accept(TokenType::Comma)?;
246✔
721
                if comma.is_none() {
246✔
722
                    break;
134✔
723
                }
112✔
724
            }
725
            self.check_trailing_comma(comma.as_ref())?;
150✔
726
            self.expect(TokenType::RParen, "a closing parenthesis", &lparen_token)?;
148✔
727
        }
138✔
728
        // Parse the quantum parameters into the symbol table.
729
        let mut num_qubits = 0usize;
272✔
730
        let mut comma = None;
272✔
731
        while let Some(qubit_token) = self.accept(TokenType::Id)? {
402✔
732
            let qubit_name = qubit_token.id(&self.context).to_owned();
388✔
733
            if let Some(symbol) = self.gate_symbols.insert(
388✔
734
                qubit_name.to_owned(),
388✔
735
                GateSymbol::Qubit {
388✔
736
                    index: QubitId::new(num_qubits),
388✔
737
                },
388✔
738
            ) {
388✔
739
                return Err(QASM2ParseError::new_err(message_generic(
4✔
740
                    Some(&Position::new(
4✔
741
                        self.current_filename(),
4✔
742
                        qubit_token.line,
4✔
743
                        qubit_token.col,
4✔
744
                    )),
4✔
745
                    &format!(
4✔
746
                        "'{}' is already defined as {}",
4✔
747
                        qubit_name,
4✔
748
                        symbol.describe()
4✔
749
                    ),
4✔
750
                )));
4✔
751
            }
384✔
752
            num_qubits += 1;
384✔
753
            comma = self.accept(TokenType::Comma)?;
384✔
754
            if comma.is_none() {
384✔
755
                break;
254✔
756
            }
130✔
757
        }
758
        self.check_trailing_comma(comma.as_ref())?;
268✔
759
        if num_qubits == 0 {
266✔
760
            let eof = self.peek_token()?.is_none();
8✔
761
            let position = Position::new(self.current_filename(), gate_token.line, gate_token.col);
8✔
762
            return if eof {
8✔
763
                Err(QASM2ParseError::new_err(message_bad_eof(
4✔
764
                    Some(&position),
4✔
765
                    "a qubit identifier",
4✔
766
                )))
4✔
767
            } else {
768
                Err(QASM2ParseError::new_err(message_generic(
4✔
769
                    Some(&position),
4✔
770
                    "gates must act on at least one qubit",
4✔
771
                )))
4✔
772
            };
773
        }
258✔
774
        let lbrace_token = self.expect(TokenType::LBrace, "a gate body", &gate_token)?;
258✔
775
        bc.push(Some(InternalBytecode::DeclareGate {
252✔
776
            name: name.clone(),
252✔
777
            num_qubits,
252✔
778
        }));
252✔
779
        // The actual body of the gate.  Most of this is devolved to [Self::parse_gate_application]
780
        // to do the right thing.
781
        let mut statements = 0usize;
252✔
782
        loop {
783
            match self.peek_token()?.map(|tok| tok.ttype) {
564✔
784
                Some(TokenType::Id) => statements += self.parse_gate_application(bc, None, true)?,
336✔
785
                Some(TokenType::Barrier) => {
786
                    statements += self.parse_barrier(bc, Some(num_qubits))?
12✔
787
                }
788
                Some(TokenType::RBrace) => {
789
                    self.expect_known(TokenType::RBrace);
206✔
790
                    break;
206✔
791
                }
792
                Some(_) => {
793
                    let token = self.next_token()?.unwrap();
8✔
794
                    return Err(QASM2ParseError::new_err(message_generic(
8✔
795
                        Some(&Position::new(
8✔
796
                            self.current_filename(),
8✔
797
                            token.line,
8✔
798
                            token.col,
8✔
799
                        )),
8✔
800
                        &format!(
8✔
801
                            "only gate applications are valid within a 'gate' body, but saw {}",
8✔
802
                            token.text(&self.context)
8✔
803
                        ),
8✔
804
                    )));
8✔
805
                }
806
                None => {
807
                    return Err(QASM2ParseError::new_err(message_bad_eof(
2✔
808
                        Some(&Position::new(
2✔
809
                            self.current_filename(),
2✔
810
                            lbrace_token.line,
2✔
811
                            lbrace_token.col,
2✔
812
                        )),
2✔
813
                        "a closing brace '}' of the gate body",
2✔
814
                    )));
2✔
815
                }
816
            }
817
        }
818
        bc.push(Some(InternalBytecode::EndDeclareGate {}));
206✔
819
        self.gate_symbols.clear();
206✔
820
        let num_bytecode = statements + 2;
206✔
821
        if self.define_gate(Some(&gate_token), name, num_params, num_qubits)? {
206✔
822
            Ok(num_bytecode)
172✔
823
        } else {
824
            // The gate was built-in, so we don't actually need to emit the bytecode.  This is
825
            // uncommon, so it doesn't matter too much that we throw away allocation work we did -
826
            // it still helps that we verified that the gate body was valid OpenQASM 2.
827
            bc.truncate(bc.len() - num_bytecode);
10✔
828
            Ok(0)
10✔
829
        }
830
    }
292✔
831

832
    /// Parse an `opaque` statement.  This assumes that the `opaque` token is still in the token
833
    /// stream we are reading from.
834
    fn parse_opaque_definition(
90✔
835
        &mut self,
90✔
836
        bc: &mut Vec<Option<InternalBytecode>>,
90✔
837
    ) -> PyResult<usize> {
90✔
838
        let opaque_token = self.expect_known(TokenType::Opaque);
90✔
839
        let name = self
90✔
840
            .expect(TokenType::Id, "an identifier", &opaque_token)?
90✔
841
            .text(&self.context)
88✔
842
            .to_owned();
88✔
843
        let mut num_params = 0usize;
88✔
844
        if let Some(lparen_token) = self.accept(TokenType::LParen)? {
88✔
845
            let mut comma = None;
74✔
846
            while self.accept(TokenType::Id)?.is_some() {
122✔
847
                num_params += 1;
106✔
848
                comma = self.accept(TokenType::Comma)?;
106✔
849
                if comma.is_none() {
106✔
850
                    break;
58✔
851
                }
48✔
852
            }
853
            self.check_trailing_comma(comma.as_ref())?;
74✔
854
            self.expect(TokenType::RParen, "closing parenthesis", &lparen_token)?;
72✔
855
        }
14✔
856
        let mut num_qubits = 0usize;
70✔
857
        let mut comma = None;
70✔
858
        while self.accept(TokenType::Id)?.is_some() {
96✔
859
            num_qubits += 1;
76✔
860
            comma = self.accept(TokenType::Comma)?;
76✔
861
            if comma.is_none() {
76✔
862
                break;
50✔
863
            }
26✔
864
        }
865
        self.check_trailing_comma(comma.as_ref())?;
70✔
866
        self.expect(TokenType::Semicolon, ";", &opaque_token)?;
68✔
867
        if num_qubits == 0 {
48✔
868
            return Err(QASM2ParseError::new_err(message_generic(
4✔
869
                Some(&Position::new(
4✔
870
                    self.current_filename(),
4✔
871
                    opaque_token.line,
4✔
872
                    opaque_token.col,
4✔
873
                )),
4✔
874
                "gates must act on at least one qubit",
4✔
875
            )));
4✔
876
        }
44✔
877
        bc.push(Some(InternalBytecode::DeclareOpaque {
44✔
878
            name: name.clone(),
44✔
879
            num_qubits,
44✔
880
        }));
44✔
881
        self.define_gate(Some(&opaque_token), name, num_params, num_qubits)?;
44✔
882
        Ok(1)
36✔
883
    }
90✔
884

885
    /// Parse a gate application into the bytecode stream.  This resolves any broadcast over
886
    /// registers into a series of bytecode instructions, rather than leaving Qiskit to do it,
887
    /// which would involve much slower Python execution.  This assumes that the identifier token
888
    /// is still in the token stream.
889
    fn parse_gate_application(
2,630✔
890
        &mut self,
2,630✔
891
        bc: &mut Vec<Option<InternalBytecode>>,
2,630✔
892
        condition: Option<Condition>,
2,630✔
893
        in_gate: bool,
2,630✔
894
    ) -> PyResult<usize> {
2,630✔
895
        let name_token = self.expect_known(TokenType::Id);
2,630✔
896
        let name = name_token.id(&self.context);
2,630✔
897
        let (index, num_params, num_qubits) = match self.symbols.get(&name) {
2,630✔
898
            Some(GlobalSymbol::Gate {
899
                num_params,
2,616✔
900
                num_qubits,
2,616✔
901
                index,
2,616✔
902
            }) => Ok((*index, *num_params, *num_qubits)),
2,616✔
903
            Some(symbol) => Err(QASM2ParseError::new_err(message_generic(
6✔
904
                Some(&Position::new(
6✔
905
                    self.current_filename(),
6✔
906
                    name_token.line,
6✔
907
                    name_token.col,
6✔
908
                )),
6✔
909
                &format!("'{}' is {}, not a gate", name, symbol.describe()),
6✔
910
            ))),
6✔
911
            None => {
912
                let pos = Position::new(self.current_filename(), name_token.line, name_token.col);
8✔
913
                let message = if self.overridable_gates.contains_key(&name) {
8✔
914
                    format!("cannot use non-builtin custom instruction '{name}' before definition",)
2✔
915
                } else {
916
                    format!("'{name}' is not defined in this scope")
6✔
917
                };
918
                Err(QASM2ParseError::new_err(message_generic(
8✔
919
                    Some(&pos),
8✔
920
                    &message,
8✔
921
                )))
8✔
922
            }
923
        }?;
14✔
924
        let parameters = self.expect_gate_parameters(&name_token, num_params, in_gate)?;
2,616✔
925
        let mut qargs = Vec::<Operand<QubitId>>::with_capacity(num_qubits);
2,504✔
926
        let mut comma = None;
2,504✔
927
        if in_gate {
2,504✔
928
            while let Some(qarg) = self.accept_qarg_gate()? {
450✔
929
                qargs.push(qarg);
438✔
930
                comma = self.accept(TokenType::Comma)?;
438✔
931
                if comma.is_none() {
438✔
932
                    break;
302✔
933
                }
136✔
934
            }
935
        } else {
936
            while let Some(qarg) = self.accept_qarg()? {
3,554✔
937
                qargs.push(qarg);
3,512✔
938
                comma = self.accept(TokenType::Comma)?;
3,512✔
939
                if comma.is_none() {
3,512✔
940
                    break;
2,148✔
941
                }
1,364✔
942
            }
943
        }
944
        self.check_trailing_comma(comma.as_ref())?;
2,472✔
945
        if qargs.len() != num_qubits {
2,470✔
946
            return match self.peek_token()?.map(|tok| tok.ttype) {
38✔
947
                Some(TokenType::Semicolon) => Err(QASM2ParseError::new_err(message_generic(
16✔
948
                    Some(&Position::new(
16✔
949
                        self.current_filename(),
16✔
950
                        name_token.line,
16✔
951
                        name_token.col,
16✔
952
                    )),
16✔
953
                    &format!(
16✔
954
                        "'{}' takes {} quantum argument{}, but got {}",
955
                        name,
956
                        num_qubits,
957
                        if num_qubits == 1 { "" } else { "s" },
16✔
958
                        qargs.len()
16✔
959
                    ),
960
                ))),
961
                Some(_) => Err(QASM2ParseError::new_err(message_incorrect_requirement(
12✔
962
                    "the end of the argument list",
12✔
963
                    &name_token,
12✔
964
                    self.current_filename(),
12✔
965
                ))),
12✔
966
                None => Err(QASM2ParseError::new_err(message_bad_eof(
10✔
967
                    Some(&Position::new(
10✔
968
                        self.current_filename(),
10✔
969
                        name_token.line,
10✔
970
                        name_token.col,
10✔
971
                    )),
10✔
972
                    "the end of the argument list",
10✔
973
                ))),
10✔
974
            };
975
        }
2,432✔
976
        self.expect(TokenType::Semicolon, "';'", &name_token)?;
2,432✔
977
        self.emit_gate_application(bc, &name_token, index, parameters, &qargs, condition)
2,428✔
978
    }
2,630✔
979

980
    /// Parse the parameters (if any) from a gate application.
981
    fn expect_gate_parameters(
2,616✔
982
        &mut self,
2,616✔
983
        name_token: &Token,
2,616✔
984
        num_params: usize,
2,616✔
985
        in_gate: bool,
2,616✔
986
    ) -> PyResult<GateParameters> {
2,616✔
987
        let lparen_token = match self.accept(TokenType::LParen)? {
2,616✔
988
            Some(lparen_token) => lparen_token,
934✔
989
            None => {
990
                return Ok(if in_gate {
1,682✔
991
                    GateParameters::Expression(vec![])
178✔
992
                } else {
993
                    GateParameters::Constant(vec![])
1,504✔
994
                });
995
            }
996
        };
997
        let mut seen_params = 0usize;
934✔
998
        let mut comma = None;
934✔
999
        // This code duplication is to avoid duplication of allocation when parsing the far more
1000
        // common case of expecting constant parameters for a gate application in the body of the
1001
        // OQ2 file.
1002
        let parameters = if in_gate {
934✔
1003
            let mut parameters = Vec::<Expr>::with_capacity(num_params);
156✔
1004
            while !self.next_is(TokenType::RParen)? {
310✔
1005
                let mut expr_parser = ExprParser {
310✔
1006
                    tokens: &mut self.tokens,
310✔
1007
                    context: &mut self.context,
310✔
1008
                    gate_symbols: &self.gate_symbols,
310✔
1009
                    global_symbols: &self.symbols,
310✔
1010
                    strict: self.strict,
310✔
1011
                };
310✔
1012
                parameters.push(expr_parser.parse_expression(&lparen_token)?);
310✔
1013
                seen_params += 1;
290✔
1014
                comma = self.accept(TokenType::Comma)?;
290✔
1015
                if comma.is_none() {
290✔
1016
                    break;
136✔
1017
                }
154✔
1018
            }
1019
            self.expect(TokenType::RParen, "')'", &lparen_token)?;
136✔
1020
            GateParameters::Expression(parameters)
136✔
1021
        } else {
1022
            let mut parameters = Vec::<f64>::with_capacity(num_params);
778✔
1023
            while !self.next_is(TokenType::RParen)? {
1,488✔
1024
                let mut expr_parser = ExprParser {
1,444✔
1025
                    tokens: &mut self.tokens,
1,444✔
1026
                    context: &mut self.context,
1,444✔
1027
                    gate_symbols: &self.gate_symbols,
1,444✔
1028
                    global_symbols: &self.symbols,
1,444✔
1029
                    strict: self.strict,
1,444✔
1030
                };
1,444✔
1031
                match expr_parser.parse_expression(&lparen_token)? {
1,444✔
1032
                    Expr::Constant(value) => parameters.push(value),
1,370✔
1033
                    _ => {
1034
                        return Err(QASM2ParseError::new_err(message_generic(
×
1035
                            Some(&Position::new(
×
1036
                                self.current_filename(),
×
1037
                                lparen_token.line,
×
1038
                                lparen_token.col,
×
1039
                            )),
×
1040
                            "non-constant expression in program body",
×
1041
                        )));
×
1042
                    }
1043
                }
1044
                seen_params += 1;
1,370✔
1045
                comma = self.accept(TokenType::Comma)?;
1,370✔
1046
                if comma.is_none() {
1,370✔
1047
                    break;
660✔
1048
                }
710✔
1049
            }
1050
            self.expect(TokenType::RParen, "')'", &lparen_token)?;
700✔
1051
            GateParameters::Constant(parameters)
700✔
1052
        };
1053
        self.check_trailing_comma(comma.as_ref())?;
836✔
1054
        if seen_params != num_params {
834✔
1055
            return Err(QASM2ParseError::new_err(message_generic(
12✔
1056
                Some(&Position::new(
12✔
1057
                    self.current_filename(),
12✔
1058
                    name_token.line,
12✔
1059
                    name_token.col,
12✔
1060
                )),
12✔
1061
                &format!(
12✔
1062
                    "'{}' takes {} parameter{}, but got {}",
1063
                    &name_token.text(&self.context),
12✔
1064
                    num_params,
1065
                    if num_params == 1 { "" } else { "s" },
12✔
1066
                    seen_params
1067
                ),
1068
            )));
1069
        }
822✔
1070
        Ok(parameters)
822✔
1071
    }
2,616✔
1072

1073
    /// Emit the bytecode for the application of a gate.  This involves resolving any broadcasts
1074
    /// in the operands of the gate (i.e. if one or more of them is a register).
1075
    fn emit_gate_application(
2,428✔
1076
        &self,
2,428✔
1077
        bc: &mut Vec<Option<InternalBytecode>>,
2,428✔
1078
        instruction: &Token,
2,428✔
1079
        gate_id: GateId,
2,428✔
1080
        parameters: GateParameters,
2,428✔
1081
        qargs: &[Operand<QubitId>],
2,428✔
1082
        condition: Option<Condition>,
2,428✔
1083
    ) -> PyResult<usize> {
2,428✔
1084
        // Fast path for most common gate patterns that don't need broadcasting.
1085
        if let Some(qubits) = match qargs {
1,922✔
1086
            [Operand::Single(index)] => Some(vec![*index]),
1,234✔
1087
            [Operand::Single(left), Operand::Single(right)] => {
692✔
1088
                if *left == *right {
692✔
1089
                    return Err(QASM2ParseError::new_err(message_generic(
4✔
1090
                        Some(&Position::new(
4✔
1091
                            self.current_filename(),
4✔
1092
                            instruction.line,
4✔
1093
                            instruction.col,
4✔
1094
                        )),
4✔
1095
                        "duplicate qubits in gate application",
4✔
1096
                    )));
4✔
1097
                }
688✔
1098
                Some(vec![*left, *right])
688✔
1099
            }
1100
            [] => Some(vec![]),
324✔
1101
            _ => None,
502✔
1102
        } {
1103
            return match parameters {
1,922✔
1104
                GateParameters::Constant(parameters) => {
1,632✔
1105
                    self.emit_single_global_gate(bc, gate_id, parameters, qubits, condition)
1,632✔
1106
                }
1107
                GateParameters::Expression(parameters) => {
290✔
1108
                    self.emit_single_gate_gate(bc, gate_id, parameters, qubits)
290✔
1109
                }
1110
            };
1111
        };
502✔
1112
        // If we're here we either have to broadcast or it's a 3+q gate - either way, we're not as
1113
        // worried about performance.
1114
        let mut qubits = HashSet::<QubitId>::with_capacity(qargs.len());
502✔
1115
        let mut broadcast_length = 0usize;
502✔
1116
        for qarg in qargs {
1,260✔
1117
            match qarg {
1,260✔
1118
                Operand::Single(index) => {
874✔
1119
                    if !qubits.insert(*index) {
874✔
1120
                        return Err(QASM2ParseError::new_err(message_generic(
12✔
1121
                            Some(&Position::new(
12✔
1122
                                self.current_filename(),
12✔
1123
                                instruction.line,
12✔
1124
                                instruction.col,
12✔
1125
                            )),
12✔
1126
                            "duplicate qubits in gate application",
12✔
1127
                        )));
12✔
1128
                    }
862✔
1129
                }
1130
                Operand::Range(size, start) => {
386✔
1131
                    if broadcast_length != 0 && broadcast_length != *size {
386✔
1132
                        return Err(QASM2ParseError::new_err(message_generic(
36✔
1133
                            Some(&Position::new(
36✔
1134
                                self.current_filename(),
36✔
1135
                                instruction.line,
36✔
1136
                                instruction.col,
36✔
1137
                            )),
36✔
1138
                            "cannot resolve broadcast in gate application",
36✔
1139
                        )));
36✔
1140
                    }
350✔
1141
                    for offset in 0..*size {
792✔
1142
                        if !qubits.insert(*start + offset) {
792✔
1143
                            return Err(QASM2ParseError::new_err(message_generic(
8✔
1144
                                Some(&Position::new(
8✔
1145
                                    self.current_filename(),
8✔
1146
                                    instruction.line,
8✔
1147
                                    instruction.col,
8✔
1148
                                )),
8✔
1149
                                "duplicate qubits in gate application",
8✔
1150
                            )));
8✔
1151
                        }
784✔
1152
                    }
1153
                    broadcast_length = *size;
342✔
1154
                }
1155
            }
1156
        }
1157
        if broadcast_length == 0 {
446✔
1158
            let qubits = qargs
294✔
1159
                .iter()
294✔
1160
                .filter_map(|qarg| {
886✔
1161
                    if let Operand::Single(index) = qarg {
886✔
1162
                        Some(*index)
818✔
1163
                    } else {
1164
                        None
68✔
1165
                    }
1166
                })
886✔
1167
                .collect::<Vec<_>>();
294✔
1168
            if qubits.len() < qargs.len() {
294✔
1169
                // We're broadcasting against at least one empty register.
1170
                return Ok(0);
44✔
1171
            }
250✔
1172
            return match parameters {
250✔
1173
                GateParameters::Constant(parameters) => {
240✔
1174
                    self.emit_single_global_gate(bc, gate_id, parameters, qubits, condition)
240✔
1175
                }
1176
                GateParameters::Expression(parameters) => {
10✔
1177
                    self.emit_single_gate_gate(bc, gate_id, parameters, qubits)
10✔
1178
                }
1179
            };
1180
        }
152✔
1181
        for i in 0..broadcast_length {
446✔
1182
            let qubits = qargs
446✔
1183
                .iter()
446✔
1184
                .map(|qarg| match qarg {
684✔
1185
                    Operand::Single(index) => *index,
48✔
1186
                    Operand::Range(_, start) => *start + i,
636✔
1187
                })
684✔
1188
                .collect::<Vec<_>>();
446✔
1189
            match parameters {
446✔
1190
                GateParameters::Constant(ref parameters) => {
446✔
1191
                    self.emit_single_global_gate(
446✔
1192
                        bc,
446✔
1193
                        gate_id,
446✔
1194
                        parameters.clone(),
446✔
1195
                        qubits,
446✔
1196
                        condition.clone(),
446✔
1197
                    )?;
×
1198
                }
1199
                // Gates used in gate-body definitions can't ever broadcast, because their only
1200
                // operands are single qubits.
1201
                _ => unreachable!(),
×
1202
            }
1203
        }
1204
        Ok(broadcast_length)
152✔
1205
    }
2,428✔
1206

1207
    /// Emit the bytecode for a single gate application in the global scope.  This could
1208
    /// potentially have a classical condition.
1209
    fn emit_single_global_gate(
2,318✔
1210
        &self,
2,318✔
1211
        bc: &mut Vec<Option<InternalBytecode>>,
2,318✔
1212
        gate_id: GateId,
2,318✔
1213
        arguments: Vec<f64>,
2,318✔
1214
        qubits: Vec<QubitId>,
2,318✔
1215
        condition: Option<Condition>,
2,318✔
1216
    ) -> PyResult<usize> {
2,318✔
1217
        if let Some(condition) = condition {
2,318✔
1218
            bc.push(Some(InternalBytecode::ConditionedGate {
114✔
1219
                id: gate_id,
114✔
1220
                arguments,
114✔
1221
                qubits,
114✔
1222
                creg: condition.creg,
114✔
1223
                value: condition.value,
114✔
1224
            }));
114✔
1225
        } else {
2,204✔
1226
            bc.push(Some(InternalBytecode::Gate {
2,204✔
1227
                id: gate_id,
2,204✔
1228
                arguments,
2,204✔
1229
                qubits,
2,204✔
1230
            }));
2,204✔
1231
        }
2,204✔
1232
        Ok(1)
2,318✔
1233
    }
2,318✔
1234

1235
    /// Emit the bytecode for a single gate application in a gate-definition body.  These are not
1236
    /// allowed to be conditioned, because otherwise the containing `gate` would not be unitary.
1237
    fn emit_single_gate_gate(
300✔
1238
        &self,
300✔
1239
        bc: &mut Vec<Option<InternalBytecode>>,
300✔
1240
        gate_id: GateId,
300✔
1241
        arguments: Vec<Expr>,
300✔
1242
        qubits: Vec<QubitId>,
300✔
1243
    ) -> PyResult<usize> {
300✔
1244
        bc.push(Some(InternalBytecode::GateInBody {
300✔
1245
            id: gate_id,
300✔
1246
            arguments,
300✔
1247
            qubits,
300✔
1248
        }));
300✔
1249
        Ok(1)
300✔
1250
    }
300✔
1251

1252
    /// Parse a complete conditional statement, including the operation that follows the condition
1253
    /// (though this work is delegated to the requisite other grammar rule).  This assumes that the
1254
    /// `if` token is still on the token stream.
1255
    fn parse_conditional(&mut self, bc: &mut Vec<Option<InternalBytecode>>) -> PyResult<usize> {
216✔
1256
        let if_token = self.expect_known(TokenType::If);
216✔
1257
        let lparen_token = self.expect(TokenType::LParen, "'('", &if_token)?;
216✔
1258
        let name_token = self.expect(TokenType::Id, "classical register", &if_token)?;
214✔
1259
        self.expect(TokenType::Equals, "'=='", &if_token)?;
212✔
1260
        let value = self
206✔
1261
            .expect(TokenType::Integer, "an integer", &if_token)?
206✔
1262
            .bigint(&self.context);
204✔
1263
        self.expect(TokenType::RParen, "')'", &lparen_token)?;
204✔
1264
        let name = name_token.id(&self.context);
200✔
1265
        let creg = match self.symbols.get(&name) {
200✔
1266
            Some(GlobalSymbol::Creg { index, .. }) => Ok(*index),
194✔
1267
            Some(symbol) => Err(QASM2ParseError::new_err(message_generic(
4✔
1268
                Some(&Position::new(
4✔
1269
                    self.current_filename(),
4✔
1270
                    name_token.line,
4✔
1271
                    name_token.col,
4✔
1272
                )),
4✔
1273
                &format!(
4✔
1274
                    "'{}' is {}, not a classical register",
4✔
1275
                    name,
4✔
1276
                    symbol.describe()
4✔
1277
                ),
4✔
1278
            ))),
4✔
1279
            None => Err(QASM2ParseError::new_err(message_generic(
2✔
1280
                Some(&Position::new(
2✔
1281
                    self.current_filename(),
2✔
1282
                    name_token.line,
2✔
1283
                    name_token.col,
2✔
1284
                )),
2✔
1285
                &format!("'{name}' is not defined in this scope"),
2✔
1286
            ))),
2✔
1287
        }?;
6✔
1288
        let condition = Some(Condition { creg, value });
194✔
1289
        match self.peek_token()?.map(|tok| tok.ttype) {
194✔
1290
            Some(TokenType::Id) => self.parse_gate_application(bc, condition, false),
160✔
1291
            Some(TokenType::Measure) => self.parse_measure(bc, condition),
20✔
1292
            Some(TokenType::Reset) => self.parse_reset(bc, condition),
10✔
1293
            Some(_) => {
1294
                let token = self.next_token()?;
2✔
1295
                Err(QASM2ParseError::new_err(message_incorrect_requirement(
2✔
1296
                    "a gate application, measurement or reset",
2✔
1297
                    &token.unwrap(),
2✔
1298
                    self.current_filename(),
2✔
1299
                )))
2✔
1300
            }
1301
            None => Err(QASM2ParseError::new_err(message_bad_eof(
2✔
1302
                Some(&Position::new(
2✔
1303
                    self.current_filename(),
2✔
1304
                    if_token.line,
2✔
1305
                    if_token.col,
2✔
1306
                )),
2✔
1307
                "a gate, measurement or reset to condition",
2✔
1308
            ))),
2✔
1309
        }
1310
    }
216✔
1311

1312
    /// Parse a barrier statement.  This assumes that the `barrier` token is still in the token
1313
    /// stream.
1314
    fn parse_barrier(
198✔
1315
        &mut self,
198✔
1316
        bc: &mut Vec<Option<InternalBytecode>>,
198✔
1317
        num_gate_qubits: Option<usize>,
198✔
1318
    ) -> PyResult<usize> {
198✔
1319
        let barrier_token = self.expect_known(TokenType::Barrier);
198✔
1320
        let qubits = if !self.next_is(TokenType::Semicolon)? {
198✔
1321
            let mut qubits = Vec::new();
178✔
1322
            let mut used = HashSet::<QubitId>::new();
178✔
1323
            let mut comma = None;
178✔
1324
            while let Some(qarg) = if num_gate_qubits.is_some() {
316✔
1325
                self.accept_qarg_gate()?
18✔
1326
            } else {
1327
                self.accept_qarg()?
298✔
1328
            } {
1329
                match qarg {
306✔
1330
                    Operand::Single(index) => {
176✔
1331
                        if used.insert(index) {
176✔
1332
                            qubits.push(index)
170✔
1333
                        }
6✔
1334
                    }
1335
                    Operand::Range(size, start) => qubits.extend((0..size).filter_map(|offset| {
366✔
1336
                        let index = start + offset;
366✔
1337
                        if used.insert(index) {
366✔
1338
                            Some(index)
352✔
1339
                        } else {
1340
                            None
14✔
1341
                        }
1342
                    })),
366✔
1343
                }
1344
                comma = self.accept(TokenType::Comma)?;
306✔
1345
                if comma.is_none() {
306✔
1346
                    break;
168✔
1347
                }
138✔
1348
            }
1349
            self.check_trailing_comma(comma.as_ref())?;
174✔
1350
            qubits
172✔
1351
        } else if self.strict {
20✔
1352
            return Err(QASM2ParseError::new_err(message_generic(
2✔
1353
                Some(&Position::new(
2✔
1354
                    self.current_filename(),
2✔
1355
                    barrier_token.line,
2✔
1356
                    barrier_token.col,
2✔
1357
                )),
2✔
1358
                "[strict] barrier statements must have at least one argument",
2✔
1359
            )));
2✔
1360
        } else if let Some(num_gate_qubits) = num_gate_qubits {
18✔
1361
            (0..num_gate_qubits).map(QubitId::new).collect::<Vec<_>>()
2✔
1362
        } else {
1363
            (0..self.num_qubits).map(QubitId::new).collect::<Vec<_>>()
16✔
1364
        };
1365
        self.expect(TokenType::Semicolon, "';'", &barrier_token)?;
190✔
1366
        if qubits.is_empty() {
184✔
1367
            Ok(0)
4✔
1368
        } else {
1369
            // The qubits are empty iff the only operands are zero-sized registers.  If there's no
1370
            // quantum arguments at all, then `qubits` will contain everything.
1371
            bc.push(Some(InternalBytecode::Barrier { qubits }));
180✔
1372
            Ok(1)
180✔
1373
        }
1374
    }
198✔
1375

1376
    /// Parse a measurement operation into bytecode.  This resolves any broadcast in the
1377
    /// measurement statement into a series of bytecode instructions, so we do more of the work in
1378
    /// Rust space than in Python space.
1379
    fn parse_measure(
430✔
1380
        &mut self,
430✔
1381
        bc: &mut Vec<Option<InternalBytecode>>,
430✔
1382
        condition: Option<Condition>,
430✔
1383
    ) -> PyResult<usize> {
430✔
1384
        let measure_token = self.expect_known(TokenType::Measure);
430✔
1385
        let qarg = self.require_qarg(&measure_token)?;
430✔
1386
        self.expect(TokenType::Arrow, "'->'", &measure_token)?;
418✔
1387
        let carg = self.require_carg(&measure_token)?;
410✔
1388
        self.expect(TokenType::Semicolon, "';'", &measure_token)?;
392✔
1389
        if let Some(Condition { creg, value }) = condition {
386✔
1390
            match (qarg, carg) {
20✔
1391
                (Operand::Single(qubit), Operand::Single(clbit)) => {
4✔
1392
                    bc.push(Some(InternalBytecode::ConditionedMeasure {
4✔
1393
                        qubit,
4✔
1394
                        clbit,
4✔
1395
                        creg,
4✔
1396
                        value,
4✔
1397
                    }));
4✔
1398
                    Ok(1)
4✔
1399
                }
1400
                (Operand::Range(q_size, q_start), Operand::Range(c_size, c_start))
6✔
1401
                    if q_size == c_size =>
8✔
1402
                {
1403
                    bc.extend((0..q_size).map(|i| {
8✔
1404
                        Some(InternalBytecode::ConditionedMeasure {
8✔
1405
                            qubit: q_start + i,
8✔
1406
                            clbit: c_start + i,
8✔
1407
                            creg,
8✔
1408
                            value: value.clone(),
8✔
1409
                        })
8✔
1410
                    }));
8✔
1411
                    Ok(q_size)
6✔
1412
                }
1413
                _ => Err(QASM2ParseError::new_err(message_generic(
10✔
1414
                    Some(&Position::new(
10✔
1415
                        self.current_filename(),
10✔
1416
                        measure_token.line,
10✔
1417
                        measure_token.col,
10✔
1418
                    )),
10✔
1419
                    "cannot resolve broadcast in measurement",
10✔
1420
                ))),
10✔
1421
            }
1422
        } else {
1423
            match (qarg, carg) {
366✔
1424
                (Operand::Single(qubit), Operand::Single(clbit)) => {
232✔
1425
                    bc.push(Some(InternalBytecode::Measure { qubit, clbit }));
232✔
1426
                    Ok(1)
232✔
1427
                }
1428
                (Operand::Range(q_size, q_start), Operand::Range(c_size, c_start))
124✔
1429
                    if q_size == c_size =>
126✔
1430
                {
1431
                    bc.extend((0..q_size).map(|i| {
358✔
1432
                        Some(InternalBytecode::Measure {
358✔
1433
                            qubit: q_start + i,
358✔
1434
                            clbit: c_start + i,
358✔
1435
                        })
358✔
1436
                    }));
358✔
1437
                    Ok(q_size)
124✔
1438
                }
1439
                _ => Err(QASM2ParseError::new_err(message_generic(
10✔
1440
                    Some(&Position::new(
10✔
1441
                        self.current_filename(),
10✔
1442
                        measure_token.line,
10✔
1443
                        measure_token.col,
10✔
1444
                    )),
10✔
1445
                    "cannot resolve broadcast in measurement",
10✔
1446
                ))),
10✔
1447
            }
1448
        }
1449
    }
430✔
1450

1451
    /// Parse a single reset statement.  This resolves any broadcast in the statement, i.e. if the
1452
    /// target is a register rather than a single qubit.  This assumes that the `reset` token is
1453
    /// still in the token stream.
1454
    fn parse_reset(
36✔
1455
        &mut self,
36✔
1456
        bc: &mut Vec<Option<InternalBytecode>>,
36✔
1457
        condition: Option<Condition>,
36✔
1458
    ) -> PyResult<usize> {
36✔
1459
        let reset_token = self.expect_known(TokenType::Reset);
36✔
1460
        let qarg = self.require_qarg(&reset_token)?;
36✔
1461
        self.expect(TokenType::Semicolon, "';'", &reset_token)?;
26✔
1462
        if let Some(Condition { creg, value }) = condition {
16✔
1463
            match qarg {
10✔
1464
                Operand::Single(qubit) => {
4✔
1465
                    bc.push(Some(InternalBytecode::ConditionedReset {
4✔
1466
                        qubit,
4✔
1467
                        creg,
4✔
1468
                        value,
4✔
1469
                    }));
4✔
1470
                    Ok(1)
4✔
1471
                }
1472
                Operand::Range(size, start) => {
6✔
1473
                    bc.extend((0..size).map(|offset| {
8✔
1474
                        Some(InternalBytecode::ConditionedReset {
8✔
1475
                            qubit: start + offset,
8✔
1476
                            creg,
8✔
1477
                            value: value.clone(),
8✔
1478
                        })
8✔
1479
                    }));
8✔
1480
                    Ok(size)
6✔
1481
                }
1482
            }
1483
        } else {
1484
            match qarg {
6✔
1485
                Operand::Single(qubit) => {
2✔
1486
                    bc.push(Some(InternalBytecode::Reset { qubit }));
2✔
1487
                    Ok(0)
2✔
1488
                }
1489
                Operand::Range(size, start) => {
4✔
1490
                    bc.extend((0..size).map(|offset| {
4✔
1491
                        Some(InternalBytecode::Reset {
4✔
1492
                            qubit: start + offset,
4✔
1493
                        })
4✔
1494
                    }));
4✔
1495
                    Ok(size)
4✔
1496
                }
1497
            }
1498
        }
1499
    }
36✔
1500

1501
    /// Parse a declaration of a classical register, emitting the relevant bytecode and adding the
1502
    /// definition to the relevant parts of the internal symbol tables in the parser state.  This
1503
    /// assumes that the `creg` token is still in the token stream.
1504
    fn parse_creg(&mut self, bc: &mut Vec<Option<InternalBytecode>>) -> PyResult<usize> {
838✔
1505
        let creg_token = self.expect_known(TokenType::Creg);
838✔
1506
        let name_token = self.expect(TokenType::Id, "a valid identifier", &creg_token)?;
838✔
1507
        let name = name_token.id(&self.context);
834✔
1508
        let lbracket_token = self.expect(TokenType::LBracket, "'['", &creg_token)?;
834✔
1509
        let size = self
832✔
1510
            .expect(TokenType::Integer, "an integer", &lbracket_token)?
832✔
1511
            .int(&self.context);
830✔
1512
        self.expect(TokenType::RBracket, "']'", &lbracket_token)?;
830✔
1513
        self.expect(TokenType::Semicolon, "';'", &creg_token)?;
828✔
1514
        let symbol = GlobalSymbol::Creg {
822✔
1515
            size,
822✔
1516
            start: ClbitId::new(self.num_clbits),
822✔
1517
            index: CregId::new(self.num_cregs),
822✔
1518
        };
822✔
1519
        if self.symbols.insert(name.clone(), symbol).is_none() {
822✔
1520
            self.num_clbits += size;
812✔
1521
            self.num_cregs += 1;
812✔
1522
            bc.push(Some(InternalBytecode::DeclareCreg { name, size }));
812✔
1523
            Ok(1)
812✔
1524
        } else {
1525
            Err(QASM2ParseError::new_err(message_generic(
10✔
1526
                Some(&Position::new(
10✔
1527
                    self.current_filename(),
10✔
1528
                    name_token.line,
10✔
1529
                    name_token.col,
10✔
1530
                )),
10✔
1531
                &format!("'{}' is already defined", name_token.id(&self.context)),
10✔
1532
            )))
10✔
1533
        }
1534
    }
838✔
1535

1536
    /// Parse a declaration of a quantum register, emitting the relevant bytecode and adding the
1537
    /// definition to the relevant parts of the internal symbol tables in the parser state.  This
1538
    /// assumes that the `qreg` token is still in the token stream.
1539
    fn parse_qreg(&mut self, bc: &mut Vec<Option<InternalBytecode>>) -> PyResult<usize> {
1,216✔
1540
        let qreg_token = self.expect_known(TokenType::Qreg);
1,216✔
1541
        let name_token = self.expect(TokenType::Id, "a valid identifier", &qreg_token)?;
1,216✔
1542
        let name = name_token.id(&self.context);
1,214✔
1543
        let lbracket_token = self.expect(TokenType::LBracket, "'['", &qreg_token)?;
1,214✔
1544
        let size = self
1,212✔
1545
            .expect(TokenType::Integer, "an integer", &lbracket_token)?
1,212✔
1546
            .int(&self.context);
1,208✔
1547
        self.expect(TokenType::RBracket, "']'", &lbracket_token)?;
1,208✔
1548
        self.expect(TokenType::Semicolon, "';'", &qreg_token)?;
1,206✔
1549
        let symbol = GlobalSymbol::Qreg {
1,200✔
1550
            size,
1,200✔
1551
            start: QubitId::new(self.num_qubits),
1,200✔
1552
        };
1,200✔
1553
        if self.symbols.insert(name.clone(), symbol).is_none() {
1,200✔
1554
            self.num_qubits += size;
1,190✔
1555
            bc.push(Some(InternalBytecode::DeclareQreg { name, size }));
1,190✔
1556
            Ok(1)
1,190✔
1557
        } else {
1558
            Err(QASM2ParseError::new_err(message_generic(
10✔
1559
                Some(&Position::new(
10✔
1560
                    self.current_filename(),
10✔
1561
                    name_token.line,
10✔
1562
                    name_token.col,
10✔
1563
                )),
10✔
1564
                &format!("'{}' is already defined", name_token.id(&self.context)),
10✔
1565
            )))
10✔
1566
        }
1567
    }
1,216✔
1568

1569
    /// Parse an include statement.  This currently only actually handles includes of `qelib1.inc`,
1570
    /// which aren't actually parsed; the parser has a built-in version of the file that it simply
1571
    /// updates its state with (and the Python side of the parser does the same) rather than
1572
    /// re-parsing the same file every time.  This assumes that the `include` token is still in the
1573
    /// token stream.
1574
    fn parse_include(&mut self, bc: &mut Vec<Option<InternalBytecode>>) -> PyResult<usize> {
368✔
1575
        let include_token = self.expect_known(TokenType::Include);
368✔
1576
        let filename_token =
360✔
1577
            self.expect(TokenType::Filename, "a filename string", &include_token)?;
368✔
1578
        self.expect(TokenType::Semicolon, "';'", &include_token)?;
360✔
1579
        let filename = filename_token.filename(&self.context);
356✔
1580
        if filename == "qelib1.inc" {
356✔
1581
            self.symbols.reserve(QELIB1.len());
336✔
1582
            let mut indices = Vec::with_capacity(QELIB1.len());
336✔
1583
            for (i, (name, num_params, num_qubits)) in QELIB1.iter().enumerate() {
7,728✔
1584
                if self.define_gate(
7,728✔
1585
                    Some(&include_token),
7,728✔
1586
                    name.to_string(),
7,728✔
1587
                    *num_params,
7,728✔
1588
                    *num_qubits,
7,728✔
1589
                )? {
4,318✔
1590
                    indices.push(i);
4,318✔
1591
                }
4,318✔
1592
            }
1593
            bc.push(Some(InternalBytecode::SpecialInclude { indices }));
336✔
1594
            Ok(1)
336✔
1595
        } else {
1596
            let base_filename = std::path::PathBuf::from(&filename);
20✔
1597
            let absolute_filename = find_include_path(&base_filename, &self.include_path)
20✔
1598
                .ok_or_else(|| {
20✔
1599
                    QASM2ParseError::new_err(message_generic(
2✔
1600
                        Some(&Position::new(
2✔
1601
                            self.current_filename(),
2✔
1602
                            filename_token.line,
2✔
1603
                            filename_token.col,
2✔
1604
                        )),
2✔
1605
                        &format!(
2✔
1606
                            "unable to find '{}' in the include search path",
2✔
1607
                            base_filename.display()
2✔
1608
                        ),
2✔
1609
                    ))
1610
                })?;
2✔
1611
            let new_stream =
18✔
1612
                TokenStream::from_path(absolute_filename, self.strict).map_err(|err| {
18✔
1613
                    QASM2ParseError::new_err(message_generic(
×
1614
                        Some(&Position::new(
×
1615
                            self.current_filename(),
×
1616
                            filename_token.line,
×
1617
                            filename_token.col,
×
1618
                        )),
×
1619
                        &format!("unable to open file '{}' for reading: {}", &filename, err),
×
1620
                    ))
1621
                })?;
×
1622
            self.tokens.push(new_stream);
18✔
1623
            self.allow_version = true;
18✔
1624
            Ok(0)
18✔
1625
        }
1626
    }
368✔
1627

1628
    /// Update the parser state with the definition of a particular gate.  This does not emit any
1629
    /// bytecode because not all gate definitions need something passing to Python.  For example,
1630
    /// the Python parser initializes its state including the built-in gates `U` and `CX`, and
1631
    /// handles the `qelib1.inc` include specially as well.
1632
    ///
1633
    /// Returns whether the gate needs to be defined in Python space (`true`) or if it was some sort
1634
    /// of built-in that doesn't need the definition (`false`).
1635
    fn define_gate(
10,316✔
1636
        &mut self,
10,316✔
1637
        owner: Option<&Token>,
10,316✔
1638
        name: String,
10,316✔
1639
        num_params: usize,
10,316✔
1640
        num_qubits: usize,
10,316✔
1641
    ) -> PyResult<bool> {
10,316✔
1642
        let already_defined = |state: &Self, name: String| {
10,316✔
1643
            let pos = owner.map(|tok| Position::new(state.current_filename(), tok.line, tok.col));
16✔
1644
            Err(QASM2ParseError::new_err(message_generic(
16✔
1645
                pos.as_ref(),
16✔
1646
                &format!("'{name}' is already defined"),
16✔
1647
            )))
16✔
1648
        };
16✔
1649
        let mismatched_definitions = |state: &Self, name: String, previous: OverridableGate| {
10,316✔
1650
            let plural = |count: usize, singular: &str| {
80✔
1651
                let mut out = format!("{count} {singular}");
80✔
1652
                if count != 1 {
80✔
1653
                    out.push('s');
26✔
1654
                }
54✔
1655
                out
80✔
1656
            };
80✔
1657
            let from_custom = format!(
20✔
1658
                "{} and {}",
1659
                plural(previous.num_params, "parameter"),
20✔
1660
                plural(previous.num_qubits, "qubit")
20✔
1661
            );
1662
            let from_program = format!(
20✔
1663
                "{} and {}",
1664
                plural(num_params, "parameter"),
20✔
1665
                plural(num_qubits, "qubit")
20✔
1666
            );
1667
            let pos = owner.map(|tok| Position::new(state.current_filename(), tok.line, tok.col));
20✔
1668
            Err(QASM2ParseError::new_err(message_generic(
20✔
1669
                pos.as_ref(),
20✔
1670
                &format!(
20✔
1671
                    concat!(
20✔
1672
                        "custom instruction '{}' is mismatched with its definition: ",
20✔
1673
                        "OpenQASM program has {}, custom has {}",
20✔
1674
                    ),
20✔
1675
                    name, from_program, from_custom
20✔
1676
                ),
20✔
1677
            )))
20✔
1678
        };
20✔
1679

1680
        if let Some(symbol) = self.overridable_gates.remove(&name) {
10,316✔
1681
            if num_params != symbol.num_params || num_qubits != symbol.num_qubits {
3,470✔
1682
                return mismatched_definitions(self, name, symbol);
20✔
1683
            }
3,450✔
1684
            match self.symbols.get(&name) {
3,450✔
1685
                None => {
1686
                    // The gate wasn't a built-in, so we need to move the symbol in, but we don't
1687
                    // need to increment the number of gates because it's already got a gate ID
1688
                    // assigned.
1689
                    self.symbols.insert(name, symbol.into());
3,424✔
1690
                    Ok(false)
3,424✔
1691
                }
1692
                Some(GlobalSymbol::Gate { .. }) => {
1693
                    // The gate was built-in and we can ignore the new definition (it's the same).
1694
                    Ok(false)
26✔
1695
                }
1696
                _ => already_defined(self, name),
×
1697
            }
1698
        } else if self.symbols.contains_key(&name) {
6,846✔
1699
            already_defined(self, name)
16✔
1700
        } else {
1701
            self.symbols.insert(
6,830✔
1702
                name,
6,830✔
1703
                GlobalSymbol::Gate {
6,830✔
1704
                    num_params,
6,830✔
1705
                    num_qubits,
6,830✔
1706
                    index: GateId::new(self.num_gates),
6,830✔
1707
                },
6,830✔
1708
            );
1709
            self.num_gates += 1;
6,830✔
1710
            Ok(true)
6,830✔
1711
        }
1712
    }
10,316✔
1713

1714
    /// Parse the next OpenQASM 2 statement in the program into a series of bytecode instructions.
1715
    ///
1716
    /// This is the principal public worker function of the parser.  One call to this function
1717
    /// parses a single OpenQASM 2 statement, which may expand to several bytecode instructions if
1718
    /// there is any broadcasting to resolve, or if the statement is a gate definition.  A call to
1719
    /// this function that returns `Some` will always have pushed at least one instruction to the
1720
    /// bytecode stream (the number is included).  A return of `None` signals the end of the
1721
    /// iterator.
1722
    pub fn parse_next(
6,256✔
1723
        &mut self,
6,256✔
1724
        bc: &mut Vec<Option<InternalBytecode>>,
6,256✔
1725
    ) -> PyResult<Option<usize>> {
6,256✔
1726
        if self.strict && self.allow_version {
6,256✔
1727
            match self.peek_token()?.map(|tok| tok.ttype) {
46✔
1728
                Some(TokenType::OpenQASM) => self.parse_version(),
42✔
1729
                Some(_) => {
1730
                    let token = self.next_token()?.unwrap();
2✔
1731
                    Err(QASM2ParseError::new_err(message_generic(
2✔
1732
                        Some(&Position::new(
2✔
1733
                            self.current_filename(),
2✔
1734
                            token.line,
2✔
1735
                            token.col,
2✔
1736
                        )),
2✔
1737
                        "[strict] the first statement must be 'OPENQASM 2.0;'",
2✔
1738
                    )))
2✔
1739
                }
1740
                None => Err(QASM2ParseError::new_err(message_generic(
2✔
1741
                    None,
2✔
1742
                    "[strict] saw an empty token stream, but needed a version statement",
2✔
1743
                ))),
2✔
1744
            }?;
4✔
1745
            self.allow_version = false;
42✔
1746
        }
6,210✔
1747
        let allow_version = self.allow_version;
6,252✔
1748
        self.allow_version = false;
6,252✔
1749
        while let Some(ttype) = self.peek_token()?.map(|tok| tok.ttype) {
6,792✔
1750
            let emitted = match ttype {
6,258✔
1751
                TokenType::Id => self.parse_gate_application(bc, None, false)?,
2,134✔
1752
                TokenType::Creg => self.parse_creg(bc)?,
838✔
1753
                TokenType::Qreg => self.parse_qreg(bc)?,
1,216✔
1754
                TokenType::Include => self.parse_include(bc)?,
368✔
1755
                TokenType::Measure => self.parse_measure(bc, None)?,
410✔
1756
                TokenType::Reset => self.parse_reset(bc, None)?,
26✔
1757
                TokenType::Barrier => self.parse_barrier(bc, None)?,
186✔
1758
                TokenType::If => self.parse_conditional(bc)?,
216✔
1759
                TokenType::Opaque => self.parse_opaque_definition(bc)?,
90✔
1760
                TokenType::Gate => self.parse_gate_definition(bc)?,
292✔
1761
                TokenType::OpenQASM => {
1762
                    if allow_version {
464✔
1763
                        self.parse_version()?
462✔
1764
                    } else {
1765
                        let token = self.next_token()?.unwrap();
2✔
1766
                        return Err(QASM2ParseError::new_err(message_generic(
2✔
1767
                            Some(&Position::new(
2✔
1768
                                self.current_filename(),
2✔
1769
                                token.line,
2✔
1770
                                token.col,
2✔
1771
                            )),
2✔
1772
                            "only the first statement may be a version declaration",
2✔
1773
                        )));
2✔
1774
                    }
1775
                }
1776
                TokenType::Semicolon => {
1777
                    let token = self.next_token()?.unwrap();
18✔
1778
                    if self.strict {
18✔
1779
                        return Err(QASM2ParseError::new_err(message_generic(
4✔
1780
                            Some(&Position::new(
4✔
1781
                                self.current_filename(),
4✔
1782
                                token.line,
4✔
1783
                                token.col,
4✔
1784
                            )),
4✔
1785
                            "[strict] empty statements and/or extra semicolons are forbidden",
4✔
1786
                        )));
4✔
1787
                    } else {
1788
                        0
14✔
1789
                    }
1790
                }
1791
                _ => {
1792
                    let token = self.next_token()?.unwrap();
×
1793
                    return Err(QASM2ParseError::new_err(message_generic(
×
1794
                        Some(&Position::new(
×
1795
                            self.current_filename(),
×
1796
                            token.line,
×
1797
                            token.col,
×
1798
                        )),
×
1799
                        &format!(
×
1800
                            "needed a start-of-statement token, but instead got {}",
×
1801
                            token.text(&self.context)
×
1802
                        ),
×
1803
                    )));
×
1804
                }
1805
            };
1806
            if emitted > 0 {
5,650✔
1807
                return Ok(Some(emitted));
5,110✔
1808
            }
540✔
1809
        }
1810
        Ok(None)
510✔
1811
    }
6,256✔
1812
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc