0
# Utilities and Metadata
1
2
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.
3
4
## Capabilities
5
6
### Expression Precedence
7
8
Functions and constants for handling operator precedence in expressions to ensure proper parenthesization and evaluation order.
9
10
```python { .api }
11
def precedence(node_type: type) -> int:
12
"""
13
Get the precedence level for an expression node type.
14
15
Higher numbers indicate higher precedence (tighter binding).
16
Used by the printer to determine when parentheses are needed
17
around subexpressions.
18
19
Args:
20
node_type: AST node class (e.g., ast.BinaryExpression, ast.FunctionCall)
21
22
Returns:
23
Integer precedence level (0-14, where 14 is highest precedence)
24
25
Examples:
26
precedence(ast.FunctionCall) -> 13 # High precedence
27
precedence(ast.BinaryExpression) -> varies by operator
28
precedence(ast.Identifier) -> 14 # Highest precedence
29
"""
30
```
31
32
### Specification Metadata
33
34
Constants and information about supported OpenQASM 3 specification versions.
35
36
```python { .api }
37
supported_versions: List[str]
38
"""
39
A list of specification versions supported by this package.
40
Each version is a string, e.g., '3.0', '3.1'.
41
42
Current supported versions: ["3.0", "3.1"]
43
44
This list indicates which OpenQASM 3 language specification
45
versions are fully supported by the AST and parser.
46
"""
47
```
48
49
### Precedence Constants
50
51
Internal precedence table mapping expression types to precedence levels.
52
53
```python { .api }
54
# Expression precedence levels (from lowest to highest):
55
# 0 - Concatenation (++)
56
# 1 - Logical OR (||)
57
# 2 - Logical AND (&&)
58
# 3 - Bitwise OR (|)
59
# 4 - Bitwise XOR (^)
60
# 5 - Bitwise AND (&)
61
# 6 - Equality (==, !=)
62
# 7 - Comparison (<, <=, >, >=)
63
# 8 - Bit shifts (<<, >>)
64
# 9 - Addition/Subtraction (+, -)
65
# 10 - Multiplication/Division/Modulo (*, /, %)
66
# 11 - Power (**), Unary operators (~, !, -)
67
# 12 - Reserved for future use
68
# 13 - Function-like operations (function calls, indexing, casting, durationof)
69
# 14 - Literals and identifiers (highest precedence)
70
```
71
72
## Usage Examples
73
74
### Expression Precedence in Practice
75
76
```python
77
from openqasm3 import ast, properties
78
import openqasm3
79
80
# Understanding precedence levels
81
print(f"Identifier precedence: {properties.precedence(ast.Identifier)}")
82
print(f"Function call precedence: {properties.precedence(ast.FunctionCall)}")
83
print(f"Binary expression precedence: {properties.precedence(ast.BinaryExpression)}")
84
print(f"Unary expression precedence: {properties.precedence(ast.UnaryExpression)}")
85
86
# Create expressions that demonstrate precedence
87
# a + b * c should be parsed as a + (b * c)
88
expr1 = ast.BinaryExpression(
89
op=ast.BinaryOperator["+"],
90
lhs=ast.Identifier("a"),
91
rhs=ast.BinaryExpression(
92
op=ast.BinaryOperator["*"],
93
lhs=ast.Identifier("b"),
94
rhs=ast.Identifier("c")
95
)
96
)
97
98
# When printed, this will not need parentheses around b * c
99
# because multiplication has higher precedence than addition
100
code1 = openqasm3.dumps(expr1)
101
print(f"Expression 1: {code1}") # Output: a + b * c
102
103
# But (a + b) * c needs parentheses
104
expr2 = ast.BinaryExpression(
105
op=ast.BinaryOperator["*"],
106
lhs=ast.BinaryExpression(
107
op=ast.BinaryOperator["+"],
108
lhs=ast.Identifier("a"),
109
rhs=ast.Identifier("b")
110
),
111
rhs=ast.Identifier("c")
112
)
113
114
code2 = openqasm3.dumps(expr2)
115
print(f"Expression 2: {code2}") # Output: (a + b) * c
116
```
117
118
### Version Compatibility Checking
119
120
```python
121
from openqasm3 import spec
122
import openqasm3
123
124
def check_version_support(version_string):
125
"""Check if a version is supported by this package"""
126
if version_string in spec.supported_versions:
127
print(f"Version {version_string} is supported")
128
return True
129
else:
130
print(f"Version {version_string} is not supported")
131
print(f"Supported versions: {spec.supported_versions}")
132
return False
133
134
# Check various versions
135
check_version_support("3.0") # True
136
check_version_support("3.1") # True
137
check_version_support("2.0") # False
138
check_version_support("3.2") # False
139
140
# Parse programs with version checking
141
def safe_parse_with_version_check(qasm_source):
142
"""Parse with version validation"""
143
try:
144
program = openqasm3.parse(qasm_source)
145
146
if program.version:
147
if program.version in spec.supported_versions:
148
print(f"Successfully parsed OpenQASM {program.version} program")
149
return program
150
else:
151
print(f"Warning: Program uses unsupported version {program.version}")
152
print(f"Supported versions: {spec.supported_versions}")
153
return program
154
else:
155
print("Program has no version declaration")
156
return program
157
158
except Exception as e:
159
print(f"Parsing failed: {e}")
160
return None
161
162
# Test with different versions
163
program1 = safe_parse_with_version_check("OPENQASM 3.0;\nqubit q;")
164
program2 = safe_parse_with_version_check("OPENQASM 3.1;\nqubit q;")
165
program3 = safe_parse_with_version_check("OPENQASM 2.0;\nqubit q;")
166
```
167
168
### Custom Precedence Handling
169
170
```python
171
from openqasm3 import ast, properties
172
import openqasm3
173
174
class ExpressionAnalyzer:
175
"""Analyze expression complexity based on precedence"""
176
177
def __init__(self):
178
self.complexity_score = 0
179
self.operator_counts = {}
180
181
def analyze_expression(self, expr):
182
"""Analyze an expression's complexity"""
183
self.complexity_score = 0
184
self.operator_counts = {}
185
self._visit_expression(expr)
186
return {
187
'complexity': self.complexity_score,
188
'operators': self.operator_counts
189
}
190
191
def _visit_expression(self, expr):
192
"""Recursively analyze expression nodes"""
193
expr_type = type(expr)
194
precedence_level = properties.precedence(expr_type)
195
196
# Higher precedence (tighter binding) contributes less to complexity
197
complexity_contribution = max(1, 15 - precedence_level)
198
self.complexity_score += complexity_contribution
199
200
if isinstance(expr, ast.BinaryExpression):
201
op_name = expr.op.name
202
self.operator_counts[op_name] = self.operator_counts.get(op_name, 0) + 1
203
self._visit_expression(expr.lhs)
204
self._visit_expression(expr.rhs)
205
206
elif isinstance(expr, ast.UnaryExpression):
207
op_name = f"unary_{expr.op.name}"
208
self.operator_counts[op_name] = self.operator_counts.get(op_name, 0) + 1
209
self._visit_expression(expr.expression)
210
211
elif isinstance(expr, ast.FunctionCall):
212
self.operator_counts['function_call'] = self.operator_counts.get('function_call', 0) + 1
213
for arg in expr.arguments:
214
self._visit_expression(arg)
215
216
elif isinstance(expr, ast.IndexExpression):
217
self.operator_counts['indexing'] = self.operator_counts.get('indexing', 0) + 1
218
self._visit_expression(expr.collection)
219
220
# Test the analyzer
221
analyzer = ExpressionAnalyzer()
222
223
# Simple expression: a + b
224
simple_expr = ast.BinaryExpression(
225
op=ast.BinaryOperator["+"],
226
lhs=ast.Identifier("a"),
227
rhs=ast.Identifier("b")
228
)
229
result1 = analyzer.analyze_expression(simple_expr)
230
print(f"Simple expression complexity: {result1}")
231
232
# Complex expression: sin(a * b) + sqrt(c ** 2 + d ** 2)
233
complex_expr = ast.BinaryExpression(
234
op=ast.BinaryOperator["+"],
235
lhs=ast.FunctionCall(
236
name=ast.Identifier("sin"),
237
arguments=[ast.BinaryExpression(
238
op=ast.BinaryOperator["*"],
239
lhs=ast.Identifier("a"),
240
rhs=ast.Identifier("b")
241
)]
242
),
243
rhs=ast.FunctionCall(
244
name=ast.Identifier("sqrt"),
245
arguments=[ast.BinaryExpression(
246
op=ast.BinaryOperator["+"],
247
lhs=ast.BinaryExpression(
248
op=ast.BinaryOperator["**"],
249
lhs=ast.Identifier("c"),
250
rhs=ast.IntegerLiteral(2)
251
),
252
rhs=ast.BinaryExpression(
253
op=ast.BinaryOperator["**"],
254
lhs=ast.Identifier("d"),
255
rhs=ast.IntegerLiteral(2)
256
)
257
)]
258
)
259
)
260
result2 = analyzer.analyze_expression(complex_expr)
261
print(f"Complex expression complexity: {result2}")
262
```
263
264
### Precedence-Aware Expression Building
265
266
```python
267
from openqasm3 import ast, properties
268
import openqasm3
269
270
class ExpressionBuilder:
271
"""Build expressions with automatic precedence handling"""
272
273
def needs_parentheses(self, parent_op, child_expr, is_right_operand=False):
274
"""Determine if child expression needs parentheses"""
275
if not isinstance(child_expr, ast.BinaryExpression):
276
return False
277
278
parent_prec = self._get_binary_precedence(parent_op)
279
child_prec = self._get_binary_precedence(child_expr.op)
280
281
# Lower precedence always needs parentheses
282
if child_prec < parent_prec:
283
return True
284
285
# Same precedence on right side of non-associative operators
286
if child_prec == parent_prec and is_right_operand:
287
# Right-associative operators like ** don't need parentheses
288
if parent_op == ast.BinaryOperator["**"]:
289
return False
290
# Left-associative operators need parentheses on right
291
return True
292
293
return False
294
295
def _get_binary_precedence(self, op):
296
"""Get precedence for binary operators"""
297
precedence_map = {
298
ast.BinaryOperator["||"]: 1,
299
ast.BinaryOperator["&&"]: 2,
300
ast.BinaryOperator["|"]: 3,
301
ast.BinaryOperator["^"]: 4,
302
ast.BinaryOperator["&"]: 5,
303
ast.BinaryOperator["=="]: 6,
304
ast.BinaryOperator["!="]: 6,
305
ast.BinaryOperator["<"]: 7,
306
ast.BinaryOperator["<="]: 7,
307
ast.BinaryOperator[">"]: 7,
308
ast.BinaryOperator[">="]: 7,
309
ast.BinaryOperator["<<"]: 8,
310
ast.BinaryOperator[">>"]: 8,
311
ast.BinaryOperator["+"]: 9,
312
ast.BinaryOperator["-"]: 9,
313
ast.BinaryOperator["*"]: 10,
314
ast.BinaryOperator["/"]: 10,
315
ast.BinaryOperator["%"]: 10,
316
ast.BinaryOperator["**"]: 11,
317
}
318
return precedence_map.get(op, 0)
319
320
def build_expression_chain(self, *terms_and_ops):
321
"""Build a chain of binary expressions with proper precedence"""
322
if len(terms_and_ops) < 3:
323
return terms_and_ops[0] if terms_and_ops else None
324
325
# Parse alternating terms and operators
326
terms = terms_and_ops[::2]
327
ops = terms_and_ops[1::2]
328
329
# Build expression tree respecting precedence
330
result = terms[0]
331
for i, op in enumerate(ops):
332
right_term = terms[i + 1]
333
result = ast.BinaryExpression(
334
op=op,
335
lhs=result,
336
rhs=right_term
337
)
338
339
return result
340
341
# Example usage
342
builder = ExpressionBuilder()
343
344
# Build: a + b * c - d
345
expr = builder.build_expression_chain(
346
ast.Identifier("a"),
347
ast.BinaryOperator["+"],
348
ast.BinaryExpression(
349
op=ast.BinaryOperator["*"],
350
lhs=ast.Identifier("b"),
351
rhs=ast.Identifier("c")
352
),
353
ast.BinaryOperator["-"],
354
ast.Identifier("d")
355
)
356
357
code = openqasm3.dumps(expr)
358
print(f"Built expression: {code}")
359
360
# Test parentheses detection
361
test_expr = ast.BinaryExpression(
362
op=ast.BinaryOperator["*"],
363
lhs=ast.Identifier("x"),
364
rhs=ast.Identifier("y")
365
)
366
367
needs_parens = builder.needs_parentheses(
368
ast.BinaryOperator["+"],
369
test_expr,
370
is_right_operand=True
371
)
372
print(f"x * y needs parentheses in (? + x * y): {needs_parens}")
373
```
374
375
### Utility Functions for AST Manipulation
376
377
```python
378
from openqasm3 import ast, properties
379
import openqasm3
380
381
def simplify_expression(expr):
382
"""Simplify expressions using precedence rules"""
383
if isinstance(expr, ast.BinaryExpression):
384
# Simplify operands first
385
left = simplify_expression(expr.lhs)
386
right = simplify_expression(expr.rhs)
387
388
# Apply simplification rules
389
if expr.op == ast.BinaryOperator["+"]:
390
# 0 + x = x, x + 0 = x
391
if isinstance(left, ast.IntegerLiteral) and left.value == 0:
392
return right
393
if isinstance(right, ast.IntegerLiteral) and right.value == 0:
394
return left
395
396
elif expr.op == ast.BinaryOperator["*"]:
397
# 1 * x = x, x * 1 = x
398
if isinstance(left, ast.IntegerLiteral) and left.value == 1:
399
return right
400
if isinstance(right, ast.IntegerLiteral) and right.value == 1:
401
return left
402
# 0 * x = 0, x * 0 = 0
403
if isinstance(left, ast.IntegerLiteral) and left.value == 0:
404
return ast.IntegerLiteral(0)
405
if isinstance(right, ast.IntegerLiteral) and right.value == 0:
406
return ast.IntegerLiteral(0)
407
408
# Return with simplified operands
409
return ast.BinaryExpression(op=expr.op, lhs=left, rhs=right)
410
411
return expr
412
413
# Test simplification
414
original = ast.BinaryExpression(
415
op=ast.BinaryOperator["+"],
416
lhs=ast.BinaryExpression(
417
op=ast.BinaryOperator["*"],
418
lhs=ast.Identifier("x"),
419
rhs=ast.IntegerLiteral(1)
420
),
421
rhs=ast.IntegerLiteral(0)
422
)
423
424
simplified = simplify_expression(original)
425
print(f"Original: {openqasm3.dumps(original)}")
426
print(f"Simplified: {openqasm3.dumps(simplified)}")
427
```
428
429
## Specification Support
430
431
The utilities module ensures compatibility with multiple OpenQASM 3 specification versions:
432
433
- **Version 3.0**: Core language features, basic quantum and classical operations
434
- **Version 3.1**: Extended features including additional timing constructs and calibration enhancements
435
436
Future versions will be added to `supported_versions` as they are implemented and tested.
437
438
## Integration with Other Modules
439
440
The utilities work closely with other package modules:
441
442
- **Parser**: Uses precedence information for correct expression parsing
443
- **Printer**: Uses precedence to determine parenthesization needs
444
- **AST**: Provides precedence for all expression node types
445
- **Visitor**: Can use precedence for expression analysis and transformation
446
447
These utilities form the foundation for correct OpenQASM 3 language processing throughout the package.