Repository URL to install this package:
|
Version:
0.24.2 ▾
|
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Printers for QASM 3 AST nodes."""
import io
from typing import Sequence
from . import ast
from .experimental import ExperimentalFeatures
from .exceptions import QASM3ExporterError
class BasicPrinter:
"""A QASM 3 AST visitor which writes the tree out in text mode to a stream, where the only
formatting is simple block indentation."""
_CONSTANT_LOOKUP = {
ast.Constant.PI: "pi",
ast.Constant.EULER: "euler",
ast.Constant.TAU: "tau",
}
_MODIFIER_LOOKUP = {
ast.QuantumGateModifierName.CTRL: "ctrl",
ast.QuantumGateModifierName.NEGCTRL: "negctrl",
ast.QuantumGateModifierName.INV: "inv",
ast.QuantumGateModifierName.POW: "pow",
}
_FLOAT_WIDTH_LOOKUP = {type: str(type.value) for type in ast.FloatType}
# The visitor names include the class names, so they mix snake_case with PascalCase.
# pylint: disable=invalid-name
def __init__(
self,
stream: io.TextIOBase,
*,
indent: str,
chain_else_if: bool = False,
experimental: ExperimentalFeatures = ExperimentalFeatures(0),
):
"""
Args:
stream (io.TextIOBase): the stream that the output will be written to.
indent (str): the string to use as a single indentation level.
chain_else_if (bool): If ``True``, then constructs of the form::
if (x == 0) {
// ...
} else {
if (x == 1) {
// ...
} else {
// ...
}
}
will be collapsed into the equivalent but flatter::
if (x == 0) {
// ...
} else if (x == 1) {
// ...
} else {
// ...
}
This collapsed form may have less support on backends, so it is turned off by
default. While the output of this printer is always unambiguous, using ``else``
without immediately opening an explicit scope with ``{ }`` in nested contexts can
cause issues, in the general case, which is why it is sometimes less supported.
"""
self.stream = stream
self.indent = indent
self._current_indent = 0
self._chain_else_if = chain_else_if
self._experimental = experimental
def visit(self, node: ast.ASTNode) -> None:
"""Visit this node of the AST, printing it out to the stream in this class instance.
Normally, you will want to call this function on a complete :obj:`~qiskit.qasm3.ast.Program`
node, to print out a complete program to the stream. The visit can start from any node,
however, if you want to build up a file bit-by-bit manually.
Args:
node (ASTNode): the node to convert to QASM 3 and write out to the stream.
Raises:
RuntimeError: if an AST node is encountered that the visitor is unable to parse. This
typically means that the given AST was malformed.
"""
visitor = None
for cls_ in type(node).mro():
visitor = getattr(self, "_visit_" + cls_.__name__, None)
if visitor is not None:
break
else:
node_type = node.__class__.__name__
raise RuntimeError(
f"This visitor does not know how to handle AST nodes of type '{node_type}',"
f" but was given '{node}'."
)
visitor(node)
def _start_line(self) -> None:
self.stream.write(self._current_indent * self.indent)
def _end_statement(self) -> None:
self.stream.write(";\n")
def _end_line(self) -> None:
self.stream.write("\n")
def _write_statement(self, line: str) -> None:
self._start_line()
self.stream.write(line)
self._end_statement()
def _visit_sequence(
self, nodes: Sequence[ast.ASTNode], *, start: str = "", end: str = "", separator: str
) -> None:
if start:
self.stream.write(start)
for node in nodes[:-1]:
self.visit(node)
self.stream.write(separator)
if nodes:
self.visit(nodes[-1])
if end:
self.stream.write(end)
def _visit_Program(self, node: ast.Program) -> None:
self.visit(node.header)
for statement in node.statements:
self.visit(statement)
def _visit_Header(self, node: ast.Header) -> None:
self.visit(node.version)
for include in node.includes:
self.visit(include)
def _visit_Version(self, node: ast.Version) -> None:
self._write_statement(f"OPENQASM {node.version_number}")
def _visit_Include(self, node: ast.Include) -> None:
self._write_statement(f'include "{node.filename}"')
def _visit_Pragma(self, node: ast.Pragma) -> None:
self._write_statement(f"#pragma {node.content}")
def _visit_CalibrationGrammarDeclaration(self, node: ast.CalibrationGrammarDeclaration) -> None:
self._write_statement(f'defcalgrammar "{node.name}"')
def _visit_FloatType(self, node: ast.FloatType) -> None:
self.stream.write(f"float[{self._FLOAT_WIDTH_LOOKUP[node]}]")
def _visit_IntType(self, node: ast.IntType) -> None:
self.stream.write("int")
if node.size is not None:
self.stream.write(f"[{node.size}]")
def _visit_BitArrayType(self, node: ast.BitArrayType) -> None:
self.stream.write(f"bit[{node.size}]")
def _visit_Identifier(self, node: ast.Identifier) -> None:
self.stream.write(node.string)
def _visit_PhysicalQubitIdentifier(self, node: ast.PhysicalQubitIdentifier) -> None:
self.stream.write("$")
self.visit(node.identifier)
def _visit_Expression(self, node: ast.Expression) -> None:
self.stream.write(str(node.something))
def _visit_Constant(self, node: ast.Constant) -> None:
self.stream.write(self._CONSTANT_LOOKUP[node])
def _visit_SubscriptedIdentifier(self, node: ast.SubscriptedIdentifier) -> None:
self.visit(node.identifier)
self.stream.write("[")
self.visit(node.subscript)
self.stream.write("]")
def _visit_Range(self, node: ast.Range) -> None:
if node.start is not None:
self.visit(node.start)
self.stream.write(":")
if node.step is not None:
self.visit(node.step)
self.stream.write(":")
if node.end is not None:
self.visit(node.end)
def _visit_IndexSet(self, node: ast.IndexSet) -> None:
self._visit_sequence(node.values, start="{", separator=", ", end="}")
def _visit_QuantumMeasurement(self, node: ast.QuantumMeasurement) -> None:
self.stream.write("measure ")
self._visit_sequence(node.identifierList, separator=", ")
def _visit_QuantumMeasurementAssignment(self, node: ast.QuantumMeasurementAssignment) -> None:
self._start_line()
self.visit(node.identifier)
self.stream.write(" = ")
self.visit(node.quantumMeasurement)
self._end_statement()
def _visit_QuantumReset(self, node: ast.QuantumReset) -> None:
self._start_line()
self.stream.write("reset ")
self.visit(node.identifier)
self._end_statement()
def _visit_QuantumDelay(self, node: ast.QuantumDelay) -> None:
self._start_line()
self.stream.write("delay[")
self.visit(node.duration)
self.stream.write("] ")
self._visit_sequence(node.qubits, separator=", ")
self._end_statement()
def _visit_Integer(self, node: ast.Integer) -> None:
self.stream.write(str(node.something))
def _visit_DurationLiteral(self, node: ast.DurationLiteral) -> None:
self.stream.write(f"{node.value}{node.unit.value}")
def _visit_Designator(self, node: ast.Designator) -> None:
self.stream.write("[")
self.visit(node.expression)
self.stream.write("]")
def _visit_ClassicalDeclaration(self, node: ast.ClassicalDeclaration) -> None:
self._start_line()
self.visit(node.type)
self.stream.write(" ")
self.visit(node.identifier)
if node.initializer is not None:
self.stream.write(" = ")
self.visit(node.initializer)
self._end_statement()
def _visit_AssignmentStatement(self, node: ast.AssignmentStatement) -> None:
self._start_line()
self.visit(node.lvalue)
self.stream.write(" = ")
self.visit(node.rvalue)
self._end_statement()
def _visit_IODeclaration(self, node: ast.IODeclaration) -> None:
self._start_line()
modifier = "input" if node.modifier is ast.IOModifier.INPUT else "output"
self.stream.write(modifier + " ")
self.visit(node.type)
self.stream.write(" ")
self.visit(node.identifier)
self._end_statement()
def _visit_QuantumDeclaration(self, node: ast.QuantumDeclaration) -> None:
self._start_line()
self.stream.write("qubit")
self.visit(node.designator)
self.stream.write(" ")
self.visit(node.identifier)
self._end_statement()
def _visit_AliasStatement(self, node: ast.AliasStatement) -> None:
self._start_line()
self.stream.write("let ")
self.visit(node.identifier)
self.stream.write(" = ")
self._visit_sequence(node.concatenation, separator=" ++ ")
self._end_statement()
def _visit_QuantumGateModifier(self, node: ast.QuantumGateModifier) -> None:
self.stream.write(self._MODIFIER_LOOKUP[node.modifier])
if node.argument:
self.stream.write("(")
self.visit(node.argument)
self.stream.write(")")
def _visit_QuantumGateCall(self, node: ast.QuantumGateCall) -> None:
self._start_line()
if node.modifiers:
self._visit_sequence(node.modifiers, end=" @ ", separator=" @ ")
self.visit(node.quantumGateName)
if node.parameters:
self._visit_sequence(node.parameters, start="(", end=")", separator=", ")
self.stream.write(" ")
self._visit_sequence(node.indexIdentifierList, separator=", ")
self._end_statement()
def _visit_SubroutineCall(self, node: ast.SubroutineCall) -> None:
self._start_line()
self.visit(node.identifier)
if node.expressionList:
self._visit_sequence(node.expressionList, start="(", end=")", separator=", ")
self.stream.write(" ")
self._visit_sequence(node.indexIdentifierList, separator=", ")
self._end_statement()
def _visit_QuantumBarrier(self, node: ast.QuantumBarrier) -> None:
self._start_line()
self.stream.write("barrier ")
self._visit_sequence(node.indexIdentifierList, separator=", ")
self._end_statement()
def _visit_ProgramBlock(self, node: ast.ProgramBlock) -> None:
self.stream.write("{\n")
self._current_indent += 1
for statement in node.statements:
self.visit(statement)
self._current_indent -= 1
self._start_line()
self.stream.write("}")
def _visit_ReturnStatement(self, node: ast.ReturnStatement) -> None:
self._start_line()
if node.expression:
self.stream.write("return ")
self.visit(node.expression)
else:
self.stream.write("return")
self._end_statement()
def _visit_QuantumArgument(self, node: ast.QuantumArgument) -> None:
self.stream.write("qubit")
if node.designator:
self.visit(node.designator)
self.stream.write(" ")
self.visit(node.identifier)
def _visit_QuantumGateSignature(self, node: ast.QuantumGateSignature) -> None:
self.visit(node.name)
if node.params:
self._visit_sequence(node.params, start="(", end=")", separator=", ")
self.stream.write(" ")
self._visit_sequence(node.qargList, separator=", ")
def _visit_QuantumGateDefinition(self, node: ast.QuantumGateDefinition) -> None:
self._start_line()
self.stream.write("gate ")
self.visit(node.quantumGateSignature)
self.stream.write(" ")
self.visit(node.quantumBlock)
self._end_line()
def _visit_SubroutineDefinition(self, node: ast.SubroutineDefinition) -> None:
self._start_line()
self.stream.write("def ")
self.visit(node.identifier)
self._visit_sequence(node.arguments, start="(", end=")", separator=", ")
self.stream.write(" ")
self.visit(node.subroutineBlock)
self._end_line()
def _visit_CalibrationDefinition(self, node: ast.CalibrationDefinition) -> None:
self._start_line()
self.stream.write("defcal ")
self.visit(node.name)
self.stream.write(" ")
if node.calibrationArgumentList:
self._visit_sequence(node.calibrationArgumentList, start="(", end=")", separator=", ")
self.stream.write(" ")
self._visit_sequence(node.identifierList, separator=", ")
# This is temporary: calibration definition blocks are not currently (2021-10-04) defined
# properly.
self.stream.write(" {}")
self._end_line()
def _visit_LtOperator(self, _node: ast.LtOperator) -> None:
self.stream.write(">")
def _visit_GtOperator(self, _node: ast.GtOperator) -> None:
self.stream.write("<")
def _visit_EqualsOperator(self, _node: ast.EqualsOperator) -> None:
self.stream.write("==")
def _visit_ComparisonExpression(self, node: ast.ComparisonExpression) -> None:
self.visit(node.left)
self.stream.write(" ")
self.visit(node.relation)
self.stream.write(" ")
self.visit(node.right)
def _visit_BreakStatement(self, _node: ast.BreakStatement) -> None:
self._write_statement("break")
def _visit_ContinueStatement(self, _node: ast.ContinueStatement) -> None:
self._write_statement("continue")
def _visit_BranchingStatement(
self, node: ast.BranchingStatement, chained: bool = False
) -> None:
if not chained:
self._start_line()
self.stream.write("if (")
self.visit(node.condition)
self.stream.write(") ")
self.visit(node.true_body)
if node.false_body is not None:
self.stream.write(" else ")
# Special handling to flatten a perfectly nested structure of
# if {...} else { if {...} else {...} }
# into the simpler
# if {...} else if {...} else {...}
# but only if we're allowed to by our options.
if (
self._chain_else_if
and len(node.false_body.statements) == 1
and isinstance(node.false_body.statements[0], ast.BranchingStatement)
):
self._visit_BranchingStatement(node.false_body.statements[0], chained=True)
else:
self.visit(node.false_body)
if not chained:
# The visitor to the first ``if`` will end the line.
self._end_line()
def _visit_ForLoopStatement(self, node: ast.ForLoopStatement) -> None:
self._start_line()
self.stream.write("for ")
self.visit(node.parameter)
self.stream.write(" in ")
if isinstance(node.indexset, ast.Range):
self.stream.write("[")
self.visit(node.indexset)
self.stream.write("]")
else:
self.visit(node.indexset)
self.stream.write(" ")
self.visit(node.body)
self._end_line()
def _visit_WhileLoopStatement(self, node: ast.WhileLoopStatement) -> None:
self._start_line()
self.stream.write("while (")
self.visit(node.condition)
self.stream.write(") ")
self.visit(node.body)
self._end_line()
def _visit_SwitchStatement(self, node: ast.SwitchStatement) -> None:
if ExperimentalFeatures.SWITCH_CASE_V1 not in self._experimental:
raise QASM3ExporterError(
"'switch' statements are not stabilised in OpenQASM 3 yet."
" To enable experimental support, set the flag"
" 'ExperimentalFeatures.SWITCH_CASE_V1' in the 'experimental' keyword"
" argument of the printer."
)
self._start_line()
self.stream.write("switch (")
self.visit(node.target)
self.stream.write(") {")
self._end_line()
self._current_indent += 1
for labels, case in node.cases:
if not labels:
continue
for label in labels[:-1]:
self._start_line()
self.stream.write("case ")
self.visit(label)
self.stream.write(":")
self._end_line()
self._start_line()
if isinstance(labels[-1], ast.DefaultCase):
self.visit(labels[-1])
else:
self.stream.write("case ")
self.visit(labels[-1])
self.stream.write(": ")
self.visit(case)
self._end_line()
self._write_statement("break")
self._current_indent -= 1
self._start_line()
self.stream.write("}")
self._end_line()
def _visit_DefaultCase(self, _node: ast.DefaultCase) -> None:
self.stream.write("default")