diff --git a/CHANGELOG.md b/CHANGELOG.md index 88a9f2163..caaff410e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ Changelog - Allow `np.ndarray` when writing QAM memory. Disallow non-integer and non-float types. - Fix typo where `qc.compiler.calibration_program` should be `qc.compiler.get_calibration_program()`. +- `DefFrame` string-valued fields that contain JSON strings now round trip to valid Quil and back to + JSON via `DefFrame.out` and `parse`. Quil and JSON both claim `"` as their only string delimiter, + so the JSON `"`s are escaped in the Quil. [v3.0.0](https://github.com/rigetti/pyquil/releases/tag/v3.0.0) ------------------------------------------------------------------------------------ diff --git a/pyquil/_parser/grammar.lark b/pyquil/_parser/grammar.lark index 5f0b93a62..9a5da3fff 100644 --- a/pyquil/_parser/grammar.lark +++ b/pyquil/_parser/grammar.lark @@ -60,7 +60,7 @@ def_circuit : "DEFCIRCUIT" name [ variables ] [ qubit_designators ] ":" indented // | "DEFCIRCUIT" name [ variables ] qubit_designators ":" indented_instrs -> def_circuit_qubits def_frame : "DEFFRAME" frame ( ":" frame_spec+ )? -frame_spec : _NEWLINE_TAB frame_attr ":" (expression | string ) +frame_spec : _NEWLINE_TAB frame_attr ":" ( expression | string ) !frame_attr : "SAMPLE-RATE" | "INITIAL-FREQUENCY" | "DIRECTION" diff --git a/pyquil/_parser/parser.py b/pyquil/_parser/parser.py index 0b345fdc7..c8ebd28a8 100644 --- a/pyquil/_parser/parser.py +++ b/pyquil/_parser/parser.py @@ -1,3 +1,4 @@ +import json import pkgutil import operator from typing import List @@ -142,10 +143,7 @@ def def_frame(self, frame, *specs): for (spec_name, spec_value) in specs: name = names.get(spec_name, None) if name: - if isinstance(spec_value, str) and spec_value[0] == '"' and spec_value[-1] == '"': - # Strip quotes if necessary - spec_value = spec_value[1:-1] - options[name] = spec_value + options[name] = json.loads(str(spec_value)) else: raise ValueError( f"Unexpectected attribute {spec_name} in definition of frame {frame}. " f"{frame}, {specs}" diff --git a/pyquil/quilbase.py b/pyquil/quilbase.py index f3ec7b0d0..476602d8b 100644 --- a/pyquil/quilbase.py +++ b/pyquil/quilbase.py @@ -17,6 +17,8 @@ Contains the core pyQuil objects that correspond to Quil instructions. """ import collections +import json + from numbers import Complex from typing import ( Any, @@ -1375,7 +1377,6 @@ def out(self) -> str: for value, name in options: if value is None: continue - if isinstance(value, str): - value = f'"{value}"' - r += f"\n {name}: {value}" + else: + r += f"\n {name}: {json.dumps(value)}" return r + "\n" diff --git a/test/unit/test_parser.py b/test/unit/test_parser.py index 16b190bd8..c0995c1fa 100644 --- a/test/unit/test_parser.py +++ b/test/unit/test_parser.py @@ -604,6 +604,29 @@ def test_parsing_defframe(): assert excp.value.token == Token("IDENTIFIER", "UNSUPPORTED") +def test_parsing_defframe_round_trip_with_json(): + fdef = DefFrame( + frame=Frame(qubits=[FormalArgument("My-Cool-Qubit")], name="bananas"), + direction="go west", + initial_frequency=123.4, + center_frequency=124.5, + hardware_object='{"key1": 3.1, "key2": "value2"}', + sample_rate=5, + ) + fdef_out = fdef.out() + assert ( + fdef.out() + == r"""DEFFRAME My-Cool-Qubit "bananas": + DIRECTION: "go west" + INITIAL-FREQUENCY: 123.4 + CENTER-FREQUENCY: 124.5 + HARDWARE-OBJECT: "{\"key1\": 3.1, \"key2\": \"value2\"}" + SAMPLE-RATE: 5 +""" + ) + parse_equals(fdef_out, fdef) + + def test_parsing_defcal(): parse_equals("DEFCAL X 0:\n" " NOP\n", DefCalibration("X", [], [Qubit(0)], [NOP])) parse_equals(