Reference OpenQASM AST in Python - contains the reference abstract syntax tree (AST) for representing OpenQASM 3 programs, tools to parse text into this AST, and tools to manipulate the AST
—
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.
Simple functions for converting AST nodes to OpenQASM 3 text.
def dump(node: ast.QASMNode, file: io.TextIOBase, **kwargs) -> None:
"""
Write textual OpenQASM 3 code representing an AST node to an open stream.
Args:
node: AST node to convert (typically ast.Program but can be any node)
file: Open text stream to write to (file object, StringIO, etc.)
**kwargs: Formatting options passed to Printer constructor
Raises:
ValueError: If node cannot be converted to valid OpenQASM 3
"""
def dumps(node: ast.QASMNode, **kwargs) -> str:
"""
Get a string representation of OpenQASM 3 code from an AST node.
Args:
node: AST node to convert
**kwargs: Formatting options passed to Printer constructor
Returns:
String containing valid OpenQASM 3 code
Raises:
ValueError: If node cannot be converted to valid OpenQASM 3
"""State object for tracking formatting context during code generation.
@dataclass
class PrinterState:
"""
State object for the print visitor that tracks indentation and formatting context.
This object is mutated during the printing process to maintain proper
indentation levels and handle special formatting cases.
"""
current_indent: int = 0
"""Current indentation level (number of indent steps)"""
skip_next_indent: bool = False
"""Used for chained 'else if' formatting to avoid extra indentation"""
def increase_scope(self):
"""
Context manager to temporarily increase indentation level.
Usage:
with state.increase_scope():
# Code here will be indented one level deeper
printer.visit_some_node(node, state)
"""Comprehensive AST visitor for converting nodes to OpenQASM 3 text with configurable formatting.
class Printer(QASMVisitor[PrinterState]):
"""
Internal AST visitor for writing AST nodes as valid OpenQASM 3 text.
This class inherits from QASMVisitor and provides specialized visitor
methods for every AST node type. It handles proper precedence, formatting,
and syntax generation for the complete OpenQASM 3 language.
"""
def __init__(
self,
stream: io.TextIOBase,
indent: str = " ",
chain_else_if: bool = True,
old_measurement: bool = False
):
"""
Initialize the printer with formatting options.
Args:
stream: Output stream to write to
indent: Indentation string (default: two spaces)
chain_else_if: Whether to flatten nested if-else into 'else if' format
old_measurement: Use OpenQASM 2 arrow syntax for measurements
"""
def visit(self, node: ast.QASMNode, context: Optional[PrinterState] = None) -> None:
"""
Main entry point for visiting nodes and generating code.
Args:
node: AST node to convert to text
context: Optional printer state (created automatically if None)
"""The Printer class provides visitor methods for all AST node types.
# Program structure
def visit_Program(self, node: ast.Program, context: PrinterState) -> None: ...
def visit_CompoundStatement(self, node: ast.CompoundStatement, context: PrinterState) -> None: ...
# Declarations
def visit_QubitDeclaration(self, node: ast.QubitDeclaration, context: PrinterState) -> None: ...
def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: PrinterState) -> None: ...
def visit_ConstantDeclaration(self, node: ast.ConstantDeclaration, context: PrinterState) -> None: ...
def visit_IODeclaration(self, node: ast.IODeclaration, context: PrinterState) -> None: ...
def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: PrinterState) -> None: ...
# Quantum elements
def visit_QuantumGateDefinition(self, node: ast.QuantumGateDefinition, context: PrinterState) -> None: ...
def visit_QuantumGate(self, node: ast.QuantumGate, context: PrinterState) -> None: ...
def visit_QuantumPhase(self, node: ast.QuantumPhase, context: PrinterState) -> None: ...
def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: PrinterState) -> None: ...
def visit_QuantumReset(self, node: ast.QuantumReset, context: PrinterState) -> None: ...
def visit_QuantumMeasurement(self, node: ast.QuantumMeasurement, context: PrinterState) -> None: ...
# Control flow
def visit_BranchingStatement(self, node: ast.BranchingStatement, context: PrinterState) -> None: ...
def visit_WhileLoop(self, node: ast.WhileLoop, context: PrinterState) -> None: ...
def visit_ForInLoop(self, node: ast.ForInLoop, context: PrinterState) -> None: ...
def visit_SwitchStatement(self, node: ast.SwitchStatement, context: PrinterState) -> None: ...
# Subroutines and functions
def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: PrinterState) -> None: ...
def visit_ReturnStatement(self, node: ast.ReturnStatement, context: PrinterState) -> None: ...
def visit_FunctionCall(self, node: ast.FunctionCall, context: PrinterState) -> None: ...
# Expressions
def visit_BinaryExpression(self, node: ast.BinaryExpression, context: PrinterState) -> None: ...
def visit_UnaryExpression(self, node: ast.UnaryExpression, context: PrinterState) -> None: ...
def visit_IndexExpression(self, node: ast.IndexExpression, context: PrinterState) -> None: ...
def visit_Cast(self, node: ast.Cast, context: PrinterState) -> None: ...
def visit_Concatenation(self, node: ast.Concatenation, context: PrinterState) -> None: ...
# Literals
def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: PrinterState) -> None: ...
def visit_FloatLiteral(self, node: ast.FloatLiteral, context: PrinterState) -> None: ...
def visit_ImaginaryLiteral(self, node: ast.ImaginaryLiteral, context: PrinterState) -> None: ...
def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: PrinterState) -> None: ...
def visit_BitstringLiteral(self, node: ast.BitstringLiteral, context: PrinterState) -> None: ...
def visit_DurationLiteral(self, node: ast.DurationLiteral, context: PrinterState) -> None: ...
def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: PrinterState) -> None: ...
# Types
def visit_IntType(self, node: ast.IntType, context: PrinterState) -> None: ...
def visit_FloatType(self, node: ast.FloatType, context: PrinterState) -> None: ...
def visit_ComplexType(self, node: ast.ComplexType, context: PrinterState) -> None: ...
def visit_ArrayType(self, node: ast.ArrayType, context: PrinterState) -> None: ...
# ... and many more type visitor methods
# Timing and calibration
def visit_DelayInstruction(self, node: ast.DelayInstruction, context: PrinterState) -> None: ...
def visit_Box(self, node: ast.Box, context: PrinterState) -> None: ...
def visit_CalibrationDefinition(self, node: ast.CalibrationDefinition, context: PrinterState) -> None: ...import openqasm3
from openqasm3 import ast
import io
# Create an AST programmatically
program = ast.Program(
version="3.0",
statements=[
ast.QubitDeclaration(
qubit=ast.Identifier("q"),
size=ast.IntegerLiteral(2)
),
ast.QuantumGate(
modifiers=[],
name=ast.Identifier("h"),
arguments=[],
qubits=[ast.IndexedIdentifier(
name=ast.Identifier("q"),
indices=[[ast.IntegerLiteral(0)]]
)]
)
]
)
# Convert to string
qasm_code = openqasm3.dumps(program)
print(qasm_code)
# Output:
# OPENQASM 3.0;
# qubit[2] q;
# h q[0];
# Write to file
with open('output.qasm', 'w') as f:
openqasm3.dump(program, f)import openqasm3
from openqasm3 import ast
# Create a program with nested control flow
program = ast.Program(
version="3.0",
statements=[
ast.ClassicalDeclaration(
type=ast.IntType(),
identifier=ast.Identifier("x"),
init_expression=ast.IntegerLiteral(5)
),
ast.BranchingStatement(
condition=ast.BinaryExpression(
op=ast.BinaryOperator[">"],
lhs=ast.Identifier("x"),
rhs=ast.IntegerLiteral(0)
),
if_block=[
ast.ExpressionStatement(
expression=ast.FunctionCall(
name=ast.Identifier("print"),
arguments=[ast.Identifier("x")]
)
)
],
else_block=[
ast.BranchingStatement(
condition=ast.BinaryExpression(
op=ast.BinaryOperator["<"],
lhs=ast.Identifier("x"),
rhs=ast.IntegerLiteral(0)
),
if_block=[
ast.ExpressionStatement(
expression=ast.FunctionCall(
name=ast.Identifier("print"),
arguments=[ast.StringLiteral("negative")]
)
)
],
else_block=[]
)
]
)
]
)
# Default formatting (chained else-if)
default_code = openqasm3.dumps(program)
print("Default formatting:")
print(default_code)
# Custom formatting with different indentation
custom_code = openqasm3.dumps(program, indent=" ", chain_else_if=False)
print("\nCustom formatting:")
print(custom_code)import openqasm3
from openqasm3 import ast
# Create complex mathematical expressions
complex_expr = ast.BinaryExpression(
op=ast.BinaryOperator["+"],
lhs=ast.BinaryExpression(
op=ast.BinaryOperator["*"],
lhs=ast.Identifier("a"),
rhs=ast.BinaryExpression(
op=ast.BinaryOperator["**"],
lhs=ast.Identifier("b"),
rhs=ast.IntegerLiteral(2)
)
),
rhs=ast.UnaryExpression(
op=ast.UnaryOperator["-"],
expression=ast.FunctionCall(
name=ast.Identifier("sin"),
arguments=[ast.Identifier("theta")]
)
)
)
# The printer handles precedence automatically
expr_code = openqasm3.dumps(complex_expr)
print(f"Expression: {expr_code}")
# Output: a * b ** 2 + -sin(theta)import openqasm3
from openqasm3 import ast
import io
# Create a program
program = ast.Program(
version="3.0",
statements=[
ast.Include(filename="stdgates.inc"),
ast.QubitDeclaration(qubit=ast.Identifier("q"), size=ast.IntegerLiteral(3))
]
)
# Write to different types of streams
output_buffer = io.StringIO()
openqasm3.dump(program, output_buffer)
result = output_buffer.getvalue()
print("Buffer output:", result)
# Write to file with custom formatting
with open('formatted.qasm', 'w') as f:
openqasm3.dump(program, f, indent="\t") # Use tabs for indentation
# Write large programs piece by piece
from openqasm3.printer import Printer, PrinterState
output = io.StringIO()
printer = Printer(output, indent=" ")
state = PrinterState()
# Write program header
printer.visit(ast.Program(version="3.0", statements=[]), state)
# Write statements one by one
for i in range(10):
gate = ast.QuantumGate(
modifiers=[],
name=ast.Identifier("h"),
arguments=[],
qubits=[ast.IndexedIdentifier(
name=ast.Identifier("q"),
indices=[[ast.IntegerLiteral(i)]]
)]
)
printer.visit(gate, state)
print("Incremental output:", output.getvalue())from openqasm3.printer import Printer, PrinterState
from openqasm3 import ast
import io
class CustomPrinter(Printer):
"""Custom printer with additional formatting rules"""
def visit_QuantumGate(self, node, context):
"""Custom formatting for quantum gates"""
# Add comments before certain gates
if node.name.name in ['h', 'x', 'y', 'z']:
self.stream.write(f"// Pauli gate: {node.name.name}\n")
self.stream.write(" " * (context.current_indent * len(self.indent)))
# Use parent implementation for actual gate printing
super().visit_QuantumGate(node, context)
def visit_QubitDeclaration(self, node, context):
"""Add extra information for qubit declarations"""
super().visit_QubitDeclaration(node, context)
# Add comment with qubit count
if node.size and isinstance(node.size, ast.IntegerLiteral):
self.stream.write(f" // {node.size.value} qubits declared")
# Use custom printer
program = ast.Program(
version="3.0",
statements=[
ast.QubitDeclaration(qubit=ast.Identifier("q"), size=ast.IntegerLiteral(2)),
ast.QuantumGate(
modifiers=[],
name=ast.Identifier("h"),
arguments=[],
qubits=[ast.Identifier("q")]
)
]
)
output = io.StringIO()
printer = CustomPrinter(output)
printer.visit(program)
print("Custom formatted output:")
print(output.getvalue())import openqasm3
from openqasm3 import ast
def safe_code_generation(node):
"""Generate code with error handling"""
try:
code = openqasm3.dumps(node)
return code, None
except Exception as e:
return None, str(e)
# Test with valid node
valid_node = ast.IntegerLiteral(42)
code, error = safe_code_generation(valid_node)
if error:
print(f"Error: {error}")
else:
print(f"Generated: {code}")
# Test with incomplete node (missing required fields)
try:
incomplete_node = ast.QuantumGate(
modifiers=[],
name=None, # Invalid: name cannot be None
arguments=[],
qubits=[]
)
code = openqasm3.dumps(incomplete_node)
except Exception as e:
print(f"Caught error with incomplete node: {e}")The printer supports several formatting options through keyword arguments:
The code generation system supports the complete OpenQASM 3 specification:
The printer is designed to produce valid OpenQASM 3 code that can be parsed back into equivalent AST structures:
# Original program
original = '''
OPENQASM 3.0;
qubit[2] q;
h q[0];
cx q[0], q[1];
'''
# Parse to AST
program = openqasm3.parse(original)
# Generate code
generated = openqasm3.dumps(program)
# Parse again
reparsed = openqasm3.parse(generated)
# The AST structures should be equivalent
assert program == reparsed # (ignoring span information)Install with Tessl CLI
npx tessl i tessl/pypi-openqasm3