Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support stabilised syntax for OpenQASM 3 switch #11417

Merged
merged 6 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions qiskit/qasm3/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,11 +596,31 @@ class DefaultCase(Expression):
"""An object representing the `default` special label in switch statements."""


class SwitchStatement(Statement):
"""AST node for the proposed 'switch-case' extension to OpenQASM 3."""
class SwitchStatementPreview(Statement):
"""AST node for the proposed 'switch-case' extension to OpenQASM 3, before the syntax was
Copy link
Contributor

@jlapeyre jlapeyre Jan 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to think of a better more useful name than ....Preview. I think contemporary software devs find the word "Legacy" distasteful. But it's probably widely understood by casual programmers. Maybe SwitchStatementOldStyle. In OQ3, "old style" refers to OQ2 legacy syntax. In this case it's not from a previous version. But OldStyle tells me that I probably don't want this, even if I am not familiar with its history.

Or even SwitchStatementObsolete. This really tells me I should not be using it.

stabilized. This corresponds to the :attr:`.ExperimentalFeatures.SWITCH_CASE_V1` logic.

The stabilized form of the syntax instead uses :class:`.SwitchStatement`."""

def __init__(
self, target: Expression, cases: Iterable[Tuple[Iterable[Expression], ProgramBlock]]
):
self.target = target
self.cases = [(tuple(values), case) for values, case in cases]


class SwitchStatement(Statement):
"""AST node for the stable 'switch' statement of OpenQASM 3.

The only real difference from an AST form is that the default is required to be separate; it
cannot be joined with other cases (even though that's meaningless, the V1 syntax permitted it)."""

def __init__(
self,
target: Expression,
cases: Iterable[Tuple[Iterable[Expression], ProgramBlock]],
default: Optional[ProgramBlock] = None,
):
self.target = target
self.cases = [(tuple(values), case) for values, case in cases]
self.default = default
42 changes: 41 additions & 1 deletion qiskit/qasm3/experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,44 @@ class ExperimentalFeatures(enum.Flag):
SWITCH_CASE_V1 = enum.auto()
"""Support exporting switch-case statements as proposed by
https://github.com/openqasm/openqasm/pull/463 at `commit bfa787aa3078
<https://github.com/openqasm/openqasm/pull/463/commits/bfa787aa3078>`__."""
<https://github.com/openqasm/openqasm/pull/463/commits/bfa787aa3078>`__.

These have the output format:

.. code-block::

switch (i) {
case 0:
case 1:
x $0;
break;

case 2: {
z $0;
}
break;

default: {
cx $0, $1;
}
break;
}

This differs from the syntax of the ``switch`` statement as stabilized. If this flag is not
passed, then the parser will instead output using the stabilized syntax, which would render the
same example above as:

.. code-block::

switch (i) {
case 0, 1 {
x $0;
}
case 2 {
z $0;
}
default {
cx $0, $1;
}
}
"""
55 changes: 37 additions & 18 deletions qiskit/qasm3/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,14 +833,6 @@ def build_if_statement(self, instruction: CircuitInstruction) -> ast.BranchingSt

def build_switch_statement(self, instruction: CircuitInstruction) -> Iterable[ast.Statement]:
"""Build a :obj:`.SwitchCaseOp` into a :class:`.ast.SwitchStatement`."""
if ExperimentalFeatures.SWITCH_CASE_V1 not in self.experimental:
raise QASM3ExporterError(
"'switch' statements are not stabilized in OpenQASM 3 yet."
" To enable experimental support, set the flag"
" 'ExperimentalFeatures.SWITCH_CASE_V1' in the 'experimental' keyword"
" argument of the exporter."
)

real_target = self.build_expression(expr.lift(instruction.operation.target))
global_scope = self.global_scope()
target = self._reserve_variable_name(
Expand All @@ -850,21 +842,48 @@ def build_switch_statement(self, instruction: CircuitInstruction) -> Iterable[as
ast.ClassicalDeclaration(ast.IntType(), target, None)
)

def case(values, case_block):
values = [
ast.DefaultCase() if v is CASE_DEFAULT else self.build_integer(v) for v in values
if ExperimentalFeatures.SWITCH_CASE_V1 in self.experimental:
# In this case, defaults can be folded in with other cases (useless as that is).

def case(values, case_block):
values = [
ast.DefaultCase() if v is CASE_DEFAULT else self.build_integer(v)
for v in values
]
self.push_scope(case_block, instruction.qubits, instruction.clbits)
case_body = self.build_program_block(case_block.data)
self.pop_scope()
return values, case_body

return [
ast.AssignmentStatement(target, real_target),
ast.SwitchStatementPreview(
target,
(
case(values, block)
for values, block in instruction.operation.cases_specifier()
),
),
]
self.push_scope(case_block, instruction.qubits, instruction.clbits)
case_body = self.build_program_block(case_block.data)

# Handle the stabilised syntax.
cases = []
default = None
for values, block in instruction.operation.cases_specifier():
self.push_scope(block, instruction.qubits, instruction.clbits)
case_body = self.build_program_block(block.data)
self.pop_scope()
return values, case_body
if CASE_DEFAULT in values:
# Even if it's mixed in with other cases, we can skip them and only output the
# `default` since that's valid and execution will be the same; the evaluation of
# case labels can't have side effects.
default = case_body
continue
cases.append(([self.build_integer(value) for value in values], case_body))

return [
ast.AssignmentStatement(target, real_target),
ast.SwitchStatement(
target,
(case(values, block) for values, block in instruction.operation.cases_specifier()),
),
ast.SwitchStatement(target, cases, default=default),
]

def build_while_loop(self, instruction: CircuitInstruction) -> ast.WhileLoopStatement:
Expand Down
35 changes: 27 additions & 8 deletions qiskit/qasm3/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

from . import ast
from .experimental import ExperimentalFeatures
from .exceptions import QASM3ExporterError

# Precedence and associativity table for prefix, postfix and infix operators. The rules are a
# lookup table of two-tuples; the "binding power" of the operator to the left and to the right.
Expand Down Expand Up @@ -504,13 +503,33 @@ def _visit_WhileLoopStatement(self, node: ast.WhileLoopStatement) -> None:
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
self._start_line()
self.stream.write("case ")
self._visit_sequence(labels, separator=", ")
self.stream.write(" ")
self.visit(case)
self._end_line()
if node.default is not None:
self._start_line()
self.stream.write("default ")
self.visit(node.default)
self._end_line()
self._current_indent -= 1
self._start_line()
self.stream.write("}")
self._end_line()

def _visit_SwitchStatementPreview(self, node: ast.SwitchStatementPreview) -> None:
# This is the pre-release syntax, which had lots of extra `break` statements in it.
self._start_line()
self.stream.write("switch (")
self.visit(node.target)
Expand Down
16 changes: 16 additions & 0 deletions releasenotes/notes/qasm3-stable-switch-61a10036d1587778.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
features:
- |
The OpenQASM 3 exporter (see :func:`~.qasm3.dump` and :func:`~.qasm3.dumps` functions in
:mod:`qiskit.qasm3`) now supports the stabilised syntax of the ``switch`` statement in OpenQASM 3
by default. The pre-certification syntax of the ``switch`` statement is still available by
using the :attr:`.ExperimentalFeatures.SWITCH_CASE_V1` flag in the ``experimental`` argument of
the exporter. There is no feature flag required for the stabilised syntax, but if you are
interfacing with other tooling that is not yet updated, you may need to pass the experimental
flag.

The syntax of the stabilised form is slightly different with regards to terminating ``break``
Comment on lines +5 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:mod:`qiskit.qasm3`) now supports the stabilised syntax of the ``switch`` statement in OpenQASM 3
by default. The pre-certification syntax of the ``switch`` statement is still available by
using the :attr:`.ExperimentalFeatures.SWITCH_CASE_V1` flag in the ``experimental`` argument of
the exporter. There is no feature flag required for the stabilised syntax, but if you are
interfacing with other tooling that is not yet updated, you may need to pass the experimental
flag.
The syntax of the stabilised form is slightly different with regards to terminating ``break``
:mod:`qiskit.qasm3`) now supports the stabilized syntax of the ``switch`` statement in OpenQASM 3
by default. The pre-certification syntax of the ``switch`` statement is still available by
using the :attr:`.ExperimentalFeatures.SWITCH_CASE_V1` flag in the ``experimental`` argument of
the exporter. There is no feature flag required for the stabilized syntax, but if you are
interfacing with other tooling that is not yet updated, you may need to pass the experimental
flag.
The syntax of the stabilized form is slightly different with regards to terminating ``break``

statements (no longer required nor permitted), and multiple cases are now combined into a single
``case`` line, rather than using C-style fall-through. For more detail, see `the OpenQASM 3
documentation on the switch-case construct
<https://openqasm.com/language/classical.html#the-switch-statement>`__.
Loading
Loading