Skip to content

Commit cc82f52

Browse files
authored
Fix QASM lexer when identifiers start with keywords (#7018)
* 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
1 parent 6c9db55 commit cc82f52

File tree

7 files changed

+60
-53
lines changed

7 files changed

+60
-53
lines changed

cirq-core/cirq/contrib/acquaintance/gates.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,12 @@ def _decompose_(self, qubits: Sequence['cirq.Qid']) -> Iterator['cirq.OP_TREE']:
316316
parts_qubits = list(left_part + right_part)
317317
parts[i] = parts_qubits[: len(right_part)]
318318
parts[i + 1] = parts_qubits[len(right_part) :]
319-
layers.prior_interstitial.sort(key=op_sort_key) # type: ignore[arg-type]
319+
if op_sort_key is not None:
320+
layers.prior_interstitial.sort(key=op_sort_key)
320321
for l in ('prior_interstitial', 'pre', 'intra', 'post'):
321322
yield getattr(layers, l)
322-
layers.posterior_interstitial.sort(key=op_sort_key) # type: ignore[arg-type]
323+
if op_sort_key is not None:
324+
layers.posterior_interstitial.sort(key=op_sort_key)
323325
yield layers.posterior_interstitial
324326

325327
assert list(

cirq-core/cirq/contrib/qasm_import/_lexer.py

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import re
1616
from typing import Optional
17-
import numpy as np
1817
import ply.lex as lex
1918

2019
from cirq.contrib.qasm_import.exception import QasmException
@@ -35,8 +34,7 @@ def __init__(self):
3534
'reset': 'RESET',
3635
'gate': 'GATE',
3736
'if': 'IF',
38-
'->': 'ARROW',
39-
'==': 'EQ',
37+
'pi': 'PI',
4038
}
4139

4240
tokens = [
@@ -46,7 +44,8 @@ def __init__(self):
4644
'STDGATESINC',
4745
'QELIBINC',
4846
'ID',
49-
'PI',
47+
'ARROW',
48+
'EQ',
5049
] + list(reserved.values())
5150

5251
def t_newline(self, t):
@@ -55,11 +54,6 @@ def t_newline(self, t):
5554

5655
t_ignore = ' \t'
5756

58-
def t_PI(self, t):
59-
r"""pi"""
60-
t.value = np.pi
61-
return t
62-
6357
# all numbers except NATURAL_NUMBERs:
6458
# it's useful to have this separation to be able to handle indices
6559
# separately. In case of the parameter expressions, we are "OR"-ing
@@ -97,38 +91,6 @@ def t_STDGATESINC(self, t):
9791
r"""include(\s+)"stdgates.inc";"""
9892
return t
9993

100-
def t_QREG(self, t):
101-
r"""qreg"""
102-
return t
103-
104-
def t_QUBIT(self, t):
105-
r"""qubit"""
106-
return t
107-
108-
def t_CREG(self, t):
109-
r"""creg"""
110-
return t
111-
112-
def t_BIT(self, t):
113-
r"""bit"""
114-
return t
115-
116-
def t_MEASURE(self, t):
117-
r"""measure"""
118-
return t
119-
120-
def t_RESET(self, t):
121-
r"""reset"""
122-
return t
123-
124-
def t_GATE(self, t):
125-
r"""gate"""
126-
return t
127-
128-
def t_IF(self, t):
129-
r"""if"""
130-
return t
131-
13294
def t_ARROW(self, t):
13395
"""->"""
13496
return t
@@ -139,6 +101,8 @@ def t_EQ(self, t):
139101

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

144108
def t_COMMENT(self, t):

cirq-core/cirq/contrib/qasm_import/_lexer_test.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# limitations under the License.
1414

1515
import pytest
16-
import numpy as np
1716
from cirq.contrib.qasm_import import QasmException
1817
from cirq.contrib.qasm_import._lexer import QasmLexer
1918

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

105104

106-
def test_pi():
105+
@pytest.mark.parametrize('token', QasmLexer.reserved.keys())
106+
def test_keywords(token):
107107
lexer = QasmLexer()
108-
lexer.input('pi')
109-
token = lexer.token()
110-
assert token.type == "PI"
111-
assert token.value == np.pi
108+
identifier = f'{token} {token}'
109+
lexer.input(identifier)
110+
t = lexer.token()
111+
assert t.type == QasmLexer.reserved[token]
112+
assert t.value == token
113+
t2 = lexer.token()
114+
assert t2.type == QasmLexer.reserved[token]
115+
assert t2.value == token
116+
117+
118+
@pytest.mark.parametrize('token', QasmLexer.reserved.keys())
119+
@pytest.mark.parametrize('separator', ['', '_'])
120+
def test_identifier_starts_or_ends_with_keyword(token, separator):
121+
lexer = QasmLexer()
122+
identifier = f'{token}{separator}{token}'
123+
lexer.input(identifier)
124+
t = lexer.token()
125+
assert t.type == "ID"
126+
assert t.value == identifier
112127

113128

114129
def test_qreg():

cirq-core/cirq/contrib/qasm_import/_parser.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,10 +510,13 @@ def p_expr_binary(self, p):
510510

511511
def p_term(self, p):
512512
"""term : NUMBER
513-
| NATURAL_NUMBER
514-
| PI"""
513+
| NATURAL_NUMBER"""
515514
p[0] = p[1]
516515

516+
def p_pi(self, p):
517+
"""term : PI"""
518+
p[0] = np.pi
519+
517520
# qargs : qarg ',' qargs
518521
# | qarg ';'
519522

cirq-core/cirq/contrib/qasm_import/_parser_test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,3 +1499,22 @@ def _test_parse_exception(qasm: str, cirq_err: str, qiskit_err: str | None):
14991499
"Skipped _test_qiskit_parse_exception because "
15001500
"qiskit isn't installed to verify against."
15011501
)
1502+
1503+
1504+
def test_nested_custom_gate_has_keyword_in_name():
1505+
qasm = """OPENQASM 2.0;
1506+
include "qelib1.inc";
1507+
qreg q[1];
1508+
gate gateGate qb { x qb; }
1509+
gate qregGate qa { gateGate qa; }
1510+
qregGate q;
1511+
"""
1512+
qb = cirq.NamedQubit('qb')
1513+
inner = cirq.FrozenCircuit(cirq.X(qb))
1514+
qa = cirq.NamedQubit('qa')
1515+
middle = cirq.FrozenCircuit(cirq.CircuitOperation(inner, qubit_map={qb: qa}))
1516+
q_0 = cirq.NamedQubit('q_0')
1517+
expected = cirq.Circuit(cirq.CircuitOperation(middle, qubit_map={qa: q_0}))
1518+
parser = QasmParser()
1519+
parsed_qasm = parser.parse(qasm)
1520+
assert parsed_qasm.circuit == expected

cirq-web/cirq_web/circuits/circuit.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ def get_client_code(self) -> str:
5757
<button id="camera-reset">Reset Camera</button>
5858
<button id="camera-toggle">Toggle Camera Type</button>
5959
<script>
60-
let viz_{stripped_id} = createGridCircuit({self.serialized_circuit}, {moments}, "{self.id}", {self.padding_factor});
60+
let viz_{stripped_id} = createGridCircuit(
61+
{self.serialized_circuit}, {moments}, "{self.id}", {self.padding_factor}
62+
);
6163
6264
document.getElementById("camera-reset").addEventListener('click', () => {{
6365
viz_{stripped_id}.scene.setCameraAndControls(viz_{stripped_id}.circuit);

cirq-web/cirq_web/circuits/circuit_test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ def test_circuit_client_code(qubit):
5151
<button id="camera-reset">Reset Camera</button>
5252
<button id="camera-toggle">Toggle Camera Type</button>
5353
<script>
54-
let viz_{stripped_id} = createGridCircuit({str(circuit_obj)}, {str(moments)}, "{circuit.id}", {circuit.padding_factor});
54+
let viz_{stripped_id} = createGridCircuit(
55+
{str(circuit_obj)}, {str(moments)}, "{circuit.id}", {circuit.padding_factor}
56+
);
5557
5658
document.getElementById("camera-reset").addEventListener('click', () => {{
5759
viz_{stripped_id}.scene.setCameraAndControls(viz_{stripped_id}.circuit);

0 commit comments

Comments
 (0)