or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

code-generation.mddocs/

0

# Code Generation

1

2

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.

3

4

## Capabilities

5

6

### High-Level Functions

7

8

Simple functions for converting AST nodes to OpenQASM 3 text.

9

10

```python { .api }

11

def dump(node: ast.QASMNode, file: io.TextIOBase, **kwargs) -> None:

12

"""

13

Write textual OpenQASM 3 code representing an AST node to an open stream.

14

15

Args:

16

node: AST node to convert (typically ast.Program but can be any node)

17

file: Open text stream to write to (file object, StringIO, etc.)

18

**kwargs: Formatting options passed to Printer constructor

19

20

Raises:

21

ValueError: If node cannot be converted to valid OpenQASM 3

22

"""

23

24

def dumps(node: ast.QASMNode, **kwargs) -> str:

25

"""

26

Get a string representation of OpenQASM 3 code from an AST node.

27

28

Args:

29

node: AST node to convert

30

**kwargs: Formatting options passed to Printer constructor

31

32

Returns:

33

String containing valid OpenQASM 3 code

34

35

Raises:

36

ValueError: If node cannot be converted to valid OpenQASM 3

37

"""

38

```

39

40

### Printer State Management

41

42

State object for tracking formatting context during code generation.

43

44

```python { .api }

45

@dataclass

46

class PrinterState:

47

"""

48

State object for the print visitor that tracks indentation and formatting context.

49

50

This object is mutated during the printing process to maintain proper

51

indentation levels and handle special formatting cases.

52

"""

53

54

current_indent: int = 0

55

"""Current indentation level (number of indent steps)"""

56

57

skip_next_indent: bool = False

58

"""Used for chained 'else if' formatting to avoid extra indentation"""

59

60

def increase_scope(self):

61

"""

62

Context manager to temporarily increase indentation level.

63

64

Usage:

65

with state.increase_scope():

66

# Code here will be indented one level deeper

67

printer.visit_some_node(node, state)

68

"""

69

```

70

71

### Main Printer Class

72

73

Comprehensive AST visitor for converting nodes to OpenQASM 3 text with configurable formatting.

74

75

```python { .api }

76

class Printer(QASMVisitor[PrinterState]):

77

"""

78

Internal AST visitor for writing AST nodes as valid OpenQASM 3 text.

79

80

This class inherits from QASMVisitor and provides specialized visitor

81

methods for every AST node type. It handles proper precedence, formatting,

82

and syntax generation for the complete OpenQASM 3 language.

83

"""

84

85

def __init__(

86

self,

87

stream: io.TextIOBase,

88

indent: str = " ",

89

chain_else_if: bool = True,

90

old_measurement: bool = False

91

):

92

"""

93

Initialize the printer with formatting options.

94

95

Args:

96

stream: Output stream to write to

97

indent: Indentation string (default: two spaces)

98

chain_else_if: Whether to flatten nested if-else into 'else if' format

99

old_measurement: Use OpenQASM 2 arrow syntax for measurements

100

"""

101

102

def visit(self, node: ast.QASMNode, context: Optional[PrinterState] = None) -> None:

103

"""

104

Main entry point for visiting nodes and generating code.

105

106

Args:

107

node: AST node to convert to text

108

context: Optional printer state (created automatically if None)

109

"""

110

```

111

112

### Specialized Visitor Methods

113

114

The Printer class provides visitor methods for all AST node types.

115

116

```python { .api }

117

# Program structure

118

def visit_Program(self, node: ast.Program, context: PrinterState) -> None: ...

119

def visit_CompoundStatement(self, node: ast.CompoundStatement, context: PrinterState) -> None: ...

120

121

# Declarations

122

def visit_QubitDeclaration(self, node: ast.QubitDeclaration, context: PrinterState) -> None: ...

123

def visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration, context: PrinterState) -> None: ...

124

def visit_ConstantDeclaration(self, node: ast.ConstantDeclaration, context: PrinterState) -> None: ...

125

def visit_IODeclaration(self, node: ast.IODeclaration, context: PrinterState) -> None: ...

126

def visit_ExternDeclaration(self, node: ast.ExternDeclaration, context: PrinterState) -> None: ...

127

128

# Quantum elements

129

def visit_QuantumGateDefinition(self, node: ast.QuantumGateDefinition, context: PrinterState) -> None: ...

130

def visit_QuantumGate(self, node: ast.QuantumGate, context: PrinterState) -> None: ...

131

def visit_QuantumPhase(self, node: ast.QuantumPhase, context: PrinterState) -> None: ...

132

def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: PrinterState) -> None: ...

133

def visit_QuantumReset(self, node: ast.QuantumReset, context: PrinterState) -> None: ...

134

def visit_QuantumMeasurement(self, node: ast.QuantumMeasurement, context: PrinterState) -> None: ...

135

136

# Control flow

137

def visit_BranchingStatement(self, node: ast.BranchingStatement, context: PrinterState) -> None: ...

138

def visit_WhileLoop(self, node: ast.WhileLoop, context: PrinterState) -> None: ...

139

def visit_ForInLoop(self, node: ast.ForInLoop, context: PrinterState) -> None: ...

140

def visit_SwitchStatement(self, node: ast.SwitchStatement, context: PrinterState) -> None: ...

141

142

# Subroutines and functions

143

def visit_SubroutineDefinition(self, node: ast.SubroutineDefinition, context: PrinterState) -> None: ...

144

def visit_ReturnStatement(self, node: ast.ReturnStatement, context: PrinterState) -> None: ...

145

def visit_FunctionCall(self, node: ast.FunctionCall, context: PrinterState) -> None: ...

146

147

# Expressions

148

def visit_BinaryExpression(self, node: ast.BinaryExpression, context: PrinterState) -> None: ...

149

def visit_UnaryExpression(self, node: ast.UnaryExpression, context: PrinterState) -> None: ...

150

def visit_IndexExpression(self, node: ast.IndexExpression, context: PrinterState) -> None: ...

151

def visit_Cast(self, node: ast.Cast, context: PrinterState) -> None: ...

152

def visit_Concatenation(self, node: ast.Concatenation, context: PrinterState) -> None: ...

153

154

# Literals

155

def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: PrinterState) -> None: ...

156

def visit_FloatLiteral(self, node: ast.FloatLiteral, context: PrinterState) -> None: ...

157

def visit_ImaginaryLiteral(self, node: ast.ImaginaryLiteral, context: PrinterState) -> None: ...

158

def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: PrinterState) -> None: ...

159

def visit_BitstringLiteral(self, node: ast.BitstringLiteral, context: PrinterState) -> None: ...

160

def visit_DurationLiteral(self, node: ast.DurationLiteral, context: PrinterState) -> None: ...

161

def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: PrinterState) -> None: ...

162

163

# Types

164

def visit_IntType(self, node: ast.IntType, context: PrinterState) -> None: ...

165

def visit_FloatType(self, node: ast.FloatType, context: PrinterState) -> None: ...

166

def visit_ComplexType(self, node: ast.ComplexType, context: PrinterState) -> None: ...

167

def visit_ArrayType(self, node: ast.ArrayType, context: PrinterState) -> None: ...

168

# ... and many more type visitor methods

169

170

# Timing and calibration

171

def visit_DelayInstruction(self, node: ast.DelayInstruction, context: PrinterState) -> None: ...

172

def visit_Box(self, node: ast.Box, context: PrinterState) -> None: ...

173

def visit_CalibrationDefinition(self, node: ast.CalibrationDefinition, context: PrinterState) -> None: ...

174

```

175

176

## Usage Examples

177

178

### Basic Code Generation

179

180

```python

181

import openqasm3

182

from openqasm3 import ast

183

import io

184

185

# Create an AST programmatically

186

program = ast.Program(

187

version="3.0",

188

statements=[

189

ast.QubitDeclaration(

190

qubit=ast.Identifier("q"),

191

size=ast.IntegerLiteral(2)

192

),

193

ast.QuantumGate(

194

modifiers=[],

195

name=ast.Identifier("h"),

196

arguments=[],

197

qubits=[ast.IndexedIdentifier(

198

name=ast.Identifier("q"),

199

indices=[[ast.IntegerLiteral(0)]]

200

)]

201

)

202

]

203

)

204

205

# Convert to string

206

qasm_code = openqasm3.dumps(program)

207

print(qasm_code)

208

# Output:

209

# OPENQASM 3.0;

210

# qubit[2] q;

211

# h q[0];

212

213

# Write to file

214

with open('output.qasm', 'w') as f:

215

openqasm3.dump(program, f)

216

```

217

218

### Custom Formatting Options

219

220

```python

221

import openqasm3

222

from openqasm3 import ast

223

224

# Create a program with nested control flow

225

program = ast.Program(

226

version="3.0",

227

statements=[

228

ast.ClassicalDeclaration(

229

type=ast.IntType(),

230

identifier=ast.Identifier("x"),

231

init_expression=ast.IntegerLiteral(5)

232

),

233

ast.BranchingStatement(

234

condition=ast.BinaryExpression(

235

op=ast.BinaryOperator[">"],

236

lhs=ast.Identifier("x"),

237

rhs=ast.IntegerLiteral(0)

238

),

239

if_block=[

240

ast.ExpressionStatement(

241

expression=ast.FunctionCall(

242

name=ast.Identifier("print"),

243

arguments=[ast.Identifier("x")]

244

)

245

)

246

],

247

else_block=[

248

ast.BranchingStatement(

249

condition=ast.BinaryExpression(

250

op=ast.BinaryOperator["<"],

251

lhs=ast.Identifier("x"),

252

rhs=ast.IntegerLiteral(0)

253

),

254

if_block=[

255

ast.ExpressionStatement(

256

expression=ast.FunctionCall(

257

name=ast.Identifier("print"),

258

arguments=[ast.StringLiteral("negative")]

259

)

260

)

261

],

262

else_block=[]

263

)

264

]

265

)

266

]

267

)

268

269

# Default formatting (chained else-if)

270

default_code = openqasm3.dumps(program)

271

print("Default formatting:")

272

print(default_code)

273

274

# Custom formatting with different indentation

275

custom_code = openqasm3.dumps(program, indent=" ", chain_else_if=False)

276

print("\nCustom formatting:")

277

print(custom_code)

278

```

279

280

### Working with Complex Expressions

281

282

```python

283

import openqasm3

284

from openqasm3 import ast

285

286

# Create complex mathematical expressions

287

complex_expr = ast.BinaryExpression(

288

op=ast.BinaryOperator["+"],

289

lhs=ast.BinaryExpression(

290

op=ast.BinaryOperator["*"],

291

lhs=ast.Identifier("a"),

292

rhs=ast.BinaryExpression(

293

op=ast.BinaryOperator["**"],

294

lhs=ast.Identifier("b"),

295

rhs=ast.IntegerLiteral(2)

296

)

297

),

298

rhs=ast.UnaryExpression(

299

op=ast.UnaryOperator["-"],

300

expression=ast.FunctionCall(

301

name=ast.Identifier("sin"),

302

arguments=[ast.Identifier("theta")]

303

)

304

)

305

)

306

307

# The printer handles precedence automatically

308

expr_code = openqasm3.dumps(complex_expr)

309

print(f"Expression: {expr_code}")

310

# Output: a * b ** 2 + -sin(theta)

311

```

312

313

### Stream-Based Output

314

315

```python

316

import openqasm3

317

from openqasm3 import ast

318

import io

319

320

# Create a program

321

program = ast.Program(

322

version="3.0",

323

statements=[

324

ast.Include(filename="stdgates.inc"),

325

ast.QubitDeclaration(qubit=ast.Identifier("q"), size=ast.IntegerLiteral(3))

326

]

327

)

328

329

# Write to different types of streams

330

output_buffer = io.StringIO()

331

openqasm3.dump(program, output_buffer)

332

result = output_buffer.getvalue()

333

print("Buffer output:", result)

334

335

# Write to file with custom formatting

336

with open('formatted.qasm', 'w') as f:

337

openqasm3.dump(program, f, indent="\t") # Use tabs for indentation

338

339

# Write large programs piece by piece

340

from openqasm3.printer import Printer, PrinterState

341

342

output = io.StringIO()

343

printer = Printer(output, indent=" ")

344

state = PrinterState()

345

346

# Write program header

347

printer.visit(ast.Program(version="3.0", statements=[]), state)

348

349

# Write statements one by one

350

for i in range(10):

351

gate = ast.QuantumGate(

352

modifiers=[],

353

name=ast.Identifier("h"),

354

arguments=[],

355

qubits=[ast.IndexedIdentifier(

356

name=ast.Identifier("q"),

357

indices=[[ast.IntegerLiteral(i)]]

358

)]

359

)

360

printer.visit(gate, state)

361

362

print("Incremental output:", output.getvalue())

363

```

364

365

### Custom Printer Extension

366

367

```python

368

from openqasm3.printer import Printer, PrinterState

369

from openqasm3 import ast

370

import io

371

372

class CustomPrinter(Printer):

373

"""Custom printer with additional formatting rules"""

374

375

def visit_QuantumGate(self, node, context):

376

"""Custom formatting for quantum gates"""

377

# Add comments before certain gates

378

if node.name.name in ['h', 'x', 'y', 'z']:

379

self.stream.write(f"// Pauli gate: {node.name.name}\n")

380

self.stream.write(" " * (context.current_indent * len(self.indent)))

381

382

# Use parent implementation for actual gate printing

383

super().visit_QuantumGate(node, context)

384

385

def visit_QubitDeclaration(self, node, context):

386

"""Add extra information for qubit declarations"""

387

super().visit_QubitDeclaration(node, context)

388

389

# Add comment with qubit count

390

if node.size and isinstance(node.size, ast.IntegerLiteral):

391

self.stream.write(f" // {node.size.value} qubits declared")

392

393

# Use custom printer

394

program = ast.Program(

395

version="3.0",

396

statements=[

397

ast.QubitDeclaration(qubit=ast.Identifier("q"), size=ast.IntegerLiteral(2)),

398

ast.QuantumGate(

399

modifiers=[],

400

name=ast.Identifier("h"),

401

arguments=[],

402

qubits=[ast.Identifier("q")]

403

)

404

]

405

)

406

407

output = io.StringIO()

408

printer = CustomPrinter(output)

409

printer.visit(program)

410

411

print("Custom formatted output:")

412

print(output.getvalue())

413

```

414

415

### Error Handling and Validation

416

417

```python

418

import openqasm3

419

from openqasm3 import ast

420

421

def safe_code_generation(node):

422

"""Generate code with error handling"""

423

try:

424

code = openqasm3.dumps(node)

425

return code, None

426

except Exception as e:

427

return None, str(e)

428

429

# Test with valid node

430

valid_node = ast.IntegerLiteral(42)

431

code, error = safe_code_generation(valid_node)

432

if error:

433

print(f"Error: {error}")

434

else:

435

print(f"Generated: {code}")

436

437

# Test with incomplete node (missing required fields)

438

try:

439

incomplete_node = ast.QuantumGate(

440

modifiers=[],

441

name=None, # Invalid: name cannot be None

442

arguments=[],

443

qubits=[]

444

)

445

code = openqasm3.dumps(incomplete_node)

446

except Exception as e:

447

print(f"Caught error with incomplete node: {e}")

448

```

449

450

## Formatting Options

451

452

The printer supports several formatting options through keyword arguments:

453

454

- **indent**: String used for each indentation level (default: " ")

455

- **chain_else_if**: Whether to format nested if-else as "else if" (default: True)

456

- **old_measurement**: Use OpenQASM 2 arrow syntax for measurements (default: False)

457

458

## Language Support

459

460

The code generation system supports the complete OpenQASM 3 specification:

461

462

- **All statement types**: declarations, gates, control flow, subroutines

463

- **Complete expression system**: literals, operators, function calls, casts

464

- **Type annotations**: all classical and quantum types

465

- **Timing constructs**: delays, boxes, duration literals

466

- **Calibration blocks**: defcal, defcalgrammar, cal statements

467

- **Advanced features**: includes, pragmas, annotations, modifiers

468

469

## Round-Trip Guarantee

470

471

The printer is designed to produce valid OpenQASM 3 code that can be parsed back into equivalent AST structures:

472

473

```python

474

# Original program

475

original = '''

476

OPENQASM 3.0;

477

qubit[2] q;

478

h q[0];

479

cx q[0], q[1];

480

'''

481

482

# Parse to AST

483

program = openqasm3.parse(original)

484

485

# Generate code

486

generated = openqasm3.dumps(program)

487

488

# Parse again

489

reparsed = openqasm3.parse(generated)

490

491

# The AST structures should be equivalent

492

assert program == reparsed # (ignoring span information)

493

```