From 16cdcf19b32bac5e93fc648d2a61b045d7983acf Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Wed, 22 May 2024 17:18:09 +0200 Subject: [PATCH 01/22] Fix building of tests in Conan profiles. Fix building of tests in setup.py. Update README.md. --- README.md | 4 ++-- conan/profiles/debug | 3 --- conan/profiles/release | 3 --- conan/profiles/tests-debug | 3 +++ conan/profiles/tests-release | 3 +++ setup.py | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5f10f21f..5e72925a 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ All the `tests`, except for `linux-x64` profiles, enable Address Sanitizer. Profiles are a shorthand for command line options. The command above could be written, similarly, as: ``` -conan build . -s:a compiler.cppstd=20 -s:a libqasm/*:build_type=Debug -o libqasm/*:build_tests=True -o libqasm/*:asan_enabled=True -b missing +conan build . -s:a compiler.cppstd=20 -s:a libqasm/*:build_type=Debug -o libqasm/*:asan_enabled=True -c tools.build:skip_test=False -b missing ``` This is the list of options that could be specified either in a profile or in the command line: @@ -90,7 +90,7 @@ This is the list of options that could be specified either in a profile or in th - `libqasm/*:build_type={Debug,Release}`: builds in debug or release mode. - `libqasm/*:shared={True,False}`: builds a shared object library instead of a static library, if applicable. -Tests are enabled by default. To disable them, use `-c tools.build:skip_test=True`. +Tests are disabled by default. To enable them, use `-c tools.build:skip_test=False`. ## Install diff --git a/conan/profiles/debug b/conan/profiles/debug index 808a1a9d..734c83b4 100644 --- a/conan/profiles/debug +++ b/conan/profiles/debug @@ -7,8 +7,5 @@ libqasm/*:build_type=Debug [options] libqasm/*:asan_enabled=False -[conf] -tools.build:skip_test=True - [replace_requires] nodejs/*: nodejs/16.20.2 diff --git a/conan/profiles/release b/conan/profiles/release index bbb6c22f..9399ae9b 100644 --- a/conan/profiles/release +++ b/conan/profiles/release @@ -7,8 +7,5 @@ libqasm/*:build_type=Release [options] libqasm/*:asan_enabled=False -[conf] -tools.build:skip_test=True - [replace_requires] nodejs/*: nodejs/16.20.2 diff --git a/conan/profiles/tests-debug b/conan/profiles/tests-debug index cc5c85ed..a1e59481 100644 --- a/conan/profiles/tests-debug +++ b/conan/profiles/tests-debug @@ -6,3 +6,6 @@ libqasm/*:build_type=Debug [options] libqasm/*:asan_enabled=True + +[conf] +tools.build:skip_test=False diff --git a/conan/profiles/tests-release b/conan/profiles/tests-release index 8bb289fc..ef26bed8 100644 --- a/conan/profiles/tests-release +++ b/conan/profiles/tests-release @@ -6,3 +6,6 @@ libqasm/*:build_type=Release [options] libqasm/*:asan_enabled=True + +[conf] +tools.build:skip_test=False diff --git a/setup.py b/setup.py index 979f96e5..62af7f89 100755 --- a/setup.py +++ b/setup.py @@ -107,8 +107,8 @@ def run(self): ['-b']['missing'] ['-tf'][''] ) - if not build_tests: - cmd = cmd['-c']['tools.build:skip_test=True'] + if build_tests: + cmd = cmd['-c']['tools.build:skip_test=False'] if platform.system() == "Darwin": cmd = cmd['-c']['tools.build:defines=["_LIBCPP_DISABLE_AVAILABILITY"]'] cmd & FG From b4f8cb3b1aaa04466299d1141356b7134fc6af8a Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Wed, 22 May 2024 17:24:41 +0200 Subject: [PATCH 02/22] Fix building of tests in setup.py. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 62af7f89..3c804910 100755 --- a/setup.py +++ b/setup.py @@ -107,7 +107,7 @@ def run(self): ['-b']['missing'] ['-tf'][''] ) - if build_tests: + if build_tests == "True": cmd = cmd['-c']['tools.build:skip_test=False'] if platform.system() == "Darwin": cmd = cmd['-c']['tools.build:defines=["_LIBCPP_DISABLE_AVAILABILITY"]'] From 76e95310ae341317401273f2b748a85b126e2964 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 4 Jul 2024 12:22:30 +0200 Subject: [PATCH 03/22] Fix deserialization of CBOR strings. --- python/module/cqasm/v3x/primitives.py | 6 +++++ test/v3x/python/test_v3x_analyzer.py | 32 +++++++++++++-------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/python/module/cqasm/v3x/primitives.py b/python/module/cqasm/v3x/primitives.py index dc5e8fe9..b74557ed 100644 --- a/python/module/cqasm/v3x/primitives.py +++ b/python/module/cqasm/v3x/primitives.py @@ -45,6 +45,12 @@ def serialize(typ, val): def deserialize(typ, val): if isinstance(typ, str): return None + elif isinstance(val['x'], bytes) and typ is Str: + # CBOR strings are serialized as bytes objects + # The correct conversion to Str is through a decoding + # Str(b'qubit', 'utf-8') would generate the string "qubit". This is the same as b'qubit'.decode('utf-8') + # Whereas Str(b'qubit') would generate the string "b'qubit'" + return Str(val['x'], 'utf-8') elif typ is Str: return Str(val['x']) elif typ is Bool: diff --git a/test/v3x/python/test_v3x_analyzer.py b/test/v3x/python/test_v3x_analyzer.py index 46a795eb..7d270ef4 100644 --- a/test/v3x/python/test_v3x_analyzer.py +++ b/test/v3x/python/test_v3x_analyzer.py @@ -12,26 +12,26 @@ def test_parse_string_returning_ast(self): self.assertEqual(ast.version.items[0], 3) qubit_array = ast.block.statements[0] - self.assertEqual(qubit_array.name.name, "b'q'") - self.assertEqual(qubit_array.typ.name.name, "b'qubit'") + self.assertEqual(qubit_array.name.name, "q") + self.assertEqual(qubit_array.typ.name.name, "qubit") self.assertEqual(qubit_array.typ.size.value, 5) bit_array = ast.block.statements[1] - self.assertEqual(bit_array.name.name, "b'b'") - self.assertEqual(bit_array.typ.name.name, "b'bit'") + self.assertEqual(bit_array.name.name, "b") + self.assertEqual(bit_array.typ.name.name, "bit") self.assertEqual(bit_array.typ.size.value, 5) h_instruction = ast.block.statements[2] - self.assertEqual(h_instruction.name.name, "b'H'") + self.assertEqual(h_instruction.name.name, "H") h_operand = h_instruction.operands.items[0] - self.assertEqual(h_operand.expr.name, "b'q'") + self.assertEqual(h_operand.expr.name, "q") self.assertEqual(h_operand.indices.items[0].first.value, 0) self.assertEqual(h_operand.indices.items[0].last.value, 4) measure_instruction = ast.block.statements[3] - self.assertEqual(measure_instruction.name.name, "b'measure'") - self.assertEqual(measure_instruction.lhs.name, "b'b'") - self.assertEqual(measure_instruction.rhs.name, "b'q'") + self.assertEqual(measure_instruction.name.name, "measure") + self.assertEqual(measure_instruction.lhs.name, "b") + self.assertEqual(measure_instruction.rhs.name, "q") def test_parse_string_returning_errors(self): program_str = "version 3;qubit[5] q;bit[5] b;H q[0:4];b = measure" @@ -49,9 +49,9 @@ def test_analyze_string_returning_ast(self): self.assertEqual(ast.version.items[0], 3) h_instruction = ast.block.statements[0] - self.assertEqual(h_instruction.name, "b'H'") + self.assertEqual(h_instruction.name, "H") h_operand = h_instruction.operands[0] - self.assertEqual(h_operand.variable.name, "b'q'") + self.assertEqual(h_operand.variable.name, "q") self.assertIsInstance(h_operand.variable.typ, cq.types.QubitArray) self.assertEqual(h_operand.variable.typ.size, 5) self.assertEqual(h_operand.indices[0].value, 0) @@ -61,21 +61,21 @@ def test_analyze_string_returning_ast(self): self.assertEqual(h_operand.indices[4].value, 4) measure_instruction = ast.block.statements[1] - self.assertEqual(measure_instruction.name, "b'measure'") + self.assertEqual(measure_instruction.name, "measure") measure_bit_operand = measure_instruction.operands[0] - self.assertEqual(measure_bit_operand.variable.name, "b'b'") + self.assertEqual(measure_bit_operand.variable.name, "b") self.assertIsInstance(measure_bit_operand.variable.typ, cq.types.BitArray) self.assertEqual(measure_bit_operand.variable.typ.size, 5) measure_qubit_operand = measure_instruction.operands[1] - self.assertEqual(measure_qubit_operand.variable.name, "b'q'") + self.assertEqual(measure_qubit_operand.variable.name, "q") self.assertIsInstance(measure_qubit_operand.variable.typ, cq.types.QubitArray) self.assertEqual(measure_qubit_operand.variable.typ.size, 5) qubit_array = ast.variables[0] - self.assertEqual(qubit_array.name, "b'q'") + self.assertEqual(qubit_array.name, "q") self.assertIsInstance(qubit_array.typ, cq.types.QubitArray) bit_array = ast.variables[1] - self.assertEqual(bit_array.name, "b'b'") + self.assertEqual(bit_array.name, "b") self.assertIsInstance(bit_array.typ, cq.types.BitArray) def test_analyze_string_returning_errors(self): From d1a79ebb994e6d6b4ee895991b4bc729ce2781ed Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 4 Jul 2024 12:31:42 +0200 Subject: [PATCH 04/22] Fix check for bytes objects. --- python/module/cqasm/v3x/primitives.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/module/cqasm/v3x/primitives.py b/python/module/cqasm/v3x/primitives.py index b74557ed..8e38071d 100644 --- a/python/module/cqasm/v3x/primitives.py +++ b/python/module/cqasm/v3x/primitives.py @@ -45,9 +45,8 @@ def serialize(typ, val): def deserialize(typ, val): if isinstance(typ, str): return None - elif isinstance(val['x'], bytes) and typ is Str: - # CBOR strings are serialized as bytes objects - # The correct conversion to Str is through a decoding + elif typ is Str and isinstance(val['x'], bytes): + # CBOR strings are bytes objects. The correct conversion to Str would be through a decoding. # Str(b'qubit', 'utf-8') would generate the string "qubit". This is the same as b'qubit'.decode('utf-8') # Whereas Str(b'qubit') would generate the string "b'qubit'" return Str(val['x'], 'utf-8') From 93cfdc5a84dc558d8775b2c8654fc1174f108438 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:19:58 +0200 Subject: [PATCH 05/22] Fix generation of MacOS/x64 (with Python >= 3.11) wheels. Remove support for Python 3.8. Use cibuildwheel for generating the wheels. Add pyproject.toml. Update setup.py. - Remove bdist, sdist, and egg_info. --- .github/workflows/assets.yml | 269 ++++++----------------------------- pyproject.toml | 22 +++ setup.py | 58 +++----- 3 files changed, 85 insertions(+), 264 deletions(-) create mode 100644 pyproject.toml diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml index 696abadf..43cbd132 100644 --- a/.github/workflows/assets.yml +++ b/.github/workflows/assets.yml @@ -9,247 +9,61 @@ on: - "release**" jobs: - macos-x64: - name: PyPI wheels for macOS/x64 - runs-on: macos-13 # x64 + cibw-wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: matrix: - python: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" + # macos-13 is an x64 runner, macos-14 is an arm64 runner + os: [ubuntu-latest, windows-latest, macos-13, macos-14] steps: - name: Checkout uses: actions/checkout@v4 - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip conan setuptools wheel - brew install swig - - name: Build wheel - run: python setup.py bdist_wheel - - name: Wheel path - id: wheel - working-directory: pybuild/dist/ - run: | - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_OUTPUT - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.18.1 + - name: Build wheels + run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 with: - name: pypi-macos-x64-py${{ matrix.python }} - path: pybuild/dist/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: pybuild/dist/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip + name: cibw-wheels-${{ matrix.os }} + path: ./wheelhouse/*.whl - macos-arm64: - name: PyPI wheels for macOS/arm64 - runs-on: macos-14 # arm64 - strategy: - matrix: - python: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" + cibw-wheels-linux-arm64: + name: Build wheels on linux-arm64 + runs-on: [self-hosted, ARM64, Linux] + container: + image: python:3.12-slim + volumes: + - /var/run/docker.sock:/var/run/docker.sock steps: - name: Checkout uses: actions/checkout@v4 - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip conan setuptools wheel - brew install swig - - name: Build wheel - run: python setup.py bdist_wheel - - name: Wheel path - id: wheel - working-directory: pybuild/dist/ - run: | - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_OUTPUT - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV - - uses: actions/upload-artifact@v4 - with: - name: pypi-macos-arm64-py${{ matrix.python }} - path: pybuild/dist/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: pybuild/dist/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip - - manylinux-x64: - name: PyPI wheels for Manylinux (x64) - runs-on: ubuntu-latest - container: quay.io/pypa/manylinux${{ matrix.manylinux }}_x86_64:latest - env: - SWIG_VERSION: ${{ matrix.swig_version }} - strategy: - matrix: - manylinux: - - "_2_28" - cpython_version: - - "cp38-cp38" - - "cp39-cp39" - - "cp310-cp310" - - "cp311-cp311" - - "cp312-cp312" - include: - - manylinux: _2_28 - swig_version: 'swig-3.0.12-19.module_el8.3.0+6167+838326ab' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install dependencies + - name: Install docker run: | - dnf install -y $SWIG_VERSION - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - python -m pip install --upgrade pip conan wheel auditwheel - - name: Build wheel - run: | - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - conan remove -c "*/*" - /opt/python/${{ matrix.cpython_version }}/bin/python setup.py bdist_wheel - /opt/python/${{ matrix.cpython_version }}/bin/python -m auditwheel repair pybuild/dist/*.whl - - name: Wheel path - id: wheel - working-directory: wheelhouse - run: | - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_OUTPUT - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV - - uses: actions/upload-artifact@v4 - with: - name: pypi-linux-x64-${{ matrix.cpython_version }} - path: wheelhouse/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: wheelhouse/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip + apt-get update + apt-get install -y ca-certificates curl + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc - manylinux-arm64: - name: PyPI wheels for Manylinux (arm64) - runs-on: - - "self-hosted" - - "ARM64" - - "Linux" - container: quay.io/pypa/manylinux${{ matrix.manylinux }}_aarch64:latest - env: - JAVA_VERSION: ${{ matrix.java_version }} - SWIG_VERSION: ${{ matrix.swig_version }} - strategy: - matrix: - manylinux: - - "_2_28" - cpython_version: - - "cp38-cp38" - - "cp39-cp39" - - "cp310-cp310" - - "cp311-cp311" - - "cp312-cp312" - # We are having problems when zulu-opendjk Conan package on an armv8 architecture. - # zulu-openjdk provides the Java JRE required by the ANTLR generator. - # So, for the time being, we are installing Java manually for this platform - include: - - manylinux: _2_28 - java_version: 'java-11-openjdk-11.0.21.0.9-2.el8' - swig_version: 'swig-3.0.12-19.module_el8.4.0+2254+838326ab' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install dependencies - run: | - dnf install -y $JAVA_VERSION $SWIG_VERSION - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - python -m pip install --upgrade pip conan wheel auditwheel - - name: Build wheel - run: | - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - conan remove -c "*/*" - python setup.py bdist_wheel - python -m auditwheel repair pybuild/dist/*.whl - - name: Wheel path - id: wheel - working-directory: wheelhouse + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null + apt-get update + apt-get install -y docker-ce-cli + - name: Install cibuildwheel and build wheels run: | - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_OUTPUT - echo "WHEEL_NAME=$(ls *.whl)" >> $GITHUB_ENV - - uses: actions/upload-artifact@v4 - with: - name: pypi-linux-arm64-${{ matrix.cpython_version }} - path: wheelhouse/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} + pip install cibuildwheel==2.18.1 + cibuildwheel --output-dir wheelhouse env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: wheelhouse/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip - - windows-x64: - name: PyPI wheels for Windows - runs-on: windows-latest - strategy: - matrix: - python: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" - steps: - - name: Checkout - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: python -m pip install --upgrade pip conan setuptools wheel - - name: Build wheel - run: python setup.py bdist_wheel - - name: Wheel path - id: wheel - working-directory: pybuild/dist/ - run: | - echo "WHEEL_NAME=$(Get-ChildItem -name *.whl)" >> $env:GITHUB_OUTPUT - echo "WHEEL_NAME=$(Get-ChildItem -name *.whl)" >> $env:GITHUB_ENV - shell: powershell + CIBW_BEFORE_ALL_LINUX: yum install -y java-11-openjdk - uses: actions/upload-artifact@v4 with: - name: pypi-windows-py${{ matrix.python }} - path: pybuild/dist/${{ env.WHEEL_NAME }} - - uses: actions/upload-release-asset@v1 - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: pybuild/dist/${{ env.WHEEL_NAME }} - asset_name: ${{ env.WHEEL_NAME }} - asset_content_type: application/zip + name: cibw-wheels-linux-arm64 + path: ./wheelhouse/*.whl emscripten-wasm: name: WASM binaries for emscripten @@ -292,13 +106,9 @@ jobs: publish: name: Publish - if: ${{ github.event_name == 'release' && github.event.action == 'created' }} needs: - - macos-x64 - - macos-arm64 - - manylinux-x64 - - manylinux-arm64 - - windows-x64 + - cibw-wheels + - cibw-wheels-linux-arm64 - emscripten-wasm runs-on: ubuntu-latest steps: @@ -307,7 +117,8 @@ jobs: id: download - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@v1.8.14 + if: ${{ github.event_name == 'release' && github.event.action == 'created' }} with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} - packages_dir: ${{ steps.download.outputs.download-path }}/pypi-* + packages_dir: ${{ steps.download.outputs.download-path }}/cibw-* diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d40c08ac --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = [ + "setuptools", + "swig" +] +build-backend = "setuptools.build_meta" + +[tool.cibuildwheel] +before-all = "uname -a" +build-verbosity = 3 +skip = [ + "cp36-*", "cp37-*", "cp38-*", + "*i686*", "*ppc64le*", "*pypy*", "*s390x*", "*musllinux*", + "pp*", + "*-win32", +] +test-requires = "pytest" +test-command = "pytest {project}/test" + +[tool.cibuildwheel.linux] +manylinux-x86_64-image = "manylinux_2_28" +manylinux-aarch64-image = "manylinux_2_28" diff --git a/setup.py b/setup.py index 3c804910..d2f78082 100755 --- a/setup.py +++ b/setup.py @@ -12,19 +12,31 @@ from setuptools.command.build_ext import build_ext as _build_ext from distutils.command.build import build as _build from setuptools.command.install import install as _install -from distutils.command.bdist import bdist as _bdist -from wheel.bdist_wheel import bdist_wheel as _bdist_wheel -from distutils.command.sdist import sdist as _sdist -from setuptools.command.egg_info import egg_info as _egg_info -from version import get_version + +# TODO: I had to copy-paste get_version from version.py here +# because I couldn't get 'from version import get_version' work with pyproject.toml +def get_version(verbose=False): + """Extract version information from source code""" + inc_dir = root_dir + os.sep + "include" # C++ include directory + matcher = re.compile('static const char \*version\{ "(.*)" \}') + version = None + with open(os.path.join(inc_dir, "version.hpp"), "r") as f: + for ln in f: + m = matcher.match(ln) + if m: + version = m.group(1) + break + if verbose: + print("get_version: %s" % version) + return version + root_dir = os.getcwd() # root of the repository src_dir = root_dir + os.sep + 'src' # C++ source directory pysrc_dir = root_dir + os.sep + 'python' # Python source files target_dir = root_dir + os.sep + 'pybuild' # python-specific build directory build_dir = target_dir + os.sep + 'build' # directory for setuptools to dump various files into -dist_dir = target_dir + os.sep + 'dist' # wheel output directory cbuild_dir = target_dir + os.sep + 'cbuild' # cmake build directory prefix_dir = target_dir + os.sep + 'prefix' # cmake install prefix srcmod_dir = pysrc_dir + os.sep + 'module' # libqasm Python module directory, source files only @@ -134,31 +146,6 @@ def run(self): _install.run(self) -class bdist(_bdist): - def finalize_options(self): - _bdist.finalize_options(self) - self.dist_dir = os.path.relpath(dist_dir) - - -class bdist_wheel(_bdist_wheel): - def run(self): - if platform.system() == "Darwin": - os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.10' - _bdist_wheel.run(self) - - -class sdist(_sdist): - def finalize_options(self): - _sdist.finalize_options(self) - self.dist_dir = os.path.relpath(dist_dir) - - -class egg_info(_egg_info): - def initialize_options(self): - _egg_info.initialize_options(self) - self.egg_base = os.path.relpath(module_dir) - - setup( name='libqasm', version=get_version(), @@ -173,6 +160,11 @@ def initialize_options(self): 'Operating System :: MacOS', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Scientific/Engineering' ], packages=['libqasm', 'cqasm', 'cqasm.v3x'], @@ -182,14 +174,10 @@ def initialize_options(self): # This is here just to have the rest of setuptools understand that this is a Python module with an extension in it. ext_modules=[Extension('libqasm._libqasm', [])], cmdclass={ - 'bdist': bdist, - 'bdist_wheel': bdist_wheel, 'build_ext': build_ext, 'build': build, 'install': install, 'clean': clean, - 'egg_info': egg_info, - 'sdist': sdist, }, setup_requires=[ 'conan', From 36fd89772c69f630afb5a45887e7637c04a2ce04 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:27:25 +0200 Subject: [PATCH 06/22] Remove reference to Python 3.8. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index d2f78082..8d87f799 100755 --- a/setup.py +++ b/setup.py @@ -160,7 +160,6 @@ def run(self): 'Operating System :: MacOS', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', From 8e39c1f0289b363b2d1b9ffbbc58f4cc215f34f7 Mon Sep 17 00:00:00 2001 From: rturrado Date: Sun, 28 Jul 2024 20:16:37 +0200 Subject: [PATCH 07/22] Address a comment from Olaf's code review. --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 8d87f799..18968f8f 100755 --- a/setup.py +++ b/setup.py @@ -14,8 +14,6 @@ from setuptools.command.install import install as _install -# TODO: I had to copy-paste get_version from version.py here -# because I couldn't get 'from version import get_version' work with pyproject.toml def get_version(verbose=False): """Extract version information from source code""" inc_dir = root_dir + os.sep + "include" # C++ include directory From 7ed778d8f37d3f3e69207b80384cb1171bdc2f39 Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 2 Aug 2024 14:40:32 +0200 Subject: [PATCH 08/22] Add reset instruction. Update lexer. Update parser. Update tree-gen classes. Update ANTLR AST to tree-gen AST converter. Update semantic analyzer. Register reset instruction types. --- include/v3x/AnalyzeTreeGenAstVisitor.hpp | 1 + include/v3x/BuildTreeGenAstVisitor.hpp | 1 + src/v3x/AnalyzeTreeGenAstVisitor.cpp | 28 ++++++++++++++++++++++++ src/v3x/BuildTreeGenAstVisitor.cpp | 10 +++++++++ src/v3x/CqasmLexer.g4 | 1 + src/v3x/CqasmParser.g4 | 1 + src/v3x/cqasm-ast.tree | 5 +++++ src/v3x/register-instructions.cpp | 3 +++ 8 files changed, 50 insertions(+) diff --git a/include/v3x/AnalyzeTreeGenAstVisitor.hpp b/include/v3x/AnalyzeTreeGenAstVisitor.hpp index 228c6592..c143def9 100644 --- a/include/v3x/AnalyzeTreeGenAstVisitor.hpp +++ b/include/v3x/AnalyzeTreeGenAstVisitor.hpp @@ -33,6 +33,7 @@ class AnalyzeTreeGenAstVisitor : public ast::Visitor { std::any visit_variable(ast::Variable &node) override; std::any visit_gate(ast::Gate &node) override; std::any visit_measure_instruction(ast::MeasureInstruction &node) override; + std::any visit_reset_instruction(ast::ResetInstruction &node) override; std::any visit_expression_list(ast::ExpressionList &node) override; std::any visit_expression(ast::Expression &node) override; std::any visit_unary_minus_expression(ast::UnaryMinusExpression &node) override; diff --git a/include/v3x/BuildTreeGenAstVisitor.hpp b/include/v3x/BuildTreeGenAstVisitor.hpp index 7d315b90..13d0d434 100644 --- a/include/v3x/BuildTreeGenAstVisitor.hpp +++ b/include/v3x/BuildTreeGenAstVisitor.hpp @@ -66,6 +66,7 @@ class BuildTreeGenAstVisitor : public BuildCustomAstVisitor { std::any visitGlobalBlockStatement(CqasmParser::GlobalBlockStatementContext *context) override; std::any visitVariableDefinition(CqasmParser::VariableDefinitionContext *context) override; std::any visitMeasureInstruction(CqasmParser::MeasureInstructionContext *context) override; + std::any visitResetInstruction(CqasmParser::ResetInstructionContext *context) override; std::any visitGate(CqasmParser::GateContext *context) override; std::any visitType(CqasmParser::TypeContext *context) override; std::any visitQubitType(CqasmParser::QubitTypeContext *context) override; diff --git a/src/v3x/AnalyzeTreeGenAstVisitor.cpp b/src/v3x/AnalyzeTreeGenAstVisitor.cpp index 03ac7f7c..913c6a1c 100644 --- a/src/v3x/AnalyzeTreeGenAstVisitor.cpp +++ b/src/v3x/AnalyzeTreeGenAstVisitor.cpp @@ -232,6 +232,34 @@ std::any AnalyzeTreeGenAstVisitor::visit_measure_instruction(ast::MeasureInstruc return ret; } +std::any AnalyzeTreeGenAstVisitor::visit_reset_instruction(ast::ResetInstruction &node) { + auto ret = tree::Maybe(); + try { + // Set operand + // Notice operands have to be added in this order + // Otherwise instruction resolution would fail + auto operands = values::Values(); + if (!node.operand.empty()) { + operands.add(std::any_cast(visit_expression(*node.operand))); + } + + // Resolve the instruction + ret.set(analyzer_.resolve_instruction(node.name->name, operands)); + + // Copy annotation data + ret->annotations = std::any_cast>(visit_annotated(*node.as_annotated())); + ret->copy_annotation(node); + + // Add the statement to the current scope + analyzer_.add_statement_to_current_scope(ret); + } catch (error::AnalysisError &err) { + err.context(node); + result_.errors.push_back(std::move(err)); + ret.reset(); + } + return ret; +} + std::any AnalyzeTreeGenAstVisitor::visit_expression_list(ast::ExpressionList &node) { auto ret = values::Values(); std::transform( diff --git a/src/v3x/BuildTreeGenAstVisitor.cpp b/src/v3x/BuildTreeGenAstVisitor.cpp index 9a867b95..e64028ac 100644 --- a/src/v3x/BuildTreeGenAstVisitor.cpp +++ b/src/v3x/BuildTreeGenAstVisitor.cpp @@ -165,6 +165,16 @@ std::any BuildTreeGenAstVisitor::visitMeasureInstruction(CqasmParser::MeasureIns return One{ ret }; } +std::any BuildTreeGenAstVisitor::visitResetInstruction(CqasmParser::ResetInstructionContext *context) { + auto ret = tree::make(); + ret->name = tree::make(context->RESET()->getText()); + ret->operand = context->expression() + ? Maybe{ std::any_cast>(context->expression()->accept(this)) } + : Maybe{}; + setNodeAnnotation(ret, context->RESET()->getSymbol()); + return One{ ret }; +} + std::any BuildTreeGenAstVisitor::visitGate(CqasmParser::GateContext *context) { auto ret = tree::make(); ret->name = tree::make(context->IDENTIFIER()->getText()); diff --git a/src/v3x/CqasmLexer.g4 b/src/v3x/CqasmLexer.g4 index 15991622..84121c53 100644 --- a/src/v3x/CqasmLexer.g4 +++ b/src/v3x/CqasmLexer.g4 @@ -49,6 +49,7 @@ TERNARY_CONDITIONAL_OP: '?'; // Keywords VERSION: 'version' -> pushMode(VERSION_STATEMENT); MEASURE: 'measure'; +RESET: 'reset'; QUBIT_TYPE: 'qubit'; BIT_TYPE: 'bit'; diff --git a/src/v3x/CqasmParser.g4 b/src/v3x/CqasmParser.g4 index 397b5392..70795074 100644 --- a/src/v3x/CqasmParser.g4 +++ b/src/v3x/CqasmParser.g4 @@ -32,6 +32,7 @@ variableDefinition: type IDENTIFIER; instruction: expression EQUALS MEASURE expression # measureInstruction + | RESET (expression)? # resetInstruction | IDENTIFIER (OPEN_PARENS expression CLOSE_PARENS)? expressionList # gate ; diff --git a/src/v3x/cqasm-ast.tree b/src/v3x/cqasm-ast.tree index 1d5d130f..1dfeafc8 100644 --- a/src/v3x/cqasm-ast.tree +++ b/src/v3x/cqasm-ast.tree @@ -186,6 +186,11 @@ annotated { lhs: One; rhs: One; } + + reset_instruction { + name: One; + operand: Maybe; + } } } } diff --git a/src/v3x/register-instructions.cpp b/src/v3x/register-instructions.cpp index 75860a9c..df57628c 100644 --- a/src/v3x/register-instructions.cpp +++ b/src/v3x/register-instructions.cpp @@ -38,6 +38,9 @@ void register_instructions(analyzer::Analyzer *analyzer) { analyzer->register_instruction("mX90", "V"); analyzer->register_instruction("mY90", "Q"); analyzer->register_instruction("mY90", "V"); + analyzer->register_instruction("reset", std::nullopt); + analyzer->register_instruction("reset", "Q"); + analyzer->register_instruction("reset", "V"); analyzer->register_instruction("Rx", "Qf"); analyzer->register_instruction("Rx", "Vf"); analyzer->register_instruction("Ry", "Qf"); From 6d7de00146ddef87bc62fd9d883775979e0b22f6 Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 2 Aug 2024 17:56:04 +0200 Subject: [PATCH 09/22] Add integration tests. --- .../reset_instruction/5/ast.golden.txt | 46 +++++++++++++ res/v3x/parsing/reset_instruction/5/input.cq | 5 ++ .../5/semantic.3.0.golden.txt | 2 + .../no_operand/ast.golden.txt | 42 ++++++++++++ .../reset_instruction/no_operand/input.cq | 5 ++ .../no_operand/semantic.3.0.golden.txt | 33 ++++++++++ .../reset_instruction/q/ast.golden.txt | 46 +++++++++++++ res/v3x/parsing/reset_instruction/q/input.cq | 5 ++ .../q/semantic.3.0.golden.txt | 47 ++++++++++++++ .../q_range_0_4}/ast.golden.txt | 60 ++++++----------- .../reset_instruction/q_range_0_4/input.cq | 5 ++ .../q_range_0_4/semantic.3.0.golden.txt | 64 +++++++++++++++++++ 12 files changed, 320 insertions(+), 40 deletions(-) create mode 100644 res/v3x/parsing/reset_instruction/5/ast.golden.txt create mode 100644 res/v3x/parsing/reset_instruction/5/input.cq create mode 100644 res/v3x/parsing/reset_instruction/5/semantic.3.0.golden.txt create mode 100644 res/v3x/parsing/reset_instruction/no_operand/ast.golden.txt create mode 100644 res/v3x/parsing/reset_instruction/no_operand/input.cq create mode 100644 res/v3x/parsing/reset_instruction/no_operand/semantic.3.0.golden.txt create mode 100644 res/v3x/parsing/reset_instruction/q/ast.golden.txt create mode 100644 res/v3x/parsing/reset_instruction/q/input.cq create mode 100644 res/v3x/parsing/reset_instruction/q/semantic.3.0.golden.txt rename res/v3x/parsing/{measure_instruction/range_q_0_4_to_array_of_5_b => reset_instruction/q_range_0_4}/ast.golden.txt (51%) create mode 100644 res/v3x/parsing/reset_instruction/q_range_0_4/input.cq create mode 100644 res/v3x/parsing/reset_instruction/q_range_0_4/semantic.3.0.golden.txt diff --git a/res/v3x/parsing/reset_instruction/5/ast.golden.txt b/res/v3x/parsing/reset_instruction/5/ast.golden.txt new file mode 100644 index 00000000..79958aaf --- /dev/null +++ b/res/v3x/parsing/reset_instruction/5/ast.golden.txt @@ -0,0 +1,46 @@ +SUCCESS +Program( + version: < + Version( # input.cq:1:9..10 + items: 3 + ) + > + block: < + GlobalBlock( + statements: [ + Variable( # input.cq:3:7..8 + name: < + Identifier( + name: q + ) + > + typ: < + Type( # input.cq:3:1..6 + name: < + Keyword( + name: qubit + ) + > + size: - + ) + > + annotations: [] + ) + ResetInstruction( # input.cq:5:1..6 + name: < + Identifier( + name: reset + ) + > + operand: < + IntegerLiteral( # input.cq:5:7..8 + value: 5 + ) + > + annotations: [] + ) + ] + ) + > +) + diff --git a/res/v3x/parsing/reset_instruction/5/input.cq b/res/v3x/parsing/reset_instruction/5/input.cq new file mode 100644 index 00000000..d3274241 --- /dev/null +++ b/res/v3x/parsing/reset_instruction/5/input.cq @@ -0,0 +1,5 @@ +version 3 + +qubit q + +reset 5 diff --git a/res/v3x/parsing/reset_instruction/5/semantic.3.0.golden.txt b/res/v3x/parsing/reset_instruction/5/semantic.3.0.golden.txt new file mode 100644 index 00000000..0f14ed04 --- /dev/null +++ b/res/v3x/parsing/reset_instruction/5/semantic.3.0.golden.txt @@ -0,0 +1,2 @@ +ERROR +Error at input.cq:5:1..6: failed to resolve instruction 'reset' with argument pack (int) diff --git a/res/v3x/parsing/reset_instruction/no_operand/ast.golden.txt b/res/v3x/parsing/reset_instruction/no_operand/ast.golden.txt new file mode 100644 index 00000000..6d393e87 --- /dev/null +++ b/res/v3x/parsing/reset_instruction/no_operand/ast.golden.txt @@ -0,0 +1,42 @@ +SUCCESS +Program( + version: < + Version( # input.cq:1:9..10 + items: 3 + ) + > + block: < + GlobalBlock( + statements: [ + Variable( # input.cq:3:7..8 + name: < + Identifier( + name: q + ) + > + typ: < + Type( # input.cq:3:1..6 + name: < + Keyword( + name: qubit + ) + > + size: - + ) + > + annotations: [] + ) + ResetInstruction( # input.cq:5:1..6 + name: < + Identifier( + name: reset + ) + > + operand: - + annotations: [] + ) + ] + ) + > +) + diff --git a/res/v3x/parsing/reset_instruction/no_operand/input.cq b/res/v3x/parsing/reset_instruction/no_operand/input.cq new file mode 100644 index 00000000..59195cca --- /dev/null +++ b/res/v3x/parsing/reset_instruction/no_operand/input.cq @@ -0,0 +1,5 @@ +version 3 + +qubit q + +reset diff --git a/res/v3x/parsing/reset_instruction/no_operand/semantic.3.0.golden.txt b/res/v3x/parsing/reset_instruction/no_operand/semantic.3.0.golden.txt new file mode 100644 index 00000000..4a75cf36 --- /dev/null +++ b/res/v3x/parsing/reset_instruction/no_operand/semantic.3.0.golden.txt @@ -0,0 +1,33 @@ +SUCCESS +Program( + api_version: 3.0 + version: < + Version( + items: 3 + ) + > + block: < + Block( + statements: [ + Instruction( + instruction_ref: reset() + name: reset + operands: [] + annotations: [] + ) + ] + ) + > + variables: [ + Variable( + name: q + typ: < + Qubit( + size: 1 + ) + > + annotations: [] + ) + ] +) + diff --git a/res/v3x/parsing/reset_instruction/q/ast.golden.txt b/res/v3x/parsing/reset_instruction/q/ast.golden.txt new file mode 100644 index 00000000..924a6cdb --- /dev/null +++ b/res/v3x/parsing/reset_instruction/q/ast.golden.txt @@ -0,0 +1,46 @@ +SUCCESS +Program( + version: < + Version( # input.cq:1:9..10 + items: 3 + ) + > + block: < + GlobalBlock( + statements: [ + Variable( # input.cq:3:7..8 + name: < + Identifier( + name: q + ) + > + typ: < + Type( # input.cq:3:1..6 + name: < + Keyword( + name: qubit + ) + > + size: - + ) + > + annotations: [] + ) + ResetInstruction( # input.cq:5:1..6 + name: < + Identifier( + name: reset + ) + > + operand: < + Identifier( # input.cq:5:7..8 + name: q + ) + > + annotations: [] + ) + ] + ) + > +) + diff --git a/res/v3x/parsing/reset_instruction/q/input.cq b/res/v3x/parsing/reset_instruction/q/input.cq new file mode 100644 index 00000000..e749ce98 --- /dev/null +++ b/res/v3x/parsing/reset_instruction/q/input.cq @@ -0,0 +1,5 @@ +version 3 + +qubit q + +reset q diff --git a/res/v3x/parsing/reset_instruction/q/semantic.3.0.golden.txt b/res/v3x/parsing/reset_instruction/q/semantic.3.0.golden.txt new file mode 100644 index 00000000..ca1b79a9 --- /dev/null +++ b/res/v3x/parsing/reset_instruction/q/semantic.3.0.golden.txt @@ -0,0 +1,47 @@ +SUCCESS +Program( + api_version: 3.0 + version: < + Version( + items: 3 + ) + > + block: < + Block( + statements: [ + Instruction( + instruction_ref: reset(qubit) + name: reset + operands: [ + VariableRef( + variable --> < + Variable( + name: q + typ: < + Qubit( + size: 1 + ) + > + annotations: [] + ) + > + ) + ] + annotations: [] + ) + ] + ) + > + variables: [ + Variable( + name: q + typ: < + Qubit( + size: 1 + ) + > + annotations: [] + ) + ] +) + diff --git a/res/v3x/parsing/measure_instruction/range_q_0_4_to_array_of_5_b/ast.golden.txt b/res/v3x/parsing/reset_instruction/q_range_0_4/ast.golden.txt similarity index 51% rename from res/v3x/parsing/measure_instruction/range_q_0_4_to_array_of_5_b/ast.golden.txt rename to res/v3x/parsing/reset_instruction/q_range_0_4/ast.golden.txt index ddd7657c..f02cce73 100644 --- a/res/v3x/parsing/measure_instruction/range_q_0_4_to_array_of_5_b/ast.golden.txt +++ b/res/v3x/parsing/reset_instruction/q_range_0_4/ast.golden.txt @@ -5,9 +5,9 @@ Program( items: 3 ) > - statements: < - StatementList( - items: [ + block: < + GlobalBlock( + statements: [ Variable( # input.cq:3:10..11 name: < Identifier( @@ -15,49 +15,29 @@ Program( ) > typ: < - Keyword( - name: qubit - ) - > - size: < - IntegerLiteral( - value: 9 - ) - > - annotations: [] - ) - Variable( # input.cq:4:8..9 - name: < - Identifier( - name: b - ) - > - typ: < - Keyword( - name: bit - ) - > - size: < - IntegerLiteral( - value: 5 + Type( # input.cq:3:1..9 + name: < + Keyword( + name: qubit + ) + > + size: < + IntegerLiteral( + value: 9 + ) + > ) > annotations: [] ) - MeasureInstruction( # input.cq:6:5..12 + ResetInstruction( # input.cq:5:1..6 name: < Identifier( - name: measure - ) - > - condition: - - lhs: < - Identifier( # input.cq:6:1..2 - name: b + name: reset ) > - rhs: < - Index( # input.cq:6:13..14 + operand: < + Index( # input.cq:5:7..8 expr: < Identifier( name: q @@ -68,12 +48,12 @@ Program( items: [ IndexRange( first: < - IntegerLiteral( # input.cq:6:15..16 + IntegerLiteral( # input.cq:5:9..10 value: 0 ) > last: < - IntegerLiteral( # input.cq:6:17..18 + IntegerLiteral( # input.cq:5:11..12 value: 4 ) > diff --git a/res/v3x/parsing/reset_instruction/q_range_0_4/input.cq b/res/v3x/parsing/reset_instruction/q_range_0_4/input.cq new file mode 100644 index 00000000..8144294a --- /dev/null +++ b/res/v3x/parsing/reset_instruction/q_range_0_4/input.cq @@ -0,0 +1,5 @@ +version 3 + +qubit[9] q + +reset q[0:4] diff --git a/res/v3x/parsing/reset_instruction/q_range_0_4/semantic.3.0.golden.txt b/res/v3x/parsing/reset_instruction/q_range_0_4/semantic.3.0.golden.txt new file mode 100644 index 00000000..51634b7c --- /dev/null +++ b/res/v3x/parsing/reset_instruction/q_range_0_4/semantic.3.0.golden.txt @@ -0,0 +1,64 @@ +SUCCESS +Program( + api_version: 3.0 + version: < + Version( + items: 3 + ) + > + block: < + Block( + statements: [ + Instruction( + instruction_ref: reset(qubit array) + name: reset + operands: [ + IndexRef( + variable --> < + Variable( + name: q + typ: < + QubitArray( + size: 9 + ) + > + annotations: [] + ) + > + indices: [ + ConstInt( + value: 0 + ) + ConstInt( + value: 1 + ) + ConstInt( + value: 2 + ) + ConstInt( + value: 3 + ) + ConstInt( + value: 4 + ) + ] + ) + ] + annotations: [] + ) + ] + ) + > + variables: [ + Variable( + name: q + typ: < + QubitArray( + size: 9 + ) + > + annotations: [] + ) + ] +) + From 9486801ca9685756266ba1b55adcb546acfc3303 Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 2 Aug 2024 17:56:32 +0200 Subject: [PATCH 10/22] Remove unused folders. --- .../ast.golden.txt | 92 -------------- .../ast.golden.txt | 92 -------------- .../ast.golden.txt | 92 -------------- .../range_q_3_4_to_b_1_3/ast.golden.txt | 116 ------------------ 4 files changed, 392 deletions(-) delete mode 100644 res/v3x/parsing/measure_instruction/array_of_5_q_to_range_b_0_4/ast.golden.txt delete mode 100644 res/v3x/parsing/measure_instruction/array_of_5_q_to_range_b_2_6/ast.golden.txt delete mode 100644 res/v3x/parsing/measure_instruction/range_q_2_6_to_array_of_5_b/ast.golden.txt delete mode 100644 res/v3x/parsing/measure_instruction/range_q_3_4_to_b_1_3/ast.golden.txt diff --git a/res/v3x/parsing/measure_instruction/array_of_5_q_to_range_b_0_4/ast.golden.txt b/res/v3x/parsing/measure_instruction/array_of_5_q_to_range_b_0_4/ast.golden.txt deleted file mode 100644 index e87d8fde..00000000 --- a/res/v3x/parsing/measure_instruction/array_of_5_q_to_range_b_0_4/ast.golden.txt +++ /dev/null @@ -1,92 +0,0 @@ -SUCCESS -Program( - version: < - Version( # input.cq:1:9..10 - items: 3 - ) - > - statements: < - StatementList( - items: [ - Variable( # input.cq:3:10..11 - name: < - Identifier( - name: q - ) - > - typ: < - Keyword( - name: qubit - ) - > - size: < - IntegerLiteral( - value: 5 - ) - > - annotations: [] - ) - Variable( # input.cq:4:8..9 - name: < - Identifier( - name: b - ) - > - typ: < - Keyword( - name: bit - ) - > - size: < - IntegerLiteral( - value: 9 - ) - > - annotations: [] - ) - MeasureInstruction( # input.cq:6:10..17 - name: < - Identifier( - name: measure - ) - > - condition: - - lhs: < - Index( # input.cq:6:1..2 - expr: < - Identifier( - name: b - ) - > - indices: < - IndexList( - items: [ - IndexRange( - first: < - IntegerLiteral( # input.cq:6:3..4 - value: 0 - ) - > - last: < - IntegerLiteral( # input.cq:6:5..6 - value: 4 - ) - > - ) - ] - ) - > - ) - > - rhs: < - Identifier( # input.cq:6:18..19 - name: q - ) - > - annotations: [] - ) - ] - ) - > -) - diff --git a/res/v3x/parsing/measure_instruction/array_of_5_q_to_range_b_2_6/ast.golden.txt b/res/v3x/parsing/measure_instruction/array_of_5_q_to_range_b_2_6/ast.golden.txt deleted file mode 100644 index e9747670..00000000 --- a/res/v3x/parsing/measure_instruction/array_of_5_q_to_range_b_2_6/ast.golden.txt +++ /dev/null @@ -1,92 +0,0 @@ -SUCCESS -Program( - version: < - Version( # input.cq:1:9..10 - items: 3 - ) - > - statements: < - StatementList( - items: [ - Variable( # input.cq:3:10..11 - name: < - Identifier( - name: q - ) - > - typ: < - Keyword( - name: qubit - ) - > - size: < - IntegerLiteral( - value: 5 - ) - > - annotations: [] - ) - Variable( # input.cq:4:8..9 - name: < - Identifier( - name: b - ) - > - typ: < - Keyword( - name: bit - ) - > - size: < - IntegerLiteral( - value: 9 - ) - > - annotations: [] - ) - MeasureInstruction( # input.cq:6:10..17 - name: < - Identifier( - name: measure - ) - > - condition: - - lhs: < - Index( # input.cq:6:1..2 - expr: < - Identifier( - name: b - ) - > - indices: < - IndexList( - items: [ - IndexRange( - first: < - IntegerLiteral( # input.cq:6:3..4 - value: 2 - ) - > - last: < - IntegerLiteral( # input.cq:6:5..6 - value: 6 - ) - > - ) - ] - ) - > - ) - > - rhs: < - Identifier( # input.cq:6:18..19 - name: q - ) - > - annotations: [] - ) - ] - ) - > -) - diff --git a/res/v3x/parsing/measure_instruction/range_q_2_6_to_array_of_5_b/ast.golden.txt b/res/v3x/parsing/measure_instruction/range_q_2_6_to_array_of_5_b/ast.golden.txt deleted file mode 100644 index 203d08cb..00000000 --- a/res/v3x/parsing/measure_instruction/range_q_2_6_to_array_of_5_b/ast.golden.txt +++ /dev/null @@ -1,92 +0,0 @@ -SUCCESS -Program( - version: < - Version( # input.cq:1:9..10 - items: 3 - ) - > - statements: < - StatementList( - items: [ - Variable( # input.cq:3:10..11 - name: < - Identifier( - name: q - ) - > - typ: < - Keyword( - name: qubit - ) - > - size: < - IntegerLiteral( - value: 9 - ) - > - annotations: [] - ) - Variable( # input.cq:4:8..9 - name: < - Identifier( - name: b - ) - > - typ: < - Keyword( - name: bit - ) - > - size: < - IntegerLiteral( - value: 5 - ) - > - annotations: [] - ) - MeasureInstruction( # input.cq:6:5..12 - name: < - Identifier( - name: measure - ) - > - condition: - - lhs: < - Identifier( # input.cq:6:1..2 - name: b - ) - > - rhs: < - Index( # input.cq:6:13..14 - expr: < - Identifier( - name: q - ) - > - indices: < - IndexList( - items: [ - IndexRange( - first: < - IntegerLiteral( # input.cq:6:15..16 - value: 2 - ) - > - last: < - IntegerLiteral( # input.cq:6:17..18 - value: 6 - ) - > - ) - ] - ) - > - ) - > - annotations: [] - ) - ] - ) - > -) - diff --git a/res/v3x/parsing/measure_instruction/range_q_3_4_to_b_1_3/ast.golden.txt b/res/v3x/parsing/measure_instruction/range_q_3_4_to_b_1_3/ast.golden.txt deleted file mode 100644 index 7416253b..00000000 --- a/res/v3x/parsing/measure_instruction/range_q_3_4_to_b_1_3/ast.golden.txt +++ /dev/null @@ -1,116 +0,0 @@ -SUCCESS -Program( - version: < - Version( # input.cq:1:9..10 - items: 3 - ) - > - statements: < - StatementList( - items: [ - Variable( # input.cq:3:10..11 - name: < - Identifier( - name: q - ) - > - typ: < - Keyword( - name: qubit - ) - > - size: < - IntegerLiteral( - value: 5 - ) - > - annotations: [] - ) - Variable( # input.cq:4:8..9 - name: < - Identifier( - name: b - ) - > - typ: < - Keyword( - name: bit - ) - > - size: < - IntegerLiteral( - value: 5 - ) - > - annotations: [] - ) - MeasureInstruction( # input.cq:6:11..18 - name: < - Identifier( - name: measure - ) - > - condition: - - lhs: < - Index( # input.cq:6:1..2 - expr: < - Identifier( - name: b - ) - > - indices: < - IndexList( - items: [ - IndexItem( - index: < - IntegerLiteral( # input.cq:6:3..4 - value: 1 - ) - > - ) - IndexItem( - index: < - IntegerLiteral( # input.cq:6:6..7 - value: 3 - ) - > - ) - ] - ) - > - ) - > - rhs: < - Index( # input.cq:6:19..20 - expr: < - Identifier( - name: q - ) - > - indices: < - IndexList( - items: [ - IndexRange( - first: < - IntegerLiteral( # input.cq:6:21..22 - value: 3 - ) - > - last: < - IntegerLiteral( # input.cq:6:23..24 - value: 4 - ) - > - ) - ] - ) - > - ) - > - annotations: [] - ) - ] - ) - > -) - From eca70c31756568a9d7ff1b3aedecab129e59ad9f Mon Sep 17 00:00:00 2001 From: rturrado Date: Fri, 2 Aug 2024 18:06:20 +0200 Subject: [PATCH 11/22] Remove redundant parentheses. --- src/v3x/CqasmParser.g4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v3x/CqasmParser.g4 b/src/v3x/CqasmParser.g4 index 70795074..efb41a27 100644 --- a/src/v3x/CqasmParser.g4 +++ b/src/v3x/CqasmParser.g4 @@ -32,7 +32,7 @@ variableDefinition: type IDENTIFIER; instruction: expression EQUALS MEASURE expression # measureInstruction - | RESET (expression)? # resetInstruction + | RESET expression? # resetInstruction | IDENTIFIER (OPEN_PARENS expression CLOSE_PARENS)? expressionList # gate ; From 74fa4b2867ca603644193a77e5002bf30dbaafb7 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 15 Aug 2024 10:59:26 +0200 Subject: [PATCH 12/22] Update dependencies versions. --- conanfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conanfile.py b/conanfile.py index 016841e5..47d77796 100644 --- a/conanfile.py +++ b/conanfile.py @@ -90,11 +90,11 @@ def layout(self): def build_requirements(self): self.tool_requires("tree-gen/1.0.7") if self.settings.arch != "armv8": - self.tool_requires("zulu-openjdk/21.0.1") + self.tool_requires("zulu-openjdk/21.0.4") if self.settings.arch == "wasm": self.tool_requires("emsdk/3.1.50") if self._should_build_test: - self.test_requires("gtest/1.14.0") + self.test_requires("gtest/1.15.0") def validate(self): if self.settings.compiler.cppstd: @@ -108,7 +108,7 @@ def validate(self): raise ConanInvalidConfiguration(f"{self.ref} requires antlr4-cppruntime to be built with the same shared option value.") def requirements(self): - self.requires("fmt/10.2.1", transitive_headers=True) + self.requires("fmt/11.0.2", transitive_headers=True) self.requires("range-v3/0.12.0", transitive_headers=True) self.requires("tree-gen/1.0.7", transitive_headers=True, transitive_libs=True) if not self.settings.arch == "wasm": From ecae124bde3632572e17e8faa4d0df197ea0cc37 Mon Sep 17 00:00:00 2001 From: rturrado Date: Thu, 15 Aug 2024 13:32:31 +0200 Subject: [PATCH 13/22] Fix warning in test.yml. strategy/fail-fast was being used without strategy/matrix. --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8b568ff6..75bacd21 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -127,6 +127,9 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false + matrix: + build_type: + - Release steps: - name: Checkout uses: actions/checkout@v4 From b0655276b01f9a7759dbb6a56f5a9fd7a034cf81 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Wed, 17 Jul 2024 18:43:38 +0200 Subject: [PATCH 14/22] [CQT-70] Check if C++/Python API documentation can be generated through Sphinx. --- .gitattributes | 1 - .gitmodules | 0 .readthedocs.yaml | 28 -- CMakeLists.txt | 11 + README.md | 242 +++++----------- conan/profiles/docs-release | 8 + .../docs-release-apple_clang-macos-arm64 | 5 + .../docs-release-apple_clang-macos-x64 | 5 + conan/profiles/docs-release-clang-linux-arm64 | 9 + conan/profiles/docs-release-clang-linux-x64 | 9 + conan/profiles/docs-release-gcc-linux-arm64 | 4 + conan/profiles/docs-release-gcc-linux-x64 | 1 + conan/profiles/docs-release-msvc-windows-x64 | 1 + conanfile.py | 3 + doc/CMakeLists.txt | 37 +++ doc/mkdocs | 50 ++++ doc/mkdocs.yml | 59 ++++ doc/src/about/authors.md | 2 + doc/src/about/contributing.md | 10 + doc/src/about/license.md | 3 + doc/src/about/release-notes.md | 1 + doc/src/api/cpp.md | 7 + doc/src/api/emscripten.md | 9 + doc/src/api/python.md | 7 + doc/src/cpp_highlighting.js | 4 + doc/src/dev-guide/cpp.md | 17 ++ doc/src/dev-guide/dev-guide.md | 127 +++++++++ doc/src/dev-guide/emscripten.md | 23 ++ doc/src/dev-guide/python.md | 11 + doc/src/index.md | 18 ++ doc/src/libqasm.css | 61 ++++ doc/src/python_highlighting.js | 4 + doc/src/typescript_highlighting.js | 4 + doc/src/user-guide/cpp.md | 38 +++ doc/src/user-guide/emscripten.md | 24 ++ doc/src/user-guide/python.md | 21 ++ doc/src/user-guide/user-guide.md | 5 + emscripten/emscripten_wrapper.hpp | 74 ++++- include/v3x/cqasm-py.hpp | 149 ++++++---- python/module/cqasm/v3x/__init__.py | 93 ++++-- .../mkdocstrings_handlers/cpp/__init__.py | 255 +++++++++++++++++ .../cpp/templates/README | 1 + .../emscripten/__init__.py | 254 +++++++++++++++++ .../emscripten/templates/README | 1 + .../mkdocstrings_handlers/python/__init__.py | 264 ++++++++++++++++++ .../python/templates/README | 1 + src/CMakeLists.txt | 3 + src/v3x/cqasm-types.cpp | 2 +- 48 files changed, 1673 insertions(+), 293 deletions(-) delete mode 100644 .gitmodules delete mode 100644 .readthedocs.yaml create mode 100644 conan/profiles/docs-release create mode 100644 conan/profiles/docs-release-apple_clang-macos-arm64 create mode 100644 conan/profiles/docs-release-apple_clang-macos-x64 create mode 100644 conan/profiles/docs-release-clang-linux-arm64 create mode 100644 conan/profiles/docs-release-clang-linux-x64 create mode 100644 conan/profiles/docs-release-gcc-linux-arm64 create mode 100644 conan/profiles/docs-release-gcc-linux-x64 create mode 100644 conan/profiles/docs-release-msvc-windows-x64 create mode 100644 doc/CMakeLists.txt create mode 100644 doc/mkdocs create mode 100644 doc/mkdocs.yml create mode 100644 doc/src/about/authors.md create mode 100644 doc/src/about/contributing.md create mode 100644 doc/src/about/license.md create mode 100644 doc/src/about/release-notes.md create mode 100644 doc/src/api/cpp.md create mode 100644 doc/src/api/emscripten.md create mode 100644 doc/src/api/python.md create mode 100644 doc/src/cpp_highlighting.js create mode 100644 doc/src/dev-guide/cpp.md create mode 100644 doc/src/dev-guide/dev-guide.md create mode 100644 doc/src/dev-guide/emscripten.md create mode 100644 doc/src/dev-guide/python.md create mode 100644 doc/src/index.md create mode 100644 doc/src/libqasm.css create mode 100644 doc/src/python_highlighting.js create mode 100644 doc/src/typescript_highlighting.js create mode 100644 doc/src/user-guide/cpp.md create mode 100644 doc/src/user-guide/emscripten.md create mode 100644 doc/src/user-guide/python.md create mode 100644 doc/src/user-guide/user-guide.md create mode 100644 scripts/python/mkdocstrings_handlers/cpp/__init__.py create mode 100644 scripts/python/mkdocstrings_handlers/cpp/templates/README create mode 100644 scripts/python/mkdocstrings_handlers/emscripten/__init__.py create mode 100644 scripts/python/mkdocstrings_handlers/emscripten/templates/README create mode 100644 scripts/python/mkdocstrings_handlers/python/__init__.py create mode 100644 scripts/python/mkdocstrings_handlers/python/templates/README diff --git a/.gitattributes b/.gitattributes index ed911378..3812f848 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,5 +7,4 @@ # (see read_file and write_file implementation in utils.{hpp,cpp}) # Since we are comparing the actual test files against "golden test files" (e.g. ast.golden.txt), # we also want the golden test files to use LF line endings. -res/v1x/parsing/*/*/*.* text eol=lf res/v3x/parsing/*/*/*.* text eol=lf diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index 65a39b28..00000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -conda: - environment: docs/.conda.yml - -# Optionally build your docs in additional formats such as PDF -formats: - - pdf - -# Optionally set the version of Python and requirements required to build your docs -python: - version: 3.11 - install: - - method: setuptools - path: . - -submodules: - include: all - recursive: true - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c576472..088709d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,12 @@ option(LIBQASM_BUILD_PYTHON ) mark_as_advanced(LIBQASM_BUILD_PYTHON) +# Whether doc should be built. +option(LIBQASM_BUILD_DOCS + "whether the doc should be built and added to `make doc`" + OFF +) + # Where the 'libqasm' Python module should be built. set(LIBQASM_PYTHON_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/python/libqasm" CACHE STRING "Where to install the libqasm Python module" @@ -110,4 +116,9 @@ if(LIBQASM_BUILD_EMSCRIPTEN) add_subdirectory(emscripten) endif() +# Add the doc directory. +if(LIBQASM_BUILD_DOCS) + add_subdirectory(doc) +endif() + endif() # NOT TARGET cqasm diff --git a/README.md b/README.md index 5e72925a..b59e686b 100644 --- a/README.md +++ b/README.md @@ -1,208 +1,108 @@ -# libQASM: Library to parse cQASM v3.0 files +# libQASM [![CI](https://github.com/QuTech-Delft/libqasm/workflows/Test/badge.svg)](https://github.com/qutech-delft/libqasm/actions) +[![Conan Center](https://img.shields.io/conan/v/libqasm)](https://conan.io/center/recipes/libqasm) [![PyPI](https://badgen.net/pypi/v/libqasm)](https://pypi.org/project/libqasm/) +![OS](https://img.shields.io/badge/os-emscripten%20%7C%20linux%20%7C%20macos%20%7C%20windows-blue?style=flat-square) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -## File organization +libQASM is a library to parse programs written in the cQASM quantum programming language, developed by QuTech. +At the moment, libQASM only supports cQASM v3.0 programs. -For development, see: +It performs lexical, syntactic, and semantic analysis of an input program received via a file or a string. +It produces one of the following results: -- `include`: public headers. -- `src`: source files. -- `test`: test files. -- `python`: SWIG interface. -- `res`: resource files, for testing. +- A syntactic or semantic AST (Abstract Syntax Tree) object. Depending on if we are parsing or analysing. +- A list of parsing or analysing errors. In case the input program was malformed. +- A JSON representation of either the AST or the list of errors. -For build process, continuous integration, and documentation: +It can be used within: -- `conan`: Conan profiles. -- `emscripten`: bindings and test for the emscripten binaries. -- `scripts`: helper scripts used during the build process. -- `.github`: GitHub Actions configuration files. -- `doc`: documentation. +- C++ projects (as a [Conan package](https://conan.io/center/recipes/libqasm)). +- Python projects (as a [Python package](https://pypi.org/project/libqasm/)). +- Emscripten projects (via a Typescript frontend). -Build outputs may go into: +Check out [QX simulator](https://github.com/QuTech-Delft/qx-simulator) +and [OpenSquirrel](https://github.com/QuTech-Delft/OpenSquirrel) compiler +for an example of use in a C++ and a Python project, respectively. -- `build/`: the C++ library output files generated by Conan. -- `pybuild`: the Python library output files generated by `setup.py`. +## Getting started -## Dependencies +Given a cQASM program `example.cq`. -* C++ compiler with C++20 support (gcc 11, clang 14, msvc 17) -* `CMake` >= 3.12 -* `git` -* `Python` 3.x plus `pip`, with the following package: - * `conan` >= 2.0 - -### ARM specific dependencies +```cQASM +version 3.0 -We are having problems when using the `zulu-opendjk` Conan package on an ARMv8 architecture. -`zulu-openjdk` provides the Java JRE required by the ANTLR generator. -So, for the time being, we are installing Java manually for this platform. - -* `Java JRE` >= 11 - -## Build - -This version of `libqasm` can only be compiled via the Conan package manager. -You'll need to create a default profile before using it for the first time. - -The installation of dependencies, as well as the compilation, can be done in one go. - -``` -git clone https://github.com/QuTech-Delft/libqasm.git -cd libqasm -conan profile detect -conan build . -pr:a=conan/profiles/tests-debug -b missing -``` - -Notice: -- the `conan profile` command only has to be run only once, and not before every build. -- the `conan build` command is building `libqasm` in Debug mode with tests using the `tests-debug` profile. -- the `-b missing` parameter asks `conan` to build packages from sources -in case it cannot find the binary packages for the current configuration (platform, OS, compiler, build type...). - -### Build profiles - -A group of predefined profiles is provided under the `conan/profiles` folder.
-They follow the `[tests](-build_type)(-compiler)(-os)(-arch)[-shared]` naming convention: - - `tests`: if tests are being built. - - `build_type`: can be `debug` or `release`. - - `compiler`: `apple-clang`, `clang`, `gcc`, `msvc`. - - `os`: `emscripten`, `linux`, `macos`, `windows`. - - `arch`: `arm64`, `wasm`, `x64`. - - `shared`: if the library is being built in shared mode. - -All the profiles set the C++ standard to 20.
-All the `tests`, except for `linux-x64` profiles, enable Address Sanitizer. - -### Build options - -Profiles are a shorthand for command line options. The command above could be written, similarly, as: - -``` -conan build . -s:a compiler.cppstd=20 -s:a libqasm/*:build_type=Debug -o libqasm/*:asan_enabled=True -c tools.build:skip_test=False -b missing -``` - -This is the list of options that could be specified either in a profile or in the command line: - -- `libqasm/*:asan_enabled={True,False}`: enables Address Sanitizer. -- `libqasm/*:build_type={Debug,Release}`: builds in debug or release mode. -- `libqasm/*:shared={True,False}`: builds a shared object library instead of a static library, if applicable. - -Tests are disabled by default. To enable them, use `-c tools.build:skip_test=False`. - -## Install - -### From Python - -Install from the project root directory as follows: - -``` -python3 -m pip install --verbose . -``` - -You can test if it works by running: +qubit[2] q +bit[2] b +H q[0] +CNOT q[0], q[1] +b = measure q ``` -python3 -m pytest -``` - -### From C++ -The `CMakeLists.txt` file in the root directory includes install targets: +We can parse or analyze this circuit, using libQASM through the following programming language: -``` -conan create --version 0.6.6 . -pr:a=tests-debug -b missing -``` +### C++ -You can test if it works by doing: +```cpp +#include "v3/cqasm-py.hpp" +int main() { + auto parse_result = V3xAnalyzer::parse_file("example.cq"); + + auto analyzer = V3xAnalyzer(); + auto analysis_result = analyzer.analyze_file("example.cq"); +} ``` -cd test/Debug -ctest -C Debug --output-on-failure -``` - -## Use from another project - -### From Python -The `libqasm` module should provide access to the `V3xAnalyzer` API: -- `parse_file`, -- `parse_string`, -- `analyze_file`, and -- `analyzer_string`. -The `cqasm.v3x` module is also available for a more fine-grained use of the library. +### Emscripten -``` -import cqasm.v3x.ast -import cqasm.v3x.instruction -import cqasm.v3x.primitives -import cqasm.v3x.semantic -import cqasm.v3x.types -import cqasm.v3x.values -``` - -### From C++ - -`libqasm` can be requested as a Conan package from a `conanfile.py`. - -``` -def build_requirements(self): - self.tool_requires("libqasm/0.6.5") -def requirements(self): - self.requires("libqasm/0.6.5") -``` +The emscripten API only allows to input a cQASM program as a string. -And then linked against from a `CMakeLists.txt`: +```typescript +import { default as wrapper } from 'cqasm_emscripten.mjs'; +wrapper().then(function(result: any) { + let analyzer = new result["EmscriptenWrapper"]() + let program = ` + version 3 + qubit[2] q + bit[2] b + H q[0] + CNOT q[0], q[1] + b = measure q + ` + let output = analyzer.parse_string_to_json(program) + analyzer.delete() +}).catch((error: any) => { + console.error("unhandledRejection", error, "\n"); +}); ``` -target_link_libraries( PUBLIC libqasm::libqasm) -``` - -Note that the following dependency is required for `libqasm` to build: - -* `Java JRE` >= 11 -The header file `cqasm.hpp` should provide access to the following API: -- `cqasm::v3x::analyze_file`, and -- `cqasm::v3x::analyze_string`. +### Python -Again, other header files are available for a more fine-grained use of the library. +```python +from libqasm import Analyzer -## Docker +if __name__ == "__main__": + parse_result = Analyzer.parse_file('example.cq') -This tests the library in a container with the bare minimum requirements for `libqasm`. - -``` -docker build . + analyzer = Analyzer() + analysis_result = analyzer.analyze_file('example.cq') ``` -**Note for Windows users:** The above might fail on Windows due to the `autocrlf` transformation that git does. -If you are having trouble with this just create new clone of this repository using: +## Documentation -``` -git clone --config core.autocrlf=input git@github.com:QuTech-Delft/libqasm.git -``` - -## Emscripten - -The generation of emscripten binaries has been tested as a cross-compilation from an ubuntu/x64 platform. +The [libQASM documentation](https://QuTech-Delft.github.io/OpenSquirrel/) is hosted through GitHub Pages. -``` -conan build . -pr=conan/profiles/release-clang-emscripten-wasm -pr:b=conan/profiles/release -b missing -``` +## License -The output of this build lives in `build/Release/emscripten`: -- `cqasm_emscripten.js`. -- `cqasm_emscripten.wasm`. +libQASM is licensed under the Apache License, Version 2.0. See +[LICENSE](https://github.com/QuTech-Delft/OpenSquirrel/blob/master/LICENSE.md) for the full +license text. -Note that `cqasm_emscripten.js` is an ES6 module. An example of how to use it would be: +## Authors -``` -cd build/Release/emscripten -mv cqasm_emscripten.js cqasm_emscripten.mjs -cd ../../../emscripten -deno run -A test_libqasm.ts -``` \ No newline at end of file +Quantum Inspire: [support@quantum-inspire.com](mailto:"support@quantum-inspire.com") diff --git a/conan/profiles/docs-release b/conan/profiles/docs-release new file mode 100644 index 00000000..a6bd58a8 --- /dev/null +++ b/conan/profiles/docs-release @@ -0,0 +1,8 @@ +include(default) + +[settings] +compiler.cppstd=20 +libqasm/*:build_type=Release + +[options] +libqasm/*:build_docs=True diff --git a/conan/profiles/docs-release-apple_clang-macos-arm64 b/conan/profiles/docs-release-apple_clang-macos-arm64 new file mode 100644 index 00000000..eccdeca0 --- /dev/null +++ b/conan/profiles/docs-release-apple_clang-macos-arm64 @@ -0,0 +1,5 @@ +include(docs-release) + +[settings] +compiler=apple-clang +compiler.version=14 diff --git a/conan/profiles/docs-release-apple_clang-macos-x64 b/conan/profiles/docs-release-apple_clang-macos-x64 new file mode 100644 index 00000000..eccdeca0 --- /dev/null +++ b/conan/profiles/docs-release-apple_clang-macos-x64 @@ -0,0 +1,5 @@ +include(docs-release) + +[settings] +compiler=apple-clang +compiler.version=14 diff --git a/conan/profiles/docs-release-clang-linux-arm64 b/conan/profiles/docs-release-clang-linux-arm64 new file mode 100644 index 00000000..07296e63 --- /dev/null +++ b/conan/profiles/docs-release-clang-linux-arm64 @@ -0,0 +1,9 @@ +include(docs-release) + +[settings] +compiler=clang +compiler.version=14 + +[conf] +tools.build:cxxflags=["-stdlib=libc++"] +tools.build:compiler_executables={ 'c' : 'clang', 'cpp' : 'clang++' } diff --git a/conan/profiles/docs-release-clang-linux-x64 b/conan/profiles/docs-release-clang-linux-x64 new file mode 100644 index 00000000..07296e63 --- /dev/null +++ b/conan/profiles/docs-release-clang-linux-x64 @@ -0,0 +1,9 @@ +include(docs-release) + +[settings] +compiler=clang +compiler.version=14 + +[conf] +tools.build:cxxflags=["-stdlib=libc++"] +tools.build:compiler_executables={ 'c' : 'clang', 'cpp' : 'clang++' } diff --git a/conan/profiles/docs-release-gcc-linux-arm64 b/conan/profiles/docs-release-gcc-linux-arm64 new file mode 100644 index 00000000..6e80b08d --- /dev/null +++ b/conan/profiles/docs-release-gcc-linux-arm64 @@ -0,0 +1,4 @@ +include(docs-release) + +[conf] +tools.build:cxxflags=["-Wno-error=maybe-uninitialized"] diff --git a/conan/profiles/docs-release-gcc-linux-x64 b/conan/profiles/docs-release-gcc-linux-x64 new file mode 100644 index 00000000..f186681f --- /dev/null +++ b/conan/profiles/docs-release-gcc-linux-x64 @@ -0,0 +1 @@ +include(docs-release) diff --git a/conan/profiles/docs-release-msvc-windows-x64 b/conan/profiles/docs-release-msvc-windows-x64 new file mode 100644 index 00000000..f186681f --- /dev/null +++ b/conan/profiles/docs-release-msvc-windows-x64 @@ -0,0 +1 @@ +include(docs-release) diff --git a/conanfile.py b/conanfile.py index 016841e5..4d0f520a 100644 --- a/conanfile.py +++ b/conanfile.py @@ -30,6 +30,7 @@ class LibqasmConan(ConanFile): "shared": [True, False], "fPIC": [True, False], "asan_enabled": [True, False], + "build_docs": [True, False], "build_python": [True, False], "cqasm_python_dir": [None, "ANY"], "python_dir": [None, "ANY"], @@ -39,6 +40,7 @@ class LibqasmConan(ConanFile): "shared": False, "fPIC": True, "asan_enabled": False, + "build_docs": False, "build_python": False, "cqasm_python_dir": None, "python_dir": None, @@ -120,6 +122,7 @@ def generate(self): tc = CMakeToolchain(self) tc.variables["ASAN_ENABLED"] = self.options.asan_enabled tc.variables["BUILD_SHARED_LIBS"] = self.options.shared + tc.variables["LIBQASM_BUILD_DOCS"] = self.options.build_docs tc.variables["LIBQASM_BUILD_EMSCRIPTEN"] = self.settings.arch == "wasm" tc.variables["LIBQASM_BUILD_PYTHON"] = self.options.build_python tc.variables["LIBQASM_BUILD_TESTS"] = self._should_build_test diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 00000000..cf0725ca --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,37 @@ +message(STATUS "Building docs") + +find_program(MKDOCS mkdocs REQUIRED) + +set(sources + src/about/contributing.md + src/about/license.md + src/about/release-notes.md + src/api/cpp.md + src/api/emscripten.md + src/api/python.md + src/dev-guide/dev-guide.md + src/dev-guide/cpp.md + src/dev-guide/emscripten.md + src/dev-guide/python.md + src/user-guide/user-guide.md + src/user-guide/cpp.md + src/user-guide/emscripten.md + src/user-guide/python.md + src/index.md +) + +add_custom_target(doc ALL + COMMAND ${CMAKE_COMMAND} + -E env PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/../scripts/python + ${MKDOCS} build -f ${CMAKE_CURRENT_SOURCE_DIR}/mkdocs.yml + # MkDocs requires the site dir to be outside of the doc dir. + --site-dir ${CMAKE_CURRENT_BINARY_DIR}/html + --no-directory-urls + SOURCES ${sources} +) + +include(GNUInstallDirs) +install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/ + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/libqasm OPTIONAL +) diff --git a/doc/mkdocs b/doc/mkdocs new file mode 100644 index 00000000..ca0412d4 --- /dev/null +++ b/doc/mkdocs @@ -0,0 +1,50 @@ +#!/usr/bin/python3 + +# A script to invoke mkdocs with the correct environment. +# Additionally, it supports deploying via mike: +# ./mkdocs deploy [mike-deploy-options] + +import errno +import os +import shutil +import sys +from subprocess import call + +doc_dir = os.path.dirname(os.path.normpath(__file__)) +scripts_dir = os.path.join(os.path.dirname(doc_dir), 'scripts') +build_dir = os.path.join(os.path.dirname(doc_dir), 'build', 'Release', 'doc') + +# Set PYTHONPATH for the mkdocstrings handler. +env = os.environ.copy() +path = env.get('PYTHONPATH') +env['PYTHONPATH'] = \ + (path + ':' if path else '') + os.path.join(scripts_dir, 'python') + +config_path = os.path.join(doc_dir, 'mkdocs.yml') +args = sys.argv[1:] +if len(args) > 0: + command = args[0] + if command == 'deploy': + git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:' + site_repo = git_url + 'QuTech-Delft/libqasm.git' + + site_dir = os.path.join(build_dir, 'libqasm') + try: + shutil.rmtree(site_dir) + except OSError as e: + if e.errno == errno.ENOENT: + pass + ret = call(['git', 'clone', '--depth=1', site_repo, site_dir]) + if ret != 0: + sys.exit(ret) + + # Copy the config to the build dir because the site is built relative to it. + config_build_path = os.path.join(build_dir, 'mkdocs.yml') + shutil.copyfile(config_path, config_build_path) + + sys.exit(call( + ['mike'] + args + ['--config-file', config_build_path, '--branch', 'master'], cwd=site_dir, env=env) + ) + elif not command.startswith('-'): + args += ['-f', config_path] +sys.exit(call(['mkdocs'] + args, env=env)) diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml new file mode 100644 index 00000000..4f0585c4 --- /dev/null +++ b/doc/mkdocs.yml @@ -0,0 +1,59 @@ +site_name: libQASM + +docs_dir: ../doc/src + +repo_url: https://github.com/QuTech-Delft/libqasm + +theme: + name: material + features: + - navigation.tabs + - navigation.top + - toc.integrate + +extra_javascript: + - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js + - cpp_highlighting.js + - python_highlighting.js + - typescript_highlighting.js + +extra_css: + - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css + - libqasm.css + +markdown_extensions: + - admonition + - pymdownx.inlinehilite + - pymdownx.snippets + +plugins: + - search + - mkdocstrings: + default_handler: python + +nav: + - Home: index.md + - User Guide: + - User Guide: user-guide/user-guide.md + - C++: user-guide/cpp.md + - Emscripten: user-guide/emscripten.md + - Python: user-guide/python.md + - Developer Guide: + - Dev Guide: dev-guide/dev-guide.md + - C++: dev-guide/cpp.md + - Emscripten: dev-guide/emscripten.md + - Python: dev-guide/python.md + - API: + - C++: api/cpp.md + - Emscripten: api/emscripten.md + - Python: api/python.md + - About: + - Release Notes: about/release-notes.md + - Contributing: about/contributing.md + - License: about/license.md + - Authors: about/authors.md + +extra: + version: + provider: mike + generator: false diff --git a/doc/src/about/authors.md b/doc/src/about/authors.md new file mode 100644 index 00000000..3f2ec0e1 --- /dev/null +++ b/doc/src/about/authors.md @@ -0,0 +1,2 @@ +libQASM is developed as part of the Quantum Inspire project: +[support@quantum-inspire.com](mailto:"support@quantum-inspire.com") diff --git a/doc/src/about/contributing.md b/doc/src/about/contributing.md new file mode 100644 index 00000000..4c1ac911 --- /dev/null +++ b/doc/src/about/contributing.md @@ -0,0 +1,10 @@ +Contributions are what make the open source community such an amazing place to learn, inspire, and create. +Any contributions you make are greatly appreciated. + +If you have a suggestion that would make this better, please fork the repo and create a pull request. + +1. Fork the project. +2. Create your feature branch (`git checkout -b feature/AmazingFeature`). +3. Commit your changes (`git commit -m 'Add some AmazingFeature'`). +4. Push to the branch (`git push origin feature/AmazingFeature`). +5. Open a pull request. diff --git a/doc/src/about/license.md b/doc/src/about/license.md new file mode 100644 index 00000000..5ec3bba1 --- /dev/null +++ b/doc/src/about/license.md @@ -0,0 +1,3 @@ +libQASM is licensed under the Apache License, Version 2.0. See +[LICENSE](https://github.com/QuTech-Delft/OpenSquirrel/blob/master/LICENSE.md) for the full +license text. diff --git a/doc/src/about/release-notes.md b/doc/src/about/release-notes.md new file mode 100644 index 00000000..1314c8f3 --- /dev/null +++ b/doc/src/about/release-notes.md @@ -0,0 +1 @@ +!!! info "Coming soon" diff --git a/doc/src/api/cpp.md b/doc/src/api/cpp.md new file mode 100644 index 00000000..19d82bc8 --- /dev/null +++ b/doc/src/api/cpp.md @@ -0,0 +1,7 @@ +`libqasm` C++ API is defined in `include/v3x/cqasm-py.hpp`. + +The only exported class is `V3xAnalyzer`. +This is actually a C++ class that works as a binding for accessing C++ code from Python. + +::: V3xAnalyzer + handler: cpp diff --git a/doc/src/api/emscripten.md b/doc/src/api/emscripten.md new file mode 100644 index 00000000..1b2d55d1 --- /dev/null +++ b/doc/src/api/emscripten.md @@ -0,0 +1,9 @@ +`libqasm` is also deployed as an Emscripten binary with a Typescript frontend. + +`libqasm` Typescript API is defined in `emscripten/emscripten_wrapper.hpp`. + +The only exported class is `EmscriptenWrapper`. +This is actually a C++ class that works as a binding for accessing C++ code from Typescript. + +::: EmscriptenWrapper + handler: emscripten diff --git a/doc/src/api/python.md b/doc/src/api/python.md new file mode 100644 index 00000000..b43061a6 --- /dev/null +++ b/doc/src/api/python.md @@ -0,0 +1,7 @@ +`libqasm` Python API is defined in `python/module/cqasm/v3x/__init__.py`. + +The only exported class is `Analyzer`. +This is actually a Python class that works as a binding for accessing C++ code from Python. + +::: Analyzer + handler: python diff --git a/doc/src/cpp_highlighting.js b/doc/src/cpp_highlighting.js new file mode 100644 index 00000000..ab4c571e --- /dev/null +++ b/doc/src/cpp_highlighting.js @@ -0,0 +1,4 @@ +document$.subscribe(() => { + hljs.highlightAll(), + { language: 'c++' } +}) diff --git a/doc/src/dev-guide/cpp.md b/doc/src/dev-guide/cpp.md new file mode 100644 index 00000000..e0e164b9 --- /dev/null +++ b/doc/src/dev-guide/cpp.md @@ -0,0 +1,17 @@ +You can build the C++ binaries from the project's root directory. +The following line will also build and cache the `libqasm` Conan package. + +!!! note + You may need to execute the `conan profile detect` command if this is the first time you run Conan. + +```shell +conan profile detect +conan create --version 0.6.6 . -pr:a=tests-debug -b missing +``` + +You can test the C++ binaries: + +```shell +cd test/Debug +ctest -C Debug --output-on-failure +``` diff --git a/doc/src/dev-guide/dev-guide.md b/doc/src/dev-guide/dev-guide.md new file mode 100644 index 00000000..4b84e0fb --- /dev/null +++ b/doc/src/dev-guide/dev-guide.md @@ -0,0 +1,127 @@ +## File organization + +For development, see: + +- `include`: public headers. +- `src`: source files. +- `test`: test files. +- `python`: SWIG interface. +- `res`: resource files, for testing. + +For build process, continuous integration, and documentation: + +- `conan`: Conan profiles. +- `emscripten`: bindings and test for the Emscripten binaries. +- `scripts`: helper scripts used during the build process. +- `.github`: GitHub Actions configuration files. +- `doc`: documentation. + +Build outputs may go into: + +- `build/`: the C++ library output files generated by Conan. +- `pybuild`: the Python library output files generated by `setup.py`. + +## Dependencies + +- C++ compiler with C++20 support (gcc 11, clang 14, msvc 17) +- `CMake` >= 3.12 +- `git` +- `Python` 3.x plus `pip`, with the following package: + - `conan` >= 2.0 + +### ARM builds + +We are having problems when using the `zulu-openjdk` Conan package on an ARMv8 architecture. +`zulu-openjdk` provides the Java JRE required by the ANTLR generator. +For the time being, we install Java manually for this platform. + +- `Java JRE` >= 11 + +### Documentation + +- `doxygen` +- `mkdocs` with the following packages: + - `mike` + - `mkdocs-material` + - `mkdocstrings` + - `pymdown-extensions` + +## Build + +This version of `libqasm` can only be compiled via the Conan package manager. +You will need to create a default profile before using it for the first time. + +The installation of dependencies, as well as the compilation, can be done in one go. + +```shell +git clone https://github.com/QuTech-Delft/libqasm.git +cd libqasm +conan profile detect +conan build . -pr:a=conan/profiles/tests-debug -b missing +``` + +!!! note + + - the `conan profile` command only has to be run only once, and not before every build. + - the `conan build` command is building `libqasm` in Debug mode with tests using the `tests-debug` profile. + - the `-b missing` parameter asks `conan` to build packages from sources + in case it cannot find the binary packages for the current configuration (platform, OS, compiler, build type...). + +### Profiles + +A group of predefined profiles is provided under the `conan/profiles` folder. +They follow the `[tests|docs](-build_type)(-compiler)(-os)(-arch)[-shared]` naming convention: + +- `tests`: if tests are being built. +- `docs`: if docs are being built. +- `build_type`: can be `debug` or `release`. +- `compiler`: `apple-clang`, `clang`, `gcc`, `msvc`. +- `os`: `emscripten`, `linux`, `macos`, `windows`. +- `arch`: `arm64`, `wasm`, `x64`. +- `shared`: if the library is being built in shared mode. + +All the profiles set the C++ standard to 20. +All the `tests`, except for `linux-x64` profiles, enable Address Sanitizer. + +### Options + +Profiles are a shorthand for command line options. The command above could be written, similarly, as: + +```shell +conan build . -s:a compiler.cppstd=20 -s:a libqasm/*:build_type=Debug -o libqasm/*:asan_enabled=True -c tools.build:skip_test=False -b missing +``` + +This is the list of options that could be specified either in a profile or in the command line: + +- `libqasm/*:asan_enabled={True,False}`: enables Address Sanitizer. +- `libqasm/*:build_docs={True,False}`: builds documentation or not. +- `libqasm/*:build_type={Debug,Release}`: builds in debug or release mode. +- `libqasm/*:shared={True,False}`: builds a shared object library instead of a static library, if applicable. + +Tests are disabled by default. To enable them, use `-c tools.build:skip_test=False`. + +### Documentation + +Build with a `docs-release-` Conan profile. +Then serve on `http://127.0.0.1:8000/` by running the local `mkdocs` from the `doc` folder. + +```shell +conan build . -pr:a=conan/profiles/docs-release-clang-linux-x64 -b missing +cd doc +python3 mkdocs serve +``` + +## Docker + +This tests the library in a container with the bare minimum requirements for `libqasm`. + +```shell +docker build . +``` + +**Note for Windows users:** The above might fail on Windows due to the `autocrlf` transformation that git does. +If you run into this issue, then create new clone of this repository: + +```shell +git clone --config core.autocrlf=input git@github.com:QuTech-Delft/libqasm.git +``` diff --git a/doc/src/dev-guide/emscripten.md b/doc/src/dev-guide/emscripten.md new file mode 100644 index 00000000..05c9b26c --- /dev/null +++ b/doc/src/dev-guide/emscripten.md @@ -0,0 +1,23 @@ +You can build the Emscripten binaries from the project's root directory. +The generation of Emscripten binaries has been tested as a cross-compilation from an ubuntu/x64 platform. + +```shell +conan build . -pr=conan/profiles/release-clang-emscripten-wasm -pr:b=conan/profiles/release -b missing +``` + +The output of this build lives in `build/Release/emscripten`: + +- `cqasm_emscripten.js`. +- `cqasm_emscripten.wasm`. + +Note that `cqasm_emscripten.js` is an ES6 module. +Its extension has to be renamed to `.mjs` before using it from Typescript code. + +You can test the Emscripten binaries: + +```shell +cd build/Release/emscripten +mv cqasm_emscripten.js cqasm_emscripten.mjs +cd ../../../emscripten +deno run -A test_libqasm.ts +``` diff --git a/doc/src/dev-guide/python.md b/doc/src/dev-guide/python.md new file mode 100644 index 00000000..d28dc49a --- /dev/null +++ b/doc/src/dev-guide/python.md @@ -0,0 +1,11 @@ +You can build and install the Python package from the project's root directory. + +```shell +python3 -m pip install --verbose . +``` + +You can test the Python package: + +```shell +python3 -m pytest +``` diff --git a/doc/src/index.md b/doc/src/index.md new file mode 100644 index 00000000..0b07cce7 --- /dev/null +++ b/doc/src/index.md @@ -0,0 +1,18 @@ +libQASM is a library to parse cQASM programs, developed by QuTech. + +It performs lexical, syntactic, and semantic analysis of an input program received via a file or a string. +It produces one of the following results: + +- A syntactic or semantic AST (Abstract Syntax Tree) object. Depending on if we are parsing or analysing. +- A list of parsing or analysing errors. In case the input program was malformed. +- A JSON representation of either the AST or the list of errors. + +It can be used from: + +- C++ projects (as a [Conan package](https://conan.io/center/recipes/libqasm)). +- Python projects (as a [Python package](https://pypi.org/project/libqasm/)). +- Emscripten projects (via a Typescript frontend). + +Check out [QX simulator](https://github.com/QuTech-Delft/qx-simulator) +and [OpenSquirrel](https://github.com/QuTech-Delft/OpenSquirrel) compiler +for an example of use in a C++ and a Python project, respectively. diff --git a/doc/src/libqasm.css b/doc/src/libqasm.css new file mode 100644 index 00000000..4c642c0e --- /dev/null +++ b/doc/src/libqasm.css @@ -0,0 +1,61 @@ +:root { + --md-primary-fg-color: #0050D0; +} + +.md-grid { + max-width: 960px; +} + +@media (min-width: 400px) { + .md-tabs { + display: block; + } +} + +.docblock { + border-left: .05rem solid var(--md-primary-fg-color); +} + +.docblock-desc { + margin-left: 1em; +} + +pre > code.decl { + white-space: pre-wrap; +} + + +code.decl > div { + text-indent: -2ch; /* Negative indent to counteract the indent on the first line */ + padding-left: 2ch; /* Add padding to the left to create an indent */ +} + +.features-container { + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: center; /* Center the items horizontally */ +} + +.feature { + flex: 1 1 calc(50% - 20px); /* Two columns with space between */ + max-width: 600px; /* Set the maximum width for the feature boxes */ + box-sizing: border-box; + padding: 10px; + overflow: hidden; /* Hide overflow content */ + text-overflow: ellipsis; /* Handle text overflow */ + white-space: normal; /* Allow text wrapping */ +} + +.feature h2 { + margin-top: 0px; + font-weight: bold; +} + +@media (max-width: 768px) { + .feature { + flex: 1 1 100%; /* Stack columns on smaller screens */ + max-width: 100%; /* Allow full width on smaller screens */ + white-space: normal; /* Allow text wrapping on smaller screens */ + } +} diff --git a/doc/src/python_highlighting.js b/doc/src/python_highlighting.js new file mode 100644 index 00000000..5a97af21 --- /dev/null +++ b/doc/src/python_highlighting.js @@ -0,0 +1,4 @@ +document$.subscribe(() => { + hljs.highlightAll(), + { language: 'python' } +}) diff --git a/doc/src/typescript_highlighting.js b/doc/src/typescript_highlighting.js new file mode 100644 index 00000000..9eb432fc --- /dev/null +++ b/doc/src/typescript_highlighting.js @@ -0,0 +1,4 @@ +document$.subscribe(() => { + hljs.highlightAll(), + { language: 'typescript' } +}) diff --git a/doc/src/user-guide/cpp.md b/doc/src/user-guide/cpp.md new file mode 100644 index 00000000..df3bd269 --- /dev/null +++ b/doc/src/user-guide/cpp.md @@ -0,0 +1,38 @@ +`libqasm` can be requested as a Conan package from a `conanfile.py`. + +``` +def build_requirements(self): + self.tool_requires("libqasm/0.6.6") +def requirements(self): + self.requires("libqasm/0.6.6") +``` + +And then linked against from a `CMakeLists.txt`: + +``` +target_link_libraries( PUBLIC libqasm::libqasm) +``` + +Note that the following dependency is required for `libqasm` to build: + +* `Java JRE` >= 11 + +**Example**: + +```cpp +#include "v3x/cqasm-py.hpp" + +auto program = std::string{ R"( + version 3.0 + qubit[2] q + bit[2] b + H q[0] + CNOT q[0], q[1] + b = measure q +)" }; + +auto parse_result = V3xAnalyzer::parse_string(program); + +auto analyzer = V3xAnalyzer(); +auto analysis_result = analyzer.analyze_string(program); +``` diff --git a/doc/src/user-guide/emscripten.md b/doc/src/user-guide/emscripten.md new file mode 100644 index 00000000..1b9a344f --- /dev/null +++ b/doc/src/user-guide/emscripten.md @@ -0,0 +1,24 @@ +`libqasm` can be used from a web environment via a Typescript frontend. + +Emscripten API only allows to input a cQASM program via a string +and always returns a JSON string output. + +**Example**: + +```typescript +import { default as wrapper } from 'cqasm_emscripten.mjs'; + +wrapper().then(function(result: any) { + let cqasm = new result["EmscriptenWrapper"]() + let program = `version 3.0 + qubit[2] q + bit[2] b + H q[0] + CNOT q[0], q[1] + b = measure q` + let output = cqasm.parse_string_to_json(program, "bell.cq") + cqasm.delete() +}).catch((error: any) => { + console.error("unhandledRejection", error, "\n"); +}); +``` diff --git a/doc/src/user-guide/python.md b/doc/src/user-guide/python.md new file mode 100644 index 00000000..6b12de74 --- /dev/null +++ b/doc/src/user-guide/python.md @@ -0,0 +1,21 @@ +`libqasm` can be imported as a Python package. + +**Example**: + +```python +from libqasm import Analyzer + +program = r''' + version 3.0 + qubit[2] q + bit[2] b + H q[0] + CNOT q[0], q[1] + b = measure q +''' + +parse_result = Analyzer.parse_string(program, "bell.cq") + +analyzer = Analyzer() +analysis_result = analyzer.analyze_string(program, "bell.cq") +``` diff --git a/doc/src/user-guide/user-guide.md b/doc/src/user-guide/user-guide.md new file mode 100644 index 00000000..178e4269 --- /dev/null +++ b/doc/src/user-guide/user-guide.md @@ -0,0 +1,5 @@ +`libqasm` can be used from: + +- C++ projects (as a [Conan package](https://conan.io/center/recipes/libqasm)). +- Python projects (as a [Python package](https://pypi.org/project/libqasm/)). +- Emscripten projects (via a Typescript frontend). diff --git a/emscripten/emscripten_wrapper.hpp b/emscripten/emscripten_wrapper.hpp index 864325c0..78095238 100644 --- a/emscripten/emscripten_wrapper.hpp +++ b/emscripten/emscripten_wrapper.hpp @@ -4,37 +4,89 @@ #include +/** + * Main class for parsing and analyzing cQASM files. + * + * This class works as a binding for accessing C++ code from Typescript. + * + * **Example**: + * + * import { default as wrapper } from 'cqasm_emscripten.mjs'; + * + * wrapper().then(function(result: any) { + * let cqasm = new result["EmscriptenWrapper"]() + * let program = ... + * let output = cqasm.parse_string_to_json(program, ...) + * cqasm.delete() + * }).catch((error: any) => { + * console.error("unhandledRejection", error, "\n"); + * }); + */ struct EmscriptenWrapper { EmscriptenWrapper() = default; /** - * Returns libqasm version. + * Returns the version of the `libqasm` library. + * + * **Example**: + * + * let version = cqasm.get_version() */ std::string get_version(); /** - * Parses a data string containing a v3.x program. - * The file_name is only used when reporting errors. - * No version check or conversion is performed. + * Parses a string containing a cQASM v3.0 program. + * + * No version check is performed. + * + * The `file_name` is only used when reporting errors. + * * Returns a string. - * If the parsing was successful, that string contains a v3.x syntactic AST. + * If the parsing was successful, the string contains a syntactic Abstract Syntax Tree (AST). * Otherwise, it will contain a list of errors. * The JSON representation of each error follows the Language Server Protocol (LSP) specification. * Every error is mapped to an LSP Diagnostic structure: - * severity is hardcoded to 1 at the moment (value corresponding to an Error level). + * `severity` is hardcoded to 1 at the moment (value corresponding to an Error level). + * + * **Example**: + * + * let program = ` + * version 3 + * qubit[2] q + * bit[2] b + * H q[0] + * CNOT q[0], q[1] + * b = measure q + * ` + * let output = parse_string_to_json(program, "bell.cq") */ std::string parse_string_to_json(const std::string &data, const std::string &file_name); /** - * Parses and analyzes a data string containing a v3.x program. - * The file_name is only used when reporting errors. - * No version check or conversion is performed. + * Parses and analyzes a string containing a cQASM v3.0 program. + * + * No version check is performed. + * + * The `file_name` is only used when reporting errors. + * * Returns a string. - * If the parsing was successful, that string contains a v3.x semantic AST. + * If the parsing was successful, the string contains a semantic Abstract Syntax Tree (AST). * Otherwise, it will contain a list of errors. * The JSON representation of each error follows the Language Server Protocol (LSP) specification. * Every error is mapped to an LSP Diagnostic structure: - * severity is hardcoded to 1 at the moment (value corresponding to an Error level). + * `severity` is hardcoded to 1 at the moment (value corresponding to an Error level). + * + * **Example**: + * + * let program = ` + * version 3 + * qubit[2] q + * bit[2] b + * H q[0] + * CNOT q[0], q[1] + * b = measure q + * ` + * let output = analyze_string_to_json(program, "bell.cq") */ std::string analyze_string_to_json(const std::string &data, const std::string &file_name); }; diff --git a/include/v3x/cqasm-py.hpp b/include/v3x/cqasm-py.hpp index c048f8d9..ee3f1662 100644 --- a/include/v3x/cqasm-py.hpp +++ b/include/v3x/cqasm-py.hpp @@ -19,7 +19,53 @@ class Analyzer; } /** - * Main class for parsing and analyzing cQASM files with the v3.x API. + * Main class for parsing and analyzing cQASM v3.0 files. + * + * This class works as a binding for accessing C++ code from Python. + * + * The parsing methods are static because they do not change the status of the analyzer. + * Instead, they just invoke free functions that create a temporary instance of a parser. + * + * None of the parsing or the analyzing methods perform any version check. + * + * `parse_file`, `parse_string`, `analyze_file`, and `analyze_string`: + * + * - Return a vector of strings. + * The first string is reserved for the CBOR serialization + * of the syntactic/semantic (in the case of parsing/analyzing) Abstract Syntax Tree (AST) + * of the input program. + * Any additional strings represent error messages. + * Notice that the AST and error messages will not be available at the same time. + * + * `parse_file_to_json`, `parse_string_to_json`, `analyze_file_to_json`, and `analyze_string_to_json`: + * + * - Return a string in JSON format. + * If the parsing was successful, that string contains a JSON representation + * of the AST of the input program. + * Otherwise, it will contain a list of errors. + * The JSON representation of each error follows the Language Server Protocol (LSP) specification. + * Every error is mapped to an LSP Diagnostic structure: + * `severity` is hardcoded to 1 at the moment (value corresponding to an Error level). + * + * `parse_string`, `parse_string_to_json`, `analyze_string, and `analyze_string_to_json`: + * + * - have an optional second argument: `file_name`. It is only used when reporting errors. + * + * **Example**: + * + * auto result = analyze_file("grover.cq"); + * + * **Example**: + * + * auto program = std::string{ R"( + * version 3.0 + * qubit[2] q + * bit[2] b + * H q[0] + * CNOT q[0], q[1] + * b = measure q + * )" }; + * auto result = parse_string_to_json(program, "bell.cq"); */ class V3xAnalyzer { /** @@ -29,109 +75,94 @@ class V3xAnalyzer { public: /** - * Creates a new v3.x semantic analyzer. - * When without_defaults is specified, the default instruction set is not loaded into the instruction table, - * so you have to specify the entire instruction set using register_instruction(). + * Creates a new cQASM v3.0 semantic analyzer. + * + * `max_version` is optional. It has a default value of `3.0`. The maximum cQASM version supported by the analyzer. + * + * `without_defaults` is optional. If set, the default instruction set is not loaded into the instruction table, + * so you have to specify the entire instruction set using `register_instruction()`. * Otherwise, those functions only add to the defaults. - * Unlike the C++ version of the analyzer class, - * the initial mappings and functions are not configurable at all. + * + * The initial mappings and functions are not configurable. * The defaults for these are always used. */ V3xAnalyzer(const std::string &max_version = "3.0", bool without_defaults = false); /** + * Default destructor. + */ + /* * std::unique_ptr requires T to be a complete class for the ~T operation. - * Since we are using a forward declaration for Analyzer, we need to declare ~T in the header file, + * Since we are using a forward declaration for Analyzer, we need to declare ~V3xAnalyzer in the header file * and implement it in the source file. */ ~V3xAnalyzer(); /** * Registers an instruction type. - * The arguments are passed straight to instruction::Instruction's constructor. + * + * The `param_types` can be: + * - `Q` = qubit. + * - `V` = qubit array. + * - `B` = bit. + * - `W` = bit array. + * - `i` = int. + * - `f` = float. + * + * **Example**: + * + * register_instruction("H", "Q"); + */ + /* + * The arguments are passed directly to `instruction::Instruction`'s constructor. */ void register_instruction(const std::string &name, const std::string ¶m_types = ""); /** - * Only parses the given file. - * The file must be in v3.x syntax. - * No version check or conversion is performed. - * Returns a vector of strings, of which the first is reserved for the CBOR serialization of the v3.x AST. - * Any additional strings represent error messages. - * Notice that the AST and error messages won't be available at the same time. + * Parses a file containing a cQASM v3.0 program. */ static std::vector parse_file(const std::string &file_name); /** - * Parses a file containing a v3.x program. - * No version check or conversion is performed. - * Returns a string. - * If the parsing was successful, that string contains a v3.x syntactic AST. - * Otherwise, it will contain a list of errors. - * The JSON representation of each error follows the Language Server Protocol (LSP) specification. - * Every error is mapped to an LSP Diagnostic structure: - * severity is hardcoded to 1 at the moment (value corresponding to an Error level). + * Parses a file containing a cQASM v3.0 program. */ static std::string parse_file_to_json(const std::string &file_name); /** - * Same as parse_file(), but instead receives the file contents directly. - * The file_name, if non-empty, is only used when reporting errors. + * Parses a string containing a cQASM v3.0 program. + * + * `file_name` is optional. It is only used when reporting errors. */ static std::vector parse_string(const std::string &data, const std::string &file_name = ""); /** - * Parses a data string containing a v3.x program. - * The file_name is only used when reporting errors. - * No version check or conversion is performed. - * Returns a string. - * If the parsing was successful, that string contains a v3.x syntactic AST. - * Otherwise, it will contain a list of errors. - * The JSON representation of each error follows the Language Server Protocol (LSP) specification. - * Every error is mapped to an LSP Diagnostic structure: - * severity is hardcoded to 1 at the moment (value corresponding to an Error level). + * Parses a string containing a cQASM v3.0 program. + * + * `file_name` is optional. It is only used when reporting errors. */ static std::string parse_string_to_json(const std::string &data, const std::string &file_name = ""); /** - * Parses and analyzes the given file. - * If the file is written in a later file version, - * this function may try to reduce it to the maximum v3.x API version support advertised - * using this object's constructor. - * Returns a vector of strings, of which the first is reserved for the CBOR serialization of the v3.x semantic tree. - * Any additional strings represent error messages. - * Notice that the AST and error messages won't be available at the same time. + * Parses and analyzes a file containing a cQASM v3.0 program. */ std::vector analyze_file(const std::string &file_name) const; /** - * Parses and analyzes a file containing a v3.x program. - * No version check or conversion is performed. - * Returns a string. - * If the parsing was successful, that string contains a v3.x semantic AST. - * Otherwise, it will contain a list of errors. - * The JSON representation of each error follows the Language Server Protocol (LSP) specification. - * Every error is mapped to an LSP Diagnostic structure: - * severity is hardcoded to 1 at the moment (value corresponding to an Error level). + * Parses and analyzes a file containing a cQASM v3.0 program. */ std::string analyze_file_to_json(const std::string &file_name) const; /** - * Same as analyze_file(), but instead receives the file contents directly. - * The file_name, if non-empty, is only used when reporting errors. + * Parses and analyzes a string containing a cQASM v3.0 program. + * + * `file_name` is optional. It is only used when reporting errors. */ std::vector analyze_string(const std::string &data, const std::string &file_name = "") const; /** - * Parses and analyzes a data string containing a v3.x program. - * The file_name is only used when reporting errors. - * No version check or conversion is performed. - * Returns a string. - * If the parsing was successful, that string contains a v3.x semantic AST. - * Otherwise, it will contain a list of errors. - * The JSON representation of each error follows the Language Server Protocol (LSP) specification. - * Every error is mapped to an LSP Diagnostic structure: - * severity is hardcoded to 1 at the moment (value corresponding to an Error level). + * Parses and analyzes a string containing a cQASM v3.0 program. + * + * `file_name` is optional. It is only used when reporting errors. */ std::string analyze_string_to_json(const std::string &data, const std::string &file_name = "") const; }; diff --git a/python/module/cqasm/v3x/__init__.py b/python/module/cqasm/v3x/__init__.py index 7891e555..298b3419 100644 --- a/python/module/cqasm/v3x/__init__.py +++ b/python/module/cqasm/v3x/__init__.py @@ -4,16 +4,58 @@ class Analyzer(libqasm.V3xAnalyzer): - # parse_file and parse_string are static methods because they do not change the status of the analyzer - # Instead, they just invoke free functions that create a temporary instance of a parser - # analyze_file and analyze_string are not static methods because they change the status of the analyzer + """! + Main class for parsing and analyzing cQASM v3.0 files. - # parse_file, parse_string, analyze_file, and analyze_string return a vector of strings - # If the length of the vector is 1, the string is a serialization of the AST - # Otherwise, it is a list of errors + This class works as a binding for accessing C++ code from Python. + + The parsing methods are static because they do not change the status of the analyzer. + Instead, they just invoke free functions that create a temporary instance of a parser. + + None of the parsing or the analyzing methods perform any version check. + + `parse_file`, `parse_string`, `analyze_file`, and `analyze_string`: + + - return a vector of strings. + If the length of the vector is 1, the string is a serialization + of the syntactic/semantic (in the case of parsing/analyzing) Abstract Syntax Tree (AST) + of the input program. + Otherwise, it is a list of errors. + + `parse_file_to_json`, `parse_string_to_json`, `analyze_file_to_json`, and `analyze_string_to_json`: + + - return a string in JSON format. + If the parsing was successful, the string contains a JSON representation + of the AST of the input program. + Otherwise, it will contain a list of errors. + The JSON representation of each error follows the Language Server Protocol (LSP) specification. + Every error is mapped to an LSP Diagnostic structure: + `severity` is hardcoded to 1 at the moment (value corresponding to an Error level). + + `parse_string`, `parse_string_to_json`, `analyze_string, and `analyze_string_to_json`: + + - have an optional second argument: `file_name`. It is only used when reporting errors. + + **Example**: + + result = libqasm.analyze_file("grover.cq"); + + **Example**: + + program = r''' + version 3.0 + qubit[2] q + bit[2] b + H q[0] + CNOT q[0], q[1] + b = measure q + ''' + result = libqasm.parse_string_to_json(program, "bell.cq"); + """ @staticmethod - def parse_file(*args): + def parse_file(*args) -> list[str]: + """! Parses a file containing a cQASM v3.0 program.""" ret = libqasm.V3xAnalyzer.parse_file(*args) if len(ret) == 1: serialized_ast_str = str(ret[0]) @@ -23,7 +65,13 @@ def parse_file(*args): return [str(error) for error in ret[1:]] @staticmethod - def parse_string(*args): + def parse_file_to_json(*args) -> str: + """! Parses a file containing a cQASM v3.0 program.""" + return libqasm.V3xAnalyzer.parse_file_to_json(*args) + + @staticmethod + def parse_string(*args) -> list[str]: + """! Parses a string containing a cQASM v3.0 program.""" ret = libqasm.V3xAnalyzer.parse_string(*args) if len(ret) == 1: serialized_ast_str = str(ret[0]) @@ -32,7 +80,13 @@ def parse_string(*args): return deserialized_ast return [str(error) for error in ret[1:]] - def analyze_file(self, *args): + @staticmethod + def parse_string_to_json(*args) -> str: + """! Parses a string containing a cQASM v3.0 program.""" + return libqasm.V3xAnalyzer.parse_string_to_json(*args) + + def analyze_file(self, *args) -> list[str]: + """! Parses and analyzes a file containing a cQASM v3.0 program.""" ret = super().analyze_file(*args) if len(ret) == 1: serialized_ast_str = str(ret[0]) @@ -41,7 +95,12 @@ def analyze_file(self, *args): return deserialized_ast return [str(error) for error in ret[1:]] - def analyze_string(self, *args): + def analyze_file_to_json(self, *args) -> str: + """! Parses and analyzes a file containing a cQASM v3.0 program.""" + return super().analyze_file_to_json(*args) + + def analyze_string(self, *args) -> list[str]: + """! Parses and analyzes a string containing a cQASM v3.0 program.""" ret = super().analyze_string(*args) if len(ret) == 1: serialized_ast_str = str(ret[0]) @@ -50,16 +109,6 @@ def analyze_string(self, *args): return deserialized_ast return [str(error) for error in ret[1:]] - @staticmethod - def parse_file_to_json(*args): - return libqasm.V3xAnalyzer.parse_file_to_json(*args) - - @staticmethod - def parse_string_to_json(*args): - return libqasm.V3xAnalyzer.parse_string_to_json(*args) - - def analyze_file_to_json(self, *args): - return super().analyze_file_to_json(*args) - - def analyze_string_to_json(self, *args): + def analyze_string_to_json(self, *args) -> str: + """! Parses and analyzes a string containing a cQASM v3.0 program.""" return super().analyze_string_to_json(*args) diff --git a/scripts/python/mkdocstrings_handlers/cpp/__init__.py b/scripts/python/mkdocstrings_handlers/cpp/__init__.py new file mode 100644 index 00000000..09e53ab2 --- /dev/null +++ b/scripts/python/mkdocstrings_handlers/cpp/__init__.py @@ -0,0 +1,255 @@ +# A basic mkdocstrings handler for {fmt}. +# Copyright (c) 2012 - present, Victor Zverovich +# https://github.com/fmtlib/fmt/blob/master/LICENSE + +import os +import xml.etree.ElementTree as ElementTree + +from pathlib import Path +from subprocess import CalledProcessError, PIPE, Popen, STDOUT +from typing import Any, List, Mapping, Optional + +from mkdocstrings.handlers.base import BaseHandler + + +class Definition: + """A definition extracted by Doxygen.""" + + def __init__( + self, + name: str, + kind: Optional[str] = None, + node: Optional[ElementTree.Element] = None, + is_member: bool = False + ): + self.name = name + self.kind = kind if kind is not None else node.get('kind') + self.id = name if not is_member else None + self.desc = None + self.is_static = False + self.members = None + self.params = None + self.type = None + + +# A map from Doxygen to HTML tags. +tag_map = { + 'bold': 'b', + 'computeroutput': 'code', + 'emphasis': 'em', + 'itemizedlist': 'ul', + 'listitem': 'li', + 'para': 'p', + 'programlisting': 'pre', + 'verbatim': 'pre', +} + +# A map from Doxygen tags to text. +tag_text_map = { + 'codeline': '', + 'highlight': '', + 'sp': ' ', +} + + +def escape_html(s: str) -> str: + return s.replace("<", "<") + + +def doxyxml2html(nodes: List[ElementTree.Element]): + out = '' + for n in nodes: + tag = tag_map.get(n.tag) + if not tag: + out += tag_text_map[n.tag] + out += '<' + tag + '>' if tag else '' + out += '' if tag == 'pre' else '' + if n.text: + out += escape_html(n.text) + out += doxyxml2html(list(n)) + out += '' if tag == 'pre' else '' + out += '' if tag else '' + if n.tail: + out += n.tail + return out + + +def get_description(node: ElementTree.Element) -> List[ElementTree.Element]: + return node.findall('briefdescription/para') + \ + node.findall('detaileddescription/para') + + +def normalize_type(t: str) -> str: + normalized_type = t.replace('< ', '<').replace(' >', '>') + return normalized_type.replace(' &', '&').replace(' *', '*') + + +def convert_type(t: ElementTree.Element) -> Optional[str]: + if t is None: + return None + result = t.text if t.text else '' + for ref in t: + result += ref.text + if ref.tail: + result += ref.tail + result += t.tail.strip() + result = normalize_type(result) + return normalize_type(result) + + +def convert_params(func: ElementTree.Element) -> List[Definition]: + params = [] + for p in func.findall('param'): + d = Definition(p.find('declname').text, 'param') + d.type = convert_type(p.find('type')) + params.append(d) + return params + + +def render_param(param: Definition) -> str: + return param.type + (f' {param.name}' if len(param.name) > 0 else '') + + +def render_decl(d: Definition) -> str: + text = '' + if d.id is not None: + text += f'\n' + text += '
'
+
+    text += '
' + end = ';' + if d.is_static: + text += 'static ' + if d.kind == 'function' or d.kind == 'variable': + text += escape_html(d.type) + ' ' if len(d.type) > 0 else '' + else: + text += d.kind + ' ' + text += d.name + + if d.params is not None: + params = ', '.join([ + (p.type + ' ' if p.type else '') + p.name for p in d.params]) + text += '(' + escape_html(params) + ')' + + text += end + text += '
' + text += '
\n' + if d.id is not None: + text += f'
\n' + return text + + +class CppHandler(BaseHandler): + def __init__(self, **kwargs: Any) -> None: + super().__init__(handler='cpp', **kwargs) + + headers = [ + 'cqasm-py.hpp' + ] + + # Run doxygen. + cmd = ['doxygen', '-'] + scripts_dir = Path(__file__).parents[3] + top_dir = os.path.dirname(scripts_dir) + include_dir = os.path.join(top_dir, 'include', 'v3x') + self._ns2doxyxml = {} + build_dir = os.path.join(top_dir, 'build', 'Release', 'doc', 'cpp') + os.makedirs(build_dir, exist_ok=True) + self._doxyxml_dir = os.path.join(build_dir, 'doxyxml') + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + _, _ = p.communicate(input=r''' + PROJECT_NAME = libqasm + GENERATE_XML = YES + GENERATE_LATEX = NO + GENERATE_HTML = NO + INPUT = {0} + XML_OUTPUT = {1} + QUIET = YES + AUTOLINK_SUPPORT = NO + MACRO_EXPANSION = YES + PREDEFINED = _WIN32=1 \ + __linux__=1 \ + ''' + .format(' '.join([os.path.join(include_dir, h) for h in headers]), self._doxyxml_dir) + .encode('utf-8') + ) + if p.returncode != 0: + raise CalledProcessError(p.returncode, cmd) + + # Merge all file-level XMLs into one to simplify search. + self._file_doxyxml = None + for h in headers: + filename = h.replace(".hpp", "_8hpp.xml") + with open(os.path.join(self._doxyxml_dir, filename)) as f: + doxyxml = ElementTree.parse(f) + if self._file_doxyxml is None: + self._file_doxyxml = doxyxml + continue + root = self._file_doxyxml.getroot() + for node in doxyxml.getroot(): + root.append(node) + + def collect_compound(self) -> Definition: + """Collect a compound definition such as a struct.""" + path = os.path.join(self._doxyxml_dir, 'classV3xAnalyzer.xml') + with open(path) as f: + xml = ElementTree.parse(f) + node = xml.find('compounddef') + d = Definition('V3xAnalyzer', node=node) + d.desc = get_description(node) + d.members = [] + for m in \ + node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \ + node.findall('sectiondef[@kind="public-static-func"]/memberdef') + \ + node.findall('sectiondef[@kind="public-func"]/memberdef'): + name = m.find('name').text + # Doxygen incorrectly classifies members of private unnamed unions as + # public members of the containing class. + if name.endswith('_'): + continue + desc = get_description(m) + is_static = m.get('static') == 'yes' + if len(desc) == 0: + continue + kind = m.get('kind') + member = Definition(name if name else '', kind=kind, is_member=True) + t = m.find('type') + member.type = convert_type(t) + if kind == 'function': + member.params = convert_params(m) + member.desc = desc + member.is_static = is_static + d.members.append(member) + return d + + def collect(self, _identifier: str, _config: Mapping[str, Any]) -> Definition: + return self.collect_compound() + + def render(self, d: Definition, config: dict) -> str: + if d.id is not None: + self.do_heading('', 0, id=d.id) + text = '
\n' + text += render_decl(d) + text += '
\n' + text += doxyxml2html(d.desc) + if d.members is not None: + for m in d.members: + text += self.render(m, config) + text += '
\n' + text += '
\n' + return text + + +def get_handler( + theme: str, + custom_templates: Optional[str] = None, + **_config: Any +) -> CppHandler: + """Return an instance of `CppHandler`. + + Arguments: + theme: The theme to use when rendering contents. + custom_templates: Directory containing custom templates. + **_config: Configuration passed to the handler. + """ + return CppHandler(theme=theme, custom_templates=custom_templates) diff --git a/scripts/python/mkdocstrings_handlers/cpp/templates/README b/scripts/python/mkdocstrings_handlers/cpp/templates/README new file mode 100644 index 00000000..7a299fe9 --- /dev/null +++ b/scripts/python/mkdocstrings_handlers/cpp/templates/README @@ -0,0 +1 @@ +mkdocstrings requires a handler to have a templates directory. \ No newline at end of file diff --git a/scripts/python/mkdocstrings_handlers/emscripten/__init__.py b/scripts/python/mkdocstrings_handlers/emscripten/__init__.py new file mode 100644 index 00000000..cfa3b932 --- /dev/null +++ b/scripts/python/mkdocstrings_handlers/emscripten/__init__.py @@ -0,0 +1,254 @@ +# A basic mkdocstrings handler for {fmt}. +# Copyright (c) 2012 - present, Victor Zverovich +# https://github.com/fmtlib/fmt/blob/master/LICENSE + +import os +import xml.etree.ElementTree as ElementTree + +from pathlib import Path +from subprocess import CalledProcessError, PIPE, Popen, STDOUT +from typing import Any, List, Mapping, Optional + +from mkdocstrings.handlers.base import BaseHandler + + +class Definition: + """A definition extracted by Doxygen.""" + + def __init__( + self, + name: str, + kind: Optional[str] = None, + node: Optional[ElementTree.Element] = None, + is_member: bool = False + ): + self.name = name + self.kind = kind if kind is not None else node.get('kind') + self.id = name if not is_member else None + self.desc = None + self.is_static = False + self.members = None + self.params = None + self.type = None + + +# A map from Doxygen to HTML tags. +tag_map = { + 'bold': 'b', + 'emphasis': 'em', + 'computeroutput': 'code', + 'para': 'p', + 'programlisting': 'pre', + 'verbatim': 'pre' +} + +# A map from Doxygen tags to text. +tag_text_map = { + 'codeline': '', + 'highlight': '', + 'sp': ' ' +} + + +def escape_html(s: str) -> str: + return s.replace("<", "<") + + +def doxyxml2html(nodes: List[ElementTree.Element]): + out = '' + for n in nodes: + tag = tag_map.get(n.tag) + if not tag: + out += tag_text_map[n.tag] + out += '<' + tag + '>' if tag else '' + out += '' if tag == 'pre' else '' + if n.text: + out += escape_html(n.text) + out += doxyxml2html(list(n)) + out += '' if tag == 'pre' else '' + out += '' if tag else '' + if n.tail: + out += n.tail + return out + + +def get_description(node: ElementTree.Element) -> List[ElementTree.Element]: + return node.findall('briefdescription/para') + \ + node.findall('detaileddescription/para') + + +def normalize_type(t: str) -> str: + normalized_type = t.replace('< ', '<').replace(' >', '>') + return normalized_type.replace(' &', '&').replace(' *', '*') + + +def convert_type(t: ElementTree.Element) -> Optional[str]: + if t is None: + return None + result = t.text if t.text else '' + for ref in t: + result += ref.text + if ref.tail: + result += ref.tail + result += t.tail.strip() + result = normalize_type(result) + return normalize_type(result) + + +def convert_params(func: ElementTree.Element) -> List[Definition]: + params = [] + for p in func.findall('param'): + d = Definition(p.find('declname').text, 'param') + d.type = convert_type(p.find('type')) + params.append(d) + return params + + +def render_param(param: Definition) -> str: + return param.type + (f' {param.name}' if len(param.name) > 0 else '') + + +def render_decl(d: Definition) -> str: + text = '' + if d.id is not None: + text += f'\n' + text += '
'
+
+    text += '
' + end = ';' + if d.is_static: + text += 'static ' + if d.kind == 'function' or d.kind == 'variable': + text += escape_html(d.type) + ' ' if len(d.type) > 0 else '' + else: + text += d.kind + ' ' + text += d.name + + if d.params is not None: + params = ', '.join([ + (p.type + ' ' if p.type else '') + p.name for p in d.params]) + text += '(' + escape_html(params) + ')' + + text += end + text += '
' + text += '
\n' + if d.id is not None: + text += f'
\n' + return text + + +class EmscriptenHandler(BaseHandler): + def __init__(self, **kwargs: Any) -> None: + super().__init__(handler='emscripten', **kwargs) + + headers = [ + 'emscripten_wrapper.hpp' + ] + + # Run doxygen. + cmd = ['doxygen', '-'] + scripts_dir = Path(__file__).parents[3] + top_dir = os.path.dirname(scripts_dir) + include_dir = os.path.join(top_dir, 'emscripten') + self._ns2doxyxml = {} + build_dir = os.path.join(top_dir, 'build', 'Release', 'doc', 'ts') + os.makedirs(build_dir, exist_ok=True) + self._doxyxml_dir = os.path.join(build_dir, 'doxyxml') + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + _, _ = p.communicate(input=r''' + PROJECT_NAME = libqasm + GENERATE_XML = YES + GENERATE_LATEX = NO + GENERATE_HTML = NO + INPUT = {0} + XML_OUTPUT = {1} + QUIET = YES + AUTOLINK_SUPPORT = NO + MACRO_EXPANSION = YES + PREDEFINED = _WIN32=1 \ + __linux__=1 + ''' + .format(' '.join([os.path.join(include_dir, h) for h in headers]), self._doxyxml_dir) + .encode('utf-8') + ) + if p.returncode != 0: + raise CalledProcessError(p.returncode, cmd) + + # Merge all file-level XMLs into one to simplify search. + self._file_doxyxml = None + for h in headers: + filename = h.replace("_", "__") + filename = filename.replace(".hpp", "_8hpp.xml") + with open(os.path.join(self._doxyxml_dir, filename)) as f: + doxyxml = ElementTree.parse(f) + if self._file_doxyxml is None: + self._file_doxyxml = doxyxml + continue + root = self._file_doxyxml.getroot() + for node in doxyxml.getroot(): + root.append(node) + + def collect_compound(self) -> Definition: + """Collect a compound definition such as a struct.""" + path = os.path.join(self._doxyxml_dir, 'structEmscriptenWrapper.xml') + with open(path) as f: + xml = ElementTree.parse(f) + node = xml.find('compounddef') + d = Definition('EmscriptenWrapper', node=node) + d.desc = get_description(node) + d.members = [] + for m in \ + node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \ + node.findall('sectiondef[@kind="public-static-func"]/memberdef') + \ + node.findall('sectiondef[@kind="public-func"]/memberdef'): + name = m.find('name').text + # Doxygen incorrectly classifies members of private unnamed unions as + # public members of the containing class. + if name.endswith('_'): + continue + desc = get_description(m) + is_static = m.get('static') == 'yes' + if len(desc) == 0: + continue + kind = m.get('kind') + member = Definition(name if name else '', kind=kind, is_member=True) + t = m.find('type') + member.type = convert_type(t) + if kind == 'function': + member.params = convert_params(m) + member.desc = desc + member.is_static = is_static + d.members.append(member) + return d + + def collect(self, _identifier: str, _config: Mapping[str, Any]) -> Definition: + return self.collect_compound() + + def render(self, d: Definition, config: dict) -> str: + if d.id is not None: + self.do_heading('', 0, id=d.id) + text = '
\n' + text += render_decl(d) + text += '
\n' + text += doxyxml2html(d.desc) + if d.members is not None: + for m in d.members: + text += self.render(m, config) + text += '
\n' + text += '
\n' + return text + + +def get_handler( + theme: str, + custom_templates: Optional[str] = None, + **_config: Any +) -> EmscriptenHandler: + """Return an instance of `EmscriptenHandler`. + + Arguments: + theme: The theme to use when rendering contents. + custom_templates: Directory containing custom templates. + **_config: Configuration passed to the handler. + """ + return EmscriptenHandler(theme=theme, custom_templates=custom_templates) diff --git a/scripts/python/mkdocstrings_handlers/emscripten/templates/README b/scripts/python/mkdocstrings_handlers/emscripten/templates/README new file mode 100644 index 00000000..7a299fe9 --- /dev/null +++ b/scripts/python/mkdocstrings_handlers/emscripten/templates/README @@ -0,0 +1 @@ +mkdocstrings requires a handler to have a templates directory. \ No newline at end of file diff --git a/scripts/python/mkdocstrings_handlers/python/__init__.py b/scripts/python/mkdocstrings_handlers/python/__init__.py new file mode 100644 index 00000000..e8c5e28a --- /dev/null +++ b/scripts/python/mkdocstrings_handlers/python/__init__.py @@ -0,0 +1,264 @@ +# A basic mkdocstrings handler for {fmt}. +# Copyright (c) 2012 - present, Victor Zverovich +# https://github.com/fmtlib/fmt/blob/master/LICENSE + +import os +import re +import xml.etree.ElementTree as ElementTree + +from pathlib import Path +from subprocess import CalledProcessError, PIPE, Popen, STDOUT +from typing import Any, List, Mapping, Optional + +from mkdocstrings.handlers.base import BaseHandler + + +class Definition: + """A definition extracted by Doxygen.""" + + def __init__( + self, + name: str, + kind: Optional[str] = None, + node: Optional[ElementTree.Element] = None, + is_member: bool = False + ): + self.name = name + self.kind = kind if kind is not None else node.get('kind') + self.id = name if not is_member else None + self.desc = None + self.is_static = False + self.members = None + self.params = None + self.type = None + + +# A map from Doxygen to HTML tags. +tag_map = { + 'bold': 'b', + 'computeroutput': 'code', + 'emphasis': 'em', + 'itemizedlist': 'ul', + 'listitem': 'li', + 'para': 'p', + 'programlisting': 'pre', + 'verbatim': 'pre', +} + +# A map from Doxygen tags to text. +tag_text_map = { + 'codeline': '', + 'highlight': '', + 'sp': ' ' +} + + +def escape_html(s: str) -> str: + return s.replace("<", "<") + + +def doxyxml2html(nodes: List[ElementTree.Element]): + out = '' + for n in nodes: + tag = tag_map.get(n.tag) + if not tag: + out += tag_text_map[n.tag] + out += '<' + tag + '>' if tag else '' + out += '' if tag == 'pre' else '' + if n.text: + out += escape_html(n.text) + out += doxyxml2html(list(n)) + out += '' if tag == 'pre' else '' + out += '' if tag else '' + if n.tail: + out += n.tail + return out + + +def get_description(node: ElementTree.Element) -> List[ElementTree.Element]: + return node.findall('briefdescription/para') + \ + node.findall('detaileddescription/para') + + +def normalize_type(t: str) -> str: + normalized_type = t.replace('< ', '<').replace(' >', '>').replace(' &', '&') + return normalized_type.replace(' *', '*').replace('* ', '*') + + +def remove_namespaces_suffix(d: str) -> str: + return re.sub(r'.*::', '', d) + + +def convert_type(t: ElementTree.Element) -> Optional[str]: + if t is None: + return None + result = t.text if t.text else '' + for ref in t: + result += ref.text + if ref.tail: + result += ref.tail + result += t.tail.strip() + return normalize_type(result) + + +def convert_params(func: ElementTree.Element) -> List[Definition]: + params = [] + for p in func.findall('param'): + name = p.find('defname').text if p.find('defname') is not None else p.find('declname').text + d = Definition(name, 'param') + d.type = convert_type(p.find('type')) + params.append(d) + return params + + +def render_param(param: Definition) -> str: + return param.type + (f' {param.name}' if len(param.name) > 0 else '') + + +def render_decl(d: Definition) -> str: + text = '' + if d.id is not None: + text += f'\n' + text += '
'
+    text += '
' + if d.kind == 'function': + if d.is_static: + text += '@staticmethod\n' + text += d.name + if d.params is not None: + params = ', '.join([ + "{}{}{}".format( + p.type if p.type and p.type != 'self' else '', + ' ' if p.type != 'self' and p.type != '*' else '', + p.name) + for p in d.params]) + text += '(' + escape_html(params) + ')' + if len(d.type) > 0: + text += ' -> ' + escape_html(d.type) + elif d.kind == 'variable': + text += d.name + ': ' + escape_html(d.type) + else: + text += d.kind + ' ' + remove_namespaces_suffix(d.name) + + text += '
' + text += '
\n' + if d.id is not None: + text += f'
\n' + return text + + +class PythonHandler(BaseHandler): + def __init__(self, **kwargs: Any) -> None: + super().__init__(handler='python', **kwargs) + + headers = [ + '__init__.py' + ] + + # Run doxygen. + cmd = ['doxygen', '-'] + scripts_dir = Path(__file__).parents[3] + top_dir = os.path.dirname(scripts_dir) + include_dir = os.path.join(top_dir, 'python', 'module', 'cqasm', 'v3x') + self._ns2doxyxml = {} + build_dir = os.path.join(top_dir, 'build', 'Release', 'doc', 'python') + os.makedirs(build_dir, exist_ok=True) + self._doxyxml_dir = os.path.join(build_dir, 'doxyxml') + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + _, _ = p.communicate(input=r''' + PROJECT_NAME = libqasm + GENERATE_XML = YES + GENERATE_LATEX = NO + GENERATE_HTML = NO + INPUT = {0} + XML_OUTPUT = {1} + QUIET = YES + AUTOLINK_SUPPORT = NO + MACRO_EXPANSION = YES + PREDEFINED = _WIN32=1 \ + __linux__=1 \ + ''' + .format(' '.join([os.path.join(include_dir, h) for h in headers]), self._doxyxml_dir) + .encode('utf-8') + ) + if p.returncode != 0: + raise CalledProcessError(p.returncode, cmd) + + # Merge all file-level XMLs into one to simplify search. + self._file_doxyxml = None + for h in headers: + filename = h.replace("_", "__") + filename = filename.replace(".py", "_8py.xml") + with open(os.path.join(self._doxyxml_dir, filename)) as f: + doxyxml = ElementTree.parse(f) + if self._file_doxyxml is None: + self._file_doxyxml = doxyxml + continue + root = self._file_doxyxml.getroot() + for node in doxyxml.getroot(): + root.append(node) + + def collect_compound(self) -> Definition: + """Collect a compound definition such as a struct.""" + path = os.path.join(self._doxyxml_dir, 'classcqasm_1_1v3x_1_1Analyzer.xml') + with open(path) as f: + xml = ElementTree.parse(f) + node = xml.find('compounddef') + d = Definition('cqasm::v3x::Analyzer', node=node) + d.desc = get_description(node) + d.members = [] + for m in \ + node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \ + node.findall('sectiondef[@kind="public-static-func"]/memberdef') + \ + node.findall('sectiondef[@kind="public-func"]/memberdef'): + name = m.find('name').text + # Doxygen incorrectly classifies members of private unnamed unions as + # public members of the containing class. + if name.endswith('_'): + continue + desc = get_description(m) + is_static = m.get('static') == 'yes' + if len(desc) == 0: + continue + kind = m.get('kind') + member = Definition(name if name else '', kind=kind, is_member=True) + t = m.find('type') + member.type = convert_type(t) + if kind == 'function': + member.params = convert_params(m) + member.desc = desc + member.is_static = is_static + d.members.append(member) + return d + + def collect(self, _identifier: str, _config: Mapping[str, Any]) -> Definition: + return self.collect_compound() + + def render(self, d: Definition, config: dict) -> str: + if d.id is not None: + self.do_heading('', 0, id=d.id) + text = '
\n' + text += render_decl(d) + text += '
\n' + text += doxyxml2html(d.desc) + if d.members is not None: + for m in d.members: + text += self.render(m, config) + text += '
\n' + text += '
\n' + return text + + +def get_handler( + theme: str, + custom_templates: Optional[str] = None, + **_config: Any +) -> PythonHandler: + """Return an instance of `PythonHandler`. + + Arguments: + theme: The theme to use when rendering contents. + custom_templates: Directory containing custom templates. + **_config: Configuration passed to the handler. + """ + return PythonHandler(theme=theme, custom_templates=custom_templates) diff --git a/scripts/python/mkdocstrings_handlers/python/templates/README b/scripts/python/mkdocstrings_handlers/python/templates/README new file mode 100644 index 00000000..7a299fe9 --- /dev/null +++ b/scripts/python/mkdocstrings_handlers/python/templates/README @@ -0,0 +1 @@ +mkdocstrings requires a handler to have a templates directory. \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22951f7b..b2c1335b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -211,6 +211,7 @@ endif() if(LIBQASM_BUILD_EMSCRIPTEN) set(antlr4-runtime_INCLUDE_DIRS "${antlr4-runtime_SOURCE_DIR}/runtime/Cpp/runtime/src") set(fmt_INCLUDE_DIRS "${fmt_SOURCE_DIR}/include") + set(range-v3_INCLUDE_DIRS "${range-v3_SOURCE_DIR}/include") set(tree-gen_INCLUDE_DIRS "${tree-gen_SOURCE_DIR}/include") endif() target_include_directories(cqasm-lib-obj @@ -223,6 +224,7 @@ target_include_directories(cqasm-lib-obj PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/../include/v3x" PRIVATE "${antlr4-runtime_INCLUDE_DIRS}/" PRIVATE "${fmt_INCLUDE_DIRS}/" + PRIVATE "${range-v3_INCLUDE_DIRS}/" PRIVATE "${tree-gen_INCLUDE_DIRS}/" ) @@ -302,6 +304,7 @@ message(STATUS " CMAKE_CURRENT_BINARY_DIR/../include/: ${CMAKE_CURRENT_BINARY_DIR}/../include/\n" " antlr4-runtime_INCLUDE_DIRS: ${antlr4-runtime_INCLUDE_DIRS}\n" " fmt_INCLUDE_DIRS: ${fmt_INCLUDE_DIRS}\n" + " range-v3_INCLUDE_DIRS: ${range-v3_INCLUDE_DIRS}\n" " tree-gen_INCLUDE_DIRS: ${tree-gen_INCLUDE_DIRS}\n" ) diff --git a/src/v3x/cqasm-types.cpp b/src/v3x/cqasm-types.cpp index d1c65094..ba64a824 100644 --- a/src/v3x/cqasm-types.cpp +++ b/src/v3x/cqasm-types.cpp @@ -18,7 +18,7 @@ namespace cqasm::v3x::types { * - i = int * - f = float * - V = qubit array - * - W = bit array + * - W = bit array */ Type from_spec(const char c) { switch (c) { From f02d94bc7bb4ec7a45921bda416481823c899bca Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 19 Aug 2024 16:29:03 +0200 Subject: [PATCH 15/22] Update tree-gen version to 1.0.8. Fix compilation errors due to updating to fmt version 11.0.2. --- conanfile.py | 4 ++-- src/cqasm-version.cpp | 2 +- src/v3x/cqasm-types.cpp | 2 ++ src/v3x/cqasm-values.cpp | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/conanfile.py b/conanfile.py index 47d77796..6a322cb5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -88,7 +88,7 @@ def layout(self): self.cpp.build.libdirs = ["."] def build_requirements(self): - self.tool_requires("tree-gen/1.0.7") + self.tool_requires("tree-gen/1.0.8") if self.settings.arch != "armv8": self.tool_requires("zulu-openjdk/21.0.4") if self.settings.arch == "wasm": @@ -110,7 +110,7 @@ def validate(self): def requirements(self): self.requires("fmt/11.0.2", transitive_headers=True) self.requires("range-v3/0.12.0", transitive_headers=True) - self.requires("tree-gen/1.0.7", transitive_headers=True, transitive_libs=True) + self.requires("tree-gen/1.0.8", transitive_headers=True, transitive_libs=True) if not self.settings.arch == "wasm": self.requires("antlr4-cppruntime/4.13.1", transitive_headers=True) diff --git a/src/cqasm-version.cpp b/src/cqasm-version.cpp index a219d28f..fb34a5f9 100644 --- a/src/cqasm-version.cpp +++ b/src/cqasm-version.cpp @@ -5,7 +5,7 @@ #include "cqasm-version.hpp" #include - +#include #include namespace cqasm::version { diff --git a/src/v3x/cqasm-types.cpp b/src/v3x/cqasm-types.cpp index d1c65094..1d367f95 100644 --- a/src/v3x/cqasm-types.cpp +++ b/src/v3x/cqasm-types.cpp @@ -5,6 +5,8 @@ #include "v3x/cqasm-types.hpp" #include +#include + namespace cqasm::v3x::types { diff --git a/src/v3x/cqasm-values.cpp b/src/v3x/cqasm-values.cpp index f973acd7..d0dfc753 100644 --- a/src/v3x/cqasm-values.cpp +++ b/src/v3x/cqasm-values.cpp @@ -4,9 +4,9 @@ #include "v3x/cqasm-values.hpp" -#include - #include +#include +#include #include #include // runtime_error From 11ae4042f4e0a150ca6bbd74d405172e6705f5e3 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 19 Aug 2024 16:30:46 +0200 Subject: [PATCH 16/22] Remove unused headers. --- src/v3x/cqasm-values.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/v3x/cqasm-values.cpp b/src/v3x/cqasm-values.cpp index d0dfc753..7d96d559 100644 --- a/src/v3x/cqasm-values.cpp +++ b/src/v3x/cqasm-values.cpp @@ -4,7 +4,6 @@ #include "v3x/cqasm-values.hpp" -#include #include #include #include From f457f2e380049546df1e080bdcaf637278596312 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 19 Aug 2024 16:52:10 +0200 Subject: [PATCH 17/22] Fix compilation errors due to updating to fmt version 11.0.2. --- test/v3x/cpp/parsing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/v3x/cpp/parsing.cpp b/test/v3x/cpp/parsing.cpp index 2a43815b..1682c8ef 100644 --- a/test/v3x/cpp/parsing.cpp +++ b/test/v3x/cpp/parsing.cpp @@ -1,9 +1,9 @@ #include "parsing.hpp" +#include #include +#include #include - -#include #include #include "test-register.hpp" From a90e46e80a6cb621231b473a42f19a3ac9a4b0c1 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 19 Aug 2024 17:05:51 +0200 Subject: [PATCH 18/22] Update ANTLR4 version also in generate_antlr_parser.py. --- scripts/generate_antlr_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_antlr_parser.py b/scripts/generate_antlr_parser.py index d46de138..153cd782 100644 --- a/scripts/generate_antlr_parser.py +++ b/scripts/generate_antlr_parser.py @@ -21,7 +21,7 @@ def print_usage(): class AntlrJarFileDownloader: - file_version = "4.13.0" + file_version = "4.13.1" file_name = "antlr4-{}-complete.jar".format(file_version) file_path = "{}/{}".format(tempfile.gettempdir(), file_name) From ab4114a61617589dbb2aa5005ea18f55b4c131f9 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 19 Aug 2024 17:16:48 +0200 Subject: [PATCH 19/22] Revert update of zulu-openjdk version (from 21.0.4 back to 21.0.1). --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 6a322cb5..b7be5cfe 100644 --- a/conanfile.py +++ b/conanfile.py @@ -90,7 +90,7 @@ def layout(self): def build_requirements(self): self.tool_requires("tree-gen/1.0.8") if self.settings.arch != "armv8": - self.tool_requires("zulu-openjdk/21.0.4") + self.tool_requires("zulu-openjdk/21.0.1") if self.settings.arch == "wasm": self.tool_requires("emsdk/3.1.50") if self._should_build_test: From fdb7039dd417e12849567f3e9fb9c8845134259c Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 19 Aug 2024 11:57:14 +0200 Subject: [PATCH 20/22] [CQT-6] Parsing and analyzing the same file with a syntactic error can return different messages. --- res/v3x/parsing/version/vrsion/ast.golden.txt | 2 ++ res/v3x/parsing/version/vrsion/input.cq | 1 + test/v3x/python/test_v3x_analyzer.py | 12 ++++++++++++ 3 files changed, 15 insertions(+) create mode 100644 res/v3x/parsing/version/vrsion/ast.golden.txt create mode 100644 res/v3x/parsing/version/vrsion/input.cq diff --git a/res/v3x/parsing/version/vrsion/ast.golden.txt b/res/v3x/parsing/version/vrsion/ast.golden.txt new file mode 100644 index 00000000..897e0530 --- /dev/null +++ b/res/v3x/parsing/version/vrsion/ast.golden.txt @@ -0,0 +1,2 @@ +ERROR +Error at input.cq:1:1..7: mismatched input 'vrsion' expecting {NEW_LINE, ';', 'version'} diff --git a/res/v3x/parsing/version/vrsion/input.cq b/res/v3x/parsing/version/vrsion/input.cq new file mode 100644 index 00000000..916702a4 --- /dev/null +++ b/res/v3x/parsing/version/vrsion/input.cq @@ -0,0 +1 @@ +vrsion 3.0 diff --git a/test/v3x/python/test_v3x_analyzer.py b/test/v3x/python/test_v3x_analyzer.py index 7d270ef4..01d3ba2f 100644 --- a/test/v3x/python/test_v3x_analyzer.py +++ b/test/v3x/python/test_v3x_analyzer.py @@ -84,3 +84,15 @@ def test_analyze_string_returning_errors(self): errors = v3x_analyzer.analyze_string(program_str) expected_errors = ["Error at :1:24..25: index 3 out of range (size 3)"] self.assertEqual(errors, expected_errors) + + def test_to_json_vrsion(self): + """This test is used to check that a program with a syntactic error + produces the same error message both when parsing and analyzing.""" + # res/v3x/parsing/version/vrsion + program_str = "vrsion 3" + v3x_analyzer = cq.Analyzer() + actual_parse_json = v3x_analyzer.parse_string_to_json(program_str) + actual_analyze_json = v3x_analyzer.analyze_string_to_json(program_str) + self.assertEqual(actual_parse_json, actual_analyze_json) + expected_json = '''{"errors":[{"range":{"start":{"line":1,"character":1},"end":{"line":1,"character":7}},"message":"mismatched input 'vrsion' expecting {NEW_LINE, ';', 'version'}","severity":1}]}''' + self.assertEqual(actual_parse_json, expected_json) From 27fc73712d3fafaf54995ddba8a132fb1f8712e6 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:20:14 +0200 Subject: [PATCH 21/22] [CQT-135] Update README. --- README.md | 7 +++---- doc/mkdocs.yml | 1 + doc/src/api/cpp.md | 2 +- doc/src/api/emscripten.md | 4 ++-- doc/src/api/python.md | 2 +- doc/src/dev-guide/dev-guide.md | 9 +++++---- doc/src/dev-guide/emscripten.md | 5 +++-- doc/src/user-guide/cpp.md | 7 +++---- doc/src/user-guide/docker.md | 13 +++++++++++++ doc/src/user-guide/emscripten.md | 2 +- doc/src/user-guide/python.md | 2 +- doc/src/user-guide/user-guide.md | 3 ++- src/CMakeLists.txt | 10 +++++----- 13 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 doc/src/user-guide/docker.md diff --git a/README.md b/README.md index b59e686b..cf00a852 100644 --- a/README.md +++ b/README.md @@ -95,13 +95,12 @@ if __name__ == "__main__": ## Documentation -The [libQASM documentation](https://QuTech-Delft.github.io/OpenSquirrel/) is hosted through GitHub Pages. +The [libQASM documentation](https://QuTech-Delft.github.io/libqasm/) is hosted through GitHub Pages. ## License -libQASM is licensed under the Apache License, Version 2.0. See -[LICENSE](https://github.com/QuTech-Delft/OpenSquirrel/blob/master/LICENSE.md) for the full -license text. +libQASM is licensed under the Apache License, Version 2.0. +See [LICENSE](https://github.com/QuTech-Delft/libqasm/blob/master/LICENSE.md) for the full license text. ## Authors diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml index 4f0585c4..0b1565e5 100644 --- a/doc/mkdocs.yml +++ b/doc/mkdocs.yml @@ -36,6 +36,7 @@ nav: - User Guide: - User Guide: user-guide/user-guide.md - C++: user-guide/cpp.md + - Docker: user-guide/docker.md - Emscripten: user-guide/emscripten.md - Python: user-guide/python.md - Developer Guide: diff --git a/doc/src/api/cpp.md b/doc/src/api/cpp.md index 19d82bc8..9b693fc9 100644 --- a/doc/src/api/cpp.md +++ b/doc/src/api/cpp.md @@ -1,4 +1,4 @@ -`libqasm` C++ API is defined in `include/v3x/cqasm-py.hpp`. +libQASM C++ API is defined in `include/v3x/cqasm-py.hpp`. The only exported class is `V3xAnalyzer`. This is actually a C++ class that works as a binding for accessing C++ code from Python. diff --git a/doc/src/api/emscripten.md b/doc/src/api/emscripten.md index 1b2d55d1..683ffa30 100644 --- a/doc/src/api/emscripten.md +++ b/doc/src/api/emscripten.md @@ -1,6 +1,6 @@ -`libqasm` is also deployed as an Emscripten binary with a Typescript frontend. +libQASM is also deployed as an Emscripten binary with a Typescript frontend. -`libqasm` Typescript API is defined in `emscripten/emscripten_wrapper.hpp`. +libQASM Typescript API is defined in `emscripten/emscripten_wrapper.hpp`. The only exported class is `EmscriptenWrapper`. This is actually a C++ class that works as a binding for accessing C++ code from Typescript. diff --git a/doc/src/api/python.md b/doc/src/api/python.md index b43061a6..0715a62a 100644 --- a/doc/src/api/python.md +++ b/doc/src/api/python.md @@ -1,4 +1,4 @@ -`libqasm` Python API is defined in `python/module/cqasm/v3x/__init__.py`. +libQASM Python API is defined in `python/module/cqasm/v3x/__init__.py`. The only exported class is `Analyzer`. This is actually a Python class that works as a binding for accessing C++ code from Python. diff --git a/doc/src/dev-guide/dev-guide.md b/doc/src/dev-guide/dev-guide.md index 4b84e0fb..2cdaa251 100644 --- a/doc/src/dev-guide/dev-guide.md +++ b/doc/src/dev-guide/dev-guide.md @@ -28,6 +28,7 @@ Build outputs may go into: - `git` - `Python` 3.x plus `pip`, with the following package: - `conan` >= 2.0 +- `SWIG` ### ARM builds @@ -48,7 +49,7 @@ For the time being, we install Java manually for this platform. ## Build -This version of `libqasm` can only be compiled via the Conan package manager. +This version of libQASM can only be compiled via the Conan package manager. You will need to create a default profile before using it for the first time. The installation of dependencies, as well as the compilation, can be done in one go. @@ -63,14 +64,14 @@ conan build . -pr:a=conan/profiles/tests-debug -b missing !!! note - the `conan profile` command only has to be run only once, and not before every build. - - the `conan build` command is building `libqasm` in Debug mode with tests using the `tests-debug` profile. + - the `conan build` command is building libQASM in Debug mode with tests using the `tests-debug` profile. - the `-b missing` parameter asks `conan` to build packages from sources in case it cannot find the binary packages for the current configuration (platform, OS, compiler, build type...). ### Profiles A group of predefined profiles is provided under the `conan/profiles` folder. -They follow the `[tests|docs](-build_type)(-compiler)(-os)(-arch)[-shared]` naming convention: +They follow the `[tests-|docs-](build_type)(-compiler)(-os)(-arch)[-shared]` naming convention: - `tests`: if tests are being built. - `docs`: if docs are being built. @@ -113,7 +114,7 @@ python3 mkdocs serve ## Docker -This tests the library in a container with the bare minimum requirements for `libqasm`. +This tests the library in a container with the bare minimum requirements for libQASM. ```shell docker build . diff --git a/doc/src/dev-guide/emscripten.md b/doc/src/dev-guide/emscripten.md index 05c9b26c..1b151b41 100644 --- a/doc/src/dev-guide/emscripten.md +++ b/doc/src/dev-guide/emscripten.md @@ -10,8 +10,9 @@ The output of this build lives in `build/Release/emscripten`: - `cqasm_emscripten.js`. - `cqasm_emscripten.wasm`. -Note that `cqasm_emscripten.js` is an ES6 module. -Its extension has to be renamed to `.mjs` before using it from Typescript code. +!!! note + `cqasm_emscripten.js` is an ES6 module. + Its extension has to be renamed to `.mjs` before using it from Typescript code. You can test the Emscripten binaries: diff --git a/doc/src/user-guide/cpp.md b/doc/src/user-guide/cpp.md index df3bd269..45ee4fd4 100644 --- a/doc/src/user-guide/cpp.md +++ b/doc/src/user-guide/cpp.md @@ -1,4 +1,4 @@ -`libqasm` can be requested as a Conan package from a `conanfile.py`. +libQASM can be requested as a Conan package from a `conanfile.py`. ``` def build_requirements(self): @@ -13,9 +13,8 @@ And then linked against from a `CMakeLists.txt`: target_link_libraries( PUBLIC libqasm::libqasm) ``` -Note that the following dependency is required for `libqasm` to build: - -* `Java JRE` >= 11 +!!! note + You will need to have `Java JRE` >= 11 installed in case Conan needs to build libQASM. **Example**: diff --git a/doc/src/user-guide/docker.md b/doc/src/user-guide/docker.md new file mode 100644 index 00000000..170cf29a --- /dev/null +++ b/doc/src/user-guide/docker.md @@ -0,0 +1,13 @@ +libQASM can be tested in a container with the bare minimum requirements. + +```shell +docker build . +``` + +!!! note + The above might fail on Windows due to the `autocrlf` transformation that git does. + If you are having trouble with this just create new clone of this repository using: + + ``` + git clone --config core.autocrlf=input git@github.com:QuTech-Delft/libqasm.git + ``` diff --git a/doc/src/user-guide/emscripten.md b/doc/src/user-guide/emscripten.md index 1b9a344f..5f17642d 100644 --- a/doc/src/user-guide/emscripten.md +++ b/doc/src/user-guide/emscripten.md @@ -1,4 +1,4 @@ -`libqasm` can be used from a web environment via a Typescript frontend. +libQASM can be used from a web environment via a Typescript frontend. Emscripten API only allows to input a cQASM program via a string and always returns a JSON string output. diff --git a/doc/src/user-guide/python.md b/doc/src/user-guide/python.md index 6b12de74..314195fd 100644 --- a/doc/src/user-guide/python.md +++ b/doc/src/user-guide/python.md @@ -1,4 +1,4 @@ -`libqasm` can be imported as a Python package. +libQASM can be imported as a Python package. **Example**: diff --git a/doc/src/user-guide/user-guide.md b/doc/src/user-guide/user-guide.md index 178e4269..7a53665d 100644 --- a/doc/src/user-guide/user-guide.md +++ b/doc/src/user-guide/user-guide.md @@ -1,5 +1,6 @@ -`libqasm` can be used from: +libQASM can be used from: - C++ projects (as a [Conan package](https://conan.io/center/recipes/libqasm)). - Python projects (as a [Python package](https://pypi.org/project/libqasm/)). - Emscripten projects (via a Typescript frontend). +- Docker. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b2c1335b..07bc4d3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -74,12 +74,8 @@ if(NOT range-v3_FOUND) add_library(range-v3::range-v3 ALIAS range-v3) endif() -# tree-gen executable -find_package(tree-gen REQUIRED) -find_program(TREE_GEN_EXECUTABLE tree-gen REQUIRED) -message(STATUS "TREE_GEN_EXECUTABLE: ${TREE_GEN_EXECUTABLE}") - # tree-gen library +find_package(tree-gen REQUIRED) if(NOT tree-gen_FOUND OR LIBQASM_BUILD_EMSCRIPTEN) message(STATUS "Fetching tree-gen") FetchContent_Declare(tree-gen @@ -90,6 +86,10 @@ if(NOT tree-gen_FOUND OR LIBQASM_BUILD_EMSCRIPTEN) FetchContent_MakeAvailable(tree-gen) endif() +# tree-gen executable +find_program(TREE_GEN_EXECUTABLE tree-gen REQUIRED) +message(STATUS "TREE_GEN_EXECUTABLE: ${TREE_GEN_EXECUTABLE}") + #------------------------------------------------------------------------------- # cQASM common code generation and inclusion #------------------------------------------------------------------------------- From fc658f92a5c802a4e6e5d5f3835cddb48e2cb6c5 Mon Sep 17 00:00:00 2001 From: rturrado Date: Mon, 26 Aug 2024 16:04:34 +0200 Subject: [PATCH 22/22] Revert unwanted changes. --- .github/workflows/assets.yml | 20 ++------------------ setup.py | 19 ------------------- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml index 5d13a1f4..43cbd132 100644 --- a/.github/workflows/assets.yml +++ b/.github/workflows/assets.yml @@ -13,33 +13,17 @@ jobs: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: - fail-fast: false matrix: # macos-13 is an x64 runner, macos-14 is an arm64 runner os: [ubuntu-latest, windows-latest, macos-13, macos-14] steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up QEMU - if: runner.os == 'Linux' - uses: docker/setup-qemu-action@v3 - with: - platforms: all - - name: Build wheels - uses: pypa/cibuildwheel@v2.18.1 - env: - # Configure cibuildwheel to build native archs ('auto'), and some emulated ones - CIBW_ARCHS_LINUX: auto aarch64 - uses: actions/setup-python@v5 - name: Install cibuildwheel - run: | - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - python -m pip install cibuildwheel==2.18.1 - dnf install -y docker + run: python -m pip install cibuildwheel==2.18.1 - name: Build wheels - run: | - export PATH="/opt/python/${{ matrix.cpython_version }}/bin:$PATH" - python -m cibuildwheel --output-dir wheelhouse + run: python -m cibuildwheel --output-dir wheelhouse - uses: actions/upload-artifact@v4 with: name: cibw-wheels-${{ matrix.os }} diff --git a/setup.py b/setup.py index 83baa6f8..18968f8f 100755 --- a/setup.py +++ b/setup.py @@ -30,25 +30,6 @@ def get_version(verbose=False): return version - -# TODO: I had to copy-paste get_version from version.py here -# because I couldn't get 'from version import get_version' work with pyproject.toml -def get_version(verbose=False): - """Extract version information from source code""" - inc_dir = root_dir + os.sep + "include" # C++ include directory - matcher = re.compile('static const char \*version\{ "(.*)" \}') - version = None - with open(os.path.join(inc_dir, "version.hpp"), "r") as f: - for ln in f: - m = matcher.match(ln) - if m: - version = m.group(1) - break - if verbose: - print("get_version: %s" % version) - return version - - root_dir = os.getcwd() # root of the repository src_dir = root_dir + os.sep + 'src' # C++ source directory pysrc_dir = root_dir + os.sep + 'python' # Python source files