Repository: Qiskit/qiskit

Issue:
Comments should be AST nodes
Consider the following program:
```
from qiskit.qasm import Qasm
qasm_string = """//this is a comment
OPENQASM 2.0;
qreg q[2]; // This is another comment
"""

node_circuit = Qasm(data=qasm_string).parse()
print(node_circuit.qasm())
print([ type(i) for i in node_circuit.children])
```
The output is:
```
OPENQASM 2.0;
qreg q[2];

[<class 'qiskit.qasm._node._format.Format'>, <class 'qiskit.qasm._node._qreg.Qreg'>]
```

### What is the expected enhancement?

The following output:
```
//this is a comment
OPENQASM 2.0; 
qreg q[2];
// This is another comment

[<class 'qiskit.qasm._node._comment.Comment'>, <class 'qiskit.qasm._node._format.Format'>, <class 'qiskit.qasm._node._qreg.Qreg'>, <class 'qiskit.qasm._node._comment.Comment'>]
```

The current parser ignores comments. There is room for discussion if a parser should create an AST with this information (a Full Syntax Tree).  I think in our context is a handy thing to have. For example, to add annotations or metainformation, which might be handy in such experimental field.


Discussion:
Usually, comments are ignored in the lexer stage, but I'm a big fan of comment nodes. A common strategy is to provide a flag for retaining these nodes or not.
I'm not sure we the expected behaviour is what you point. I would have expected the new code to keep the comments in the same locations, aside from some normalization so this:

```
OPENQASM 2.0;                          // a very separated comment
```

would translate to this:

```
OPENQASM 2.0; // a very separated comment
```

But it looks counter-intuitive to find the comment in a new line after the code it is commenting. Even thinking that the behaviour preserves the ordering of the nodes, it challenges the semantic purpose of the comment. Suppose:

```
OPENQASM 2.0;
if (c0 == 1) cnot(q[0], q[1]);  // it depends on the first classical register
if (c1 == 1) cnot(q[0], q[1]);
```

would be translated into:

```
OPENQASM 2.0;
if (c0 == 1) cnot(q[0], q[1]);
// it depends on the first classical register
if (c1 == 1) cnot(q[0], q[1]);
```

which makes the comment to refer to the last statement, which is not true.

I would rather expect this comment to be placed before the statement but the optimal solution is to keep it at the statement level.

The OPEN QASM 2.0 spec is inconsistent regarding comments: the [formal grammar](https://github.com/Qiskit/openqasm/blob/master/spec/qasm2.rst#open-qasm-grammar) does not include them (suggesting they are unimportant from a semantic point of view) but a summarizing statements table at the end of the [language description section]((https://github.com/Qiskit/openqasm/blob/master/spec/qasm2.rst#language)) include comments among the statements.

From a grammatical point of view, one of the solutions is to retain the concrete the [concrete syntax tree or parsing tree](https://en.wikipedia.org/wiki/Parse_tree) and use it to recompose the original form of the source code.

Another, perhaps more practical solution, would be to allow the AST nodes to carry some metainformation that can include an inlinded comment node.
The "unparse" method is `qasm()` and it has some room for improvement in terms of reconstructing line and spacing or using the positioning metadata.
Examples:
<table align="left">
  <tr>
    <th> input to parse </th>
    <th> output of unparse</th>
    </tr>
  <tr>
    <td>
<pre>
OPENQASM 2.0;
qreg q[2]; qreg r[1];
</pre>
   </td>
    <td>
<pre>
OPENQASM 2.0;
qreg q[2];
qreg r[1];
</pre>
    </td>
    </tr>

  <tr>
    <td>
<pre>
OPENQASM 2.0;
qreg q[2];




&nbsp;
qreg r[1];</pre>
   </td>
    <td>
<pre>
OPENQASM 2.0;
qreg q[2];
qreg r[1];
</pre>
    </td>
    </tr>


  <tr>
    <td>
<pre>
OPENQASM 2.0;
qreg q[2];                           qreg r[1];</pre>
   </td>
    <td>
<pre>
OPENQASM 2.0;
qreg q[2];
qreg r[1];
</pre>
    </td>
    </tr>

<tr>
    <td>
<pre>
OPENQASM 2.0;
gate name q {}   </td>
    <td>
<pre>OPENQASM 2.0;
gate name q
{
}
</pre>
    </td>
    </tr>
</table>

Basically, the (un)parser is ignoring spacing and line positioning and my implementation of comment nodes (PR #906) follows that previous philosophy.  Each node unparsing *forces* new spacing and new-lines all over (like in the last case, a gate declaration distributes the curly brackets in different lines). If better unparsing is a feature that we want, join the discussion in #923 .
In your example:
```
if (c0 == 1) cnot(q[0], q[1]);
// it depends on the first classical register
if (c1 == 1) cnot(q[0], q[1]);
```
the reason for this is not connected with the comment node. The new line between the comment and the conditional statement is added by the state concatenation code (see https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/qasm/_node/_program.py#L28). Makes sense to open a new issue about this?
As mentioned before, I don't mind some normalization happening. The output of the unparsing is more verbose, agree, but it is not incorrect. However, making an inline comment to be pushed after the code commented is against a very well-established tradition of putting comments before or at the same level of the commented code.

I don't mind to keep this open to track the fact that OPEANQASM 2.0 has a documented case where comments are relevant for the AST as docstrings that we are not implementing. But the current description of the issue is incorrect, even talking about ASTs only:

![captura de pantalla 2018-09-21 a las 20 10 00](https://user-images.githubusercontent.com/757942/45898490-6502c280-bdda-11e8-9c9f-9d6fbf8de871.png)

It is incorrect because something like:
```
OPENQASM 2.0;
qreg q[2]; // qreg comment
gate custom q { ... }
```
would be parsed as: `[Qreg, Comment, Gate]`.

And:
```
OPENQASM 2.0;
qreg q[2];
// Metadata for custom gate
gate custom q { ... }
```
would be equally parsed: `[Qreg, Comment, Gate]` without a way of distinguishing among them.
The unparser if patched in ff1f735, by keeping line information.

@1ucian0 how do you suggest the AST should look when parsing an inline comment?
after ff1f735, the line information is saved and the unparse puts the inline comment in the correct line. Currently the parse parse this:
```
OPENQASM 2.0;

// Metadata for custom gate
gate custom q { ... }
```
into something like this:
```
prog:
  |- header - number
  |- comment
  |- gate ...
```

Changing it into this:
```
prog:
  |- header - number
  |- gate ...
       |- comment
```
is not trivial.
I think this is going to be very difficult to do in a sensible way.  Consider the code

```CX // argle
q[0],q[1];
```

This is valid, and parses the same as 

```CX q[0],q[1];
```

To make sufficient changes to the AST to support comments basically *anywhere* that are part of the AST, is going to make it insanely ugly.  And it'll make the grammar a real mess, to boot.

But there is hope.  There is a more-or-less standard way of getting what you wish, at the cost of a smarter lexer.  Basically, instead of the lexer returning tokens that are mere strings (or other constants), it returns a triple, of (<token>, <start-position in input>, <end-position in input>).  Both positions are returned, so that (fixed-width) operators and keywords can be handled the same as identifiers and other variable-width tokens.  The AST node has a slot where the tokens returned in parsing that AST node *directly* (not its children) are stored.  And at pretty-printing time, instead of (possibly empty) whitespace between text, the pretty-printer uses the saved tokens to look at the original input stream and pull out the whitespace (if any) which was used originally, and prints that.

I don't know if PLY supports this mode of use; I'll look into it.  I know that Ocaml's "alternate" parser/lexer system, Camlp4/Camlp5 supports it -- it was a design point -- and I suspect that the standard parser/lexer can also be used in that way.

But basically, saving the comments in the AST is .... not a good idea.
What do you mean by `q[0],q[1];` being valid?

It's my impression that the OpenQASM authors thought this way to add "metadata" to the gate definition:
```
// comment
gate name(params) qargs
{ body }
```
I agree that might not be the best way to reach their goal. This kind of comment might be treat differently than `h q[0]; // comment` by the parser. I don't have any use case for their later use. But I would like to support the former.
Arrrgh!  I didn't even *notice* that github took out the comment.  What I meant was that comments are valid anywhere, e.g. between the "," and the "q" in "cx q[0], q[1] ;"

Comments are valid between any two lexemes, and if you want a comment to be a token, then the modifications to the grammar to support that will be .... *painful*.  Whereas the method I suggest is both standard, and straightforward to implement.
My understanding of the specification is that comments are between `//` and line-end.

Probably my original post was wrongly trying to be too generic. Sorry. I'm only interested in the use case from the specification, about adding metadata to gates.
Comments *are* between `//` and line-end.  But remark that
```
cx q[0],
q[1] ;
```

is valid QASM, and hence I can put a comment after the comma.  Similarly with
```
cx q[0]
, q[1] ;
```

And (I'm not going to go experiment, but I'm pretty sure) you could do the same before (or after) the "[".  Etc.

oh! I see what you mean:
```
OPENQASM 2.0;
qreg q[2]; 
CX q[0], // comment 
q[1]; 
```
You are right.
An end-of-line comment is one thing; inserting a comment after the comma, splitting the line and naming a target register on the next line looks quite odd.

Those cases look pathological. I think no classical assembler would accept that syntax?
That said, comments are indeed nodes in nuqasm2, but I have not dealt with the case of an end-of-line comment, which easily could be done.
I wasn't suggesting that such comments are desired; rather, that if you want to make comments be part of the grammar, you're going to have to change where they're allowed, b/c currently such pathological cases are allowed.  That's all I mean.

Also, you're probably right that "old-school line-oriented language-processors" probably don't accept such comments.  And I find that GNU AS doesn't accept them, either. 

The problem is easily solvable -- just prohibit comments except at end-of-statement (and at beginning-of-file).
Currently my nuqasm2 POC accepts comment lines anywhere and adds them to the AST. However, I didn't consider the end-of-line comment, which probably should be permitted.

_2019-04-06 : nuqasm2 now accepts end-of-line comment_ 

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -43,7 +43,6 @@
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.parameter import Parameter
-from qiskit.qasm.qasm import Qasm
 from qiskit.qasm.exceptions import QasmError
 from qiskit.circuit.exceptions import CircuitError
 from .parameterexpression import ParameterExpression, ParameterValueType
@@ -2504,11 +2503,23 @@ def from_qasm_file(path: str) -> "QuantumCircuit":
 
         Args:
           path (str): Path to the file for a QASM program
+
         Return:
           QuantumCircuit: The QuantumCircuit object for the input QASM
+
+        See also:
+            :func:`.qasm2.load`: the complete interface to the OpenQASM 2 importer.
         """
-        qasm = Qasm(filename=path)
-        return _circuit_from_qasm(qasm)
+        # pylint: disable=cyclic-import
+        from qiskit import qasm2
+
+        return qasm2.load(
+            path,
+            include_path=qasm2.LEGACY_INCLUDE_PATH,
+            custom_instructions=qasm2.LEGACY_CUSTOM_INSTRUCTIONS,
+            custom_classical=qasm2.LEGACY_CUSTOM_CLASSICAL,
+            strict=False,
+        )
 
     @staticmethod
     def from_qasm_str(qasm_str: str) -> "QuantumCircuit":
@@ -2518,9 +2529,20 @@ def from_qasm_str(qasm_str: str) -> "QuantumCircuit":
           qasm_str (str): A QASM program string
         Return:
           QuantumCircuit: The QuantumCircuit object for the input QASM
+
+        See also:
+            :func:`.qasm2.loads`: the complete interface to the OpenQASM 2 importer.
         """
-        qasm = Qasm(data=qasm_str)
-        return _circuit_from_qasm(qasm)
+        # pylint: disable=cyclic-import
+        from qiskit import qasm2
+
+        return qasm2.loads(
+            qasm_str,
+            include_path=qasm2.LEGACY_INCLUDE_PATH,
+            custom_instructions=qasm2.LEGACY_CUSTOM_INSTRUCTIONS,
+            custom_classical=qasm2.LEGACY_CUSTOM_CLASSICAL,
+            strict=False,
+        )
 
     @property
     def global_phase(self) -> ParameterValueType:
@@ -4858,16 +4880,6 @@ def qubit_stop_time(self, *qubits: Union[Qubit, int]) -> float:
         return 0  # If there are no instructions over bits
 
 
-def _circuit_from_qasm(qasm: Qasm) -> "QuantumCircuit":
-    # pylint: disable=cyclic-import
-    from qiskit.converters import ast_to_dag
-    from qiskit.converters import dag_to_circuit
-
-    ast = qasm.parse()
-    dag = ast_to_dag(ast)
-    return dag_to_circuit(dag, copy_operations=False)
-
-
 def _standard_compare(value1, value2):
     if value1 < value2:
         return -1


Test patch:
diff --git a/test/python/circuit/test_circuit_load_from_qasm.py b/test/python/qasm2/test_circuit_methods.py
similarity index 97%
rename from test/python/circuit/test_circuit_load_from_qasm.py
rename to test/python/qasm2/test_circuit_methods.py
--- a/test/python/circuit/test_circuit_load_from_qasm.py
+++ b/test/python/qasm2/test_circuit_methods.py
@@ -120,14 +120,6 @@ def test_fail_qasm_file(self):
         """
         self.assertRaises(QiskitError, QuantumCircuit.from_qasm_file, "")
 
-    def test_fail_qasm_string(self):
-        """
-        Test fail_qasm_string.
-
-        If all is correct we should get a QiskitError
-        """
-        self.assertRaises(QiskitError, QuantumCircuit.from_qasm_str, "")
-
     def test_qasm_text(self):
         """
         Test qasm_text and get_circuit.
@@ -492,6 +484,21 @@ def test_from_qasm_str_delay(self):
         expected.delay(172, qr[0])
         self.assertEqualUnroll("u", circuit, expected)
 
+    def test_definition_with_u_cx(self):
+        """Test that gate-definition bodies can use U and CX."""
+        qasm_string = """
+OPENQASM 2.0;
+gate bell q0, q1 { U(pi/2, 0, pi) q0; CX q0, q1; }
+qreg q[2];
+bell q[0], q[1];
+"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+        qr = QuantumRegister(2, "q")
+        expected = QuantumCircuit(qr)
+        expected.h(0)
+        expected.cx(0, 1)
+        self.assertEqualUnroll(["u", "cx"], circuit, expected)
+
     def assertEqualUnroll(self, basis, circuit, expected):
         """Compares the dags after unrolling to basis"""
         circuit_dag = circuit_to_dag(circuit)
diff --git a/test/python/qasm2/test_legacy_importer.py b/test/python/qasm2/test_legacy_importer.py
new file mode 100644
--- /dev/null
+++ b/test/python/qasm2/test_legacy_importer.py
@@ -0,0 +1,508 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2017.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+
+"""Test cases for the legacy OpenQASM 2 parser."""
+
+# pylint: disable=missing-function-docstring
+
+
+import os
+
+from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
+from qiskit.circuit import Gate, Parameter
+from qiskit.converters import ast_to_dag, dag_to_circuit
+from qiskit.exceptions import QiskitError
+from qiskit.qasm import Qasm
+from qiskit.test import QiskitTestCase
+from qiskit.transpiler.passes import Unroller
+from qiskit.converters.circuit_to_dag import circuit_to_dag
+
+
+def from_qasm_str(qasm_str):
+    return dag_to_circuit(ast_to_dag(Qasm(data=qasm_str).parse()))
+
+
+def from_qasm_file(path):
+    return dag_to_circuit(ast_to_dag(Qasm(filename=path).parse()))
+
+
+class LoadFromQasmTest(QiskitTestCase):
+    """Test circuit.from_qasm_* set of methods."""
+
+    def setUp(self):
+        super().setUp()
+        self.qasm_file_name = "entangled_registers.qasm"
+        self.qasm_dir = os.path.join(
+            os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qasm"
+        )
+        self.qasm_file_path = os.path.join(self.qasm_dir, self.qasm_file_name)
+
+    def test_qasm_file(self):
+        """
+        Test qasm_file and get_circuit.
+
+        If all is correct we should get the qasm file loaded in _qasm_file_path
+        """
+        q_circuit = from_qasm_file(self.qasm_file_path)
+        qr_a = QuantumRegister(4, "a")
+        qr_b = QuantumRegister(4, "b")
+        cr_c = ClassicalRegister(4, "c")
+        cr_d = ClassicalRegister(4, "d")
+        q_circuit_2 = QuantumCircuit(qr_a, qr_b, cr_c, cr_d)
+        q_circuit_2.h(qr_a)
+        q_circuit_2.cx(qr_a, qr_b)
+        q_circuit_2.barrier(qr_a)
+        q_circuit_2.barrier(qr_b)
+        q_circuit_2.measure(qr_a, cr_c)
+        q_circuit_2.measure(qr_b, cr_d)
+        self.assertEqual(q_circuit, q_circuit_2)
+
+    def test_loading_all_qelib1_gates(self):
+        """Test setting up a circuit with all gates defined in qiskit/qasm/libs/qelib1.inc."""
+        from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, CU1Gate, CU3Gate, UGate
+
+        all_gates_qasm = os.path.join(self.qasm_dir, "all_gates.qasm")
+        qasm_circuit = from_qasm_file(all_gates_qasm)
+
+        ref_circuit = QuantumCircuit(3, 3)
+
+        # abstract gates (legacy)
+        ref_circuit.append(UGate(0.2, 0.1, 0.6), [0])
+        ref_circuit.cx(0, 1)
+        # the hardware primitives
+        ref_circuit.append(U3Gate(0.2, 0.1, 0.6), [0])
+        ref_circuit.append(U2Gate(0.1, 0.6), [0])
+        ref_circuit.append(U1Gate(0.6), [0])
+        ref_circuit.id(0)
+        ref_circuit.cx(0, 1)
+        # the standard single qubit gates
+        ref_circuit.u(0.2, 0.1, 0.6, 0)
+        ref_circuit.p(0.6, 0)
+        ref_circuit.x(0)
+        ref_circuit.y(0)
+        ref_circuit.z(0)
+        ref_circuit.h(0)
+        ref_circuit.s(0)
+        ref_circuit.t(0)
+        ref_circuit.sdg(0)
+        ref_circuit.tdg(0)
+        ref_circuit.sx(0)
+        ref_circuit.sxdg(0)
+        # the standard rotations
+        ref_circuit.rx(0.1, 0)
+        ref_circuit.ry(0.1, 0)
+        ref_circuit.rz(0.1, 0)
+        # the barrier
+        ref_circuit.barrier()
+        # the standard user-defined gates
+        ref_circuit.swap(0, 1)
+        ref_circuit.cswap(0, 1, 2)
+        ref_circuit.cy(0, 1)
+        ref_circuit.cz(0, 1)
+        ref_circuit.ch(0, 1)
+        ref_circuit.csx(0, 1)
+        ref_circuit.append(CU1Gate(0.6), [0, 1])
+        ref_circuit.append(CU3Gate(0.2, 0.1, 0.6), [0, 1])
+        ref_circuit.cp(0.6, 0, 1)
+        ref_circuit.cu(0.2, 0.1, 0.6, 0, 0, 1)
+        ref_circuit.ccx(0, 1, 2)
+        ref_circuit.crx(0.6, 0, 1)
+        ref_circuit.cry(0.6, 0, 1)
+        ref_circuit.crz(0.6, 0, 1)
+        ref_circuit.rxx(0.2, 0, 1)
+        ref_circuit.rzz(0.2, 0, 1)
+        ref_circuit.measure([0, 1, 2], [0, 1, 2])
+
+        self.assertEqual(qasm_circuit, ref_circuit)
+
+    def test_fail_qasm_file(self):
+        """
+        Test fail_qasm_file.
+
+        If all is correct we should get a QiskitError
+        """
+        self.assertRaises(QiskitError, from_qasm_file, "")
+
+    def test_qasm_text(self):
+        """
+        Test qasm_text and get_circuit.
+
+        If all is correct we should get the qasm file loaded from the string
+        """
+        qasm_string = "// A simple 8 qubit example\nOPENQASM 2.0;\n"
+        qasm_string += 'include "qelib1.inc";\nqreg a[4];\n'
+        qasm_string += "qreg b[4];\ncreg c[4];\ncreg d[4];\nh a;\ncx a, b;\n"
+        qasm_string += "barrier a;\nbarrier b;\nmeasure a[0]->c[0];\n"
+        qasm_string += "measure a[1]->c[1];\nmeasure a[2]->c[2];\n"
+        qasm_string += "measure a[3]->c[3];\nmeasure b[0]->d[0];\n"
+        qasm_string += "measure b[1]->d[1];\nmeasure b[2]->d[2];\n"
+        qasm_string += "measure b[3]->d[3];"
+        q_circuit = from_qasm_str(qasm_string)
+
+        qr_a = QuantumRegister(4, "a")
+        qr_b = QuantumRegister(4, "b")
+        cr_c = ClassicalRegister(4, "c")
+        cr_d = ClassicalRegister(4, "d")
+        ref = QuantumCircuit(qr_a, qr_b, cr_c, cr_d)
+        ref.h(qr_a[3])
+        ref.cx(qr_a[3], qr_b[3])
+        ref.h(qr_a[2])
+        ref.cx(qr_a[2], qr_b[2])
+        ref.h(qr_a[1])
+        ref.cx(qr_a[1], qr_b[1])
+        ref.h(qr_a[0])
+        ref.cx(qr_a[0], qr_b[0])
+        ref.barrier(qr_b)
+        ref.measure(qr_b, cr_d)
+        ref.barrier(qr_a)
+        ref.measure(qr_a, cr_c)
+
+        self.assertEqual(len(q_circuit.cregs), 2)
+        self.assertEqual(len(q_circuit.qregs), 2)
+        self.assertEqual(q_circuit, ref)
+
+    def test_qasm_text_conditional(self):
+        """
+        Test qasm_text and get_circuit when conditionals are present.
+        """
+        qasm_string = (
+            "\n".join(
+                [
+                    "OPENQASM 2.0;",
+                    'include "qelib1.inc";',
+                    "qreg q[1];",
+                    "creg c0[4];",
+                    "creg c1[4];",
+                    "x q[0];",
+                    "if(c1==4) x q[0];",
+                ]
+            )
+            + "\n"
+        )
+        q_circuit = from_qasm_str(qasm_string)
+
+        qr = QuantumRegister(1, "q")
+        cr0 = ClassicalRegister(4, "c0")
+        cr1 = ClassicalRegister(4, "c1")
+        ref = QuantumCircuit(qr, cr0, cr1)
+        ref.x(qr[0])
+        ref.x(qr[0]).c_if(cr1, 4)
+
+        self.assertEqual(len(q_circuit.cregs), 2)
+        self.assertEqual(len(q_circuit.qregs), 1)
+        self.assertEqual(q_circuit, ref)
+
+    def test_opaque_gate(self):
+        """
+        Test parse an opaque gate
+
+        See https://github.com/Qiskit/qiskit-terra/issues/1566.
+        """
+
+        qasm_string = (
+            "\n".join(
+                [
+                    "OPENQASM 2.0;",
+                    'include "qelib1.inc";',
+                    "opaque my_gate(theta,phi,lambda) a,b;",
+                    "qreg q[3];",
+                    "my_gate(1,2,3) q[1],q[2];",
+                ]
+            )
+            + "\n"
+        )
+        circuit = from_qasm_str(qasm_string)
+
+        qr = QuantumRegister(3, "q")
+        expected = QuantumCircuit(qr)
+        expected.append(Gate(name="my_gate", num_qubits=2, params=[1, 2, 3]), [qr[1], qr[2]])
+
+        self.assertEqual(circuit, expected)
+
+    def test_qasm_example_file(self):
+        """Loads qasm/example.qasm."""
+        qasm_filename = os.path.join(self.qasm_dir, "example.qasm")
+        expected_circuit = from_qasm_str(
+            "\n".join(
+                [
+                    "OPENQASM 2.0;",
+                    'include "qelib1.inc";',
+                    "qreg q[3];",
+                    "qreg r[3];",
+                    "creg c[3];",
+                    "creg d[3];",
+                    "h q[2];",
+                    "cx q[2],r[2];",
+                    "measure r[2] -> d[2];",
+                    "h q[1];",
+                    "cx q[1],r[1];",
+                    "measure r[1] -> d[1];",
+                    "h q[0];",
+                    "cx q[0],r[0];",
+                    "measure r[0] -> d[0];",
+                    "barrier q[0],q[1],q[2];",
+                    "measure q[2] -> c[2];",
+                    "measure q[1] -> c[1];",
+                    "measure q[0] -> c[0];",
+                ]
+            )
+            + "\n"
+        )
+
+        q_circuit = from_qasm_file(qasm_filename)
+
+        self.assertEqual(q_circuit, expected_circuit)
+        self.assertEqual(len(q_circuit.cregs), 2)
+        self.assertEqual(len(q_circuit.qregs), 2)
+
+    def test_qasm_qas_string_order(self):
+        """Test that gates are returned in qasm in ascending order."""
+        expected_qasm = (
+            "\n".join(
+                [
+                    "OPENQASM 2.0;",
+                    'include "qelib1.inc";',
+                    "qreg q[3];",
+                    "h q[0];",
+                    "h q[1];",
+                    "h q[2];",
+                ]
+            )
+            + "\n"
+        )
+        qasm_string = """OPENQASM 2.0;
+        include "qelib1.inc";
+        qreg q[3];
+        h q;"""
+        q_circuit = from_qasm_str(qasm_string)
+
+        self.assertEqual(q_circuit.qasm(), expected_qasm)
+
+    def test_from_qasm_str_custom_gate1(self):
+        """Test load custom gates (simple case)"""
+        qasm_string = """OPENQASM 2.0;
+                        include "qelib1.inc";
+                        gate rinv q {sdg q; h q; sdg q; h q; }
+                        qreg qr[1];
+                        rinv qr[0];"""
+        circuit = from_qasm_str(qasm_string)
+
+        rinv_q = QuantumRegister(1, name="q")
+        rinv_gate = QuantumCircuit(rinv_q, name="rinv")
+        rinv_gate.sdg(rinv_q)
+        rinv_gate.h(rinv_q)
+        rinv_gate.sdg(rinv_q)
+        rinv_gate.h(rinv_q)
+        rinv = rinv_gate.to_instruction()
+        qr = QuantumRegister(1, name="qr")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.append(rinv, [qr[0]])
+
+        self.assertEqualUnroll(["sdg", "h"], circuit, expected)
+
+    def test_from_qasm_str_custom_gate2(self):
+        """Test load custom gates (no so simple case, different bit order)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate swap2 a,b {
+                           cx a,b;
+                           cx b,a;  // different bit order
+                           cx a,b;
+                         }
+                         qreg qr[3];
+                         swap2 qr[0], qr[1];
+                         swap2 qr[1], qr[2];"""
+        circuit = from_qasm_str(qasm_string)
+
+        ab_args = QuantumRegister(2, name="ab")
+        swap_gate = QuantumCircuit(ab_args, name="swap2")
+        swap_gate.cx(ab_args[0], ab_args[1])
+        swap_gate.cx(ab_args[1], ab_args[0])
+        swap_gate.cx(ab_args[0], ab_args[1])
+        swap = swap_gate.to_instruction()
+
+        qr = QuantumRegister(3, name="qr")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.append(swap, [qr[0], qr[1]])
+        expected.append(swap, [qr[1], qr[2]])
+
+        self.assertEqualUnroll(["cx"], expected, circuit)
+
+    def test_from_qasm_str_custom_gate3(self):
+        """Test load custom gates (no so simple case, different bit count)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate cswap2 a,b,c
+                         {
+                           cx c,b;  // different bit count
+                           ccx a,b,c; //previously defined gate
+                           cx c,b;
+                         }
+                         qreg qr[3];
+                         cswap2 qr[1], qr[0], qr[2];"""
+        circuit = from_qasm_str(qasm_string)
+
+        abc_args = QuantumRegister(3, name="abc")
+        cswap_gate = QuantumCircuit(abc_args, name="cswap2")
+        cswap_gate.cx(abc_args[2], abc_args[1])
+        cswap_gate.ccx(abc_args[0], abc_args[1], abc_args[2])
+        cswap_gate.cx(abc_args[2], abc_args[1])
+        cswap = cswap_gate.to_instruction()
+
+        qr = QuantumRegister(3, name="qr")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.append(cswap, [qr[1], qr[0], qr[2]])
+
+        self.assertEqualUnroll(["cx", "h", "tdg", "t"], circuit, expected)
+
+    def test_from_qasm_str_custom_gate4(self):
+        """Test load custom gates (parameterized)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_gate(phi,lambda) q {u(1.5707963267948966,phi,lambda) q;}
+                         qreg qr[1];
+                         my_gate(pi, pi) qr[0];"""
+        circuit = from_qasm_str(qasm_string)
+
+        my_gate_circuit = QuantumCircuit(1, name="my_gate")
+        phi = Parameter("phi")
+        lam = Parameter("lambda")
+        my_gate_circuit.u(1.5707963267948966, phi, lam, 0)
+        my_gate = my_gate_circuit.to_gate()
+
+        qr = QuantumRegister(1, name="qr")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.append(my_gate, [qr[0]])
+        expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793})
+
+        self.assertEqualUnroll("u", circuit, expected)
+
+    def test_from_qasm_str_custom_gate5(self):
+        """Test load custom gates (parameterized, with biop and constant)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_gate(phi,lambda) q {u(pi/2,phi,lambda) q;} // biop with pi
+                         qreg qr[1];
+                         my_gate(pi, pi) qr[0];"""
+        circuit = from_qasm_str(qasm_string)
+
+        my_gate_circuit = QuantumCircuit(1, name="my_gate")
+        phi = Parameter("phi")
+        lam = Parameter("lambda")
+        my_gate_circuit.u(1.5707963267948966, phi, lam, 0)
+        my_gate = my_gate_circuit.to_gate()
+
+        qr = QuantumRegister(1, name="qr")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.append(my_gate, [qr[0]])
+        expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793})
+
+        self.assertEqualUnroll("u", circuit, expected)
+
+    def test_from_qasm_str_custom_gate6(self):
+        """Test load custom gates (parameters used in expressions)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-591668924
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_gate(phi,lambda) q
+                           {rx(phi+pi) q; ry(lambda/2) q;}  // parameters used in expressions
+                         qreg qr[1];
+                         my_gate(pi, pi) qr[0];"""
+        circuit = from_qasm_str(qasm_string)
+
+        my_gate_circuit = QuantumCircuit(1, name="my_gate")
+        phi = Parameter("phi")
+        lam = Parameter("lambda")
+        my_gate_circuit.rx(phi + 3.141592653589793, 0)
+        my_gate_circuit.ry(lam / 2, 0)
+        my_gate = my_gate_circuit.to_gate()
+
+        qr = QuantumRegister(1, name="qr")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.append(my_gate, [qr[0]])
+        expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793})
+
+        self.assertEqualUnroll(["rx", "ry"], circuit, expected)
+
+    def test_from_qasm_str_custom_gate7(self):
+        """Test load custom gates (build in functions)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592208951
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_gate(phi,lambda) q
+                            {u(asin(cos(phi)/2), phi+pi, lambda/2) q;}  // build func
+                         qreg qr[1];
+                         my_gate(pi, pi) qr[0];"""
+        circuit = from_qasm_str(qasm_string)
+
+        qr = QuantumRegister(1, name="qr")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.u(-0.5235987755982988, 6.283185307179586, 1.5707963267948966, qr[0])
+        self.assertEqualUnroll("u", circuit, expected)
+
+    def test_from_qasm_str_nested_custom_gate(self):
+        """Test chain of custom gates
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592261942
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_other_gate(phi,lambda) q
+                           {u(asin(cos(phi)/2), phi+pi, lambda/2) q;}
+                         gate my_gate(phi) r
+                           {my_other_gate(phi, phi+pi) r;}
+                         qreg qr[1];
+                         my_gate(pi) qr[0];"""
+        circuit = from_qasm_str(qasm_string)
+
+        qr = QuantumRegister(1, name="qr")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.u(-0.5235987755982988, 6.283185307179586, 3.141592653589793, qr[0])
+        self.assertEqualUnroll("u", circuit, expected)
+
+    def test_from_qasm_str_delay(self):
+        """Test delay instruction/opaque-gate
+        See: https://github.com/Qiskit/qiskit-terra/issues/6510
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+
+                         opaque delay(time) q;
+
+                         qreg q[1];
+                         delay(172) q[0];"""
+        circuit = from_qasm_str(qasm_string)
+
+        qr = QuantumRegister(1, name="q")
+        expected = QuantumCircuit(qr, name="circuit")
+        expected.delay(172, qr[0])
+        self.assertEqualUnroll("u", circuit, expected)
+
+    def assertEqualUnroll(self, basis, circuit, expected):
+        """Compares the dags after unrolling to basis"""
+        circuit_dag = circuit_to_dag(circuit)
+        expected_dag = circuit_to_dag(expected)
+
+        circuit_result = Unroller(basis).run(circuit_dag)
+        expected_result = Unroller(basis).run(expected_dag)
+
+        self.assertEqual(circuit_result, expected_result)