Skip to content

Commit

Permalink
Fix QASM lexer when identifiers start with keywords (quantumlib#7018)
Browse files Browse the repository at this point in the history
* Fix QASM lexer when identifiers start with keywords

* improve test

* Change use of `reserved` to be more standard

* more tests

* rollback mypy change

* reorder tests, remove redundant pi test

* mypy

* mypy

* dedupe test
  • Loading branch information
daxfohl authored Feb 5, 2025
1 parent 6c9db55 commit cc82f52
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 53 deletions.
6 changes: 4 additions & 2 deletions cirq-core/cirq/contrib/acquaintance/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,12 @@ def _decompose_(self, qubits: Sequence['cirq.Qid']) -> Iterator['cirq.OP_TREE']:
parts_qubits = list(left_part + right_part)
parts[i] = parts_qubits[: len(right_part)]
parts[i + 1] = parts_qubits[len(right_part) :]
layers.prior_interstitial.sort(key=op_sort_key) # type: ignore[arg-type]
if op_sort_key is not None:
layers.prior_interstitial.sort(key=op_sort_key)
for l in ('prior_interstitial', 'pre', 'intra', 'post'):
yield getattr(layers, l)
layers.posterior_interstitial.sort(key=op_sort_key) # type: ignore[arg-type]
if op_sort_key is not None:
layers.posterior_interstitial.sort(key=op_sort_key)
yield layers.posterior_interstitial

assert list(
Expand Down
46 changes: 5 additions & 41 deletions cirq-core/cirq/contrib/qasm_import/_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

import re
from typing import Optional
import numpy as np
import ply.lex as lex

from cirq.contrib.qasm_import.exception import QasmException
Expand All @@ -35,8 +34,7 @@ def __init__(self):
'reset': 'RESET',
'gate': 'GATE',
'if': 'IF',
'->': 'ARROW',
'==': 'EQ',
'pi': 'PI',
}

tokens = [
Expand All @@ -46,7 +44,8 @@ def __init__(self):
'STDGATESINC',
'QELIBINC',
'ID',
'PI',
'ARROW',
'EQ',
] + list(reserved.values())

def t_newline(self, t):
Expand All @@ -55,11 +54,6 @@ def t_newline(self, t):

t_ignore = ' \t'

def t_PI(self, t):
r"""pi"""
t.value = np.pi
return t

# all numbers except NATURAL_NUMBERs:
# it's useful to have this separation to be able to handle indices
# separately. In case of the parameter expressions, we are "OR"-ing
Expand Down Expand Up @@ -97,38 +91,6 @@ def t_STDGATESINC(self, t):
r"""include(\s+)"stdgates.inc";"""
return t

def t_QREG(self, t):
r"""qreg"""
return t

def t_QUBIT(self, t):
r"""qubit"""
return t

def t_CREG(self, t):
r"""creg"""
return t

def t_BIT(self, t):
r"""bit"""
return t

def t_MEASURE(self, t):
r"""measure"""
return t

def t_RESET(self, t):
r"""reset"""
return t

def t_GATE(self, t):
r"""gate"""
return t

def t_IF(self, t):
r"""if"""
return t

def t_ARROW(self, t):
"""->"""
return t
Expand All @@ -139,6 +101,8 @@ def t_EQ(self, t):

def t_ID(self, t):
r"""[a-zA-Z][a-zA-Z\d_]*"""
if t.value in QasmLexer.reserved:
t.type = QasmLexer.reserved[t.value]
return t

def t_COMMENT(self, t):
Expand Down
27 changes: 21 additions & 6 deletions cirq-core/cirq/contrib/qasm_import/_lexer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# limitations under the License.

import pytest
import numpy as np
from cirq.contrib.qasm_import import QasmException
from cirq.contrib.qasm_import._lexer import QasmLexer

Expand Down Expand Up @@ -103,12 +102,28 @@ def test_numbers(number: str):
assert token.value == float(number)


def test_pi():
@pytest.mark.parametrize('token', QasmLexer.reserved.keys())
def test_keywords(token):
lexer = QasmLexer()
lexer.input('pi')
token = lexer.token()
assert token.type == "PI"
assert token.value == np.pi
identifier = f'{token} {token}'
lexer.input(identifier)
t = lexer.token()
assert t.type == QasmLexer.reserved[token]
assert t.value == token
t2 = lexer.token()
assert t2.type == QasmLexer.reserved[token]
assert t2.value == token


@pytest.mark.parametrize('token', QasmLexer.reserved.keys())
@pytest.mark.parametrize('separator', ['', '_'])
def test_identifier_starts_or_ends_with_keyword(token, separator):
lexer = QasmLexer()
identifier = f'{token}{separator}{token}'
lexer.input(identifier)
t = lexer.token()
assert t.type == "ID"
assert t.value == identifier


def test_qreg():
Expand Down
7 changes: 5 additions & 2 deletions cirq-core/cirq/contrib/qasm_import/_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,10 +510,13 @@ def p_expr_binary(self, p):

def p_term(self, p):
"""term : NUMBER
| NATURAL_NUMBER
| PI"""
| NATURAL_NUMBER"""
p[0] = p[1]

def p_pi(self, p):
"""term : PI"""
p[0] = np.pi

# qargs : qarg ',' qargs
# | qarg ';'

Expand Down
19 changes: 19 additions & 0 deletions cirq-core/cirq/contrib/qasm_import/_parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1499,3 +1499,22 @@ def _test_parse_exception(qasm: str, cirq_err: str, qiskit_err: str | None):
"Skipped _test_qiskit_parse_exception because "
"qiskit isn't installed to verify against."
)


def test_nested_custom_gate_has_keyword_in_name():
qasm = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[1];
gate gateGate qb { x qb; }
gate qregGate qa { gateGate qa; }
qregGate q;
"""
qb = cirq.NamedQubit('qb')
inner = cirq.FrozenCircuit(cirq.X(qb))
qa = cirq.NamedQubit('qa')
middle = cirq.FrozenCircuit(cirq.CircuitOperation(inner, qubit_map={qb: qa}))
q_0 = cirq.NamedQubit('q_0')
expected = cirq.Circuit(cirq.CircuitOperation(middle, qubit_map={qa: q_0}))
parser = QasmParser()
parsed_qasm = parser.parse(qasm)
assert parsed_qasm.circuit == expected
4 changes: 3 additions & 1 deletion cirq-web/cirq_web/circuits/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ def get_client_code(self) -> str:
<button id="camera-reset">Reset Camera</button>
<button id="camera-toggle">Toggle Camera Type</button>
<script>
let viz_{stripped_id} = createGridCircuit({self.serialized_circuit}, {moments}, "{self.id}", {self.padding_factor});
let viz_{stripped_id} = createGridCircuit(
{self.serialized_circuit}, {moments}, "{self.id}", {self.padding_factor}
);
document.getElementById("camera-reset").addEventListener('click', () => {{
viz_{stripped_id}.scene.setCameraAndControls(viz_{stripped_id}.circuit);
Expand Down
4 changes: 3 additions & 1 deletion cirq-web/cirq_web/circuits/circuit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ def test_circuit_client_code(qubit):
<button id="camera-reset">Reset Camera</button>
<button id="camera-toggle">Toggle Camera Type</button>
<script>
let viz_{stripped_id} = createGridCircuit({str(circuit_obj)}, {str(moments)}, "{circuit.id}", {circuit.padding_factor});
let viz_{stripped_id} = createGridCircuit(
{str(circuit_obj)}, {str(moments)}, "{circuit.id}", {circuit.padding_factor}
);
document.getElementById("camera-reset").addEventListener('click', () => {{
viz_{stripped_id}.scene.setCameraAndControls(viz_{stripped_id}.circuit);
Expand Down

0 comments on commit cc82f52

Please sign in to comment.