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

Qiskit / qiskit / 14845034704

05 May 2025 07:04PM UTC coverage: 87.819% (-0.06%) from 87.875%
14845034704

push

github

web-flow
Oxidize classical expression use in Rust code. (#14176)

* s

* Rename BitData to ObjectRegistry.

* s

* Update tests for new error string.

This is only really a user-facing error message when
working with DAGCircuit, since QuantumCircuit first
checks if the bits being added to it are duplicates.
And, in the case of DAGCircuit, the previous error
message was already unfriendly:

ValueError: Existing bit ShareableQubit(Owned { register: OwningRegisterInfo { name: "q16", size: 2, subclass: QUBIT }, index: 0 }) cannot be re-added in strict mode.

* Fix comment in commutation checker.

* Fix lint.

* Address review comments.

* Unify identifier handling in DAG.

Tracks stretches the same way we track vars.
Also happens to fix a bug in DAG equality where
order mattered between stretch captures (it should
never have). And, fixes a serialization bug with
stretches.

* Fix tests.

* Improve testing and fix bug.

* Add pickle and deepcopy tests for stretches.

Also fix lint.

* Initial commit.

* Implement types.

* Wire-up Rust-based Types.

* Add more plumbing for Expr.

* Expose Op enums.

* WIP

* WIP

* Test passing.

* Refactor module organization.

* Fix clippy.

* Downcast instead of extract.

* Fix merge.

* Undo breaking name change.

* Add release note.

* Fix lint.

* Fix Python lint.

* Add missing documentation, fix opcode docs.

* Impl repr for expressions.

* Fix pickling for facade enums.

* Manually revert DAGCircuit changes.

I'll do these in a separate PR.

* Revert "Manually revert DAGCircuit changes."

This reverts commit 35938ec06.

* WIP

* Pass 1.

* Use identifier_info for copy_empty_like.

* Convert to expr during creation of QuantumCircuitData.

* Remove Python token need for Var and Stretch.

* Use sets for vars. Whoops!

* Fix lint.

* Use fast-rng feature for uuid.

* Fix merge, further oxidize compose.

* Updates after merge.

* Address review comments.

304 of 373 new or added lines in 5 files covered. (81.5%)

47 existing lines in 6 files now uncovered.

74490 of 84822 relevant lines covered (87.82%)

434366.73 hits per line

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

96.68
/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 {
25,048✔
72
                Self(value)
25,048✔
73
            }
25,048✔
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,504✔
84
                Self::new(self.0 + rhs)
5,504✔
85
            }
5,504✔
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,264✔
246
        tokens: TokenStream,
1,264✔
247
        include_path: Vec<std::path::PathBuf>,
1,264✔
248
        custom_instructions: &[CustomInstruction],
1,264✔
249
        custom_classical: &[CustomClassical],
1,264✔
250
        strict: bool,
1,264✔
251
    ) -> PyResult<Self> {
1,264✔
252
        let mut state = State {
1,264✔
253
            tokens: vec![tokens],
1,264✔
254
            context: TokenContext::new(),
1,264✔
255
            include_path,
1,264✔
256
            // For Qiskit-created circuits, all files will have the builtin gates and `qelib1.inc`,
1,264✔
257
            // so we allocate with that in mind.  There may well be overlap between libraries and
1,264✔
258
            // custom instructions, but this is small-potatoes allocation and we'd rather not have
1,264✔
259
            // to reallocate.
1,264✔
260
            symbols: HashMap::with_capacity(
1,264✔
261
                N_BUILTIN_GATES + QELIB1.len() + custom_instructions.len() + custom_classical.len(),
1,264✔
262
            ),
1,264✔
263
            gate_symbols: HashMap::new(),
1,264✔
264
            overridable_gates: HashMap::new(),
1,264✔
265
            num_qubits: 0,
1,264✔
266
            num_clbits: 0,
1,264✔
267
            num_cregs: 0,
1,264✔
268
            num_gates: 0,
1,264✔
269
            allow_version: true,
1,264✔
270
            strict,
1,264✔
271
        };
1,264✔
272
        for inst in custom_instructions {
7,186✔
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
            );
5,922✔
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,262✔
302
        state.define_gate(None, "CX".to_owned(), 0, 2)?;
1,260✔
303
        for classical in custom_classical {
1,664✔
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
            ) {
412✔
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
                        ),
2✔
326
                        _ => format!(
2✔
327
                            "custom classical instruction '{}' has a naming clash with a custom gate",
2✔
328
                            &classical.name,
2✔
329
                        ),
2✔
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,240✔
343
    }
1,264✔
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>> {
48,256✔
349
        let mut pointer = self.tokens.len() - 1;
48,256✔
350
        while pointer > 0 {
48,258✔
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)
48,118✔
359
    }
48,256✔
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,662✔
364
        let mut pointer = self.tokens.len() - 1;
30,662✔
365
        while pointer > 0 && self.tokens[pointer].peek(&mut self.context)?.is_none() {
30,680✔
366
            pointer -= 1;
18✔
367
        }
18✔
368
        self.tokens[pointer].peek(&mut self.context)
30,662✔
369
    }
30,662✔
370

371
    /// Get the filename associated with the currently active token stream.
372
    fn current_filename(&self) -> &std::ffi::OsStr {
618✔
373
        &self.tokens[self.tokens.len() - 1].filename
618✔
374
    }
618✔
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,302✔
379
        let out = self.next_token().unwrap().unwrap();
7,302✔
380
        if out.ttype != expected {
7,302✔
381
            panic!(
×
382
                "expected '{}' but got '{}'",
×
383
                expected.describe(),
×
384
                out.ttype.describe()
×
385
            )
×
386
        }
7,302✔
387
        out
7,302✔
388
    }
7,302✔
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> {
27,276✔
395
        let token = match self.next_token()? {
27,276✔
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,
27,144✔
407
        };
27,144✔
408
        if token.ttype == expected {
27,144✔
409
            Ok(token)
26,966✔
410
        } else {
411
            Err(QASM2ParseError::new_err(message_incorrect_requirement(
178✔
412
                required,
178✔
413
                &token,
178✔
414
                self.current_filename(),
178✔
415
            )))
178✔
416
        }
417
    }
27,276✔
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,986✔
422
        let peeked = self.peek_token()?;
19,986✔
423
        if peeked.is_some() && peeked.unwrap().ttype == expected {
19,986✔
424
            self.next_token()
13,642✔
425
        } else {
426
            Ok(None)
6,344✔
427
        }
428
    }
19,986✔
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,960✔
438
        match (self.strict, comma) {
3,960✔
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,946✔
448
        }
449
    }
3,960✔
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,202✔
458
        let (name, name_token) = match self.accept(TokenType::Id)? {
4,202✔
459
            None => return Ok(None),
34✔
460
            Some(token) => (token.id(&self.context), token),
4,168✔
461
        };
462
        let (register_size, register_start) = match self.symbols.get(&name) {
4,168✔
463
            Some(GlobalSymbol::Qreg { size, start }) => (*size, *start),
4,146✔
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!("'{}' is not defined in this scope", name),
4✔
486
                )))
4✔
487
            }
488
        };
489
        self.complete_operand(&name, register_size, register_start)
4,146✔
490
            .map(Some)
4,146✔
491
    }
4,202✔
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
        };
450✔
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!("'{}' is a parameter, not a qubit", name),
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!("'{}' is not defined in this scope", name),
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>> {
446✔
537
        match self.peek_token()?.map(|tok| tok.ttype) {
446✔
538
            Some(TokenType::Id) => self.accept_qarg().map(Option::unwrap),
438✔
539
            Some(_) => {
540
                let token = self.next_token()?;
4✔
541
                Err(QASM2ParseError::new_err(message_incorrect_requirement(
4✔
542
                    "a quantum argument",
4✔
543
                    &token.unwrap(),
4✔
544
                    self.current_filename(),
4✔
545
                )))
4✔
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
    }
446✔
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>>> {
368✔
566
        let (name, name_token) = match self.accept(TokenType::Id)? {
368✔
567
            None => return Ok(None),
×
568
            Some(token) => (token.id(&self.context), token),
368✔
569
        };
570
        let (register_size, register_start) = match self.symbols.get(&name) {
368✔
571
            Some(GlobalSymbol::Creg { size, start, .. }) => (*size, *start),
360✔
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!("'{}' is not defined in this scope", name),
2✔
594
                )))
2✔
595
            }
596
        };
597
        self.complete_operand(&name, register_size, register_start)
360✔
598
            .map(Some)
360✔
599
    }
368✔
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>> {
370✔
604
        match self.peek_token()?.map(|tok| tok.ttype) {
370✔
605
            Some(TokenType::Id) => self.accept_carg().map(Option::unwrap),
368✔
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
    }
370✔
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,506✔
629
        &mut self,
4,506✔
630
        name: &str,
4,506✔
631
        register_size: usize,
4,506✔
632
        register_start: T,
4,506✔
633
    ) -> PyResult<Operand<T>>
4,506✔
634
    where
4,506✔
635
        T: std::ops::Add<usize, Output = T>,
4,506✔
636
    {
4,506✔
637
        let lbracket_token = match self.accept(TokenType::LBracket)? {
4,506✔
638
            Some(token) => token,
3,874✔
639
            None => return Ok(Operand::Range(register_size, register_start)),
632✔
640
        };
641
        let index_token = self.expect(TokenType::Integer, "an integer index", &lbracket_token)?;
3,874✔
642
        let index = index_token.int(&self.context);
3,850✔
643
        self.expect(TokenType::RBracket, "a closing bracket", &lbracket_token)?;
3,850✔
644
        if index < register_size {
3,820✔
645
            Ok(Operand::Single(register_start + index))
3,814✔
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 {} is out-of-range for register '{}' of size {}",
6✔
655
                    index, name, register_size
6✔
656
                ),
6✔
657
            )))
6✔
658
        }
659
    }
4,506✔
660

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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