or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

visitors.mddocs/

0

# AST Visitors and Transformers

1

2

Visitor and transformer patterns for systematic AST traversal, analysis, and modification with optional context support. These classes enable powerful AST manipulation patterns including tree walking, analysis, transformation, and code generation.

3

4

## Capabilities

5

6

### Base Visitor Class

7

8

Generic visitor pattern for AST traversal with flexible context support.

9

10

```python { .api }

11

class QASMVisitor(Generic[T]):

12

"""

13

A node visitor base class that walks the abstract syntax tree and calls a

14

visitor function for every node found. This function may return a value

15

which is forwarded by the `visit` method.

16

17

The optional context argument allows the visitor to hold temporary state

18

while visiting nodes without modifying the AST or the visitor instance.

19

"""

20

21

def visit(self, node: QASMNode, context: Optional[T] = None):

22

"""

23

Visit a node and dispatch to the appropriate visitor method.

24

25

Args:

26

node: AST node to visit

27

context: Optional context object for holding temporary state

28

29

Returns:

30

Result from the visitor method (if any)

31

"""

32

33

def generic_visit(self, node: QASMNode, context: Optional[T] = None):

34

"""

35

Called if no explicit visitor function exists for a node.

36

37

This method recursively visits all child nodes in the AST.

38

Override this method to change the default traversal behavior.

39

40

Args:

41

node: AST node to visit

42

context: Optional context object

43

44

Returns:

45

None by default, but can be overridden

46

"""

47

```

48

49

### AST Transformer Class

50

51

Visitor subclass that enables modification of AST nodes during traversal.

52

53

```python { .api }

54

class QASMTransformer(QASMVisitor[T]):

55

"""

56

A QASMVisitor subclass that allows modification of AST nodes during traversal.

57

58

This class enables tree transformation operations including node replacement,

59

deletion, and list modifications. It handles the complexities of maintaining

60

valid AST structure during transformations.

61

"""

62

63

def generic_visit(self, node: QASMNode, context: Optional[T] = None) -> QASMNode:

64

"""

65

Visit and potentially modify nodes and their children.

66

67

This method visits all child nodes and can replace them with modified

68

versions. It handles lists, single nodes, and None values appropriately.

69

70

Args:

71

node: AST node to visit and potentially transform

72

context: Optional context object

73

74

Returns:

75

The node (potentially modified) or a replacement node

76

"""

77

```

78

79

## Usage Examples

80

81

### Basic AST Analysis

82

83

```python

84

from openqasm3 import ast, visitor

85

import openqasm3

86

87

class GateAnalyzer(visitor.QASMVisitor):

88

"""Analyze quantum gates in a program"""

89

90

def __init__(self):

91

self.gate_calls = []

92

self.gate_definitions = []

93

self.total_qubits = 0

94

95

def visit_QuantumGate(self, node):

96

"""Visit quantum gate calls"""

97

self.gate_calls.append({

98

'name': node.name.name,

99

'qubits': len(node.qubits),

100

'parameters': len(node.arguments)

101

})

102

self.generic_visit(node)

103

104

def visit_QuantumGateDefinition(self, node):

105

"""Visit quantum gate definitions"""

106

self.gate_definitions.append({

107

'name': node.name.name,

108

'qubits': len(node.qubits),

109

'parameters': len(node.arguments)

110

})

111

self.generic_visit(node)

112

113

def visit_QubitDeclaration(self, node):

114

"""Count total qubits declared"""

115

if node.size:

116

# Array declaration like qubit[4] q

117

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

118

self.total_qubits += node.size.value

119

else:

120

# Single qubit declaration like qubit q

121

self.total_qubits += 1

122

self.generic_visit(node)

123

124

# Use the analyzer

125

qasm_source = '''

126

OPENQASM 3.0;

127

qubit[3] q;

128

gate bell q0, q1 {

129

h q0;

130

cx q0, q1;

131

}

132

bell q[0], q[1];

133

h q[2];

134

'''

135

136

program = openqasm3.parse(qasm_source)

137

analyzer = GateAnalyzer()

138

analyzer.visit(program)

139

140

print(f"Total qubits: {analyzer.total_qubits}")

141

print(f"Gate definitions: {analyzer.gate_definitions}")

142

print(f"Gate calls: {analyzer.gate_calls}")

143

```

144

145

### AST Transformation

146

147

```python

148

from openqasm3 import ast, visitor

149

import openqasm3

150

151

class GateInverter(visitor.QASMTransformer):

152

"""Transform gates to add inverse modifiers"""

153

154

def visit_QuantumGate(self, node):

155

"""Add inverse modifier to all gates"""

156

# Create inverse modifier

157

inv_modifier = ast.QuantumGateModifier(

158

modifier=ast.GateModifierName.inv,

159

argument=None

160

)

161

162

# Create new gate with inverse modifier

163

new_gate = ast.QuantumGate(

164

modifiers=[inv_modifier] + node.modifiers,

165

name=node.name,

166

arguments=node.arguments,

167

qubits=node.qubits,

168

duration=node.duration

169

)

170

171

return self.generic_visit(new_gate)

172

173

# Transform a program

174

program = openqasm3.parse('''

175

OPENQASM 3.0;

176

qubit[2] q;

177

h q[0];

178

cx q[0], q[1];

179

''')

180

181

transformer = GateInverter()

182

transformed_program = transformer.visit(program)

183

184

# Convert back to text to see the result

185

result = openqasm3.dumps(transformed_program)

186

print(result)

187

```

188

189

### Context-Based Analysis

190

191

```python

192

from openqasm3 import ast, visitor

193

import openqasm3

194

from typing import List, Set

195

196

class ScopeInfo:

197

"""Context object to track scope information"""

198

def __init__(self):

199

self.declared_variables: Set[str] = set()

200

self.used_variables: Set[str] = set()

201

self.scope_level: int = 0

202

203

class VariableAnalyzer(visitor.QASMVisitor[ScopeInfo]):

204

"""Analyze variable usage with scope tracking"""

205

206

def visit_Program(self, node, context=None):

207

"""Start analysis with global scope"""

208

if context is None:

209

context = ScopeInfo()

210

211

self.generic_visit(node, context)

212

213

# Report unused variables

214

unused = context.declared_variables - context.used_variables

215

if unused:

216

print(f"Unused variables: {unused}")

217

218

def visit_ClassicalDeclaration(self, node, context):

219

"""Track variable declarations"""

220

var_name = node.identifier.name

221

context.declared_variables.add(var_name)

222

print(f"Declared variable '{var_name}' at scope level {context.scope_level}")

223

224

# Visit initialization expression

225

if node.init_expression:

226

self.visit(node.init_expression, context)

227

228

def visit_Identifier(self, node, context):

229

"""Track variable usage"""

230

var_name = node.name

231

if var_name in context.declared_variables:

232

context.used_variables.add(var_name)

233

234

self.generic_visit(node, context)

235

236

def visit_SubroutineDefinition(self, node, context):

237

"""Enter new scope for subroutine"""

238

# Create new scope context

239

new_context = ScopeInfo()

240

new_context.declared_variables = context.declared_variables.copy()

241

new_context.used_variables = context.used_variables.copy()

242

new_context.scope_level = context.scope_level + 1

243

244

# Add subroutine parameters to scope

245

for arg in node.arguments:

246

if isinstance(arg, ast.ClassicalArgument):

247

new_context.declared_variables.add(arg.name.name)

248

249

# Visit subroutine body with new context

250

for stmt in node.body:

251

self.visit(stmt, new_context)

252

253

# Merge used variables back to parent context

254

context.used_variables.update(new_context.used_variables)

255

256

# Analyze a program with variable usage

257

program = openqasm3.parse('''

258

OPENQASM 3.0;

259

int[32] x = 42;

260

int[32] y = 10;

261

int[32] result;

262

263

def int[32] add_numbers(int[32] a, int[32] b) {

264

return a + b;

265

}

266

267

result = add_numbers(x, y);

268

''')

269

270

analyzer = VariableAnalyzer()

271

analyzer.visit(program)

272

```

273

274

### Custom Visitor for Code Generation

275

276

```python

277

from openqasm3 import ast, visitor

278

import openqasm3

279

280

class QuantumCircuitExtractor(visitor.QASMVisitor):

281

"""Extract quantum circuit information"""

282

283

def __init__(self):

284

self.circuit_info = {

285

'qubits': {},

286

'gates': [],

287

'measurements': []

288

}

289

290

def visit_QubitDeclaration(self, node):

291

"""Extract qubit information"""

292

qubit_name = node.qubit.name

293

if node.size:

294

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

295

size = node.size.value

296

else:

297

size = f"expr({node.size})"

298

else:

299

size = 1

300

301

self.circuit_info['qubits'][qubit_name] = size

302

self.generic_visit(node)

303

304

def visit_QuantumGate(self, node):

305

"""Extract gate operations"""

306

gate_info = {

307

'name': node.name.name,

308

'qubits': [],

309

'parameters': [],

310

'modifiers': []

311

}

312

313

# Extract qubit targets

314

for qubit in node.qubits:

315

if isinstance(qubit, ast.Identifier):

316

gate_info['qubits'].append(qubit.name)

317

elif isinstance(qubit, ast.IndexedIdentifier):

318

qubit_ref = f"{qubit.name.name}[{qubit.indices}]"

319

gate_info['qubits'].append(qubit_ref)

320

321

# Extract parameters

322

for param in node.arguments:

323

if isinstance(param, ast.Identifier):

324

gate_info['parameters'].append(param.name)

325

elif isinstance(param, ast.FloatLiteral):

326

gate_info['parameters'].append(param.value)

327

elif isinstance(param, ast.IntegerLiteral):

328

gate_info['parameters'].append(param.value)

329

330

# Extract modifiers

331

for modifier in node.modifiers:

332

mod_info = {'type': modifier.modifier.name}

333

if modifier.argument:

334

mod_info['argument'] = str(modifier.argument)

335

gate_info['modifiers'].append(mod_info)

336

337

self.circuit_info['gates'].append(gate_info)

338

self.generic_visit(node)

339

340

def visit_QuantumMeasurementStatement(self, node):

341

"""Extract measurement operations"""

342

measurement_info = {

343

'qubit': str(node.measure.qubit),

344

'target': str(node.target) if node.target else None

345

}

346

self.circuit_info['measurements'].append(measurement_info)

347

self.generic_visit(node)

348

349

# Extract circuit information

350

program = openqasm3.parse('''

351

OPENQASM 3.0;

352

qubit[3] q;

353

bit[3] c;

354

355

h q[0];

356

cx q[0], q[1];

357

ry(pi/4) q[2];

358

measure q[0] -> c[0];

359

measure q[1] -> c[1];

360

''')

361

362

extractor = QuantumCircuitExtractor()

363

extractor.visit(program)

364

365

import json

366

print(json.dumps(extractor.circuit_info, indent=2))

367

```

368

369

### Advanced Transformation with Error Handling

370

371

```python

372

from openqasm3 import ast, visitor

373

import openqasm3

374

375

class SafeGateReplacer(visitor.QASMTransformer):

376

"""Safely replace gate names with error handling"""

377

378

def __init__(self, replacement_map):

379

self.replacement_map = replacement_map

380

self.warnings = []

381

382

def visit_QuantumGate(self, node):

383

"""Replace gate names according to mapping"""

384

original_name = node.name.name

385

386

if original_name in self.replacement_map:

387

new_name = self.replacement_map[original_name]

388

389

# Create new gate with replaced name

390

new_gate = ast.QuantumGate(

391

modifiers=node.modifiers,

392

name=ast.Identifier(new_name),

393

arguments=node.arguments,

394

qubits=node.qubits,

395

duration=node.duration

396

)

397

398

print(f"Replaced gate '{original_name}' with '{new_name}'")

399

return self.generic_visit(new_gate)

400

else:

401

# Gate not in replacement map

402

self.warnings.append(f"Gate '{original_name}' not found in replacement map")

403

return self.generic_visit(node)

404

405

# Use the transformer

406

replacements = {

407

'h': 'hadamard',

408

'cx': 'cnot',

409

'x': 'pauli_x'

410

}

411

412

program = openqasm3.parse('''

413

OPENQASM 3.0;

414

qubit[2] q;

415

h q[0];

416

cx q[0], q[1];

417

y q[1]; // This gate won't be replaced

418

''')

419

420

replacer = SafeGateReplacer(replacements)

421

transformed = replacer.visit(program)

422

423

print("\nWarnings:")

424

for warning in replacer.warnings:

425

print(f" {warning}")

426

427

print("\nTransformed program:")

428

print(openqasm3.dumps(transformed))

429

```

430

431

## Advanced Patterns

432

433

### Combining Multiple Visitors

434

435

```python

436

from openqasm3 import visitor

437

438

class CompositeAnalyzer(visitor.QASMVisitor):

439

"""Combine multiple analysis passes"""

440

441

def __init__(self):

442

self.analyzers = [

443

GateAnalyzer(),

444

VariableAnalyzer(),

445

QuantumCircuitExtractor()

446

]

447

448

def visit(self, node, context=None):

449

# Run all analyzers

450

for analyzer in self.analyzers:

451

analyzer.visit(node, context)

452

453

# Run default visit

454

return super().visit(node, context)

455

```

456

457

### Visitor with State Management

458

459

```python

460

class StatefulVisitor(visitor.QASMVisitor):

461

"""Visitor that maintains complex state"""

462

463

def __init__(self):

464

self.state_stack = []

465

self.current_function = None

466

self.call_graph = {}

467

468

def _push_state(self, state_name):

469

self.state_stack.append(state_name)

470

471

def _pop_state(self):

472

if self.state_stack:

473

return self.state_stack.pop()

474

return None

475

476

def visit_SubroutineDefinition(self, node):

477

self._push_state('subroutine')

478

old_function = self.current_function

479

self.current_function = node.name.name

480

481

self.generic_visit(node)

482

483

self.current_function = old_function

484

self._pop_state()

485

```

486

487

The visitor pattern provides a powerful and flexible way to work with OpenQASM 3 ASTs, enabling everything from simple analysis to complex transformations while maintaining clean, readable code.