Repository: rigetticomputing/pyquil
Pull request #513 (2018-07-26 02:28:25)

Fix defcircuit PyQuil parsing

stevenheidel: Fixes #455 


Patch:
diff --git a/README.md b/README.md
index 35139ab..af0170b 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ each measurement pair will be `00` or `11`.)
 ...        MEASURE(0, 0),
 ...        MEASURE(1, 1))
 <pyquil.pyquil.Program object at 0x101ebfc50>
->>> print p
+>>> print(p)
 H 0
 CNOT 0 1
 MEASURE 0 [0]
@@ -100,6 +100,8 @@ The following projects have been contributed by community members:
   contributed by [Ravisankar A V](https://github.com/rasa97)
 - [Quil in Javascript](https://github.com/mapmeld/jsquil)
   contributed by [Nick Doiron](https://github.com/mapmeld)
+- [Quil in Java](https://github.com/QCHackers/jquil)
+  contributed by [Victory Omole](https://github.com/vtomole)
 
 ## Developing PyQuil
 
diff --git a/pyquil/_parser/PyQuilListener.py b/pyquil/_parser/PyQuilListener.py
index 7c74a78..ac1b142 100644
--- a/pyquil/_parser/PyQuilListener.py
+++ b/pyquil/_parser/PyQuilListener.py
@@ -17,7 +17,6 @@
 import operator
 from typing import Any, List, Iterator, Callable
 from numbers import Number
-import sys
 
 import numpy as np
 from antlr4 import InputStream, CommonTokenStream, ParseTreeWalker
@@ -34,14 +33,9 @@ from pyquil.quilbase import Gate, DefGate, Measurement, Addr, JumpTarget, Label,
     Reset, Wait, ClassicalTrue, ClassicalFalse, ClassicalNot, ClassicalAnd, ClassicalOr, ClassicalMove, \
     ClassicalExchange, Nop, RawInstr, Qubit, Pragma, AbstractInstruction
 
-if sys.version_info.major == 2:
-    from .gen2.QuilLexer import QuilLexer
-    from .gen2.QuilListener import QuilListener
-    from .gen2 .QuilParser import QuilParser
-elif sys.version_info.major == 3:
-    from .gen3.QuilLexer import QuilLexer
-    from .gen3.QuilListener import QuilListener
-    from .gen3.QuilParser import QuilParser
+from .gen3.QuilLexer import QuilLexer
+from .gen3.QuilListener import QuilListener
+from .gen3.QuilParser import QuilParser
 
 
 def run_parser(quil):
@@ -102,22 +96,42 @@ class PyQuilListener(QuilListener):
     Functions are invoked when the parser reaches the various different constructs in Quil.
     """
     def __init__(self):
-        # type: () -> None
-        self.result = []    # type: List[AbstractInstruction]
+        self.result = []  # type: List[AbstractInstruction]
+        self.previous_result = None  # type: List[AbstractInstruction]
 
-    def exitDefGate(self, ctx):
-        # type: (QuilParser.DefGateContext) -> None
+    def exitDefGate(self, ctx: QuilParser.DefGateContext):
         gate_name = ctx.name().getText()
         matrix = _matrix(ctx.matrix())
         parameters = list(map(_variable, ctx.variable()))
         self.result.append(DefGate(gate_name, matrix, parameters))
 
-    def exitDefCircuit(self, ctx):
-        # type: (QuilParser.DefCircuitContext) -> None
-        self.result.append(RawInstr(ctx.getText()))
+    # DEFCIRCUIT parsing:
+    # When we enter a circuit definition we create a backup of the instructions seen up to that point. Then, when the
+    # listener continues walking through the circuit instructions it will add to an empty list. Once we leave the
+    # circuit we then take all those instructions, shove them into a RawInstr (since PyQuil has no support for circuit
+    # definitions yet), recover the backup, and then continue on our way.
 
-    def exitGate(self, ctx):
-        # type: (QuilParser.GateContext) -> None
+    def enterDefCircuit(self, ctx: QuilParser.DefCircuitContext) -> None:
+        self.previous_result = self.result
+        self.result = []
+
+    def exitDefCircuit(self, ctx: QuilParser.DefCircuitContext):
+        circuit_name = ctx.name().getText()
+        variables = [variable.getText() for variable in ctx.variable()]
+        qubitVariables = [qubitVariable.getText() for qubitVariable in ctx.qubitVariable()]
+
+        if variables:
+            raw_defcircuit = 'DEFCIRCUIT {}({}) {}:'.format(circuit_name, ', '.join(variables), ' '.join(qubitVariables))
+        else:
+            raw_defcircuit = 'DEFCIRCUIT {} {}:'.format(circuit_name, ' '.join(qubitVariables))
+
+        raw_defcircuit += '\n    '.join([''] + [instr.out() for instr in self.result])
+        self.previous_result.append(RawInstr(raw_defcircuit))
+
+        self.result = self.previous_result
+        self.previous_result = None
+
+    def exitGate(self, ctx: QuilParser.GateContext):
         gate_name = ctx.name().getText()
         params = list(map(_param, ctx.param()))
         qubits = list(map(_qubit, ctx.qubit()))
@@ -130,8 +144,20 @@ class PyQuilListener(QuilListener):
         else:
             self.result.append(Gate(gate_name, params, qubits))
 
-    def exitMeasure(self, ctx):
-        # type: (QuilParser.MeasureContext) -> None
+    def exitCircuitGate(self, ctx: QuilParser.CircuitGateContext):
+        """
+        PyQuil has no constructs yet for representing gate instructions within a DEFCIRCUIT (ie. gates where the qubits
+        are inputs to the call to the circuit). Therefore we parse them as a raw instructions.
+        """
+        gate_name = ctx.name().getText()
+        params = [param.getText() for param in ctx.param()]
+        qubits = [qubit.getText() for qubit in ctx.circuitQubit()]
+        if params:
+            self.result.append(RawInstr('{}({}) {}'.format(gate_name, ', '.join(params), ' '.join(qubits))))
+        else:
+            self.result.append(RawInstr('{} {}'.format(gate_name, ' '.join(qubits))))
+
+    def exitMeasure(self, ctx: QuilParser.MeasureContext):
         qubit = _qubit(ctx.qubit())
         classical = None
         if ctx.addr():
diff --git a/pyquil/quilbase.py b/pyquil/quilbase.py
index caddadc..c4eed71 100644
--- a/pyquil/quilbase.py
+++ b/pyquil/quilbase.py
@@ -466,4 +466,4 @@ class RawInstr(AbstractInstruction):
         return self.instr
 
     def __repr__(self):
-        return '<RawInstr>'
+        return '<RawInstr {}>'.format(self.instr)


Test patch:
diff --git a/pyquil/tests/test_parser.py b/pyquil/tests/test_parser.py
index 828d429..75a3ded 100644
--- a/pyquil/tests/test_parser.py
+++ b/pyquil/tests/test_parser.py
@@ -20,7 +20,7 @@ from pyquil.gates import *
 from pyquil.parameters import Parameter, quil_sin, quil_cos, Segment, Add
 from pyquil.parser import parse
 from pyquil.quilatom import Addr
-from pyquil.quilbase import Label, JumpTarget, Jump, JumpWhen, JumpUnless, DefGate, Qubit, Pragma
+from pyquil.quilbase import Label, JumpTarget, Jump, JumpWhen, JumpUnless, DefGate, Qubit, Pragma, RawInstr
 from pyquil.tests.utils import parse_equals
 
 
@@ -182,3 +182,12 @@ def test_invalid():
 
 def test_empty_program():
     parse_equals("")
+
+
+def test_def_circuit():
+    defcircuit = """
+DEFCIRCUIT bell a b:
+    H a
+    CNOT a b
+""".strip()
+    parse_equals(defcircuit, RawInstr(defcircuit))
diff --git a/pyquil/tests/utils.py b/pyquil/tests/utils.py
index 84c70ff..0fdde9c 100644
--- a/pyquil/tests/utils.py
+++ b/pyquil/tests/utils.py
@@ -2,4 +2,6 @@ from pyquil.parser import parse
 
 
 def parse_equals(quil_string, *instructions):
-    assert list(instructions) == parse(quil_string)
+    expected = list(instructions)
+    actual = parse(quil_string)
+    assert expected == actual