From e6047175850dd789a73ecae7b0891fdbd649a75d Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Mon, 11 Aug 2025 11:54:11 -0700 Subject: [PATCH 01/17] switch to pyproject --- pyproject.toml | 22 ++++++++++++++++++++++ setup.py | 20 -------------------- 2 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..427db53 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "itanium_demangler" +version = "1.1" +authors = [ + { name = "whitequark", email = "whitequark@whitequark.org" }, +] +description = "Pure Python parser for mangled itanium symbols" +readme = "README.md" +requires-python = ">=3.7" +license = { text = "BSD" } +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "License :: OSI Approved :: BSD License", +] + +[project.urls] +"Homepage" = "https://github.com/whitequark/python-itanium_demangler" \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 70cf148..0000000 --- a/setup.py +++ /dev/null @@ -1,20 +0,0 @@ -import setuptools - - -setuptools.setup( - name="itanium_demangler", - version="1.1", - author="whitequark", - author_email="whitequark@whitequark.org", - description="Pure Python parser for mangled itanium symbols", - long_description=open("README.md").read(), - long_description_content_type="text/markdown", - license="BSD", - url="https://github.com/whitequark/python-itanium_demangler", - packages=setuptools.find_packages(), - classifiers=[ - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 2", - "Operating System :: OS Independent", - ], -) From 84a0fb3ef0c8ce7bbaf059edb4a199aa9c5a83f2 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Mon, 11 Aug 2025 11:56:08 -0700 Subject: [PATCH 02/17] use in-project license file --- MANIFEST.in | 1 - pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 449fd12..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include LICENSE-0BSD.txt diff --git a/pyproject.toml b/pyproject.toml index 427db53..124896b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ description = "Pure Python parser for mangled itanium symbols" readme = "README.md" requires-python = ">=3.7" -license = { text = "BSD" } +license = { file = "LICENSE-0BSD.txt" } classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent", From e0e3009ec0f01c0951d98b566f30a6fda69e9ae8 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Mon, 11 Aug 2025 11:57:10 -0700 Subject: [PATCH 03/17] lock uv --- uv.lock | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 uv.lock diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..da55455 --- /dev/null +++ b/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 1 +requires-python = ">=3.7" + +[[package]] +name = "itanium-demangler" +version = "1.1" +source = { editable = "." } From 46ad65a5506db082c14ba4109b47032dedc27b15 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Tue, 12 Aug 2025 16:31:45 -0700 Subject: [PATCH 04/17] pull in pytest --- pyproject.toml | 7 +- uv.lock | 312 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 124896b..8549e6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,4 +19,9 @@ classifiers = [ ] [project.urls] -"Homepage" = "https://github.com/whitequark/python-itanium_demangler" \ No newline at end of file +"Homepage" = "https://github.com/whitequark/python-itanium_demangler" + +[project.optional-dependencies] +test = [ + "pytest", +] diff --git a/uv.lock b/uv.lock index da55455..a35d125 100644 --- a/uv.lock +++ b/uv.lock @@ -1,8 +1,320 @@ version = 1 revision = 1 requires-python = ">=3.7" +resolution-markers = [ + "python_full_version >= '3.9'", + "python_full_version == '3.8.*'", + "python_full_version < '3.8'", +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "zipp", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/82/f6e29c8d5c098b6be61460371c2c5591f4a335923639edec43b3830650a4/importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4", size = 53569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/94/64287b38c7de4c90683630338cf28f129decbba0a44f0c6db35a873c73c4/importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5", size = 22934 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] [[package]] name = "itanium-demangler" version = "1.1" source = { editable = "." } + +[package.optional-dependencies] +test = [ + { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] + +[package.metadata] +requires-dist = [{ name = "pytest", marker = "extra == 'test'" }] +provides-extras = ["test"] + +[[package]] +name = "packaging" +version = "24.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/b5/b43a27ac7472e1818c4bafd44430e69605baefe1f34440593e0332ec8b4d/packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9", size = 147882 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", size = 53488 }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/42/8f2833655a29c4e9cb52ee8a2be04ceac61bcff4a680fb338cbd3d1e322d/pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3", size = 61613 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/32/4a79112b8b87b21450b066e102d6608907f4c885ed7b04c3fdb085d4d6ae/pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", size = 17695 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pytest" +version = "7.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.8' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.8'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.8'" }, + { name = "iniconfig", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "packaging", version = "24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "pluggy", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, + { name = "tomli", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1f/9d8e98e4133ffb16c90f3b405c43e38d3abb715bb5d7a63a5a684f7e46a3/pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", size = 1357116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ff/f6e8b8f39e08547faece4bd80f89d5a8de68a38b2d179cc1c4490ffa3286/pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8", size = 325287 }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.8.*' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.8.*'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "packaging", version = "25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, + { name = "tomli", version = "2.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.9' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "packaging", version = "25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pygments", marker = "python_full_version >= '3.9'" }, + { name = "tomli", version = "2.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.8'", +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/8b/0111dd7d6c1478bf83baa1cab85c686426c7a6274119aceb2bd9d35395ad/typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2", size = 72876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", size = 33232 }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.8.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, +] + +[[package]] +name = "zipp" +version = "3.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/27/f0ac6b846684cecce1ee93d32450c45ab607f65c2e0255f0092032d91f07/zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", size = 18454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556", size = 6758 }, +] From 84c4d410c5e755d3ea64e07fb3d0cfb6658d5c2f Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Tue, 12 Aug 2025 16:40:50 -0700 Subject: [PATCH 05/17] move to pytest --- tests/test.py | 173 ----------------------------- tests/test_all.py | 276 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+), 173 deletions(-) delete mode 100644 tests/test.py create mode 100644 tests/test_all.py diff --git a/tests/test.py b/tests/test.py deleted file mode 100644 index e4aabf2..0000000 --- a/tests/test.py +++ /dev/null @@ -1,173 +0,0 @@ -import unittest - -from itanium_demangler import parse, _operators, _builtin_types - - -class TestDemangler(unittest.TestCase): - def assertParses(self, mangled, ast): - result = parse(mangled) - self.assertEqual(result, ast) - - def assertDemangles(self, mangled, demangled): - result = parse(mangled) - if result is not None: - result = str(result) - self.assertEqual(result, demangled) - - def test_name(self): - self.assertDemangles('_Z3foo', 'foo') - self.assertDemangles('_Z3x', None) - - def test_ctor_dtor(self): - self.assertDemangles('_ZN3fooC1E', 'foo::{ctor}') - self.assertDemangles('_ZN3fooC2E', 'foo::{base ctor}') - self.assertDemangles('_ZN3fooC3E', 'foo::{allocating ctor}') - self.assertDemangles('_ZN3fooD0E', 'foo::{deleting dtor}') - self.assertDemangles('_ZN3fooD1E', 'foo::{dtor}') - self.assertDemangles('_ZN3fooD2E', 'foo::{base dtor}') - self.assertDemangles('_ZN3fooC1IcEEc', 'foo::{ctor}(char)') - self.assertDemangles('_ZN3fooD1IcEEc', 'foo::{dtor}(char)') - - def test_operator(self): - for op in _operators: - if _operators[op] in ['new', 'new[]', 'delete', 'delete[]']: - continue - self.assertDemangles('_Z' + op, 'operator' + _operators[op]) - self.assertDemangles('_Znw', 'operator new') - self.assertDemangles('_Zna', 'operator new[]') - self.assertDemangles('_Zdl', 'operator delete') - self.assertDemangles('_Zda', 'operator delete[]') - self.assertDemangles('_Zcvi', 'operator int') - - def test_std_substs(self): - self.assertDemangles('_ZSt3foo', 'std::foo') - self.assertDemangles('_ZStN3fooE', 'std::foo') - self.assertDemangles('_ZSs', 'std::string') - self.assertParses('_ZSt', None) - self.assertDemangles('_Z3fooISt6vectorE', 'foo') - self.assertDemangles('_ZSaIhE', 'std::allocator') - - def test_nested_name(self): - self.assertDemangles('_ZN3fooE', 'foo') - self.assertDemangles('_ZN3foo5bargeE', 'foo::barge') - self.assertDemangles('_ZN3fooIcE5bargeE', 'foo::barge') - self.assertDemangles('_ZNK3fooE', 'foo const') - self.assertDemangles('_ZNV3fooE', 'foo volatile') - self.assertDemangles('_ZNKR3fooE', 'foo const&') - self.assertDemangles('_ZNKO3fooE', 'foo const&&') - self.assertParses('_ZNKO3foo', None) - - def test_template_args(self): - self.assertDemangles('_Z3fooIcE', 'foo') - self.assertDemangles('_ZN3fooIcEE', 'foo') - self.assertParses('_Z3fooI', None) - - def test_builtin_types(self): - for ty in _builtin_types: - self.assertDemangles('_Z1fI' + ty + 'E', 'f<' + str(_builtin_types[ty]) + '>') - - def test_qualified_type(self): - self.assertDemangles('_Z1fIriE', 'f') - self.assertDemangles('_Z1fIKiE', 'f') - self.assertDemangles('_Z1fIViE', 'f') - self.assertDemangles('_Z1fIVVViE', 'f') - - def test_function_type(self): - self.assertDemangles('_Z1fv', 'f()') - self.assertDemangles('_Z1fi', 'f(int)') - self.assertDemangles('_Z1fic', 'f(int, char)') - self.assertDemangles('_ZN1fEic', 'f(int, char)') - self.assertDemangles('_ZN1fIEEic', 'int f<>(char)') - self.assertDemangles('_ZN1fIEC1Eic', 'f<>::{ctor}(int, char)') - - def test_indirect_type(self): - self.assertDemangles('_Z1fIPiE', 'f') - self.assertDemangles('_Z1fIPPiE', 'f') - self.assertDemangles('_Z1fIRiE', 'f') - self.assertDemangles('_Z1fIOiE', 'f') - self.assertDemangles('_Z1fIKRiE', 'f') - self.assertDemangles('_Z1fIRKiE', 'f') - - def test_literal(self): - self.assertDemangles('_Z1fILi1EE', 'f<(int)1>') - self.assertDemangles('_Z1fIL_Z1gEE', 'f') - - def test_argpack(self): - self.assertDemangles('_Z1fILb0EJciEE', 'f<(bool)0, char, int>') - self.assertDemangles('_Z1fILb0EIciEE', 'f<(bool)0, char, int>') - self.assertDemangles('_Z1fIJciEEvDpOT_', 'void f(char, int)') - self.assertDemangles('_Z1fIIciEEvDpOT_', 'void f(char, int)') - - def test_special(self): - self.assertDemangles('_ZTV1f', 'vtable for f') - self.assertDemangles('_ZTT1f', 'vtt for f') - self.assertDemangles('_ZTI1f', 'typeinfo for f') - self.assertDemangles('_ZTS1f', 'typeinfo name for f') - self.assertDemangles('_ZThn16_1fv', 'non-virtual thunk for f()') - self.assertDemangles('_ZTv16_8_1fv', 'virtual thunk for f()') - self.assertDemangles('_ZGV1f', 'guard variable for f') - self.assertDemangles('_ZGTt1fv', 'transaction clone for f()') - - def test_template_param(self): - self.assertDemangles('_ZN1fIciEEvT_PT0_', 'void f(char, int*)') - self.assertParses('_ZN1fIciEEvT_PT0', None) - - def test_substitution(self): - self.assertDemangles('_Z3fooIEvS_', 'void foo<>(foo)') - self.assertDemangles('_ZN3foo3barIES_E', 'foo::bar<>::foo') - self.assertDemangles('_ZN3foo3barIES0_E', 'foo::bar<>::foo::bar') - self.assertDemangles('_ZN3foo3barIES1_E', 'foo::bar<>::foo::bar<>') - self.assertParses('_ZN3foo3barIES_ES2_', None) - self.assertDemangles('_Z3fooIS_E', 'foo') - self.assertDemangles('_ZSt3fooIS_E', 'std::foo') - self.assertDemangles('_Z3fooIPiEvS0_', 'void foo(int*)') - self.assertDemangles('_Z3fooISaIcEEvS0_', - 'void foo>(std::allocator)') - self.assertDemangles('_Z3fooI3barS0_E', 'foo') - self.assertDemangles('_ZN2n11fEPNS_1bEPNS_2n21cEPNS2_2n31dE', - 'n1::f(n1::b*, n1::n2::c*, n1::n2::n3::d*)') - self.assertDemangles('_ZN1f1gES_IFvvEE', 'f::g(f)') - self.assertDemangles('_ZplIcET_S0_', 'char operator+(char)') - self.assertParses('_ZplIcET_S1_', None) - # Operator template results don't get added to substitutions - self.assertParses('_ZStplIcEvS0_', None) - - def test_abi_tag(self): - self.assertDemangles('_Z3fooB5cxx11v', 'foo[abi:cxx11]()') - - def test_const(self): - self.assertDemangles('_ZL3foo', 'foo') - - def test_operator_template(self): - self.assertDemangles('_ZmiIiE', 'operator-') - self.assertDemangles('_ZmiIiEvv', 'void operator-()') - self.assertDemangles('_ZmiIiEvKT_RT_', 'void operator-(int const, int&)') - self.assertDemangles('_ZcviIiE', 'operator int') - self.assertDemangles('_ZcviIiEv', 'operator int()') - self.assertDemangles('_ZcviIiET_T_', 'operator int(int, int)') - - def test_array(self): - self.assertDemangles('_Z1fA1_c', 'f(char[(int)1])') - self.assertDemangles('_Z1fRA1_c', 'f(char(&)[(int)1])') - self.assertDemangles('_Z1fIA1_cS0_E', 'f') - self.assertParses('_Z1fA1c', None) - - def test_function(self): - self.assertDemangles('_Z1fFvvE', 'f(void ())') - self.assertDemangles('_Z1fPFvvE', 'f(void (*)())') - self.assertDemangles('_Z1fPPFvvE', 'f(void (**)())') - self.assertDemangles('_Z1fRPFvvE', 'f(void (*&)())') - self.assertDemangles('_Z1fKFvvE', 'f(void () const)') - - def test_member_data(self): - self.assertDemangles('_Z1fM3fooi', 'f(int foo::*)') - self.assertDemangles('_Z1fMN3foo3barEi', 'f(int foo::bar::*)') - self.assertDemangles('_Z1fM3fooN3bar1XE', 'f(bar::X foo::*)') - self.assertDemangles('_Z1fM3fooIcE3bar', 'f(bar foo::*)') - self.assertDemangles('_Z1fM3foo3barIlE', 'f(bar foo::*)') - self.assertDemangles('_Z3fooPM2ABi', 'foo(int AB::**)') - - def test_member_function(self): - self.assertDemangles('_Z1fM3fooFvvE', 'f(void (foo::*)())') - self.assertDemangles('_Z1fMN3foo3barEFvvE', 'f(void (foo::bar::*)())') - self.assertDemangles('_Z3fooRM3barFviE', 'foo(void (bar::*&)(int))') diff --git a/tests/test_all.py b/tests/test_all.py new file mode 100644 index 0000000..0a36bf5 --- /dev/null +++ b/tests/test_all.py @@ -0,0 +1,276 @@ +import pytest + +from itanium_demangler import parse, _operators, _builtin_types + + +def assert_parses(mangled, ast): + result = parse(mangled) + assert result == ast + + +def assert_demangles(mangled, demangled): + result = parse(mangled) + if result is not None: + result = str(result) + assert result == demangled + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z3foo', 'foo'), + ('_Z3x', None), +]) +def test_name(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_ZN3fooC1E', 'foo::{ctor}'), + ('_ZN3fooC2E', 'foo::{base ctor}'), + ('_ZN3fooC3E', 'foo::{allocating ctor}'), + ('_ZN3fooD0E', 'foo::{deleting dtor}'), + ('_ZN3fooD1E', 'foo::{dtor}'), + ('_ZN3fooD2E', 'foo::{base dtor}'), + ('_ZN3fooC1IcEEc', 'foo::{ctor}(char)'), + ('_ZN3fooD1IcEEc', 'foo::{dtor}(char)'), +]) +def test_ctor_dtor(mangled, demangled): + assert_demangles(mangled, demangled) + + +# Create a list of operators for parametrization, excluding special cases +_operator_tests = [ + (op, 'operator' + _operators[op]) for op in _operators + if _operators[op] not in ['new', 'new[]', 'delete', 'delete[]'] +] + [ + ('nw', 'operator new'), + ('na', 'operator new[]'), + ('dl', 'operator delete'), + ('da', 'operator delete[]'), +] + + +@pytest.mark.parametrize("op_code, op_str", _operator_tests) +def test_operators(op_code, op_str): + assert_demangles('_Z' + op_code, op_str) + + +def test_operator_cast(): + assert_demangles('_Zcvi', 'operator int') + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_ZSt3foo', 'std::foo'), + ('_ZStN3fooE', 'std::foo'), + ('_ZSs', 'std::string'), + ('_Z3fooISt6vectorE', 'foo'), + ('_ZSaIhE', 'std::allocator'), +]) +def test_std_substs(mangled, demangled): + assert_demangles(mangled, demangled) + + +def test_std_substs_none(): + assert_parses('_ZSt', None) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_ZN3fooE', 'foo'), + ('_ZN3foo5bargeE', 'foo::barge'), + ('_ZN3fooIcE5bargeE', 'foo::barge'), + ('_ZNK3fooE', 'foo const'), + ('_ZNV3fooE', 'foo volatile'), + ('_ZNKR3fooE', 'foo const&'), + ('_ZNKO3fooE', 'foo const&&'), +]) +def test_nested_name(mangled, demangled): + assert_demangles(mangled, demangled) + + +def test_nested_name_none(): + assert_parses('_ZNKO3foo', None) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z3fooIcE', 'foo'), + ('_ZN3fooIcEE', 'foo'), +]) +def test_template_args(mangled, demangled): + assert_demangles(mangled, demangled) + + +def test_template_args_none(): + assert_parses('_Z3fooI', None) + + +@pytest.mark.parametrize("type_code, type_node", _builtin_types.items()) +def test_builtin_types(type_code, type_node): + mangled = '_Z1fI' + type_code + 'E' + demangled = 'f<' + str(type_node) + '>' + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z1fIriE', 'f'), + ('_Z1fIKiE', 'f'), + ('_Z1fIViE', 'f'), + ('_Z1fIVVViE', 'f'), +]) +def test_qualified_type(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z1fv', 'f()'), + ('_Z1fi', 'f(int)'), + ('_Z1fic', 'f(int, char)'), + ('_ZN1fEic', 'f(int, char)'), + ('_ZN1fIEEic', 'int f<>(char)'), + ('_ZN1fIEC1Eic', 'f<>::{ctor}(int, char)'), +]) +def test_function_type(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z1fIPiE', 'f'), + ('_Z1fIPPiE', 'f'), + ('_Z1fIRiE', 'f'), + ('_Z1fIOiE', 'f'), + ('_Z1fIKRiE', 'f'), + ('_Z1fIRKiE', 'f'), +]) +def test_indirect_type(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z1fILi1EE', 'f<(int)1>'), + ('_Z1fIL_Z1gEE', 'f'), +]) +def test_literal(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z1fILb0EJciEE', 'f<(bool)0, char, int>'), + ('_Z1fILb0EIciEE', 'f<(bool)0, char, int>'), + ('_Z1fIJciEEvDpOT_', 'void f(char, int)'), + ('_Z1fIIciEEvDpOT_', 'void f(char, int)'), +]) +def test_argpack(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_ZTV1f', 'vtable for f'), + ('_ZTT1f', 'vtt for f'), + ('_ZTI1f', 'typeinfo for f'), + ('_ZTS1f', 'typeinfo name for f'), + ('_ZThn16_1fv', 'non-virtual thunk for f()'), + ('_ZTv16_8_1fv', 'virtual thunk for f()'), + ('_ZGV1f', 'guard variable for f'), + ('_ZGTt1fv', 'transaction clone for f()'), +]) +def test_special(mangled, demangled): + assert_demangles(mangled, demangled) + + +def test_template_param(): + assert_demangles('_ZN1fIciEEvT_PT0_', 'void f(char, int*)') + + +def test_template_param_none(): + assert_parses('_ZN1fIciEEvT_PT0', None) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z3fooIEvS_', 'void foo<>(foo)'), + ('_ZN3foo3barIES_E', 'foo::bar<>::foo'), + ('_ZN3foo3barIES0_E', 'foo::bar<>::foo::bar'), + ('_ZN3foo3barIES1_E', 'foo::bar<>::foo::bar<>'), + ('_Z3fooIS_E', 'foo'), + ('_ZSt3fooIS_E', 'std::foo'), + ('_Z3fooIPiEvS0_', 'void foo(int*)'), + ('_Z3fooISaIcEEvS0_', + 'void foo>(std::allocator)'), + ('_Z3fooI3barS0_E', 'foo'), + ('_ZN2n11fEPNS_1bEPNS_2n21cEPNS2_2n31dE', + 'n1::f(n1::b*, n1::n2::c*, n1::n2::n3::d*)'), + ('_ZN1f1gES_IFvvEE', 'f::g(f)'), + ('_ZplIcET_S0_', 'char operator+(char)'), +]) +def test_substitution(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled", [ + '_ZN3foo3barIES_ES2_', + '_ZplIcET_S1_', + '_ZStplIcEvS0_', +]) +def test_substitution_none(mangled): + assert_parses(mangled, None) + + +def test_abi_tag(): + assert_demangles('_Z3fooB5cxx11v', 'foo[abi:cxx11]()') + + +def test_const(): + assert_demangles('_ZL3foo', 'foo') + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_ZmiIiE', 'operator-'), + ('_ZmiIiEvv', 'void operator-()'), + ('_ZmiIiEvKT_RT_', 'void operator-(int const, int&)'), + ('_ZcviIiE', 'operator int'), + ('_ZcviIiEv', 'operator int()'), + ('_ZcviIiET_T_', 'operator int(int, int)'), +]) +def test_operator_template(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z1fA1_c', 'f(char[(int)1])'), + ('_Z1fRA1_c', 'f(char(&)[(int)1])'), + ('_Z1fIA1_cS0_E', 'f'), +]) +def test_array(mangled, demangled): + assert_demangles(mangled, demangled) + + +def test_array_none(): + assert_parses('_Z1fA1c', None) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z1fFvvE', 'f(void ())'), + ('_Z1fPFvvE', 'f(void (*)())'), + ('_Z1fPPFvvE', 'f(void (**)())'), + ('_Z1fRPFvvE', 'f(void (*&)())'), + ('_Z1fKFvvE', 'f(void () const)'), +]) +def test_function(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", + [('_Z1fM3fooi', 'f(int foo::*)'), + ('_Z1fMN3foo3barEi', 'f(int foo::bar::*)'), + ('_Z1fM3fooN3bar1XE', 'f(bar::X foo::*)'), + ('_Z1fM3fooIcE3bar', 'f(bar foo::*)'), + ('_Z1fM3foo3barIlE', 'f(bar foo::*)'), + ('_Z3fooPM2ABi', 'foo(int AB::**)')]) +def test_member_data(mangled, demangled): + assert_demangles(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z1fM3fooFvvE', 'f(void (foo::*)())'), + ('_Z1fMN3foo3barEFvvE', 'f(void (foo::bar::*)())'), + ('_Z3fooRM3barFviE', 'foo(void (bar::*&)(int))'), +]) +def test_member_function(mangled, demangled): + assert_demangles(mangled, demangled) From 91aa482c30adbfe9bc0c98e34c2ef1d11a347434 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Fri, 15 Aug 2025 02:45:25 -0700 Subject: [PATCH 06/17] early attempt with abominable function type --- itanium_demangler/__init__.py | 146 ++++++++++++++++++++++++++++++++-- tests/test_all.py | 56 +++++++++---- 2 files changed, 179 insertions(+), 23 deletions(-) diff --git a/itanium_demangler/__init__.py b/itanium_demangler/__init__.py index d57b52f..853e9bb 100644 --- a/itanium_demangler/__init__.py +++ b/itanium_demangler/__init__.py @@ -20,6 +20,8 @@ * `qual_name`: `node.value` (`tuple`) holds a sequence of `name` and `tpl_args` nodes, possibly ending in a `ctor`, `dtor` or `operator` node * `abi`: `node.value` holds a name node, `node.qual` (`frozenset`) holds a set of ABI tags + * `abominable`: `node.value` holds a name node, `node.qual` (`frozenset`) holds any + combinations from `"const"`, `"volatile"`, `"&"`, and `"&&"` Type nodes: * `name` and `qual_name` specify a type by its name @@ -164,6 +166,32 @@ def __str__(self): else: return repr(self) + def encoding(self): + if self.kind == 'name': + return f'{len(self.value)}{self.value}' + elif self.kind == 'builtin': + return _mangled_builtin_types.get(self, "") + elif self.kind == 'qual_name': + # Note: This doesn't handle substitutions, which is a complex topic. + if _is_nested_name(self): + return f'N{"".join(p.encoding() for p in self.value)}E' + else: + return "".join(p.encoding() for p in self.value) + elif self.kind == 'tpl_args': + return f'I{"".join(p.encoding() for p in self.value)}E' + elif self.kind == 'pointer': + return f'P{self.value.encoding()}' + elif self.kind == 'lvalue': + return f'R{self.value.encoding()}' + elif self.kind == 'rvalue': + return f'O{self.value.encoding()}' + elif self.kind == 'ctor': + return _mangled_ctor_map[self.value] + elif self.kind == 'dtor': + return _mangled_dtor_map[self.value] + + return "" + def left(self): if self.kind == "pointer": return self.value.left() + "*" @@ -196,12 +224,40 @@ def __repr__(self): def __str__(self): if self.kind == 'abi': - return str(self.value) + "".join(['[abi:' + tag + ']' for tag in self.qual]) + return str(self.value) + "".join(['[abi:' + tag + ']' for tag in sorted(self.qual)]) elif self.kind == 'cv_qual': - return ' '.join([str(self.value)] + list(self.qual)) + return ' '.join([str(self.value)] + sorted(self.qual)) + elif self.kind == 'abominable': + return ' '.join([str(self.value)] + _order_abominable_qualifiers(self)) else: return repr(self) + def encoding(self, parent=None): + if self.kind == 'abi': + return self.value.encoding() + ''.join(f'B{len(x)}{x}' for x in sorted(self.qual)) + elif self.kind == 'cv_qual': + text = "" + if 'const' in self.qual: + text += 'K' + if 'volatile' in self.qual: + text += 'V' + if 'restrict' in self.qual: + text += 'r' + return f'{text}{self.value.encoding()}' + elif self.kind == 'abominable': + text = "" + if 'const' in self.qual: + text += 'K' + if 'volatile' in self.qual: + text += 'V' + if '&' in self.qual: + text += 'R' + elif '&&' in self.qual: + text += 'O' + return f'N{text}{"".join(p.encoding() for p in self.value.value)}E' + else: + return "" + def left(self): return str(self) @@ -225,6 +281,11 @@ def __str__(self): else: return repr(self) + def encoding(self): + if self.kind == 'literal': + return 'L' + self.ty.encoding() + str(self.value) + 'E' + return "" + def left(self): return str(self) @@ -248,12 +309,19 @@ def __str__(self): result = "" if self.ret_ty is not None: result += str(self.ret_ty) + ' ' + qual = None if self.name is not None: - result += str(self.name) + if self.name.kind == 'abominable': + qual = _order_abominable_qualifiers(self.name) + result += str(self.name.value) + else: + result += str(self.name) if self.arg_tys == (Node('builtin', 'void'),): result += '()' else: result += '(' + ', '.join(map(str, self.arg_tys)) + ')' + if qual: + result += ' ' + ' '.join(qual) return result else: return repr(self) @@ -281,6 +349,23 @@ def right(self): else: return "" + def encoding(self): + if self.kind == 'func': + if self.name is None: + result = 'F' + else: + result = self.name.encoding() + if self.ret_ty is not None: + result += self.ret_ty.encoding() + result += ''.join(p.encoding() for p in self.arg_tys) + # it's a bare-function-type + if self.name is None: + result += 'E' + return result + else: + return "" + + def map(self, f): if self.kind == 'func': return self._replace(name=f(self.name) if self.name else None, @@ -351,6 +436,9 @@ def right(self): else: return "" + def encoding(self): + return f'M{self.cls_ty.encoding()}{self.member_ty.encoding()}' + def map(self, f): if self.kind in ('data', 'func'): return self._replace(cls_ty=f(self.cls_ty) if self.cls_ty else None, @@ -460,6 +548,10 @@ def map(self, f): 'Dn': Node('qual_name', (Node('name', 'std'), Node('builtin', 'nullptr_t'))) } +_mangled_ctor_map = {v: k for k, v in _ctor_dtor_map.items() if k.startswith('C')} +_mangled_dtor_map = {v: k for k, v in _ctor_dtor_map.items() if k.startswith('D')} +_mangled_builtin_types = {v: k for k, v in _builtin_types.items()} + def _handle_cv(qualifiers, node): qualifier_set = set() @@ -482,6 +574,20 @@ def _handle_indirect(qualifier, node): return Node('rvalue', node) return node +def _handle_abominable(cv_qualifiers, ref_qualifier, node): + qualifier_set = set() + if 'V' in cv_qualifiers: + qualifier_set.add('volatile') + if 'K' in cv_qualifiers: + qualifier_set.add('const') + if ref_qualifier == 'R': + qualifier_set.add('&') + elif ref_qualifier == 'O': + qualifier_set.add('&&') + if qualifier_set: + return QualNode('abominable', value=node, qual=frozenset(qualifier_set)) + else: + return node _NUMBER_RE = re.compile(r"\d+") @@ -593,8 +699,7 @@ def _parse_name(cursor, is_nested=False): else: cursor.add_subst(Node('qual_name', tuple(nodes))) node = Node('qual_name', tuple(nodes)) - node = _handle_cv(match.group('cv_qual'), node) - node = _handle_indirect(match.group('ref_qual'), node) + node = _handle_abominable(match.group('cv_qual'), match.group('ref_qual'), node) elif match.group('template_param') is not None: seq_id = _parse_seq_id(cursor) if seq_id is None: @@ -884,7 +989,12 @@ def parse(raw): ast = _expand_arg_packs(ast) return ast -def is_ctor_or_dtor(ast) -> bool: + +def mangle(ast): + return f'_Z{ast.encoding()}' + + +def _is_ctor_or_dtor(ast) -> bool: if ast.kind == 'func': return _is_ctor_or_dtor(ast.name) elif ast.kind == 'qual_name': @@ -893,6 +1003,30 @@ def is_ctor_or_dtor(ast) -> bool: else: return False + +def _order_abominable_qualifiers(ast) -> list: + result = [] + if 'const' in ast.qual: + result.append('const') + if 'volatile' in ast.qual: + result.append('volatile') + if '&' in ast.qual: + result.append('&') + elif '&&' in ast.qual: + result.append('&&') + return result + + +def _is_nested_name(ast) -> bool: + if ast.kind == 'qual_name': + if len(ast.value) == 2: + return ast.value[-1].kind != 'tpl_args' + else: + return len(ast.value) > 1 + else: + return ast.kind == 'abominable' + + # ================================================================================================ diff --git a/tests/test_all.py b/tests/test_all.py index 0a36bf5..1eab2b1 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -1,6 +1,6 @@ import pytest -from itanium_demangler import parse, _operators, _builtin_types +from itanium_demangler import parse, mangle, _operators, _builtin_types def assert_parses(mangled, ast): @@ -9,18 +9,28 @@ def assert_parses(mangled, ast): def assert_demangles(mangled, demangled): + """Asserts that a mangled name demangles to the expected string.""" result = parse(mangled) if result is not None: result = str(result) assert result == demangled +def assert_roundtrip(mangled, demangled): + """Asserts demangling and that the parsed AST mangles back to the original.""" + result = parse(mangled) + if result is not None: + assert mangle(result) == mangled + result = str(result) + assert result == demangled + + @pytest.mark.parametrize("mangled, demangled", [ ('_Z3foo', 'foo'), ('_Z3x', None), ]) def test_name(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", [ @@ -79,11 +89,14 @@ def test_std_substs_none(): ('_ZN3fooIcE5bargeE', 'foo::barge'), ('_ZNK3fooE', 'foo const'), ('_ZNV3fooE', 'foo volatile'), - ('_ZNKR3fooE', 'foo const&'), - ('_ZNKO3fooE', 'foo const&&'), + ('_ZNKR3fooE', 'foo const &'), + ('_ZNKO3fooE', 'foo const &&'), ]) def test_nested_name(mangled, demangled): - assert_demangles(mangled, demangled) + if demangled.isalnum(): + assert_demangles(mangled, demangled) + else: + assert_roundtrip(mangled, demangled) def test_nested_name_none(): @@ -92,10 +105,10 @@ def test_nested_name_none(): @pytest.mark.parametrize("mangled, demangled", [ ('_Z3fooIcE', 'foo'), - ('_ZN3fooIcEE', 'foo'), + ('_ZN2ns3fooIcEE', 'ns::foo'), ]) def test_template_args(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) def test_template_args_none(): @@ -113,22 +126,22 @@ def test_builtin_types(type_code, type_node): ('_Z1fIriE', 'f'), ('_Z1fIKiE', 'f'), ('_Z1fIViE', 'f'), - ('_Z1fIVVViE', 'f'), + ('_Z1fIKViE', 'f'), ]) def test_qualified_type(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", [ ('_Z1fv', 'f()'), ('_Z1fi', 'f(int)'), ('_Z1fic', 'f(int, char)'), - ('_ZN1fEic', 'f(int, char)'), - ('_ZN1fIEEic', 'int f<>(char)'), + ('_Z1fic', 'f(int, char)'), + ('_Z1fIEic', 'int f<>(char)'), ('_ZN1fIEC1Eic', 'f<>::{ctor}(int, char)'), ]) def test_function_type(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", [ @@ -140,7 +153,7 @@ def test_function_type(mangled, demangled): ('_Z1fIRKiE', 'f'), ]) def test_indirect_type(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", [ @@ -213,7 +226,8 @@ def test_substitution_none(mangled): def test_abi_tag(): - assert_demangles('_Z3fooB5cxx11v', 'foo[abi:cxx11]()') + assert_roundtrip('_Z3fooB5cxx11v', 'foo[abi:cxx11]()') + assert_roundtrip('_Z1AB3barB3foo', 'A[abi:bar][abi:foo]') def test_const(): @@ -253,7 +267,7 @@ def test_array_none(): ('_Z1fKFvvE', 'f(void () const)'), ]) def test_function(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", @@ -264,7 +278,7 @@ def test_function(mangled, demangled): ('_Z1fM3foo3barIlE', 'f(bar foo::*)'), ('_Z3fooPM2ABi', 'foo(int AB::**)')]) def test_member_data(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", [ @@ -273,4 +287,12 @@ def test_member_data(mangled, demangled): ('_Z3fooRM3barFviE', 'foo(void (bar::*&)(int))'), ]) def test_member_function(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_Z3fooIRKN5boost2lsEEiv', 'int foo()'), + ('_ZNKR5boost2ls5memfnEv', 'boost::ls::memfn() const &'), +]) +def test_calls(mangled, demangled): + assert_roundtrip(mangled, demangled) From f9b849ffc8bb6bfee0edb7bb415ba597e1e7a0a9 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Fri, 15 Aug 2025 11:37:46 -0700 Subject: [PATCH 07/17] support operators --- itanium_demangler/__init__.py | 38 ++++++++++++++++++++++++++--------- tests/test_all.py | 4 ++-- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/itanium_demangler/__init__.py b/itanium_demangler/__init__.py index 853e9bb..4226f55 100644 --- a/itanium_demangler/__init__.py +++ b/itanium_demangler/__init__.py @@ -15,6 +15,8 @@ the type of destructor * `oper`: `node.value` (`str`) holds a symbolic operator name, without the keyword "operator" + * `oper_unary`: same as `oper` but to distinguish unary operators from their binary + counterparts * `oper_cast`: `node.value` holds a type node * `tpl_args`: `node.value` (`tuple`) holds a sequence of type nodes * `qual_name`: `node.value` (`tuple`) holds a sequence of `name` and `tpl_args` nodes, @@ -130,8 +132,8 @@ def __str__(self): return '{base dtor}' else: assert False - elif self.kind == 'oper': - if self.value.startswith('new') or self.value.startswith('delete'): + elif self.kind in ('oper', 'oper_unary'): + if self.value[0].isalpha(): return 'operator ' + self.value else: return 'operator' + self.value @@ -189,6 +191,12 @@ def encoding(self): return _mangled_ctor_map[self.value] elif self.kind == 'dtor': return _mangled_dtor_map[self.value] + elif self.kind == 'oper': + return _mangled_operators[self.value] + elif self.kind == 'oper_unary': + return _mangled_unary_operators[self.value] + elif self.kind == 'oper_cast': + return f'cv{self.value.encoding()}' return "" @@ -466,15 +474,19 @@ def map(self, f): 'Sd': [Node('name', 'std'), Node('name', 'iostream')], } +# `!` is also unary, but without ambiguity +_unary_operators = { + 'ps': '+', + 'ng': '-', + 'ad': '&', + 'de': '*', +} + _operators = { 'nw': 'new', 'na': 'new[]', 'dl': 'delete', 'da': 'delete[]', - 'ps': '+', # (unary) - 'ng': '-', # (unary) - 'ad': '&', # (unary) - 'de': '*', # (unary) 'co': '~', 'pl': '+', 'mi': '-', @@ -551,6 +563,8 @@ def map(self, f): _mangled_ctor_map = {v: k for k, v in _ctor_dtor_map.items() if k.startswith('C')} _mangled_dtor_map = {v: k for k, v in _ctor_dtor_map.items() if k.startswith('D')} _mangled_builtin_types = {v: k for k, v in _builtin_types.items()} +_mangled_unary_operators = {v: k for k, v in _unary_operators.items()} +_mangled_operators = {v: k for k, v in _operators.items()} def _handle_cv(qualifiers, node): @@ -663,7 +677,11 @@ def _parse_name(cursor, is_nested=False): elif match.group('std_name') is not None: node = Node('qual_name', _std_names[match.group('std_name')]) elif match.group('operator_name') is not None: - node = Node('oper', _operators[match.group('operator_name')]) + encoded = match.group('operator_name') + if encoded in _unary_operators: + node = Node('oper_unary', _unary_operators[encoded]) + else: + node = Node('oper', _operators[encoded]) elif match.group('operator_cv') is not None: ty = _parse_type(cursor) if ty is None: @@ -727,11 +745,11 @@ def _parse_name(cursor, is_nested=False): node = QualNode('abi', node, frozenset(abi_tags)) if not is_nested and cursor.accept('I') and ( - node.kind in ('name', 'oper', 'oper_cast') or + node.kind in ('name', 'oper', 'oper_unary', 'oper_cast') or match.group('std_prefix') is not None or match.group('std_name') is not None or match.group('substitution') is not None): - if node.kind in ('name', 'oper', 'oper_cast') or match.group('std_prefix') is not None: + if node.kind in ('name', 'oper', 'oper_unary', 'oper_cast') or match.group('std_prefix') is not None: cursor.add_subst(node) # ::= templ_args = _parse_until_end(cursor, 'tpl_args', _parse_type) if templ_args is None: @@ -739,7 +757,7 @@ def _parse_name(cursor, is_nested=False): node = Node('qual_name', (node, templ_args)) if ((match.group('std_prefix') is not None or match.group('std_name') is not None) and - node.value[0].value[1].kind not in ('oper', 'oper_cast')): + node.value[0].value[1].kind not in ('oper', 'oper_unary', 'oper_cast')): cursor.add_subst(node) return node diff --git a/tests/test_all.py b/tests/test_all.py index 1eab2b1..b5cfe94 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -61,11 +61,11 @@ def test_ctor_dtor(mangled, demangled): @pytest.mark.parametrize("op_code, op_str", _operator_tests) def test_operators(op_code, op_str): - assert_demangles('_Z' + op_code, op_str) + assert_roundtrip('_Z' + op_code, op_str) def test_operator_cast(): - assert_demangles('_Zcvi', 'operator int') + assert_roundtrip('_Zcvi', 'operator int') @pytest.mark.parametrize("mangled, demangled", [ From 15d63854a052b924ef723f9c032b010c6ee41bea Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Fri, 15 Aug 2025 12:05:24 -0700 Subject: [PATCH 08/17] support std::nullptr_t --- itanium_demangler/__init__.py | 7 ++++--- tests/test_all.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/itanium_demangler/__init__.py b/itanium_demangler/__init__.py index 4226f55..50bd44b 100644 --- a/itanium_demangler/__init__.py +++ b/itanium_demangler/__init__.py @@ -172,10 +172,11 @@ def encoding(self): if self.kind == 'name': return f'{len(self.value)}{self.value}' elif self.kind == 'builtin': - return _mangled_builtin_types.get(self, "") + return _mangled_builtin_types[self] elif self.kind == 'qual_name': - # Note: This doesn't handle substitutions, which is a complex topic. - if _is_nested_name(self): + if self == _builtin_types['Dn']: + return 'Dn' + elif _is_nested_name(self): return f'N{"".join(p.encoding() for p in self.value)}E' else: return "".join(p.encoding() for p in self.value) diff --git a/tests/test_all.py b/tests/test_all.py index b5cfe94..1882eae 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -119,7 +119,7 @@ def test_template_args_none(): def test_builtin_types(type_code, type_node): mangled = '_Z1fI' + type_code + 'E' demangled = 'f<' + str(type_node) + '>' - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", [ From 1409d51d513502f9d233c6c68826868bf75d42e7 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Fri, 15 Aug 2025 15:21:44 -0700 Subject: [PATCH 09/17] support references to entities --- itanium_demangler/__init__.py | 10 ++++++++-- tests/test_all.py | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/itanium_demangler/__init__.py b/itanium_demangler/__init__.py index 50bd44b..0ec37a7 100644 --- a/itanium_demangler/__init__.py +++ b/itanium_demangler/__init__.py @@ -33,6 +33,7 @@ `"const"`, `"volatile"`, or `"restrict"` * `literal`: `node.value` (`str`) holds the literal representation as-is, `node.ty` holds a type node specifying the type of the literal + * `entity`: `node.value` holds a name node that refers to a declared entity * `function`: `node.name` holds a name node specifying the function name, `node.ret_ty` holds a type node specifying the return type of a template function, if any, or `None`, ``node.arg_tys` (`tuple`) holds a sequence of type nodes @@ -287,12 +288,16 @@ def __repr__(self): def __str__(self): if self.kind == 'literal': return '(' + str(self.ty) + ')' + str(self.value) + elif self.kind == 'entity': + return str(self.value) else: return repr(self) def encoding(self): if self.kind == 'literal': - return 'L' + self.ty.encoding() + str(self.value) + 'E' + return f'L{self.ty.encoding()}{self.value}E' + elif self.kind == 'entity': + return f'L{mangle(self.value)}E' return "" def left(self): @@ -302,6 +307,7 @@ def right(self): return "" def map(self, f): + # does not affect references to entities if self.kind == 'literal': return self._replace(ty=f(self.ty)) else: @@ -856,7 +862,7 @@ def _parse_expr_primary(cursor): return None elif match.group('mangled_name') is not None: mangled_name = cursor.advance_until('E') - return _parse_mangled_name(_Cursor(mangled_name)) + return CastNode('entity', _parse_mangled_name(_Cursor(mangled_name)), None) elif match.group('literal') is not None: ty = _parse_type(cursor) if ty is None: diff --git a/tests/test_all.py b/tests/test_all.py index 1882eae..a10ab36 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -161,7 +161,7 @@ def test_indirect_type(mangled, demangled): ('_Z1fIL_Z1gEE', 'f'), ]) def test_literal(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", [ @@ -293,6 +293,7 @@ def test_member_function(mangled, demangled): @pytest.mark.parametrize("mangled, demangled", [ ('_Z3fooIRKN5boost2lsEEiv', 'int foo()'), ('_ZNKR5boost2ls5memfnEv', 'boost::ls::memfn() const &'), + ('_Z3da2IL_Z4wellEEiv', 'int da2()') ]) def test_calls(mangled, demangled): assert_roundtrip(mangled, demangled) From df864b62c92db732eae1231282a80cf4a50705b5 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Fri, 15 Aug 2025 16:01:23 -0700 Subject: [PATCH 10/17] support array types --- itanium_demangler/__init__.py | 7 +++++++ tests/test_all.py | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/itanium_demangler/__init__.py b/itanium_demangler/__init__.py index 0ec37a7..dac665c 100644 --- a/itanium_demangler/__init__.py +++ b/itanium_demangler/__init__.py @@ -417,6 +417,13 @@ def right(self): else: return "" + def encoding(self): + if self.kind == 'array': + # instantiation-dependent array bound expression is not supported + return f'A{self.dimension.value}_{self.ty.encoding()}' + else: + return "" + def map(self, f): if self.kind == 'array': return self._replace(dimension=f(self.dimension) if self.dimension else None, diff --git a/tests/test_all.py b/tests/test_all.py index a10ab36..697161d 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -44,7 +44,7 @@ def test_name(mangled, demangled): ('_ZN3fooD1IcEEc', 'foo::{dtor}(char)'), ]) def test_ctor_dtor(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) # Create a list of operators for parametrization, excluding special cases @@ -252,7 +252,10 @@ def test_operator_template(mangled, demangled): ('_Z1fIA1_cS0_E', 'f'), ]) def test_array(mangled, demangled): - assert_demangles(mangled, demangled) + if 'S' in mangled: + assert_demangles(mangled, demangled) + else: + assert_roundtrip(mangled, demangled) def test_array_none(): From a1dc0b58f15f30820ba67c7a4cf8e5290417a9a4 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Fri, 15 Aug 2025 16:35:34 -0700 Subject: [PATCH 11/17] support recovering std abbrev without tpl_args or cvref --- itanium_demangler/__init__.py | 21 ++++++++++++++++++++- tests/test_all.py | 5 +++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/itanium_demangler/__init__.py b/itanium_demangler/__init__.py index dac665c..3cc53cb 100644 --- a/itanium_demangler/__init__.py +++ b/itanium_demangler/__init__.py @@ -178,7 +178,13 @@ def encoding(self): if self == _builtin_types['Dn']: return 'Dn' elif _is_nested_name(self): - return f'N{"".join(p.encoding() for p in self.value)}E' + prefix, rest = _infer_std_names(self.value) + if prefix is None: + return f'N{"".join(p.encoding() for p in rest)}E' + elif len(rest) > 1: + return f'N{prefix}{"".join(p.encoding() for p in rest)}E' + else: + return f'{prefix}{"".join(p.encoding() for p in rest)}' else: return "".join(p.encoding() for p in self.value) elif self.kind == 'tpl_args': @@ -1059,6 +1065,19 @@ def _is_nested_name(ast) -> bool: return ast.kind == 'abominable' +def _infer_std_names(components): + best_match_prefix = None + best_match_len = 0 + + for prefix, std_name in _std_names.items(): + std_len = len(std_name) + if len(components) >= std_len and list(components[:std_len]) == std_name: + if std_len > best_match_len: + best_match_len = std_len + best_match_prefix = prefix + + return best_match_prefix, list(components[best_match_len:]) + # ================================================================================================ diff --git a/tests/test_all.py b/tests/test_all.py index 697161d..679aa04 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -70,13 +70,14 @@ def test_operator_cast(): @pytest.mark.parametrize("mangled, demangled", [ ('_ZSt3foo', 'std::foo'), - ('_ZStN3fooE', 'std::foo'), + ('_ZNSt3sub3fooE', 'std::sub::foo'), ('_ZSs', 'std::string'), ('_Z3fooISt6vectorE', 'foo'), + ('_Z3fooINSt3__19allocatorIcEEE', 'foo>'), ('_ZSaIhE', 'std::allocator'), ]) def test_std_substs(mangled, demangled): - assert_demangles(mangled, demangled) + assert_roundtrip(mangled, demangled) def test_std_substs_none(): From 792428b7600e1f12d90f1901a848a35107f42cce Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Sat, 16 Aug 2025 00:56:28 -0700 Subject: [PATCH 12/17] support std names in abominable functions --- itanium_demangler/__init__.py | 9 ++++----- tests/test_all.py | 10 +++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/itanium_demangler/__init__.py b/itanium_demangler/__init__.py index 3cc53cb..15d106e 100644 --- a/itanium_demangler/__init__.py +++ b/itanium_demangler/__init__.py @@ -179,9 +179,7 @@ def encoding(self): return 'Dn' elif _is_nested_name(self): prefix, rest = _infer_std_names(self.value) - if prefix is None: - return f'N{"".join(p.encoding() for p in rest)}E' - elif len(rest) > 1: + if len(rest) > 1: return f'N{prefix}{"".join(p.encoding() for p in rest)}E' else: return f'{prefix}{"".join(p.encoding() for p in rest)}' @@ -270,7 +268,8 @@ def encoding(self, parent=None): text += 'R' elif '&&' in self.qual: text += 'O' - return f'N{text}{"".join(p.encoding() for p in self.value.value)}E' + prefix, rest = _infer_std_names(self.value.value) + return f'N{text}{prefix}{"".join(p.encoding() for p in rest)}E' else: return "" @@ -1066,7 +1065,7 @@ def _is_nested_name(ast) -> bool: def _infer_std_names(components): - best_match_prefix = None + best_match_prefix = '' best_match_len = 0 for prefix, std_name in _std_names.items(): diff --git a/tests/test_all.py b/tests/test_all.py index 679aa04..c14d634 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -297,7 +297,15 @@ def test_member_function(mangled, demangled): @pytest.mark.parametrize("mangled, demangled", [ ('_Z3fooIRKN5boost2lsEEiv', 'int foo()'), ('_ZNKR5boost2ls5memfnEv', 'boost::ls::memfn() const &'), - ('_Z3da2IL_Z4wellEEiv', 'int da2()') + ('_Z3da2IL_Z4wellEEiv', 'int da2()'), + ('_ZNSt6thread4joinEv', 'std::thread::join()'), ]) def test_calls(mangled, demangled): assert_roundtrip(mangled, demangled) + + +@pytest.mark.parametrize("mangled, demangled", [ + ('_ZNKSt8valarrayIiE4sizeEv', 'std::valarray::size() const'), +]) +def test_std_substs_extra(mangled, demangled): + assert_roundtrip(mangled, demangled) From e8e070431b2cbf163a617f37ab5c41c1fabf7269 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Sat, 16 Aug 2025 00:57:01 -0700 Subject: [PATCH 13/17] bump version --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8549e6a..6ec97dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "itanium_demangler" -version = "1.1" +version = "1.2" authors = [ { name = "whitequark", email = "whitequark@whitequark.org" }, ] diff --git a/uv.lock b/uv.lock index a35d125..c94c76b 100644 --- a/uv.lock +++ b/uv.lock @@ -70,7 +70,7 @@ wheels = [ [[package]] name = "itanium-demangler" -version = "1.1" +version = "1.2" source = { editable = "." } [package.optional-dependencies] From c09f4ac6d39bcbb4054890f736bc69d1b112e318 Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Sat, 16 Aug 2025 01:26:08 -0700 Subject: [PATCH 14/17] test more nested abbrevs --- tests/test_all.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_all.py b/tests/test_all.py index c14d634..3bf1b37 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -244,7 +244,10 @@ def test_const(): ('_ZcviIiET_T_', 'operator int(int, int)'), ]) def test_operator_template(mangled, demangled): - assert_demangles(mangled, demangled) + if 'T' in mangled: + assert_demangles(mangled, demangled) + else: + assert_roundtrip(mangled, demangled) @pytest.mark.parametrize("mangled, demangled", [ @@ -306,6 +309,8 @@ def test_calls(mangled, demangled): @pytest.mark.parametrize("mangled, demangled", [ ('_ZNKSt8valarrayIiE4sizeEv', 'std::valarray::size() const'), + ('_ZNSt6vectorIiSaIiEED1Ev', + 'std::vector>::{dtor}()'), ]) -def test_std_substs_extra(mangled, demangled): +def test_std_substs_nested(mangled, demangled): assert_roundtrip(mangled, demangled) From d8fbd3bd3b298c0cc139b69407924981cd693aca Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Sat, 16 Aug 2025 22:38:59 -0700 Subject: [PATCH 15/17] distinguish unreachable branches from unsupported --- itanium_demangler/__init__.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/itanium_demangler/__init__.py b/itanium_demangler/__init__.py index 15d106e..32da543 100644 --- a/itanium_demangler/__init__.py +++ b/itanium_demangler/__init__.py @@ -1,4 +1,3 @@ -# encoding:utf-8 name = "itanium_demangler" """ @@ -34,7 +33,7 @@ * `literal`: `node.value` (`str`) holds the literal representation as-is, `node.ty` holds a type node specifying the type of the literal * `entity`: `node.value` holds a name node that refers to a declared entity - * `function`: `node.name` holds a name node specifying the function name, + * `func`: `node.name` holds a name node specifying the function name, `node.ret_ty` holds a type node specifying the return type of a template function, if any, or `None`, ``node.arg_tys` (`tuple`) holds a sequence of type nodes specifying thefunction arguments @@ -203,8 +202,8 @@ def encoding(self): return _mangled_unary_operators[self.value] elif self.kind == 'oper_cast': return f'cv{self.value.encoding()}' - - return "" + else: + raise NotImplementedError(f'{self.kind!r} is not supported') def left(self): if self.kind == "pointer": @@ -246,9 +245,9 @@ def __str__(self): else: return repr(self) - def encoding(self, parent=None): + def encoding(self): if self.kind == 'abi': - return self.value.encoding() + ''.join(f'B{len(x)}{x}' for x in sorted(self.qual)) + return self.value.encoding() + "".join(f'B{len(x)}{x}' for x in sorted(self.qual)) elif self.kind == 'cv_qual': text = "" if 'const' in self.qual: @@ -270,8 +269,6 @@ def encoding(self, parent=None): text += 'O' prefix, rest = _infer_std_names(self.value.value) return f'N{text}{prefix}{"".join(p.encoding() for p in rest)}E' - else: - return "" def left(self): return str(self) @@ -303,7 +300,6 @@ def encoding(self): return f'L{self.ty.encoding()}{self.value}E' elif self.kind == 'entity': return f'L{mangle(self.value)}E' - return "" def left(self): return str(self) @@ -382,9 +378,6 @@ def encoding(self): if self.name is None: result += 'E' return result - else: - return "" - def map(self, f): if self.kind == 'func': @@ -426,8 +419,6 @@ def encoding(self): if self.kind == 'array': # instantiation-dependent array bound expression is not supported return f'A{self.dimension.value}_{self.ty.encoding()}' - else: - return "" def map(self, f): if self.kind == 'array': From 436ab740f6f43a4d555d983f079e32a11f88264b Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Sat, 16 Aug 2025 23:02:10 -0700 Subject: [PATCH 16/17] make dev/test group local --- pyproject.toml | 6 +++--- uv.lock | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6ec97dc..67c4d9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ [project.urls] "Homepage" = "https://github.com/whitequark/python-itanium_demangler" -[project.optional-dependencies] -test = [ - "pytest", +[dependency-groups] +dev = [ + "pytest" ] diff --git a/uv.lock b/uv.lock index c94c76b..f0472f8 100644 --- a/uv.lock +++ b/uv.lock @@ -73,16 +73,17 @@ name = "itanium-demangler" version = "1.2" source = { editable = "." } -[package.optional-dependencies] -test = [ +[package.dev-dependencies] +dev = [ { name = "pytest", version = "7.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8'" }, { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.8.*'" }, { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] [package.metadata] -requires-dist = [{ name = "pytest", marker = "extra == 'test'" }] -provides-extras = ["test"] + +[package.metadata.requires-dev] +dev = [{ name = "pytest" }] [[package]] name = "packaging" From a6aec367b86d934737e54dadbdc6003d3952732d Mon Sep 17 00:00:00 2001 From: Zhihao Yuan Date: Mon, 18 Aug 2025 10:55:10 -0700 Subject: [PATCH 17/17] put the 3 unary op back in the operator tests --- tests/test_all.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_all.py b/tests/test_all.py index 3bf1b37..3ddc325 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -1,6 +1,7 @@ import pytest -from itanium_demangler import parse, mangle, _operators, _builtin_types +from itanium_demangler import parse, mangle +from itanium_demangler import _operators, _unary_operators, _builtin_types def assert_parses(mangled, ast): @@ -47,10 +48,11 @@ def test_ctor_dtor(mangled, demangled): assert_roundtrip(mangled, demangled) -# Create a list of operators for parametrization, excluding special cases +# Create a list of operators for parameterization, coping with special cases +_any_operators = dict(_operators, **_unary_operators) _operator_tests = [ - (op, 'operator' + _operators[op]) for op in _operators - if _operators[op] not in ['new', 'new[]', 'delete', 'delete[]'] + (op, 'operator' + _any_operators[op]) for op in _any_operators + if _any_operators[op] not in ['new', 'new[]', 'delete', 'delete[]'] ] + [ ('nw', 'operator new'), ('na', 'operator new[]'),