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

utilities.mddocs/

Utilities and Metadata

Expression precedence handling, specification version metadata, and other utility functions for working with OpenQASM 3 ASTs. These utilities provide essential support for expression parsing, language versioning, and AST manipulation.

Capabilities

Expression Precedence

Functions and constants for handling operator precedence in expressions to ensure proper parenthesization and evaluation order.

def precedence(node_type: type) -> int:
    """
    Get the precedence level for an expression node type.
    
    Higher numbers indicate higher precedence (tighter binding).
    Used by the printer to determine when parentheses are needed
    around subexpressions.
    
    Args:
        node_type: AST node class (e.g., ast.BinaryExpression, ast.FunctionCall)
    
    Returns:
        Integer precedence level (0-14, where 14 is highest precedence)
    
    Examples:
        precedence(ast.FunctionCall) -> 13  # High precedence
        precedence(ast.BinaryExpression) -> varies by operator
        precedence(ast.Identifier) -> 14    # Highest precedence
    """

Specification Metadata

Constants and information about supported OpenQASM 3 specification versions.

supported_versions: List[str]
"""
A list of specification versions supported by this package.
Each version is a string, e.g., '3.0', '3.1'.

Current supported versions: ["3.0", "3.1"]

This list indicates which OpenQASM 3 language specification
versions are fully supported by the AST and parser.
"""

Precedence Constants

Internal precedence table mapping expression types to precedence levels.

# Expression precedence levels (from lowest to highest):
# 0  - Concatenation (++)
# 1  - Logical OR (||)
# 2  - Logical AND (&&)
# 3  - Bitwise OR (|)
# 4  - Bitwise XOR (^)
# 5  - Bitwise AND (&)
# 6  - Equality (==, !=)
# 7  - Comparison (<, <=, >, >=)
# 8  - Bit shifts (<<, >>)
# 9  - Addition/Subtraction (+, -)
# 10 - Multiplication/Division/Modulo (*, /, %)
# 11 - Power (**), Unary operators (~, !, -)
# 12 - Reserved for future use
# 13 - Function-like operations (function calls, indexing, casting, durationof)
# 14 - Literals and identifiers (highest precedence)

Usage Examples

Expression Precedence in Practice

from openqasm3 import ast, properties
import openqasm3

# Understanding precedence levels
print(f"Identifier precedence: {properties.precedence(ast.Identifier)}")
print(f"Function call precedence: {properties.precedence(ast.FunctionCall)}")
print(f"Binary expression precedence: {properties.precedence(ast.BinaryExpression)}")
print(f"Unary expression precedence: {properties.precedence(ast.UnaryExpression)}")

# Create expressions that demonstrate precedence
# a + b * c should be parsed as a + (b * c)
expr1 = ast.BinaryExpression(
    op=ast.BinaryOperator["+"],
    lhs=ast.Identifier("a"),
    rhs=ast.BinaryExpression(
        op=ast.BinaryOperator["*"],
        lhs=ast.Identifier("b"),
        rhs=ast.Identifier("c")
    )
)

# When printed, this will not need parentheses around b * c
# because multiplication has higher precedence than addition
code1 = openqasm3.dumps(expr1)
print(f"Expression 1: {code1}")  # Output: a + b * c

# But (a + b) * c needs parentheses
expr2 = ast.BinaryExpression(
    op=ast.BinaryOperator["*"],
    lhs=ast.BinaryExpression(
        op=ast.BinaryOperator["+"],
        lhs=ast.Identifier("a"),
        rhs=ast.Identifier("b")
    ),
    rhs=ast.Identifier("c")
)

code2 = openqasm3.dumps(expr2)
print(f"Expression 2: {code2}")  # Output: (a + b) * c

Version Compatibility Checking

from openqasm3 import spec
import openqasm3

def check_version_support(version_string):
    """Check if a version is supported by this package"""
    if version_string in spec.supported_versions:
        print(f"Version {version_string} is supported")
        return True
    else:
        print(f"Version {version_string} is not supported")
        print(f"Supported versions: {spec.supported_versions}")
        return False

# Check various versions
check_version_support("3.0")    # True
check_version_support("3.1")    # True
check_version_support("2.0")    # False
check_version_support("3.2")    # False

# Parse programs with version checking
def safe_parse_with_version_check(qasm_source):
    """Parse with version validation"""
    try:
        program = openqasm3.parse(qasm_source)
        
        if program.version:
            if program.version in spec.supported_versions:
                print(f"Successfully parsed OpenQASM {program.version} program")
                return program
            else:
                print(f"Warning: Program uses unsupported version {program.version}")
                print(f"Supported versions: {spec.supported_versions}")
                return program
        else:
            print("Program has no version declaration")
            return program
            
    except Exception as e:
        print(f"Parsing failed: {e}")
        return None

# Test with different versions
program1 = safe_parse_with_version_check("OPENQASM 3.0;\nqubit q;")
program2 = safe_parse_with_version_check("OPENQASM 3.1;\nqubit q;")
program3 = safe_parse_with_version_check("OPENQASM 2.0;\nqubit q;")

Custom Precedence Handling

from openqasm3 import ast, properties
import openqasm3

class ExpressionAnalyzer:
    """Analyze expression complexity based on precedence"""
    
    def __init__(self):
        self.complexity_score = 0
        self.operator_counts = {}
    
    def analyze_expression(self, expr):
        """Analyze an expression's complexity"""
        self.complexity_score = 0
        self.operator_counts = {}
        self._visit_expression(expr)
        return {
            'complexity': self.complexity_score,
            'operators': self.operator_counts
        }
    
    def _visit_expression(self, expr):
        """Recursively analyze expression nodes"""
        expr_type = type(expr)
        precedence_level = properties.precedence(expr_type)
        
        # Higher precedence (tighter binding) contributes less to complexity
        complexity_contribution = max(1, 15 - precedence_level)
        self.complexity_score += complexity_contribution
        
        if isinstance(expr, ast.BinaryExpression):
            op_name = expr.op.name
            self.operator_counts[op_name] = self.operator_counts.get(op_name, 0) + 1
            self._visit_expression(expr.lhs)
            self._visit_expression(expr.rhs)
        
        elif isinstance(expr, ast.UnaryExpression):
            op_name = f"unary_{expr.op.name}"
            self.operator_counts[op_name] = self.operator_counts.get(op_name, 0) + 1
            self._visit_expression(expr.expression)
        
        elif isinstance(expr, ast.FunctionCall):
            self.operator_counts['function_call'] = self.operator_counts.get('function_call', 0) + 1
            for arg in expr.arguments:
                self._visit_expression(arg)
        
        elif isinstance(expr, ast.IndexExpression):
            self.operator_counts['indexing'] = self.operator_counts.get('indexing', 0) + 1
            self._visit_expression(expr.collection)

# Test the analyzer
analyzer = ExpressionAnalyzer()

# Simple expression: a + b
simple_expr = ast.BinaryExpression(
    op=ast.BinaryOperator["+"],
    lhs=ast.Identifier("a"),
    rhs=ast.Identifier("b")
)
result1 = analyzer.analyze_expression(simple_expr)
print(f"Simple expression complexity: {result1}")

# Complex expression: sin(a * b) + sqrt(c ** 2 + d ** 2)
complex_expr = ast.BinaryExpression(
    op=ast.BinaryOperator["+"],
    lhs=ast.FunctionCall(
        name=ast.Identifier("sin"),
        arguments=[ast.BinaryExpression(
            op=ast.BinaryOperator["*"],
            lhs=ast.Identifier("a"),
            rhs=ast.Identifier("b")
        )]
    ),
    rhs=ast.FunctionCall(
        name=ast.Identifier("sqrt"),
        arguments=[ast.BinaryExpression(
            op=ast.BinaryOperator["+"],
            lhs=ast.BinaryExpression(
                op=ast.BinaryOperator["**"],
                lhs=ast.Identifier("c"),
                rhs=ast.IntegerLiteral(2)
            ),
            rhs=ast.BinaryExpression(
                op=ast.BinaryOperator["**"],
                lhs=ast.Identifier("d"),
                rhs=ast.IntegerLiteral(2)
            )
        )]
    )
)
result2 = analyzer.analyze_expression(complex_expr)
print(f"Complex expression complexity: {result2}")

Precedence-Aware Expression Building

from openqasm3 import ast, properties
import openqasm3

class ExpressionBuilder:
    """Build expressions with automatic precedence handling"""
    
    def needs_parentheses(self, parent_op, child_expr, is_right_operand=False):
        """Determine if child expression needs parentheses"""
        if not isinstance(child_expr, ast.BinaryExpression):
            return False
        
        parent_prec = self._get_binary_precedence(parent_op)
        child_prec = self._get_binary_precedence(child_expr.op)
        
        # Lower precedence always needs parentheses
        if child_prec < parent_prec:
            return True
        
        # Same precedence on right side of non-associative operators
        if child_prec == parent_prec and is_right_operand:
            # Right-associative operators like ** don't need parentheses
            if parent_op == ast.BinaryOperator["**"]:
                return False
            # Left-associative operators need parentheses on right
            return True
        
        return False
    
    def _get_binary_precedence(self, op):
        """Get precedence for binary operators"""
        precedence_map = {
            ast.BinaryOperator["||"]: 1,
            ast.BinaryOperator["&&"]: 2,
            ast.BinaryOperator["|"]: 3,
            ast.BinaryOperator["^"]: 4,
            ast.BinaryOperator["&"]: 5,
            ast.BinaryOperator["=="]: 6,
            ast.BinaryOperator["!="]: 6,
            ast.BinaryOperator["<"]: 7,
            ast.BinaryOperator["<="]: 7,
            ast.BinaryOperator[">"]: 7,
            ast.BinaryOperator[">="]: 7,
            ast.BinaryOperator["<<"]: 8,
            ast.BinaryOperator[">>"]: 8,
            ast.BinaryOperator["+"]: 9,
            ast.BinaryOperator["-"]: 9,
            ast.BinaryOperator["*"]: 10,
            ast.BinaryOperator["/"]: 10,
            ast.BinaryOperator["%"]: 10,
            ast.BinaryOperator["**"]: 11,
        }
        return precedence_map.get(op, 0)
    
    def build_expression_chain(self, *terms_and_ops):
        """Build a chain of binary expressions with proper precedence"""
        if len(terms_and_ops) < 3:
            return terms_and_ops[0] if terms_and_ops else None
        
        # Parse alternating terms and operators
        terms = terms_and_ops[::2]
        ops = terms_and_ops[1::2]
        
        # Build expression tree respecting precedence
        result = terms[0]
        for i, op in enumerate(ops):
            right_term = terms[i + 1]
            result = ast.BinaryExpression(
                op=op,
                lhs=result,
                rhs=right_term
            )
        
        return result

# Example usage
builder = ExpressionBuilder()

# Build: a + b * c - d
expr = builder.build_expression_chain(
    ast.Identifier("a"),
    ast.BinaryOperator["+"],
    ast.BinaryExpression(
        op=ast.BinaryOperator["*"],
        lhs=ast.Identifier("b"),
        rhs=ast.Identifier("c")
    ),
    ast.BinaryOperator["-"],
    ast.Identifier("d")
)

code = openqasm3.dumps(expr)
print(f"Built expression: {code}")

# Test parentheses detection
test_expr = ast.BinaryExpression(
    op=ast.BinaryOperator["*"],
    lhs=ast.Identifier("x"),
    rhs=ast.Identifier("y")
)

needs_parens = builder.needs_parentheses(
    ast.BinaryOperator["+"], 
    test_expr, 
    is_right_operand=True
)
print(f"x * y needs parentheses in (? + x * y): {needs_parens}")

Utility Functions for AST Manipulation

from openqasm3 import ast, properties
import openqasm3

def simplify_expression(expr):
    """Simplify expressions using precedence rules"""
    if isinstance(expr, ast.BinaryExpression):
        # Simplify operands first
        left = simplify_expression(expr.lhs)
        right = simplify_expression(expr.rhs)
        
        # Apply simplification rules
        if expr.op == ast.BinaryOperator["+"]:
            # 0 + x = x, x + 0 = x
            if isinstance(left, ast.IntegerLiteral) and left.value == 0:
                return right
            if isinstance(right, ast.IntegerLiteral) and right.value == 0:
                return left
        
        elif expr.op == ast.BinaryOperator["*"]:
            # 1 * x = x, x * 1 = x
            if isinstance(left, ast.IntegerLiteral) and left.value == 1:
                return right
            if isinstance(right, ast.IntegerLiteral) and right.value == 1:
                return left
            # 0 * x = 0, x * 0 = 0
            if isinstance(left, ast.IntegerLiteral) and left.value == 0:
                return ast.IntegerLiteral(0)
            if isinstance(right, ast.IntegerLiteral) and right.value == 0:
                return ast.IntegerLiteral(0)
        
        # Return with simplified operands
        return ast.BinaryExpression(op=expr.op, lhs=left, rhs=right)
    
    return expr

# Test simplification
original = ast.BinaryExpression(
    op=ast.BinaryOperator["+"],
    lhs=ast.BinaryExpression(
        op=ast.BinaryOperator["*"],
        lhs=ast.Identifier("x"),
        rhs=ast.IntegerLiteral(1)
    ),
    rhs=ast.IntegerLiteral(0)
)

simplified = simplify_expression(original)
print(f"Original: {openqasm3.dumps(original)}")
print(f"Simplified: {openqasm3.dumps(simplified)}")

Specification Support

The utilities module ensures compatibility with multiple OpenQASM 3 specification versions:

  • Version 3.0: Core language features, basic quantum and classical operations
  • Version 3.1: Extended features including additional timing constructs and calibration enhancements

Future versions will be added to supported_versions as they are implemented and tested.

Integration with Other Modules

The utilities work closely with other package modules:

  • Parser: Uses precedence information for correct expression parsing
  • Printer: Uses precedence to determine parenthesization needs
  • AST: Provides precedence for all expression node types
  • Visitor: Can use precedence for expression analysis and transformation

These utilities form the foundation for correct OpenQASM 3 language processing throughout the package.

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