1use oq3_semantics::asg;
6use oq3_semantics::symbols::{SymbolId, SymbolIdResult, SymbolTable, SymbolType};
7use oq3_semantics::types::{IsConst, Type};
8use std::collections::hash_map::DefaultHasher;
9use std::hash::{Hash, Hasher};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Op {
14 pub kind: &'static str,
17 pub name: String,
19 pub operands: String,
21 pub params: String,
25}
26
27impl Op {
28 pub fn align_key(&self) -> String {
31 format!("{} {} {}", self.kind, self.name, self.operands)
32 }
33
34 pub fn render(&self) -> String {
35 let mut s = format!("{} {}", self.name, self.operands);
36 if !self.params.is_empty() {
37 s.push_str(&format!(" ({})", self.params));
38 }
39 s.trim_end().to_string()
40 }
41}
42
43#[derive(Debug, Default, Clone, PartialEq, Eq)]
45pub struct Summary {
46 pub qubits: usize,
47 pub bits: usize,
48 pub total_ops: usize,
49 pub two_qubit_gates: usize,
50 pub gate_defs: usize,
51}
52
53pub struct Extracted {
54 pub ops: Vec<Op>,
55 pub summary: Summary,
56}
57
58fn short_hash<T: Hash>(value: &T) -> String {
59 let mut h = DefaultHasher::new();
60 value.hash(&mut h);
61 format!("#{:016x}", h.finish())
62}
63
64fn hash_debug<T: std::fmt::Debug>(value: &T, table: &SymbolTable) -> String {
67 let names = symbol_names(table);
68 let raw = format!("{value:?}");
69 let mut out = String::with_capacity(raw.len());
70 let mut rest = raw.as_str();
71 const TAG: &str = "SymbolId(";
72 while let Some(pos) = rest.find(TAG) {
73 out.push_str(&rest[..pos]);
74 rest = &rest[pos + TAG.len()..];
75 let parsed = rest.find(')').and_then(|close| {
76 rest[..close]
77 .parse::<usize>()
78 .ok()
79 .filter(|n| *n < names.len())
80 .map(|n| (close, n))
81 });
82 match parsed {
83 Some((close, n)) => {
84 out.push_str("sym:");
85 out.push_str(&names[n]);
86 rest = &rest[close + 1..];
87 }
88 None => out.push_str(TAG),
90 }
91 }
92 out.push_str(rest);
93 short_hash(&out)
94}
95
96fn symbol_names(table: &SymbolTable) -> Vec<String> {
99 let count = table
100 .clone()
101 .new_binding("__qcdiff_count_probe", &Type::Bool(IsConst::False))
102 .map(|id| symbol_id_value(&id))
103 .unwrap_or(0);
104 let mut id = SymbolId::new();
105 (0..count)
106 .map(|_| table[&id.post_increment()].name().to_string())
107 .collect()
108}
109
110fn symbol_id_value(id: &SymbolId) -> usize {
111 format!("{id:?}")
112 .trim_start_matches("SymbolId(")
113 .trim_end_matches(')')
114 .parse()
115 .unwrap_or(0)
116}
117
118fn sym_name(id: &SymbolIdResult, table: &SymbolTable) -> String {
119 match id {
120 Ok(sid) => table[sid].name().to_string(),
121 Err(_) => "<unresolved>".to_string(),
122 }
123}
124
125fn lvalue_str(lv: &asg::LValue, table: &SymbolTable) -> String {
126 match lv {
127 asg::LValue::Identifier(id) => sym_name(id, table),
128 asg::LValue::IndexedIdentifier(ii) => indexed_identifier_str(ii, table),
129 }
130}
131
132fn indexed_identifier_str(ii: &asg::IndexedIdentifier, table: &SymbolTable) -> String {
133 let mut s = sym_name(ii.identifier(), table);
134 for idx in ii.indexes() {
135 s.push_str(&format!("[{}]", hash_or_indexes(idx, table)));
136 }
137 s
138}
139
140fn hash_or_indexes(idx: &asg::IndexOperator, table: &SymbolTable) -> String {
141 match idx {
142 asg::IndexOperator::SetExpression(se) => exprs_str(se.expressions(), table),
143 asg::IndexOperator::ExpressionList(el) => exprs_str(&el.expressions, table),
144 }
145}
146
147fn exprs_str(exprs: &[asg::TExpr], table: &SymbolTable) -> String {
148 exprs
149 .iter()
150 .map(|e| expr_str(e, table))
151 .collect::<Vec<_>>()
152 .join(",")
153}
154
155fn exprs_str_tracked(
159 exprs: &[asg::TExpr],
160 table: &SymbolTable,
161 referenced: &mut std::collections::BTreeSet<String>,
162) -> String {
163 let rendered: Vec<String> = exprs.iter().map(|e| expr_str(e, table)).collect();
164 for q in &rendered {
165 referenced.insert(q.clone());
166 }
167 rendered.join(",")
168}
169
170fn expr_str(texpr: &asg::TExpr, table: &SymbolTable) -> String {
174 use asg::Expr;
175 match texpr.expression() {
176 Expr::Literal(lit) => literal_str(lit, table),
177 Expr::Identifier(id) => sym_name(id, table),
178 Expr::IndexedIdentifier(ii) => indexed_identifier_str(ii, table),
179 Expr::GateOperand(go) => gate_operand_str(go, table),
180 Expr::BinaryExpr(be) => format!(
181 "({}{}{})",
182 expr_str(be.left(), table),
183 binary_op_str(be.op()),
184 expr_str(be.right(), table)
185 ),
186 Expr::UnaryExpr(ue) => format!(
187 "({}{})",
188 unary_op_str(ue.op()),
189 expr_str(ue.operand(), table)
190 ),
191 Expr::MeasureExpression(me) => format!("measure {}", expr_str(me.operand(), table)),
192 Expr::IndexExpression(ie) => hash_debug(&ie, table),
195 Expr::HardwareQubit(hq) => format!("${}", hq.identifier()),
196 other => hash_debug(&other, table),
197 }
198}
199
200fn literal_str(lit: &asg::Literal, table: &SymbolTable) -> String {
201 use asg::Literal as L;
202 match lit {
203 L::Int(i) => format!("{}{}", if *i.sign() { "" } else { "-" }, i.value()),
204 L::Float(f) => f.value().to_string(),
205 L::Bool(b) => format!("{:?}", b.value()),
206 other => hash_debug(&other, table),
207 }
208}
209
210fn binary_op_str(op: &asg::BinaryOp) -> &'static str {
211 use asg::{ArithOp, BinaryOp, CmpOp};
212 match op {
213 BinaryOp::ArithOp(a) => match a {
214 ArithOp::Add => "+",
215 ArithOp::Sub => "-",
216 ArithOp::Mul => "*",
217 ArithOp::Div => "/",
218 ArithOp::Mod => "%",
219 _ => "ยท",
220 },
221 BinaryOp::CmpOp(c) => match c {
222 CmpOp::Eq => "==",
223 CmpOp::Neq => "!=",
224 },
225 BinaryOp::ConcatenationOp => "++",
226 }
227}
228
229fn unary_op_str(op: &asg::UnaryOp) -> &'static str {
230 use asg::UnaryOp;
231 match op {
232 UnaryOp::Minus => "-",
233 UnaryOp::Not => "!",
234 UnaryOp::BitNot => "~",
235 }
236}
237
238fn gate_operand_str(go: &asg::GateOperand, table: &SymbolTable) -> String {
239 match go {
240 asg::GateOperand::Identifier(id) => sym_name(id, table),
241 asg::GateOperand::IndexedIdentifier(ii) => indexed_identifier_str(ii, table),
242 asg::GateOperand::HardwareQubit(hq) => format!("${}", hq.identifier()),
243 }
244}
245
246fn modifier_prefix(mods: &[asg::GateModifier], table: &SymbolTable) -> String {
247 let mut s = String::new();
248 for m in mods {
249 match m {
250 asg::GateModifier::Inv => s.push_str("inv@"),
251 asg::GateModifier::Pow(p) => s.push_str(&format!("pow({})@", expr_str(p, table))),
252 asg::GateModifier::Ctrl(n) => match n {
253 Some(e) => s.push_str(&format!("ctrl({})@", expr_str(e, table))),
254 None => s.push_str("ctrl@"),
255 },
256 asg::GateModifier::NegCtrl(n) => match n {
257 Some(e) => s.push_str(&format!("negctrl({})@", expr_str(e, table))),
258 None => s.push_str("negctrl@"),
259 },
260 }
261 }
262 s
263}
264
265fn qubit_width(ty: &Type) -> usize {
267 match ty {
268 Type::Qubit => 1,
269 Type::QubitArray(dims) => dims.dims().iter().product(),
270 _ => 0,
271 }
272}
273
274fn bit_width(ty: &Type) -> usize {
275 match ty {
276 Type::Bit(_) => 1,
277 Type::BitArray(dims, _) => dims.dims().iter().product(),
278 _ => 0,
279 }
280}
281
282pub fn extract(program: &asg::Program, table: &SymbolTable) -> Extracted {
283 let mut ops = Vec::new();
284 let mut summary = Summary::default();
285 let mut referenced = std::collections::BTreeSet::new();
286
287 for stmt in program.stmts() {
288 extract_stmt(stmt, table, &mut ops, &mut summary, &mut referenced);
289 }
290
291 summary.qubits = summary.qubits.max(referenced.len());
294
295 summary.total_ops = ops.len();
296 summary.two_qubit_gates = ops
297 .iter()
298 .filter(|op| op.kind == "gate" && op.operands.split(',').count() >= 2)
299 .count();
300 Extracted { ops, summary }
301}
302
303fn extract_stmt(
304 stmt: &asg::Stmt,
305 table: &SymbolTable,
306 ops: &mut Vec<Op>,
307 summary: &mut Summary,
308 referenced: &mut std::collections::BTreeSet<String>,
309) {
310 use asg::Stmt;
311 match stmt {
312 Stmt::GateCall(gc) => {
313 let name = format!(
314 "{}{}",
315 modifier_prefix(gc.modifiers(), table),
316 sym_name(gc.name(), table)
317 );
318 ops.push(Op {
319 kind: "gate",
320 name,
321 operands: exprs_str_tracked(gc.qubits(), table, referenced),
322 params: gc.params().map(|p| exprs_str(p, table)).unwrap_or_default(),
323 });
324 }
325 Stmt::GPhaseCall(g) => ops.push(Op {
326 kind: "gate",
327 name: "gphase".into(),
328 operands: String::new(),
329 params: expr_str(g.arg(), table),
330 }),
331 Stmt::ModifiedGPhaseCall(g) => ops.push(Op {
332 kind: "gate",
333 name: format!("{}gphase", modifier_prefix(g.modifiers(), table)),
334 operands: String::new(),
335 params: expr_str(g.arg(), table),
336 }),
337 Stmt::Reset(r) => ops.push(Op {
338 kind: "reset",
339 name: "reset".into(),
340 operands: {
341 let q = expr_str(r.gate_operand(), table);
342 referenced.insert(q.clone());
343 q
344 },
345 params: String::new(),
346 }),
347 Stmt::Barrier(b) => ops.push(Op {
348 kind: "barrier",
349 name: "barrier".into(),
350 operands: b
351 .qubits()
352 .map(|q| exprs_str_tracked(q, table, referenced))
353 .unwrap_or_else(|| "(all)".into()),
354 params: String::new(),
355 }),
356 Stmt::Assignment(a) => {
357 if let asg::Expr::MeasureExpression(me) = a.rvalue().expression() {
361 ops.push(Op {
362 kind: "measure",
363 name: "measure".into(),
364 operands: {
365 let q = expr_str(me.operand(), table);
366 referenced.insert(q.clone());
367 format!("{} -> {}", q, lvalue_str(a.lvalue(), table))
368 },
369 params: String::new(),
370 });
371 } else {
372 ops.push(Op {
373 kind: "assign",
374 name: lvalue_str(a.lvalue(), table),
375 operands: String::new(),
376 params: expr_str(a.rvalue(), table),
377 });
378 }
379 }
380 Stmt::ExprStmt(texpr) => {
381 if let asg::Expr::MeasureExpression(me) = texpr.expression() {
382 ops.push(Op {
383 kind: "measure",
384 name: "measure".into(),
385 operands: expr_str(me.operand(), table),
386 params: String::new(),
387 });
388 } else {
389 ops.push(Op {
390 kind: "expr",
391 name: "expr".into(),
392 operands: String::new(),
393 params: expr_str(texpr, table),
394 });
395 }
396 }
397 Stmt::If(i) => ops.push(Op {
401 kind: "ctrl-flow",
402 name: "if".into(),
403 operands: expr_str(i.condition(), table),
404 params: hash_debug(&(i.then_branch(), i.else_branch()), table),
405 }),
406 Stmt::While(w) => ops.push(Op {
407 kind: "ctrl-flow",
408 name: "while".into(),
409 operands: expr_str(w.condition(), table),
410 params: hash_debug(&w.loop_body(), table),
411 }),
412 Stmt::ForStmt(f) => ops.push(Op {
413 kind: "ctrl-flow",
414 name: "for".into(),
415 operands: sym_name(f.loop_var(), table),
416 params: hash_debug(&(f.iterable(), f.loop_body()), table),
417 }),
418 Stmt::SwitchCaseStmt(s) => ops.push(Op {
419 kind: "ctrl-flow",
420 name: "switch".into(),
421 operands: String::new(),
422 params: hash_debug(&s, table),
423 }),
424 Stmt::Block(b) => ops.push(Op {
425 kind: "ctrl-flow",
426 name: "block".into(),
427 operands: String::new(),
428 params: hash_debug(&b, table),
429 }),
430 Stmt::GateDefinition(gd) => {
431 summary.gate_defs += 1;
432 ops.push(Op {
433 kind: "gatedef",
434 name: sym_name(gd.name(), table),
435 operands: String::new(),
436 params: hash_debug(&gd, table),
437 });
438 }
439 Stmt::Delay(_) => ops.push(Op {
440 kind: "delay",
441 name: "delay".into(),
442 operands: String::new(),
443 params: hash_debug(&stmt, table),
444 }),
445 Stmt::DeclareQuantum(d) => {
446 if let Ok(sid) = d.name() {
447 summary.qubits += qubit_width(table[sid].symbol_type());
448 }
449 }
450 Stmt::DeclareClassical(d) => {
451 if let Ok(sid) = d.name() {
452 summary.bits += bit_width(table[sid].symbol_type());
453 }
454 }
455 Stmt::AnnotatedStmt(a) => extract_stmt(a.statement(), table, ops, summary, referenced),
456 _ => {}
459 }
460}