1pub mod ast;
2pub mod rules;
3
4use self::ast::ParsedStatement;
5use self::rules::{comment, creg, gate_call, include, measure, openqasm_version, qreg};
6use crate::ir::{Circuit, GateType, Operation};
7use nom::{branch::alt, character::complete::multispace0};
8use std::collections::HashMap;
9
10fn map_gate_type(name: &str, params: &[f64]) -> GateType {
13 match name {
14 "h" => GateType::H,
15 "x" => GateType::X,
16 "y" => GateType::Y,
17 "z" => GateType::Z,
18 "cx" => GateType::CX,
19 "rx" => GateType::RX(params.first().cloned().unwrap_or(0.0)),
20 "ry" => GateType::RY(params.first().cloned().unwrap_or(0.0)),
21 "rz" => GateType::RZ(params.first().cloned().unwrap_or(0.0)),
22 "u1" => GateType::RZ(params.first().cloned().unwrap_or(0.0)), "u2" => GateType::U(
24 std::f64::consts::FRAC_PI_2,
25 params.first().cloned().unwrap_or(0.0),
26 params.get(1).cloned().unwrap_or(0.0),
27 ), "u3" | "U" => GateType::U(
29 params.first().cloned().unwrap_or(0.0),
30 params.get(1).cloned().unwrap_or(0.0),
31 params.get(2).cloned().unwrap_or(0.0),
32 ),
33 "id" => GateType::ID,
34 "s" => GateType::S,
35 "sdg" => GateType::Sdg,
36 "t" => GateType::T,
37 "tdg" => GateType::Tdg,
38 "swap" => GateType::SWAP,
39 "ccx" => GateType::CCX,
40 _ => GateType::Custom(name.to_string()),
41 }
42}
43
44pub fn parse_qasm(input: &str) -> Result<Circuit, String> {
45 let mut circuit = Circuit::new(0, 0);
46 let mut qregs: HashMap<String, (usize, usize)> = HashMap::new(); let mut cregs: HashMap<String, (usize, usize)> = HashMap::new(); let mut total_qubits = 0;
49 let mut total_cbits = 0;
50
51 let mut current_input = input;
52
53 loop {
55 current_input = multispace0::<&str, nom::error::Error<&str>>(current_input)
56 .map_err(|e| e.to_string())?
57 .0;
58 if current_input.is_empty() {
59 return Err("Empty file or missing OPENQASM header".to_string());
60 }
61
62 if let Ok((rem, _)) = comment(current_input) {
63 current_input = rem;
64 continue;
65 }
66 break;
67 }
68
69 let (rem, version) = openqasm_version(current_input).map_err(|_| {
70 "Missing or invalid OPENQASM header. File must start with 'OPENQASM 2.0;'".to_string()
71 })?;
72
73 if version != "2.0" {
74 return Err(format!(
75 "Unsupported OpenQASM version: '{}'. Only '2.0' is supported.",
76 version
77 ));
78 }
79 current_input = rem;
80
81 loop {
83 current_input = multispace0::<&str, nom::error::Error<&str>>(current_input)
84 .map_err(|e| e.to_string())?
85 .0;
86 if current_input.is_empty() {
87 break;
88 }
89
90 if let Ok((rem, _)) = comment(current_input) {
91 current_input = rem;
92 continue;
93 }
94
95 let (rem, stmt) = alt((include, qreg, creg, measure, gate_call))(current_input)
96 .map_err(|_e| format!("Parse error at: {}", current_input))?;
97
98 current_input = rem;
99
100 match stmt {
101 ParsedStatement::Ignore => {}
102 ParsedStatement::QReg(name, size) => {
103 qregs.insert(name, (total_qubits, size));
104 total_qubits += size;
105 }
106 ParsedStatement::CReg(name, size) => {
107 cregs.insert(name, (total_cbits, size));
108 total_cbits += size;
109 }
110 ParsedStatement::Gate(name, qubits, params) => {
111 let mut resolved_qubits = Vec::new();
112 for (q_name, q_idx) in qubits {
113 if let Some(&(start, size)) = qregs.get(&q_name) {
114 if q_idx < size {
115 resolved_qubits.push(start + q_idx);
116 } else {
117 return Err(format!(
118 "Qubit index out of bounds: {}[{}]",
119 q_name, q_idx
120 ));
121 }
122 } else {
123 return Err(format!("Undefined quantum register: {}", q_name));
124 }
125 }
126
127 let gate_type = map_gate_type(&name, ¶ms);
128 circuit.add_op(Operation::Gate {
129 name: gate_type,
130 qubits: resolved_qubits,
131 params,
132 });
133 }
134 ParsedStatement::Measure((q_name, q_idx), (c_name, c_idx)) => {
135 let resolved_q = if let Some(&(start, size)) = qregs.get(&q_name) {
136 if q_idx < size {
137 start + q_idx
138 } else {
139 return Err(format!("Qubit index out of bounds: {}[{}]", q_name, q_idx));
140 }
141 } else {
142 return Err(format!("Undefined quantum register: {}", q_name));
143 };
144
145 let resolved_c = if let Some(&(start, size)) = cregs.get(&c_name) {
146 if c_idx < size {
147 start + c_idx
148 } else {
149 return Err(format!(
150 "Classical bit index out of bounds: {}[{}]",
151 c_name, c_idx
152 ));
153 }
154 } else {
155 return Err(format!("Undefined classical register: {}", c_name));
156 };
157
158 circuit.add_op(Operation::Measure {
159 qubit: resolved_q,
160 cbit: resolved_c,
161 });
162 }
163 _ => {}
164 }
165 }
166
167 circuit.num_qubits = total_qubits;
168 circuit.num_cbits = total_cbits;
169 Ok(circuit)
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_standard_gates() {
178 let qasm = r#"
179 OPENQASM 2.0;
180 qreg q[3];
181 u3(0.1, 0.2, 0.3) q[0];
182 id q[0];
183 s q[1];
184 sdg q[1];
185 t q[2];
186 tdg q[2];
187 ccx q[0], q[1], q[2];
188 "#;
189 let circuit = parse_qasm(qasm).expect("Failed to parse standard gates");
190 assert_eq!(circuit.operations.len(), 7);
191 if let Operation::Gate { name, .. } = &circuit.operations[0] {
193 assert!(matches!(name, GateType::U(..)));
194 } else {
195 panic!("Expected U gate");
196 }
197 }
198
199 #[test]
200 fn test_header() {
201 assert_eq!(
202 openqasm_version("OPENQASM 2.0;"),
203 Ok(("", "2.0".to_string()))
204 );
205 }
206
207 #[test]
208 fn test_qreg() {
209 assert_eq!(
210 qreg("qreg q[2];"),
211 Ok(("", ParsedStatement::QReg("q".to_string(), 2)))
212 );
213 }
214
215 #[test]
216 fn test_creg() {
217 assert_eq!(
218 creg("creg c[3];"),
219 Ok(("", ParsedStatement::CReg("c".to_string(), 3)))
220 );
221 }
222
223 #[test]
224 fn test_gate_call_no_params() {
225 assert_eq!(
226 gate_call("h q[0];"),
227 Ok((
228 "",
229 ParsedStatement::Gate("h".to_string(), vec![("q".to_string(), 0)], vec![])
230 ))
231 );
232 }
233
234 #[test]
235 fn test_gate_call_with_params() {
236 assert_eq!(
237 gate_call("rx(1.57) q[0];"),
238 Ok((
239 "",
240 ParsedStatement::Gate("rx".to_string(), vec![("q".to_string(), 0)], vec![1.57])
241 ))
242 );
243 }
244
245 #[test]
246 fn test_measure() {
247 assert_eq!(
248 measure("measure q[0] -> c[0];"),
249 Ok((
250 "",
251 ParsedStatement::Measure(("q".to_string(), 0), ("c".to_string(), 0))
252 ))
253 );
254 }
255
256 #[test]
257 fn test_valid_file() {
258 let qasm = "OPENQASM 2.0; qreg q[1];";
259 assert!(parse_qasm(qasm).is_ok());
260 }
261
262 #[test]
263 fn test_invalid_version() {
264 let qasm = "OPENQASM 3.0; qreg q[1];";
265 let err = parse_qasm(qasm).unwrap_err();
266 assert!(err.contains("Unsupported OpenQASM version"));
267 }
268
269 #[test]
270 fn test_missing_header() {
271 let qasm = "qreg q[1];";
272 let err = parse_qasm(qasm).unwrap_err();
273 assert!(err.contains("Missing or invalid OPENQASM header"));
274 }
275
276 #[test]
277 fn test_garbage() {
278 let qasm = "NOT A QASM FILE";
279 let err = parse_qasm(qasm).unwrap_err();
280 assert!(err.contains("Missing or invalid OPENQASM header"));
281 }
282}