0
# AST Visitors and Transformers
1
2
Visitor and transformer patterns for systematic AST traversal, analysis, and modification with optional context support. These classes enable powerful AST manipulation patterns including tree walking, analysis, transformation, and code generation.
3
4
## Capabilities
5
6
### Base Visitor Class
7
8
Generic visitor pattern for AST traversal with flexible context support.
9
10
```python { .api }
11
class QASMVisitor(Generic[T]):
12
"""
13
A node visitor base class that walks the abstract syntax tree and calls a
14
visitor function for every node found. This function may return a value
15
which is forwarded by the `visit` method.
16
17
The optional context argument allows the visitor to hold temporary state
18
while visiting nodes without modifying the AST or the visitor instance.
19
"""
20
21
def visit(self, node: QASMNode, context: Optional[T] = None):
22
"""
23
Visit a node and dispatch to the appropriate visitor method.
24
25
Args:
26
node: AST node to visit
27
context: Optional context object for holding temporary state
28
29
Returns:
30
Result from the visitor method (if any)
31
"""
32
33
def generic_visit(self, node: QASMNode, context: Optional[T] = None):
34
"""
35
Called if no explicit visitor function exists for a node.
36
37
This method recursively visits all child nodes in the AST.
38
Override this method to change the default traversal behavior.
39
40
Args:
41
node: AST node to visit
42
context: Optional context object
43
44
Returns:
45
None by default, but can be overridden
46
"""
47
```
48
49
### AST Transformer Class
50
51
Visitor subclass that enables modification of AST nodes during traversal.
52
53
```python { .api }
54
class QASMTransformer(QASMVisitor[T]):
55
"""
56
A QASMVisitor subclass that allows modification of AST nodes during traversal.
57
58
This class enables tree transformation operations including node replacement,
59
deletion, and list modifications. It handles the complexities of maintaining
60
valid AST structure during transformations.
61
"""
62
63
def generic_visit(self, node: QASMNode, context: Optional[T] = None) -> QASMNode:
64
"""
65
Visit and potentially modify nodes and their children.
66
67
This method visits all child nodes and can replace them with modified
68
versions. It handles lists, single nodes, and None values appropriately.
69
70
Args:
71
node: AST node to visit and potentially transform
72
context: Optional context object
73
74
Returns:
75
The node (potentially modified) or a replacement node
76
"""
77
```
78
79
## Usage Examples
80
81
### Basic AST Analysis
82
83
```python
84
from openqasm3 import ast, visitor
85
import openqasm3
86
87
class GateAnalyzer(visitor.QASMVisitor):
88
"""Analyze quantum gates in a program"""
89
90
def __init__(self):
91
self.gate_calls = []
92
self.gate_definitions = []
93
self.total_qubits = 0
94
95
def visit_QuantumGate(self, node):
96
"""Visit quantum gate calls"""
97
self.gate_calls.append({
98
'name': node.name.name,
99
'qubits': len(node.qubits),
100
'parameters': len(node.arguments)
101
})
102
self.generic_visit(node)
103
104
def visit_QuantumGateDefinition(self, node):
105
"""Visit quantum gate definitions"""
106
self.gate_definitions.append({
107
'name': node.name.name,
108
'qubits': len(node.qubits),
109
'parameters': len(node.arguments)
110
})
111
self.generic_visit(node)
112
113
def visit_QubitDeclaration(self, node):
114
"""Count total qubits declared"""
115
if node.size:
116
# Array declaration like qubit[4] q
117
if isinstance(node.size, ast.IntegerLiteral):
118
self.total_qubits += node.size.value
119
else:
120
# Single qubit declaration like qubit q
121
self.total_qubits += 1
122
self.generic_visit(node)
123
124
# Use the analyzer
125
qasm_source = '''
126
OPENQASM 3.0;
127
qubit[3] q;
128
gate bell q0, q1 {
129
h q0;
130
cx q0, q1;
131
}
132
bell q[0], q[1];
133
h q[2];
134
'''
135
136
program = openqasm3.parse(qasm_source)
137
analyzer = GateAnalyzer()
138
analyzer.visit(program)
139
140
print(f"Total qubits: {analyzer.total_qubits}")
141
print(f"Gate definitions: {analyzer.gate_definitions}")
142
print(f"Gate calls: {analyzer.gate_calls}")
143
```
144
145
### AST Transformation
146
147
```python
148
from openqasm3 import ast, visitor
149
import openqasm3
150
151
class GateInverter(visitor.QASMTransformer):
152
"""Transform gates to add inverse modifiers"""
153
154
def visit_QuantumGate(self, node):
155
"""Add inverse modifier to all gates"""
156
# Create inverse modifier
157
inv_modifier = ast.QuantumGateModifier(
158
modifier=ast.GateModifierName.inv,
159
argument=None
160
)
161
162
# Create new gate with inverse modifier
163
new_gate = ast.QuantumGate(
164
modifiers=[inv_modifier] + node.modifiers,
165
name=node.name,
166
arguments=node.arguments,
167
qubits=node.qubits,
168
duration=node.duration
169
)
170
171
return self.generic_visit(new_gate)
172
173
# Transform a program
174
program = openqasm3.parse('''
175
OPENQASM 3.0;
176
qubit[2] q;
177
h q[0];
178
cx q[0], q[1];
179
''')
180
181
transformer = GateInverter()
182
transformed_program = transformer.visit(program)
183
184
# Convert back to text to see the result
185
result = openqasm3.dumps(transformed_program)
186
print(result)
187
```
188
189
### Context-Based Analysis
190
191
```python
192
from openqasm3 import ast, visitor
193
import openqasm3
194
from typing import List, Set
195
196
class ScopeInfo:
197
"""Context object to track scope information"""
198
def __init__(self):
199
self.declared_variables: Set[str] = set()
200
self.used_variables: Set[str] = set()
201
self.scope_level: int = 0
202
203
class VariableAnalyzer(visitor.QASMVisitor[ScopeInfo]):
204
"""Analyze variable usage with scope tracking"""
205
206
def visit_Program(self, node, context=None):
207
"""Start analysis with global scope"""
208
if context is None:
209
context = ScopeInfo()
210
211
self.generic_visit(node, context)
212
213
# Report unused variables
214
unused = context.declared_variables - context.used_variables
215
if unused:
216
print(f"Unused variables: {unused}")
217
218
def visit_ClassicalDeclaration(self, node, context):
219
"""Track variable declarations"""
220
var_name = node.identifier.name
221
context.declared_variables.add(var_name)
222
print(f"Declared variable '{var_name}' at scope level {context.scope_level}")
223
224
# Visit initialization expression
225
if node.init_expression:
226
self.visit(node.init_expression, context)
227
228
def visit_Identifier(self, node, context):
229
"""Track variable usage"""
230
var_name = node.name
231
if var_name in context.declared_variables:
232
context.used_variables.add(var_name)
233
234
self.generic_visit(node, context)
235
236
def visit_SubroutineDefinition(self, node, context):
237
"""Enter new scope for subroutine"""
238
# Create new scope context
239
new_context = ScopeInfo()
240
new_context.declared_variables = context.declared_variables.copy()
241
new_context.used_variables = context.used_variables.copy()
242
new_context.scope_level = context.scope_level + 1
243
244
# Add subroutine parameters to scope
245
for arg in node.arguments:
246
if isinstance(arg, ast.ClassicalArgument):
247
new_context.declared_variables.add(arg.name.name)
248
249
# Visit subroutine body with new context
250
for stmt in node.body:
251
self.visit(stmt, new_context)
252
253
# Merge used variables back to parent context
254
context.used_variables.update(new_context.used_variables)
255
256
# Analyze a program with variable usage
257
program = openqasm3.parse('''
258
OPENQASM 3.0;
259
int[32] x = 42;
260
int[32] y = 10;
261
int[32] result;
262
263
def int[32] add_numbers(int[32] a, int[32] b) {
264
return a + b;
265
}
266
267
result = add_numbers(x, y);
268
''')
269
270
analyzer = VariableAnalyzer()
271
analyzer.visit(program)
272
```
273
274
### Custom Visitor for Code Generation
275
276
```python
277
from openqasm3 import ast, visitor
278
import openqasm3
279
280
class QuantumCircuitExtractor(visitor.QASMVisitor):
281
"""Extract quantum circuit information"""
282
283
def __init__(self):
284
self.circuit_info = {
285
'qubits': {},
286
'gates': [],
287
'measurements': []
288
}
289
290
def visit_QubitDeclaration(self, node):
291
"""Extract qubit information"""
292
qubit_name = node.qubit.name
293
if node.size:
294
if isinstance(node.size, ast.IntegerLiteral):
295
size = node.size.value
296
else:
297
size = f"expr({node.size})"
298
else:
299
size = 1
300
301
self.circuit_info['qubits'][qubit_name] = size
302
self.generic_visit(node)
303
304
def visit_QuantumGate(self, node):
305
"""Extract gate operations"""
306
gate_info = {
307
'name': node.name.name,
308
'qubits': [],
309
'parameters': [],
310
'modifiers': []
311
}
312
313
# Extract qubit targets
314
for qubit in node.qubits:
315
if isinstance(qubit, ast.Identifier):
316
gate_info['qubits'].append(qubit.name)
317
elif isinstance(qubit, ast.IndexedIdentifier):
318
qubit_ref = f"{qubit.name.name}[{qubit.indices}]"
319
gate_info['qubits'].append(qubit_ref)
320
321
# Extract parameters
322
for param in node.arguments:
323
if isinstance(param, ast.Identifier):
324
gate_info['parameters'].append(param.name)
325
elif isinstance(param, ast.FloatLiteral):
326
gate_info['parameters'].append(param.value)
327
elif isinstance(param, ast.IntegerLiteral):
328
gate_info['parameters'].append(param.value)
329
330
# Extract modifiers
331
for modifier in node.modifiers:
332
mod_info = {'type': modifier.modifier.name}
333
if modifier.argument:
334
mod_info['argument'] = str(modifier.argument)
335
gate_info['modifiers'].append(mod_info)
336
337
self.circuit_info['gates'].append(gate_info)
338
self.generic_visit(node)
339
340
def visit_QuantumMeasurementStatement(self, node):
341
"""Extract measurement operations"""
342
measurement_info = {
343
'qubit': str(node.measure.qubit),
344
'target': str(node.target) if node.target else None
345
}
346
self.circuit_info['measurements'].append(measurement_info)
347
self.generic_visit(node)
348
349
# Extract circuit information
350
program = openqasm3.parse('''
351
OPENQASM 3.0;
352
qubit[3] q;
353
bit[3] c;
354
355
h q[0];
356
cx q[0], q[1];
357
ry(pi/4) q[2];
358
measure q[0] -> c[0];
359
measure q[1] -> c[1];
360
''')
361
362
extractor = QuantumCircuitExtractor()
363
extractor.visit(program)
364
365
import json
366
print(json.dumps(extractor.circuit_info, indent=2))
367
```
368
369
### Advanced Transformation with Error Handling
370
371
```python
372
from openqasm3 import ast, visitor
373
import openqasm3
374
375
class SafeGateReplacer(visitor.QASMTransformer):
376
"""Safely replace gate names with error handling"""
377
378
def __init__(self, replacement_map):
379
self.replacement_map = replacement_map
380
self.warnings = []
381
382
def visit_QuantumGate(self, node):
383
"""Replace gate names according to mapping"""
384
original_name = node.name.name
385
386
if original_name in self.replacement_map:
387
new_name = self.replacement_map[original_name]
388
389
# Create new gate with replaced name
390
new_gate = ast.QuantumGate(
391
modifiers=node.modifiers,
392
name=ast.Identifier(new_name),
393
arguments=node.arguments,
394
qubits=node.qubits,
395
duration=node.duration
396
)
397
398
print(f"Replaced gate '{original_name}' with '{new_name}'")
399
return self.generic_visit(new_gate)
400
else:
401
# Gate not in replacement map
402
self.warnings.append(f"Gate '{original_name}' not found in replacement map")
403
return self.generic_visit(node)
404
405
# Use the transformer
406
replacements = {
407
'h': 'hadamard',
408
'cx': 'cnot',
409
'x': 'pauli_x'
410
}
411
412
program = openqasm3.parse('''
413
OPENQASM 3.0;
414
qubit[2] q;
415
h q[0];
416
cx q[0], q[1];
417
y q[1]; // This gate won't be replaced
418
''')
419
420
replacer = SafeGateReplacer(replacements)
421
transformed = replacer.visit(program)
422
423
print("\nWarnings:")
424
for warning in replacer.warnings:
425
print(f" {warning}")
426
427
print("\nTransformed program:")
428
print(openqasm3.dumps(transformed))
429
```
430
431
## Advanced Patterns
432
433
### Combining Multiple Visitors
434
435
```python
436
from openqasm3 import visitor
437
438
class CompositeAnalyzer(visitor.QASMVisitor):
439
"""Combine multiple analysis passes"""
440
441
def __init__(self):
442
self.analyzers = [
443
GateAnalyzer(),
444
VariableAnalyzer(),
445
QuantumCircuitExtractor()
446
]
447
448
def visit(self, node, context=None):
449
# Run all analyzers
450
for analyzer in self.analyzers:
451
analyzer.visit(node, context)
452
453
# Run default visit
454
return super().visit(node, context)
455
```
456
457
### Visitor with State Management
458
459
```python
460
class StatefulVisitor(visitor.QASMVisitor):
461
"""Visitor that maintains complex state"""
462
463
def __init__(self):
464
self.state_stack = []
465
self.current_function = None
466
self.call_graph = {}
467
468
def _push_state(self, state_name):
469
self.state_stack.append(state_name)
470
471
def _pop_state(self):
472
if self.state_stack:
473
return self.state_stack.pop()
474
return None
475
476
def visit_SubroutineDefinition(self, node):
477
self._push_state('subroutine')
478
old_function = self.current_function
479
self.current_function = node.name.name
480
481
self.generic_visit(node)
482
483
self.current_function = old_function
484
self._pop_state()
485
```
486
487
The visitor pattern provides a powerful and flexible way to work with OpenQASM 3 ASTs, enabling everything from simple analysis to complex transformations while maintaining clean, readable code.