Skip to main content

q_rust/parser/
mod.rs

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
10// --- Resolution & Mapping ---
11
12fn 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)), // u1(lambda) = RZ(lambda)
23        "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        ), // u2(phi, lambda) = U(pi/2, phi, lambda)
28        "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(); // name -> (start_index, size)
47    let mut cregs: HashMap<String, (usize, usize)> = HashMap::new(); // name -> (start_index, size)
48    let mut total_qubits = 0;
49    let mut total_cbits = 0;
50
51    let mut current_input = input;
52
53    // 1. Skip initial comments/whitespace and parse Header
54    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    // 2. Parse remaining statements
82    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, &params);
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        // Check first gate is U
192        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}