├── .github └── workflows │ └── python-app.yml ├── .gitignore ├── LICENSE ├── README.md ├── RELEASE_NOTES.json ├── pyproject.toml ├── release_package.py ├── setup.cfg ├── setup.py ├── src ├── code_generation │ ├── __init__.py │ ├── core │ │ ├── __init__.py │ │ ├── code_generator.py │ │ └── code_style.py │ ├── cpp │ │ ├── __init__.py │ │ ├── cpp_array.py │ │ ├── cpp_class.py │ │ ├── cpp_enum.py │ │ ├── cpp_function.py │ │ ├── cpp_generator.py │ │ └── cpp_variable.py │ ├── html │ │ ├── __init__.py │ │ └── html_generator.py │ └── version.py └── example.py └── tests ├── create_assets.py ├── test_assets ├── array.cpp ├── class.cpp ├── class.h ├── enum.cpp ├── factorial.cpp ├── factorial.h ├── func.cpp ├── func.h └── var.cpp ├── test_cpp_file.py ├── test_cpp_function_writer.py ├── test_cpp_variable_writer.py └── test_html_writer.py /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python Code Generator 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.9 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.9 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install flake8 pytest 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | - name: Lint with flake8 29 | run: | 30 | # stop the build if there are Python syntax errors or undefined names 31 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 32 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 33 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 34 | - name: Run test 35 | run: | 36 | python release_package.py --mode install 37 | python tests/test_cpp_file.py 38 | python tests/test_cpp_function_writer.py 39 | python tests/test_cpp_variable_writer.py 40 | python tests/test_html_writer.py 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | #vim files 141 | .*.swp 142 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Yurii Cherkasov, Erich Schroeter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | C++ Code Generator 2 | ============== 3 | 4 | Simple and straightforward code generator for creating C++ code. It also could be used for generating code in any programming language. Written in Python, works both with Python 2 and 3 5 | 6 | Every C++ element could render its current state to a string that could be evaluated as 7 | a legal C++ construction. 8 | Some elements could be rendered to a pair of representations (C++ classes and functions declaration and implementation) 9 | 10 | ### Special thanks 11 | 12 | Thanks to Eric Reynolds, the idea of this project mainly based on his article published on 13 | http://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python 14 | 15 | However, this solution has been both simplified and extended compared to the initial idea. 16 | 17 | ## Usage examples 18 | 19 | ### Generate C++ code from Python code 20 | 21 | #### Creating variables 22 | 23 | ##### Python code 24 | ```python 25 | cpp = CodeFile('example.cpp') 26 | cpp('int i = 0;') 27 | 28 | x_variable = CppVariable(name='x', type='int const&', is_static=True, is_constexpr=True, initialization_value='42') 29 | x_variable.render_to_string(cpp) 30 | 31 | name_variable = CppVariable(name='name', type='char*', is_extern=True) 32 | name_variable.render_to_string(cpp) 33 | ``` 34 | 35 | ##### Generated C++ code 36 | ```c++ 37 | int i = 0; 38 | static constexpr int const& x = 42; 39 | extern char* name; 40 | ``` 41 | 42 | #### Creating functions 43 | 44 | ##### Python code 45 | ```python 46 | def handle_to_factorial(self, cpp): 47 | cpp('return n < 1 ? 1 : (n * factorial(n - 1));') 48 | 49 | cpp = CodeFile('example.cpp') 50 | 51 | factorial_function = CppFunction(name='factorial', 52 | ret_type='int', 53 | is_constexpr=True, 54 | implementation_handle=handle_to_factorial, 55 | documentation='/// Calculates and returns the factorial of \p n.') 56 | factorial_function.add_argument('int n') 57 | factorial_function.render_to_string(cpp) 58 | ``` 59 | 60 | ##### Generated C++ code 61 | ```c++ 62 | /// Calculates and returns the factorial of \p n. 63 | constexpr int factorial(int n) 64 | { 65 | return n <= 1 ? 1 : (n * factorial(n - 1)); 66 | } 67 | ``` 68 | 69 | #### Creating classes and structures 70 | 71 | ##### Python code 72 | ```python 73 | cpp = CppFile('example.cpp') 74 | with cpp.block('class A', ';'): 75 | cpp.label('public:') 76 | cpp('int m_classMember1;') 77 | cpp('double m_classMember2;') 78 | ``` 79 | 80 | ##### Generated C++ code 81 | ```c++ 82 | class A 83 | { 84 | public: 85 | int m_classMember1; 86 | double m_classMember2; 87 | }; 88 | ``` 89 | 90 | #### Rendering `CppClass` objects to C++ declaration and implementation 91 | 92 | ##### Python code 93 | 94 | ```python 95 | cpp_class = CppClass(name = 'MyClass', is_struct = True) 96 | cpp_class.add_variable(CppVariable(name = "m_var", 97 | type = 'size_t', 98 | is_static = True, 99 | is_const = True, 100 | initialization_value = 255)) 101 | ``` 102 | 103 | ##### Generated C++ declaration 104 | 105 | ```c++ 106 | struct MyClass 107 | { 108 | static const size_t m_var; 109 | } 110 | ``` 111 | 112 | #### Generated C++ implementation 113 | ```c++ 114 | const size_t MyClass::m_var = 255; 115 | ``` 116 | 117 | Module `cpp_generator.py` highly depends on parent `code_generator.py`, as it uses 118 | code generating and formatting primitives implemented there. 119 | 120 | The main object referenced from `code_generator.py` is `CppFile`, 121 | which is passed as a parameter to `render_to_string(cpp)` Python method 122 | 123 | It could also be used for composing more complicated C++ code, 124 | that does not supported by `cpp_generator` 125 | 126 | Class `ANSICodeStyle` is responsible for code formatting. Re-implement it if you wish to apply any other formatting style. 127 | 128 | 129 | It support: 130 | 131 | - functional calls: 132 | ```python 133 | cpp('int a = 10;') 134 | ``` 135 | 136 | - `with` semantic: 137 | ```python 138 | with cpp.block('class MyClass', ';') 139 | class_definition(cpp) 140 | ``` 141 | 142 | - append code to the last string without EOL: 143 | ```python 144 | cpp.append(', p = NULL);') 145 | ``` 146 | 147 | - empty lines: 148 | ```python 149 | cpp.newline(2) 150 | ``` 151 | 152 | ## Maintainers 153 | 154 | ### Executing unit tests 155 | The following command will execute the unit tests. 156 | 157 | ```bash 158 | python -m unittest cpp_generator_tests.py 159 | ``` 160 | 161 | ### Updating unit tests fixed data 162 | After changing a unit test the fixed data needs to be updated to successfully pass the unit tests. 163 | 164 | ```bash 165 | python -c 'from test_cpp_generator import generate_reference_code; generate_reference_code()' 166 | ``` 167 | 168 | After executing that command, the fixed data under `tests/test_assets` will be updated and will need to be committed to git. 169 | 170 | -------------------------------------------------------------------------------- /RELEASE_NOTES.json: -------------------------------------------------------------------------------- 1 | { 2 | "release": { 3 | "download_link": "https://{package_name_dash}-package.s3.amazonaws.com/server/{package_name}-{version}-py3-none-any.whl" 4 | }, 5 | "releases": { 6 | "2.x.x": { 7 | "release_notes": [ 8 | "Create subdirectories for core, cpp and html generators" 9 | ] 10 | }, 11 | "2.3.0": { 12 | "release_notes": [ 13 | "Migration to Python 3.8", 14 | "Split `Cpp*` generators implementation into multiple files", 15 | "Implemented HTML code generator" 16 | ] 17 | }, 18 | "2.1.1": { 19 | "release_notes": [ 20 | "Application is now available as a package", 21 | "Auto-upload to S3 bucket" 22 | ] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /release_package.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import sys 4 | import argparse 5 | import platform 6 | import pathlib 7 | import json 8 | from subprocess import run 9 | from configparser import ConfigParser 10 | 11 | cfg = ConfigParser() 12 | cfg.read(filenames=['setup.cfg']) 13 | VERSION = cfg.get('metadata', 'version') 14 | 15 | # Home dir 16 | HOME = pathlib.Path.home() 17 | 18 | # Name with underscore (wheel filename) 19 | PACKAGE_NAME = cfg.get('metadata', 'name') 20 | 21 | # Name with dash (pip name, URL, S3 bucket) 22 | PACKAGE_NAME_DASH = PACKAGE_NAME.replace('_', '-') 23 | 24 | PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)))) 25 | PYTHON = "python3" 26 | PIP = "pip3" 27 | 28 | 29 | def is_linux(): 30 | """ 31 | :return: True if system is Linux, False otherwise 32 | """ 33 | return platform.system() == 'Linux' 34 | 35 | 36 | def is_macos(): 37 | """ 38 | :return: True if system is MacOS, False otherwise 39 | """ 40 | return platform.system() == 'Darwin' 41 | 42 | 43 | def is_windows(): 44 | """ 45 | :return: True if system is Windows, False otherwise 46 | """ 47 | return platform.system() == 'Windows' 48 | 49 | 50 | if is_macos() or is_linux(): 51 | PYTHON = "python3" 52 | PIP = "pip3" 53 | elif is_windows(): 54 | PYTHON = "python" 55 | PIP = "pip" 56 | 57 | 58 | def executable_exists(executable): 59 | """ 60 | :param executable: Name of the executable 61 | :return: True if executable exists, False otherwise 62 | """ 63 | try: 64 | run([executable, '--version']) 65 | return True 66 | except FileNotFoundError: 67 | return False 68 | 69 | 70 | def sanity_check(args): 71 | """ 72 | Check if all required executables and configs are available 73 | """ 74 | if not os.path.isdir(os.path.join(PROJECT_DIR, 'src', PACKAGE_NAME)): 75 | print(f'Cannot find src/{PACKAGE_NAME}') 76 | sys.exit(1) 77 | if args.create_release and not release_version_exists(VERSION): 78 | print(f'No release notes found for version {VERSION}') 79 | sys.exit(1) 80 | if args.upload_s3 and not executable_exists('aws'): 81 | print('awscli not installed') 82 | sys.exit(1) 83 | if args.create_release and not executable_exists('gh'): 84 | print('GitHub CLI not installed') 85 | sys.exit(1) 86 | if args.publish_pypi and not executable_exists('twine'): 87 | print('twine not installed') 88 | sys.exit(1) 89 | if args.publish_pypi and not os.path.isfile(os.path.join(HOME, '.pypirc')): 90 | print('No ~/.pypirc file found') 91 | sys.exit(1) 92 | 93 | 94 | def wheel_path(): 95 | """ 96 | :return: Path to the wheel file 97 | """ 98 | return os.path.join(PROJECT_DIR, 'dist', f'{PACKAGE_NAME}-{VERSION}-py3-none-any.whl') 99 | 100 | 101 | def uninstall_wheel(): 102 | """ 103 | pip.exe uninstall -y {PACKAGE_NAME_DASH} 104 | """ 105 | run([PIP, 'uninstall', '-y', PACKAGE_NAME_DASH]) 106 | 107 | 108 | def publish_pypi(): 109 | f""" 110 | Publish the package to PyPI 111 | Example: 112 | twine upload dist/{PACKAGE_NAME}-2.9.34-py3-none-any.whl 113 | """ 114 | run([PYTHON, '-m', 'pip', 'install', '--upgrade', 'build', 'twine']) 115 | run(['twine', 'check', 'dist/*']) 116 | run(['twine', 'upload', 'dist/*']) 117 | 118 | 119 | def build_wheel(): 120 | """ 121 | python.exe -m pip install --upgrade pip 122 | python.exe -m pip install --upgrade build 123 | python.exe -m build 124 | """ 125 | run([PYTHON, '-m', 'pip', 'install', '--upgrade', 'pip']) 126 | run([PYTHON, '-m', 'pip', 'install', '--upgrade', 'build']) 127 | run([PYTHON, '-m', 'build']) 128 | 129 | 130 | def install_wheel(): 131 | """ 132 | pip.exe install ./dist/{PACKAGE_NAME}-{VERSION}-py3-none-any.whl 133 | """ 134 | run([PIP, 'install', wheel_path()]) 135 | 136 | 137 | def install_wheel_devmode(): 138 | f""" 139 | pip.exe install -e ./dist/{PACKAGE_NAME}-{VERSION}-py3-none-any.whl 140 | """ 141 | run([PIP, 'install', '-e', '.']) 142 | 143 | 144 | def cleanup_old_wheels(): 145 | f""" 146 | Remove all previous {PACKAGE_NAME}-{VERSION}-py3-none-any.whl in dist 147 | """ 148 | if os.path.isdir(os.path.join(PROJECT_DIR, 'dist')): 149 | for file in os.listdir(os.path.join(PROJECT_DIR, 'dist')): 150 | if file.startswith(f'{PACKAGE_NAME}-'): 151 | os.remove(os.path.join(PROJECT_DIR, 'dist', file)) 152 | 153 | 154 | def upload_s3(): 155 | f""" 156 | Upload the package to S3 157 | Example: 158 | aws s3 cp {PACKAGE_NAME}-2.9.31-py3-none-any.whl s3://{PACKAGE_NAME_DASH}/packages/ 159 | aws s3api put-object-acl 160 | --bucket {PACKAGE_NAME_DASH} 161 | --key packages/{PACKAGE_NAME}-2.9.31-py3-none-any.whl 162 | --acl public-read 163 | """ 164 | run(['aws', 's3', 'cp', wheel_path(), f's3://{PACKAGE_NAME_DASH}/packages/']) 165 | run(['aws', 's3api', 'put-object-acl', 166 | '--bucket', f'{PACKAGE_NAME_DASH}', 167 | '--key', f'packages/{PACKAGE_NAME}-{VERSION}-py3-none-any.whl', 168 | '--acl', 'public-read']) 169 | 170 | 171 | def tag_release(): 172 | """ 173 | Tag the release on GitHub 174 | Example: 175 | git tag -a release.2.9.34 -m"Release 2.9.34" 176 | git push origin --tags master 177 | """ 178 | run(['git', 'tag', '-a', 'release.{}'.format(VERSION), '-m', 'Release {}'.format(VERSION)]) 179 | run(['git', 'push', 'origin', '--tags', 'master']) 180 | 181 | 182 | def create_release(release_file): 183 | f""" 184 | Create a release on GitHub 185 | Example: 186 | gh release create release.2.9.34 dist/{PACKAGE_NAME}-2.9.34-py3-none-any.whl --title 2.9.34 --notes-file RELEASE.md 187 | """ 188 | run(['gh', 'release', 'create', 'release.{}'.format(VERSION), wheel_path(), 189 | '--title', '{}'.format(VERSION), 190 | '--notes-file', release_file]) 191 | 192 | 193 | def release_version_exists(version): 194 | """ 195 | Check if version with respective release notes exists in RELEASE_NOTES.json 196 | :param version: Version to check in format 2.9.34 197 | :return: True if version exists, False otherwise 198 | """ 199 | with open(os.path.join(PROJECT_DIR, 'RELEASE_NOTES.json'), 'r') as release_json: 200 | release_notes = json.load(release_json) 201 | return True if version in release_notes['releases'] else False 202 | 203 | 204 | def tmp_release_notes(): 205 | """ 206 | Read the last release notes in JSON format from release_notes.json and create a temporary release notes file 207 | :return: Path to the temporary release notes file 208 | """ 209 | # read release_notes.json as dict 210 | release_md = 'RELEASE.md' 211 | with open('RELEASE_NOTES.json', 'r') as release_json: 212 | release_notes = json.load(release_json) 213 | 214 | if not release_version_exists(VERSION): 215 | print(f'No release notes found for version {VERSION}') 216 | sys.exit(1) 217 | 218 | last_release = release_notes['releases'][VERSION]['release_notes'] 219 | url_template = release_notes['release']['download_link'] 220 | release_url = url_template.format(version=VERSION, 221 | package_name=PACKAGE_NAME, 222 | package_name_dash=PACKAGE_NAME_DASH) 223 | # create a temporary release notes file 224 | with open(release_md, 'w') as release_tmp: 225 | release_tmp.write('## Release notes\n') 226 | for note in last_release: 227 | release_tmp.write('* {}\n'.format(note)) 228 | release_tmp.write('## Staging Area Download URL\n') 229 | release_tmp.write('[Wheel Package {} on AWS S3]({})\n'.format(VERSION, release_url)) 230 | return os.path.abspath(release_md) 231 | 232 | 233 | def increment_minor_version(file_path: str): 234 | """ 235 | Increment minor package version number, e.g. 2.2.9 -> 2.2.10 236 | :return: New version number 237 | """ 238 | config = ConfigParser() 239 | config.read(file_path) 240 | version = config['metadata']['version'] 241 | major, minor, patch = [int(v) for v in version.split('.')] 242 | patch += 1 243 | new_version = f"{major}.{minor}.{patch}" 244 | config['metadata']['version'] = new_version 245 | with open(file_path, 'w') as f: 246 | config.write(f) 247 | return new_version 248 | 249 | 250 | def main(): 251 | parser = argparse.ArgumentParser(description='Command-line params') 252 | parser.add_argument('--mode', 253 | help='What to do with the package', 254 | choices=["build", "install", "dev-mode", "reinstall", "uninstall"], 255 | default="reinstall", 256 | required=False) 257 | parser.add_argument('--upload-s3', 258 | help='Upload the package to S3', 259 | action='store_true', 260 | required=False) 261 | parser.add_argument('--create-release', 262 | help='Create a release on GitHub', 263 | action='store_true', 264 | required=False) 265 | parser.add_argument('--publish-pypi', 266 | help='Publish the package to PyPI server', 267 | action='store_true', 268 | default=False, 269 | required=False) 270 | parser.add_argument('--increment-version', 271 | help='Increment minor version number, e.g. 2.2.9 -> 2.2.10', 272 | action='store_true', 273 | default=False, 274 | required=False) 275 | args = parser.parse_args() 276 | 277 | global VERSION 278 | print(f'Package name: {PACKAGE_NAME}') 279 | print(f'Package name2: {PACKAGE_NAME_DASH}') 280 | print(f'Version: {VERSION}') 281 | sanity_check(args) 282 | 283 | if args.increment_version: 284 | new_version = increment_minor_version('setup.cfg') 285 | VERSION = new_version 286 | print(f'Increment version to {new_version}') 287 | 288 | if args.mode == "build": 289 | build_wheel() 290 | elif args.mode == "install": 291 | cleanup_old_wheels() 292 | build_wheel() 293 | install_wheel() 294 | elif args.mode == "dev-mode": 295 | cleanup_old_wheels() 296 | build_wheel() 297 | install_wheel_devmode() 298 | elif args.mode == "reinstall": 299 | cleanup_old_wheels() 300 | uninstall_wheel() 301 | build_wheel() 302 | install_wheel() 303 | elif args.mode == "uninstall": 304 | uninstall_wheel() 305 | else: 306 | print("Unknown mode") 307 | 308 | if args.upload_s3 and args.mode != "uninstall": 309 | upload_s3() 310 | 311 | if args.create_release and args.mode != "uninstall": 312 | release_file = tmp_release_notes() 313 | tag_release() 314 | create_release(release_file=release_file) 315 | os.remove(release_file) 316 | 317 | if args.publish_pypi and args.mode != "uninstall": 318 | publish_pypi() 319 | 320 | return 0 321 | 322 | 323 | if __name__ == '__main__': 324 | sys.exit(main()) 325 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = code_generation 3 | version = 2.4.0 4 | 5 | [options] 6 | packages = find: 7 | python_requires = >=3.8 8 | 9 | [options.packages.find] 10 | where = src 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pathlib 3 | from setuptools import find_packages, setup 4 | from configparser import ConfigParser 5 | 6 | cfg = ConfigParser() 7 | cfg.read(filenames=['setup.cfg']) 8 | VERSION = cfg.get('metadata', 'version') 9 | PACKAGE_NAME = cfg.get('metadata', 'name') 10 | PYTHON_REQUIRES = cfg.get('options', 'python_requires') 11 | 12 | # The directory containing this file 13 | HERE = pathlib.Path(__file__).parent 14 | 15 | # The text of the README file 16 | README = (HERE / "README.md").read_text(encoding='utf8') 17 | 18 | # Add future dependencies here 19 | DEPENDENCIES = ["pathlib"] 20 | 21 | setup( 22 | name=PACKAGE_NAME, 23 | version=VERSION, 24 | author="Yurii Cherkasov", 25 | author_email="strategarius@protonmail.com", 26 | description="Provides functionality of generating source code programmatically", 27 | long_description=README, 28 | long_description_content_type="text/markdown", 29 | url="https://github.com/yuchdev/code_generator", 30 | project_urls={ 31 | "Bug Tracker": "https://github.com/yuchdev/code_generator/issues", 32 | }, 33 | classifiers=[ 34 | # How mature is this project? Common values are 35 | # 3 - Alpha 36 | # 4 - Beta 37 | # 5 - Production/Stable 38 | 'Development Status :: 5 - Production/Stable', 39 | 40 | # Indicate who your project is intended for 41 | 'Intended Audience :: Developers', 42 | 43 | # Specify the Python versions you support here. In particular, ensure 44 | # that you indicate you support Python 3. These classifiers are *not* 45 | # checked by 'pip install'. See instead 'python_requires' below. 46 | 'Programming Language :: Python :: 3', 47 | 'License :: OSI Approved :: MIT License', 48 | 'Operating System :: OS Independent', 49 | ], 50 | packages=find_packages(where=str(HERE / 'src')), 51 | package_dir={'': 'src'}, 52 | package_data={PACKAGE_NAME: ['defaults/*']}, 53 | python_requires=PYTHON_REQUIRES, 54 | include_package_data=True, 55 | install_requires=DEPENDENCIES, 56 | ) 57 | -------------------------------------------------------------------------------- /src/code_generation/__init__.py: -------------------------------------------------------------------------------- 1 | from . import core 2 | from . import cpp 3 | from . import html 4 | -------------------------------------------------------------------------------- /src/code_generation/core/__init__.py: -------------------------------------------------------------------------------- 1 | from . import code_generator 2 | from . import code_style 3 | -------------------------------------------------------------------------------- /src/code_generation/core/code_generator.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from code_generation.core.code_style import ANSICodeStyle 3 | 4 | __doc__ = """ 5 | Simple and straightforward code generator that could be used for generating code 6 | on any programming language and to be a 'building block' for creating more complicated 7 | code generator. 8 | Thanks to Eric Reynolds, the code mainly based on his article published on 9 | https://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python 10 | However, it was both significantly extended since then and also simplified. 11 | 12 | Used under the Code Project Open License 13 | https://www.codeproject.com/info/cpol10.aspx 14 | 15 | Examples of usage: 16 | 17 | 1. 18 | # Python code 19 | cpp = CodeFile('example.cpp') 20 | cpp('int i = 0;') 21 | 22 | // Generated C++ code 23 | int i = 0; 24 | 25 | 2. 26 | # Python code 27 | cpp = CppFile('example.cpp') 28 | with cpp.block('class A', ';'): 29 | cpp.label('public') 30 | cpp('int m_classMember1;') 31 | cpp('double m_classMember2;') 32 | 33 | // Generated C++ code 34 | class A 35 | { 36 | public: 37 | int m_classMember1; 38 | double m_classMember2; 39 | }; 40 | 41 | Class `ANSICodeStyle` is responsible for code formatting. 42 | Re-implement it if you wish to apply any other formatting style. 43 | 44 | """ 45 | 46 | 47 | class CodeFile: 48 | """ 49 | The class is a main instrument of code generation 50 | 51 | It can generate plain strings using functional calls 52 | Ex: 53 | code = CodeFile(python_src_file) 54 | code('import os, sys') 55 | 56 | Is supports 'with' semantic for indentation blocks creation 57 | Ex: 58 | # Python code 59 | with code('for i in range(0, 5):'): 60 | code('lst.append(i*i)') 61 | 62 | # Generated code: 63 | for i in range(0, 5): 64 | lst.append(i*i) 65 | 66 | It can append code to the last line: 67 | Ex. 68 | # Python code 69 | cpp = CodeFile('ex.cpp') 70 | cpp('class Derived') 71 | cpp.append(' : public Base') 72 | 73 | // Generated code 74 | class Derived : public Base 75 | 76 | And finally, it can insert a number of empty lines 77 | cpp.newline(3) 78 | """ 79 | # Current formatting style (assigned as a class attribute to generate all files uniformly) 80 | Formatter = ANSICodeStyle 81 | 82 | def __init__(self, filename, writer=None): 83 | """ 84 | Creates a new source file 85 | @param: filename source file to create (rewrite if exists) 86 | @param: writer optional writer to write output to 87 | """ 88 | self.current_indent = 0 89 | self.last = None 90 | self.filename = filename 91 | if writer: 92 | self.out = writer 93 | else: 94 | self.out = open(filename, "w") 95 | 96 | def close(self): 97 | """ 98 | File created, just close the handle 99 | """ 100 | self.out.close() 101 | self.out = None 102 | 103 | def write(self, text, indent=0, endline=True): 104 | """ 105 | Write a new line with line ending 106 | """ 107 | self.out.write('{0}{1}{2}'.format(self.Formatter.indent * (self.current_indent+indent), 108 | text, 109 | self.Formatter.endline if endline else '')) 110 | 111 | def append(self, x): 112 | """ 113 | Append to the existing line without line ending 114 | """ 115 | self.out.write(x) 116 | 117 | def __call__(self, text, indent=0, endline=True): 118 | """ 119 | Supports 'object()' semantic, i.e. 120 | cpp('#include ') 121 | inserts appropriate line 122 | """ 123 | self.write(text, indent, endline) 124 | 125 | def block(self, text, postfix=''): 126 | """ 127 | Returns a stub for C++ {} close 128 | Supports 'with' semantic, i.e. 129 | cpp.block(class_name, ';'): 130 | """ 131 | return CodeFile.Formatter(self, text, postfix) 132 | 133 | def endline(self, count=1): 134 | """ 135 | Insert an endline 136 | """ 137 | self.write(CodeFile.Formatter.endline * count, endline=False) 138 | 139 | def newline(self, n=1): 140 | """ 141 | Insert one or several empty lines 142 | """ 143 | for _ in range(n): 144 | self.write('') 145 | 146 | 147 | class CppFile(CodeFile): 148 | """ 149 | This class extends CodeFile class with some specific C++ constructions 150 | """ 151 | def __init__(self, filename, writer=None): 152 | """ 153 | Create C++ source file 154 | """ 155 | CodeFile.__init__(self, filename, writer) 156 | 157 | def label(self, text): 158 | """ 159 | Could be used for access specifiers or ANSI C labels, e.g. 160 | private: 161 | a: 162 | """ 163 | self.write('{0}:'.format(text), -1) 164 | -------------------------------------------------------------------------------- /src/code_generation/core/code_style.py: -------------------------------------------------------------------------------- 1 | __doc__ = """Formatters for different styles of code generation 2 | """ 3 | 4 | 5 | class ANSICodeStyle: 6 | """ 7 | Class represents C++ {} close and its formatting style. 8 | It supports ANSI C style with braces on the new lines, like that: 9 | // C++ code 10 | { 11 | // C++ code 12 | }; 13 | finishing postfix is optional (e.g. necessary for classes, unnecessary for namespaces) 14 | """ 15 | 16 | # EOL symbol 17 | endline = "\n" 18 | 19 | # Tab (indentation) symbol 20 | indent = "\t" 21 | 22 | def __init__(self, owner, text, postfix): 23 | """ 24 | @param: owner - CodeFile where text is written to 25 | @param: text - text opening C++ close 26 | @param: postfix - optional terminating symbol (e.g. ; for classes) 27 | """ 28 | self.owner = owner 29 | if self.owner.last is not None: 30 | with self.owner.last: 31 | pass 32 | self.owner.write("".join(text)) 33 | self.owner.last = self 34 | self.postfix = postfix 35 | 36 | def __enter__(self): 37 | """ 38 | Open code block 39 | """ 40 | self.owner.write("{") 41 | self.owner.current_indent += 1 42 | self.owner.last = None 43 | 44 | def __exit__(self, *_): 45 | """ 46 | Close code block 47 | """ 48 | if self.owner.last is not None: 49 | with self.owner.last: 50 | pass 51 | self.owner.current_indent -= 1 52 | self.owner.write("}" + self.postfix) 53 | 54 | 55 | class HTMLStyle: 56 | """ 57 | Class representing HTML close and its formatting style. 58 | It supports HTML DOM-tree style, like that: 59 | // HTML code 60 | 61 | // HTML content 62 | 63 | """ 64 | # EOL symbol 65 | endline = "\n" 66 | 67 | # Tab (indentation) symbol is 2 spaces 68 | indent = " " 69 | 70 | def __init__(self, owner, element, *attrs, **kwattrs): 71 | """ 72 | @param: owner - CodeFile where text is written to 73 | @param: element - HTML element name 74 | @param: attrs - optional opening tag content, like attributes ['class="class1"', 'id="id1"'] 75 | @param: kwattrs - optional opening tag attributes, like class="class1", id="id1" 76 | """ 77 | self.owner = owner 78 | if self.owner.last is not None: 79 | with self.owner.last: 80 | pass 81 | self.element = element 82 | attributes = "".join(f' {attr}' for attr in attrs) 83 | attributes += "".join(f' {key}="{value}"' for key, value in kwattrs.items()) 84 | self.attributes = attributes 85 | self.owner.last = self 86 | 87 | def __enter__(self): 88 | """ 89 | Open code block 90 | """ 91 | self.owner.write(f"<{self.element}{self.attributes}>") 92 | self.owner.current_indent += 1 93 | self.owner.last = None 94 | 95 | def __exit__(self, *_): 96 | """ 97 | Close code block 98 | """ 99 | if self.owner.last is not None: 100 | with self.owner.last: 101 | pass 102 | self.owner.current_indent -= 1 103 | self.owner.write(f"") 104 | -------------------------------------------------------------------------------- /src/code_generation/cpp/__init__.py: -------------------------------------------------------------------------------- 1 | from . import cpp_array 2 | from . import cpp_class 3 | from . import cpp_enum 4 | from . import cpp_function 5 | from . import cpp_generator 6 | from . import cpp_variable 7 | -------------------------------------------------------------------------------- /src/code_generation/cpp/cpp_array.py: -------------------------------------------------------------------------------- 1 | from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation 2 | 3 | 4 | # noinspection PyUnresolvedReferences 5 | class CppArray(CppLanguageElement): 6 | """ 7 | The Python class that generates string representation for C++ array (automatic or class member) 8 | For example: 9 | 10 | int arr[] = {1,2,2}; 11 | double doubles[5] = {1.0,2.0}; 12 | 13 | class MyClass 14 | { 15 | int m_arr1[10]; 16 | static double m_arr2[]; 17 | ... 18 | } 19 | Available properties: 20 | 21 | type - string, variable type 22 | (is_)static - boolean, 'static' prefix 23 | (is_)const - boolean, 'const' prefix 24 | (is_)class_member - boolean, for appropriate definition/declaration rendering 25 | array_size - integer, size of array if required 26 | newline_align - in the array definition rendering place every item on the new string 27 | 28 | NOTE: versions 2.0+ of CodeGenerator support boolean properties without "is_" suffix, 29 | but old versions preserved for backward compatibility 30 | """ 31 | availablePropertiesNames = {'type', 32 | 'is_static', 33 | 'static', 34 | 'is_const', 35 | 'const', 36 | 'is_class_member', 37 | 'class_member', 38 | 'array_size', 39 | 'newline_align'} | CppLanguageElement.availablePropertiesNames 40 | 41 | def __init__(self, **properties): 42 | self.is_static = False 43 | self.is_const = False 44 | self.is_class_member = False 45 | self.array_size = 0 46 | self.newline_align = None 47 | 48 | # array elements 49 | self.items = [] 50 | 51 | input_property_names = set(properties.keys()) 52 | self.check_input_properties_names(input_property_names) 53 | super(CppArray, self).__init__(properties) 54 | self.init_class_properties(current_class_properties=self.availablePropertiesNames, 55 | input_properties_dict=properties) 56 | 57 | def _render_static(self): 58 | """ 59 | @return: 'static' prefix if required 60 | """ 61 | return 'static ' if self.is_static else '' 62 | 63 | def _render_const(self): 64 | """ 65 | @return: 'const' prefix if required 66 | """ 67 | return 'const ' if self.is_const else '' 68 | 69 | def _render_size(self): 70 | """ 71 | @return: array size 72 | """ 73 | return self.array_size if self.array_size else '' 74 | 75 | def _render_content(self): 76 | """ 77 | @return: array items if any 78 | """ 79 | return ', '.join(self.items) if self.items else 'nullptr' 80 | 81 | def _render_value(self, cpp): 82 | """ 83 | Render to string array items 84 | """ 85 | if not self.items: 86 | raise RuntimeError('Empty arrays do not supported') 87 | for item in self.items[:-1]: 88 | cpp('{0},'.format(item)) 89 | cpp('{0}'.format(self.items[-1])) 90 | 91 | def declaration(self): 92 | """ 93 | @return: CppDeclaration wrapper, that could be used 94 | for declaration rendering using render_to_string(cpp) interface 95 | """ 96 | return CppDeclaration(self) 97 | 98 | def definition(self): 99 | """ 100 | @return: CppImplementation wrapper, that could be used 101 | for definition rendering using render_to_string(cpp) interface 102 | """ 103 | return CppImplementation(self) 104 | 105 | def add_array_item(self, item): 106 | """ 107 | If variable is an array it could contain a number of items 108 | @param: item - string 109 | """ 110 | self.items.append(item) 111 | 112 | def add_array_items(self, items): 113 | """ 114 | If variable is an array it could contain a number of items 115 | @param: items - list of strings 116 | """ 117 | self.items.extend(items) 118 | 119 | def render_to_string(self, cpp): 120 | """ 121 | Generates definition for the C++ array. 122 | Output depends on the array type 123 | 124 | Generates something like 125 | int my_array[5] = {1, 2, 0}; 126 | const char* my_array[] = {"Hello", "World"}; 127 | 128 | That method is used for generating automatic (non-class members) arrays 129 | For class members use render_to_string_declaration/render_to_string_implementation methods 130 | """ 131 | if self.is_class_member and not (self.is_static and self.is_const): 132 | raise RuntimeError('For class member variables use definition() and declaration() methods') 133 | 134 | # newline-formatting of array elements makes sense only if array is not empty 135 | if self.newline_align and self.items: 136 | with cpp.block(f'{self._render_static()}{self._render_const()}{self.type} ' 137 | f'{self.name}[{self._render_size()}] = ', ';'): 138 | # render array items 139 | self._render_value(cpp) 140 | else: 141 | cpp(f'{self._render_static()}{self._render_const()}{self.type} ' 142 | f'{self.name}[{self._render_size()}] = {{{self._render_content()}}};') 143 | 144 | def render_to_string_declaration(self, cpp): 145 | """ 146 | Generates declaration for the C++ array. 147 | Non-static arrays-class members do not supported 148 | 149 | Example: 150 | static int my_class_member_array[]; 151 | """ 152 | if not self.is_class_member: 153 | raise RuntimeError('For automatic variable use its render_to_string() method') 154 | cpp(f'{self._render_static()}{self._render_const()}{self.type} {self.name}[{self._render_size()}];') 155 | 156 | def render_to_string_implementation(self, cpp): 157 | """ 158 | Generates definition for the C++ array. 159 | Output depends on the array type 160 | 161 | Example: 162 | int MyClass::m_my_static_array[] = 163 | { 164 | ... 165 | }; 166 | 167 | Non-static arrays-class members do not supported 168 | """ 169 | if not self.is_class_member: 170 | raise RuntimeError('For automatic variable use its render_to_string() method') 171 | 172 | # generate definition for the static class member arrays only 173 | # other types are not supported 174 | if not self.is_static: 175 | raise RuntimeError('Only static arrays as class members are supported') 176 | 177 | # newline-formatting of array elements makes sense only if array is not empty 178 | if self.newline_align and self.items: 179 | with cpp.block(f'{self._render_static()}{self._render_const()}{self.type} ' 180 | f'{self.name}[{self._render_size()}] = ', ';'): 181 | # render array items 182 | self._render_value(cpp) 183 | else: 184 | cpp(f'{self._render_static()}{self._render_const()}{self.type} ' 185 | f'{self.name}[{self._render_size()}] = {{{self._render_content()}}};') 186 | -------------------------------------------------------------------------------- /src/code_generation/cpp/cpp_class.py: -------------------------------------------------------------------------------- 1 | from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation 2 | from code_generation.cpp.cpp_function import CppFunction 3 | from textwrap import dedent 4 | 5 | 6 | class CppClass(CppLanguageElement): 7 | """ 8 | The Python class that generates string representation for C++ class or struct. 9 | Usually contains a number of child elements - internal classes, enums, methods and variables. 10 | Available properties: 11 | is_struct - boolean, use 'struct' keyword for class declaration, 'class' otherwise 12 | documentation - string, '/// Example doxygen' 13 | 14 | Example of usage: 15 | 16 | # Python code 17 | cpp_class = CppClass(name = 'MyClass', is_struct = True) 18 | cpp_class.add_variable(CppVariable(name = "m_var", 19 | type = 'size_t', 20 | is_static = True, 21 | is_const = True, 22 | initialization_value = 255)) 23 | 24 | def handle(cpp): cpp('return m_var;') 25 | 26 | cpp_class.add_method(CppFunction(name = "GetVar", 27 | ret_type = 'size_t', 28 | is_static = True, 29 | implementation_handle = handle)) 30 | 31 | // Generated C++ declaration 32 | struct MyClass 33 | { 34 | static const size_t m_var; 35 | static size_t GetVar(); 36 | } 37 | 38 | // Generated C++ definition 39 | const size_t MyClass::m_var = 255; 40 | 41 | size_t MyClass::GetVar() 42 | { 43 | return m_var; 44 | } 45 | """ 46 | availablePropertiesNames = {'is_struct', 47 | 'documentation', 48 | 'parent_class'} | CppLanguageElement.availablePropertiesNames 49 | 50 | class CppMethod(CppFunction): 51 | """ 52 | The Python class that generates string representation for C++ method 53 | Parameters are passed as plain strings('int a', 'void p = NULL' etc.) 54 | Available properties: 55 | ret_type - string, return value for the method ('void', 'int'). Could not be set for constructors 56 | is_static - boolean, static method prefix 57 | is_const - boolean, const method prefix, could not be static 58 | is_virtual - boolean, virtual method postfix, could not be static 59 | is_pure_virtual - boolean, ' = 0' method postfix, could not be static 60 | documentation - string, '/// Example doxygen' 61 | implementation_handle - reference to a function that receives 'self' and C++ code generator handle 62 | (see code_generator.cpp) and generates method body without braces 63 | Ex. 64 | #Python code 65 | def functionBody(self, cpp): cpp('return 42;') 66 | f1 = CppFunction(name = 'GetAnswer', 67 | ret_type = 'int', 68 | documentation = '// Generated code', 69 | implementation_handle = functionBody) 70 | 71 | // Generated code 72 | int MyClass::GetAnswer() 73 | { 74 | return 42; 75 | } 76 | """ 77 | availablePropertiesNames = {'ret_type', 78 | 'is_static', 79 | 'is_constexpr', 80 | 'is_virtual', 81 | 'is_inline', 82 | 'is_pure_virtual', 83 | 'is_const', 84 | 'is_override', 85 | 'is_final', 86 | 'implementation_handle', 87 | 'documentation'} | CppLanguageElement.availablePropertiesNames 88 | 89 | def __init__(self, **properties): 90 | # arguments are plain strings 91 | # e.g. 'int* a', 'const string& s', 'size_t sz = 10' 92 | self.is_static = False 93 | self.is_constexpr = False 94 | self.is_virtual = False 95 | self.is_inline = False 96 | self.is_pure_virtual = False 97 | self.is_const = False 98 | self.is_override = False 99 | self.is_final = False 100 | self.arguments = [] 101 | self.implementation_handle = properties.get('implementation_handle') 102 | self.documentation = properties.get('documentation') 103 | 104 | # check properties 105 | input_property_names = set(properties.keys()) 106 | self.check_input_properties_names(input_property_names) 107 | super().__init__(**properties) 108 | self.init_class_properties(current_class_properties=self.availablePropertiesNames, 109 | input_properties_dict=properties) 110 | 111 | def _render_static(self): 112 | """ 113 | Before function name, declaration only 114 | Static functions can't be const, virtual or pure virtual 115 | """ 116 | return 'static ' if self.is_static else '' 117 | 118 | def _render_constexpr(self): 119 | """ 120 | Before function name, declaration only 121 | Constexpr functions can't be const, virtual or pure virtual 122 | """ 123 | return 'constexpr ' if self.is_constexpr else '' 124 | 125 | def _render_virtual(self): 126 | """ 127 | Before function name, could be in declaration or definition 128 | Virtual functions can't be static or constexpr 129 | """ 130 | return 'virtual ' if self.is_virtual else '' 131 | 132 | def _render_inline(self): 133 | """ 134 | Before function name, could be in declaration or definition 135 | Inline functions can't be static, virtual or constexpr 136 | """ 137 | return 'inline ' if self.is_inline else '' 138 | 139 | def _render_ret_type(self): 140 | """ 141 | Return type, could be in declaration or definition 142 | """ 143 | return self.ret_type if self.ret_type else '' 144 | 145 | def _render_pure(self): 146 | """ 147 | After function name, declaration only 148 | Pure virtual functions must be virtual 149 | """ 150 | return ' = 0' if self.is_pure_virtual else '' 151 | 152 | def _render_const(self): 153 | """ 154 | After function name, could be in declaration or definition 155 | Const functions can't be static, virtual or constexpr 156 | """ 157 | return ' const' if self.is_const else '' 158 | 159 | def _render_override(self): 160 | """ 161 | After function name, could be in declaration or definition 162 | Override functions must be virtual 163 | """ 164 | return ' override' if self.is_override else '' 165 | 166 | def _render_final(self): 167 | """ 168 | After function name, could be in declaration or definition 169 | Final functions must be virtual 170 | """ 171 | return ' final' if self.is_final else '' 172 | 173 | def _sanity_check(self): 174 | """ 175 | Check whether attributes compose a correct C++ code 176 | """ 177 | if self.is_inline and (self.is_virtual or self.is_pure_virtual): 178 | raise ValueError(f'Inline method {self.name} could not be virtual') 179 | if self.is_constexpr and (self.is_virtual or self.is_pure_virtual): 180 | raise ValueError(f'Constexpr method {self.name} could not be virtual') 181 | if self.is_const and self.is_static: 182 | raise ValueError(f'Static method {self.name} could not be const') 183 | if self.is_const and self.is_virtual: 184 | raise ValueError(f'Virtual method {self.name} could not be const') 185 | if self.is_const and self.is_pure_virtual: 186 | raise ValueError(f'Pure virtual method {self.name} could not be const') 187 | if self.is_override and not self.is_virtual: 188 | raise ValueError(f'Override method {self.name} should be virtual') 189 | if self.is_inline and (self.is_virtual or self.is_pure_virtual): 190 | raise ValueError(f'Inline method {self.name} could not be virtual') 191 | if self.is_final and not self.is_virtual: 192 | raise ValueError(f'Final method {self.name} should be virtual') 193 | if self.is_static and self.is_virtual: 194 | raise ValueError(f'Static method {self.name} could not be virtual') 195 | if self.is_pure_virtual and not self.is_virtual: 196 | raise ValueError(f'Pure virtual method {self.name} is also a virtual method') 197 | if not self.ref_to_parent: 198 | raise ValueError(f'Method {self.name} object must be a child of CppClass') 199 | if self.is_constexpr and self.implementation_handle is None: 200 | raise ValueError(f'Method {self.name} object must be initialized when "constexpr"') 201 | if self.is_pure_virtual and self.implementation_handle is not None: 202 | raise ValueError(f'Pure virtual method {self.name} could not be implemented') 203 | 204 | def add_argument(self, argument): 205 | """ 206 | @param: argument string representation of the C++ function argument ('int a', 'void p = NULL' etc) 207 | """ 208 | self.arguments.append(argument) 209 | 210 | def args(self): 211 | """ 212 | @return: string arguments 213 | """ 214 | return ", ".join(self.arguments) 215 | 216 | def implementation(self, cpp): 217 | """ 218 | The method calls Python function that creates C++ method body if handle exists 219 | """ 220 | if self.implementation_handle is not None: 221 | self.implementation_handle(self, cpp) 222 | 223 | def declaration(self): 224 | """ 225 | @return: CppDeclaration wrapper, that could be used 226 | for declaration rendering using render_to_string(cpp) interface 227 | """ 228 | return CppDeclaration(self) 229 | 230 | def definition(self): 231 | """ 232 | @return: CppImplementation wrapper, that could be used 233 | for definition rendering using render_to_string(cpp) interface 234 | """ 235 | return CppImplementation(self) 236 | 237 | def render_to_string(self, cpp): 238 | """ 239 | By default, method is rendered as a declaration together with implementation, 240 | like the method is implemented within the C++ class body, e.g. 241 | class A 242 | { 243 | void f() 244 | { 245 | ... 246 | } 247 | } 248 | """ 249 | # check all properties for the consistency 250 | self._sanity_check() 251 | if self.documentation: 252 | cpp(dedent(self.documentation)) 253 | with cpp.block(f'{self._render_static()}{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}' 254 | f'{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})' 255 | f'{self._render_const()}' 256 | f'{self._render_override()}' 257 | f'{self._render_final()}' 258 | f'{self._render_pure()}'): 259 | self.implementation(cpp) 260 | 261 | def render_to_string_declaration(self, cpp): 262 | """ 263 | Special case for a method declaration string representation. 264 | Generates just a function signature terminated by ';' 265 | Example: 266 | int GetX() const; 267 | """ 268 | # check all properties for the consistency 269 | self._sanity_check() 270 | if self.is_constexpr: 271 | if self.documentation: 272 | cpp(dedent(self.documentation)) 273 | self.render_to_string(cpp) 274 | else: 275 | cpp(f'{self._render_static()}{self._render_virtual()}{self._render_inline()}' 276 | f'{self._render_ret_type()} {self.name}({self.args()})' 277 | f'{self._render_const()}' 278 | f'{self._render_override()}' 279 | f'{self._render_final()}' 280 | f'{self._render_pure()};') 281 | 282 | def render_to_string_implementation(self, cpp): 283 | """ 284 | Special case for a method implementation string representation. 285 | Generates method string in the form 286 | Example: 287 | int MyClass::GetX() const 288 | { 289 | ... 290 | } 291 | Generates method body if self.implementation_handle property exists 292 | """ 293 | # check all properties for the consistency 294 | self._sanity_check() 295 | 296 | if self.implementation_handle is None: 297 | raise RuntimeError(f'No implementation handle for the method {self.name}') 298 | 299 | if self.documentation and not self.is_constexpr: 300 | cpp(dedent(self.documentation)) 301 | with cpp.block(f'{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}' 302 | f'{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})' 303 | f'{self._render_const()}' 304 | f'{self._render_override()}' 305 | f'{self._render_final()}' 306 | f'{self._render_pure()}'): 307 | self.implementation(cpp) 308 | 309 | def __init__(self, **properties): 310 | self.is_struct = False 311 | self.documentation = None 312 | self.parent_class = None 313 | input_property_names = set(properties.keys()) 314 | self.check_input_properties_names(input_property_names) 315 | super(CppClass, self).__init__(properties) 316 | self.init_class_properties(current_class_properties=self.availablePropertiesNames, 317 | input_properties_dict=properties) 318 | 319 | # aggregated classes 320 | self.internal_class_elements = [] 321 | 322 | # class members 323 | self.internal_variable_elements = [] 324 | 325 | # array class members 326 | self.internal_array_elements = [] 327 | 328 | # class methods 329 | self.internal_method_elements = [] 330 | 331 | # class enums 332 | self.internal_enum_elements = [] 333 | 334 | def _parent_class(self): 335 | """ 336 | @return: parent class object 337 | """ 338 | return self.parent_class if self.parent_class else "" 339 | 340 | def inherits(self): 341 | """ 342 | @return: string representation of the inheritance 343 | """ 344 | return f' : public {self._parent_class()}' if self.parent_class else '' 345 | 346 | ######################################## 347 | # ADD CLASS MEMBERS 348 | def add_enum(self, enum): 349 | """ 350 | @param: enum CppEnum instance 351 | """ 352 | enum.ref_to_parent = self 353 | self.internal_enum_elements.append(enum) 354 | 355 | def add_variable(self, cpp_variable): 356 | """ 357 | @param: cpp_variable CppVariable instance 358 | """ 359 | cpp_variable.ref_to_parent = self 360 | cpp_variable.is_class_member = True 361 | self.internal_variable_elements.append(cpp_variable) 362 | 363 | def add_array(self, cpp_variable): 364 | """ 365 | @param: cpp_variable CppVariable instance 366 | """ 367 | cpp_variable.ref_to_parent = self 368 | cpp_variable.is_class_member = True 369 | self.internal_array_elements.append(cpp_variable) 370 | 371 | def add_internal_class(self, cpp_class): 372 | """ 373 | Add nested class 374 | @param: cpp_class CppClass instance 375 | """ 376 | cpp_class.ref_to_parent = self 377 | self.internal_class_elements.append(cpp_class) 378 | 379 | def add_method(self, method): 380 | """ 381 | @param: method CppFunction instance 382 | """ 383 | method.ref_to_parent = self 384 | method.is_method = True 385 | self.internal_method_elements.append(method) 386 | 387 | ######################################## 388 | # RENDER CLASS MEMBERS 389 | def _render_internal_classes_declaration(self, cpp): 390 | """ 391 | Generates section of nested classes 392 | Could be placed both in 'private:' or 'public:' sections 393 | Method is protected as it is used by CppClass only 394 | """ 395 | for classItem in self.internal_class_elements: 396 | classItem.declaration().render_to_string(cpp) 397 | cpp.newline() 398 | 399 | def _render_enum_section(self, cpp): 400 | """ 401 | Render to string all contained enums 402 | Method is protected as it is used by CppClass only 403 | """ 404 | for enumItem in self.internal_enum_elements: 405 | enumItem.render_to_string(cpp) 406 | cpp.newline() 407 | 408 | def _render_variables_declaration(self, cpp): 409 | """ 410 | Render to string all contained variable class members 411 | Method is protected as it is used by CppClass only 412 | """ 413 | for varItem in self.internal_variable_elements: 414 | varItem.declaration().render_to_string(cpp) 415 | cpp.newline() 416 | 417 | def _render_array_declaration(self, cpp): 418 | """ 419 | Render to string all contained array class members 420 | Method is protected as it is used by CppClass only 421 | """ 422 | for arrItem in self.internal_array_elements: 423 | arrItem.declaration().render_to_string(cpp) 424 | cpp.newline() 425 | 426 | def _render_methods_declaration(self, cpp): 427 | """ 428 | Generates all class methods declaration 429 | Should be placed in 'public:' section 430 | Method is protected as it is used by CppClass only 431 | """ 432 | for funcItem in self.internal_method_elements: 433 | funcItem.render_to_string_declaration(cpp) 434 | cpp.newline() 435 | 436 | def render_static_members_implementation(self, cpp): 437 | """ 438 | Generates definition for all static class variables 439 | Method is public, as it could be used for nested classes 440 | int MyClass::my_static_array[] = {} 441 | """ 442 | # generate definition for static variables 443 | static_vars = [variable for variable in self.internal_variable_elements if variable.is_static] 444 | for varItem in static_vars: 445 | varItem.definition().render_to_string(cpp) 446 | cpp.newline() 447 | for arrItem in self.internal_array_elements: 448 | arrItem.definition().render_to_string(cpp) 449 | cpp.newline() 450 | 451 | # do the same for nested classes 452 | for classItem in self.internal_class_elements: 453 | classItem.render_static_members_implementation(cpp) 454 | cpp.newline() 455 | 456 | def render_methods_implementation(self, cpp): 457 | """ 458 | Generates all class methods declaration 459 | Should be placed in 'public:' section 460 | Method is public, as it could be used for nested classes 461 | """ 462 | # generate methods implementation section 463 | for funcItem in self.internal_method_elements: 464 | if not funcItem.is_pure_virtual: 465 | funcItem.render_to_string_implementation(cpp) 466 | cpp.newline() 467 | # do the same for nested classes 468 | for classItem in self.internal_class_elements: 469 | classItem.render_static_members_implementation(cpp) 470 | cpp.newline() 471 | 472 | ######################################## 473 | # GROUP GENERATED SECTIONS 474 | def class_interface(self, cpp): 475 | """ 476 | Generates section that generally used as an 'open interface' 477 | Generates string representation for enums, internal classes and methods 478 | Should be placed in 'public:' section 479 | """ 480 | self._render_enum_section(cpp) 481 | self._render_internal_classes_declaration(cpp) 482 | self._render_methods_declaration(cpp) 483 | 484 | def private_class_members(self, cpp): 485 | """ 486 | Generates section of class member variables. 487 | Should be placed in 'private:' section 488 | """ 489 | self._render_variables_declaration(cpp) 490 | self._render_array_declaration(cpp) 491 | 492 | def render_to_string(self, cpp): 493 | """ 494 | Render to string both declaration and definition. 495 | A rare case enough, because the only code generator handle is used. 496 | Typically class declaration is rendered to *.h file, and definition to *.cpp 497 | """ 498 | self.render_to_string_declaration(cpp) 499 | self.render_to_string_implementation(cpp) 500 | 501 | def render_to_string_declaration(self, cpp): 502 | """ 503 | Render to string class declaration. 504 | Typically handle to header should be passed as 'cpp' param 505 | """ 506 | if self.documentation: 507 | cpp(dedent(self.documentation)) 508 | 509 | with cpp.block(f'{self._render_class_type()} {self.name} {self.inherits()}', postfix=';'): 510 | 511 | # in case of struct all members meant to be public 512 | if not self.is_struct: 513 | cpp.label('public') 514 | self.class_interface(cpp) 515 | cpp.newline() 516 | 517 | # in case of struct all members meant to be public 518 | if not self.is_struct: 519 | cpp.label('private') 520 | self.private_class_members(cpp) 521 | 522 | def _render_class_type(self): 523 | """ 524 | @return: 'class' or 'struct' keyword 525 | """ 526 | return 'struct' if self.is_struct else 'class' 527 | 528 | def render_to_string_implementation(self, cpp): 529 | """ 530 | Render to string class definition. 531 | Typically handle to *.cpp file should be passed as 'cpp' param 532 | """ 533 | cpp.newline(2) 534 | self.render_static_members_implementation(cpp) 535 | self.render_methods_implementation(cpp) 536 | 537 | def declaration(self): 538 | """ 539 | @return: CppDeclaration wrapper, that could be used 540 | for declaration rendering using render_to_string(cpp) interface 541 | """ 542 | return CppDeclaration(self) 543 | 544 | def definition(self): 545 | """ 546 | @return: CppImplementation wrapper, that could be used 547 | for definition rendering using render_to_string(cpp) interface 548 | """ 549 | return CppImplementation(self) 550 | -------------------------------------------------------------------------------- /src/code_generation/cpp/cpp_enum.py: -------------------------------------------------------------------------------- 1 | from code_generation.cpp.cpp_generator import CppLanguageElement 2 | 3 | __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: 4 | classes, methods and functions, variables, enums. 5 | Every C++ element could render its current state to a string that could be evaluated as 6 | a legal C++ construction. 7 | 8 | Some elements could be rendered to a pair of representations (i.e. declaration and definition) 9 | 10 | Example: 11 | # Python code 12 | cpp_class = CppClass(name = 'MyClass', is_struct = True) 13 | cpp_class.add_variable(CppVariable(name = "m_var", 14 | type = 'size_t', 15 | is_static = True, 16 | is_const = True, 17 | initialization_value = 255)) 18 | 19 | // Generated C++ declaration 20 | struct MyClass 21 | { 22 | static const size_t m_var; 23 | } 24 | 25 | // Generated C++ definition 26 | const size_t MyClass::m_var = 255; 27 | 28 | 29 | That module uses and highly depends on code_generator.py as it uses 30 | code generating and formatting primitives implemented there. 31 | 32 | The main object referenced from code_generator.py is CppFile, 33 | which is passed as a parameter to render_to_string(cpp) Python method 34 | 35 | It could also be used for composing more complicated C++ code, 36 | that does not supported by cpp_generator 37 | 38 | It support: 39 | 40 | - functional calls: 41 | cpp('int a = 10;') 42 | 43 | - 'with' semantic: 44 | with cpp.block('class MyClass', ';') 45 | class_definition(cpp) 46 | 47 | - append code to the last string without EOL: 48 | cpp.append(', p = NULL);') 49 | 50 | - empty lines: 51 | cpp.newline(2) 52 | 53 | For detailed information see code_generator.py documentation. 54 | """ 55 | 56 | 57 | class CppEnum(CppLanguageElement): 58 | """ 59 | The Python class that generates string representation for C++ enum 60 | All enum elements are explicitly initialized with incremented values 61 | 62 | Available properties: 63 | prefix - string, prefix added to every enum element, 'e' by default ('eItem1') 64 | add_counter - boolean, terminating value that shows count of enum elements added, 'True' by default. 65 | 66 | Example of usage: 67 | # Python code 68 | enum_elements = CppEnum(name = 'Items') 69 | for item in ['Chair', 'Table', 'Shelve']: 70 | enum_elements.add_item(enum_elements.name) 71 | 72 | // Generated C++ code 73 | enum Items 74 | { 75 | eChair = 0, 76 | eTable = 1, 77 | eShelve = 2, 78 | eItemsCount = 3 79 | } 80 | """ 81 | availablePropertiesNames = {'prefix', 82 | 'enum_class', 83 | 'add_counter'} | CppLanguageElement.availablePropertiesNames 84 | 85 | def __init__(self, **properties): 86 | self.enum_class = False 87 | # check properties 88 | input_property_names = set(properties.keys()) 89 | self.check_input_properties_names(input_property_names) 90 | super(CppEnum, self).__init__(properties) 91 | 92 | self.init_class_properties(current_class_properties=self.availablePropertiesNames, 93 | input_properties_dict=properties) 94 | 95 | # place enum items here 96 | self.enum_items = [] 97 | 98 | def _render_class(self): 99 | return 'class ' if self.enum_class else '' 100 | 101 | def add_item(self, item): 102 | """ 103 | @param: item - string representation for the enum element 104 | """ 105 | self.enum_items.append(item) 106 | 107 | def add_items(self, items): 108 | """ 109 | @param: items - list of strings 110 | """ 111 | self.enum_items.extend(items) 112 | 113 | # noinspection PyUnresolvedReferences 114 | def render_to_string(self, cpp): 115 | """ 116 | Generates a string representation for the enum 117 | It always contains a terminating value that shows count of enum elements 118 | enum MyEnum 119 | { 120 | eItem1 = 0, 121 | eItem2 = 1, 122 | eMyEnumCount = 2 123 | } 124 | """ 125 | counter = 0 126 | final_prefix = self.prefix if self.prefix is not None else 'e' 127 | with cpp.block(f'enum {self._render_class()}{self.name}', postfix=';'): 128 | for item in self.enum_items: 129 | cpp(f'{final_prefix}{item} = {counter},') 130 | counter += 1 131 | if self.add_counter in [None, True]: 132 | last_element = f'{final_prefix}{self.name}Count = {counter}' 133 | cpp(last_element) 134 | -------------------------------------------------------------------------------- /src/code_generation/cpp/cpp_function.py: -------------------------------------------------------------------------------- 1 | from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation 2 | from textwrap import dedent 3 | 4 | 5 | class CppFunction(CppLanguageElement): 6 | """ 7 | The Python class that generates string representation for C++ function (not method!) 8 | Parameters are passed as plain strings('int a', 'void p = NULL' etc.) 9 | Available properties: 10 | ret_type - string, return value for the method ('void', 'int'). Could not be set for constructors 11 | is_constexpr - boolean, const method prefix 12 | documentation - string, '/// Example doxygen' 13 | implementation_handle - reference to a function that receives 'self' and C++ code generator handle 14 | (see code_generator.cpp) and generates method body without braces 15 | Ex. 16 | #Python code 17 | def functionBody(self, cpp): cpp('return 42;') 18 | f1 = CppFunction(name = 'GetAnswer', 19 | ret_type = 'int', 20 | documentation = '// Generated code', 21 | implementation_handle = functionBody) 22 | 23 | // Generated code 24 | int GetAnswer() 25 | { 26 | return 42; 27 | } 28 | """ 29 | availablePropertiesNames = {'ret_type', 30 | 'is_constexpr', 31 | 'implementation_handle', 32 | 'documentation'} | CppLanguageElement.availablePropertiesNames 33 | 34 | def __init__(self, **properties): 35 | # arguments are plain strings 36 | # e.g. 'int* a', 'const string& s', 'size_t sz = 10' 37 | self.arguments = [] 38 | self.ret_type = None 39 | self.implementation_handle = None 40 | self.documentation = None 41 | self.is_constexpr = False 42 | 43 | # check properties 44 | input_property_names = set(properties.keys()) 45 | self.check_input_properties_names(input_property_names) 46 | super(CppFunction, self).__init__(properties) 47 | self.init_class_properties(current_class_properties=self.availablePropertiesNames, 48 | input_properties_dict=properties) 49 | 50 | def _sanity_check(self): 51 | """ 52 | Check whether attributes compose a correct C++ code 53 | """ 54 | if self.is_constexpr and self.implementation_handle is None: 55 | raise ValueError(f'Constexpr function {self.name} must have implementation') 56 | 57 | def _render_constexpr(self): 58 | """ 59 | Before function name, declaration only 60 | Constexpr functions can't be const, virtual or pure virtual 61 | """ 62 | return 'constexpr ' if self.is_constexpr else '' 63 | 64 | def args(self): 65 | """ 66 | @return: string arguments 67 | """ 68 | return ", ".join(self.arguments) 69 | 70 | def add_argument(self, argument): 71 | """ 72 | @param: argument string representation of the C++ function argument ('int a', 'void p = NULL' etc) 73 | """ 74 | self.arguments.append(argument) 75 | 76 | def implementation(self, cpp): 77 | """ 78 | The method calls Python function that creates C++ method body if handle exists 79 | """ 80 | if self.implementation_handle is not None: 81 | self.implementation_handle(self, cpp) 82 | 83 | def declaration(self): 84 | """ 85 | @return: CppDeclaration wrapper, that could be used 86 | for declaration rendering using render_to_string(cpp) interface 87 | """ 88 | return CppDeclaration(self) 89 | 90 | def definition(self): 91 | """ 92 | @return: CppImplementation wrapper, that could be used 93 | for definition rendering using render_to_string(cpp) interface 94 | """ 95 | return CppImplementation(self) 96 | 97 | def render_to_string(self, cpp): 98 | """ 99 | By function method is rendered as a declaration together with implementation 100 | void f() 101 | { 102 | ... 103 | } 104 | """ 105 | # check all properties for the consistency 106 | self._sanity_check() 107 | if self.documentation: 108 | cpp(dedent(self.documentation)) 109 | with cpp.block(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})'): 110 | self.implementation(cpp) 111 | 112 | def render_to_string_declaration(self, cpp): 113 | """ 114 | Special case for a function declaration string representation. 115 | Generates just a function signature terminated by ';' 116 | Example: 117 | int GetX(); 118 | """ 119 | # check all properties for the consistency 120 | if self.is_constexpr: 121 | if self.documentation: 122 | cpp(dedent(self.documentation)) 123 | self.render_to_string(cpp) 124 | else: 125 | cpp(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()});') 126 | 127 | def render_to_string_implementation(self, cpp): 128 | """ 129 | Special case for a function implementation string representation. 130 | Generates function string in the form 131 | Example: 132 | int GetX() const 133 | { 134 | ... 135 | } 136 | Generates method body if self.implementation_handle property exists 137 | """ 138 | if self.implementation_handle is None: 139 | raise RuntimeError(f'No implementation handle for the function {self.name}') 140 | 141 | # check all properties for the consistency 142 | if self.documentation and not self.is_constexpr: 143 | cpp(dedent(self.documentation)) 144 | with cpp.block(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})'): 145 | self.implementation(cpp) 146 | -------------------------------------------------------------------------------- /src/code_generation/cpp/cpp_generator.py: -------------------------------------------------------------------------------- 1 | __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: 2 | classes, methods and functions, variables, enums. 3 | Every C++ element could render its current state to a string that could be evaluated as 4 | a legal C++ construction. 5 | 6 | Some elements could be rendered to a pair of representations (i.e. declaration and definition) 7 | 8 | Example: 9 | # Python code 10 | cpp_class = CppClass(name = 'MyClass', is_struct = True) 11 | cpp_class.add_variable(CppVariable(name = "m_var", 12 | type = 'size_t', 13 | is_static = True, 14 | is_const = True, 15 | initialization_value = 255)) 16 | 17 | // Generated C++ declaration 18 | struct MyClass 19 | { 20 | static const size_t m_var; 21 | } 22 | 23 | // Generated C++ definition 24 | const size_t MyClass::m_var = 255; 25 | 26 | 27 | That module uses and highly depends on code_generator.py as it uses 28 | code generating and formatting primitives implemented there. 29 | 30 | The main object referenced from code_generator.py is CppFile, 31 | which is passed as a parameter to render_to_string(cpp) Python method 32 | 33 | It could also be used for composing more complicated C++ code, 34 | that does not supported by cpp_generator 35 | 36 | It support: 37 | 38 | - functional calls: 39 | cpp('int a = 10;') 40 | 41 | - 'with' semantic: 42 | with cpp.block('class MyClass', ';') 43 | class_definition(cpp) 44 | 45 | - append code to the last string without EOL: 46 | cpp.append(', p = NULL);') 47 | 48 | - empty lines: 49 | cpp.newline(2) 50 | 51 | For detailed information see code_generator.py documentation. 52 | """ 53 | 54 | 55 | ########################################################################### 56 | # declaration/Implementation helpers 57 | class CppDeclaration(object): 58 | """ 59 | declaration/Implementation pair is used to split one element code generation to 60 | declaration and implementation parts 61 | E.g. method declaration 62 | struct Obj 63 | { 64 | int GetItem() const; 65 | } 66 | 67 | ... and method implementation 68 | int Obj::GetItem() const {...} 69 | 70 | That could be necessary to use unified render_to_string() interface, that is impossible for 71 | C++ primitives having two string representations (i.e. declaration and definition) 72 | """ 73 | 74 | def __init__(self, cpp_element): 75 | self.cpp_element = cpp_element 76 | 77 | def render_to_string(self, cpp): 78 | self.cpp_element.render_to_string_declaration(cpp) 79 | 80 | 81 | class CppImplementation(object): 82 | """ 83 | See declaration description 84 | """ 85 | 86 | def __init__(self, cpp_element): 87 | self.cpp_element = cpp_element 88 | 89 | def render_to_string(self, cpp): 90 | self.cpp_element.render_to_string_implementation(cpp) 91 | 92 | 93 | # C++ language element generators 94 | class CppLanguageElement(object): 95 | """ 96 | The base class for all C++ language elements. 97 | Contains dynamic storage for element properties 98 | (e.g. is_static for the variable is_virtual for the class method etc) 99 | """ 100 | availablePropertiesNames = {'name', 'ref_to_parent'} 101 | 102 | def __init__(self, properties): 103 | """ 104 | @param: properties - Basic C++ element properties (name, ref_to_parent) 105 | class is a parent for method or a member variable 106 | """ 107 | self.name = properties.get('name') 108 | self.ref_to_parent = properties.get('ref_to_parent') 109 | 110 | def check_input_properties_names(self, input_property_names): 111 | """ 112 | Ensure that all properties that passed to the CppLanguageElement are recognized. 113 | Raise an exception otherwise 114 | """ 115 | unknown_properties = input_property_names.difference(self.availablePropertiesNames) 116 | if unknown_properties: 117 | raise AttributeError( 118 | f'Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}') 119 | 120 | def init_class_properties(self, current_class_properties, input_properties_dict, default_property_value=None): 121 | """ 122 | @param: current_class_properties - all available properties for the C++ element to be generated 123 | @param: input_properties_dict - values for the initialized properties (e.g. is_const=True) 124 | @param: default_property_value - value for properties that are not initialized 125 | (None by default, because of same as False semantic) 126 | """ 127 | # Set all available properties to DefaultValue 128 | for propertyName in current_class_properties: 129 | if propertyName not in CppLanguageElement.availablePropertiesNames: 130 | setattr(self, propertyName, default_property_value) 131 | 132 | # Set all defined properties values (all undefined will be left with defaults) 133 | for (propertyName, propertyValue) in input_properties_dict.items(): 134 | if propertyName not in CppLanguageElement.availablePropertiesNames: 135 | setattr(self, propertyName, propertyValue) 136 | 137 | def process_boolean_properties(self, properties): 138 | """ 139 | For every boolean property starting from 'is_' prefix generate a property without 'is_' prefix 140 | """ 141 | res = {**properties} 142 | for prop in self.availablePropertiesNames: 143 | if prop.startswith("is_"): 144 | res[prop.replace("is_", "")] = properties.get(prop, False) 145 | return res 146 | 147 | def init_boolean_properties(self, current_class_properties, input_properties_dict): 148 | """ 149 | Check if input properties contain either 'is_' prefixed properties or non-prefixed properties 150 | If so, initialize prefixed properties with non-prefixed values 151 | """ 152 | for prop in self.availablePropertiesNames: 153 | if prop.startswith("is_"): 154 | non_prefixed = prop.replace("is_", "") 155 | if non_prefixed in input_properties_dict: 156 | setattr(self, prop, input_properties_dict[non_prefixed]) 157 | current_class_properties.update(input_properties_dict) 158 | 159 | def render_to_string(self, cpp): 160 | """ 161 | @param: cpp - handle that supports code generation interface (see code_generator.py) 162 | Typically it is passed to all child elements so that render their content 163 | """ 164 | raise NotImplementedError('CppLanguageElement is an abstract class') 165 | 166 | def parent_qualifier(self): 167 | """ 168 | Generate string for class name qualifiers 169 | Should be used for methods implementation and static class members definition. 170 | Ex. 171 | void MyClass::MyMethod() 172 | int MyClass::m_staticVar = 0; 173 | 174 | Supports for nested classes, e.g. 175 | void MyClass::NestedClass:: 176 | """ 177 | full_parent_qualifier = '' 178 | parent = self.ref_to_parent 179 | # walk through all existing parents 180 | while parent: 181 | full_parent_qualifier = f'{parent.name}::{full_parent_qualifier}' 182 | parent = parent.ref_to_parent 183 | return full_parent_qualifier 184 | 185 | def fully_qualified_name(self): 186 | """ 187 | Generate string for fully qualified name of the element 188 | Ex. 189 | MyClass::NestedClass::Method() 190 | """ 191 | return f'{self.parent_qualifier()}{self.name}' 192 | -------------------------------------------------------------------------------- /src/code_generation/cpp/cpp_variable.py: -------------------------------------------------------------------------------- 1 | from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation 2 | from textwrap import dedent 3 | 4 | __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: 5 | classes, methods and functions, variables, enums. 6 | Every C++ element could render its current state to a string that could be evaluated as 7 | a legal C++ construction. 8 | 9 | Some elements could be rendered to a pair of representations (i.e. declaration and definition) 10 | 11 | Example: 12 | # Python code 13 | cpp_class = CppClass(name = 'MyClass', is_struct = True) 14 | cpp_class.add_variable(CppVariable(name = "m_var", 15 | type = 'size_t', 16 | is_static = True, 17 | is_const = True, 18 | initialization_value = 255)) 19 | 20 | // Generated C++ declaration 21 | struct MyClass 22 | { 23 | static const size_t m_var; 24 | } 25 | 26 | // Generated C++ definition 27 | const size_t MyClass::m_var = 255; 28 | 29 | 30 | That module uses and highly depends on code_generator.py as it uses 31 | code generating and formatting primitives implemented there. 32 | 33 | The main object referenced from code_generator.py is CppFile, 34 | which is passed as a parameter to render_to_string(cpp) Python method 35 | 36 | It could also be used for composing more complicated C++ code, 37 | that does not supported by cpp_generator 38 | 39 | It support: 40 | 41 | - functional calls: 42 | cpp('int a = 10;') 43 | 44 | - 'with' semantic: 45 | with cpp.block('class MyClass', ';') 46 | class_definition(cpp) 47 | 48 | - append code to the last string without EOL: 49 | cpp.append(', p = NULL);') 50 | 51 | - empty lines: 52 | cpp.newline(2) 53 | 54 | For detailed information see code_generator.py documentation. 55 | """ 56 | 57 | 58 | # noinspection PyUnresolvedReferences 59 | class CppVariable(CppLanguageElement): 60 | """ 61 | The Python class that generates string representation for C++ variable (automatic or class member) 62 | For example: 63 | class MyClass 64 | { 65 | int m_var1; 66 | double m_var2; 67 | ... 68 | } 69 | Available properties: 70 | type - string, variable type 71 | is_static - boolean, 'static' prefix 72 | is_extern - boolean, 'extern' prefix 73 | is_const - boolean, 'const' prefix 74 | is_constexpr - boolean, 'constexpr' prefix 75 | initialization_value - string, initialization_value to be initialized with. 76 | 'a = initialization_value;' for automatic variables, 'a(initialization_value)' for the class member 77 | documentation - string, '/// Example doxygen' 78 | is_class_member - boolean, for appropriate definition/declaration rendering 79 | """ 80 | availablePropertiesNames = {'type', 81 | 'is_static', 82 | 'is_extern', 83 | 'is_const', 84 | 'is_constexpr', 85 | 'initialization_value', 86 | 'documentation', 87 | 'is_class_member'} | CppLanguageElement.availablePropertiesNames 88 | 89 | def __init__(self, **properties): 90 | input_property_names = set(properties.keys()) 91 | self.check_input_properties_names(input_property_names) 92 | super(CppVariable, self).__init__(properties) 93 | self.init_class_properties(current_class_properties=self.availablePropertiesNames, 94 | input_properties_dict=properties) 95 | 96 | def _sanity_check(self): 97 | """ 98 | @raise: ValueError, if some properties are not valid 99 | """ 100 | if self.is_const and self.is_constexpr: 101 | raise ValueError("Variable object can be either 'const' or 'constexpr', not both") 102 | if self.is_constexpr and not self.initialization_value: 103 | raise ValueError("Variable object must be initialized when 'constexpr'") 104 | if self.is_static and self.is_extern: 105 | raise ValueError("Variable object can be either 'extern' or 'static', not both") 106 | 107 | def _render_static(self): 108 | """ 109 | @return: 'static' prefix, can't be used with 'extern' 110 | """ 111 | return 'static ' if self.is_static else '' 112 | 113 | def _render_extern(self): 114 | """ 115 | @return: 'extern' prefix, can't be used with 'static' 116 | """ 117 | return 'extern ' if self.is_extern else '' 118 | 119 | def _render_const(self): 120 | """ 121 | @return: 'const' prefix, can't be used with 'constexpr' 122 | """ 123 | return 'const ' if self.is_const else '' 124 | 125 | def _render_constexpr(self): 126 | """ 127 | @return: 'constexpr' prefix, can't be used with 'const' 128 | """ 129 | return 'constexpr ' if self.is_constexpr else '' 130 | 131 | def _render_init_value(self): 132 | """ 133 | @return: string, initialization_value to be initialized with 134 | """ 135 | return self.initialization_value if self.initialization_value else '' 136 | 137 | def assignment(self, value): 138 | """ 139 | Generates assignment statement for the variable, e.g. 140 | a = 10; 141 | b = 20; 142 | """ 143 | return f'{self.name} = {value}' 144 | 145 | def declaration(self): 146 | """ 147 | @return: CppDeclaration wrapper, that could be used 148 | for declaration rendering using render_to_string(cpp) interface 149 | """ 150 | return CppDeclaration(self) 151 | 152 | def definition(self): 153 | """ 154 | @return: CppImplementation wrapper, that could be used 155 | for definition rendering using render_to_string(cpp) interface 156 | """ 157 | return CppImplementation(self) 158 | 159 | def render_to_string(self, cpp): 160 | """ 161 | Only automatic variables or static const class members could be rendered using this method 162 | Generates complete variable definition, e.g. 163 | int a = 10; 164 | const double b = M_PI; 165 | """ 166 | self._sanity_check() 167 | if self.is_class_member and not (self.is_static and self.is_const): 168 | raise RuntimeError('For class member variables use definition() and declaration() methods') 169 | elif self.is_extern: 170 | cpp(f'{self._render_extern()}{self.type} {self.name};') 171 | else: 172 | if self.documentation: 173 | cpp(dedent(self.documentation)) 174 | cpp(f'{self._render_static()}{self._render_const()}{self._render_constexpr()}' 175 | f'{self.type} {self.assignment(self._render_init_value())};') 176 | 177 | def render_to_string_declaration(self, cpp): 178 | """ 179 | Generates declaration for the class member variables, for example 180 | int m_var; 181 | """ 182 | if not self.is_class_member: 183 | raise RuntimeError('For automatic variable use its render_to_string() method') 184 | 185 | if self.documentation and self.is_class_member: 186 | cpp(dedent(self.documentation)) 187 | cpp(f'{self._render_static()}{self._render_extern()}{self._render_const()}{self._render_constexpr()}' 188 | f'{self.type} {self.name if not self.is_constexpr else self.assignment(self._render_init_value())};') 189 | 190 | def render_to_string_implementation(self, cpp): 191 | """ 192 | Generates definition for the class member variable. 193 | Output depends on the variable type 194 | 195 | Generates something like 196 | int MyClass::m_my_static_var = 0; 197 | 198 | for static class members, and 199 | m_var(0) 200 | for non-static class members. 201 | That string could be used in constructor initialization string 202 | """ 203 | if not self.is_class_member: 204 | raise RuntimeError('For automatic variable use its render_to_string() method') 205 | 206 | # generate definition for the static class member 207 | if not self.is_constexpr: 208 | if self.is_static: 209 | cpp(f'{self._render_const()}{self._render_constexpr()}' 210 | f'{self.type} {self.fully_qualified_name()} = {self._render_init_value()};') 211 | # generate definition for non-static static class member, e.g. m_var(0) 212 | # (string for the constructor initialization list) 213 | else: 214 | cpp(f'{self.name}({self._render_init_value()})') 215 | -------------------------------------------------------------------------------- /src/code_generation/html/__init__.py: -------------------------------------------------------------------------------- 1 | from . import html_generator 2 | -------------------------------------------------------------------------------- /src/code_generation/html/html_generator.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from code_generation.core.code_style import HTMLStyle 3 | 4 | 5 | class HtmlFile: 6 | 7 | Formatter = HTMLStyle 8 | 9 | def __init__(self, filename, writer=None): 10 | self.current_indent = 0 11 | self.last = None 12 | self.filename = filename 13 | if writer: 14 | self.out = writer 15 | else: 16 | self.out = open(filename, "w") 17 | 18 | def close(self): 19 | """ 20 | File created, just close the handle 21 | """ 22 | self.out.close() 23 | self.out = None 24 | 25 | def write(self, text, indent=0, endline=True): 26 | """ 27 | Write a new line with line ending 28 | """ 29 | self.out.write('{0}{1}{2}'.format(self.Formatter.indent * (self.current_indent+indent), 30 | text, 31 | self.Formatter.endline if endline else '')) 32 | 33 | def append(self, x): 34 | """ 35 | Append to the existing line without line ending 36 | """ 37 | self.out.write(x) 38 | 39 | def __call__(self, text, indent=0, endline=True): 40 | """ 41 | Supports 'object()' semantic, i.e. 42 | cpp('#include ') 43 | inserts appropriate line 44 | """ 45 | self.write(text, indent, endline) 46 | 47 | def block(self, element, **attributes): 48 | """ 49 | Returns a stub for HTML element 50 | Supports 'with' semantic, i.e. 51 | html.block(element='p', id='id1', name='name1'): 52 | """ 53 | return self.Formatter(self, element=element, **attributes) 54 | 55 | def endline(self, count=1): 56 | """ 57 | Insert an endline 58 | """ 59 | self.write(self.Formatter.endline * count, endline=False) 60 | 61 | def newline(self, n=1): 62 | """ 63 | Insert one or several empty lines 64 | """ 65 | for _ in range(n): 66 | self.write('') 67 | -------------------------------------------------------------------------------- /src/code_generation/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "1.1.x" 2 | -------------------------------------------------------------------------------- /src/example.py: -------------------------------------------------------------------------------- 1 | from code_generation import code_generator 2 | from code_generation import cpp_generator 3 | 4 | # Create a new code file 5 | cpp = code_generator.CodeFile('example.cpp') 6 | cpp('int i = 0;') 7 | 8 | # Create a new variable 'x' 9 | x_variable = cpp_generator.CppVariable( 10 | name='x', 11 | type='int const&', 12 | is_static=True, 13 | is_constexpr=True, 14 | initialization_value='42') 15 | x_variable.render_to_string(cpp) 16 | 17 | # Create a new variable 'name' 18 | name_variable = cpp_generator.CppVariable( 19 | name='name', 20 | type='char*', 21 | is_extern=True) 22 | name_variable.render_to_string(cpp) 23 | 24 | 25 | # Generated C++ code 26 | """ 27 | int i = 0; 28 | static constexpr int const& x = 42; 29 | extern char* name; 30 | """ 31 | -------------------------------------------------------------------------------- /tests/create_assets.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from code_generation.core.code_generator import CppFile 4 | from code_generation.cpp.cpp_variable import CppVariable 5 | from code_generation.cpp.cpp_enum import CppEnum 6 | from code_generation.cpp.cpp_array import CppArray 7 | from code_generation.cpp.cpp_function import CppFunction 8 | from code_generation.cpp.cpp_class import CppClass 9 | 10 | __doc__ = """Do not call this script unless generator logic is changed 11 | """ 12 | 13 | PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) 14 | 15 | 16 | def generate_enum(output_dir='.'): 17 | """ 18 | Generate model data (C++ enum) 19 | Do not call unless generator logic is changed 20 | """ 21 | cpp = CppFile(os.path.join(output_dir, 'enum.cpp')) 22 | enum_elements = CppEnum(name='Items') 23 | for item in ['Chair', 'Table', 'Shelve']: 24 | enum_elements.add_item(item) 25 | enum_elements.render_to_string(cpp) 26 | 27 | enum_elements_custom = CppEnum(name='Items', prefix='it') 28 | for item in ['Chair', 'Table', 'Shelve']: 29 | enum_elements_custom.add_item(item) 30 | enum_elements_custom.render_to_string(cpp) 31 | 32 | enum_elements_custom = CppEnum(name='Items', prefix='') 33 | for item in ['Chair', 'Table', 'Shelve']: 34 | enum_elements_custom.add_item(item) 35 | enum_elements_custom.render_to_string(cpp) 36 | 37 | enum_elements_custom = CppEnum(name='Items', prefix='', add_counter=False) 38 | for item in ['Chair', 'Table', 'Shelve']: 39 | enum_elements_custom.add_item(item) 40 | enum_elements_custom.render_to_string(cpp) 41 | 42 | enum_elements_custom = CppEnum(name='Items', add_counter=False) 43 | for item in ['Chair', 'Table', 'Shelve']: 44 | enum_elements_custom.add_item(item) 45 | enum_elements_custom.render_to_string(cpp) 46 | 47 | cpp.close() 48 | 49 | 50 | def generate_var(output_dir='.'): 51 | """ 52 | Generate model data (C++ variables) 53 | Do not call unless generator logic is changed 54 | """ 55 | cpp = CppFile(os.path.join(output_dir, 'var.cpp')) 56 | variables = [CppVariable(name="var1", 57 | type="char*", 58 | is_class_member=False, 59 | is_static=False, 60 | is_const=True, 61 | initialization_value='0'), 62 | CppVariable(name="var2", 63 | type="int", 64 | is_class_member=False, 65 | is_static=True, 66 | is_const=False, 67 | initialization_value='0'), 68 | CppVariable(name="var3", 69 | type="std::string", 70 | is_class_member=False, 71 | is_static=False, 72 | is_const=False), 73 | CppVariable(name="var4", 74 | type="int", 75 | documentation='// A number', 76 | is_class_member=False, 77 | is_static=False, 78 | is_const=False), 79 | ] 80 | 81 | for var in variables: 82 | var.render_to_string(cpp) 83 | 84 | cpp.close() 85 | 86 | 87 | def generate_array(output_dir='.'): 88 | cpp = CppFile(os.path.join(output_dir, 'array.cpp')) 89 | arrays = [] 90 | a1 = CppArray(name='array1', type='int', is_const=True, array_size=5) 91 | a1.add_array_items(['1', '2', '3']) 92 | a2 = CppArray(name='array2', type='const char*', is_const=True) 93 | a2.add_array_item('"Item1"') 94 | a2.add_array_item('"Item2"') 95 | a3 = CppArray(name='array3', type='const char*', is_const=True, newline_align=True) 96 | a3.add_array_item('"ItemNewline1"') 97 | a3.add_array_item('"ItemNewline2"') 98 | 99 | arrays.append(a1) 100 | arrays.append(a2) 101 | arrays.append(a3) 102 | 103 | for arr in arrays: 104 | arr.render_to_string(cpp) 105 | 106 | cpp.close() 107 | 108 | 109 | def generate_func(output_dir='.'): 110 | """ 111 | Generate model data (C++ functions) 112 | Do not call unless generator logic is changed 113 | """ 114 | cpp = CppFile(os.path.join(output_dir, 'func.cpp')) 115 | hpp = CppFile(os.path.join(output_dir, 'func.h')) 116 | 117 | def function_body(_, cpp1): 118 | cpp1('return 42;') 119 | 120 | functions = [CppFunction(name='GetParam', ret_type='int'), 121 | CppFunction(name='Calculate', ret_type='void'), 122 | CppFunction(name='GetAnswer', ret_type='int', implementation_handle=function_body), 123 | CppFunction(name='Help', ret_type='char *', documentation='/// Returns the help documentation.'), 124 | ] 125 | for func in functions: 126 | func.render_to_string(hpp) 127 | for func in functions: 128 | func.render_to_string_declaration(hpp) 129 | for func in functions: 130 | func.render_to_string_implementation(cpp) 131 | cpp.close() 132 | hpp.close() 133 | 134 | 135 | def generate_class(output_dir='.'): 136 | """ 137 | Generate model data (C++ classes) 138 | Do not call unless generator logic is changed 139 | """ 140 | my_class_cpp = CppFile(os.path.join(output_dir, 'class.cpp')) 141 | my_class_h = CppFile(os.path.join(output_dir, 'class.h')) 142 | my_class = CppClass(name='MyClass', ref_to_parent=None) 143 | example_class = CppClass( 144 | name='Example', 145 | documentation="""\ 146 | /// An example 147 | /// class with 148 | /// multiline documentation""", 149 | ) 150 | 151 | enum_elements = CppEnum(name='Items', prefix='wd') 152 | for item in ['One', 'Two', 'Three']: 153 | enum_elements.add_item(item) 154 | my_class.add_enum(enum_elements) 155 | 156 | nested_class = CppClass(name='Nested', is_struct=True) 157 | nested_class.add_variable(CppVariable(name="m_gcAnswer", 158 | type="size_t", 159 | is_class_member=True, 160 | is_static=True, 161 | is_const=True, 162 | initialization_value='42')) 163 | my_class.add_internal_class(nested_class) 164 | 165 | my_class.add_variable(CppVariable(name="m_var1", 166 | type="int", 167 | initialization_value='1')) 168 | 169 | my_class.add_variable(CppVariable(name="m_var2", 170 | type="int*")) 171 | 172 | my_class.add_variable(CppVariable(name="m_var3", 173 | type="int", 174 | is_constexpr=True, 175 | is_static=True, 176 | is_class_member=True, 177 | initialization_value=42)) 178 | 179 | example_class.add_variable(CppVariable( 180 | name="m_var1", 181 | documentation="/// A number.", 182 | type="int"), 183 | ) 184 | 185 | a2 = CppArray(name='array2', type='char*', is_const=True, is_static=True, ) 186 | a2.add_array_item('"Item1"') 187 | a2.add_array_item('"Item2"') 188 | a3 = CppArray(name='array3', type='char*', is_static=True, is_const=True, newline_align=True) 189 | a3.add_array_item('"ItemNewline1"') 190 | a3.add_array_item('"ItemNewline2"') 191 | 192 | my_class.add_array(a2) 193 | my_class.add_array(a3) 194 | 195 | def method_body(_, cpp1): 196 | cpp1('return m_var1;') 197 | 198 | my_class.add_method(CppFunction(name="GetParam", 199 | ret_type="int", 200 | is_method=True, 201 | is_const=True, 202 | implementation_handle=method_body)) 203 | 204 | my_class.add_method(CppFunction(name="VirtualMethod", 205 | ret_type="int", 206 | is_method=True, 207 | is_virtual=True)) 208 | 209 | my_class.add_method(CppFunction(name="PureVirtualMethod", 210 | ret_type="void", 211 | is_method=True, 212 | is_virtual=True, 213 | is_pure_virtual=True)) 214 | 215 | example_class.add_method(CppFunction( 216 | name="DoNothing", 217 | documentation="""\ 218 | /** 219 | * Example multiline documentation. 220 | */""", 221 | ret_type="void"), 222 | ) 223 | 224 | my_class.declaration().render_to_string(my_class_h) 225 | example_class.declaration().render_to_string(my_class_h) 226 | my_class.definition().render_to_string(my_class_cpp) 227 | example_class.definition().render_to_string(my_class_cpp) 228 | my_class_cpp.close() 229 | my_class_h.close() 230 | 231 | 232 | def generate_factorial(output_dir='.'): 233 | cpp = CppFile(os.path.join(output_dir, 'factorial.cpp')) 234 | h = CppFile(os.path.join(output_dir, 'factorial.h')) 235 | 236 | def handle_to_factorial(_, cpp_file): 237 | cpp_file('return n < 1 ? 1 : (n * factorial(n - 1));') 238 | 239 | func = CppFunction(name="factorial", ret_type="int", 240 | implementation_handle=handle_to_factorial, 241 | is_constexpr=True) 242 | func.add_argument('int n') 243 | func.render_to_string(cpp) 244 | func.render_to_string_declaration(h) 245 | cpp.close() 246 | h.close() 247 | 248 | 249 | def generate_reference_code(): 250 | """ 251 | Generate model data for C++ generator 252 | Do not call unless generator logic is changed 253 | """ 254 | asset_dir = os.path.join(PROJECT_DIR, 'new_assets') 255 | generate_enum(output_dir=asset_dir) 256 | generate_var(output_dir=asset_dir) 257 | generate_array(output_dir=asset_dir) 258 | generate_func(output_dir=asset_dir) 259 | generate_class(output_dir=asset_dir) 260 | generate_factorial(output_dir=asset_dir) 261 | 262 | 263 | if __name__ == "__main__": 264 | generate_reference_code() 265 | -------------------------------------------------------------------------------- /tests/test_assets/array.cpp: -------------------------------------------------------------------------------- 1 | const int array1[5] = {1, 2, 3}; 2 | const const char* array2[] = {"Item1", "Item2"}; 3 | const const char* array3[] = 4 | { 5 | "ItemNewline1", 6 | "ItemNewline2" 7 | }; 8 | -------------------------------------------------------------------------------- /tests/test_assets/class.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | static const char* MyClass::array2[] = {"Item1", "Item2"}; 5 | 6 | static const char* MyClass::array3[] = 7 | { 8 | "ItemNewline1", 9 | "ItemNewline2" 10 | }; 11 | 12 | const size_t MyClass::Nested::m_gcAnswer = 42; 13 | 14 | 15 | int MyClass::GetParam() const 16 | { 17 | return m_var1; 18 | } 19 | 20 | /*virtual*/int MyClass::VirtualMethod() 21 | { 22 | } 23 | 24 | /*virtual*/void MyClass::PureVirtualMethod() = 0 25 | { 26 | } 27 | 28 | const size_t MyClass::Nested::m_gcAnswer = 42; 29 | 30 | 31 | 32 | 33 | /** 34 | * Example multiline documentation. 35 | */ 36 | void Example::DoNothing() 37 | { 38 | } 39 | 40 | -------------------------------------------------------------------------------- /tests/test_assets/class.h: -------------------------------------------------------------------------------- 1 | class MyClass 2 | { 3 | public: 4 | enum Items 5 | { 6 | wdOne = 0, 7 | wdTwo = 1, 8 | wdThree = 2, 9 | wdItemsCount = 3 10 | }; 11 | 12 | struct Nested 13 | { 14 | 15 | static const size_t m_gcAnswer; 16 | 17 | }; 18 | 19 | int GetParam() const ; 20 | 21 | virtual int VirtualMethod(); 22 | 23 | virtual void PureVirtualMethod() = 0; 24 | 25 | 26 | private: 27 | int m_var1; 28 | 29 | int* m_var2; 30 | 31 | static constexpr int m_var3 = 42; 32 | 33 | static const char* array2[]; 34 | 35 | static const char* array3[]; 36 | 37 | }; 38 | /// An example 39 | /// class with 40 | /// multiline documentation 41 | class Example 42 | { 43 | public: 44 | void DoNothing(); 45 | 46 | 47 | private: 48 | /// A number. 49 | int m_var1; 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /tests/test_assets/enum.cpp: -------------------------------------------------------------------------------- 1 | enum Items 2 | { 3 | eChair = 0, 4 | eTable = 1, 5 | eShelve = 2, 6 | eItemsCount = 3 7 | }; 8 | enum Items 9 | { 10 | itChair = 0, 11 | itTable = 1, 12 | itShelve = 2, 13 | itItemsCount = 3 14 | }; 15 | enum Items 16 | { 17 | Chair = 0, 18 | Table = 1, 19 | Shelve = 2, 20 | ItemsCount = 3 21 | }; 22 | enum Items 23 | { 24 | Chair = 0, 25 | Table = 1, 26 | Shelve = 2, 27 | }; 28 | enum Items 29 | { 30 | eChair = 0, 31 | eTable = 1, 32 | eShelve = 2, 33 | }; 34 | -------------------------------------------------------------------------------- /tests/test_assets/factorial.cpp: -------------------------------------------------------------------------------- 1 | constexpr int factorial(int n) 2 | { 3 | return n < 1 ? 1 : (n * factorial(n - 1)); 4 | } 5 | -------------------------------------------------------------------------------- /tests/test_assets/factorial.h: -------------------------------------------------------------------------------- 1 | constexpr int factorial(int n) 2 | { 3 | return n < 1 ? 1 : (n * factorial(n - 1)); 4 | } 5 | -------------------------------------------------------------------------------- /tests/test_assets/func.cpp: -------------------------------------------------------------------------------- 1 | int GetParam() 2 | { 3 | } 4 | void Calculate() 5 | { 6 | } 7 | int GetAnswer() 8 | { 9 | return 42; 10 | } 11 | /// Returns the help documentation. 12 | char * Help() 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /tests/test_assets/func.h: -------------------------------------------------------------------------------- 1 | int GetParam() 2 | { 3 | } 4 | void Calculate() 5 | { 6 | } 7 | int GetAnswer() 8 | { 9 | return 42; 10 | } 11 | char * Help() 12 | { 13 | } 14 | int GetParam(); 15 | void Calculate(); 16 | int GetAnswer(); 17 | char * Help(); 18 | -------------------------------------------------------------------------------- /tests/test_assets/var.cpp: -------------------------------------------------------------------------------- 1 | const char* var1 = 0; 2 | static int var2 = 0; 3 | std::string var3; 4 | // A number 5 | int var4; 6 | -------------------------------------------------------------------------------- /tests/test_cpp_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import filecmp 4 | 5 | from code_generation.core.code_generator import CppFile 6 | from code_generation.cpp.cpp_variable import CppVariable 7 | from code_generation.cpp.cpp_enum import CppEnum 8 | from code_generation.cpp.cpp_array import CppArray 9 | from code_generation.cpp.cpp_function import CppFunction 10 | from code_generation.cpp.cpp_class import CppClass 11 | 12 | __doc__ = """ 13 | Unit tests for C++ code generator 14 | """ 15 | 16 | 17 | class TestCppFileIo(unittest.TestCase): 18 | """ 19 | Test C++ code generation by writing to file 20 | """ 21 | 22 | def test_cpp_array(self): 23 | """ 24 | Test C++ variables generation 25 | """ 26 | cpp = CppFile('array.cpp') 27 | arrays = [] 28 | a1 = CppArray(name='array1', type='int', is_const=True, array_size=5) 29 | a1.add_array_items(['1', '2', '3']) 30 | a2 = CppArray(name='array2', type='const char*', is_const=True) 31 | a2.add_array_item('"Item1"') 32 | a2.add_array_item('"Item2"') 33 | a3 = CppArray(name='array3', type='const char*', is_const=True, newline_align=True) 34 | a3.add_array_item('"ItemNewline1"') 35 | a3.add_array_item('"ItemNewline2"') 36 | 37 | arrays.append(a1) 38 | arrays.append(a2) 39 | arrays.append(a3) 40 | 41 | for arr in arrays: 42 | arr.render_to_string(cpp) 43 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'array.cpp')) 44 | cpp.close() 45 | self.assertTrue(os.path.exists('array.cpp')) 46 | if os.path.exists('array.cpp'): 47 | os.remove('array.cpp') 48 | 49 | def test_cpp_class(self): 50 | """ 51 | Test C++ classes generation 52 | """ 53 | my_class_cpp = CppFile('class.cpp') 54 | my_class_h = CppFile('class.h') 55 | my_class = CppClass(name='MyClass') 56 | 57 | enum_elements = CppEnum(name='Items', prefix='wd') 58 | for item in ['One', 'Two', 'Three']: 59 | enum_elements.add_item(item) 60 | my_class.add_enum(enum_elements) 61 | 62 | nested_class = CppClass(name='Nested', is_struct=True) 63 | nested_class.add_variable(CppVariable(name="m_gcAnswer", 64 | type="size_t", 65 | is_class_member=True, 66 | is_static=True, 67 | is_const=True, 68 | initialization_value='42')) 69 | my_class.add_internal_class(nested_class) 70 | 71 | my_class.add_variable(CppVariable(name="m_var1", 72 | type="int", 73 | initialization_value='1')) 74 | 75 | my_class.add_variable(CppVariable(name="m_var2", 76 | type="int*")) 77 | 78 | a2 = CppArray(name='array2', type='char*', is_const=True, is_static=True, ) 79 | a2.add_array_item('"Item1"') 80 | a2.add_array_item('"Item2"') 81 | a3 = CppArray(name='array3', type='char*', is_static=True, is_const=True, newline_align=True) 82 | a3.add_array_item('"ItemNewline1"') 83 | a3.add_array_item('"ItemNewline2"') 84 | 85 | my_class.add_array(a2) 86 | my_class.add_array(a3) 87 | 88 | def const_method_body(_, cpp): 89 | cpp('return m_var1;') 90 | 91 | def virtual_method_body(_, cpp): 92 | cpp('return 0;') 93 | 94 | def static_method_body(_, cpp): 95 | cpp('return 0;') 96 | 97 | my_class.add_method(CppClass.CppMethod(name="GetParam", 98 | ret_type="int", 99 | is_const=True, 100 | implementation_handle=const_method_body)) 101 | 102 | my_class.add_method(CppClass.CppMethod(name="VirtualMethod", 103 | ret_type="int", 104 | is_virtual=True, 105 | implementation_handle=virtual_method_body)) 106 | 107 | my_class.add_method(CppClass.CppMethod(name="PureVirtualMethod", 108 | ret_type="void", 109 | is_virtual=True, 110 | is_pure_virtual=True)) 111 | 112 | my_class.add_method(CppClass.CppMethod(name="StaticMethodMethod", 113 | ret_type="int", 114 | is_static=True, 115 | implementation_handle=static_method_body)) 116 | 117 | my_class.declaration().render_to_string(my_class_h) 118 | my_class.definition().render_to_string(my_class_cpp) 119 | 120 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'class.cpp')) 121 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'class.h')) 122 | my_class_cpp.close() 123 | my_class_h.close() 124 | self.assertTrue(os.path.exists('class.cpp')) 125 | if os.path.exists('class.cpp'): 126 | os.remove('class.cpp') 127 | self.assertTrue(os.path.exists('class.h')) 128 | if os.path.exists('class.h'): 129 | os.remove('class.h') 130 | 131 | def test_cpp_enum(self): 132 | """ 133 | Test C++ enums generation 134 | """ 135 | cpp = CppFile('enum.cpp') 136 | enum_elements = CppEnum(name='Items') 137 | for item in ['Chair', 'Table', 'Shelve']: 138 | enum_elements.add_item(item) 139 | enum_elements.render_to_string(cpp) 140 | 141 | enum_elements_custom = CppEnum(name='Items', prefix='it') 142 | for item in ['Chair', 'Table', 'Shelve']: 143 | enum_elements_custom.add_item(item) 144 | enum_elements_custom.render_to_string(cpp) 145 | 146 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'enum.cpp')) 147 | cpp.close() 148 | if os.path.exists('enum.cpp'): 149 | os.remove('enum.cpp') 150 | 151 | def test_cpp_function(self): 152 | """ 153 | Test C++ functions generation 154 | """ 155 | cpp = CppFile('func.cpp') 156 | hpp = CppFile('func.h') 157 | 158 | def function_body(_, cpp1): 159 | cpp1('return 42;') 160 | 161 | functions = [CppFunction(name='GetParam', ret_type='int'), 162 | CppFunction(name='Calculate', ret_type='void'), 163 | CppFunction(name='GetAnswer', ret_type='int', implementation_handle=function_body)] 164 | 165 | for func in functions: 166 | func.render_to_string(hpp) 167 | for func in functions: 168 | func.render_to_string_declaration(hpp) 169 | functions[2].render_to_string_implementation(cpp) 170 | 171 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'func.cpp')) 172 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'func.h')) 173 | cpp.close() 174 | hpp.close() 175 | if os.path.exists('func.cpp'): 176 | os.remove('func.cpp') 177 | if os.path.exists('func.h'): 178 | os.remove('func.h') 179 | 180 | def test_cpp_variable(self): 181 | """ 182 | Test C++ variables generation 183 | """ 184 | cpp = CppFile('var.cpp') 185 | variables = [CppVariable(name="var1", 186 | type="char*", 187 | is_class_member=False, 188 | is_static=False, 189 | is_const=True, 190 | initialization_value='0'), 191 | CppVariable(name="var2", 192 | type="int", 193 | is_class_member=False, 194 | is_static=True, 195 | is_const=False, 196 | initialization_value='0'), 197 | CppVariable(name="var3", 198 | type="std::string", 199 | is_class_member=False, 200 | is_static=False, 201 | is_const=False), 202 | CppVariable(name="var3", 203 | type="std::string", 204 | is_class_member=False, 205 | is_static=False, 206 | is_const=False)] 207 | 208 | for var in variables: 209 | var.render_to_string(cpp) 210 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'var.cpp')) 211 | cpp.close() 212 | if os.path.exists('var.cpp'): 213 | os.remove('var.cpp') 214 | 215 | 216 | if __name__ == "__main__": 217 | unittest.main() 218 | -------------------------------------------------------------------------------- /tests/test_cpp_function_writer.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import io 3 | from textwrap import dedent 4 | 5 | from code_generation.core.code_generator import CppFile 6 | from code_generation.cpp.cpp_function import CppFunction 7 | 8 | __doc__ = """ 9 | Unit tests for C++ code generator 10 | """ 11 | 12 | 13 | def handle_to_factorial(_, cpp): 14 | cpp('return n < 1 ? 1 : (n * factorial(n - 1));') 15 | 16 | 17 | class TestCppFunctionStringIo(unittest.TestCase): 18 | """ 19 | Test C++ function generation by writing to StringIO 20 | """ 21 | 22 | def test_is_constexpr_no_implementation_raises(self): 23 | writer = io.StringIO() 24 | cpp = CppFile(None, writer=writer) 25 | func = CppFunction(name="factorial", ret_type="int", is_constexpr=True) 26 | self.assertRaises(ValueError, func.render_to_string, cpp) 27 | 28 | def test_is_constexpr_render_to_string(self): 29 | writer = io.StringIO() 30 | cpp = CppFile(None, writer=writer) 31 | func = CppFunction(name="factorial", ret_type="int", 32 | implementation_handle=handle_to_factorial, is_constexpr=True) 33 | func.add_argument('int n') 34 | func.render_to_string(cpp) 35 | self.assertIn(dedent("""\ 36 | constexpr int factorial(int n) 37 | { 38 | \treturn n < 1 ? 1 : (n * factorial(n - 1)); 39 | }"""), writer.getvalue()) 40 | 41 | def test_is_constexpr_render_to_string_declaration(self): 42 | writer = io.StringIO() 43 | cpp = CppFile(None, writer=writer) 44 | func = CppFunction(name="factorial", ret_type="int", 45 | implementation_handle=handle_to_factorial, is_constexpr=True) 46 | func.add_argument('int n') 47 | func.render_to_string_declaration(cpp) 48 | self.assertIn(dedent("""\ 49 | constexpr int factorial(int n) 50 | { 51 | \treturn n < 1 ? 1 : (n * factorial(n - 1)); 52 | }"""), writer.getvalue()) 53 | 54 | def test_docstring_example(self): 55 | writer = io.StringIO() 56 | cpp = CppFile(None, writer=writer) 57 | factorial_function = CppFunction(name='factorial', ret_type='int', is_constexpr=True, 58 | implementation_handle=handle_to_factorial, 59 | documentation='/// Calculates and returns the factorial of p @n.') 60 | factorial_function.add_argument('int n') 61 | factorial_function.render_to_string(cpp) 62 | self.assertIn(dedent("""\ 63 | /// Calculates and returns the factorial of p @n. 64 | constexpr int factorial(int n) 65 | { 66 | \treturn n < 1 ? 1 : (n * factorial(n - 1)); 67 | }"""), writer.getvalue()) 68 | 69 | 70 | if __name__ == "__main__": 71 | unittest.main() 72 | -------------------------------------------------------------------------------- /tests/test_cpp_variable_writer.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import io 3 | 4 | from code_generation.core.code_generator import CppFile 5 | from code_generation.cpp.cpp_variable import CppVariable 6 | 7 | __doc__ = """Unit tests for C++ code generator 8 | """ 9 | 10 | 11 | class TestCppVariableStringIo(unittest.TestCase): 12 | """ 13 | Test C++ variable generation by writing to StringIO 14 | """ 15 | 16 | def test_cpp_var(self): 17 | writer = io.StringIO() 18 | cpp = CppFile(None, writer=writer) 19 | variables = CppVariable(name="var1", 20 | type="char*", 21 | is_class_member=False, 22 | is_static=False, 23 | is_const=True, 24 | initialization_value='0') 25 | variables.render_to_string(cpp) 26 | print(writer.getvalue()) 27 | self.assertEqual('const char* var1 = 0;\n', writer.getvalue()) 28 | 29 | def test_is_constexpr_const_raises(self): 30 | writer = io.StringIO() 31 | cpp = CppFile(None, writer=writer) 32 | var = CppVariable(name="COUNT", type="int", is_class_member=True, is_const=True, 33 | is_constexpr=True, initialization_value='0') 34 | self.assertRaises(ValueError, var.render_to_string, cpp) 35 | 36 | def test_is_constexpr_no_implementation_raises(self): 37 | writer = io.StringIO() 38 | cpp = CppFile(None, writer=writer) 39 | var = CppVariable(name="COUNT", type="int", is_class_member=True, is_constexpr=True) 40 | self.assertRaises(ValueError, var.render_to_string, cpp) 41 | 42 | def test_is_constexpr_render_to_string(self): 43 | writer = io.StringIO() 44 | cpp = CppFile(None, writer=writer) 45 | variables = CppVariable(name="COUNT", 46 | type="int", 47 | is_class_member=False, 48 | is_constexpr=True, 49 | initialization_value='0') 50 | variables.render_to_string(cpp) 51 | self.assertIn('constexpr int COUNT = 0;', writer.getvalue()) 52 | 53 | def test_is_constexpr_render_to_string_declaration(self): 54 | writer = io.StringIO() 55 | cpp = CppFile(None, writer=writer) 56 | variables = CppVariable(name="COUNT", 57 | type="int", 58 | is_class_member=True, 59 | is_constexpr=True, 60 | initialization_value='0') 61 | variables.render_to_string_declaration(cpp) 62 | self.assertIn('constexpr int COUNT = 0;', writer.getvalue()) 63 | 64 | def test_is_extern_static_raises(self): 65 | writer = io.StringIO() 66 | cpp = CppFile(None, writer=writer) 67 | var = CppVariable(name="var1", type="char*", is_static=True, is_extern=True) 68 | self.assertRaises(ValueError, var.render_to_string, cpp) 69 | 70 | def test_is_extern_render_to_string(self): 71 | writer = io.StringIO() 72 | cpp = CppFile(None, writer=writer) 73 | v = CppVariable(name="var1", type="char*", is_extern=True) 74 | v.render_to_string(cpp) 75 | self.assertIn('extern char* var1;', writer.getvalue()) 76 | 77 | 78 | if __name__ == "__main__": 79 | unittest.main() 80 | -------------------------------------------------------------------------------- /tests/test_html_writer.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import io 3 | 4 | from code_generation.html.html_generator import * 5 | 6 | __doc__ = """ 7 | Unit tests for HTML code generator 8 | """ 9 | 10 | 11 | class TestHTMLFunctionGenerator(unittest.TestCase): 12 | 13 | def test_is_constexpr_render_to_string(self): 14 | writer = io.StringIO() 15 | html = HtmlFile(None, writer=writer) 16 | with html.block(element='p', id='id1', name='name1'): 17 | html('Text') 18 | print(writer.getvalue()) 19 | result = """

\n Text\n

\n""" 20 | self.assertIn(result, writer.getvalue()) 21 | 22 | 23 | if __name__ == "__main__": 24 | unittest.main() 25 | --------------------------------------------------------------------------------