openqasm_parser/openqasm/
mod.rs

1//! All the openqasm specific code
2
3mod ast;
4mod lexer_rules;
5pub mod semantic_analysis;
6mod token;
7
8use self::semantic_analysis::{Cbit, Condition, Gate, GateOperation, Operation, Qubit};
9use crate::openqasm::token::Token;
10use crate::parser::ast::parse;
11use crate::parser::lexer::Lexer;
12use ast::MainProgram;
13use semantic_analysis::{OpenQASMProgram, SemanticError};
14use std::path::Path;
15use std::{collections::HashMap, fs::read_to_string};
16use token::TokenMatch;
17
18/// The different errors that can occur when parsing an openqasm program
19#[derive(Debug)]
20pub enum OpenQASMError {
21    /// The file(or some included file) could not be opened.
22    FileError,
23
24    /// The program contains some invalid token.
25    TokenError,
26
27    /// The program contains a syntactic error.
28    SyntaxError,
29
30    /// The program contains a semantic error. It contains a [SemanticError]
31    /// which gives more precise information about what kind of semantic error occured.
32    SemanticError(SemanticError),
33}
34
35/// The most importan function exposed by the library.
36/// This function reads a file containing openqasm 2.0 code and parses it.
37/// If the file include statments they are replaced by the contents of the included file
38/// before being parsed.
39///
40/// Returns an [OpenQASMProgram] if parsing was successful. This datatype contains information
41/// about the different quantum and classical registers, custom gates and operations performed.
42///
43/// If parsing was unsuccuessful an [OpenQASMError] is returned.
44///
45/// # Example
46/// ```
47/// use openqasm_parser::openqasm;
48/// use std::path::Path;
49///
50/// let program = openqasm::parse_openqasm(Path::new("openqasmfile.qasm"));
51/// ```
52pub fn parse_openqasm(file_path: &Path) -> Result<OpenQASMProgram, OpenQASMError> {
53    let mut lexer = Lexer::new();
54    lexer_rules::add_open_qasm_rules(&mut lexer);
55
56    let tokens = read_file_tokens(&lexer, file_path)?;
57
58    let t_vec: Vec<TokenMatch> = tokens
59        .iter()
60        .map(|(a, s)| (*a, s.into_iter().collect::<String>()))
61        .collect();
62
63    let ast = parse::<MainProgram, Token>(&t_vec).map_err(|_| OpenQASMError::SyntaxError)?;
64
65    OpenQASMProgram::from_ast(&ast).map_err(|e| OpenQASMError::SemanticError(e))
66}
67
68/// Reads a file and converts it into tokens. If the file contains include statements
69/// the function recursively calls itself on the included file and splices it into the
70/// tokenstream at the position of the include statement. Used by parse_openqasm.
71///
72/// Returns either a vector of Tokens, FileError or TokenError.
73fn read_file_tokens(
74    lexer: &Lexer<Token>,
75    file_path: &Path,
76) -> Result<Vec<(Token, Vec<char>)>, OpenQASMError> {
77    let file_str = read_to_string(file_path).map_err(|_| OpenQASMError::FileError)?;
78    let file_chars = file_str.bytes().map(|b| b as char).collect();
79
80    // Parse the file into a list of tokens
81    let mut tokens = lexer
82        .parse(file_chars)
83        .map_err(|_| OpenQASMError::TokenError)?;
84
85    // Check for include statements and splice in the tokens from the other file
86    for i in 0..tokens.len() {
87        if i + 2 < tokens.len()
88            && tokens[i + 0].0 == Token::Include
89            && tokens[i + 1].0 == Token::Str
90            && tokens[i + 2].0 == Token::Semicolon
91        {
92            let file_dir = file_path.parent().ok_or(OpenQASMError::FileError)?;
93
94            // Convert the included files path to be relative to the parent file
95            let other_file_path_str = tokens[i + 1].1.clone().into_iter().collect::<String>();
96            let other_file_path_str = &other_file_path_str[1..other_file_path_str.len() - 1];
97            let other_file_path = file_dir.join(Path::new(other_file_path_str));
98
99            let other_tokens = read_file_tokens(lexer, &other_file_path)?;
100
101            tokens.splice(i..i + 3, other_tokens);
102        }
103    }
104
105    Ok(tokens)
106}
107
108/// An enum containing only the "basic" operations which can be performed by
109/// an openqasm program. This is useful because unlike the [Operation] enum, this
110/// doesn't contain custom gates which are more difficult to handle.
111pub enum BasicOp {
112    /// The general unary gate supported by openqasm
113    U(f32, f32, f32, Qubit),
114
115    /// The binary gate supported by openqasm, also called cnot
116    CX(Qubit, Qubit),
117
118    /// Operation for measuring a qubit
119    Measure(Qubit, Cbit),
120
121    /// Reset a qubit to its zero state
122    ResetQ(Qubit),
123
124    /// Reset a cbit to its zero state
125    ResetC(Cbit),
126}
127
128impl OpenQASMProgram {
129    /// Retrieves a list of [BasicOp]s from an [OpenQASMProgram] so that the operations can
130    /// be for example perfomed by a simulator.
131    ///
132    /// Each [BasicOp] can optionally be paired with a [Condition], if the operation was applied
133    /// in an 'if' statement.
134    pub fn get_basic_operations(&self) -> Vec<(Option<Condition>, BasicOp)> {
135        let mut res = Vec::new();
136
137        for (condition, op) in self.operations.iter() {
138            for op in op.get_basic_operations(&self.gates) {
139                res.push((condition.clone(), op));
140            }
141        }
142
143        res
144    }
145}
146
147impl Operation {
148
149    /// Retrieves a vector of [BasicOp]s from an [Operation]. These are similar datastructures
150    /// except [BasicOp] doesn't contain a [Custom](Operation::Custom) variant. If the [Operation] is a custom gate
151    /// then it is converted to a list of several basic gates. Otherwise the single element list
152    /// containing the [BasicOp] is returned.
153    pub fn get_basic_operations(&self, gates: &HashMap<String, Gate>) -> Vec<BasicOp> {
154        match self {
155            Operation::U(p1, p2, p3, a) => vec![BasicOp::U(*p1, *p2, *p3, a.clone())],
156            Operation::CX(a1, a2) => vec![BasicOp::CX(a1.clone(), a2.clone())],
157            Operation::Custom(name, params, args) => gates
158                .get(name)
159                .map(|gate| {
160                    gate.operations
161                        .iter()
162                        .flat_map(|gate_op| {
163                            gate_op
164                                .get_operation(params, args)
165                                .get_basic_operations(gates)
166                        })
167                        .collect()
168                })
169                .unwrap_or(vec![]),
170            Operation::Measure(a1, a2) => vec![BasicOp::Measure(a1.clone(), a2.clone())],
171            Operation::ResetQ(a) => vec![BasicOp::ResetQ(a.clone())],
172            Operation::ResetC(a) => vec![BasicOp::ResetC(a.clone())],
173        }
174    }
175}
176
177impl GateOperation {
178
179    /// Converts a [GateOperation] into an [Operation].
180    pub fn get_operation(&self, params: &Vec<f32>, args: &Vec<Qubit>) -> Operation {
181        match self {
182            GateOperation::U(p1, p2, p3, a) => {
183                Operation::U(p1(params), p2(params), p3(params), args[*a].clone())
184            }
185            GateOperation::CX(a1, a2) => Operation::CX(args[*a1].clone(), args[*a2].clone()),
186            GateOperation::Custom(name, gate_params, gate_args) => Operation::Custom(
187                name.clone(),
188                gate_params.iter().map(|gp| gp(params)).collect(),
189                gate_args.iter().map(|ga| args[*ga].clone()).collect(),
190            ),
191        }
192    }
193}