or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-nodes.mdcode-generation.mdindex.mdparser.mdutilities.mdvisitors.md

utilities.mddocs/

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.