0
# Code Generation
1
2
Convert AST nodes back to valid OpenQASM 3 text with configurable formatting and complete language support. The code generation system provides round-trip capability from AST back to textual OpenQASM 3 with proper syntax and formatting preservation.
3
4
## Capabilities
5
6
### High-Level Functions
7
8
Simple functions for converting AST nodes to OpenQASM 3 text.
9
10
```python { .api }
11
def dump(node: ast.QASMNode, file: io.TextIOBase, **kwargs) -> None:
12
"""
13
Write textual OpenQASM 3 code representing an AST node to an open stream.
14
15
Args:
16
node: AST node to convert (typically ast.Program but can be any node)
17
file: Open text stream to write to (file object, StringIO, etc.)
18
**kwargs: Formatting options passed to Printer constructor
19
20
Raises:
21
ValueError: If node cannot be converted to valid OpenQASM 3
22
"""
23
24
def dumps(node: ast.QASMNode, **kwargs) -> str:
25
"""
26
Get a string representation of OpenQASM 3 code from an AST node.
27
28
Args:
29
node: AST node to convert
30
**kwargs: Formatting options passed to Printer constructor
31
32
Returns:
33
String containing valid OpenQASM 3 code
34
35
Raises:
36
ValueError: If node cannot be converted to valid OpenQASM 3
37
"""
38
```
39
40
### Printer State Management
41
42
State object for tracking formatting context during code generation.
43
44
```python { .api }
45
@dataclass
46
class PrinterState:
47
"""
48
State object for the print visitor that tracks indentation and formatting context.
49
50
This object is mutated during the printing process to maintain proper
51
indentation levels and handle special formatting cases.
52
"""
53
54
current_indent: int = 0
55
"""Current indentation level (number of indent steps)"""
56
57
skip_next_indent: bool = False
58
"""Used for chained 'else if' formatting to avoid extra indentation"""
59
60
def increase_scope(self):
61
"""
62
Context manager to temporarily increase indentation level.
63
64
Usage:
65
with state.increase_scope():
66
# Code here will be indented one level deeper
67
printer.visit_some_node(node, state)
68
"""
69
```
70
71
### Main Printer Class
72
73
Comprehensive AST visitor for converting nodes to OpenQASM 3 text with configurable formatting.
74
75
```python { .api }
76
class Printer(QASMVisitor[PrinterState]):
77
"""
78
Internal AST visitor for writing AST nodes as valid OpenQASM 3 text.
79
80
This class inherits from QASMVisitor and provides specialized visitor
81
methods for every AST node type. It handles proper precedence, formatting,
82
and syntax generation for the complete OpenQASM 3 language.
83
"""
84
85
def __init__(
86
self,
87
stream: io.TextIOBase,
88
indent: str = " ",
89
chain_else_if: bool = True,
90
old_measurement: bool = False
91
):
92
"""
93
Initialize the printer with formatting options.
94
95
Args:
96
stream: Output stream to write to
97
indent: Indentation string (default: two spaces)
98
chain_else_if: Whether to flatten nested if-else into 'else if' format
99
old_measurement: Use OpenQASM 2 arrow syntax for measurements
100
"""
101
102
def visit(self, node: ast.QASMNode, context: Optional[PrinterState] = None) -> None:
103
"""
104
Main entry point for visiting nodes and generating code.
105
106
Args:
107
node: AST node to convert to text
108
context: Optional printer state (created automatically if None)
109
"""
110
```
111
112
### Specialized Visitor Methods
113
114
The Printer class provides visitor methods for all AST node types.
115
116
```python { .api }
117
# Program structure
118
def visit_Program(self, node: ast.Program, context: PrinterState) -> None: ...
119
def visit_CompoundStatement(self, node: ast.CompoundStatement, context: PrinterState) -> None: ...
120
121
# Declarations
122
def visit_QubitDeclaration(self, node: ast.QubitDeclaration, context: PrinterState) -> None: ...
123
def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: PrinterState) -> None: ...
124
def visit_ConstantDeclaration(self, node: ast.ConstantDeclaration, context: PrinterState) -> None: ...
125
def visit_IODeclaration(self, node: ast.IODeclaration, context: PrinterState) -> None: ...
126
def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: PrinterState) -> None: ...
127
128
# Quantum elements
129
def visit_QuantumGateDefinition(self, node: ast.QuantumGateDefinition, context: PrinterState) -> None: ...
130
def visit_QuantumGate(self, node: ast.QuantumGate, context: PrinterState) -> None: ...
131
def visit_QuantumPhase(self, node: ast.QuantumPhase, context: PrinterState) -> None: ...
132
def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: PrinterState) -> None: ...
133
def visit_QuantumReset(self, node: ast.QuantumReset, context: PrinterState) -> None: ...
134
def visit_QuantumMeasurement(self, node: ast.QuantumMeasurement, context: PrinterState) -> None: ...
135
136
# Control flow
137
def visit_BranchingStatement(self, node: ast.BranchingStatement, context: PrinterState) -> None: ...
138
def visit_WhileLoop(self, node: ast.WhileLoop, context: PrinterState) -> None: ...
139
def visit_ForInLoop(self, node: ast.ForInLoop, context: PrinterState) -> None: ...
140
def visit_SwitchStatement(self, node: ast.SwitchStatement, context: PrinterState) -> None: ...
141
142
# Subroutines and functions
143
def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: PrinterState) -> None: ...
144
def visit_ReturnStatement(self, node: ast.ReturnStatement, context: PrinterState) -> None: ...
145
def visit_FunctionCall(self, node: ast.FunctionCall, context: PrinterState) -> None: ...
146
147
# Expressions
148
def visit_BinaryExpression(self, node: ast.BinaryExpression, context: PrinterState) -> None: ...
149
def visit_UnaryExpression(self, node: ast.UnaryExpression, context: PrinterState) -> None: ...
150
def visit_IndexExpression(self, node: ast.IndexExpression, context: PrinterState) -> None: ...
151
def visit_Cast(self, node: ast.Cast, context: PrinterState) -> None: ...
152
def visit_Concatenation(self, node: ast.Concatenation, context: PrinterState) -> None: ...
153
154
# Literals
155
def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: PrinterState) -> None: ...
156
def visit_FloatLiteral(self, node: ast.FloatLiteral, context: PrinterState) -> None: ...
157
def visit_ImaginaryLiteral(self, node: ast.ImaginaryLiteral, context: PrinterState) -> None: ...
158
def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: PrinterState) -> None: ...
159
def visit_BitstringLiteral(self, node: ast.BitstringLiteral, context: PrinterState) -> None: ...
160
def visit_DurationLiteral(self, node: ast.DurationLiteral, context: PrinterState) -> None: ...
161
def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: PrinterState) -> None: ...
162
163
# Types
164
def visit_IntType(self, node: ast.IntType, context: PrinterState) -> None: ...
165
def visit_FloatType(self, node: ast.FloatType, context: PrinterState) -> None: ...
166
def visit_ComplexType(self, node: ast.ComplexType, context: PrinterState) -> None: ...
167
def visit_ArrayType(self, node: ast.ArrayType, context: PrinterState) -> None: ...
168
# ... and many more type visitor methods
169
170
# Timing and calibration
171
def visit_DelayInstruction(self, node: ast.DelayInstruction, context: PrinterState) -> None: ...
172
def visit_Box(self, node: ast.Box, context: PrinterState) -> None: ...
173
def visit_CalibrationDefinition(self, node: ast.CalibrationDefinition, context: PrinterState) -> None: ...
174
```
175
176
## Usage Examples
177
178
### Basic Code Generation
179
180
```python
181
import openqasm3
182
from openqasm3 import ast
183
import io
184
185
# Create an AST programmatically
186
program = ast.Program(
187
version="3.0",
188
statements=[
189
ast.QubitDeclaration(
190
qubit=ast.Identifier("q"),
191
size=ast.IntegerLiteral(2)
192
),
193
ast.QuantumGate(
194
modifiers=[],
195
name=ast.Identifier("h"),
196
arguments=[],
197
qubits=[ast.IndexedIdentifier(
198
name=ast.Identifier("q"),
199
indices=[[ast.IntegerLiteral(0)]]
200
)]
201
)
202
]
203
)
204
205
# Convert to string
206
qasm_code = openqasm3.dumps(program)
207
print(qasm_code)
208
# Output:
209
# OPENQASM 3.0;
210
# qubit[2] q;
211
# h q[0];
212
213
# Write to file
214
with open('output.qasm', 'w') as f:
215
openqasm3.dump(program, f)
216
```
217
218
### Custom Formatting Options
219
220
```python
221
import openqasm3
222
from openqasm3 import ast
223
224
# Create a program with nested control flow
225
program = ast.Program(
226
version="3.0",
227
statements=[
228
ast.ClassicalDeclaration(
229
type=ast.IntType(),
230
identifier=ast.Identifier("x"),
231
init_expression=ast.IntegerLiteral(5)
232
),
233
ast.BranchingStatement(
234
condition=ast.BinaryExpression(
235
op=ast.BinaryOperator[">"],
236
lhs=ast.Identifier("x"),
237
rhs=ast.IntegerLiteral(0)
238
),
239
if_block=[
240
ast.ExpressionStatement(
241
expression=ast.FunctionCall(
242
name=ast.Identifier("print"),
243
arguments=[ast.Identifier("x")]
244
)
245
)
246
],
247
else_block=[
248
ast.BranchingStatement(
249
condition=ast.BinaryExpression(
250
op=ast.BinaryOperator["<"],
251
lhs=ast.Identifier("x"),
252
rhs=ast.IntegerLiteral(0)
253
),
254
if_block=[
255
ast.ExpressionStatement(
256
expression=ast.FunctionCall(
257
name=ast.Identifier("print"),
258
arguments=[ast.StringLiteral("negative")]
259
)
260
)
261
],
262
else_block=[]
263
)
264
]
265
)
266
]
267
)
268
269
# Default formatting (chained else-if)
270
default_code = openqasm3.dumps(program)
271
print("Default formatting:")
272
print(default_code)
273
274
# Custom formatting with different indentation
275
custom_code = openqasm3.dumps(program, indent=" ", chain_else_if=False)
276
print("\nCustom formatting:")
277
print(custom_code)
278
```
279
280
### Working with Complex Expressions
281
282
```python
283
import openqasm3
284
from openqasm3 import ast
285
286
# Create complex mathematical expressions
287
complex_expr = ast.BinaryExpression(
288
op=ast.BinaryOperator["+"],
289
lhs=ast.BinaryExpression(
290
op=ast.BinaryOperator["*"],
291
lhs=ast.Identifier("a"),
292
rhs=ast.BinaryExpression(
293
op=ast.BinaryOperator["**"],
294
lhs=ast.Identifier("b"),
295
rhs=ast.IntegerLiteral(2)
296
)
297
),
298
rhs=ast.UnaryExpression(
299
op=ast.UnaryOperator["-"],
300
expression=ast.FunctionCall(
301
name=ast.Identifier("sin"),
302
arguments=[ast.Identifier("theta")]
303
)
304
)
305
)
306
307
# The printer handles precedence automatically
308
expr_code = openqasm3.dumps(complex_expr)
309
print(f"Expression: {expr_code}")
310
# Output: a * b ** 2 + -sin(theta)
311
```
312
313
### Stream-Based Output
314
315
```python
316
import openqasm3
317
from openqasm3 import ast
318
import io
319
320
# Create a program
321
program = ast.Program(
322
version="3.0",
323
statements=[
324
ast.Include(filename="stdgates.inc"),
325
ast.QubitDeclaration(qubit=ast.Identifier("q"), size=ast.IntegerLiteral(3))
326
]
327
)
328
329
# Write to different types of streams
330
output_buffer = io.StringIO()
331
openqasm3.dump(program, output_buffer)
332
result = output_buffer.getvalue()
333
print("Buffer output:", result)
334
335
# Write to file with custom formatting
336
with open('formatted.qasm', 'w') as f:
337
openqasm3.dump(program, f, indent="\t") # Use tabs for indentation
338
339
# Write large programs piece by piece
340
from openqasm3.printer import Printer, PrinterState
341
342
output = io.StringIO()
343
printer = Printer(output, indent=" ")
344
state = PrinterState()
345
346
# Write program header
347
printer.visit(ast.Program(version="3.0", statements=[]), state)
348
349
# Write statements one by one
350
for i in range(10):
351
gate = ast.QuantumGate(
352
modifiers=[],
353
name=ast.Identifier("h"),
354
arguments=[],
355
qubits=[ast.IndexedIdentifier(
356
name=ast.Identifier("q"),
357
indices=[[ast.IntegerLiteral(i)]]
358
)]
359
)
360
printer.visit(gate, state)
361
362
print("Incremental output:", output.getvalue())
363
```
364
365
### Custom Printer Extension
366
367
```python
368
from openqasm3.printer import Printer, PrinterState
369
from openqasm3 import ast
370
import io
371
372
class CustomPrinter(Printer):
373
"""Custom printer with additional formatting rules"""
374
375
def visit_QuantumGate(self, node, context):
376
"""Custom formatting for quantum gates"""
377
# Add comments before certain gates
378
if node.name.name in ['h', 'x', 'y', 'z']:
379
self.stream.write(f"// Pauli gate: {node.name.name}\n")
380
self.stream.write(" " * (context.current_indent * len(self.indent)))
381
382
# Use parent implementation for actual gate printing
383
super().visit_QuantumGate(node, context)
384
385
def visit_QubitDeclaration(self, node, context):
386
"""Add extra information for qubit declarations"""
387
super().visit_QubitDeclaration(node, context)
388
389
# Add comment with qubit count
390
if node.size and isinstance(node.size, ast.IntegerLiteral):
391
self.stream.write(f" // {node.size.value} qubits declared")
392
393
# Use custom printer
394
program = ast.Program(
395
version="3.0",
396
statements=[
397
ast.QubitDeclaration(qubit=ast.Identifier("q"), size=ast.IntegerLiteral(2)),
398
ast.QuantumGate(
399
modifiers=[],
400
name=ast.Identifier("h"),
401
arguments=[],
402
qubits=[ast.Identifier("q")]
403
)
404
]
405
)
406
407
output = io.StringIO()
408
printer = CustomPrinter(output)
409
printer.visit(program)
410
411
print("Custom formatted output:")
412
print(output.getvalue())
413
```
414
415
### Error Handling and Validation
416
417
```python
418
import openqasm3
419
from openqasm3 import ast
420
421
def safe_code_generation(node):
422
"""Generate code with error handling"""
423
try:
424
code = openqasm3.dumps(node)
425
return code, None
426
except Exception as e:
427
return None, str(e)
428
429
# Test with valid node
430
valid_node = ast.IntegerLiteral(42)
431
code, error = safe_code_generation(valid_node)
432
if error:
433
print(f"Error: {error}")
434
else:
435
print(f"Generated: {code}")
436
437
# Test with incomplete node (missing required fields)
438
try:
439
incomplete_node = ast.QuantumGate(
440
modifiers=[],
441
name=None, # Invalid: name cannot be None
442
arguments=[],
443
qubits=[]
444
)
445
code = openqasm3.dumps(incomplete_node)
446
except Exception as e:
447
print(f"Caught error with incomplete node: {e}")
448
```
449
450
## Formatting Options
451
452
The printer supports several formatting options through keyword arguments:
453
454
- **indent**: String used for each indentation level (default: " ")
455
- **chain_else_if**: Whether to format nested if-else as "else if" (default: True)
456
- **old_measurement**: Use OpenQASM 2 arrow syntax for measurements (default: False)
457
458
## Language Support
459
460
The code generation system supports the complete OpenQASM 3 specification:
461
462
- **All statement types**: declarations, gates, control flow, subroutines
463
- **Complete expression system**: literals, operators, function calls, casts
464
- **Type annotations**: all classical and quantum types
465
- **Timing constructs**: delays, boxes, duration literals
466
- **Calibration blocks**: defcal, defcalgrammar, cal statements
467
- **Advanced features**: includes, pragmas, annotations, modifiers
468
469
## Round-Trip Guarantee
470
471
The printer is designed to produce valid OpenQASM 3 code that can be parsed back into equivalent AST structures:
472
473
```python
474
# Original program
475
original = '''
476
OPENQASM 3.0;
477
qubit[2] q;
478
h q[0];
479
cx q[0], q[1];
480
'''
481
482
# Parse to AST
483
program = openqasm3.parse(original)
484
485
# Generate code
486
generated = openqasm3.dumps(program)
487
488
# Parse again
489
reparsed = openqasm3.parse(generated)
490
491
# The AST structures should be equivalent
492
assert program == reparsed # (ignoring span information)
493
```