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

Qiskit / qiskit / 16830026850

08 Aug 2025 12:08PM UTC coverage: 87.665%. Remained the same
16830026850

push

github

web-flow
Fix clippy issues in new MCX synthesis plugin (#14843)

In the recently merged #14666 two small clippy issues with latest stable
slipped into the PR. We run clippy with our MSRV in CI for a stable CI
as newer versions of Rust/clippy introduce new rules and/or
change/improve existing rules so a new rust release would break CI which
is disruptive. However, you can still use newer clippy for local
development and when you do the issues it finds highlight real things
that need to be fixed in the code. This commit fixes the two things that
clippy flags in the mcx plugin code, one is just a code style change but
the other is a small docs formatting issue which would have resulted in
weird formatting for the rendered rustdoc.

1 of 1 new or added line in 1 file covered. (100.0%)

19 existing lines in 5 files now uncovered.

82212 of 93780 relevant lines covered (87.66%)

547397.33 hits per line

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

97.56
/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 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

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
    message_bad_eof, message_generic, message_incorrect_requirement, Position, QASM2ParseError,
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 {
24,794✔
72
                Self(value)
24,794✔
73
            }
24,794✔
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 {
5,486✔
84
                Self::new(self.0 + rhs)
5,486✔
85
            }
5,486✔
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: PyObject,
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 {
2,780✔
141
        Self::Gate {
2,780✔
142
            num_params: value.num_params,
2,780✔
143
            num_qubits: value.num_qubits,
2,780✔
144
            index: value.index,
2,780✔
145
        }
2,780✔
146
    }
2,780✔
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,224✔
246
        tokens: TokenStream,
1,224✔
247
        include_path: Vec<std::path::PathBuf>,
1,224✔
248
        custom_instructions: &[CustomInstruction],
1,224✔
249
        custom_classical: &[CustomClassical],
1,224✔
250
        strict: bool,
1,224✔
251
    ) -> PyResult<Self> {
1,224✔
252
        let mut state = State {
1,224✔
253
            tokens: vec![tokens],
1,224✔
254
            context: TokenContext::new(),
1,224✔
255
            include_path,
1,224✔
256
            // For Qiskit-created circuits, all files will have the builtin gates and `qelib1.inc`,
1,224✔
257
            // so we allocate with that in mind.  There may well be overlap between libraries and
1,224✔
258
            // custom instructions, but this is small-potatoes allocation and we'd rather not have
1,224✔
259
            // to reallocate.
1,224✔
260
            symbols: HashMap::with_capacity(
1,224✔
261
                N_BUILTIN_GATES + QELIB1.len() + custom_instructions.len() + custom_classical.len(),
1,224✔
262
            ),
1,224✔
263
            gate_symbols: HashMap::new(),
1,224✔
264
            overridable_gates: HashMap::new(),
1,224✔
265
            num_qubits: 0,
1,224✔
266
            num_clbits: 0,
1,224✔
267
            num_cregs: 0,
1,224✔
268
            num_gates: 0,
1,224✔
269
            allow_version: true,
1,224✔
270
            strict,
1,224✔
271
        };
1,224✔
272
        for inst in custom_instructions {
7,146✔
273
            if state.symbols.contains_key(&inst.name)
5,924✔
274
                || state.overridable_gates.contains_key(&inst.name)
5,924✔
275
            {
276
                return Err(QASM2ParseError::new_err(message_generic(
2✔
277
                    None,
2✔
278
                    &format!("duplicate custom instruction '{}'", inst.name),
2✔
279
                )));
2✔
280
            }
5,922✔
281
            state.overridable_gates.insert(
5,922✔
282
                inst.name.clone(),
5,922✔
283
                OverridableGate {
5,922✔
284
                    num_params: inst.num_params,
5,922✔
285
                    num_qubits: inst.num_qubits,
5,922✔
286
                    index: GateId::new(state.num_gates),
5,922✔
287
                },
5,922✔
288
            );
289
            if inst.builtin {
5,922✔
290
                state.symbols.insert(
2,622✔
291
                    inst.name.clone(),
2,622✔
292
                    GlobalSymbol::Gate {
2,622✔
293
                        num_params: inst.num_params,
2,622✔
294
                        num_qubits: inst.num_qubits,
2,622✔
295
                        index: GateId::new(state.num_gates),
2,622✔
296
                    },
2,622✔
297
                );
2,622✔
298
            }
3,300✔
299
            state.num_gates += 1;
5,922✔
300
        }
301
        state.define_gate(None, "U".to_owned(), 3, 1)?;
1,222✔
302
        state.define_gate(None, "CX".to_owned(), 0, 2)?;
1,220✔
303
        for classical in custom_classical {
1,624✔
304
            if BUILTIN_CLASSICAL.contains(&&*classical.name) {
424✔
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
            }
412✔
313
            match state.symbols.insert(
412✔
314
                classical.name.clone(),
412✔
315
                GlobalSymbol::Classical {
412✔
316
                    num_params: classical.num_params,
412✔
317
                    callable: classical.callable.clone(),
412✔
318
                },
412✔
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 '{}'",
2✔
324
                            &classical.name,
2✔
325
                        ),
326
                        _ => format!(
2✔
327
                            "custom classical instruction '{}' has a naming clash with a custom gate",
2✔
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
                _ => (),
406✔
340
            }
341
        }
342
        Ok(state)
1,200✔
343
    }
1,224✔
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>> {
47,280✔
349
        let mut pointer = self.tokens.len() - 1;
47,280✔
350
        while pointer > 0 {
47,282✔
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)
47,142✔
359
    }
47,280✔
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,460✔
364
        let mut pointer = self.tokens.len() - 1;
30,460✔
365
        while pointer > 0 && self.tokens[pointer].peek(&mut self.context)?.is_none() {
30,478✔
366
            pointer -= 1;
18✔
367
        }
18✔
368
        self.tokens[pointer].peek(&mut self.context)
30,460✔
369
    }
30,460✔
370

371
    /// Get the filename associated with the currently active token stream.
372
    fn current_filename(&self) -> &std::ffi::OsStr {
578✔
373
        &self.tokens[self.tokens.len() - 1].filename
578✔
374
    }
578✔
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,108✔
379
        let out = self.next_token().unwrap().unwrap();
7,108✔
380
        if out.ttype != expected {
7,108✔
381
            panic!(
×
382
                "expected '{}' but got '{}'",
×
383
                expected.describe(),
×
384
                out.ttype.describe()
×
385
            )
386
        }
7,108✔
387
        out
7,108✔
388
    }
7,108✔
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> {
26,490✔
395
        let token = match self.next_token()? {
26,490✔
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,
26,358✔
407
        };
408
        if token.ttype == expected {
26,358✔
409
            Ok(token)
26,230✔
410
        } else {
411
            Err(QASM2ParseError::new_err(message_incorrect_requirement(
128✔
412
                required,
128✔
413
                &token,
128✔
414
                self.current_filename(),
128✔
415
            )))
128✔
416
        }
417
    }
26,490✔
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>> {
19,998✔
422
        let peeked = self.peek_token()?;
19,998✔
423
        if peeked.is_some() && peeked.unwrap().ttype == expected {
19,998✔
424
            self.next_token()
13,638✔
425
        } else {
426
            Ok(None)
6,360✔
427
        }
428
    }
19,998✔
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,964✔
432
        let peeked = self.peek_token()?;
1,964✔
433
        Ok(peeked.is_some() && peeked.unwrap().ttype == expected)
1,960✔
434
    }
1,964✔
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<()> {
3,974✔
438
        match (self.strict, comma) {
3,974✔
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(()),
3,960✔
448
        }
449
    }
3,974✔
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,182✔
458
        let (name, name_token) = match self.accept(TokenType::Id)? {
4,182✔
459
            None => return Ok(None),
30✔
460
            Some(token) => (token.id(&self.context), token),
4,152✔
461
        };
462
        let (register_size, register_start) = match self.symbols.get(&name) {
4,152✔
463
            Some(GlobalSymbol::Qreg { size, start }) => (*size, *start),
4,130✔
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,130✔
490
            .map(Some)
4,130✔
491
    }
4,182✔
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>>> {
450✔
496
        let (name, name_token) = match self.accept(TokenType::Id)? {
450✔
497
            None => return Ok(None),
×
498
            Some(token) => (token.id(&self.context), token),
450✔
499
        };
500
        match self.gate_symbols.get(&name) {
450✔
501
            Some(GateSymbol::Qubit { index }) => Ok(Some(Operand::Single(*index))),
438✔
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 => {
511
                if let Some(symbol) = self.symbols.get(&name) {
10✔
512
                    Err(QASM2ParseError::new_err(message_generic(
8✔
513
                        Some(&Position::new(
8✔
514
                            self.current_filename(),
8✔
515
                            name_token.line,
8✔
516
                            name_token.col,
8✔
517
                        )),
8✔
518
                        &format!("'{}' is {}, not a qubit", name, symbol.describe()),
8✔
519
                    )))
8✔
520
                } else {
521
                    Err(QASM2ParseError::new_err(message_generic(
2✔
522
                        Some(&Position::new(
2✔
523
                            self.current_filename(),
2✔
524
                            name_token.line,
2✔
525
                            name_token.col,
2✔
526
                        )),
2✔
527
                        &format!("'{name}' is not defined in this scope"),
2✔
528
                    )))
2✔
529
                }
530
            }
531
        }
532
    }
450✔
533

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1717
    /// Parse the next OpenQASM 2 statement in the program into a series of bytecode instructions.
1718
    ///
1719
    /// This is the principal public worker function of the parser.  One call to this function
1720
    /// parses a single OpenQASM 2 statement, which may expand to several bytecode instructions if
1721
    /// there is any broadcasting to resolve, or if the statement is a gate definition.  A call to
1722
    /// this function that returns `Some` will always have pushed at least one instruction to the
1723
    /// bytecode stream (the number is included).  A return of `None` signals the end of the
1724
    /// iterator.
1725
    pub fn parse_next(
6,276✔
1726
        &mut self,
6,276✔
1727
        bc: &mut Vec<Option<InternalBytecode>>,
6,276✔
1728
    ) -> PyResult<Option<usize>> {
6,276✔
1729
        if self.strict && self.allow_version {
6,276✔
1730
            match self.peek_token()?.map(|tok| tok.ttype) {
46✔
1731
                Some(TokenType::OpenQASM) => self.parse_version(),
42✔
1732
                Some(_) => {
1733
                    let token = self.next_token()?.unwrap();
2✔
1734
                    Err(QASM2ParseError::new_err(message_generic(
2✔
1735
                        Some(&Position::new(
2✔
1736
                            self.current_filename(),
2✔
1737
                            token.line,
2✔
1738
                            token.col,
2✔
1739
                        )),
2✔
1740
                        "[strict] the first statement must be 'OPENQASM 2.0;'",
2✔
1741
                    )))
2✔
1742
                }
1743
                None => Err(QASM2ParseError::new_err(message_generic(
2✔
1744
                    None,
2✔
1745
                    "[strict] saw an empty token stream, but needed a version statement",
2✔
1746
                ))),
2✔
1747
            }?;
4✔
1748
            self.allow_version = false;
42✔
1749
        }
6,230✔
1750
        let allow_version = self.allow_version;
6,272✔
1751
        self.allow_version = false;
6,272✔
1752
        while let Some(ttype) = self.peek_token()?.map(|tok| tok.ttype) {
6,866✔
1753
            let emitted = match ttype {
6,360✔
1754
                TokenType::Id => self.parse_gate_application(bc, None, false)?,
2,082✔
1755
                TokenType::Creg => self.parse_creg(bc)?,
954✔
1756
                TokenType::Qreg => self.parse_qreg(bc)?,
1,250✔
1757
                TokenType::Include => self.parse_include(bc)?,
342✔
1758
                TokenType::Measure => self.parse_measure(bc, None)?,
372✔
1759
                TokenType::Reset => self.parse_reset(bc, None)?,
28✔
1760
                TokenType::Barrier => self.parse_barrier(bc, None)?,
172✔
1761
                TokenType::If => self.parse_conditional(bc)?,
222✔
1762
                TokenType::Opaque => self.parse_opaque_definition(bc)?,
98✔
1763
                TokenType::Gate => self.parse_gate_definition(bc)?,
304✔
1764
                TokenType::OpenQASM => {
1765
                    if allow_version {
516✔
1766
                        self.parse_version()?
514✔
1767
                    } else {
1768
                        let token = self.next_token()?.unwrap();
2✔
1769
                        return Err(QASM2ParseError::new_err(message_generic(
2✔
1770
                            Some(&Position::new(
2✔
1771
                                self.current_filename(),
2✔
1772
                                token.line,
2✔
1773
                                token.col,
2✔
1774
                            )),
2✔
1775
                            "only the first statement may be a version declaration",
2✔
1776
                        )));
2✔
1777
                    }
1778
                }
1779
                TokenType::Semicolon => {
1780
                    let token = self.next_token()?.unwrap();
18✔
1781
                    if self.strict {
18✔
1782
                        return Err(QASM2ParseError::new_err(message_generic(
4✔
1783
                            Some(&Position::new(
4✔
1784
                                self.current_filename(),
4✔
1785
                                token.line,
4✔
1786
                                token.col,
4✔
1787
                            )),
4✔
1788
                            "[strict] empty statements and/or extra semicolons are forbidden",
4✔
1789
                        )));
4✔
1790
                    } else {
1791
                        0
14✔
1792
                    }
1793
                }
1794
                _ => {
1795
                    let token = self.next_token()?.unwrap();
2✔
1796
                    return Err(QASM2ParseError::new_err(message_generic(
2✔
1797
                        Some(&Position::new(
2✔
1798
                            self.current_filename(),
2✔
1799
                            token.line,
2✔
1800
                            token.col,
2✔
1801
                        )),
2✔
1802
                        &format!(
2✔
1803
                            "needed a start-of-statement token, but instead got {}",
2✔
1804
                            token.text(&self.context)
2✔
1805
                        ),
2✔
1806
                    )));
2✔
1807
                }
1808
            };
1809
            if emitted > 0 {
5,672✔
1810
                return Ok(Some(emitted));
5,078✔
1811
            }
594✔
1812
        }
1813
        Ok(None)
482✔
1814
    }
6,276✔
1815
}
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