diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d41080 --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# IDEs +.idea/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a70ddf9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "." + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..a083795 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# C-Py-Py + +```python +from cpp import * + +x = (cpp)[std::vectorv({1, 2, 3})] +x.push_back(4) +(cpp)[std::cout] << "Vector x: " << x << (cpp)[std::endl] +# -> prints 'Vector x: [1, 2, 3]' +for i in auto& x: + (cpp)[std::cout] << "Incrementing " << i << "..." << (cpp)[std::endl] + # -> prints 'Incrementing 1...', 'Incrementing 2...', etc. + i += 1 + +(cpp)[std::cout] << "Vector after: " << x << (cpp)[std::endl] +# -> prints 'Vector after: [2, 3, 4, 5]' +``` + +## How? + +### Template notation + +The `<>` template notation was quite difficult to pull off. Python has a weird concept of multiple-boolean-operators, so the following: + +```python +x = (cpp)[std::vectorv({1, 2, 3})] +``` + +is equivalent to + +```python +x = (cpp)[std::((vector < int) and (int > v({1, 2, 3})))] +``` + +We can then overwrite the less than operator for the object `vector` to simply return True, so that it's negligible: + +```python +x = (cpp)[std::(True and (int > v({1, 2, 3})))] +x = (cpp)[std::(int > v({1, 2, 3}))] +``` + +Now we can overwrite the less than operator on a different class (`v`, in this case) so that it simply takes in a fully formed vector object as `self` and a type as the comparison, then tries to transform that into a new vector of the type in the comparison. This would be equivalent to: + +```python +x = (cpp)[std::(v({1, 2, 3}))] +``` + +### C++-style namespacing + +Nearly there. For the namespacing (`::`), we turn to the only place in the Python syntax where adjacent colons are allowed: slice notation. The code above is equivalent to: + +```python +x = cpp[slice(std, None, v({1, 2, 3}))] +``` + +We can define `cpp` to be an instance of a class that overrides the `__getitem__` method to simply return the rightmost part of the slice: + +```python +class cpp: + def __getitem__(self, other: slice): + return other.step +cpp = cpp() +``` + +Now the code is equivalent to just: + +```python +x = v({1, 2, 3}) +``` + +where `v` is essentially a thin wrapper around `list`. + +### cout + +`cout` performs a small sleight-of-hand. Since Python is evaluated left-to-right, we have to have the `<<` operator reduce each of the expressions down into a single format string, then pass that to `endl` to actually do the printing. We do this by making `cout`.__lshift__() return a `Coutable`: + +```python +class _Coutable: + def __init__(self, o) -> None: + self._total_str: str = format(o) + + def __lshift__(self, other: Any) -> Self: + if other is endl: + print(self._total_str) + self._total_str = self._total_str + format(other) + return self +``` + +This class will just keep accumulating objects' formatted representations until it hits endl, when it will print everything out. + +### Taking references + +Unfortunately, Python's `for _ in _:` syntax is pretty rigid, and won't allow any operations in-between for and in, so we have to stick the `auto&` on the right side. This + +## Why? + +Scientists are hard at work trying to come up with an answer to that question. diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..6aa8b71 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,278 @@ +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +tests_no_zope = ["cloudpickle", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] +tests = ["cloudpickle", "zope.interface", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] +docs = ["sphinx-notfound-page", "zope.interface", "sphinx", "furo"] +dev = ["cloudpickle", "pre-commit", "sphinx-notfound-page", "sphinx", "furo", "zope.interface", "pytest-mypy-plugins", "mypy (>=0.900,!=0.940)", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] + +[[package]] +name = "black" +version = "22.6.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +uvloop = ["uvloop (>=0.15.2)"] +jupyter = ["tokenize-rt (>=3.2.0)", "ipython (>=7.8.0)"] +d = ["aiohttp (>=3.7.4)"] +colorama = ["colorama (>=0.4.3)"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "invoke-iife" +version = "1.1.0" +description = "Bringing the fun of immediately-invoked function expressions to Python!" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)", "pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "appdirs (==1.4.4)"] +docs = ["sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +testing = ["pytest-benchmark", "pytest"] +dev = ["tox", "pre-commit"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["xmlschema", "requests", "pygments (>=2.7.2)", "nose", "mock", "hypothesis (>=3.56)", "argcomplete"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[metadata] +lock-version = "1.1" +python-versions = "^3.11" +content-hash = "fd87afa88377aab991d7d75d66eaa1f95afb2a0604f0c0f44a4316e0fa78640f" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +black = [ + {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"}, + {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"}, + {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"}, + {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"}, + {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"}, + {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"}, + {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"}, + {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"}, + {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"}, + {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"}, + {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"}, + {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"}, + {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"}, + {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"}, + {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"}, + {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"}, + {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"}, + {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"}, + {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"}, + {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"}, + {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"}, + {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"}, + {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +invoke-iife = [ + {file = "invoke-iife-1.1.0.tar.gz", hash = "sha256:8568b4a9dd0cb1f79d5fd3b41bca05711c8cee1247f692d00620103a07ddf65d"}, + {file = "invoke_iife-1.1.0-py3-none-any.whl", hash = "sha256:6353585ef1dbacb9345953401d275e9ecd9445307649c3b2a157451146194e44"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..66c4cc0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[tool.poetry] +name = "c-py-py" +version = "0.1.0" +description = "" +authors = ["torshepherd "] +license = "MIT" +readme = "README.md" + +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Software Development :: Libraries :: Python Modules", +] + +packages = [ + { include = "cpp", from = "src" } +] + +[tool.poetry.dependencies] +python = "^3.11" +invoke-iife = "^1.1.0" +typing-extensions = "^4.3.0" + +[tool.poetry.dev-dependencies] +pytest = "^7.1.2" +black = "^22.6.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/src/cpp/__init__.py b/src/cpp/__init__.py new file mode 100644 index 0000000..91b6ab0 --- /dev/null +++ b/src/cpp/__init__.py @@ -0,0 +1,192 @@ +from typing import Any, Callable, Literal, NoReturn, Optional, Type, TypeVar, overload + +from iife import iife +from typing_extensions import Self + + +class _Commentable: + def __floordiv__(self, other: Any) -> NoReturn: + print(end='') + + +@iife +class cpp(_Commentable): + def __getitem__(self, other: slice): + return other.step + + +@iife +class std: + ... + + +@iife +class auto: + def __and__(self, other: Any) -> Any: + return other + + +@iife +class endl(_Commentable): + ... + + +@iife +class cout(_Commentable): + def __lshift__(self, other: Any) -> Self: + if other is endl: + print() + else: + print(other, end="") + return self + + def __repr__(self) -> str: + return "" + + +T = TypeVar("T") + + +class std_vector(list[T], _Commentable): + @overload + def __init__( + self, + initializer_list_or_size: set[T] | list[T], + value: None = None, + ) -> None: + ... + + @overload + def __init__( + self, + initializer_list_or_size: int, + value: T, + ) -> None: + ... + + @overload + def __init__( + self, + initializer_list_or_size: None = None, + value: None = None, + ) -> None: + ... + + def __init__( + self, + initializer_list_or_size: Optional[set[T] | list[T] | int] = None, + value: Optional[T] = None, + ) -> None: + + self.size: Callable[[], int] = lambda: len(self) + self.empty: Callable[[], bool] = lambda: len(self) == 0 + self.clear: Callable[[], None] = lambda: self.__init__() + self.resize: Callable[[int, T], None] = lambda n, v: self.__init__(n, v) + + if isinstance(initializer_list_or_size, int): + assert value is not None, "Must provide value for vector of size {}".format( + initializer_list_or_size + ) + self._datatype: Type[T] = type(value) + super().__init__([value] * initializer_list_or_size) + else: + assert ( + initializer_list_or_size is not None + ), "Must provide initializer list or size for vector" + self._datatype: Type[T] = type(next(iter(initializer_list_or_size))) + super().__init__(initializer_list_or_size) + + def __lt__(self, template_param: Type[T]) -> Self: + if not self._datatype: + self._datatype = template_param + else: + try: + return std_vector(list(template_param(i._value) for i in self)) + except Exception as e: + raise TypeError( + f"Cannot construct vector of type {template_param} from arguments of type {self._datatype}" + ) + return self + + def push_back(self, value) -> Self: + self.append(value) + return self + + def back(self) -> T: + return self.__getitem__(-1) + + def front(self) -> T: + return self.__getitem__(0) + + def pop_back(self) -> Self: + self.pop(-1) + return self + + def at(self, i: int) -> T: + return self[i] + + def __iter__(self) -> "element_ref": + return element_ref(self) + + +class element_ref: + def __init__(self, vector: std_vector[T], idx=0) -> None: + self._vector = vector + self._index: int = idx + + def __next__(self) -> Self: + if self._index >= len(self._vector): + raise StopIteration + else: + self._index += 1 + return self + + def __repr__(self) -> str: + return self._vector[self._index - 1].__repr__() + + def __iadd__(self, other: Any) -> Self: + self._vector[self._index - 1] += other + return self + + @property + def _value(self): + return self._vector[self._index - 1] + + +@iife +class vector: + def __lt__(self, _: type) -> Literal[True]: + return True + + +# Type aliases +v = std_vector + +# fmt: off +if __name__ == "__main__": + # include + # include + + # TODO: support c++-y classes + # class MyClass: + # @public + # def getData(self): + # return self.data + + # @private + # data: int = 0 + + (cpp) // "Construct a vector from an initializer list" + x: std_vector[int] = (cpp) [std::vectorv({1, 2, 3})] # type: ignore + + x.push_back(4) // "Adds 4 to the end of the vector" + + (cpp) [std::cout] << "Vector x: " << x << (cpp) [std::endl] // "prints out [1, 2, 3, 4]" + + + (cpp) // "Loop over references to elements of x:" + for i in auto& x: + (cpp) [std::cout] << "Incrementing " << i << "..." << (cpp) [std::endl] + i += 1 + + (cpp) [std::cout] << "Vector after: " << x << (cpp) [std::endl] diff --git a/tests/test_cpp.py b/tests/test_cpp.py new file mode 100644 index 0000000..80daca0 --- /dev/null +++ b/tests/test_cpp.py @@ -0,0 +1,55 @@ +from pytest import raises +from cpp import * + +# cpp // "Construct a vector from an initializer list" +# x: std_vector[int] = _[std::vectorv({1, 2, 3})] + + +# x.push_back(4) // "Adds 4 to the end of the vector" + +# _[std::cout] << "Vector x: " << x << _[std::endl] // "prints out [1, 2, 3, 4]" + +# cpp // "Range-based for loop over vector x:" +# for i in auto& x: +# _[std::cout] << "Incrementing " << i << "..." << _[std::endl] +# i += 1 + +# _[std::cout] << "Vector after: " << x << _[std::endl] + + +def test_cout(capsys) -> None: + x = [1, 2, 3] + cpp[std::cout] << "Testing: " << x + assert capsys.readouterr().out == "Testing: [1, 2, 3]" + cpp[std::cout] << "Testing: " << x << cpp[std::endl] + assert capsys.readouterr().out == "Testing: [1, 2, 3]\n" + + +def test_vector() -> None: + x = [1, 2, 3] + y = cpp[std :: vector < int > v(x)] # type: ignore + assert y == [1, 2, 3] + y.push_back(4) + assert y == [1, 2, 3, 4] + z = cpp[std::vectorv(3, 0.0)] + assert z == [0.0, 0.0, 0.0] + w = cpp[std::vectorv({1, 2, 3})] + assert w == [1.0, 2.0, 3.0] + + +def test_vector_conversions() -> None: + x = [1, 2, 3] + y = cpp[std :: vector < int > v(x)] # type: ignore + y.push_back(4.0) + assert y == [1.0, 2.0, 3.0, 4.0] + with raises(TypeError): + w = ["a", "b", "c"] + z = cpp[std :: vector < int > v(w)] # type: ignore + + +def test_loop_by_reference() -> None: + x = [1, 2, 3] + y = cpp[std :: vector < int > v(x)] # type: ignore + for i in auto & y: + i += 1 + assert y == [2, 3, 4]