main: initial library
Some checks failed
Some checks failed
This commit is contained in:
116
.gitea/workflows/ci.yml
Normal file
116
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
style-check:
|
||||||
|
name: Code Style (autopep8)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Python 3.12
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install PDM + autopep8
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pdm autopep8
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pdm install --dev
|
||||||
|
|
||||||
|
- name: Run autopep8 style check
|
||||||
|
run: |
|
||||||
|
autopep8 --recursive --diff --exit-code --ignore E501 src
|
||||||
|
continue-on-error: false
|
||||||
|
|
||||||
|
|
||||||
|
tests:
|
||||||
|
name: Tests (Python ${{ matrix.python-version }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.12", "3.13", "3.14"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install PDM
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pdm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pdm install --dev
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: pdm run test
|
||||||
|
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
name: Coverage (Python 3.12)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: tests
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout (with parent)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Install Python 3.12
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install PDM
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pdm
|
||||||
|
|
||||||
|
- name: Run coverage on parent commit
|
||||||
|
run: |
|
||||||
|
git checkout HEAD^
|
||||||
|
pdm install --dev
|
||||||
|
pdm run coverage
|
||||||
|
pdm run python -m coverage json -o /tmp/parent-coverage.json
|
||||||
|
git checkout -
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pdm install --dev
|
||||||
|
|
||||||
|
- name: Run coverage on current commit
|
||||||
|
run: |
|
||||||
|
pdm run coverage
|
||||||
|
pdm run python -m coverage json -o /tmp/current-coverage.json
|
||||||
|
|
||||||
|
- name: Check coverage did not decrease
|
||||||
|
run: |
|
||||||
|
pdm run python -c "
|
||||||
|
import json, sys
|
||||||
|
with open('/tmp/parent-coverage.json') as f:
|
||||||
|
parent = json.load(f)['totals']['percent_covered']
|
||||||
|
with open('/tmp/current-coverage.json') as f:
|
||||||
|
current = json.load(f)['totals']['percent_covered']
|
||||||
|
print(f'Parent coverage: {parent:.2f}%')
|
||||||
|
print(f'Current coverage: {current:.2f}%')
|
||||||
|
if current < parent:
|
||||||
|
print(f'ERROR: Coverage decreased by {parent - current:.2f}%')
|
||||||
|
sys.exit(1)
|
||||||
|
print(f'OK: Coverage maintained or improved by {current - parent:.2f}%')
|
||||||
|
"
|
||||||
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.cache/
|
||||||
|
.coverage
|
||||||
|
.coverage-report/
|
||||||
|
.pdm.lock/
|
||||||
|
.vscode/
|
||||||
|
**/__pycache__/
|
||||||
|
**/*.pyc
|
||||||
|
.pdm-build/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
pytest_cache/
|
||||||
|
.venv/
|
||||||
|
var/
|
||||||
|
.env
|
||||||
19
.style.yapf
Normal file
19
.style.yapf
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[style]
|
||||||
|
based_on_style = pep8
|
||||||
|
column_limit = 240
|
||||||
|
blank_line_before_class_docstring = False
|
||||||
|
blank_lines_around_top_level_definition = 2
|
||||||
|
blank_line_before_nested_class_or_def = False
|
||||||
|
continuation_align_style = Fixed
|
||||||
|
continuation_indent_width = 4
|
||||||
|
dedent_closing_brackets = True
|
||||||
|
align_closing_bracket_with_visual_indent = False
|
||||||
|
split_before_closing_bracket = True
|
||||||
|
split_before_first_argument = False
|
||||||
|
coalesce_brackets = True
|
||||||
|
each_dict_entry_on_separate_line = True
|
||||||
|
spaces_around_dict_delimiters = True
|
||||||
|
spaces_before_comment = 1
|
||||||
|
split_all_comma_separated_values = True
|
||||||
|
split_before_dict_set_generator = True
|
||||||
|
allow_split_before_dict_value = False
|
||||||
47
README.md
Normal file
47
README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# dragiyski-vulkan-binding
|
||||||
|
|
||||||
|
Python bindings for the [Vulkan](https://www.vulkan.org/) graphics and compute API, generated on-demand from the official [Vulkan XML registry](https://github.com/KhronosGroup/Vulkan-Docs).
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The binding is *lazy*: the module structure is set up at import time, but individual symbols (types, constants, functions, macros) are resolved and bound only when first accessed. This keeps startup overhead minimal regardless of how large the registry is.
|
||||||
|
|
||||||
|
Bindings are derived from `dragiyski-vulkan-registry`, which parses the official Vulkan XML registry and exposes its taxonomy. The binding layer translates that taxonomy into Python-callable objects backed by `ctypes`, including:
|
||||||
|
|
||||||
|
- **Macros** — version utility macros such as `VK_MAKE_VERSION`, `VK_MAKE_API_VERSION`, and their corresponding accessors, exposed as plain Python callables with matching signatures.
|
||||||
|
- **Constants** — scalar values such as `VK_HEADER_VERSION`, exposed as Python `int` or `float`.
|
||||||
|
- **Types** — Vulkan structs, unions, handles, and enums, mapped to `ctypes` equivalents.
|
||||||
|
- **Commands** — Vulkan API commands, loadable via the standard `vkGetInstanceProcAddr` / `vkGetDeviceProcAddr` mechanism.
|
||||||
|
- **Callbacks** — Vulkan callback function pointer types, wrapped as `ctypes.CFUNCTYPE` factories.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.12 or later
|
||||||
|
- [`pycparser`](https://github.com/eliben/pycparser) ≥ 2.23
|
||||||
|
- [`dragiyski-vulkan-registry`](https://git.dragiyski.org/dragiyski/vulkan-registry)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install dragiyski-vulkan-binding
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
import ctypes
|
||||||
|
import dragiyski.vulkan.binding as vulkan
|
||||||
|
|
||||||
|
version = vulkan.VK_MAKE_API_VERSION(0, 1, 3, 0)
|
||||||
|
print(vulkan.VK_HEADER_VERSION)
|
||||||
|
|
||||||
|
vulkan_version = ctypes.c_uint32()
|
||||||
|
vulkan.vkEnumerateInstanceVersion(ctypes.byref(vulkan_version))
|
||||||
|
print('.'.join([
|
||||||
|
str(vulkan.VK_API_VERSION_MAJOR(vulkan_version.value)),
|
||||||
|
str(vulkan.VK_API_VERSION_MINOR(vulkan_version.value)),
|
||||||
|
str(vulkan.VK_API_VERSION_PATCH(vulkan_version.value)),
|
||||||
|
]))
|
||||||
|
```
|
||||||
|
|
||||||
|
Symbols are resolved lazily on first attribute access, so importing the module itself is fast.
|
||||||
302
pdm.lock
generated
Normal file
302
pdm.lock
generated
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
# This file is @generated by PDM.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
groups = ["default", "dev"]
|
||||||
|
strategy = ["inherit_metadata"]
|
||||||
|
lock_version = "4.5.0"
|
||||||
|
content_hash = "sha256:303aec5e0f4d84b560289a07f002f0cc77a710d5c5175c76116669896742c757"
|
||||||
|
|
||||||
|
[[metadata.targets]]
|
||||||
|
requires_python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
summary = "Cross-platform colored terminal text."
|
||||||
|
groups = ["dev"]
|
||||||
|
marker = "sys_platform == \"win32\""
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.13.5"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "Code coverage measurement for Python"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"},
|
||||||
|
{file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"},
|
||||||
|
{file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.13.5"
|
||||||
|
extras = ["toml"]
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "Code coverage measurement for Python"
|
||||||
|
groups = ["dev"]
|
||||||
|
dependencies = [
|
||||||
|
"coverage==7.13.5",
|
||||||
|
"tomli; python_full_version <= \"3.11.0a6\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"},
|
||||||
|
{file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"},
|
||||||
|
{file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"},
|
||||||
|
{file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"},
|
||||||
|
{file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"},
|
||||||
|
{file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dragiyski-vulkan-registry"
|
||||||
|
version = "0.0.1"
|
||||||
|
requires_python = ">=3.12"
|
||||||
|
url = "https://git.dragiyski.org/dragiyski/vulkan-registry/releases/download/v0.0.1/dragiyski_vulkan_registry-0.0.1-py3-none-any.whl"
|
||||||
|
summary = "Extracts and parses the Vulkan API registry XML file, providing a Python interface to access the data."
|
||||||
|
groups = ["default"]
|
||||||
|
files = [
|
||||||
|
{file = "dragiyski_vulkan_registry-0.0.1-py3-none-any.whl", hash = "sha256:b77dc42606d2a06db1ca4b00a2de46c590c0a42b75082115eda2bb09e9492005"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "brain-dead simple config-ini parsing"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
|
||||||
|
{file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "26.2"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Core utilities for Python packages"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"},
|
||||||
|
{file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
requires_python = ">=3.9"
|
||||||
|
summary = "plugin and hook calling mechanisms for python"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
|
||||||
|
{file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycparser"
|
||||||
|
version = "3.0"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "C parser in Python"
|
||||||
|
groups = ["default"]
|
||||||
|
files = [
|
||||||
|
{file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"},
|
||||||
|
{file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.20.0"
|
||||||
|
requires_python = ">=3.9"
|
||||||
|
summary = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
|
||||||
|
{file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "9.0.3"
|
||||||
|
requires_python = ">=3.10"
|
||||||
|
summary = "pytest: simple powerful testing with Python"
|
||||||
|
groups = ["dev"]
|
||||||
|
dependencies = [
|
||||||
|
"colorama>=0.4; sys_platform == \"win32\"",
|
||||||
|
"exceptiongroup>=1; python_version < \"3.11\"",
|
||||||
|
"iniconfig>=1.0.1",
|
||||||
|
"packaging>=22",
|
||||||
|
"pluggy<2,>=1.5",
|
||||||
|
"pygments>=2.7.2",
|
||||||
|
"tomli>=1; python_version < \"3.11\"",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"},
|
||||||
|
{file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "7.1.0"
|
||||||
|
requires_python = ">=3.9"
|
||||||
|
summary = "Pytest plugin for measuring coverage."
|
||||||
|
groups = ["dev"]
|
||||||
|
dependencies = [
|
||||||
|
"coverage[toml]>=7.10.6",
|
||||||
|
"pluggy>=1.2",
|
||||||
|
"pytest>=7",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"},
|
||||||
|
{file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"},
|
||||||
|
]
|
||||||
49
pyproject.toml
Normal file
49
pyproject.toml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["pdm-backend", "build"]
|
||||||
|
build-backend = "pdm.backend"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "dragiyski-vulkan-binding"
|
||||||
|
version = "0.0.1"
|
||||||
|
description = "Lazy Vulkan bindings for Python, generated from the Vulkan XML registry."
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [ { name = "Plamen Dragiyski", email = "plamen@dragiyski.org" } ]
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
"pycparser>=2.23",
|
||||||
|
"dragiyski-vulkan-registry @ https://git.dragiyski.org/dragiyski/vulkan-registry/releases/download/v0.0.1/dragiyski_vulkan_registry-0.0.1-py3-none-any.whl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pytest>=9.0.1",
|
||||||
|
"pytest-cov>=7.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pdm]
|
||||||
|
package-dir = "src"
|
||||||
|
|
||||||
|
[tool.pdm.build]
|
||||||
|
includes = ["src"]
|
||||||
|
excludes = ["tests"]
|
||||||
|
|
||||||
|
[tool.pdm.scripts]
|
||||||
|
test = "pytest"
|
||||||
|
coverage = "pytest --cov=dragiyski.vulkan --cov-report=html"
|
||||||
|
|
||||||
|
[tool.pytest]
|
||||||
|
minversion="0.9"
|
||||||
|
testpaths = [
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
|
python_files = [
|
||||||
|
"test_*.py"
|
||||||
|
]
|
||||||
|
pythonpath = ["src"]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
branch = true
|
||||||
|
|
||||||
|
[tool.coverage.html]
|
||||||
|
directory = ".coverage-report/html"
|
||||||
6
src/dragiyski/vulkan/binding/__init__.py
Normal file
6
src/dragiyski/vulkan/binding/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import sys
|
||||||
|
from ._implementation import Binding
|
||||||
|
from dragiyski.vulkan.registry import taxonomy
|
||||||
|
from ._lazy import create_lazy_module
|
||||||
|
|
||||||
|
sys.modules[__name__] = create_lazy_module(sys.modules[__name__], Binding)(taxonomy)
|
||||||
35307
src/dragiyski/vulkan/binding/__init__.pyi
Normal file
35307
src/dragiyski/vulkan/binding/__init__.pyi
Normal file
File diff suppressed because it is too large
Load Diff
990
src/dragiyski/vulkan/binding/_implementation/__init__.py
Normal file
990
src/dragiyski/vulkan/binding/_implementation/__init__.py
Normal file
@@ -0,0 +1,990 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
|
from collections.abc import Callable, Collection, Mapping, Sequence
|
||||||
|
import ctypes
|
||||||
|
import inspect
|
||||||
|
import pycparser.c_ast as c_ast
|
||||||
|
from pycparser.c_parser import ParseError
|
||||||
|
from ...registry import Taxonomy
|
||||||
|
from ...registry.xml import Node
|
||||||
|
from .exception import UndefinedBindingException, CUnknownTypeException
|
||||||
|
from .cpreprocessor import CPreprocessor
|
||||||
|
from .cparser import CParser
|
||||||
|
from .cgenerator import CGenerator
|
||||||
|
from .ccompiler import CCompiler
|
||||||
|
from .c import python_ctype_map, c_type_map, platform_ctypes, python_ctype_signature_map
|
||||||
|
|
||||||
|
|
||||||
|
class VulkanBindingData(dict):
|
||||||
|
__slots__ = ('ctype', 'node', 'cdecl')
|
||||||
|
|
||||||
|
|
||||||
|
class VulkanTypeBindingData(VulkanBindingData):
|
||||||
|
__slots__ = ('members',)
|
||||||
|
|
||||||
|
|
||||||
|
class VulkanFunctionBindingData(VulkanBindingData):
|
||||||
|
__slots__ = ('result', 'arguments', 'signature')
|
||||||
|
|
||||||
|
|
||||||
|
class VulkanCallbackBindingData(VulkanFunctionBindingData):
|
||||||
|
__slots__ = ('user_data',)
|
||||||
|
|
||||||
|
|
||||||
|
class VulkanCommandBindingData(VulkanFunctionBindingData):
|
||||||
|
__slots__ = ('output', 'handle')
|
||||||
|
|
||||||
|
|
||||||
|
class VulkanElementData(dict):
|
||||||
|
__slots__ = ('ctype', 'node', 'cdecl')
|
||||||
|
|
||||||
|
|
||||||
|
class VulkanHandleData(dict):
|
||||||
|
__slots__ = ('ctype', 'node', 'define', 'parent')
|
||||||
|
|
||||||
|
|
||||||
|
class LazyBindingAccess(Mapping):
|
||||||
|
__slots__ = ('binding', 'names')
|
||||||
|
|
||||||
|
def __init__(self, binding, /, *names: str):
|
||||||
|
self.binding = binding
|
||||||
|
self.names = names
|
||||||
|
|
||||||
|
def __contains__(self, name: str):
|
||||||
|
return name in self.names
|
||||||
|
|
||||||
|
def __getitem__(self, name: str):
|
||||||
|
return self.binding[name]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.names)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.names)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
yield from self.names
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
for name in self.names:
|
||||||
|
yield self.binding[name]
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
for name in self.names:
|
||||||
|
yield name, self.binding[name]
|
||||||
|
|
||||||
|
def get(self, name: str, default=None):
|
||||||
|
if name in self.names:
|
||||||
|
try:
|
||||||
|
return self.binding[name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, 'LazyBindingAccess'):
|
||||||
|
return NotImplemented
|
||||||
|
return self.binding is other.binding and set(self.names) == set(other.names)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
result = self.__eq__(other)
|
||||||
|
if result is NotImplemented:
|
||||||
|
return NotImplemented
|
||||||
|
return not result
|
||||||
|
|
||||||
|
|
||||||
|
class BindingImplementation:
|
||||||
|
class CFunction(Callable):
|
||||||
|
def __init__(self, binding: 'BindingImplementation', ast: c_ast.Node, args: Collection[str]):
|
||||||
|
self._binding = binding
|
||||||
|
self._ast = ast
|
||||||
|
parameters = [inspect.Parameter(arg, inspect.Parameter.POSITIONAL_OR_KEYWORD) for arg in args]
|
||||||
|
self.__signature__ = inspect.Signature(parameters)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
bound = self.__signature__.bind(*args, **kwargs)
|
||||||
|
bound.apply_defaults()
|
||||||
|
variables = dict(bound.arguments)
|
||||||
|
compiler = self._binding.CCompiler(self._binding, self._binding.cparser, variables=variables)
|
||||||
|
result = compiler.compile_value_expr(self._ast)
|
||||||
|
if type(result) in python_ctype_map:
|
||||||
|
return result.value
|
||||||
|
return result
|
||||||
|
|
||||||
|
preprocessor_method = {
|
||||||
|
CPreprocessor.CValueMacro: 'compile_define_value',
|
||||||
|
CPreprocessor.CFunctionMacro: 'compile_define_function',
|
||||||
|
}
|
||||||
|
|
||||||
|
c_complex_keyword = {
|
||||||
|
ctypes.Structure: 'struct',
|
||||||
|
ctypes.Union: 'union',
|
||||||
|
}
|
||||||
|
|
||||||
|
vulkan_handle_types = {
|
||||||
|
'VK_DEFINE_HANDLE': ctypes.c_void_p,
|
||||||
|
'VK_DEFINE_NON_DISPATCHABLE_HANDLE': ctypes.c_uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
ctype_intentionally_none = {
|
||||||
|
'vkVoidFunction',
|
||||||
|
}
|
||||||
|
|
||||||
|
vulkan_handle_object_type_prefix = 'VK_OBJECT_TYPE_'
|
||||||
|
vulkan_debug_report_object_type_prefix = 'VK_DEBUG_REPORT_OBJECT_TYPE_'
|
||||||
|
|
||||||
|
def __init__(self, taxonomy: Taxonomy, *, cache: Mapping = dict(), undefined: Collection[str] = ()):
|
||||||
|
self.taxonomy = taxonomy
|
||||||
|
self.undefined = set(undefined)
|
||||||
|
self.exception = {}
|
||||||
|
self.cparser = CParser(self.taxonomy.ctypes)
|
||||||
|
self.cgenerator = CGenerator()
|
||||||
|
self.cpreprocessor = CPreprocessor(self.cparser, self.cgenerator)
|
||||||
|
self.CCompiler = CCompiler
|
||||||
|
self.cache = cache
|
||||||
|
self.ctypes_map = {
|
||||||
|
**c_type_map,
|
||||||
|
**platform_ctypes,
|
||||||
|
}
|
||||||
|
self.compile_map = {}
|
||||||
|
for category in self.taxonomy.vulkan_node_info:
|
||||||
|
if hasattr(self, f'compile_{category}'):
|
||||||
|
method = getattr(self, f'compile_{category}')
|
||||||
|
if callable(method):
|
||||||
|
self.compile_map[category] = method
|
||||||
|
self.get_vulkan_ctype_map = {
|
||||||
|
'bitmask': self.get_hidden_ctype,
|
||||||
|
'enum': self.get_hidden_ctype,
|
||||||
|
'struct': self.get_metadata_ctype,
|
||||||
|
'union': self.get_metadata_ctype,
|
||||||
|
'command': self.get_metadata_ctype,
|
||||||
|
'handle': self.get_metadata_ctype,
|
||||||
|
'callback': self.get_metadata_ctype,
|
||||||
|
}
|
||||||
|
self.init_preprocessor()
|
||||||
|
|
||||||
|
def __getitem__(self, name: str):
|
||||||
|
if name in self.cache:
|
||||||
|
return self.cache[name]
|
||||||
|
if name in self.undefined:
|
||||||
|
raise KeyError(name)
|
||||||
|
if name not in self.taxonomy.vulkan_names:
|
||||||
|
if name in self.taxonomy.alias_map:
|
||||||
|
alias = self.taxonomy.alias_map[name]
|
||||||
|
try:
|
||||||
|
value = self[alias]
|
||||||
|
except KeyError:
|
||||||
|
self.undefined.add(name)
|
||||||
|
raise KeyError(name)
|
||||||
|
self.cache[name] = value
|
||||||
|
return value
|
||||||
|
raise KeyError(name)
|
||||||
|
if name in self.exception:
|
||||||
|
raise self.exception[name]
|
||||||
|
try:
|
||||||
|
value = self.compile(name)
|
||||||
|
except UndefinedBindingException as exception:
|
||||||
|
self.undefined.add(name)
|
||||||
|
raise KeyError(name) from exception
|
||||||
|
except Exception as exception:
|
||||||
|
self.exception[name] = exception
|
||||||
|
raise exception
|
||||||
|
self.cache[name] = value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
for name in self.taxonomy.vulkan_names:
|
||||||
|
if name not in self.undefined:
|
||||||
|
yield name
|
||||||
|
|
||||||
|
def __contains__(self, name):
|
||||||
|
return name in self.cache or name in self.taxonomy.vulkan_names and name not in self.undefined
|
||||||
|
|
||||||
|
def init_preprocessor(self):
|
||||||
|
for name in self.taxonomy.vulkan_node_info['define']:
|
||||||
|
node: Node
|
||||||
|
for node in self.taxonomy.nodes[name]:
|
||||||
|
if 'name' not in node.children or '#define' not in node.get_text():
|
||||||
|
continue
|
||||||
|
name_node: Node
|
||||||
|
name_node = node.get('name')
|
||||||
|
before_text = node.get_text_before(name_node)
|
||||||
|
after_text = node.get_text_after(name_node)
|
||||||
|
define_index = before_text.rfind('#define')
|
||||||
|
before_text = before_text[define_index:]
|
||||||
|
text = before_text + name_node.get_text() + after_text
|
||||||
|
text = text.splitlines()
|
||||||
|
for last_line_index in range(len(text))[::-1]:
|
||||||
|
if not text[last_line_index].endswith('\\'):
|
||||||
|
break
|
||||||
|
text = text[:last_line_index + 1]
|
||||||
|
text = '\n'.join(text)
|
||||||
|
self.cpreprocessor.define_macro(text, name=name_node.get_text())
|
||||||
|
for name in set(self.cpreprocessor.keys()):
|
||||||
|
macro = self.cpreprocessor[name]
|
||||||
|
code = ['int _ = ', name]
|
||||||
|
if isinstance(macro, self.cpreprocessor.CFunctionMacro):
|
||||||
|
code.append('(')
|
||||||
|
code.append(', '.join(macro.arguments))
|
||||||
|
code.append(')')
|
||||||
|
code.append(';')
|
||||||
|
code = ''.join(code)
|
||||||
|
try:
|
||||||
|
self.cpreprocessor.preprocess_code_ast(code)
|
||||||
|
except ParseError:
|
||||||
|
self.undefined.add(name)
|
||||||
|
del self.cpreprocessor[name]
|
||||||
|
|
||||||
|
def resolve_alias(self, name: str):
|
||||||
|
while name in self.taxonomy.alias_map:
|
||||||
|
name = self.taxonomy.alias_map[name]
|
||||||
|
return name
|
||||||
|
|
||||||
|
def get_ctype_internal(self, name: str):
|
||||||
|
if name in self.ctype_intentionally_none:
|
||||||
|
return None
|
||||||
|
if name in self.taxonomy.alias_map:
|
||||||
|
return self.get_ctype(self.taxonomy.alias_map[name])
|
||||||
|
if name in self.taxonomy.bit_bitmask_map:
|
||||||
|
return self.get_ctype(self.taxonomy.bit_bitmask_map[name])
|
||||||
|
if name.startswith('PFN_'):
|
||||||
|
return ctypes.POINTER(self.get_ctype(name[4:]))
|
||||||
|
if name in self.taxonomy.vulkan_names:
|
||||||
|
return self.get_ctype_vulkan(name)
|
||||||
|
if name in self.taxonomy.vulkan_node_info['basetype']:
|
||||||
|
return self.compile_basetype(name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_hidden_ctype(self, name: str):
|
||||||
|
return getattr(self[name], '__vulkan_ctype__')
|
||||||
|
|
||||||
|
def get_metadata_ctype(self, name: str):
|
||||||
|
return self[name].ctype
|
||||||
|
|
||||||
|
def get_ctype_vulkan(self, name: str):
|
||||||
|
return self.get_vulkan_ctype_map[self.taxonomy.vulkan_names[name]](name)
|
||||||
|
|
||||||
|
def get_ctype(self, name: str):
|
||||||
|
if name not in self.ctypes_map:
|
||||||
|
self.ctypes_map[name] = self.get_ctype_internal(name)
|
||||||
|
return self.ctypes_map[name]
|
||||||
|
|
||||||
|
def compile(self, name: str):
|
||||||
|
type = self.taxonomy.vulkan_names[name]
|
||||||
|
return self.compile_map[type](name)
|
||||||
|
|
||||||
|
def compile_define(self, name: str):
|
||||||
|
if name not in self.cpreprocessor:
|
||||||
|
raise UndefinedBindingException(f'Undefined binding for define {name}')
|
||||||
|
macro = self.cpreprocessor[name]
|
||||||
|
for macro_type in macro.__class__.__mro__:
|
||||||
|
if macro_type in self.preprocessor_method:
|
||||||
|
method_name = self.preprocessor_method[macro_type]
|
||||||
|
method = getattr(self, method_name)
|
||||||
|
return method(macro)
|
||||||
|
raise UndefinedBindingException(f'Unsupported macro type for define {name}: {type(macro)}')
|
||||||
|
|
||||||
|
def compile_basetype(self, name: str):
|
||||||
|
for node in self.taxonomy.nodes[name]:
|
||||||
|
node: Node
|
||||||
|
if 'name' not in node.children:
|
||||||
|
continue
|
||||||
|
code = [
|
||||||
|
node.get_text_before(node.get('name')).splitlines()[-1],
|
||||||
|
node.get('name').get_text(),
|
||||||
|
node.get_text_after(node.get('name')).splitlines()[0],
|
||||||
|
]
|
||||||
|
code = ' '.join(code)
|
||||||
|
try:
|
||||||
|
ast = self.cpreprocessor.preprocess_code_ast(code)
|
||||||
|
except ParseError as error:
|
||||||
|
raise CUnknownTypeException(f'Cannot parse basetype {name}: {error}') from error
|
||||||
|
self.ctypes_map[name] = None
|
||||||
|
compiler = self.CCompiler(self, self.cparser)
|
||||||
|
return compiler.compile_type(ast.ext[0].type)
|
||||||
|
raise CUnknownTypeException(f'Missing definition for CType: {name}')
|
||||||
|
|
||||||
|
def compile_value(self, name: str):
|
||||||
|
if name not in self.taxonomy.value_group_map:
|
||||||
|
raise UndefinedBindingException(f'Undefined value: {name}')
|
||||||
|
group = self.taxonomy.value_group_map[name]
|
||||||
|
if isinstance(group, str):
|
||||||
|
if group in self.taxonomy.bit_bitmask_map:
|
||||||
|
group = self.taxonomy.bit_bitmask_map[group]
|
||||||
|
enum = self[group]
|
||||||
|
if not hasattr(enum, name):
|
||||||
|
raise UndefinedBindingException(f'Undefined value: {name}')
|
||||||
|
return getattr(enum, name)
|
||||||
|
elif name in self.taxonomy.nodes:
|
||||||
|
for node in self.taxonomy.nodes[name]:
|
||||||
|
node: Node
|
||||||
|
if node.has_attribute('value') or node.has_attribute('bitpos'):
|
||||||
|
value = self.parse_enum_value_node(node)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
raise UndefinedBindingException(f'Undefined value: {name}')
|
||||||
|
|
||||||
|
def compile_define_value(self, macro: CPreprocessor.CValueMacro):
|
||||||
|
ast = self.cpreprocessor.preprocess_code_ast(f'int _ = {macro.name};')
|
||||||
|
compiler = self.CCompiler(self, self.cparser)
|
||||||
|
result = compiler.compile_value_expr(ast.ext[0].init)
|
||||||
|
if type(result) in python_ctype_map:
|
||||||
|
return result.value
|
||||||
|
return result
|
||||||
|
|
||||||
|
def compile_define_function(self, macro: CPreprocessor.CFunctionMacro):
|
||||||
|
args = ', '.join(macro.arguments)
|
||||||
|
code = f'int _ = {macro.name}({args});'
|
||||||
|
ast = self.cpreprocessor.preprocess_code_ast(code)
|
||||||
|
return self.CFunction(self, ast.ext[0].init, macro.arguments)
|
||||||
|
|
||||||
|
def compile_enum(self, name: str):
|
||||||
|
from enum import IntEnum
|
||||||
|
return self.create_python_enum(name, IntEnum)
|
||||||
|
|
||||||
|
def compile_bitmask(self, name: str):
|
||||||
|
from enum import IntFlag
|
||||||
|
return self.create_python_enum(name, IntFlag)
|
||||||
|
|
||||||
|
def create_python_enum(self, name: str, base_class: type):
|
||||||
|
from enum import EnumType
|
||||||
|
bitmask_name = name
|
||||||
|
if name in self.taxonomy.bitmask_bit_map:
|
||||||
|
bitmask_name = self.taxonomy.bitmask_bit_map[name]
|
||||||
|
namespace = EnumType.__prepare__(name, (base_class,))
|
||||||
|
if bitmask_name in self.taxonomy.group_value_map:
|
||||||
|
for value_name in self.taxonomy.group_value_map[bitmask_name]:
|
||||||
|
value = None
|
||||||
|
for node in self.taxonomy.nodes[value_name]:
|
||||||
|
node: Node
|
||||||
|
if not self.taxonomy.is_valid_api(node):
|
||||||
|
continue
|
||||||
|
if node.get_attribute('extends') == bitmask_name:
|
||||||
|
value = self.parse_enum_value_extension_node(node)
|
||||||
|
if value is not None:
|
||||||
|
break
|
||||||
|
if node.has_attribute('value') or node.has_attribute('bitpos'):
|
||||||
|
value = self.parse_enum_value_node(node)
|
||||||
|
if value is not None:
|
||||||
|
break
|
||||||
|
if value is None:
|
||||||
|
raise RuntimeError(f'Failed to parse value for enum {name} value {value_name}')
|
||||||
|
namespace[value_name] = value
|
||||||
|
result = EnumType(name, (base_class,), namespace)
|
||||||
|
if bitmask_name in self.taxonomy.group_value_map:
|
||||||
|
for value_name in self.taxonomy.group_value_map[bitmask_name]:
|
||||||
|
value = getattr(result, value_name)
|
||||||
|
self.cache[value_name] = value
|
||||||
|
if value_name not in self.taxonomy.alias_target_map:
|
||||||
|
continue
|
||||||
|
for alias_name in self.taxonomy.alias_target_map[value_name]:
|
||||||
|
setattr(result, alias_name, value)
|
||||||
|
self.cache[alias_name] = value
|
||||||
|
ctype = ctypes.c_int
|
||||||
|
for node in self.taxonomy.nodes[name]:
|
||||||
|
node: Node
|
||||||
|
if 'type' in node.children:
|
||||||
|
ctype = self.get_ctype(node.get('type').get_text())
|
||||||
|
setattr(result, '__vulkan_ctype__', ctype)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def parse_enum_value_extension_node(self, node: Node, *, allowed_type: Collection[type] | None = None):
|
||||||
|
if node.has_attribute('offset'):
|
||||||
|
extnumber = None
|
||||||
|
if node.has_attribute('extnumber'):
|
||||||
|
extnumber = self.cparser.parse_c_int(node.get_attribute('extnumber'))
|
||||||
|
else:
|
||||||
|
if node.path[-3:] == ['extension', 'require', 'enum']:
|
||||||
|
extension_node = node.parent_node.parent_node
|
||||||
|
if extension_node.has_attribute('number'):
|
||||||
|
extnumber = self.cparser.parse_c_int(extension_node.get_attribute('number'))
|
||||||
|
if extnumber is None:
|
||||||
|
return None
|
||||||
|
value = 1000000000 + (extnumber - 1) * 1000 + self.parse_c_code_int(node.get_attribute('offset'))
|
||||||
|
elif node.has_attribute('bitpos'):
|
||||||
|
bitpos = self.cparser.parse_c_int(node.get_attribute('bitpos'))
|
||||||
|
value = 1 << bitpos
|
||||||
|
elif node.has_attribute('value'):
|
||||||
|
value = self.parse_c_code_value(node.get_attribute('value'))
|
||||||
|
if node.get_attribute('dir') == '-':
|
||||||
|
value = -value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def parse_enum_value_node(self, node: Node, *, allowed_type: Collection[type] | None = None):
|
||||||
|
value = None
|
||||||
|
if node.has_attribute('bitpos'):
|
||||||
|
bitpos = self.cparser.parse_c_int(node.get_attribute('bitpos'))
|
||||||
|
value = 1 << bitpos
|
||||||
|
elif node.has_attribute('value'):
|
||||||
|
value = self.parse_c_code_value(node.get_attribute('value'))
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if node.get_attribute('dir') == '-':
|
||||||
|
value = -value
|
||||||
|
if allowed_type is not None and not isinstance(value, tuple(allowed_type)):
|
||||||
|
raise ValueError(f'Invalid value type for enum value: expected ({", ".join([t.__name__ for t in allowed_type])}), got {type(value).__name__}')
|
||||||
|
return value
|
||||||
|
|
||||||
|
def parse_c_code_int(self, code: str):
|
||||||
|
value = self.parse_c_code_value(code)
|
||||||
|
if not isinstance(value, int):
|
||||||
|
raise ValueError(f'Expected integer value, got {type(value)} compiling code: {code}')
|
||||||
|
return value
|
||||||
|
|
||||||
|
def parse_c_code_value(self, code: str):
|
||||||
|
ast = self.cpreprocessor.preprocess_code_ast(f'int _ = {code};')
|
||||||
|
compiler = self.CCompiler(self, self.cparser)
|
||||||
|
result = compiler.compile_value_expr(ast.ext[0].init)
|
||||||
|
if type(result) in python_ctype_map:
|
||||||
|
return result.value
|
||||||
|
return result
|
||||||
|
|
||||||
|
def compile_struct(self, name: str):
|
||||||
|
return self.create_complex(name, ctypes.Structure)
|
||||||
|
|
||||||
|
def compile_union(self, name: str):
|
||||||
|
return self.create_complex(name, ctypes.Union)
|
||||||
|
|
||||||
|
def get_type_class(self, name: str):
|
||||||
|
if name.startswith('PFN_'):
|
||||||
|
callback_name = name[4:]
|
||||||
|
callback_name = self.resolve_alias(callback_name)
|
||||||
|
if callback_name in self.taxonomy.vulkan_node_info['callback']:
|
||||||
|
return callback_name, 'callback'
|
||||||
|
real_name = self.resolve_alias(name)
|
||||||
|
if real_name in self.taxonomy.bit_bitmask_map:
|
||||||
|
real_name = self.taxonomy.bit_bitmask_map[real_name]
|
||||||
|
real_name = self.resolve_alias(real_name)
|
||||||
|
if real_name in self.taxonomy.vulkan_names:
|
||||||
|
member_class = self.taxonomy.vulkan_names[real_name]
|
||||||
|
elif real_name in self.taxonomy.ctypes:
|
||||||
|
member_class = 'ctype'
|
||||||
|
else:
|
||||||
|
member_class = 'external'
|
||||||
|
return real_name, member_class
|
||||||
|
|
||||||
|
def create_complex(self, name: str, base_class: type):
|
||||||
|
binding_data = VulkanTypeBindingData()
|
||||||
|
binding_data.ctype = type(name, (base_class,), {})
|
||||||
|
self.cache[name] = binding_data
|
||||||
|
self.ctypes_map[name] = binding_data.ctype
|
||||||
|
|
||||||
|
complex_code = ['%s %s {' % (self.c_complex_keyword[base_class], name)]
|
||||||
|
members = OrderedDict()
|
||||||
|
fields = []
|
||||||
|
member_processed_attributes = {}
|
||||||
|
processed_attributes = {'api', 'name', 'category', 'structextends'}
|
||||||
|
|
||||||
|
for node in self.taxonomy.nodes[name]:
|
||||||
|
node: Node
|
||||||
|
if 'member' not in node.children:
|
||||||
|
continue
|
||||||
|
if not self.taxonomy.is_valid_api(node):
|
||||||
|
continue
|
||||||
|
binding_data.node = node
|
||||||
|
for member_node in node.get_all('member'):
|
||||||
|
member_node: Node
|
||||||
|
if not self.taxonomy.is_valid_api(member_node):
|
||||||
|
continue
|
||||||
|
if 'name' not in member_node.children:
|
||||||
|
raise ValueError(f'Member node for struct {name} is missing name attribute')
|
||||||
|
member_info = VulkanElementData()
|
||||||
|
member_info.node = member_node
|
||||||
|
member_info['is_string'] = False
|
||||||
|
member_name = member_node.get('name').get_text()
|
||||||
|
members[member_name] = member_info
|
||||||
|
member_processed_attributes[member_name] = {'api'}
|
||||||
|
if 'type' in member_node.children:
|
||||||
|
member_type = member_node.get('type').get_text()
|
||||||
|
member_info['type'], member_info['class'] = self.get_type_class(member_type)
|
||||||
|
if member_node.has_attribute('altlen'):
|
||||||
|
length_code = f'int _ = {member_node.get_attribute("altlen")};'
|
||||||
|
length_ast = self.cpreprocessor.preprocess_code_ast(length_code)
|
||||||
|
member_info['length'] = [length_ast.ext[0].init]
|
||||||
|
member_processed_attributes[member_name].update({'altlen', 'len'})
|
||||||
|
elif member_node.has_attribute('len'):
|
||||||
|
length_items = [s.strip() for s in member_node.get_attribute('len').split(',')]
|
||||||
|
if length_items[-1] == 'null-terminated':
|
||||||
|
member_info['is_string'] = True
|
||||||
|
length_items = length_items[:-1]
|
||||||
|
if len(length_items) > 0:
|
||||||
|
length_code = 'int _ = (%s);' % ', '.join(length_items)
|
||||||
|
length_ast = self.cpreprocessor.preprocess_code_ast(length_code).ext[0].init
|
||||||
|
length = []
|
||||||
|
if isinstance(length_ast, c_ast.ExprList):
|
||||||
|
for expr in length_ast.exprs:
|
||||||
|
if isinstance(expr, c_ast.ID):
|
||||||
|
length.append(expr.name)
|
||||||
|
else:
|
||||||
|
length.append(expr)
|
||||||
|
else:
|
||||||
|
if isinstance(length_ast, c_ast.ID):
|
||||||
|
length.append(length_ast.name)
|
||||||
|
else:
|
||||||
|
length.append(length_ast)
|
||||||
|
member_info['length'] = length
|
||||||
|
member_processed_attributes[member_name].add('len')
|
||||||
|
if (
|
||||||
|
member_node.has_attribute('values')
|
||||||
|
and
|
||||||
|
member_name == 'sType'
|
||||||
|
and
|
||||||
|
'type' in member_info
|
||||||
|
and
|
||||||
|
member_info['type'] == 'VkStructureType'
|
||||||
|
and
|
||||||
|
member_info['class'] == 'enum'
|
||||||
|
):
|
||||||
|
if ',' in member_node.get_attribute('values'):
|
||||||
|
raise ValueError(f'Multiple values not supported for sType member of struct {name}')
|
||||||
|
member_info['structure_type'] = self[member_node.get_attribute('values').strip()]
|
||||||
|
member_processed_attributes[member_name].add('values')
|
||||||
|
if member_node.has_attribute('optional'):
|
||||||
|
optional = []
|
||||||
|
for optional_value in member_node.get_attribute('optional').split(','):
|
||||||
|
optional_value = optional_value.strip().lower()
|
||||||
|
if optional_value == 'true':
|
||||||
|
optional.append(True)
|
||||||
|
elif optional_value == 'false':
|
||||||
|
optional.append(False)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Invalid optional value for member {member_name} of struct {name}: {optional_value}')
|
||||||
|
member_info['optional'] = optional[0] if len(optional) == 1 else optional
|
||||||
|
member_processed_attributes[member_name].add('optional')
|
||||||
|
complex_code.append('%s;' % member_node.get_text())
|
||||||
|
|
||||||
|
complex_code.append('};')
|
||||||
|
complex_code = '\n'.join(complex_code)
|
||||||
|
ast = self.cpreprocessor.preprocess_code_ast(complex_code)
|
||||||
|
compiler = self.CCompiler(self, self.cparser)
|
||||||
|
for decl in ast.ext[0].type.decls:
|
||||||
|
decl: c_ast.Decl
|
||||||
|
member_name = decl.name
|
||||||
|
member_info = members[member_name]
|
||||||
|
member_info.ctype = compiler.compile_type(decl.type)
|
||||||
|
member_info.cdecl = decl
|
||||||
|
if decl.bitsize is not None:
|
||||||
|
compiler = self.CCompiler(self, self.cparser)
|
||||||
|
bit_size = compiler.compile_value_expr(decl.bitsize)
|
||||||
|
if type(bit_size) in python_ctype_map:
|
||||||
|
bit_size = bit_size.value
|
||||||
|
if not isinstance(bit_size, int):
|
||||||
|
raise ValueError(f'Expected integer bit size for member {member_name} of struct {name}, got {type(bit_size)}')
|
||||||
|
fields.append((member_name, member_info.ctype, bit_size))
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
fields.append((member_name, member_info.ctype))
|
||||||
|
ptr_decl = decl.type
|
||||||
|
member_info['is_pointer'] = 0
|
||||||
|
while isinstance(ptr_decl, c_ast.PtrDecl):
|
||||||
|
member_info['is_pointer'] += 1
|
||||||
|
ptr_decl = ptr_decl.type
|
||||||
|
if member_info['is_pointer'] > 0 and 'optional' not in member_processed_attributes[member_name]:
|
||||||
|
member_info['optional'] = [False] * member_info['is_pointer'] if member_info['is_pointer'] > 1 else False
|
||||||
|
member_processed_attributes[member_name].add('optional')
|
||||||
|
for attribute_name in member_info.node.attributes:
|
||||||
|
if attribute_name not in member_processed_attributes[member_name]:
|
||||||
|
member_info[attribute_name] = member_info.node.get_attribute(attribute_name)
|
||||||
|
|
||||||
|
if base_class is ctypes.Structure and 'sType' in members and 'pNext' in members and members['sType'].get('structure_type') is not None:
|
||||||
|
binding_data['extends'] = self.taxonomy.vulkan_node_info[self.taxonomy.vulkan_names[name]][name]['extends']
|
||||||
|
binding_data['extended_by'] = LazyBindingAccess(self, *self.taxonomy.vulkan_node_info[self.taxonomy.vulkan_names[name]][name]['extended_by'])
|
||||||
|
binding_data['returned_only'] = False
|
||||||
|
if binding_data.node.get_attribute('returnedonly') == 'true':
|
||||||
|
binding_data['returned_only'] = True
|
||||||
|
processed_attributes.add('returnedonly')
|
||||||
|
binding_data['required_limit_type'] = False
|
||||||
|
if binding_data.node.get_attribute('requiredlimittype') == 'true':
|
||||||
|
binding_data['required_limit_type'] = True
|
||||||
|
processed_attributes.add('requiredlimittype')
|
||||||
|
if binding_data.node.has_attribute('allowduplicate'):
|
||||||
|
value = binding_data.node.get_attribute('allowduplicate').strip().lower()
|
||||||
|
if value == 'true':
|
||||||
|
binding_data['allow_duplicate'] = True
|
||||||
|
elif value == 'false':
|
||||||
|
binding_data['allow_duplicate'] = False
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Invalid value for @allowduplicate in "{name}": {value}')
|
||||||
|
processed_attributes.add('allowduplicate')
|
||||||
|
for attribute_name in binding_data.node.attributes:
|
||||||
|
if attribute_name not in processed_attributes:
|
||||||
|
binding_data[attribute_name] = binding_data.node.get_attribute(attribute_name)
|
||||||
|
binding_data.cdecl = ast.ext[0]
|
||||||
|
binding_data.ctype._fields_ = fields
|
||||||
|
binding_data.members = members
|
||||||
|
return binding_data
|
||||||
|
|
||||||
|
def compile_callback(self, name: str):
|
||||||
|
binding_data = VulkanCallbackBindingData()
|
||||||
|
Constructor = ctypes.CFUNCTYPE
|
||||||
|
if hasattr(ctypes, 'WINFUNCTYPE'):
|
||||||
|
Constructor = ctypes.WINFUNCTYPE
|
||||||
|
code = {}
|
||||||
|
args = OrderedDict()
|
||||||
|
for node in self.taxonomy.nodes[name]:
|
||||||
|
node: Node
|
||||||
|
if not self.taxonomy.is_valid_api(node):
|
||||||
|
continue
|
||||||
|
if 'proto' not in node.children:
|
||||||
|
continue
|
||||||
|
binding_data.node = node
|
||||||
|
code['return'] = node.get('proto').get_text()
|
||||||
|
code['params'] = []
|
||||||
|
for param_node in node.get_all('param'):
|
||||||
|
param_info = VulkanElementData()
|
||||||
|
param_info.node = param_node
|
||||||
|
args[param_node.get('name').get_text()] = param_info
|
||||||
|
code['params'].append(param_node.get_text())
|
||||||
|
if 'type' in param_node.children:
|
||||||
|
param_type = param_node.get('type').get_text()
|
||||||
|
param_info['type'], param_info['class'] = self.get_type_class(param_type)
|
||||||
|
if len(code) == 0:
|
||||||
|
raise UndefinedBindingException(f'Undefined callback: {name}')
|
||||||
|
code = ' '.join([code['return'], '(', ', '.join(code['params']), ');'])
|
||||||
|
ast = self.cpreprocessor.preprocess_code_ast(code).ext[0].type
|
||||||
|
compiler = self.CCompiler(self, self.cparser)
|
||||||
|
ctype_params = [compiler.compile_type(ast.type)]
|
||||||
|
return_info = VulkanElementData()
|
||||||
|
return_info.ctype = ctype_params[0]
|
||||||
|
return_info.cdecl = ast.type
|
||||||
|
proto_node = binding_data.node.get('proto')
|
||||||
|
return_info.node = proto_node
|
||||||
|
if 'type' in proto_node.children:
|
||||||
|
return_type = proto_node.get('type').get_text()
|
||||||
|
return_info['type'], return_info['class'] = self.get_type_class(return_type)
|
||||||
|
binding_data.result = return_info
|
||||||
|
if isinstance(ast.args, c_ast.ParamList):
|
||||||
|
for param in ast.args.params:
|
||||||
|
param_info = args[param.name]
|
||||||
|
param_type = compiler.compile_type(param.type)
|
||||||
|
if param_type is None:
|
||||||
|
raise CUnknownTypeException(f'Unknown type for parameter {param.name} of callback {name}')
|
||||||
|
param_info.ctype = param_type
|
||||||
|
param_info.cdecl = param
|
||||||
|
ctype_params.append(param_type)
|
||||||
|
try:
|
||||||
|
binding_data.user_data = list(args.keys()).index('pUserData')
|
||||||
|
except ValueError:
|
||||||
|
binding_data.user_data = None
|
||||||
|
binding_data.arguments = args
|
||||||
|
binding_data.ctype = Constructor(*ctype_params)
|
||||||
|
binding_data.cdecl = ast
|
||||||
|
self.cache[name] = binding_data
|
||||||
|
self.create_signature(name)
|
||||||
|
return binding_data
|
||||||
|
|
||||||
|
def compile_command(self, name: str):
|
||||||
|
binding_data = VulkanCommandBindingData()
|
||||||
|
Constructor = ctypes.CFUNCTYPE
|
||||||
|
if hasattr(ctypes, 'WINFUNCTYPE'):
|
||||||
|
Constructor = ctypes.WINFUNCTYPE
|
||||||
|
code = {}
|
||||||
|
args = OrderedDict()
|
||||||
|
|
||||||
|
param_processed_attributes = {}
|
||||||
|
processed_attributes = {'export', 'api'}
|
||||||
|
|
||||||
|
for node in self.taxonomy.nodes[name]:
|
||||||
|
node: Node
|
||||||
|
if not self.taxonomy.is_valid_api(node):
|
||||||
|
continue
|
||||||
|
if 'proto' not in node.children:
|
||||||
|
continue
|
||||||
|
binding_data.node = node
|
||||||
|
code['return'] = node.get('proto').get_text()
|
||||||
|
code['params'] = []
|
||||||
|
for param_node in node.get_all('param'):
|
||||||
|
if not self.taxonomy.is_valid_api(param_node):
|
||||||
|
continue
|
||||||
|
param_name = param_node.get('name').get_text()
|
||||||
|
param_info = VulkanElementData()
|
||||||
|
param_info.node = param_node
|
||||||
|
args[param_name] = param_info
|
||||||
|
code['params'].append(param_node.get_text())
|
||||||
|
param_processed_attributes[param_name] = {'api'}
|
||||||
|
if 'type' in param_node.children:
|
||||||
|
param_type = param_node.get('type').get_text()
|
||||||
|
param_info['type'], param_info['class'] = self.get_type_class(param_type)
|
||||||
|
if param_node.has_attribute('altlen'):
|
||||||
|
length_code = f'int _ = {param_node.get_attribute("altlen")};'
|
||||||
|
length_ast = self.cpreprocessor.preprocess_code_ast(length_code)
|
||||||
|
param_info['length'] = [length_ast.ext[0].init]
|
||||||
|
param_processed_attributes[param_name].update({'altlen', 'len'})
|
||||||
|
elif param_node.has_attribute('len'):
|
||||||
|
length_items = [s.strip() for s in param_node.get_attribute('len').split(',')]
|
||||||
|
if length_items[-1] == 'null-terminated':
|
||||||
|
param_info['is_string'] = True
|
||||||
|
length_items = length_items[:-1]
|
||||||
|
if len(length_items) > 0:
|
||||||
|
length_code = 'int _ = (%s);' % ', '.join(length_items)
|
||||||
|
length_ast = self.cpreprocessor.preprocess_code_ast(length_code).ext[0].init
|
||||||
|
length = []
|
||||||
|
if isinstance(length_ast, c_ast.ExprList):
|
||||||
|
for expr in length_ast.exprs:
|
||||||
|
if isinstance(expr, c_ast.ID):
|
||||||
|
length.append(expr.name)
|
||||||
|
else:
|
||||||
|
length.append(expr)
|
||||||
|
else:
|
||||||
|
if isinstance(length_ast, c_ast.ID):
|
||||||
|
length.append(length_ast.name)
|
||||||
|
else:
|
||||||
|
length.append(length_ast)
|
||||||
|
param_info['length'] = length
|
||||||
|
param_processed_attributes[param_name].add('len')
|
||||||
|
if param_node.has_attribute('optional'):
|
||||||
|
optional = []
|
||||||
|
for optional_value in param_node.get_attribute('optional').split(','):
|
||||||
|
optional_value = optional_value.strip().lower()
|
||||||
|
if optional_value == 'true':
|
||||||
|
optional.append(True)
|
||||||
|
elif optional_value == 'false':
|
||||||
|
optional.append(False)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Invalid optional value for param {param_name} of command {name}: {optional_value}')
|
||||||
|
param_info['optional'] = optional[0] if len(optional) == 1 else optional
|
||||||
|
param_processed_attributes[param_name].add('optional')
|
||||||
|
if param_node.has_attribute('validstructs'):
|
||||||
|
validstructs = [s.strip() for s in param_node.get_attribute('validstructs').split(',')]
|
||||||
|
if not all(name in self.taxonomy.vulkan_node_info['struct'] for name in validstructs):
|
||||||
|
raise ValueError(f'Missing validstructs required by param {param_name} of command {name}: {', '.join(name for name in validstructs if name not in self.taxonomy.vulkan_node_info['struct'])}')
|
||||||
|
param_info['valid_structs'] = validstructs
|
||||||
|
param_processed_attributes[param_name].add('validstructs')
|
||||||
|
if len(code) == 0:
|
||||||
|
raise UndefinedBindingException(f'Undefined callback: {name}')
|
||||||
|
code = ' '.join([code['return'], '(', ', '.join(code['params']), ');'])
|
||||||
|
ast = self.cpreprocessor.preprocess_code_ast(code).ext[0].type
|
||||||
|
compiler = self.CCompiler(self, self.cparser)
|
||||||
|
ctype_params = [compiler.compile_type(ast.type)]
|
||||||
|
return_info = VulkanElementData()
|
||||||
|
return_info.ctype = ctype_params[0]
|
||||||
|
return_info.cdecl = ast.type
|
||||||
|
proto_node = binding_data.node.get('proto')
|
||||||
|
return_info.node = proto_node
|
||||||
|
if 'type' in proto_node.children:
|
||||||
|
return_type = proto_node.get('type').get_text()
|
||||||
|
return_info['type'], return_info['class'] = self.get_type_class(return_type)
|
||||||
|
binding_data.result = return_info
|
||||||
|
if isinstance(ast.args, c_ast.ParamList):
|
||||||
|
for decl in ast.args.params:
|
||||||
|
param_info = args[decl.name]
|
||||||
|
param_type = compiler.compile_type(decl.type)
|
||||||
|
if param_type is None:
|
||||||
|
raise CUnknownTypeException(f'Unknown type for parameter {decl.name} of callback {name}')
|
||||||
|
param_info.ctype = param_type
|
||||||
|
param_info.cdecl = decl
|
||||||
|
ctype_params.append(param_type)
|
||||||
|
ptr_decl = decl.type
|
||||||
|
param_info['is_pointer'] = 0
|
||||||
|
while isinstance(ptr_decl, c_ast.PtrDecl):
|
||||||
|
param_info['is_pointer'] += 1
|
||||||
|
ptr_decl = ptr_decl.type
|
||||||
|
if param_info['is_pointer'] > 0 and 'optional' not in param_processed_attributes[decl.name]:
|
||||||
|
param_info['optional'] = [False] * param_info['is_pointer'] if param_info['is_pointer'] > 1 else False
|
||||||
|
param_processed_attributes[decl.name].add('optional')
|
||||||
|
for attribute_name in param_info.node.attributes:
|
||||||
|
if attribute_name not in param_processed_attributes[decl.name]:
|
||||||
|
param_info[attribute_name] = param_info.node.get_attribute(attribute_name)
|
||||||
|
binding_data.arguments = args
|
||||||
|
binding_data.ctype = Constructor(*ctype_params)
|
||||||
|
binding_data.cdecl = ast
|
||||||
|
for source_attribute, target_attribute, target_type in [
|
||||||
|
('successcodes', 'success_codes', 'VkResult'),
|
||||||
|
('errorcodes', 'error_codes', 'VkResult'),
|
||||||
|
('queues', 'queues', 'VkQueueFlagBits'),
|
||||||
|
]:
|
||||||
|
if binding_data.node.has_attribute(source_attribute):
|
||||||
|
enum_values = [self.resolve_alias(s.strip()) for s in binding_data.node.get_attribute(source_attribute).split(',')]
|
||||||
|
if not all(name in self.taxonomy.group_value_map[target_type] for name in enum_values):
|
||||||
|
raise ValueError(f'Missing enum "{target_type}" values for command {name}: {', '.join(name for name in enum_values if name not in self.taxonomy.group_value_map[target_type])}')
|
||||||
|
binding_data[target_attribute] = [self[name] for name in enum_values]
|
||||||
|
processed_attributes.add(source_attribute)
|
||||||
|
for source_attribute, target_attribute in [
|
||||||
|
('queues', 'queues'),
|
||||||
|
('tasks', 'tasks'),
|
||||||
|
('cmdbufferlevel', 'command_buffer_level'),
|
||||||
|
]:
|
||||||
|
if binding_data.node.has_attribute(source_attribute):
|
||||||
|
values = [s.strip().lower() for s in binding_data.node.get_attribute(source_attribute).split(',')]
|
||||||
|
binding_data[target_attribute] = values
|
||||||
|
processed_attributes.add(source_attribute)
|
||||||
|
for source_attribute, target_attribute in [
|
||||||
|
('allownoqueues', 'allow_no_queues'),
|
||||||
|
('conditionalrendering', 'conditional_rendering'),
|
||||||
|
]:
|
||||||
|
if binding_data.node.has_attribute(source_attribute):
|
||||||
|
value = binding_data.node.get_attribute(source_attribute).strip().lower()
|
||||||
|
if value == 'true':
|
||||||
|
binding_data[target_attribute] = True
|
||||||
|
elif value == 'false':
|
||||||
|
binding_data[target_attribute] = False
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Invalid value for "{source_attribute}" in command {name}: {value}')
|
||||||
|
processed_attributes.add(source_attribute)
|
||||||
|
for attribute_name in binding_data.node.attributes:
|
||||||
|
if attribute_name not in processed_attributes:
|
||||||
|
binding_data[attribute_name] = binding_data.node.get_attribute(attribute_name)
|
||||||
|
outputs = self.search_for_command_output(binding_data, 'arguments', const_scope=False)
|
||||||
|
handles = {}
|
||||||
|
for arg_name, arg_info in binding_data.arguments.items():
|
||||||
|
if arg_name in outputs:
|
||||||
|
continue
|
||||||
|
if arg_info['class'] == 'handle' and arg_info.ctype in (ctypes.c_void_p, ctypes.c_uint64):
|
||||||
|
handles[arg_name] = arg_info['type']
|
||||||
|
binding_data.handle = self.resolve_command_handle(handles)
|
||||||
|
if len(outputs) == 0:
|
||||||
|
if binding_data.result.ctype is not None and not (binding_data.result['class'] == 'enum' and binding_data.result['type'] == 'VkResult'):
|
||||||
|
# "return" is a C keyword and cannot be parameter name
|
||||||
|
outputs = 'return'
|
||||||
|
else:
|
||||||
|
outputs = None
|
||||||
|
else:
|
||||||
|
outputs = [output[0] if len(output) == 1 else output for output in outputs]
|
||||||
|
outputs = outputs[0] if len(outputs) == 1 else outputs
|
||||||
|
binding_data.output = outputs
|
||||||
|
self.cache[name] = binding_data
|
||||||
|
self.create_signature(name)
|
||||||
|
return binding_data
|
||||||
|
|
||||||
|
def resolve_command_handle(self, handles: dict[str, str]) -> str | None:
|
||||||
|
if len(handles) == 0:
|
||||||
|
return None
|
||||||
|
if len(handles) == 1:
|
||||||
|
return next(iter(handles.keys()))
|
||||||
|
selected_name = None
|
||||||
|
selected_length = float('Infinity')
|
||||||
|
for param_name, handle_type in handles.items():
|
||||||
|
handle_path = self.get_handle_path(handle_type)
|
||||||
|
if len(handle_path) < selected_length:
|
||||||
|
selected_name = param_name
|
||||||
|
selected_length = len(handle_path)
|
||||||
|
return selected_name
|
||||||
|
|
||||||
|
def get_handle_path(self, handle_type: str) -> list[str]:
|
||||||
|
path = [handle_type]
|
||||||
|
while True:
|
||||||
|
handle_data = self[handle_type]
|
||||||
|
if not isinstance(handle_data, VulkanHandleData):
|
||||||
|
raise ValueError(f'Invalid handle type: {handle_type}')
|
||||||
|
if handle_data.parent is None:
|
||||||
|
break
|
||||||
|
path.append(handle_data.parent)
|
||||||
|
handle_type = handle_data.parent
|
||||||
|
return path
|
||||||
|
|
||||||
|
def search_for_command_output(self, binding_data, element_name: str, const_scope=True):
|
||||||
|
outputs = []
|
||||||
|
for name, info in getattr(binding_data, element_name).items():
|
||||||
|
if not isinstance(info.cdecl.type, c_ast.PtrDecl):
|
||||||
|
continue
|
||||||
|
if 'const' in info.cdecl.quals:
|
||||||
|
# if info['class'] == 'struct':
|
||||||
|
# struct_outputs = self.search_for_command_output(self[info['type']], 'members', const_scope=True)
|
||||||
|
# outputs.extend([name, *path] for path in struct_outputs)
|
||||||
|
continue
|
||||||
|
if not const_scope:
|
||||||
|
outputs.append([name])
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
def compile_handle(self, name: str):
|
||||||
|
binding_data = VulkanHandleData()
|
||||||
|
self.cache[name] = binding_data
|
||||||
|
for node in self.taxonomy.nodes[name]:
|
||||||
|
node: Node
|
||||||
|
if not self.taxonomy.is_valid_api(node):
|
||||||
|
continue
|
||||||
|
if 'type' not in node.children or 'name' not in node.children or not node.has_attribute('objtypeenum'):
|
||||||
|
continue
|
||||||
|
handle_type = node.get('type').get_text()
|
||||||
|
if handle_type not in self.vulkan_handle_types:
|
||||||
|
raise ValueError(f'Unknown handle type for handle {name}: {handle_type}')
|
||||||
|
binding_data.ctype = self.vulkan_handle_types[handle_type]
|
||||||
|
binding_data.node = node
|
||||||
|
binding_data.define = handle_type
|
||||||
|
if node.has_attribute('parent'):
|
||||||
|
parent_name = node.get_attribute('parent')
|
||||||
|
binding_data.parent = parent_name
|
||||||
|
else:
|
||||||
|
binding_data.parent = None
|
||||||
|
object_type_name = node.get_attribute('objtypeenum')
|
||||||
|
if not object_type_name.startswith(self.vulkan_handle_object_type_prefix):
|
||||||
|
raise ValueError(f'Invalid objtypeenum for handle {name}: {object_type_name}')
|
||||||
|
binding_data['VkObjectType'] = self[object_type_name]
|
||||||
|
object_type_name = self.vulkan_debug_report_object_type_prefix + object_type_name[len(self.vulkan_handle_object_type_prefix):]
|
||||||
|
if object_type_name in self.taxonomy.vulkan_names:
|
||||||
|
binding_data['VkDebugReportObjectTypeEXT'] = self[object_type_name]
|
||||||
|
elif f'{object_type_name}_EXT' in self.taxonomy.vulkan_names:
|
||||||
|
binding_data['VkDebugReportObjectTypeEXT'] = self[f'{object_type_name}_EXT']
|
||||||
|
else:
|
||||||
|
for tag in self.taxonomy.tags:
|
||||||
|
debug_object_name = f'{object_type_name}_{tag}'
|
||||||
|
if debug_object_name in self.taxonomy.vulkan_names:
|
||||||
|
binding_data['VkDebugReportObjectTypeEXT'] = self[debug_object_name]
|
||||||
|
break
|
||||||
|
return binding_data
|
||||||
|
|
||||||
|
def create_signature(self, name: str):
|
||||||
|
binding_data = self[name]
|
||||||
|
binding_data: VulkanFunctionBindingData
|
||||||
|
parameters = []
|
||||||
|
for arg_name in binding_data.arguments:
|
||||||
|
parameter = inspect.Parameter(arg_name, inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
||||||
|
parameters.append(parameter)
|
||||||
|
binding_data.signature = inspect.Signature(parameters)
|
||||||
|
|
||||||
|
|
||||||
|
class BindingNames(Sequence):
|
||||||
|
def __init__(self, names: Collection[str], undefined: Collection[str]):
|
||||||
|
self.names = names
|
||||||
|
self.undefined = set(undefined)
|
||||||
|
|
||||||
|
def __contains__(self, name: str):
|
||||||
|
return name in self.names and name not in self.undefined
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for name in self.names:
|
||||||
|
if name not in self.undefined:
|
||||||
|
yield name
|
||||||
|
|
||||||
|
|
||||||
|
class BindAll:
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
if '__all__' not in instance.__dict__:
|
||||||
|
instance.__dict__['__all__'] = list(self.enumrate_all(instance))
|
||||||
|
return instance.__dict__['__all__']
|
||||||
|
|
||||||
|
def enumrate_all(self, instance):
|
||||||
|
for name in instance._implementation_.keys():
|
||||||
|
if name not in instance._implementation_.undefined:
|
||||||
|
try:
|
||||||
|
instance[name]
|
||||||
|
yield name
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
class Binding:
|
||||||
|
locals()['__all__'] = BindAll()
|
||||||
|
__slots__ = ('_implementation_', 'taxonomy', '__dict__', '__weakref__')
|
||||||
|
|
||||||
|
def __init__(self, taxonomy: Taxonomy, *, undefined: Collection[str] = ()):
|
||||||
|
self.__dict__['VK_NULL_HANDLE'] = 0
|
||||||
|
self.taxonomy = taxonomy
|
||||||
|
self._implementation_ = self._create_implementation(taxonomy, undefined)
|
||||||
|
|
||||||
|
def _create_implementation(self, taxonomy: Taxonomy, undefined: Collection[str]):
|
||||||
|
return BindingImplementation(taxonomy, cache=self.__dict__, undefined=undefined)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def undefined(self):
|
||||||
|
return self._implementation_.undefined
|
||||||
|
|
||||||
|
def __getattr__(self, name: str):
|
||||||
|
try:
|
||||||
|
return self._implementation_[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(name)
|
||||||
|
|
||||||
|
def __getitem__(self, name: str):
|
||||||
|
return self._implementation_[name]
|
||||||
122
src/dragiyski/vulkan/binding/_implementation/c.py
Normal file
122
src/dragiyski/vulkan/binding/_implementation/c.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import re
|
||||||
|
import ctypes
|
||||||
|
from collections.abc import Collection
|
||||||
|
import pycparser.c_ast
|
||||||
|
import pycparser.c_generator
|
||||||
|
|
||||||
|
c_type_map = {
|
||||||
|
'void': None,
|
||||||
|
'char': ctypes.c_char,
|
||||||
|
'signed char': ctypes.c_byte,
|
||||||
|
'unsigned char': ctypes.c_ubyte,
|
||||||
|
'short': ctypes.c_short,
|
||||||
|
'unsigned short': ctypes.c_ushort,
|
||||||
|
'int': ctypes.c_int,
|
||||||
|
'unsigned int': ctypes.c_uint,
|
||||||
|
'long': ctypes.c_long,
|
||||||
|
'long int': ctypes.c_long,
|
||||||
|
'unsigned long': ctypes.c_ulong,
|
||||||
|
'unsigned long int': ctypes.c_ulong,
|
||||||
|
'long long': ctypes.c_longlong,
|
||||||
|
'long long int': ctypes.c_longlong,
|
||||||
|
'unsigned long long': ctypes.c_ulonglong,
|
||||||
|
'unsigned long long int': ctypes.c_ulonglong,
|
||||||
|
'float': ctypes.c_float,
|
||||||
|
'double': ctypes.c_double,
|
||||||
|
'string': ctypes.c_char_p,
|
||||||
|
'int8_t': ctypes.c_int8,
|
||||||
|
'int16_t': ctypes.c_int16,
|
||||||
|
'int32_t': ctypes.c_int32,
|
||||||
|
'int64_t': ctypes.c_int64,
|
||||||
|
'uint8_t': ctypes.c_uint8,
|
||||||
|
'uint16_t': ctypes.c_uint16,
|
||||||
|
'uint32_t': ctypes.c_uint32,
|
||||||
|
'uint64_t': ctypes.c_uint64,
|
||||||
|
'size_t': ctypes.c_size_t,
|
||||||
|
'ssize_t': ctypes.c_ssize_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_ctypes = {
|
||||||
|
'VisualID': ctypes.c_uint32, # X11/Xlib.h: CARD32
|
||||||
|
'Window': ctypes.c_uint32, # X11/Xlib.h: CARD32 => XID
|
||||||
|
'RROutput': ctypes.c_uint32, # X11/extensions/Xrandr.h
|
||||||
|
'xcb_window_t': ctypes.c_uint32, # xcb/xcb.h
|
||||||
|
'xcb_visualid_t': ctypes.c_uint32, # xcb/xcb.h
|
||||||
|
'HINSTANCE': ctypes.c_void_p, # windows.h
|
||||||
|
'HWND': ctypes.c_void_p, # windows.h
|
||||||
|
'HMONITOR': ctypes.c_void_p, # windows.h
|
||||||
|
'HANDLE': ctypes.c_void_p, # windows.h
|
||||||
|
'DWORD': ctypes.c_uint32, # windows.h
|
||||||
|
'LPCSTR': ctypes.c_char_p, # windows.h
|
||||||
|
'LPCTSTR': ctypes.c_char_p, # windows.h
|
||||||
|
'LPCWSTR': ctypes.c_wchar_p, # windows.h
|
||||||
|
'zx_handle_t': ctypes.c_uint32, # zircon/types.h (Fuschia?)
|
||||||
|
'GgpStreamDescriptor': ctypes.c_uint32, # Google games platform?
|
||||||
|
'GgpFrameToken': ctypes.c_uint32, # Google games platform?
|
||||||
|
'NvSciSyncAttrList': ctypes.c_void_p, # NV Sci Platform
|
||||||
|
'NvSciSyncObj': ctypes.c_void_p, # NV Sci Platform
|
||||||
|
'NvSciSyncFence': ctypes.c_uint64 * 6, # NV Sci Platform
|
||||||
|
'NvSciBufAttrList': ctypes.c_void_p, # NV Sci Platform
|
||||||
|
'NvSciBufObj': ctypes.c_void_p, # NV Sci Platform
|
||||||
|
}
|
||||||
|
|
||||||
|
c_string_subst_table = {
|
||||||
|
'a': 0x07,
|
||||||
|
'b': 0x08,
|
||||||
|
'e': 0x1B,
|
||||||
|
'f': 0x0C,
|
||||||
|
'n': 0x0A,
|
||||||
|
'r': 0x0D,
|
||||||
|
't': 0x09,
|
||||||
|
'v': 0x0B,
|
||||||
|
'\\': 0x5C,
|
||||||
|
"'": 0x27,
|
||||||
|
'"': 0x22,
|
||||||
|
'?': 0x3F,
|
||||||
|
}
|
||||||
|
|
||||||
|
c_string_subst_rtable = {v: k for k, v in c_string_subst_table.items()}
|
||||||
|
|
||||||
|
python_ctype_map = {}
|
||||||
|
python_ctype_int_map = {}
|
||||||
|
python_ctype_uint_map = {}
|
||||||
|
python_ctype_sint_map = {}
|
||||||
|
python_ctype_float_map = {}
|
||||||
|
python_ctype_struct_map = {}
|
||||||
|
python_ctype_signature_map = {}
|
||||||
|
|
||||||
|
def __init__():
|
||||||
|
for name in dir(ctypes):
|
||||||
|
if not name.startswith('c_'):
|
||||||
|
continue
|
||||||
|
ctype = getattr(ctypes, name)
|
||||||
|
try:
|
||||||
|
size = ctypes.sizeof(ctype)
|
||||||
|
except TypeError:
|
||||||
|
continue
|
||||||
|
python_ctype_map[ctype] = size
|
||||||
|
if hasattr(ctype, '_type_') and isinstance(ctype._type_, str):
|
||||||
|
if ctype._type_ in 'bBhHiIlLqQnNP':
|
||||||
|
python_ctype_signature_map[ctype] = int
|
||||||
|
python_ctype_int_map[ctype] = size
|
||||||
|
if str.isupper(ctype._type_):
|
||||||
|
python_ctype_uint_map[ctype] = size
|
||||||
|
else:
|
||||||
|
python_ctype_sint_map[ctype] = size
|
||||||
|
elif ctype._type_ in 'efdg':
|
||||||
|
python_ctype_signature_map[ctype] = float
|
||||||
|
python_ctype_float_map[ctype] = size
|
||||||
|
elif ctype._type_ in 'FDG':
|
||||||
|
python_ctype_signature_map[ctype] = complex
|
||||||
|
python_ctype_struct_map[ctype._type_] = ctype
|
||||||
|
|
||||||
|
python_ctype_signature_map['P'] = int | None
|
||||||
|
python_ctype_signature_map['c'] = bytes
|
||||||
|
python_ctype_signature_map['z'] = bytes | None
|
||||||
|
python_ctype_signature_map['Z'] = str | None
|
||||||
|
python_ctype_signature_map['u'] = str
|
||||||
|
python_ctype_signature_map['?'] = bool
|
||||||
|
|
||||||
|
|
||||||
|
__init__()
|
||||||
|
del __init__
|
||||||
300
src/dragiyski/vulkan/binding/_implementation/ccompiler.py
Normal file
300
src/dragiyski/vulkan/binding/_implementation/ccompiler.py
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
import ctypes
|
||||||
|
import operator
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
import pycparser.c_ast as c_ast
|
||||||
|
from .c import c_type_map, python_ctype_map, python_ctype_uint_map, python_ctype_sint_map, python_ctype_float_map, python_ctype_struct_map
|
||||||
|
from .exception import CUnknownTypeException
|
||||||
|
from .cparser import CParser
|
||||||
|
|
||||||
|
|
||||||
|
def c_div_operator(result_type, left, right):
|
||||||
|
if result_type in python_ctype_float_map or result_type is float:
|
||||||
|
return result_type(py_truediv(result_type, left, right))
|
||||||
|
return result_type(py_floordiv(result_type, left, right))
|
||||||
|
|
||||||
|
|
||||||
|
def boolean_and(result_type, left, right):
|
||||||
|
return result_type(left and right)
|
||||||
|
|
||||||
|
|
||||||
|
def boolean_or(result_type, left, right):
|
||||||
|
return result_type(left or right)
|
||||||
|
|
||||||
|
|
||||||
|
def unary_operator_wrapper(operator_func):
|
||||||
|
def unary_op(result_type, operand):
|
||||||
|
if type(operand) in python_ctype_map:
|
||||||
|
operand = operand.value
|
||||||
|
return result_type(operator_func(operand))
|
||||||
|
return unary_op
|
||||||
|
|
||||||
|
|
||||||
|
def binary_operator_wrapper(operator_func):
|
||||||
|
def binary_op(result_type, left, right):
|
||||||
|
if type(left) in python_ctype_map:
|
||||||
|
left = left.value
|
||||||
|
if type(right) in python_ctype_map:
|
||||||
|
right = right.value
|
||||||
|
return result_type(operator_func(left, right))
|
||||||
|
return binary_op
|
||||||
|
|
||||||
|
|
||||||
|
py_truediv = binary_operator_wrapper(operator.truediv)
|
||||||
|
py_floordiv = binary_operator_wrapper(operator.floordiv)
|
||||||
|
|
||||||
|
binary_operators = {
|
||||||
|
'+': binary_operator_wrapper(operator.add),
|
||||||
|
'-': binary_operator_wrapper(operator.sub),
|
||||||
|
'*': binary_operator_wrapper(operator.mul),
|
||||||
|
'/': c_div_operator,
|
||||||
|
'%': binary_operator_wrapper(operator.mod),
|
||||||
|
'<<': binary_operator_wrapper(operator.lshift),
|
||||||
|
'>>': binary_operator_wrapper(operator.rshift),
|
||||||
|
'&': binary_operator_wrapper(operator.and_),
|
||||||
|
'|': binary_operator_wrapper(operator.or_),
|
||||||
|
'^': binary_operator_wrapper(operator.xor),
|
||||||
|
'&&': boolean_and,
|
||||||
|
'||': boolean_or,
|
||||||
|
}
|
||||||
|
|
||||||
|
unary_operators = {
|
||||||
|
'+': unary_operator_wrapper(operator.pos),
|
||||||
|
'-': unary_operator_wrapper(operator.neg),
|
||||||
|
'~': unary_operator_wrapper(operator.invert),
|
||||||
|
'!': unary_operator_wrapper(operator.not_),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def select_max_type_by_size_factory(map: Mapping[type, int]):
|
||||||
|
def selector(left_type, right_type):
|
||||||
|
left_size = map.get(left_type, 0)
|
||||||
|
right_size = map.get(right_type, 0)
|
||||||
|
if left_size > right_size:
|
||||||
|
return left_type
|
||||||
|
return right_type
|
||||||
|
return selector
|
||||||
|
|
||||||
|
|
||||||
|
def select_max_type_re_sign(unsigned_map: Mapping[type, int], signed_map: Mapping[type, int], signed_index):
|
||||||
|
def selector(*args):
|
||||||
|
signed = args[signed_index]
|
||||||
|
unsigned = args[1 - signed_index]
|
||||||
|
signed_size = signed_map.get(signed, 0)
|
||||||
|
unsigned_size = unsigned_map.get(unsigned, 0)
|
||||||
|
if signed_size >= unsigned_size:
|
||||||
|
return signed
|
||||||
|
return python_ctype_struct_map[unsigned._type_.lower()]
|
||||||
|
return selector
|
||||||
|
|
||||||
|
|
||||||
|
def select_argument_factory(index: int):
|
||||||
|
def selector(*args):
|
||||||
|
return args[index]
|
||||||
|
return selector
|
||||||
|
|
||||||
|
|
||||||
|
class CCompiler:
|
||||||
|
class CompileException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
compile_python_types = {int, float, bool, str, bytes}
|
||||||
|
|
||||||
|
compile_binary_type_map = {
|
||||||
|
'c_float:c_float': select_max_type_by_size_factory(python_ctype_float_map),
|
||||||
|
'c_float:c_uint': select_argument_factory(0),
|
||||||
|
'c_float:c_sint': select_argument_factory(0),
|
||||||
|
'c_float:py_float': select_argument_factory(0),
|
||||||
|
'c_float:py_int': select_argument_factory(0),
|
||||||
|
'c_uint:c_float': select_argument_factory(1),
|
||||||
|
'c_sint:c_float': select_argument_factory(1),
|
||||||
|
'py_float:c_float': select_argument_factory(1),
|
||||||
|
'py_int:c_float': select_argument_factory(1),
|
||||||
|
'c_uint:c_uint': select_max_type_by_size_factory(python_ctype_uint_map),
|
||||||
|
'c_sint:c_uint': select_max_type_re_sign(python_ctype_uint_map, python_ctype_sint_map, 0),
|
||||||
|
'c_uint:c_sint': select_max_type_re_sign(python_ctype_uint_map, python_ctype_sint_map, 1),
|
||||||
|
'c_sint:c_sint': select_max_type_by_size_factory(python_ctype_sint_map),
|
||||||
|
'c_uint:py_int': select_argument_factory(0),
|
||||||
|
'c_sint:py_int': select_argument_factory(0),
|
||||||
|
'py_int:c_uint': select_argument_factory(1),
|
||||||
|
'py_int:c_sint': select_argument_factory(1),
|
||||||
|
'py_float:py_int': select_argument_factory(0),
|
||||||
|
'py_int:py_float': select_argument_factory(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_value_map = {
|
||||||
|
c_ast.BinaryOp: 'compile_value_binary_op',
|
||||||
|
c_ast.UnaryOp: 'compile_value_unary_op',
|
||||||
|
c_ast.Cast: 'compile_value_cast',
|
||||||
|
c_ast.Constant: 'compile_value_constant',
|
||||||
|
c_ast.ID: 'compile_value_id',
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_type_map = {
|
||||||
|
c_ast.PtrDecl: 'compile_type_ptr',
|
||||||
|
c_ast.ArrayDecl: 'compile_type_array',
|
||||||
|
c_ast.TypeDecl: 'compile_type_decl',
|
||||||
|
c_ast.Struct: 'compile_type_decl_complex',
|
||||||
|
c_ast.Union: 'compile_type_decl_complex',
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_type_decl_map = {
|
||||||
|
c_ast.IdentifierType: 'compile_type_decl_identifier',
|
||||||
|
c_ast.Struct: 'compile_type_decl_complex',
|
||||||
|
c_ast.Union: 'compile_type_decl_complex',
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_type_ptr_map = {
|
||||||
|
None: ctypes.c_void_p,
|
||||||
|
ctypes.c_char: ctypes.c_char_p,
|
||||||
|
ctypes.c_wchar: ctypes.c_wchar_p,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
binding,
|
||||||
|
cparser: CParser,
|
||||||
|
*,
|
||||||
|
variables: Mapping[str, Any] = dict()
|
||||||
|
):
|
||||||
|
self.binding = binding
|
||||||
|
self.cparser = cparser
|
||||||
|
self.variables = variables
|
||||||
|
|
||||||
|
def compile_value_cast(self, node: c_ast.Cast):
|
||||||
|
ctype = self.compile_type(node.to_type.type)
|
||||||
|
value = self.compile_value_expr(node.expr)
|
||||||
|
if type(value) in python_ctype_map:
|
||||||
|
value = value.value
|
||||||
|
return ctype(value)
|
||||||
|
|
||||||
|
def compile_value_id(self, node: c_ast.ID):
|
||||||
|
if node.name in self.variables:
|
||||||
|
value = self.variables[node.name]
|
||||||
|
if callable(value):
|
||||||
|
return value(self, node)
|
||||||
|
return value
|
||||||
|
try:
|
||||||
|
return self.binding[node.name]
|
||||||
|
except KeyError:
|
||||||
|
raise self.CompileException(f'Undefined variable: {node.name}')
|
||||||
|
|
||||||
|
def compile_value_constant(self, node: c_ast.Constant):
|
||||||
|
if node.type not in c_type_map:
|
||||||
|
raise self.CompileException(f'Unsupported constant type: {node.type}')
|
||||||
|
return c_type_map[node.type](self.parse_value_constant(node))
|
||||||
|
|
||||||
|
def parse_value_constant(self, node: c_ast.Constant):
|
||||||
|
if 'int' in node.type:
|
||||||
|
return self.cparser.parse_c_int(node.value)
|
||||||
|
if node.type in ['float', 'double']:
|
||||||
|
return self.cparser.parse_c_float(node.value)
|
||||||
|
if node.type == 'string':
|
||||||
|
return self.cparser.parse_c_string(node.value)
|
||||||
|
raise self.CompileException(f'Unsupported constant type: {node.type}')
|
||||||
|
|
||||||
|
def compile_type_ptr(self, node: c_ast.PtrDecl):
|
||||||
|
ctype = self.compile_type(node.type)
|
||||||
|
if ctype in self.compile_type_ptr_map:
|
||||||
|
return self.compile_type_ptr_map[ctype]
|
||||||
|
ctype: 'type[ctypes._SimpleCData]'
|
||||||
|
return ctypes.POINTER(ctype)
|
||||||
|
|
||||||
|
def compile_type_array(self, node: c_ast.ArrayDecl):
|
||||||
|
ctype = self.compile_type(node.type)
|
||||||
|
ctype: 'type[ctypes._SimpleCData]'
|
||||||
|
length = self.compile_value_expr(node.dim)
|
||||||
|
if type(length) in python_ctype_map:
|
||||||
|
length = length.value
|
||||||
|
if not isinstance(length, int):
|
||||||
|
raise self.CompileException('Unsupported array dimension type: %s' % length.__class__.__name__)
|
||||||
|
return ctypes.ARRAY(ctype, length)
|
||||||
|
|
||||||
|
def compile_type_decl(self, node: c_ast.TypeDecl):
|
||||||
|
for type in node.type.__class__.__mro__:
|
||||||
|
if type in self.compile_type_decl_map:
|
||||||
|
return getattr(self, self.compile_type_decl_map[type])(node.type)
|
||||||
|
raise self.CompileException(f'Unsupported AST node type: {node.type.__class__.__name__}')
|
||||||
|
|
||||||
|
def compile_type_decl_identifier(self, node: c_ast.IdentifierType):
|
||||||
|
type_name = ' '.join(node.names)
|
||||||
|
return self.binding.get_ctype(type_name)
|
||||||
|
|
||||||
|
def compile_type_decl_complex(self, node: c_ast.Struct | c_ast.Union):
|
||||||
|
try:
|
||||||
|
value = self.binding.get_ctype(node.name)
|
||||||
|
except CUnknownTypeException as exception:
|
||||||
|
raise self.CompileException(f'Unsupported complex type: {node.name}') from exception
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if isinstance(node, c_ast.Struct) and issubclass(value, ctypes.Structure):
|
||||||
|
return value
|
||||||
|
if isinstance(node, c_ast.Union) and issubclass(value, ctypes.Union):
|
||||||
|
return value
|
||||||
|
raise self.CompileException(f'Unsupported complex type: {node.name}')
|
||||||
|
|
||||||
|
def type_deduction_binary(self, left, right):
|
||||||
|
args = [left, right]
|
||||||
|
arg_types = [type(arg) for arg in args]
|
||||||
|
# Fast path: no type conversion
|
||||||
|
if arg_types[0] == arg_types[1]:
|
||||||
|
return arg_types[0]
|
||||||
|
# Slow path: type conversion required
|
||||||
|
key = []
|
||||||
|
for arg_index in range(2):
|
||||||
|
arg_type = arg_types[arg_index]
|
||||||
|
for mro_type in arg_type.__mro__:
|
||||||
|
if mro_type in self.compile_python_types or mro_type in python_ctype_map:
|
||||||
|
arg_type = arg_types[arg_index] = mro_type
|
||||||
|
break
|
||||||
|
arg_key = [
|
||||||
|
'c' if arg_type in python_ctype_map else 'py'
|
||||||
|
]
|
||||||
|
if arg_key[0] == 'c':
|
||||||
|
if arg_type in python_ctype_float_map:
|
||||||
|
arg_key.append('float')
|
||||||
|
elif arg_type in python_ctype_sint_map:
|
||||||
|
arg_key.append('sint')
|
||||||
|
elif arg_type in python_ctype_uint_map:
|
||||||
|
arg_key.append('uint')
|
||||||
|
else:
|
||||||
|
arg_key.append(arg_type._type_)
|
||||||
|
else:
|
||||||
|
arg_key.append(arg_type.__name__)
|
||||||
|
arg_key = '_'.join(arg_key)
|
||||||
|
key.append(arg_key)
|
||||||
|
key = ':'.join(key)
|
||||||
|
if key not in self.compile_binary_type_map:
|
||||||
|
raise self.CompileException(f'Unsupported binary operator operand types: {arg_types[0].__name__}, {arg_types[1].__name__}')
|
||||||
|
return self.compile_binary_type_map[key](*arg_types)
|
||||||
|
|
||||||
|
def compile_value_binary_op(self, node: c_ast.BinaryOp):
|
||||||
|
left = self.compile_value_expr(node.left)
|
||||||
|
right = self.compile_value_expr(node.right)
|
||||||
|
result_type = self.type_deduction_binary(left, right)
|
||||||
|
try:
|
||||||
|
value = binary_operators[node.op](result_type, left, right)
|
||||||
|
except KeyError:
|
||||||
|
raise self.CompileException(f'Unsupported binary operator: {node.op}')
|
||||||
|
return value
|
||||||
|
|
||||||
|
def compile_value_unary_op(self, node: c_ast.UnaryOp):
|
||||||
|
operand = self.compile_value_expr(node.expr)
|
||||||
|
result_type = type(operand)
|
||||||
|
try:
|
||||||
|
value = unary_operators[node.op](result_type, operand)
|
||||||
|
except KeyError:
|
||||||
|
raise self.CompileException(f'Unsupported unary operator: {node.op}')
|
||||||
|
return value
|
||||||
|
|
||||||
|
def compile_type(self, node: c_ast.Node):
|
||||||
|
for type in node.__class__.__mro__:
|
||||||
|
if type in self.compile_type_map:
|
||||||
|
return getattr(self, self.compile_type_map[type])(node)
|
||||||
|
raise self.CompileException(f'Unsupported AST node type: {node.__class__.__name__}')
|
||||||
|
|
||||||
|
def compile_value_expr(self, node: c_ast.Node):
|
||||||
|
for type in node.__class__.__mro__:
|
||||||
|
if type in self.compile_value_map:
|
||||||
|
return getattr(self, self.compile_value_map[type])(node)
|
||||||
|
raise self.CompileException(f'Unsupported AST node type: {node.__class__.__name__}')
|
||||||
45
src/dragiyski/vulkan/binding/_implementation/cgenerator.py
Normal file
45
src/dragiyski/vulkan/binding/_implementation/cgenerator.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import re
|
||||||
|
import pycparser
|
||||||
|
|
||||||
|
|
||||||
|
class CGenerator(pycparser.c_generator.CGenerator):
|
||||||
|
from .c import c_string_subst_table, c_string_subst_rtable
|
||||||
|
REGEXP_SUBST_TABLE = re.compile('[%s]' % ''.join(r'\x%02X' % x for x in c_string_subst_table.values()))
|
||||||
|
|
||||||
|
def visit_Code(self, node):
|
||||||
|
return node.code
|
||||||
|
|
||||||
|
class Code(pycparser.c_ast.Node):
|
||||||
|
__slots__ = ('code', 'coord', '__weakref__')
|
||||||
|
|
||||||
|
def __init__(self, code):
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
def c_string_subst_unicode_char(match):
|
||||||
|
char = match.group(0)
|
||||||
|
code = ord(char)
|
||||||
|
if code < 65535:
|
||||||
|
return r'\u%04X' % code
|
||||||
|
else:
|
||||||
|
return r'\U%08X' % code
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def string_subst_c_table(cls, match):
|
||||||
|
char = match.group(0)
|
||||||
|
code = ord(char)
|
||||||
|
return '\\%s' % cls.c_string_subst_rtable[code]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def string_subst_unicode_char(match):
|
||||||
|
char = match.group(0)
|
||||||
|
code = ord(char)
|
||||||
|
if code < 65535:
|
||||||
|
return r'\u%04X' % code
|
||||||
|
else:
|
||||||
|
return r'\U%08X' % code
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_c_string(cls: 'CGenerator', value: str):
|
||||||
|
value = cls.REGEXP_SUBST_TABLE.sub(cls.string_subst_c_table, value)
|
||||||
|
value = re.sub(r'[^\u0000-\u007F]', cls.string_subst_unicode_char, value)
|
||||||
|
return '"%s"' % value
|
||||||
93
src/dragiyski/vulkan/binding/_implementation/cparser.py
Normal file
93
src/dragiyski/vulkan/binding/_implementation/cparser.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
from collections.abc import Collection
|
||||||
|
import re
|
||||||
|
import pycparser
|
||||||
|
|
||||||
|
|
||||||
|
class CParser(pycparser.CParser):
|
||||||
|
from .c import c_string_subst_table, c_string_subst_rtable
|
||||||
|
REGEXP_PARSE_HEX_INT = re.compile(r'(-?)0x([0-9A-Fa-f]+)')
|
||||||
|
REGEXP_PARSE_DEC_INT = re.compile(r'-?[0-9]+')
|
||||||
|
REGEXP_PARSE_FLOAT = re.compile(r'-?[0-9]+(?:\.[0-9]+)?')
|
||||||
|
REGEXP_PARSE_UNICODE_SUBST_8 = re.compile(r'\\U([0-9A-Fa-f]{8})')
|
||||||
|
REGEXP_PARSE_UNICODE_SUBST_4 = re.compile(r'\\u([0-9A-Fa-f]{4})')
|
||||||
|
REGEXP_PARSE_HEX_SUBST = re.compile(r'\\x([0-9A-Fa-f]{2})')
|
||||||
|
REGEXP_PARSE_OCT_SUBST = re.compile(r'\\([0-7]{3})')
|
||||||
|
REGEXP_PARSE_SLASH_SUBST = re.compile(r'\\(.)')
|
||||||
|
|
||||||
|
def __init__(self, type_names: Collection):
|
||||||
|
super().__init__()
|
||||||
|
self._type_names = type_names
|
||||||
|
|
||||||
|
def _lex_type_lookup_func(self, name):
|
||||||
|
if super()._lex_type_lookup_func(name):
|
||||||
|
return True
|
||||||
|
return name in self._type_names
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_c_int(cls, value):
|
||||||
|
match = cls.REGEXP_PARSE_HEX_INT.match(value)
|
||||||
|
if match:
|
||||||
|
return (-1 if len(match.group(1)) else 1) * int(match.group(2), 16)
|
||||||
|
match = cls.REGEXP_PARSE_DEC_INT.match(value)
|
||||||
|
if match:
|
||||||
|
return int(match.group(0), 10)
|
||||||
|
raise ValueError('Invalid integer value: %r' % value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_c_float(cls, value):
|
||||||
|
match = cls.REGEXP_PARSE_FLOAT.match(value)
|
||||||
|
if match:
|
||||||
|
return float(match.group(0))
|
||||||
|
raise ValueError('Invalid float value: %r' % value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_c_string(cls, value):
|
||||||
|
if len(value) < 2 or value[0] != '"' or value[-1] != '"':
|
||||||
|
raise ValueError('Invalid string value (missing open or close quotes): %s' % value)
|
||||||
|
value = value[1:-1]
|
||||||
|
|
||||||
|
value = cls.REGEXP_PARSE_UNICODE_SUBST_8.sub(cls.subst_unicode_hex, value)
|
||||||
|
value = cls.REGEXP_PARSE_UNICODE_SUBST_4.sub(cls.subst_unicode_hex, value)
|
||||||
|
value = cls.REGEXP_PARSE_HEX_SUBST.sub(cls.subst_unicode_hex, value)
|
||||||
|
value = cls.REGEXP_PARSE_OCT_SUBST.sub(cls.subst_unicode_oct, value)
|
||||||
|
value = cls.REGEXP_PARSE_SLASH_SUBST.sub(cls.subst_slash_escape, value)
|
||||||
|
return value.encode('utf-8')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_c_char(cls, value):
|
||||||
|
if len(value) < 3 or value[0] != "'" or value[-1] != "'":
|
||||||
|
raise ValueError('Invalid char value (missing open or close single quotes): %s' % value)
|
||||||
|
value = value[1:-1]
|
||||||
|
value = cls.REGEXP_PARSE_UNICODE_SUBST_8.sub(cls.subst_unicode_hex, value)
|
||||||
|
value = cls.REGEXP_PARSE_UNICODE_SUBST_4.sub(cls.subst_unicode_hex, value)
|
||||||
|
value = cls.REGEXP_PARSE_HEX_SUBST.sub(cls.subst_unicode_hex, value)
|
||||||
|
value = cls.REGEXP_PARSE_OCT_SUBST.sub(cls.subst_unicode_oct, value)
|
||||||
|
value = cls.REGEXP_PARSE_SLASH_SUBST.sub(cls.subst_slash_escape, value)
|
||||||
|
char_bytes = value.encode('utf-8')
|
||||||
|
if len(char_bytes) != 1:
|
||||||
|
raise ValueError('Invalid char value (must be a single byte): %s' % value)
|
||||||
|
return ord(char_bytes[0])
|
||||||
|
|
||||||
|
class ParseError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def subst_unicode_hex(match):
|
||||||
|
return chr(int(match.group(1), 16))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def subst_unicode_oct(match):
|
||||||
|
return chr(int(match.group(1), 8))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subst_string_c_table(cls, match):
|
||||||
|
char = match.group(0)
|
||||||
|
code = ord(char)
|
||||||
|
return '\\%s' % cls.c_string_subst_rtable[code]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subst_slash_escape(cls, match):
|
||||||
|
seq = match.group(1)
|
||||||
|
if seq in cls.c_string_subst_table:
|
||||||
|
return chr(cls.c_string_subst_table[seq])
|
||||||
|
return match.group(0)
|
||||||
162
src/dragiyski/vulkan/binding/_implementation/cpreprocessor.py
Normal file
162
src/dragiyski/vulkan/binding/_implementation/cpreprocessor.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import re
|
||||||
|
import pycparser.c_ast
|
||||||
|
from .cparser import CParser
|
||||||
|
from .cgenerator import CGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class CPreprocessor(dict):
|
||||||
|
REGEXP_MULTILINE_COMMENT = re.compile(r'\/\*.*\*\/')
|
||||||
|
REGEXP_SINGLELINE_COMMENT = re.compile(r'\/\/.*')
|
||||||
|
REGEXP_FUNC_MACRO = re.compile(r'\s*#\s*define\s+(\w+)\(([^)]+)\)(.*)')
|
||||||
|
REGEXP_VALUE_MACRO = re.compile(r'\s*#\s*define\s+(\w+)\s+(.*)')
|
||||||
|
REGEXP_C_AST_CHILD_NAME = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)(?:\[([0-9+])\])?')
|
||||||
|
|
||||||
|
class CFunctionMacro:
|
||||||
|
def __init__(self, name: str, arguments: list[str], template: list):
|
||||||
|
self.name = name
|
||||||
|
self.arguments = arguments
|
||||||
|
self.template = template
|
||||||
|
|
||||||
|
class CValueMacro:
|
||||||
|
def __init__(self, name: str, value: str):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __init__(self, parser: CParser, generator: CGenerator):
|
||||||
|
self.parser = parser
|
||||||
|
self.generator = generator
|
||||||
|
|
||||||
|
def define_macro(self, code: str, *, name: str = None):
|
||||||
|
macro = self._parse_define_code(code)
|
||||||
|
if macro is None:
|
||||||
|
return
|
||||||
|
if macro.name in self:
|
||||||
|
raise ValueError('Macro %r is already defined.' % macro.name)
|
||||||
|
if name is not None and macro.name != name:
|
||||||
|
raise ValueError('Macro name %r does not match expected name %r.' % (macro.name, name))
|
||||||
|
self[macro.name] = macro
|
||||||
|
|
||||||
|
def preprocess_code_ast(self, code: str):
|
||||||
|
ast = self.parser.parse(code)
|
||||||
|
while self.preprocess_ast(ast):
|
||||||
|
code = self.generator.visit(ast)
|
||||||
|
ast = self.parser.parse(code)
|
||||||
|
return ast
|
||||||
|
|
||||||
|
def preprocess_code(self, code: str):
|
||||||
|
ast = self.preprocess_code_ast(code)
|
||||||
|
return self.generator.visit(ast)
|
||||||
|
|
||||||
|
def preprocess_ast(self, node: pycparser.c_ast.Node):
|
||||||
|
has_substitution = False
|
||||||
|
for name, child_node in node.children():
|
||||||
|
if isinstance(child_node, pycparser.c_ast.ID):
|
||||||
|
if child_node.name in self and isinstance(self[child_node.name], self.CValueMacro):
|
||||||
|
self._ast_set_attribute(node, name, self.generator.Code(self[child_node.name].value))
|
||||||
|
has_substitution = True
|
||||||
|
continue
|
||||||
|
if isinstance(child_node, pycparser.c_ast.FuncCall):
|
||||||
|
if child_node.name.name in self and isinstance(self[child_node.name.name], self.CFunctionMacro):
|
||||||
|
assert isinstance(self[child_node.name.name], self.CFunctionMacro), """isinstance(self[child_node.name.name], self.CFunctionMacro)"""
|
||||||
|
args = [self.generator.visit(x) for x in child_node.args]
|
||||||
|
macro = self[child_node.name.name]
|
||||||
|
macro: 'CPreprocessor.CFunctionMacro'
|
||||||
|
if len(macro.arguments) != len(args):
|
||||||
|
raise self.parser.ParseError('Macro "%s" accept "%d" arguments, but called with "%d" arguments' % (child_node.name.name, len(macro.arguments), len(args)))
|
||||||
|
code = []
|
||||||
|
for part in macro.template:
|
||||||
|
if isinstance(part, str):
|
||||||
|
code.append(part)
|
||||||
|
elif part['string']:
|
||||||
|
code.append(self.generator.generate_c_string(args[part['index']]))
|
||||||
|
else:
|
||||||
|
code.append(args[part['index']])
|
||||||
|
code = ''.join(code)
|
||||||
|
self._ast_set_attribute(node, name, self.generator.Code(code))
|
||||||
|
has_substitution = True
|
||||||
|
continue
|
||||||
|
has_descendant_substitution = self.preprocess_ast(child_node)
|
||||||
|
has_substitution = has_substitution or has_descendant_substitution
|
||||||
|
return has_substitution
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _preprocessor_get_lines(cls, code) -> list[str]:
|
||||||
|
lines = []
|
||||||
|
code = cls.REGEXP_MULTILINE_COMMENT.sub('', code)
|
||||||
|
code = code.splitlines()
|
||||||
|
is_continuation_line = False
|
||||||
|
for line in code:
|
||||||
|
line = cls.REGEXP_SINGLELINE_COMMENT.sub('', line)
|
||||||
|
if len(line.strip()) <= 0:
|
||||||
|
is_continuation_line = False
|
||||||
|
continue
|
||||||
|
if is_continuation_line:
|
||||||
|
lines[-1] += ' ' + line
|
||||||
|
if lines[-1].endswith('\\'):
|
||||||
|
lines[-1] = lines[-1][:-1]
|
||||||
|
is_continuation_line = True
|
||||||
|
else:
|
||||||
|
is_continuation_line = False
|
||||||
|
else:
|
||||||
|
if line.endswith('\\'):
|
||||||
|
is_continuation_line = True
|
||||||
|
line = line[:-1]
|
||||||
|
else:
|
||||||
|
is_continuation_line = False
|
||||||
|
lines.append(line)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parse_define_code(cls, code):
|
||||||
|
code = cls._preprocessor_get_lines(code)
|
||||||
|
if len(code) > 1:
|
||||||
|
raise ValueError('Unable to define macro with more than one preprocessor line.')
|
||||||
|
if len(code) == 0:
|
||||||
|
return None
|
||||||
|
code = code[0]
|
||||||
|
is_func_macro = cls.REGEXP_FUNC_MACRO.fullmatch(code)
|
||||||
|
if is_func_macro is not None:
|
||||||
|
return cls._parser_preprocessor_func(is_func_macro)
|
||||||
|
is_value_macro = cls.REGEXP_VALUE_MACRO.fullmatch(code)
|
||||||
|
if is_value_macro is not None:
|
||||||
|
return cls._parser_preprocessor_value(is_value_macro)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parser_preprocessor_func(cls, data: re.Match):
|
||||||
|
arguments = [arg.strip() for arg in data.group(2).split(',')]
|
||||||
|
code = data.group(3).strip()
|
||||||
|
# This might not properly handle if argument name appear in a C string, but it is good enough for now.
|
||||||
|
# pycparser.cparser cannot be used, as it does not handle #-prefix and ## operator
|
||||||
|
words = [y for x in code.split('##') for y in re.split(r'\b', x)]
|
||||||
|
template = []
|
||||||
|
for word in words:
|
||||||
|
if word in arguments:
|
||||||
|
if len(template) > 0 and template[-1] == '#':
|
||||||
|
template[-1] = {'name': word, 'index': arguments.index(word), 'string': True}
|
||||||
|
else:
|
||||||
|
template.append({'name': word, 'index': arguments.index(word), 'string': False})
|
||||||
|
else:
|
||||||
|
template.append(word)
|
||||||
|
return cls.CFunctionMacro(name=data.group(1), arguments=arguments, template=template)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parser_preprocessor_value(cls, data: re.Match):
|
||||||
|
return cls.CValueMacro(name=data.group(1), value=data.group(2))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _ast_set_attribute(cls, target, name, value):
|
||||||
|
if isinstance(name, int):
|
||||||
|
target[name] = value
|
||||||
|
pattern = cls.REGEXP_C_AST_CHILD_NAME.fullmatch(name)
|
||||||
|
if pattern is None:
|
||||||
|
raise AttributeError(target, name)
|
||||||
|
index = pattern.group(2)
|
||||||
|
if index is not None:
|
||||||
|
if len(index) > 0:
|
||||||
|
index = int(index, 10)
|
||||||
|
else:
|
||||||
|
index = None
|
||||||
|
if index is not None:
|
||||||
|
getattr(target, pattern.group(1))[index] = value
|
||||||
|
else:
|
||||||
|
setattr(target, pattern.group(1), value)
|
||||||
10
src/dragiyski/vulkan/binding/_implementation/exception.py
Normal file
10
src/dragiyski/vulkan/binding/_implementation/exception.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
class UndefinedBindingException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CCompileException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CUnknownTypeException(Exception):
|
||||||
|
pass
|
||||||
11
src/dragiyski/vulkan/binding/_lazy.py
Normal file
11
src/dragiyski/vulkan/binding/_lazy.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
def create_lazy_module(module, BaseClass):
|
||||||
|
ignore_names = set().union(*list(set(dir(Class)) for Class in module.__class__.__mro__))
|
||||||
|
transfer_names = {name for name in set(dir(module)) - ignore_names if name.startswith('__') and name.endswith('__')}
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<module %r from %r>' % (self.__name__, self.__file__)
|
||||||
|
|
||||||
|
return type(module.__name__, (BaseClass,), {
|
||||||
|
'__repr__': __repr__,
|
||||||
|
**{name: getattr(module, name) for name in transfer_names},
|
||||||
|
})
|
||||||
49
tests/test_lazy_binding.py
Normal file
49
tests/test_lazy_binding.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestLazyBinding(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.binding = importlib.import_module("dragiyski.vulkan.binding")
|
||||||
|
|
||||||
|
def assert_callable_macro(self, name: str, params: list[str]):
|
||||||
|
self.assertTrue(hasattr(self.binding, name))
|
||||||
|
macro = getattr(self.binding, name)
|
||||||
|
self.assertTrue(callable(macro))
|
||||||
|
sig = inspect.signature(macro)
|
||||||
|
self.assertEqual(list(sig.parameters.keys()), params)
|
||||||
|
for param in sig.parameters.values():
|
||||||
|
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
||||||
|
|
||||||
|
def test_define_vk_make_version(self):
|
||||||
|
self.assert_callable_macro('VK_MAKE_VERSION', ['major', 'minor', 'patch'])
|
||||||
|
|
||||||
|
def test_define_vk_version_major(self):
|
||||||
|
self.assert_callable_macro('VK_VERSION_MAJOR', ['version'])
|
||||||
|
|
||||||
|
def test_define_vk_version_minor(self):
|
||||||
|
self.assert_callable_macro('VK_VERSION_MINOR', ['version'])
|
||||||
|
|
||||||
|
def test_define_vk_version_patch(self):
|
||||||
|
self.assert_callable_macro('VK_VERSION_PATCH', ['version'])
|
||||||
|
|
||||||
|
def test_define_vk_make_api_version(self):
|
||||||
|
self.assert_callable_macro('VK_MAKE_API_VERSION', ['variant', 'major', 'minor', 'patch'])
|
||||||
|
|
||||||
|
def test_define_vk_api_version_variant(self):
|
||||||
|
self.assert_callable_macro('VK_API_VERSION_VARIANT', ['version'])
|
||||||
|
|
||||||
|
def test_define_vk_api_version_major(self):
|
||||||
|
self.assert_callable_macro('VK_API_VERSION_MAJOR', ['version'])
|
||||||
|
|
||||||
|
def test_define_vk_api_version_minor(self):
|
||||||
|
self.assert_callable_macro('VK_API_VERSION_MINOR', ['version'])
|
||||||
|
|
||||||
|
def test_define_vk_api_version_patch(self):
|
||||||
|
self.assert_callable_macro('VK_API_VERSION_PATCH', ['version'])
|
||||||
|
|
||||||
|
def test_define_vk_header_version(self):
|
||||||
|
self.assertTrue(hasattr(self.binding, 'VK_HEADER_VERSION'))
|
||||||
|
self.assertIsInstance(self.binding.VK_HEADER_VERSION, int)
|
||||||
Reference in New Issue
Block a user