CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-openqasm3

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

Pending
Overview
Eval results
Files

code-generation.mddocs/

Code Generation

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.

Capabilities

High-Level Functions

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
    """

Printer State Management

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)
        """

Main Printer Class

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)
        """

Specialized Visitor Methods

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: ...

Usage Examples

Basic Code Generation

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)

Custom Formatting Options

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)

Working with Complex Expressions

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)

Stream-Based Output

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())

Custom Printer Extension

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())

Error Handling and Validation

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}")

Formatting Options

The printer supports several formatting options through keyword arguments:

  • indent: String used for each indentation level (default: " ")
  • chain_else_if: Whether to format nested if-else as "else if" (default: True)
  • old_measurement: Use OpenQASM 2 arrow syntax for measurements (default: False)

Language Support

The code generation system supports the complete OpenQASM 3 specification:

  • All statement types: declarations, gates, control flow, subroutines
  • Complete expression system: literals, operators, function calls, casts
  • Type annotations: all classical and quantum types
  • Timing constructs: delays, boxes, duration literals
  • Calibration blocks: defcal, defcalgrammar, cal statements
  • Advanced features: includes, pragmas, annotations, modifiers

Round-Trip Guarantee

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

docs

ast-nodes.md

code-generation.md

index.md

parser.md

utilities.md

visitors.md

tile.json