From 062ce5fddb5c5d919659ccbad9a6dbf3ce5cf141 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Wed, 1 Feb 2023 01:10:41 +0700 Subject: [PATCH 01/51] Rename modules --- .../cpp/{cpp_array.py => array_generator.py} | 0 .../cpp/{cpp_class.py => class_generator.py} | 0 .../cpp/{cpp_enum.py => enum_generator.py} | 0 ...{cpp_function.py => function_generator.py} | 0 .../{cpp_generator.py => language_element.py} | 0 ...{cpp_variable.py => variable_generator.py} | 0 src/code_generation/java/__init__.py | 1 + src/code_generation/java/java_generator.py | 66 +++++++++++++++++++ 8 files changed, 67 insertions(+) rename src/code_generation/cpp/{cpp_array.py => array_generator.py} (100%) rename src/code_generation/cpp/{cpp_class.py => class_generator.py} (100%) rename src/code_generation/cpp/{cpp_enum.py => enum_generator.py} (100%) rename src/code_generation/cpp/{cpp_function.py => function_generator.py} (100%) rename src/code_generation/cpp/{cpp_generator.py => language_element.py} (100%) rename src/code_generation/cpp/{cpp_variable.py => variable_generator.py} (100%) create mode 100644 src/code_generation/java/__init__.py create mode 100644 src/code_generation/java/java_generator.py diff --git a/src/code_generation/cpp/cpp_array.py b/src/code_generation/cpp/array_generator.py similarity index 100% rename from src/code_generation/cpp/cpp_array.py rename to src/code_generation/cpp/array_generator.py diff --git a/src/code_generation/cpp/cpp_class.py b/src/code_generation/cpp/class_generator.py similarity index 100% rename from src/code_generation/cpp/cpp_class.py rename to src/code_generation/cpp/class_generator.py diff --git a/src/code_generation/cpp/cpp_enum.py b/src/code_generation/cpp/enum_generator.py similarity index 100% rename from src/code_generation/cpp/cpp_enum.py rename to src/code_generation/cpp/enum_generator.py diff --git a/src/code_generation/cpp/cpp_function.py b/src/code_generation/cpp/function_generator.py similarity index 100% rename from src/code_generation/cpp/cpp_function.py rename to src/code_generation/cpp/function_generator.py diff --git a/src/code_generation/cpp/cpp_generator.py b/src/code_generation/cpp/language_element.py similarity index 100% rename from src/code_generation/cpp/cpp_generator.py rename to src/code_generation/cpp/language_element.py diff --git a/src/code_generation/cpp/cpp_variable.py b/src/code_generation/cpp/variable_generator.py similarity index 100% rename from src/code_generation/cpp/cpp_variable.py rename to src/code_generation/cpp/variable_generator.py diff --git a/src/code_generation/java/__init__.py b/src/code_generation/java/__init__.py new file mode 100644 index 0000000..2eda467 --- /dev/null +++ b/src/code_generation/java/__init__.py @@ -0,0 +1 @@ +from . import java_generator diff --git a/src/code_generation/java/java_generator.py b/src/code_generation/java/java_generator.py new file mode 100644 index 0000000..89e1d26 --- /dev/null +++ b/src/code_generation/java/java_generator.py @@ -0,0 +1,66 @@ +import sys +from code_generation.core.code_style import ANSIStyle + + +class JavaFile: + + Formatter = ANSIStyle + + def __init__(self, filename, writer=None): + self.current_indent = 0 + self.last = None + self.filename = filename + if writer: + self.out = writer + else: + self.out = open(filename, "w") + + def close(self): + """ + File created, just close the handle + """ + self.out.close() + self.out = None + + def write(self, text, indent=0, endline=True): + """ + Write a new line with line ending + """ + self.out.write('{0}{1}{2}'.format(self.Formatter.indent * (self.current_indent+indent), + text, + self.Formatter.endline if endline else '')) + + def append(self, x): + """ + Append to the existing line without line ending + """ + self.out.write(x) + + def __call__(self, text, indent=0, endline=True): + """ + Supports 'object()' semantic, i.e. + cpp('#include ') + inserts appropriate line + """ + self.write(text, indent, endline) + + def block(self, element, **attributes): + """ + Returns a stub for HTML element + Supports 'with' semantic, i.e. + html.block(element='p', id='id1', name='name1'): + """ + return self.Formatter(self, element=element, **attributes) + + def endline(self, count=1): + """ + Insert an endline + """ + self.write(self.Formatter.endline * count, endline=False) + + def newline(self, n=1): + """ + Insert one or several empty lines + """ + for _ in range(n): + self.write('') From a85e18072530ed105dc1149d9c8b7f4523e386f8 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Fri, 17 Feb 2023 18:03:14 +0700 Subject: [PATCH 02/51] Update Readme --- README.md | 118 ++++++++++++++++++++++++++++------------- tests/create_assets.py | 21 ++++++++ 2 files changed, 102 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index ce67c13..f196ea3 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,47 @@ -C++ Code Generator +# Pythonic Code Generator ============== -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 +Simple and straightforward generator for algorithmic creating of program code. +In general, it could be used for generating code in any programming language, +however, at the moment it offers specific implementations for C++, Java and HTML. -Every C++ element could render its current state to a string that could be evaluated as -a legal C++ construction. -Some elements could be rendered to a pair of representations (C++ classes and functions declaration and implementation) +Written in Python, used to work both with Python 2 and 3, however, +implementation for Python 2 is deprecated and has not been supported for a while. -### Special thanks +## Code Generation Usecases + +Programmatic source code generation can have a wide range of use cases, including: + +* Code generation for any repetitive tasks, such as generating getters and setters or creating CRUD operations +* Object serialization: Generating code to serialize and deserialize objects +* Language translations: Converting code from one programming language to another +* Formatting reports: Generating custom report files, such as HTML/CSS +* Database interactions: Generating code to interact with databases, such as generating SQL statements +* API generation: Generating API endpoints, requests and responses, and documentation for web services +* Mocking and testing: Generating code to create mock objects and test data +* Prototyping: Generating code to quickly prototype and experiment with new ideas. +* Integration with other systems: Generating code to integrate with other systems, such as web services or system RPC calls + +Overall, programmatic source code generation can help improve development efficiency, reduce human error, and automate repetitive tasks. -Thanks to Eric Reynolds, the idea of this project mainly based on his article published on -http://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python -However, this solution has been both simplified and extended compared to the initial idea. +## C++ -## Usage examples +The project initially was created to generate C++ code, and it is still the most mature implementation. +It is based on a bit of a different approach than other languages, because C++ language constructions +(like C++ classes and functions) include declarations and definitions (implementations). +Declaration usually placed in header files (`*.h`), while definition is in source files (`*.cpp`). -### Generate C++ code from Python code +Every C++ element could render its current state to a string that could be evaluated as +a legal C++ construction, in two places: declaration and definition. + +### Usage examples + +#### Generate C++ code from Python code -#### Creating variables +##### Creating variables -##### Python code +###### Python code ```python cpp = CodeFile('example.cpp') cpp('int i = 0;') @@ -32,16 +53,16 @@ name_variable = CppVariable(name='name', type='char*', is_extern=True) name_variable.render_to_string(cpp) ``` -##### Generated C++ code +###### Generated C++ code ```c++ int i = 0; static constexpr int const& x = 42; extern char* name; ``` -#### Creating functions +##### Creating functions -##### Python code +###### Python code ```python def handle_to_factorial(self, cpp): cpp('return n < 1 ? 1 : (n * factorial(n - 1));') @@ -57,7 +78,7 @@ factorial_function.add_argument('int n') factorial_function.render_to_string(cpp) ``` -##### Generated C++ code +###### Generated C++ code ```c++ /// Calculates and returns the factorial of \p n. constexpr int factorial(int n) @@ -66,9 +87,9 @@ constexpr int factorial(int n) } ``` -#### Creating classes and structures +##### Creating classes and structures -##### Python code +###### Python code ```python cpp = CppFile('example.cpp') with cpp.block('class A', ';'): @@ -77,7 +98,7 @@ with cpp.block('class A', ';'): cpp('double m_classMember2;') ``` -##### Generated C++ code +###### Generated C++ code ```c++ class A { @@ -87,9 +108,9 @@ public: }; ``` -#### Rendering `CppClass` objects to C++ declaration and implementation +##### Rendering `CppClass` objects to C++ declaration and implementation -##### Python code +###### Python code ```python cpp_class = CppClass(name = 'MyClass', is_struct = True) @@ -100,7 +121,7 @@ cpp_class.add_variable(CppVariable(name = "m_var", initialization_value = 255)) ``` -##### Generated C++ declaration +###### Generated C++ declaration ```c++ struct MyClass @@ -109,24 +130,25 @@ struct MyClass } ``` -#### Generated C++ implementation +###### Generated C++ implementation ```c++ const size_t MyClass::m_var = 255; ``` -Module `cpp_generator.py` highly depends on parent `code_generator.py`, as it uses -code generating and formatting primitives implemented there. +### Implementation Notes + +#### CppFile + +Module `core.code_generator` provides basic code generating and +formatting functionality, that could be used for generating code in any language. -The main object referenced from `code_generator.py` is `CppFile`, +The main object referenced from `code_generator` is `CppFile`, which is passed as a parameter to `render_to_string(cpp)` Python method It could also be used for composing more complicated C++ code, -that does not supported by `cpp_generator` +that may be not supported by `cpp.*` classes. -Class `ANSICodeStyle` is responsible for code formatting. Re-implement it if you wish to apply any other formatting style. - - -It support: +It supports: - functional calls: ```python @@ -149,21 +171,43 @@ cpp.append(', p = NULL);') cpp.newline(2) ``` -## Maintainers +#### ANSICodeStyle + +Class `ANSICodeStyle` is responsible for code formatting. +Re-implement it if you wish to apply any other formatting style. + + +## Java + +TODO + +## HTML + +HTML code generation is implemented in `html.*` modules. +It was added to the project mostly to generate HTML reports. + +## Maintenance ### Executing unit tests The following command will execute the unit tests. ```bash -python -m unittest cpp_generator_tests.py +python -m unittest tests/test_cpp_file.py +python -m unittest tests/test_cpp_function_writer.py +python -m unittest tests/test_cpp_variable_writer.py +python -m unittest tests/test_html_writer.py ``` -### Updating unit tests fixed data -After changing a unit test the fixed data needs to be updated to successfully pass the unit tests. +### Updating unit tests assets +After changing reference data for the unit test the test assets needs to be updated to successfully pass the unit tests. ```bash -python -c 'from test_cpp_generator import generate_reference_code; generate_reference_code()' +python create_assets.py --assets test_assets ``` After executing that command, the fixed data under `tests/test_assets` will be updated and will need to be committed to git. +### Special thanks + +Thanks to Eric Reynolds, for the initial idea of code generation in Python. +http://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python diff --git a/tests/create_assets.py b/tests/create_assets.py index 7173e7a..fa262e3 100644 --- a/tests/create_assets.py +++ b/tests/create_assets.py @@ -1,4 +1,5 @@ import os +import argparse from code_generation.core.code_generator import CppFile from code_generation.cpp.cpp_variable import CppVariable @@ -260,5 +261,25 @@ def generate_reference_code(): generate_factorial(output_dir=asset_dir) +def main(): + """ + Command-line arguments: + -h, --help: show help + -a, --assets: directory to store generated assets + Generate reference code for C++ generator + :return: system exit code + """ + parser = argparse.ArgumentParser(description='Generate reference code for C++ generator') + parser.add_argument('-a', '--assets', + help='Directory to store generated assets', + required=True) + args = parser.parse_args() + if args.assets: + generate_reference_code() + return 0 + + + if __name__ == "__main__": + assets_dir = generate_reference_code() From 98d6c8363f0102b419d69a9c64de7e2740d40b78 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Fri, 17 Feb 2023 18:12:23 +0700 Subject: [PATCH 03/51] Java class --- src/code_generation/core/code_generator.py | 18 --------------- src/code_generation/cpp/class_generator.py | 4 ++-- src/code_generation/cpp/language_element.py | 22 ++++++++++++++++++ src/code_generation/java/java_generator.py | 25 ++++++++++++++++++--- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/code_generation/core/code_generator.py b/src/code_generation/core/code_generator.py index f5dedc4..2d92d9b 100644 --- a/src/code_generation/core/code_generator.py +++ b/src/code_generation/core/code_generator.py @@ -143,21 +143,3 @@ def newline(self, n=1): for _ in range(n): self.write('') - -class CppFile(CodeFile): - """ - This class extends CodeFile class with some specific C++ constructions - """ - def __init__(self, filename, writer=None): - """ - Create C++ source file - """ - CodeFile.__init__(self, filename, writer) - - def label(self, text): - """ - Could be used for access specifiers or ANSI C labels, e.g. - private: - a: - """ - self.write('{0}:'.format(text), -1) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 094dbf5..61050f6 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -1,5 +1,5 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation -from code_generation.cpp.cpp_function import CppFunction +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.function_generator import CppFunction from textwrap import dedent diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index c1d9a7e..696c668 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -1,3 +1,5 @@ +from code_generation.core.code_generator import CodeFile + __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: classes, methods and functions, variables, enums. Every C++ element could render its current state to a string that could be evaluated as @@ -52,6 +54,26 @@ """ +class CppFile(CodeFile): + """ + This class extends CodeFile class with some specific C++ constructions + """ + + def __init__(self, filename, writer=None): + """ + Create C++ source file + """ + CodeFile.__init__(self, filename, writer) + + def label(self, text): + """ + Could be used for access specifiers or ANSI C labels, e.g. + private: + a: + """ + self.write('{0}:'.format(text), -1) + + ########################################################################### # declaration/Implementation helpers class CppDeclaration(object): diff --git a/src/code_generation/java/java_generator.py b/src/code_generation/java/java_generator.py index 89e1d26..0ac63d3 100644 --- a/src/code_generation/java/java_generator.py +++ b/src/code_generation/java/java_generator.py @@ -1,10 +1,13 @@ -import sys -from code_generation.core.code_style import ANSIStyle +__doc__ = """The module encapsulates C++ code generation logics for main Java language primitives: +classes, methods, variables, enums. +""" + +from code_generation.core.code_style import ANSICodeStyle class JavaFile: - Formatter = ANSIStyle + Formatter = ANSICodeStyle def __init__(self, filename, writer=None): self.current_indent = 0 @@ -64,3 +67,19 @@ def newline(self, n=1): """ for _ in range(n): self.write('') + + +class JavaLanguageElement(object): + """ + The base class for all Java language elements. + Contains dynamic storage for element properties + """ + availablePropertiesNames = {'name', 'ref_to_parent'} + + def __init__(self, properties): + """ + @param: properties - Basic Java element properties (name, ref_to_parent) + class is a parent for method or a member variable + """ + self.name = properties.get('name') + self.ref_to_parent = properties.get('ref_to_parent') From 947f2c4b792f65da13c9c6e242fbd3dbfedb7604 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Fri, 10 Mar 2023 14:57:08 +0700 Subject: [PATCH 04/51] Rename package --- setup.cfg | 2 +- setup.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4c398a5..4e67733 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = code_generation -version = 2.4.0 +version = 2.4.1 [options] packages = find: diff --git a/setup.py b/setup.py index b98dca0..9ecef78 100644 --- a/setup.py +++ b/setup.py @@ -54,3 +54,5 @@ include_package_data=True, install_requires=DEPENDENCIES, ) + +print(f"\n*** WARNING: `code_generation` is no longer supported. Please run `pip install code_generator2` instead. ***\n") From aea8f8b60754fa2cf07d62615639ba5caa6f01d8 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Fri, 10 Mar 2023 14:58:05 +0700 Subject: [PATCH 05/51] v 2.4.1 --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 9ecef78..b98dca0 100644 --- a/setup.py +++ b/setup.py @@ -54,5 +54,3 @@ include_package_data=True, install_requires=DEPENDENCIES, ) - -print(f"\n*** WARNING: `code_generation` is no longer supported. Please run `pip install code_generator2` instead. ***\n") From 3d7b60449103c01b81a4dddfc368e4a9665f3b77 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 13 Mar 2023 20:10:22 +0700 Subject: [PATCH 06/51] Attempt renaming package --- release_package.py | 2 +- setup.cfg | 2 +- src/code_generation/core/code_generator.py | 2 +- src/code_generation/cpp/array_generator.py | 2 +- src/code_generation/cpp/class_generator.py | 4 ++-- src/code_generation/cpp/enum_generator.py | 2 +- src/code_generation/cpp/function_generator.py | 2 +- src/code_generation/cpp/language_element.py | 2 +- src/code_generation/cpp/variable_generator.py | 2 +- src/code_generation/html/html_generator.py | 2 +- src/code_generation/java/java_generator.py | 2 +- src/example.py | 4 ++-- tests/create_assets.py | 12 ++++++------ tests/test_cpp_file.py | 12 ++++++------ tests/test_cpp_function_writer.py | 4 ++-- tests/test_cpp_variable_writer.py | 4 ++-- tests/test_html_writer.py | 2 +- 17 files changed, 31 insertions(+), 31 deletions(-) diff --git a/release_package.py b/release_package.py index bae927b..06bcf18 100644 --- a/release_package.py +++ b/release_package.py @@ -19,7 +19,7 @@ PACKAGE_NAME = cfg.get('metadata', 'name') # Name with dash (pip name, URL, S3 bucket) -PACKAGE_NAME_DASH = PACKAGE_NAME.replace('_', '-') +PACKAGE_NAME_DASH = 'code-generator2' PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)))) PYTHON = "python3" diff --git a/setup.cfg b/setup.cfg index 4e67733..81277ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = code_generation +name = code_generator version = 2.4.1 [options] diff --git a/src/code_generation/core/code_generator.py b/src/code_generation/core/code_generator.py index 2d92d9b..1c09ce9 100644 --- a/src/code_generation/core/code_generator.py +++ b/src/code_generation/core/code_generator.py @@ -1,5 +1,5 @@ import sys -from code_generation.core.code_style import ANSICodeStyle +from code_generator.core.code_style import ANSICodeStyle __doc__ = """ Simple and straightforward code generator that could be used for generating code diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index 8eed0d0..32758f1 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generator.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation # noinspection PyUnresolvedReferences diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 61050f6..9471141 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -1,5 +1,5 @@ -from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation -from code_generation.cpp.function_generator import CppFunction +from code_generator.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation +from code_generator.cpp.function_generator import CppFunction from textwrap import dedent diff --git a/src/code_generation/cpp/enum_generator.py b/src/code_generation/cpp/enum_generator.py index 5bd44bb..a5d3e61 100644 --- a/src/code_generation/cpp/enum_generator.py +++ b/src/code_generation/cpp/enum_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement +from code_generator.cpp.cpp_generator import CppLanguageElement __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: classes, methods and functions, variables, enums. diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index 77b93d6..dd28b46 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generator.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation from textwrap import dedent diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index 696c668..924d81e 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -1,4 +1,4 @@ -from code_generation.core.code_generator import CodeFile +from code_generator.core.code_generator import CodeFile __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: classes, methods and functions, variables, enums. diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index 20a955b..c63a9cb 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generator.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation from textwrap import dedent __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: diff --git a/src/code_generation/html/html_generator.py b/src/code_generation/html/html_generator.py index 22853c9..0cdd4a4 100644 --- a/src/code_generation/html/html_generator.py +++ b/src/code_generation/html/html_generator.py @@ -1,5 +1,5 @@ import sys -from code_generation.core.code_style import HTMLStyle +from code_generator.core.code_style import HTMLStyle class HtmlFile: diff --git a/src/code_generation/java/java_generator.py b/src/code_generation/java/java_generator.py index 0ac63d3..441625a 100644 --- a/src/code_generation/java/java_generator.py +++ b/src/code_generation/java/java_generator.py @@ -2,7 +2,7 @@ classes, methods, variables, enums. """ -from code_generation.core.code_style import ANSICodeStyle +from code_generator.core.code_style import ANSICodeStyle class JavaFile: diff --git a/src/example.py b/src/example.py index 59aae3c..4322b26 100644 --- a/src/example.py +++ b/src/example.py @@ -1,5 +1,5 @@ -from code_generation import code_generator -from code_generation import cpp_generator +from code_generator import code_generator +from code_generator import cpp_generator # Create a new code file cpp = code_generator.CodeFile('example.cpp') diff --git a/tests/create_assets.py b/tests/create_assets.py index fa262e3..0021772 100644 --- a/tests/create_assets.py +++ b/tests/create_assets.py @@ -1,12 +1,12 @@ import os import argparse -from code_generation.core.code_generator import CppFile -from code_generation.cpp.cpp_variable import CppVariable -from code_generation.cpp.cpp_enum import CppEnum -from code_generation.cpp.cpp_array import CppArray -from code_generation.cpp.cpp_function import CppFunction -from code_generation.cpp.cpp_class import CppClass +from code_generator.core.code_generator import CppFile +from code_generator.cpp.cpp_variable import CppVariable +from code_generator.cpp.cpp_enum import CppEnum +from code_generator.cpp.cpp_array import CppArray +from code_generator.cpp.cpp_function import CppFunction +from code_generator.cpp.cpp_class import CppClass __doc__ = """Do not call this script unless generator logic is changed """ diff --git a/tests/test_cpp_file.py b/tests/test_cpp_file.py index 6179848..7debe50 100644 --- a/tests/test_cpp_file.py +++ b/tests/test_cpp_file.py @@ -2,12 +2,12 @@ import unittest import filecmp -from code_generation.core.code_generator import CppFile -from code_generation.cpp.cpp_variable import CppVariable -from code_generation.cpp.cpp_enum import CppEnum -from code_generation.cpp.cpp_array import CppArray -from code_generation.cpp.cpp_function import CppFunction -from code_generation.cpp.cpp_class import CppClass +from code_generator.core.code_generator import CppFile +from code_generator.cpp.cpp_variable import CppVariable +from code_generator.cpp.cpp_enum import CppEnum +from code_generator.cpp.cpp_array import CppArray +from code_generator.cpp.cpp_function import CppFunction +from code_generator.cpp.cpp_class import CppClass __doc__ = """ Unit tests for C++ code generator diff --git a/tests/test_cpp_function_writer.py b/tests/test_cpp_function_writer.py index 8328aad..ef7b414 100644 --- a/tests/test_cpp_function_writer.py +++ b/tests/test_cpp_function_writer.py @@ -2,8 +2,8 @@ import io from textwrap import dedent -from code_generation.core.code_generator import CppFile -from code_generation.cpp.cpp_function import CppFunction +from code_generator.core.code_generator import CppFile +from code_generator.cpp.cpp_function import CppFunction __doc__ = """ Unit tests for C++ code generator diff --git a/tests/test_cpp_variable_writer.py b/tests/test_cpp_variable_writer.py index 8c9e0ad..7dbfc3d 100644 --- a/tests/test_cpp_variable_writer.py +++ b/tests/test_cpp_variable_writer.py @@ -1,8 +1,8 @@ import unittest import io -from code_generation.core.code_generator import CppFile -from code_generation.cpp.cpp_variable import CppVariable +from code_generator.core.code_generator import CppFile +from code_generator.cpp.cpp_variable import CppVariable __doc__ = """Unit tests for C++ code generator """ diff --git a/tests/test_html_writer.py b/tests/test_html_writer.py index d567d63..bfcaa61 100644 --- a/tests/test_html_writer.py +++ b/tests/test_html_writer.py @@ -1,7 +1,7 @@ import unittest import io -from code_generation.html.html_generator import * +from code_generator.html.html_generator import * __doc__ = """ Unit tests for HTML code generator From e72f1f908596e3bac2e8567bd07b35a75b025b5c Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 13 Mar 2023 20:15:53 +0700 Subject: [PATCH 07/51] Attempt renaming package rollback --- release_package.py | 2 +- setup.cfg | 2 +- src/code_generation/core/code_generator.py | 2 +- src/code_generation/cpp/array_generator.py | 2 +- src/code_generation/cpp/class_generator.py | 4 ++-- src/code_generation/cpp/enum_generator.py | 2 +- src/code_generation/cpp/function_generator.py | 2 +- src/code_generation/cpp/language_element.py | 2 +- src/code_generation/cpp/variable_generator.py | 2 +- src/code_generation/html/html_generator.py | 2 +- src/code_generation/java/java_generator.py | 2 +- src/example.py | 4 ++-- tests/create_assets.py | 12 ++++++------ tests/test_cpp_file.py | 12 ++++++------ tests/test_cpp_function_writer.py | 4 ++-- tests/test_cpp_variable_writer.py | 4 ++-- tests/test_html_writer.py | 2 +- 17 files changed, 31 insertions(+), 31 deletions(-) diff --git a/release_package.py b/release_package.py index 06bcf18..bae927b 100644 --- a/release_package.py +++ b/release_package.py @@ -19,7 +19,7 @@ PACKAGE_NAME = cfg.get('metadata', 'name') # Name with dash (pip name, URL, S3 bucket) -PACKAGE_NAME_DASH = 'code-generator2' +PACKAGE_NAME_DASH = PACKAGE_NAME.replace('_', '-') PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)))) PYTHON = "python3" diff --git a/setup.cfg b/setup.cfg index 81277ab..4e67733 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = code_generator +name = code_generation version = 2.4.1 [options] diff --git a/src/code_generation/core/code_generator.py b/src/code_generation/core/code_generator.py index 1c09ce9..2d92d9b 100644 --- a/src/code_generation/core/code_generator.py +++ b/src/code_generation/core/code_generator.py @@ -1,5 +1,5 @@ import sys -from code_generator.core.code_style import ANSICodeStyle +from code_generation.core.code_style import ANSICodeStyle __doc__ = """ Simple and straightforward code generator that could be used for generating code diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index 32758f1..8eed0d0 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -1,4 +1,4 @@ -from code_generator.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation # noinspection PyUnresolvedReferences diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 9471141..61050f6 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -1,5 +1,5 @@ -from code_generator.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation -from code_generator.cpp.function_generator import CppFunction +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.function_generator import CppFunction from textwrap import dedent diff --git a/src/code_generation/cpp/enum_generator.py b/src/code_generation/cpp/enum_generator.py index a5d3e61..5bd44bb 100644 --- a/src/code_generation/cpp/enum_generator.py +++ b/src/code_generation/cpp/enum_generator.py @@ -1,4 +1,4 @@ -from code_generator.cpp.cpp_generator import CppLanguageElement +from code_generation.cpp.cpp_generator import CppLanguageElement __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: classes, methods and functions, variables, enums. diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index dd28b46..77b93d6 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -1,4 +1,4 @@ -from code_generator.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation from textwrap import dedent diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index 924d81e..696c668 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -1,4 +1,4 @@ -from code_generator.core.code_generator import CodeFile +from code_generation.core.code_generator import CodeFile __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: classes, methods and functions, variables, enums. diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index c63a9cb..20a955b 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -1,4 +1,4 @@ -from code_generator.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation from textwrap import dedent __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: diff --git a/src/code_generation/html/html_generator.py b/src/code_generation/html/html_generator.py index 0cdd4a4..22853c9 100644 --- a/src/code_generation/html/html_generator.py +++ b/src/code_generation/html/html_generator.py @@ -1,5 +1,5 @@ import sys -from code_generator.core.code_style import HTMLStyle +from code_generation.core.code_style import HTMLStyle class HtmlFile: diff --git a/src/code_generation/java/java_generator.py b/src/code_generation/java/java_generator.py index 441625a..0ac63d3 100644 --- a/src/code_generation/java/java_generator.py +++ b/src/code_generation/java/java_generator.py @@ -2,7 +2,7 @@ classes, methods, variables, enums. """ -from code_generator.core.code_style import ANSICodeStyle +from code_generation.core.code_style import ANSICodeStyle class JavaFile: diff --git a/src/example.py b/src/example.py index 4322b26..59aae3c 100644 --- a/src/example.py +++ b/src/example.py @@ -1,5 +1,5 @@ -from code_generator import code_generator -from code_generator import cpp_generator +from code_generation import code_generator +from code_generation import cpp_generator # Create a new code file cpp = code_generator.CodeFile('example.cpp') diff --git a/tests/create_assets.py b/tests/create_assets.py index 0021772..fa262e3 100644 --- a/tests/create_assets.py +++ b/tests/create_assets.py @@ -1,12 +1,12 @@ import os import argparse -from code_generator.core.code_generator import CppFile -from code_generator.cpp.cpp_variable import CppVariable -from code_generator.cpp.cpp_enum import CppEnum -from code_generator.cpp.cpp_array import CppArray -from code_generator.cpp.cpp_function import CppFunction -from code_generator.cpp.cpp_class import CppClass +from code_generation.core.code_generator import CppFile +from code_generation.cpp.cpp_variable import CppVariable +from code_generation.cpp.cpp_enum import CppEnum +from code_generation.cpp.cpp_array import CppArray +from code_generation.cpp.cpp_function import CppFunction +from code_generation.cpp.cpp_class import CppClass __doc__ = """Do not call this script unless generator logic is changed """ diff --git a/tests/test_cpp_file.py b/tests/test_cpp_file.py index 7debe50..6179848 100644 --- a/tests/test_cpp_file.py +++ b/tests/test_cpp_file.py @@ -2,12 +2,12 @@ import unittest import filecmp -from code_generator.core.code_generator import CppFile -from code_generator.cpp.cpp_variable import CppVariable -from code_generator.cpp.cpp_enum import CppEnum -from code_generator.cpp.cpp_array import CppArray -from code_generator.cpp.cpp_function import CppFunction -from code_generator.cpp.cpp_class import CppClass +from code_generation.core.code_generator import CppFile +from code_generation.cpp.cpp_variable import CppVariable +from code_generation.cpp.cpp_enum import CppEnum +from code_generation.cpp.cpp_array import CppArray +from code_generation.cpp.cpp_function import CppFunction +from code_generation.cpp.cpp_class import CppClass __doc__ = """ Unit tests for C++ code generator diff --git a/tests/test_cpp_function_writer.py b/tests/test_cpp_function_writer.py index ef7b414..8328aad 100644 --- a/tests/test_cpp_function_writer.py +++ b/tests/test_cpp_function_writer.py @@ -2,8 +2,8 @@ import io from textwrap import dedent -from code_generator.core.code_generator import CppFile -from code_generator.cpp.cpp_function import CppFunction +from code_generation.core.code_generator import CppFile +from code_generation.cpp.cpp_function import CppFunction __doc__ = """ Unit tests for C++ code generator diff --git a/tests/test_cpp_variable_writer.py b/tests/test_cpp_variable_writer.py index 7dbfc3d..8c9e0ad 100644 --- a/tests/test_cpp_variable_writer.py +++ b/tests/test_cpp_variable_writer.py @@ -1,8 +1,8 @@ import unittest import io -from code_generator.core.code_generator import CppFile -from code_generator.cpp.cpp_variable import CppVariable +from code_generation.core.code_generator import CppFile +from code_generation.cpp.cpp_variable import CppVariable __doc__ = """Unit tests for C++ code generator """ diff --git a/tests/test_html_writer.py b/tests/test_html_writer.py index bfcaa61..d567d63 100644 --- a/tests/test_html_writer.py +++ b/tests/test_html_writer.py @@ -1,7 +1,7 @@ import unittest import io -from code_generator.html.html_generator import * +from code_generation.html.html_generator import * __doc__ = """ Unit tests for HTML code generator From 45addbe8ad41c0b0e3b628b8ec56597027573ca6 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 13 Mar 2023 20:31:24 +0700 Subject: [PATCH 08/51] Java language element --- src/code_generation/cpp/language_element.py | 24 +--- src/code_generation/java/language_element.py | 118 +++++++++++++++++++ 2 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 src/code_generation/java/language_element.py diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index 696c668..68f2437 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -117,7 +117,7 @@ class CppLanguageElement(object): """ The base class for all C++ language elements. Contains dynamic storage for element properties - (e.g. is_static for the variable is_virtual for the class method etc) + (e.g. is_static for the variable is_virtual for the class method etc.) """ availablePropertiesNames = {'name', 'ref_to_parent'} @@ -156,28 +156,6 @@ def init_class_properties(self, current_class_properties, input_properties_dict, if propertyName not in CppLanguageElement.availablePropertiesNames: setattr(self, propertyName, propertyValue) - def process_boolean_properties(self, properties): - """ - For every boolean property starting from 'is_' prefix generate a property without 'is_' prefix - """ - res = {**properties} - for prop in self.availablePropertiesNames: - if prop.startswith("is_"): - res[prop.replace("is_", "")] = properties.get(prop, False) - return res - - def init_boolean_properties(self, current_class_properties, input_properties_dict): - """ - Check if input properties contain either 'is_' prefixed properties or non-prefixed properties - If so, initialize prefixed properties with non-prefixed values - """ - for prop in self.availablePropertiesNames: - if prop.startswith("is_"): - non_prefixed = prop.replace("is_", "") - if non_prefixed in input_properties_dict: - setattr(self, prop, input_properties_dict[non_prefixed]) - current_class_properties.update(input_properties_dict) - def render_to_string(self, cpp): """ @param: cpp - handle that supports code generation interface (see code_generator.py) diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py new file mode 100644 index 0000000..b0182d4 --- /dev/null +++ b/src/code_generation/java/language_element.py @@ -0,0 +1,118 @@ +from code_generation.core.code_generator import CodeFile + +__doc__ = """This module encapsulates Java code generation logic for main Java language primitives: +classes, methods and functions, variables, enums. Every Java element can render its current state to a string +that can be evaluated as a legal Java construction. + +Example: +# Python code +java_class = JavaClass(name = 'MyClass') +java_class.add_variable(JavaVariable(name = "m_var", + type = 'int', + is_static = True, + is_final = True, + initialization_value = 10)) + +// Generated Java code +public class MyClass +{ + public static final int m_var = 10; +} +""" + + +class JavaFile(CodeFile): + """ + This class extends CodeFile class with some specific Java constructions + """ + + def __init__(self, filename, writer=None): + """ + Create Java source file + """ + CodeFile.__init__(self, filename, writer) + + def access(self, text): + """ + Could be used for Java class access specifiers + private: + """ + self.write('{0}:'.format(text), -1) + + +class JavaLanguageElement: + """ + The base class for all Java language elements. + Contains dynamic storage for element properties + (e.g. is_static for the variable is_abstract for the class etc.) + """ + AVAILABLE_PROPERTIES_NAMES = {'name', 'ref_to_parent'} + + def __init__(self, properties): + """ + @param: properties - Basic Java element properties (name, ref_to_parent) + class is a parent for method or a member variable + """ + self.name = properties.get('name') + self.ref_to_parent = properties.get('ref_to_parent') + + def check_input_properties_names(self, input_property_names): + """ + Ensure that all properties that are passed to the JavaLanguageElement are recognized. + Raise an exception otherwise + """ + unknown_properties = input_property_names.difference(self.AVAILABLE_PROPERTIES_NAMES) + if unknown_properties: + raise AttributeError( + f'Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}') + + def init_class_properties(self, current_class_properties, input_properties_dict, default_property_value=None): + """ + @param: current_class_properties - all available properties for the Java element to be generated + @param: input_properties_dict - values for the initialized properties (e.g. is_abstract=True) + @param: default_property_value - value for properties that are not initialized + (None by default, because of the same as False semantic) + """ + # Set all available properties to DefaultValue + for property_name in current_class_properties: + if property_name not in self.AVAILABLE_PROPERTIES_NAMES: + setattr(self, property_name, default_property_value) + + # Set all defined properties values (all undefined will be left with defaults) + for (property_name, property_value) in input_properties_dict.items(): + if property_name not in self.AVAILABLE_PROPERTIES_NAMES: + setattr(self, property_name, property_value) + + def render_to_string(self, java): + """ + @param: java - handle that supports code generation interface (see code_generator.py) + Typically it is passed to all child elements so that they can render their content + """ + raise NotImplementedError('JavaLanguageElement is an abstract class') + + def parent_qualifier(self): + """ + Generate a string for class name qualifiers + Should be used for method implementation and static class members definition. + Ex. + void MyClass.MyMethod() + static int MyClass.m_staticVar = 0; + + Supports for nested classes, e.g. + void MyClass.NestedClass. + """ + full_parent_qualifier = '' + parent = self.ref_to_parent + # walk through all existing parents + while parent: + full_parent_qualifier = f'{parent.name}.{full_parent_qualifier}' + parent = parent.ref_to_parent + return full_parent_qualifier + + def fully_qualified_name(self): + """ + Generate a string for the fully qualified name of the element + Ex. + MyClass.NestedClass.Method() + """ + return f'{self.parent_qualifier()}{self.name}' From 1c88dd78a848e0914e019b06cbf52b8892aa127d Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 13 Mar 2023 20:33:20 +0700 Subject: [PATCH 09/51] Java language element --- src/code_generation/core/code_generator.py | 1 - src/code_generation/java/java_generator.py | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/src/code_generation/core/code_generator.py b/src/code_generation/core/code_generator.py index 2d92d9b..e10acaf 100644 --- a/src/code_generation/core/code_generator.py +++ b/src/code_generation/core/code_generator.py @@ -142,4 +142,3 @@ def newline(self, n=1): """ for _ in range(n): self.write('') - diff --git a/src/code_generation/java/java_generator.py b/src/code_generation/java/java_generator.py index 0ac63d3..a0d78f2 100644 --- a/src/code_generation/java/java_generator.py +++ b/src/code_generation/java/java_generator.py @@ -67,19 +67,3 @@ def newline(self, n=1): """ for _ in range(n): self.write('') - - -class JavaLanguageElement(object): - """ - The base class for all Java language elements. - Contains dynamic storage for element properties - """ - availablePropertiesNames = {'name', 'ref_to_parent'} - - def __init__(self, properties): - """ - @param: properties - Basic Java element properties (name, ref_to_parent) - class is a parent for method or a member variable - """ - self.name = properties.get('name') - self.ref_to_parent = properties.get('ref_to_parent') From d8b341a346acd169e944493f5f150a410ad4b965 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 13 Mar 2023 20:37:18 +0700 Subject: [PATCH 10/51] C++ language element --- src/code_generation/cpp/array_generator.py | 2 +- src/code_generation/cpp/cpp_file.py | 25 +++++++++++++++++++ src/code_generation/cpp/enum_generator.py | 2 +- src/code_generation/cpp/function_generator.py | 2 +- src/code_generation/cpp/language_element.py | 25 +------------------ src/code_generation/cpp/variable_generator.py | 2 +- 6 files changed, 30 insertions(+), 28 deletions(-) create mode 100644 src/code_generation/cpp/cpp_file.py diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index 8eed0d0..fb71d0a 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation # noinspection PyUnresolvedReferences diff --git a/src/code_generation/cpp/cpp_file.py b/src/code_generation/cpp/cpp_file.py new file mode 100644 index 0000000..95a4df0 --- /dev/null +++ b/src/code_generation/cpp/cpp_file.py @@ -0,0 +1,25 @@ +from code_generation.core.code_generator import CodeFile + +__doc__ = """ +""" + + +class CppFile(CodeFile): + """ + This class extends CodeFile class with some specific C++ constructions + """ + + def __init__(self, filename, writer=None): + """ + Create C++ source file + """ + CodeFile.__init__(self, filename, writer) + + def label(self, text): + """ + Could be used for access specifiers or ANSI C labels, e.g. + private: + a: + """ + self.write('{0}:'.format(text), -1) + diff --git a/src/code_generation/cpp/enum_generator.py b/src/code_generation/cpp/enum_generator.py index 5bd44bb..93a4ec9 100644 --- a/src/code_generation/cpp/enum_generator.py +++ b/src/code_generation/cpp/enum_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement +from code_generation.cpp.language_element import CppLanguageElement __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: classes, methods and functions, variables, enums. diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index 77b93d6..7f4861c 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation from textwrap import dedent diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index 68f2437..6f238b2 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -1,5 +1,3 @@ -from code_generation.core.code_generator import CodeFile - __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: classes, methods and functions, variables, enums. Every C++ element could render its current state to a string that could be evaluated as @@ -54,28 +52,8 @@ """ -class CppFile(CodeFile): - """ - This class extends CodeFile class with some specific C++ constructions - """ - - def __init__(self, filename, writer=None): - """ - Create C++ source file - """ - CodeFile.__init__(self, filename, writer) - - def label(self, text): - """ - Could be used for access specifiers or ANSI C labels, e.g. - private: - a: - """ - self.write('{0}:'.format(text), -1) - - ########################################################################### -# declaration/Implementation helpers +# Declaration/Implementation helpers class CppDeclaration(object): """ declaration/Implementation pair is used to split one element code generation to @@ -112,7 +90,6 @@ def render_to_string(self, cpp): self.cpp_element.render_to_string_implementation(cpp) -# C++ language element generators class CppLanguageElement(object): """ The base class for all C++ language elements. diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index 20a955b..e7e81ad 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation from textwrap import dedent __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: From 08f0265cbb2a9526f78befb3bc62dcd6e616d6e1 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 13 Mar 2023 20:40:15 +0700 Subject: [PATCH 11/51] JavaFile class --- src/code_generation/core/code_generator.py | 3 +-- src/code_generation/cpp/cpp_file.py | 1 - src/code_generation/java/{java_generator.py => java_file.py} | 0 3 files changed, 1 insertion(+), 3 deletions(-) rename src/code_generation/java/{java_generator.py => java_file.py} (100%) diff --git a/src/code_generation/core/code_generator.py b/src/code_generation/core/code_generator.py index e10acaf..c45c4fc 100644 --- a/src/code_generation/core/code_generator.py +++ b/src/code_generation/core/code_generator.py @@ -1,10 +1,10 @@ -import sys from code_generation.core.code_style import ANSICodeStyle __doc__ = """ Simple and straightforward code generator that could be used for generating code on any programming language and to be a 'building block' for creating more complicated code generator. + Thanks to Eric Reynolds, the code mainly based on his article published on https://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python However, it was both significantly extended since then and also simplified. @@ -40,7 +40,6 @@ class A Class `ANSICodeStyle` is responsible for code formatting. Re-implement it if you wish to apply any other formatting style. - """ diff --git a/src/code_generation/cpp/cpp_file.py b/src/code_generation/cpp/cpp_file.py index 95a4df0..a59d2db 100644 --- a/src/code_generation/cpp/cpp_file.py +++ b/src/code_generation/cpp/cpp_file.py @@ -22,4 +22,3 @@ def label(self, text): a: """ self.write('{0}:'.format(text), -1) - diff --git a/src/code_generation/java/java_generator.py b/src/code_generation/java/java_file.py similarity index 100% rename from src/code_generation/java/java_generator.py rename to src/code_generation/java/java_file.py From f9ef622ca92351a33edad0edb5a1e6d7a0d9f490 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 13 Mar 2023 20:43:29 +0700 Subject: [PATCH 12/51] Added quick start --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index f196ea3..5e157d7 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,19 @@ Programmatic source code generation can have a wide range of use cases, includin Overall, programmatic source code generation can help improve development efficiency, reduce human error, and automate repetitive tasks. +## Installation + +`pip install code_generation` + +## Quick Start + +### C++ + +TODO + +```python +from code_generation import CppFile, CppFunction, CppVariable +``` ## C++ From 4c822ac4c4955e176b6d723a563e8fa38a0522c7 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 13 Mar 2023 20:44:08 +0700 Subject: [PATCH 13/51] Added quick start --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5e157d7..571b799 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # Pythonic Code Generator -============== Simple and straightforward generator for algorithmic creating of program code. In general, it could be used for generating code in any programming language, From d1f5d5f3bebf8c5918b273e55565efadf2c64bca Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 14 Mar 2023 00:46:02 +0700 Subject: [PATCH 14/51] Adjust imports --- src/code_generation/core/code_generator.py | 6 +-- src/code_generation/cpp/__init__.py | 12 ++--- src/code_generation/cpp/enum_generator.py | 54 ------------------- src/code_generation/cpp/function_generator.py | 2 +- src/code_generation/cpp/language_element.py | 33 +++++------- src/code_generation/java/language_element.py | 13 ++++- 6 files changed, 33 insertions(+), 87 deletions(-) diff --git a/src/code_generation/core/code_generator.py b/src/code_generation/core/code_generator.py index c45c4fc..b93c36b 100644 --- a/src/code_generation/core/code_generator.py +++ b/src/code_generation/core/code_generator.py @@ -3,17 +3,15 @@ __doc__ = """ Simple and straightforward code generator that could be used for generating code on any programming language and to be a 'building block' for creating more complicated -code generator. +code generators. Thanks to Eric Reynolds, the code mainly based on his article published on https://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python -However, it was both significantly extended since then and also simplified. - Used under the Code Project Open License https://www.codeproject.com/info/cpol10.aspx Examples of usage: - + 1. # Python code cpp = CodeFile('example.cpp') diff --git a/src/code_generation/cpp/__init__.py b/src/code_generation/cpp/__init__.py index 9c92e0f..d0f12dc 100644 --- a/src/code_generation/cpp/__init__.py +++ b/src/code_generation/cpp/__init__.py @@ -1,6 +1,6 @@ -from . import cpp_array -from . import cpp_class -from . import cpp_enum -from . import cpp_function -from . import cpp_generator -from . import cpp_variable +from . import array_generator +from . import class_generator +from . import enum_generator +from . import function_generator +from . import language_element +from . import variable_generator diff --git a/src/code_generation/cpp/enum_generator.py b/src/code_generation/cpp/enum_generator.py index 93a4ec9..401d74c 100644 --- a/src/code_generation/cpp/enum_generator.py +++ b/src/code_generation/cpp/enum_generator.py @@ -1,59 +1,5 @@ from code_generation.cpp.language_element import CppLanguageElement -__doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: -classes, methods and functions, variables, enums. -Every C++ element could render its current state to a string that could be evaluated as -a legal C++ construction. - -Some elements could be rendered to a pair of representations (i.e. declaration and definition) - -Example: -# Python code -cpp_class = CppClass(name = 'MyClass', is_struct = True) -cpp_class.add_variable(CppVariable(name = "m_var", - type = 'size_t', - is_static = True, - is_const = True, - initialization_value = 255)) - -// Generated C++ declaration -struct MyClass -{ - static const size_t m_var; -} - -// Generated C++ definition -const size_t MyClass::m_var = 255; - - -That module uses and highly depends on code_generator.py as it uses -code generating and formatting primitives implemented there. - -The main object referenced from code_generator.py is CppFile, -which is passed as a parameter to render_to_string(cpp) Python method - -It could also be used for composing more complicated C++ code, -that does not supported by cpp_generator - -It support: - -- functional calls: -cpp('int a = 10;') - -- 'with' semantic: -with cpp.block('class MyClass', ';') - class_definition(cpp) - -- append code to the last string without EOL: -cpp.append(', p = NULL);') - -- empty lines: -cpp.newline(2) - -For detailed information see code_generator.py documentation. -""" - - class CppEnum(CppLanguageElement): """ The Python class that generates string representation for C++ enum diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index 7f4861c..df407b1 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -69,7 +69,7 @@ def args(self): def add_argument(self, argument): """ - @param: argument string representation of the C++ function argument ('int a', 'void p = NULL' etc) + @param: argument string representation of the C++ function argument ('int a', 'void p = nullptr' etc.) """ self.arguments.append(argument) diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index 6f238b2..f559a1e 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -4,7 +4,7 @@ a legal C++ construction. Some elements could be rendered to a pair of representations (i.e. declaration and definition) - + Example: # Python code cpp_class = CppClass(name = 'MyClass', is_struct = True) @@ -13,42 +13,35 @@ is_static = True, is_const = True, initialization_value = 255)) - + // Generated C++ declaration struct MyClass { static const size_t m_var; } - + // Generated C++ definition const size_t MyClass::m_var = 255; - - -That module uses and highly depends on code_generator.py as it uses -code generating and formatting primitives implemented there. - -The main object referenced from code_generator.py is CppFile, -which is passed as a parameter to render_to_string(cpp) Python method - -It could also be used for composing more complicated C++ code, -that does not supported by cpp_generator - -It support: + +You can use CppFile for composing more complicated C++ code, +which is not supported by CppLanguageElement + +It supports: - functional calls: cpp('int a = 10;') - + - 'with' semantic: with cpp.block('class MyClass', ';') class_definition(cpp) - + - append code to the last string without EOL: cpp.append(', p = NULL);') - + - empty lines: cpp.newline(2) - -For detailed information see code_generator.py documentation. + +For more detailed information see CodeFile and CppFile documentation. """ diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py index b0182d4..b8b1cfb 100644 --- a/src/code_generation/java/language_element.py +++ b/src/code_generation/java/language_element.py @@ -12,11 +12,20 @@ is_static = True, is_final = True, initialization_value = 10)) +java_class.add_method(JavaMethod(name = 'main', + return_type = 'void', + is_static = True, + arguments = [JavaArgument(name = 'args', type = 'String[]')])) +java_class.render_to_file('MyClass.java') // Generated Java code public class MyClass { public static final int m_var = 10; + static public void main(String[] args) + { + return; + } } """ @@ -93,8 +102,8 @@ def render_to_string(self, java): def parent_qualifier(self): """ Generate a string for class name qualifiers - Should be used for method implementation and static class members definition. - Ex. + Should be used for method implementation and static class members' definition. + E.g. void MyClass.MyMethod() static int MyClass.m_staticVar = 0; From 777be5f7da7f472fe226c07f8eb7eca74641dd78 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 23 Mar 2023 14:49:09 +0700 Subject: [PATCH 15/51] HTML report generator --- src/code_generation/core/__init__.py | 2 +- .../core/{code_generator.py => code_file.py} | 0 src/code_generation/cpp/cpp_file.py | 2 +- src/code_generation/cpp/language_element.py | 2 +- src/code_generation/cpp/variable_generator.py | 6 +- src/code_generation/html/__init__.py | 3 +- src/code_generation/html/html_element.py | 37 +++++ .../html/{html_generator.py => html_file.py} | 15 +- src/code_generation/java/__init__.py | 3 +- src/code_generation/java/java_file.py | 62 +-------- src/code_generation/java/language_element.py | 4 +- src/example.py | 128 ++++++++++++++---- tests/create_assets.py | 2 +- tests/test_cpp_file.py | 2 +- tests/test_cpp_function_writer.py | 2 +- tests/test_cpp_variable_writer.py | 2 +- tests/test_html_writer.py | 2 +- 17 files changed, 169 insertions(+), 105 deletions(-) rename src/code_generation/core/{code_generator.py => code_file.py} (100%) create mode 100644 src/code_generation/html/html_element.py rename src/code_generation/html/{html_generator.py => html_file.py} (70%) diff --git a/src/code_generation/core/__init__.py b/src/code_generation/core/__init__.py index 1b7181b..5999c70 100644 --- a/src/code_generation/core/__init__.py +++ b/src/code_generation/core/__init__.py @@ -1,2 +1,2 @@ -from . import code_generator +from . import code_file from . import code_style diff --git a/src/code_generation/core/code_generator.py b/src/code_generation/core/code_file.py similarity index 100% rename from src/code_generation/core/code_generator.py rename to src/code_generation/core/code_file.py diff --git a/src/code_generation/cpp/cpp_file.py b/src/code_generation/cpp/cpp_file.py index a59d2db..88d5afa 100644 --- a/src/code_generation/cpp/cpp_file.py +++ b/src/code_generation/cpp/cpp_file.py @@ -1,4 +1,4 @@ -from code_generation.core.code_generator import CodeFile +from code_generation.core.code_file import CodeFile __doc__ = """ """ diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index f559a1e..10324b5 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -128,7 +128,7 @@ def init_class_properties(self, current_class_properties, input_properties_dict, def render_to_string(self, cpp): """ - @param: cpp - handle that supports code generation interface (see code_generator.py) + @param: cpp - handle that supports code generation interface (see code_file.py) Typically it is passed to all child elements so that render their content """ raise NotImplementedError('CppLanguageElement is an abstract class') diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index e7e81ad..0ddfc49 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -27,10 +27,10 @@ const size_t MyClass::m_var = 255; -That module uses and highly depends on code_generator.py as it uses +That module uses and highly depends on code_file.py as it uses code generating and formatting primitives implemented there. -The main object referenced from code_generator.py is CppFile, +The main object referenced from code_file.py is CppFile, which is passed as a parameter to render_to_string(cpp) Python method It could also be used for composing more complicated C++ code, @@ -51,7 +51,7 @@ - empty lines: cpp.newline(2) -For detailed information see code_generator.py documentation. +For detailed information see code_file.py documentation. """ diff --git a/src/code_generation/html/__init__.py b/src/code_generation/html/__init__.py index 9c6785f..1e68bc5 100644 --- a/src/code_generation/html/__init__.py +++ b/src/code_generation/html/__init__.py @@ -1 +1,2 @@ -from . import html_generator +from . import html_file +from . import html_element diff --git a/src/code_generation/html/html_element.py b/src/code_generation/html/html_element.py new file mode 100644 index 0000000..78875f9 --- /dev/null +++ b/src/code_generation/html/html_element.py @@ -0,0 +1,37 @@ +class HtmlElement: + availablePropertiesNames = {'name', 'attributes', 'self_closing'} + + def __init__(self, **properties): + """ + :param properties: Basic HTML element properties (name, self-closing, attributes) + """ + self.name = properties.get('name') + self.self_closing = properties.get('self_closing', False) + self.attributes = properties.get('attributes', {}) + + # If 'attributes' is present, set it to self.attributes and remove it from properties + if 'attributes' in properties: + self.attributes = properties['attributes'] + properties.pop('attributes') + + # Populate self.attributes with any remaining properties not already set + for key, value in properties.items(): + if key not in ('name', 'self_closing'): + self.attributes[key] = value + + def render_to_string(self, html, content=None): + """ + Generates HTML code for the self-closing element + """ + if self.self_closing: + html('<{0} {1}/>'.format(self.name, ' '.join(self._render_attributes()))) + elif content is not None: + with html.block(element=self.name, **self.attributes): + html(content) + + def _render_attributes(self): + """ + Renders attributes to string + """ + rendered_attributes = ' '.join('{0}="{1}"'.format(key, value) for key, value in self.attributes.items()) + return f' {rendered_attributes}' if len(self.attributes) else '' diff --git a/src/code_generation/html/html_generator.py b/src/code_generation/html/html_file.py similarity index 70% rename from src/code_generation/html/html_generator.py rename to src/code_generation/html/html_file.py index 22853c9..e6f71c5 100644 --- a/src/code_generation/html/html_generator.py +++ b/src/code_generation/html/html_file.py @@ -1,4 +1,3 @@ -import sys from code_generation.core.code_style import HTMLStyle @@ -26,9 +25,10 @@ def write(self, text, indent=0, endline=True): """ Write a new line with line ending """ - self.out.write('{0}{1}{2}'.format(self.Formatter.indent * (self.current_indent+indent), - text, - self.Formatter.endline if endline else '')) + assert isinstance(indent, int), f'indent {indent} is not an integer, but {type(indent)}' + assert isinstance(text, str), f'text {text} is not a string, but {type(text)}' + indent_str = self.Formatter.indent * (self.current_indent + indent) + self.out.write('{0}{1}{2}'.format(indent_str, text, self.Formatter.endline if endline else '')) def append(self, x): """ @@ -50,7 +50,12 @@ def block(self, element, **attributes): Supports 'with' semantic, i.e. html.block(element='p', id='id1', name='name1'): """ - return self.Formatter(self, element=element, **attributes) + print(f'block: {element}, {attributes}') + for a in attributes: + print(f'atr: {a}') + formatter = self.Formatter(self, element=element, **attributes) + print(f'formatter: {formatter}') + return formatter def endline(self, count=1): """ diff --git a/src/code_generation/java/__init__.py b/src/code_generation/java/__init__.py index 2eda467..0f9a258 100644 --- a/src/code_generation/java/__init__.py +++ b/src/code_generation/java/__init__.py @@ -1 +1,2 @@ -from . import java_generator +from . import java_file +from . import language_element diff --git a/src/code_generation/java/java_file.py b/src/code_generation/java/java_file.py index a0d78f2..c3b58ec 100644 --- a/src/code_generation/java/java_file.py +++ b/src/code_generation/java/java_file.py @@ -2,68 +2,20 @@ classes, methods, variables, enums. """ +from code_generation.core.code_file import CodeFile from code_generation.core.code_style import ANSICodeStyle -class JavaFile: +class JavaFile(CodeFile): Formatter = ANSICodeStyle def __init__(self, filename, writer=None): - self.current_indent = 0 - self.last = None - self.filename = filename - if writer: - self.out = writer - else: - self.out = open(filename, "w") + CodeFile.__init__(self, filename, writer) - def close(self): + def access(self, text): """ - File created, just close the handle + Access specifiers, e.g. + private: """ - self.out.close() - self.out = None - - def write(self, text, indent=0, endline=True): - """ - Write a new line with line ending - """ - self.out.write('{0}{1}{2}'.format(self.Formatter.indent * (self.current_indent+indent), - text, - self.Formatter.endline if endline else '')) - - def append(self, x): - """ - Append to the existing line without line ending - """ - self.out.write(x) - - def __call__(self, text, indent=0, endline=True): - """ - Supports 'object()' semantic, i.e. - cpp('#include ') - inserts appropriate line - """ - self.write(text, indent, endline) - - def block(self, element, **attributes): - """ - Returns a stub for HTML element - Supports 'with' semantic, i.e. - html.block(element='p', id='id1', name='name1'): - """ - return self.Formatter(self, element=element, **attributes) - - def endline(self, count=1): - """ - Insert an endline - """ - self.write(self.Formatter.endline * count, endline=False) - - def newline(self, n=1): - """ - Insert one or several empty lines - """ - for _ in range(n): - self.write('') + self.write('{0}:'.format(text), -1) diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py index b8b1cfb..96ed03d 100644 --- a/src/code_generation/java/language_element.py +++ b/src/code_generation/java/language_element.py @@ -1,4 +1,4 @@ -from code_generation.core.code_generator import CodeFile +from code_generation.core.code_file import CodeFile __doc__ = """This module encapsulates Java code generation logic for main Java language primitives: classes, methods and functions, variables, enums. Every Java element can render its current state to a string @@ -94,7 +94,7 @@ def init_class_properties(self, current_class_properties, input_properties_dict, def render_to_string(self, java): """ - @param: java - handle that supports code generation interface (see code_generator.py) + @param: java - handle that supports code generation interface (see code_file.py) Typically it is passed to all child elements so that they can render their content """ raise NotImplementedError('JavaLanguageElement is an abstract class') diff --git a/src/example.py b/src/example.py index 59aae3c..2642a7e 100644 --- a/src/example.py +++ b/src/example.py @@ -1,30 +1,98 @@ -from code_generation import code_generator -from code_generation import cpp_generator - -# Create a new code file -cpp = code_generator.CodeFile('example.cpp') -cpp('int i = 0;') - -# Create a new variable 'x' -x_variable = cpp_generator.CppVariable( - name='x', - type='int const&', - is_static=True, - is_constexpr=True, - initialization_value='42') -x_variable.render_to_string(cpp) - -# Create a new variable 'name' -name_variable = cpp_generator.CppVariable( - name='name', - type='char*', - is_extern=True) -name_variable.render_to_string(cpp) - - -# Generated C++ code -""" -int i = 0; -static constexpr int const& x = 42; -extern char* name; -""" +import argparse +import sys + +from code_generation.core.code_file import CodeFile +from code_generation.cpp.variable_generator import CppVariable +from code_generation.java.java_file import JavaFile +from code_generation.html.html_file import HtmlFile +from code_generation.html.html_element import HtmlElement + + +def cpp_example(): + """ + Generated C++ code: + int i = 0; + static constexpr int const& x = 42; + extern char* name; + """ + cpp = CodeFile('example.cpp') + cpp('int i = 0;') + + # Create a new variable 'x' + x_variable = CppVariable( + name='x', + type='int const&', + is_static=True, + is_constexpr=True, + initialization_value='42') + x_variable.render_to_string(cpp) + + # Create a new variable 'name' + name_variable = CppVariable( + name='name', + type='char*', + is_extern=True) + name_variable.render_to_string(cpp) + + +def java_example(): + """ + :return: + """ + java = JavaFile('example.java') + with java.block('class A', ';'): + java.access('public') + java('int m_classMember1;') + java('double m_classMember2;') + + +def html_example(): + html = HtmlFile('example.html') + with html.block('html'): + with html.block('head', lang='en'): + html('') + + +def html_example2(): + html = HtmlFile('example2.html') + with html.block('html'): + with html.block('head', lang='en'): + HtmlElement(name='meta', self_closing=True, charset='utf-8').render_to_string(html) + HtmlElement(name='meta', self_closing=True, viewport='width=device-width, initial-scale=1').render_to_string(html) + with html.block('body'): + # with semantic + with html.block('div', id='container'): + with html.block('div', id='header'): + html('Header') + with html.block('div', id='content'): + html('Content') + # using content parameter + HtmlElement(name='div', self_closing=False).render_to_string(html, content='Footer 1') + HtmlElement(name='footer', self_closing=False, id='real-footer').render_to_string(html, content='Footer 2') + + +def html_example3(): + html = HtmlFile('example2.html') + HtmlElement(name='footer', self_closing=False, id='real-footer').render_to_string(html, content='Footer 2') + + +def main(): + parser = argparse.ArgumentParser(description='Command-line params') + parser.add_argument('--language', + help='Programming language to show example for', + choices=["C++", "Java", "HTML"], + default="Java", + required=False) + args = parser.parse_args() + + if args.language == 'C++': + cpp_example() + elif args.language == 'Java': + java_example() + elif args.language == 'HTML': + html_example3() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tests/create_assets.py b/tests/create_assets.py index fa262e3..f2382c9 100644 --- a/tests/create_assets.py +++ b/tests/create_assets.py @@ -1,7 +1,7 @@ import os import argparse -from code_generation.core.code_generator import CppFile +from code_generation.core.code_file import CppFile from code_generation.cpp.cpp_variable import CppVariable from code_generation.cpp.cpp_enum import CppEnum from code_generation.cpp.cpp_array import CppArray diff --git a/tests/test_cpp_file.py b/tests/test_cpp_file.py index 6179848..f42afcd 100644 --- a/tests/test_cpp_file.py +++ b/tests/test_cpp_file.py @@ -2,7 +2,7 @@ import unittest import filecmp -from code_generation.core.code_generator import CppFile +from code_generation.core.code_file import CppFile from code_generation.cpp.cpp_variable import CppVariable from code_generation.cpp.cpp_enum import CppEnum from code_generation.cpp.cpp_array import CppArray diff --git a/tests/test_cpp_function_writer.py b/tests/test_cpp_function_writer.py index 8328aad..9110a28 100644 --- a/tests/test_cpp_function_writer.py +++ b/tests/test_cpp_function_writer.py @@ -2,7 +2,7 @@ import io from textwrap import dedent -from code_generation.core.code_generator import CppFile +from code_generation.core.code_file import CppFile from code_generation.cpp.cpp_function import CppFunction __doc__ = """ diff --git a/tests/test_cpp_variable_writer.py b/tests/test_cpp_variable_writer.py index 8c9e0ad..ff1403c 100644 --- a/tests/test_cpp_variable_writer.py +++ b/tests/test_cpp_variable_writer.py @@ -1,7 +1,7 @@ import unittest import io -from code_generation.core.code_generator import CppFile +from code_generation.core.code_file import CppFile from code_generation.cpp.cpp_variable import CppVariable __doc__ = """Unit tests for C++ code generator diff --git a/tests/test_html_writer.py b/tests/test_html_writer.py index d567d63..7946c30 100644 --- a/tests/test_html_writer.py +++ b/tests/test_html_writer.py @@ -1,7 +1,7 @@ import unittest import io -from code_generation.html.html_generator import * +from code_generation.html.html_file import * __doc__ = """ Unit tests for HTML code generator From 48b7ab72a0bc018b4e2a05b166c980d601e6303a Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 23 Mar 2023 16:18:21 +0700 Subject: [PATCH 16/51] Java array generator --- src/code_generation/cpp/array_generator.py | 9 +++ src/code_generation/html/html_element.py | 7 +- src/code_generation/html/html_file.py | 7 +- src/code_generation/java/java_array.py | 71 ++++++++++++++++++++ src/code_generation/java/java_file.py | 7 -- src/code_generation/java/language_element.py | 8 +-- src/example.py | 22 +++--- 7 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 src/code_generation/java/java_array.py diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index fb71d0a..e659afe 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -54,6 +54,15 @@ def __init__(self, **properties): self.init_class_properties(current_class_properties=self.availablePropertiesNames, input_properties_dict=properties) + def _sanity_check(self): + """ + Check if all required properties are set + """ + if not self.type: + raise RuntimeError('Array type is not set') + if self.is_class_member and not self.name: + raise RuntimeError('Class member array name is not set') + def _render_static(self): """ @return: 'static' prefix if required diff --git a/src/code_generation/html/html_element.py b/src/code_generation/html/html_element.py index 78875f9..50f9dab 100644 --- a/src/code_generation/html/html_element.py +++ b/src/code_generation/html/html_element.py @@ -1,3 +1,8 @@ +__doc__ = """This module encapsulates HTML code generation logic for HTML elements, +including name and attributes. Every HTML element can render its current state to a string. +""" + + class HtmlElement: availablePropertiesNames = {'name', 'attributes', 'self_closing'} @@ -24,7 +29,7 @@ def render_to_string(self, html, content=None): Generates HTML code for the self-closing element """ if self.self_closing: - html('<{0} {1}/>'.format(self.name, ' '.join(self._render_attributes()))) + html('<{0} {1}/>'.format(self.name, self._render_attributes())) elif content is not None: with html.block(element=self.name, **self.attributes): html(content) diff --git a/src/code_generation/html/html_file.py b/src/code_generation/html/html_file.py index e6f71c5..7ad1a8c 100644 --- a/src/code_generation/html/html_file.py +++ b/src/code_generation/html/html_file.py @@ -50,12 +50,7 @@ def block(self, element, **attributes): Supports 'with' semantic, i.e. html.block(element='p', id='id1', name='name1'): """ - print(f'block: {element}, {attributes}') - for a in attributes: - print(f'atr: {a}') - formatter = self.Formatter(self, element=element, **attributes) - print(f'formatter: {formatter}') - return formatter + return self.Formatter(self, element=element, **attributes) def endline(self, count=1): """ diff --git a/src/code_generation/java/java_array.py b/src/code_generation/java/java_array.py new file mode 100644 index 0000000..a4e8c38 --- /dev/null +++ b/src/code_generation/java/java_array.py @@ -0,0 +1,71 @@ +from code_generation.core.code_file import CodeFile + +__doc__ = """""" + + +from code_generation.java.language_element import JavaLanguageElement + + +class JavaArray(JavaLanguageElement): + + available_properties_names = { + 'type', + 'array_size', + 'is_const', + 'const', + 'is_class_member', + 'is_dynamic', + 'dynamic', + 'items' + } | JavaLanguageElement.available_properties_names + + def __init__(self, **properties): + """ + :param properties: + """ + self.type = '' + self.is_const = False + self.is_class_member = False + self.dynamic = False + self.array_size = 0 + self.items = [] + input_property_names = set(properties.keys()) + self.check_input_properties_names(input_property_names) + super(JavaArray, self).__init__(properties) + self.init_class_properties(current_class_properties=self.available_properties_names, + input_properties_dict=properties) + + def _sanity_check(self): + """ + Check if all required properties are set + """ + if not self.type: + raise RuntimeError('Array type is not set') + if self.is_class_member and not self.name: + raise RuntimeError('Class member array name is not set') + if self.array_size and self.dynamic: + raise RuntimeError('Array size is defined and array is dynamic') + if self.array_size and self.items: + raise RuntimeError('Array size is defined and array has items') + + def _render_static(self, java): + if self.items is None or not self.items: + values_str = ', '.join(f'{self.type}()' for _ in range(self.array_size)) + else: + values_str = ', '.join(str(item) for item in self.items) + java(f"{self.type}[] {self.name} = {{ {values_str} }};") + + def _render_dynamic(self, java): + if self.items is None or not self.items: + values_str = ', '.join(f'{self.type}()' for _ in range(self.array_size)) + else: + values_str = ', '.join(str(item) for item in self.items) + + java(f"{self.type}[] {self.name} = new {self.type}[{len(self.items)}]{{ {values_str} }};") + + def render_to_string(self, java): + self._sanity_check() + if self.dynamic: + self._render_dynamic(java) + else: + self._render_static(java) diff --git a/src/code_generation/java/java_file.py b/src/code_generation/java/java_file.py index c3b58ec..d871fa0 100644 --- a/src/code_generation/java/java_file.py +++ b/src/code_generation/java/java_file.py @@ -12,10 +12,3 @@ class JavaFile(CodeFile): def __init__(self, filename, writer=None): CodeFile.__init__(self, filename, writer) - - def access(self, text): - """ - Access specifiers, e.g. - private: - """ - self.write('{0}:'.format(text), -1) diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py index 96ed03d..89b5f56 100644 --- a/src/code_generation/java/language_element.py +++ b/src/code_generation/java/language_element.py @@ -55,7 +55,7 @@ class JavaLanguageElement: Contains dynamic storage for element properties (e.g. is_static for the variable is_abstract for the class etc.) """ - AVAILABLE_PROPERTIES_NAMES = {'name', 'ref_to_parent'} + available_properties_names = {'name', 'ref_to_parent'} def __init__(self, properties): """ @@ -70,7 +70,7 @@ def check_input_properties_names(self, input_property_names): Ensure that all properties that are passed to the JavaLanguageElement are recognized. Raise an exception otherwise """ - unknown_properties = input_property_names.difference(self.AVAILABLE_PROPERTIES_NAMES) + unknown_properties = input_property_names.difference(self.available_properties_names) if unknown_properties: raise AttributeError( f'Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}') @@ -84,12 +84,12 @@ def init_class_properties(self, current_class_properties, input_properties_dict, """ # Set all available properties to DefaultValue for property_name in current_class_properties: - if property_name not in self.AVAILABLE_PROPERTIES_NAMES: + if property_name not in JavaLanguageElement.available_properties_names: setattr(self, property_name, default_property_value) # Set all defined properties values (all undefined will be left with defaults) for (property_name, property_value) in input_properties_dict.items(): - if property_name not in self.AVAILABLE_PROPERTIES_NAMES: + if property_name not in JavaLanguageElement.available_properties_names: setattr(self, property_name, property_value) def render_to_string(self, java): diff --git a/src/example.py b/src/example.py index 2642a7e..5362a2f 100644 --- a/src/example.py +++ b/src/example.py @@ -4,6 +4,8 @@ from code_generation.core.code_file import CodeFile from code_generation.cpp.variable_generator import CppVariable from code_generation.java.java_file import JavaFile +from code_generation.java.java_array import JavaArray + from code_generation.html.html_file import HtmlFile from code_generation.html.html_element import HtmlElement @@ -40,10 +42,15 @@ def java_example(): :return: """ java = JavaFile('example.java') - with java.block('class A', ';'): - java.access('public') - java('int m_classMember1;') - java('double m_classMember2;') + with java.block('class Main', ';'): + with java.block('public static void main(String[] args)'): + java('System.out.println("Hello World!");') + JavaArray(name='my_array1', type='String', dynamic=True, items=['"a"', '"b"', '"c"'] + ).render_to_string(java) + JavaArray(name='my_array2', type='int', dynamic=False, items=['1', '2', '3'] + ).render_to_string(java) + JavaArray(name='my_array3', type='int', dynamic=False, array_size=3 + ).render_to_string(java) def html_example(): @@ -71,11 +78,6 @@ def html_example2(): HtmlElement(name='footer', self_closing=False, id='real-footer').render_to_string(html, content='Footer 2') -def html_example3(): - html = HtmlFile('example2.html') - HtmlElement(name='footer', self_closing=False, id='real-footer').render_to_string(html, content='Footer 2') - - def main(): parser = argparse.ArgumentParser(description='Command-line params') parser.add_argument('--language', @@ -90,7 +92,7 @@ def main(): elif args.language == 'Java': java_example() elif args.language == 'HTML': - html_example3() + html_example2() return 0 From ca8f8536f26d9b70c118db76aab6dc0909b4fcdb Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 23 Mar 2023 16:21:27 +0700 Subject: [PATCH 17/51] Java array generator --- src/code_generation/java/java_array.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/code_generation/java/java_array.py b/src/code_generation/java/java_array.py index a4e8c38..3a30d1e 100644 --- a/src/code_generation/java/java_array.py +++ b/src/code_generation/java/java_array.py @@ -56,12 +56,7 @@ def _render_static(self, java): java(f"{self.type}[] {self.name} = {{ {values_str} }};") def _render_dynamic(self, java): - if self.items is None or not self.items: - values_str = ', '.join(f'{self.type}()' for _ in range(self.array_size)) - else: - values_str = ', '.join(str(item) for item in self.items) - - java(f"{self.type}[] {self.name} = new {self.type}[{len(self.items)}]{{ {values_str} }};") + java(f"{self.type}[] {self.name} = new {self.type}[{self.array_size}];") def render_to_string(self, java): self._sanity_check() From fc90025fde99203641d12cdc85881359626b1a0d Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 23 Mar 2023 19:54:03 +0700 Subject: [PATCH 18/51] Java array generator --- setup.cfg | 2 +- src/code_generation/java/java_array.py | 40 ++++++++++++++++++++++---- src/example.py | 11 +++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4e67733..7f98b19 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = code_generation -version = 2.4.1 +version = 3.0.0 [options] packages = find: diff --git a/src/code_generation/java/java_array.py b/src/code_generation/java/java_array.py index 3a30d1e..03c182d 100644 --- a/src/code_generation/java/java_array.py +++ b/src/code_generation/java/java_array.py @@ -41,21 +41,51 @@ def _sanity_check(self): """ if not self.type: raise RuntimeError('Array type is not set') + if not isinstance(self.type, str): + raise RuntimeError(f'Array type is not a string, but {type(self.type)}') + if self.array_size is not None and not isinstance(self.array_size, int): + raise RuntimeError(f'Array size is not an integer, but {type(self.array_size)}') + if self.items is not None and not isinstance(self.items, list): + raise RuntimeError(f'Array items are not a list, but {type(self.items)}') if self.is_class_member and not self.name: raise RuntimeError('Class member array name is not set') - if self.array_size and self.dynamic: - raise RuntimeError('Array size is defined and array is dynamic') if self.array_size and self.items: raise RuntimeError('Array size is defined and array has items') + if self.array_size and not self.dynamic: + raise RuntimeError('Array size is defined but array is not dynamic') + + def values_str(self): + """ + String representation of array items + {1, 2, 3} + Can be used for multidimensional arrays as well + {{1, 2, 3}, {4, 5, 6}} + """ + if self.items is None or not self.items: + return '' + return f"{{ {', '.join(str(item) for item in self.items)} }}" + + def __str__(self): + """ + String representation of array + """ + return self.values_str() def _render_static(self, java): + """ + Render arrays without 'new' keyword + int[] anArray; + int[] arrayWithItems = {1, 2, 3}; + """ if self.items is None or not self.items: - values_str = ', '.join(f'{self.type}()' for _ in range(self.array_size)) + java(f"{self.type}[] {self.name};") else: - values_str = ', '.join(str(item) for item in self.items) - java(f"{self.type}[] {self.name} = {{ {values_str} }};") + java(f"{self.type}[] {self.name} = {self.values_str()};") def _render_dynamic(self, java): + """ + Render arrays with 'new' keyword + """ java(f"{self.type}[] {self.name} = new {self.type}[{self.array_size}];") def render_to_string(self, java): diff --git a/src/example.py b/src/example.py index 5362a2f..c23f4cb 100644 --- a/src/example.py +++ b/src/example.py @@ -45,12 +45,13 @@ def java_example(): with java.block('class Main', ';'): with java.block('public static void main(String[] args)'): java('System.out.println("Hello World!");') - JavaArray(name='my_array1', type='String', dynamic=True, items=['"a"', '"b"', '"c"'] - ).render_to_string(java) - JavaArray(name='my_array2', type='int', dynamic=False, items=['1', '2', '3'] - ).render_to_string(java) - JavaArray(name='my_array3', type='int', dynamic=False, array_size=3 + JavaArray(name='dynamicArray', type='String', dynamic=True, array_size=10 ).render_to_string(java) + array1 = JavaArray(name='arrayWithItems1', type='int', dynamic=False, items=['1', '2', '3']) + array2 = JavaArray(name='arrayWithItems2', type='int', dynamic=False, items=[4, 5, 6]) + array1.render_to_string(java) + array2.render_to_string(java) + JavaArray(name='multiArray', type='int', dynamic=False, items=[array1, array2]).render_to_string(java) def html_example(): From af8d278fc79e08d8c61f514617772ef46ff7518a Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Fri, 24 Mar 2023 20:21:55 +0700 Subject: [PATCH 19/51] Support non-selfclosing HtmlElement --- setup.cfg | 2 +- src/code_generation/core/code_style.py | 10 ++++++++-- src/code_generation/html/html_element.py | 3 ++- src/example.py | 10 ++++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7f98b19..4714384 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = code_generation -version = 3.0.0 +version = 3.0.2 [options] packages = find: diff --git a/src/code_generation/core/code_style.py b/src/code_generation/core/code_style.py index 50f4172..3f49a5f 100644 --- a/src/code_generation/core/code_style.py +++ b/src/code_generation/core/code_style.py @@ -79,8 +79,14 @@ def __init__(self, owner, element, *attrs, **kwattrs): with self.owner.last: pass self.element = element - attributes = "".join(f' {attr}' for attr in attrs) - attributes += "".join(f' {key}="{value}"' for key, value in kwattrs.items()) + attributes = "" + if "attributes" in kwattrs: + if isinstance(kwattrs["attributes"], dict): + attributes = "".join(f' {key}="{value}"' for key, value in kwattrs["attributes"].items()) + del kwattrs["attributes"] + else: + attributes = "".join(f' {attr}' for attr in attrs) + attributes += "".join(f' {key}="{value}"' for key, value in kwattrs.items()) self.attributes = attributes self.owner.last = self diff --git a/src/code_generation/html/html_element.py b/src/code_generation/html/html_element.py index 50f9dab..2060655 100644 --- a/src/code_generation/html/html_element.py +++ b/src/code_generation/html/html_element.py @@ -30,7 +30,8 @@ def render_to_string(self, html, content=None): """ if self.self_closing: html('<{0} {1}/>'.format(self.name, self._render_attributes())) - elif content is not None: + else: + content = content if content is not None else '' with html.block(element=self.name, **self.attributes): html(content) diff --git a/src/example.py b/src/example.py index c23f4cb..d944fe9 100644 --- a/src/example.py +++ b/src/example.py @@ -62,6 +62,16 @@ def html_example(): def html_example2(): + html = HtmlFile('example2.html') + with html.block('a', href='https://www.google.com'): + HtmlElement( + name='i', + self_closing=False, + attributes={'class': "fab fa-facebook-f", "target": "_blank"} + ).render_to_string(html) + + +def html_example3(): html = HtmlFile('example2.html') with html.block('html'): with html.block('head', lang='en'): From cc4485e8933557524ed378d486ffbafdc8afbfc0 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Sat, 25 Mar 2023 14:46:18 +0700 Subject: [PATCH 20/51] HTML file header --- RELEASE_NOTES.json | 11 +++++++++-- setup.cfg | 2 +- src/code_generation/html/html_file.py | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.json b/RELEASE_NOTES.json index 56eac4b..c231ded 100644 --- a/RELEASE_NOTES.json +++ b/RELEASE_NOTES.json @@ -3,9 +3,16 @@ "download_link": "https://{package_name_dash}-package.s3.amazonaws.com/server/{package_name}-{version}-py3-none-any.whl" }, "releases": { - "2.x.x": { + "3.x.x": { "release_notes": [ - "Create subdirectories for core, cpp and html generators" + "Change the structure of the project", + "HTML report generator", + "Java code generator" + ] + }, + "2.4.1": { + "release_notes": [ + "Create subdirectories for core, C++, Java and HTML generators" ] }, "2.3.0": { diff --git a/setup.cfg b/setup.cfg index 4714384..ccdc184 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = code_generation -version = 3.0.2 +version = 3.0.3 [options] packages = find: diff --git a/src/code_generation/html/html_file.py b/src/code_generation/html/html_file.py index 7ad1a8c..6cba827 100644 --- a/src/code_generation/html/html_file.py +++ b/src/code_generation/html/html_file.py @@ -13,6 +13,7 @@ def __init__(self, filename, writer=None): self.out = writer else: self.out = open(filename, "w") + self.write('') def close(self): """ From fc9d8cfa98b48c95902e04abc60dd7f9f1d8d8ae Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Sat, 8 Jul 2023 14:24:47 +0700 Subject: [PATCH 21/51] Implement full unit test set --- src/code_generation/cpp/enum_generator.py | 4 + .../cpp/{cpp_file.py => file_writer.py} | 0 src/code_generation/java/__init__.py | 2 +- src/code_generation/java/array_generator.py | 68 ++++++ src/code_generation/java/class_generator.py | 107 +++++++++ src/code_generation/java/enum_generator.py | 69 ++++++ .../java/{java_file.py => file_writer.py} | 0 .../java/function_generator.py | 82 +++++++ src/code_generation/java/java_array.py | 96 -------- src/code_generation/java/language_element.py | 2 +- .../java/variable_generator.py | 66 ++++++ src/example.py | 4 +- tests/cpp/test_cpp_array_writer.py | 70 ++++++ tests/cpp/test_cpp_class_writer.py | 214 ++++++++++++++++++ tests/cpp/test_cpp_enum_writer.py | 64 ++++++ tests/{ => cpp}/test_cpp_file.py | 12 +- tests/{ => cpp}/test_cpp_function_writer.py | 4 +- tests/{ => cpp}/test_cpp_variable_writer.py | 4 +- tests/{ => html}/test_html_writer.py | 0 tests/java/test_java_array_writer.py | 58 +++++ tests/java/test_java_class_writer.py | 64 ++++++ tests/java/test_java_enum_writer.py | 43 ++++ tests/java/test_java_file.py | 74 ++++++ tests/java/test_java_function_writer.py | 62 +++++ tests/java/test_java_variable_writer.py | 46 ++++ 25 files changed, 1105 insertions(+), 110 deletions(-) rename src/code_generation/cpp/{cpp_file.py => file_writer.py} (100%) create mode 100644 src/code_generation/java/array_generator.py create mode 100644 src/code_generation/java/class_generator.py create mode 100644 src/code_generation/java/enum_generator.py rename src/code_generation/java/{java_file.py => file_writer.py} (100%) create mode 100644 src/code_generation/java/function_generator.py delete mode 100644 src/code_generation/java/java_array.py create mode 100644 src/code_generation/java/variable_generator.py create mode 100644 tests/cpp/test_cpp_array_writer.py create mode 100644 tests/cpp/test_cpp_class_writer.py create mode 100644 tests/cpp/test_cpp_enum_writer.py rename tests/{ => cpp}/test_cpp_file.py (95%) rename tests/{ => cpp}/test_cpp_function_writer.py (95%) rename tests/{ => cpp}/test_cpp_variable_writer.py (96%) rename tests/{ => html}/test_html_writer.py (100%) create mode 100644 tests/java/test_java_array_writer.py create mode 100644 tests/java/test_java_class_writer.py create mode 100644 tests/java/test_java_enum_writer.py create mode 100644 tests/java/test_java_file.py create mode 100644 tests/java/test_java_function_writer.py create mode 100644 tests/java/test_java_variable_writer.py diff --git a/src/code_generation/cpp/enum_generator.py b/src/code_generation/cpp/enum_generator.py index 401d74c..89a4767 100644 --- a/src/code_generation/cpp/enum_generator.py +++ b/src/code_generation/cpp/enum_generator.py @@ -1,5 +1,6 @@ from code_generation.cpp.language_element import CppLanguageElement + class CppEnum(CppLanguageElement): """ The Python class that generates string representation for C++ enum @@ -29,6 +30,9 @@ class CppEnum(CppLanguageElement): 'add_counter'} | CppLanguageElement.availablePropertiesNames def __init__(self, **properties): + """ + :param properties: + """ self.enum_class = False # check properties input_property_names = set(properties.keys()) diff --git a/src/code_generation/cpp/cpp_file.py b/src/code_generation/cpp/file_writer.py similarity index 100% rename from src/code_generation/cpp/cpp_file.py rename to src/code_generation/cpp/file_writer.py diff --git a/src/code_generation/java/__init__.py b/src/code_generation/java/__init__.py index 0f9a258..122925e 100644 --- a/src/code_generation/java/__init__.py +++ b/src/code_generation/java/__init__.py @@ -1,2 +1,2 @@ -from . import java_file +from . import file_writer from . import language_element diff --git a/src/code_generation/java/array_generator.py b/src/code_generation/java/array_generator.py new file mode 100644 index 0000000..08e6575 --- /dev/null +++ b/src/code_generation/java/array_generator.py @@ -0,0 +1,68 @@ +from code_generation.java.language_element import JavaLanguageElement + +__doc__ = """""" + + +class JavaArray(JavaLanguageElement): + + available_properties_names = { + 'name', + 'values', + 'is_class_member', + 'add_counter' + } | JavaLanguageElement.available_properties_names + + def __init__(self, **properties): + """ + :param properties: + """ + self.name = '' + self.values = [] + self.is_class_member = False + self.add_counter = True + input_property_names = set(properties.keys()) + self.check_input_properties_names(input_property_names) + super(JavaArray, self).__init__(properties) + self.init_class_properties(current_class_properties=self.available_properties_names, + input_properties_dict=properties) + + def _sanity_check(self): + if not self.name: + raise RuntimeError('Enum name is not set') + if not isinstance(self.name, str): + raise RuntimeError(f'Enum name is not a string, but {type(self.name)}') + if self.values is not None and not isinstance(self.values, list): + raise RuntimeError(f'Enum values are not a list, but {type(self.values)}') + + def values_str(self): + if self.values is None or not self.values: + return '' + return ', '.join(str(value) for value in self.values) + + def __str__(self): + return self.values_str() + + def add_item(self, item): + """ + Add a single item to the enum + :param item: The item to add + """ + self.values.append(item) + + def add_items(self, items): + """ + Add multiple items to the enum + :param items: The items to add as a list + """ + self.values.extend(items) + + def render_to_string(self, java): + self._sanity_check() + with java.block(f'enum {self.name}', postfix=';'): + counter = 0 + for value in self.values: + java(f'{value} = {counter},') + counter += 1 + if self.add_counter: + last_element = f'{self.name}Count = {counter}' + java(last_element) diff --git a/src/code_generation/java/class_generator.py b/src/code_generation/java/class_generator.py new file mode 100644 index 0000000..625ac26 --- /dev/null +++ b/src/code_generation/java/class_generator.py @@ -0,0 +1,107 @@ +from code_generation.java.language_element import JavaLanguageElement + + +__doc__ = """""" + + +class JavaClass(JavaLanguageElement): + """ + The Python class that generates string representation for Java class. + Usually contains a number of child elements - internal classes, enums, methods and variables. + Available properties: + documentation - string, '/** Example Javadoc */' + parent_class - string, the name of the parent class (if any) + + Example of usage: + + # Python code + java_class = JavaClass(name='MyClass') + java_class.add_variable(JavaVariable(name='myVariable', type='int', is_static=True, is_final=True, initialization_value='10')) + java_class.add_method(JavaFunction(name='getVar', return_type='int', is_static=True, implementation='return myVariable;')) + + # Generated Java code + public class MyClass { + static final int myVariable = 10; + + public static int getVar() { + return myVariable; + } + } + """ + + available_properties_names = { + 'documentation', + 'parent_class' + } | JavaLanguageElement.available_properties_names + + def __init__(self, **properties): + self.documentation = None + self.parent_class = None + + input_property_names = set(properties.keys()) + self.check_input_properties_names(input_property_names) + super().__init__(properties) + self.init_class_properties( + current_class_properties=self.available_properties_names, + input_properties_dict=properties + ) + + self.internal_class_elements = [] + self.internal_variable_elements = [] + self.internal_method_elements = [] + + def _parent_class(self): + return self.parent_class if self.parent_class else "" + + def render_to_string(self, java): + if self.documentation: + java(f"/**\n{self.documentation}\n*/") + with java.block(f"public {self._render_class_type()} {self.name} {self.inherits()}"): + self.class_interface(java) + self.private_class_members(java) + + def _render_class_type(self): + return "class" + + def inherits(self): + return f"extends {self._parent_class()}" if self.parent_class else "" + + def class_interface(self, java): + self._render_internal_classes_declaration(java) + self._render_enum_section(java) + self._render_methods_declaration(java) + + def _render_internal_classes_declaration(self, java): + for class_item in self.internal_class_elements: + class_item.declaration().render_to_string(java) + java.newline() + + def _render_enum_section(self, java): + for enum_item in self.internal_enum_elements: + enum_item.render_to_string(java) + java.newline() + + def _render_variables_declaration(self, java): + for var_item in self.internal_variable_elements: + var_item.render_to_string(java) + java.newline() + + def _render_methods_declaration(self, java): + for method_item in self.internal_method_elements: + method_item.render_to_string_declaration(java) + java.newline() + + def private_class_members(self, java): + self._render_variables_declaration(java) + + def add_variable(self, java_variable): + java_variable.ref_to_parent = self + self.internal_variable_elements.append(java_variable) + + def add_method(self, java_method): + java_method.ref_to_parent = self + self.internal_method_elements.append(java_method) + + def add_internal_class(self, java_class): + java_class.ref_to_parent = self + self.internal_class_elements.append(java_class) diff --git a/src/code_generation/java/enum_generator.py b/src/code_generation/java/enum_generator.py new file mode 100644 index 0000000..db54794 --- /dev/null +++ b/src/code_generation/java/enum_generator.py @@ -0,0 +1,69 @@ +from code_generation.core.code_file import CodeFile +from code_generation.java.language_element import JavaLanguageElement + + +__doc__ = """""" + + +class JavaEnum(JavaLanguageElement): + + available_properties_names = { + 'name', + 'values', + 'is_class_member' + } | JavaLanguageElement.available_properties_names + + def __init__(self, **properties): + """ + :param properties: + """ + self.name = '' + self.values = [] + self.is_class_member = False + input_property_names = set(properties.keys()) + self.check_input_properties_names(input_property_names) + super(JavaEnum, self).__init__(properties) + self.init_class_properties(current_class_properties=self.available_properties_names, + input_properties_dict=properties) + + def _sanity_check(self): + """ + Check if all required properties are set + """ + if not self.name: + raise RuntimeError('Enum name is not set') + if not isinstance(self.name, str): + raise RuntimeError(f'Enum name is not a string, but {type(self.name)}') + if self.values is not None and not isinstance(self.values, list): + raise RuntimeError(f'Enum values are not a list, but {type(self.values)}') + if self.is_class_member and not self.name: + raise RuntimeError('Class member enum name is not set') + + def values_str(self): + """ + String representation of enum values + Can be used for multiple values as well + VALUE1, VALUE2, VALUE3 + """ + if self.values is None or not self.values: + return '' + return ', '.join(str(value) for value in self.values) + + def __str__(self): + """ + String representation of enum + """ + return self.values_str() + + def _render_static(self, java): + """ + Render static enums + """ + if self.values is None or not self.values: + java(f"enum {self.name} {{}}") + else: + java(f"enum {self.name} {{ {self.values_str()} }}") + + def render_to_string(self, java): + self._sanity_check() + self._render_static(java) diff --git a/src/code_generation/java/java_file.py b/src/code_generation/java/file_writer.py similarity index 100% rename from src/code_generation/java/java_file.py rename to src/code_generation/java/file_writer.py diff --git a/src/code_generation/java/function_generator.py b/src/code_generation/java/function_generator.py new file mode 100644 index 0000000..fb4ae18 --- /dev/null +++ b/src/code_generation/java/function_generator.py @@ -0,0 +1,82 @@ +from code_generation.java.language_element import JavaLanguageElement + + +__doc__ = """""" + + +class JavaFunction(JavaLanguageElement): + + available_properties_names = { + 'name', + 'ret_type', + 'arguments', + 'is_static', + 'is_final', + 'is_abstract', + 'is_synchronized', + 'is_native', + 'is_strictfp', + 'documentation', + 'implementation_handle' + } | JavaLanguageElement.available_properties_names + + def __init__(self, **properties): + self.name = '' + self.ret_type = '' + self.arguments = [] + self.is_static = False + self.is_final = False + self.is_abstract = False + self.is_synchronized = False + self.is_native = False + self.is_strictfp = False + self.documentation = '' + self.implementation_handle = None + + input_property_names = set(properties.keys()) + self.check_input_properties_names(input_property_names) + super(JavaFunction, self).__init__(properties) + self.init_class_properties(current_class_properties=self.available_properties_names, + input_properties_dict=properties) + + def _sanity_check(self): + if not self.name: + raise RuntimeError('Method name is not set') + if not isinstance(self.name, str): + raise RuntimeError(f'Method name is not a string, but {type(self.name)}') + if not isinstance(self.ret_type, str): + raise RuntimeError(f'Return type is not a string, but {type(self.ret_type)}') + if self.arguments is not None and not isinstance(self.arguments, list): + raise RuntimeError(f'Arguments are not a list, but {type(self.arguments)}') + + def args_str(self): + if self.arguments is None or not self.arguments: + return '' + return ', '.join(str(arg) for arg in self.arguments) + + def add_argument(self, argument): + """ + Add an argument to the method + :param argument: The argument to add + """ + self.arguments.append(argument) + + def render_to_string(self, java): + self._sanity_check() + if self.documentation: + java(f"/**\n{self.documentation}\n*/") + if self.is_abstract: + raise RuntimeError("Cannot generate implementation for abstract method.") + if self.is_static: + java('static', end=' ') + if self.is_final: + java('final', end=' ') + if self.is_synchronized: + java('synchronized', end=' ') + if self.is_native: + java('native', end=' ') + if self.is_strictfp: + java('strictfp', end=' ') + with java.block(f'{self.ret_type} {self.name}({self.args_str()})'): + if self.implementation_handle: + self.implementation_handle(java) diff --git a/src/code_generation/java/java_array.py b/src/code_generation/java/java_array.py deleted file mode 100644 index 03c182d..0000000 --- a/src/code_generation/java/java_array.py +++ /dev/null @@ -1,96 +0,0 @@ -from code_generation.core.code_file import CodeFile - -__doc__ = """""" - - -from code_generation.java.language_element import JavaLanguageElement - - -class JavaArray(JavaLanguageElement): - - available_properties_names = { - 'type', - 'array_size', - 'is_const', - 'const', - 'is_class_member', - 'is_dynamic', - 'dynamic', - 'items' - } | JavaLanguageElement.available_properties_names - - def __init__(self, **properties): - """ - :param properties: - """ - self.type = '' - self.is_const = False - self.is_class_member = False - self.dynamic = False - self.array_size = 0 - self.items = [] - input_property_names = set(properties.keys()) - self.check_input_properties_names(input_property_names) - super(JavaArray, self).__init__(properties) - self.init_class_properties(current_class_properties=self.available_properties_names, - input_properties_dict=properties) - - def _sanity_check(self): - """ - Check if all required properties are set - """ - if not self.type: - raise RuntimeError('Array type is not set') - if not isinstance(self.type, str): - raise RuntimeError(f'Array type is not a string, but {type(self.type)}') - if self.array_size is not None and not isinstance(self.array_size, int): - raise RuntimeError(f'Array size is not an integer, but {type(self.array_size)}') - if self.items is not None and not isinstance(self.items, list): - raise RuntimeError(f'Array items are not a list, but {type(self.items)}') - if self.is_class_member and not self.name: - raise RuntimeError('Class member array name is not set') - if self.array_size and self.items: - raise RuntimeError('Array size is defined and array has items') - if self.array_size and not self.dynamic: - raise RuntimeError('Array size is defined but array is not dynamic') - - def values_str(self): - """ - String representation of array items - {1, 2, 3} - Can be used for multidimensional arrays as well - {{1, 2, 3}, {4, 5, 6}} - """ - if self.items is None or not self.items: - return '' - return f"{{ {', '.join(str(item) for item in self.items)} }}" - - def __str__(self): - """ - String representation of array - """ - return self.values_str() - - def _render_static(self, java): - """ - Render arrays without 'new' keyword - int[] anArray; - int[] arrayWithItems = {1, 2, 3}; - """ - if self.items is None or not self.items: - java(f"{self.type}[] {self.name};") - else: - java(f"{self.type}[] {self.name} = {self.values_str()};") - - def _render_dynamic(self, java): - """ - Render arrays with 'new' keyword - """ - java(f"{self.type}[] {self.name} = new {self.type}[{self.array_size}];") - - def render_to_string(self, java): - self._sanity_check() - if self.dynamic: - self._render_dynamic(java) - else: - self._render_static(java) diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py index 89b5f56..1ce3927 100644 --- a/src/code_generation/java/language_element.py +++ b/src/code_generation/java/language_element.py @@ -12,7 +12,7 @@ is_static = True, is_final = True, initialization_value = 10)) -java_class.add_method(JavaMethod(name = 'main', +java_class.add_method(JavaFunction(name = 'main', return_type = 'void', is_static = True, arguments = [JavaArgument(name = 'args', type = 'String[]')])) diff --git a/src/code_generation/java/variable_generator.py b/src/code_generation/java/variable_generator.py new file mode 100644 index 0000000..1498025 --- /dev/null +++ b/src/code_generation/java/variable_generator.py @@ -0,0 +1,66 @@ +from code_generation.java.language_element import JavaLanguageElement + + +__doc__ = """""" + + +class JavaVariable(JavaLanguageElement): + """ + The Python class that generates string representation for Java variable + For example: + class MyClass { + int var1; + double var2; + ... + } + + Available properties: + type - string, variable type + is_static - boolean, 'static' prefix + is_final - boolean, 'final' prefix + initialization_value - string, initialization value to be assigned + documentation - string, '/** Example Javadoc */' + """ + + available_properties_names = { + 'type', + 'is_static', + 'is_final', + 'initialization_value', + 'documentation' + } | JavaLanguageElement.available_properties_names + + def __init__(self, **properties): + self.type = '' + self.is_static = False + self.is_final = False + self.initialization_value = '' + self.documentation = '' + + input_property_names = set(properties.keys()) + self.check_input_properties_names(input_property_names) + super().__init__(properties) + self.init_class_properties( + current_class_properties=self.available_properties_names, + input_properties_dict=properties + ) + + def render_to_string(self, java): + if self.documentation: + java(f"/**\n{self.documentation}\n*/") + java(f"{self._render_static()}{self._render_final()}{self.type} {self.name};") + + def render_to_string_declaration(self, java): + if self.documentation: + java(f"/**\n{self.documentation}\n*/") + java(f"{self._render_static()}{self._render_final()}{self.type} {self.name};") + + def render_to_string_implementation(self, java): + if self.is_static: + java(f"{self._render_static()}final {self.type} {self.name} = {self.initialization_value};") + + def _render_static(self): + return "static " if self.is_static else "" + + def _render_final(self): + return "final " if self.is_final else "" diff --git a/src/example.py b/src/example.py index d944fe9..b019011 100644 --- a/src/example.py +++ b/src/example.py @@ -3,8 +3,8 @@ from code_generation.core.code_file import CodeFile from code_generation.cpp.variable_generator import CppVariable -from code_generation.java.java_file import JavaFile -from code_generation.java.java_array import JavaArray +from code_generation.java.file_writer import JavaFile +from code_generation.java.array_generator import JavaArray from code_generation.html.html_file import HtmlFile from code_generation.html.html_element import HtmlElement diff --git a/tests/cpp/test_cpp_array_writer.py b/tests/cpp/test_cpp_array_writer.py new file mode 100644 index 0000000..bb0abc6 --- /dev/null +++ b/tests/cpp/test_cpp_array_writer.py @@ -0,0 +1,70 @@ +import unittest +import io + +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.array_generator import CppArray + +__doc__ = """Unit tests for C++ code generator +""" + + +class TestCppArrayStringIo(unittest.TestCase): + """ + Test C++ array generation by writing to StringIO + """ + + def test_cpp_array(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + arr = CppArray(name="my_array", type="int", array_size=5) + arr.add_array_items(["1", "2", "0"]) + arr.render_to_string(cpp) + expected_output = "int my_array[5] = {1, 2, 0};" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_array_with_newline_align(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + arr = CppArray(name="my_array", type="int", array_size=5, newline_align=True) + arr.add_array_items(["1", "2", "0"]) + arr.render_to_string(cpp) + expected_output = "int my_array[5] = {\n 1,\n 2,\n 0\n};" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_array_declaration(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + arr = CppArray(name="my_class_member_array", type="int", array_size=None, is_class_member=True) + arr.render_to_string_declaration(cpp) + expected_output = "int my_class_member_array[];" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_array_implementation(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + arr = CppArray(name="m_my_static_array", type="int", array_size=None, + is_class_member=True, is_static=True) + arr.add_array_items(["1", "2", "0"]) + arr.render_to_string_implementation(cpp) + expected_output = "static int m_my_static_array[] = {1, 2, 0};" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_missing_type(self): + arr = CppArray(name="my_array", array_size=5) + self.assertRaises(RuntimeError, arr.render_to_string, None) + + def test_missing_name(self): + arr = CppArray(type="int", array_size=5) + self.assertRaises(RuntimeError, arr.render_to_string, None) + + def test_class_member_missing_name(self): + arr = CppArray(type="int", is_class_member=True) + self.assertRaises(RuntimeError, arr.render_to_string_declaration, None) + + def test_class_member_static_without_name(self): + arr = CppArray(type="int", is_class_member=True, is_static=True) + self.assertRaises(RuntimeError, arr.render_to_string_implementation, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/cpp/test_cpp_class_writer.py b/tests/cpp/test_cpp_class_writer.py new file mode 100644 index 0000000..b0033d1 --- /dev/null +++ b/tests/cpp/test_cpp_class_writer.py @@ -0,0 +1,214 @@ +import unittest +import io +from textwrap import dedent + +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.enum_generator import CppEnum +from code_generation.cpp.array_generator import CppArray +from code_generation.cpp.variable_generator import CppVariable +from code_generation.cpp.class_generator import CppClass + +__doc__ = """Unit tests for C++ code generator +""" + + +class TestCppClassStringIo(unittest.TestCase): + """ + Test C++ class generation by writing to StringIO + """ + + def test_cpp_class(self): + writer = io.StringIO() + cpp_file = CppFile(None, writer=writer) + + # Create a CppClass instance + cpp_class = CppClass(name="MyClass", is_struct=True) + + # Add a CppVariable to the class + cpp_class.add_variable( + CppVariable( + name="m_var", + type="size_t", + is_static=True, + is_const=True, + initialization_value="255" + ) + ) + + # Define a function body for the CppMethod + def handle(cpp): + cpp('return m_var;') + + # Add a CppMethod to the class + cpp_class.add_method( + CppClass.CppMethod( + name="GetVar", + ret_type="size_t", + is_static=True, + implementation_handle=handle + ) + ) + + # Render the class to string + cpp_class.render_to_string(cpp_file) + + # Define the expected output + expected_output = dedent("""\ + struct MyClass + { + static const size_t m_var; + static size_t GetVar(); + };""") + + # Assert the output matches the expected output + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_class_with_inheritance(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + + # Create a parent class + parent_class = CppClass(name="ParentClass") + + # Create a child class with inheritance + child_class = CppClass(name="ChildClass", parent_class="ParentClass") + + # Add a CppVariable to the parent class + parent_class.add_variable( + CppVariable( + name="m_var", + type="int", + is_static=True, + is_const=True, + initialization_value="42" + ) + ) + + # Add a CppMethod to the parent class + parent_class.add_method( + CppClass.CppMethod( + name="GetVar", + ret_type="int", + is_static=True, + implementation_handle=lambda cpp_file: cpp_file("return m_var;") + ) + ) + + # Render the parent class to string + parent_class.render_to_string(cpp) + + # Render the child class to string + child_class.render_to_string(cpp) + + # Define the expected output + expected_output = dedent("""\ + class ParentClass + { + static const int m_var; + static int GetVar(); + }; + + class ChildClass : public ParentClass + { + };""") + + # Assert the output matches the expected output + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_class_with_nested_classes(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + + # Create a CppClass instance + cpp_class = CppClass(name="MyClass") + + # Create a nested class + nested_class = CppClass(name="NestedClass") + + # Add the nested class to the main class + cpp_class.add_internal_class(nested_class) + + # Render the main class to string + cpp_class.render_to_string(cpp) + + # Define the expected output + expected_output = dedent("""\ + class MyClass + { + class NestedClass + { + }; + };""") + + # Assert the output matches the expected output + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_class_with_enum(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + + # Create a CppClass instance + cpp_class = CppClass(name="MyClass") + + # Create a CppEnum instance + cpp_enum = CppEnum(name="Items") + + # Add enum items + cpp_enum.add_items(["Item1", "Item2", "Item3"]) + + # Add the enum to the class + cpp_class.add_enum(cpp_enum) + + # Render the class to string + cpp_class.render_to_string(cpp) + + # Define the expected output + expected_output = dedent("""\ + class MyClass + { + enum Items + { + Item1, + Item2, + Item3 + }; + };""") + + # Assert the output matches the expected output + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_class_with_array(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + + # Create a CppClass instance + cpp_class = CppClass(name="MyClass") + + # Create a CppArray instance + cpp_array = CppArray(name="Array") + cpp_array.add_array_items(["Item1", "Item2", "Item3"]) + + # Add the array to the class + cpp_class.add_array(cpp_array) + + # Render the class to string + cpp_class.render_to_string(cpp) + + # Define the expected output + expected_output = dedent("""\ + class MyClass + { + const char* Array[3] = + { + Item1, + Item2, + Item3 + }; + };""") + + # Assert the output matches the expected output + self.assertEqual(expected_output, writer.getvalue().strip()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/cpp/test_cpp_enum_writer.py b/tests/cpp/test_cpp_enum_writer.py new file mode 100644 index 0000000..00d2e11 --- /dev/null +++ b/tests/cpp/test_cpp_enum_writer.py @@ -0,0 +1,64 @@ +import unittest +import io +from textwrap import dedent + +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.enum_generator import CppEnum + +__doc__ = """Unit tests for C++ code generator +""" + + +class TestCppEnumStringIo(unittest.TestCase): + """ + Test C++ enum generation by writing to StringIO + """ + + def test_cpp_enum(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + enum = CppEnum(name="Items") + enum.add_items(["Chair", "Table", "Shelve"]) + enum.render_to_string(cpp) + expected_output = dedent("""\ + enum Items + { + Chair, + Table, + Shelve + };""") + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_enum_with_prefix(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + enum = CppEnum(name="Items", prefix="Prefix") + enum.add_items(["A", "B", "C"]) + enum.render_to_string(cpp) + expected_output = dedent("""\ + enum Items + { + PrefixA, + PrefixB, + PrefixC + };""") + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_cpp_enum_class(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + enum = CppEnum(name="Items", enum_class=True) + enum.add_items(["A", "B", "C"]) + enum.render_to_string(cpp) + expected_output = dedent("""\ + enum class Items + { + A, + B, + C + };""") + self.assertEqual(expected_output, writer.getvalue().strip()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_cpp_file.py b/tests/cpp/test_cpp_file.py similarity index 95% rename from tests/test_cpp_file.py rename to tests/cpp/test_cpp_file.py index f42afcd..27fc3e0 100644 --- a/tests/test_cpp_file.py +++ b/tests/cpp/test_cpp_file.py @@ -2,12 +2,12 @@ import unittest import filecmp -from code_generation.core.code_file import CppFile -from code_generation.cpp.cpp_variable import CppVariable -from code_generation.cpp.cpp_enum import CppEnum -from code_generation.cpp.cpp_array import CppArray -from code_generation.cpp.cpp_function import CppFunction -from code_generation.cpp.cpp_class import CppClass +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.variable_generator import CppVariable +from code_generation.cpp.enum_generator import CppEnum +from code_generation.cpp.array_generator import CppArray +from code_generation.cpp.function_generator import CppFunction +from code_generation.cpp.class_generator import CppClass __doc__ = """ Unit tests for C++ code generator diff --git a/tests/test_cpp_function_writer.py b/tests/cpp/test_cpp_function_writer.py similarity index 95% rename from tests/test_cpp_function_writer.py rename to tests/cpp/test_cpp_function_writer.py index 9110a28..a7383b2 100644 --- a/tests/test_cpp_function_writer.py +++ b/tests/cpp/test_cpp_function_writer.py @@ -2,8 +2,8 @@ import io from textwrap import dedent -from code_generation.core.code_file import CppFile -from code_generation.cpp.cpp_function import CppFunction +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.function_generator import CppFunction __doc__ = """ Unit tests for C++ code generator diff --git a/tests/test_cpp_variable_writer.py b/tests/cpp/test_cpp_variable_writer.py similarity index 96% rename from tests/test_cpp_variable_writer.py rename to tests/cpp/test_cpp_variable_writer.py index ff1403c..b6115e0 100644 --- a/tests/test_cpp_variable_writer.py +++ b/tests/cpp/test_cpp_variable_writer.py @@ -1,8 +1,8 @@ import unittest import io -from code_generation.core.code_file import CppFile -from code_generation.cpp.cpp_variable import CppVariable +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.variable_generator import CppVariable __doc__ = """Unit tests for C++ code generator """ diff --git a/tests/test_html_writer.py b/tests/html/test_html_writer.py similarity index 100% rename from tests/test_html_writer.py rename to tests/html/test_html_writer.py diff --git a/tests/java/test_java_array_writer.py b/tests/java/test_java_array_writer.py new file mode 100644 index 0000000..92dbd37 --- /dev/null +++ b/tests/java/test_java_array_writer.py @@ -0,0 +1,58 @@ +import unittest +import io + +from code_generation.java.file_writer import JavaFile +from code_generation.java.array_generator import JavaArray + + +class TestJavaArrayStringIo(unittest.TestCase): + """ + Test Java array generation by writing to StringIO + """ + + def test_java_array(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + arr = JavaArray(name="myArray", values=["1", "2", "0"]) + arr.render_to_string(java) + expected_output = "int[] myArray = {1, 2, 0};" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_java_array_with_empty_values(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + arr = JavaArray(name="emptyArray", values=[]) + arr.render_to_string(java) + expected_output = "int[] emptyArray = new int[0];" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_java_array_add_item(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + arr = JavaArray(name="myArray") + arr.add_item("item1") + arr.add_item("item2") + arr.render_to_string(java) + expected_output = "String[] myArray = {\"item1\", \"item2\"};" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_java_array_add_items(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + arr = JavaArray(name="myArray") + arr.add_items(["item1", "item2", "item3"]) + arr.render_to_string(java) + expected_output = "String[] myArray = {\"item1\", \"item2\", \"item3\"};" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_missing_name(self): + arr = JavaArray(values=["1", "2", "3"]) + self.assertRaises(RuntimeError, arr.render_to_string, None) + + def test_invalid_values_type(self): + arr = JavaArray(name="myArray", values="invalid") + self.assertRaises(RuntimeError, arr.render_to_string, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/java/test_java_class_writer.py b/tests/java/test_java_class_writer.py new file mode 100644 index 0000000..2124d0c --- /dev/null +++ b/tests/java/test_java_class_writer.py @@ -0,0 +1,64 @@ +import unittest +import io +from textwrap import dedent + +from code_generation.java.file_writer import JavaFile +from code_generation.java.class_generator import JavaClass +from code_generation.java.variable_generator import JavaVariable +from code_generation.java.function_generator import JavaFunction + + +class TestJavaClassStringIo(unittest.TestCase): + """ + Test Java class generation by writing to StringIO + """ + + def test_java_class(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + my_class = JavaClass(name="MyClass") + var1 = JavaVariable(name="myVariable", type="int", value=10) + method1 = JavaFunction(name="getVar", return_type="int", implementation="return myVariable;") + my_class.add_variable(var1) + my_class.add_method(method1) + my_class.render_to_string(java) + expected_output = dedent("""\ + public class MyClass + { + int myVariable = 10; + int getVar() + { + return myVariable; + } + } + """) + self.assertEqual(writer.getvalue(), expected_output) + + def test_java_class_with_parent_class(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + my_class = JavaClass(name="MyClass", parent_class="ParentClass") + my_class.render_to_string(java) + expected_output = "public class MyClass extends ParentClass {" + self.assertTrue(writer.getvalue().strip().startswith(expected_output)) + + def test_java_class_with_documentation(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + my_class = JavaClass(name="MyClass", documentation="/** Example Javadoc */") + my_class.render_to_string(java) + expected_output = dedent("""\ + /** + * Example Javadoc + */ + public class MyClass + {""") + self.assertTrue(writer.getvalue().strip().startswith(expected_output)) + + def test_missing_name(self): + my_class = JavaClass() + self.assertRaises(RuntimeError, my_class.render_to_string, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/java/test_java_enum_writer.py b/tests/java/test_java_enum_writer.py new file mode 100644 index 0000000..d1e0aa6 --- /dev/null +++ b/tests/java/test_java_enum_writer.py @@ -0,0 +1,43 @@ +import unittest +import io + +from code_generation.java.file_writer import JavaFile +from code_generation.java.enum_generator import JavaEnum + + +class TestJavaEnumStringIo(unittest.TestCase): + """ + Test Java enum generation by writing to StringIO + """ + + def test_java_enum(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + enum = JavaEnum(name="Color", values=["RED", "GREEN", "BLUE"]) + enum.render_to_string(java) + expected_output = "enum Color {\n RED,\n GREEN,\n BLUE\n}" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_render_to_string_declaration(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + enum = JavaEnum(name="Color", values=["RED", "GREEN", "BLUE"]) + enum.render_to_string(java) + expected_output = "enum Color;" + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_missing_name(self): + enum = JavaEnum(name=None, values=["RED", "GREEN", "BLUE"]) + self.assertRaises(RuntimeError, enum.render_to_string, None) + + def test_non_string_name(self): + enum = JavaEnum(name=123, values=["RED", "GREEN", "BLUE"]) + self.assertRaises(RuntimeError, enum.render_to_string, None) + + def test_non_list_values(self): + enum = JavaEnum(name="Color", values="RED, GREEN, BLUE") + self.assertRaises(RuntimeError, enum.render_to_string, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/java/test_java_file.py b/tests/java/test_java_file.py new file mode 100644 index 0000000..5f6654f --- /dev/null +++ b/tests/java/test_java_file.py @@ -0,0 +1,74 @@ +import unittest +import filecmp +import os + +from code_generation.java.file_writer import JavaFile +from code_generation.java.class_generator import JavaClass +from code_generation.java.variable_generator import JavaVariable +from code_generation.java.function_generator import JavaFunction + + +class TestJavaFileIo(unittest.TestCase): + """ + Test Java code generation by writing to file + """ + + def test_java_class(self): + """ + Test Java class generation + """ + java_file = JavaFile('MyClass.java') + my_class = JavaClass(name='MyClass') + my_class.add_variable(JavaVariable(name='myVariable', + type='int', + is_static=True, + is_final=True, + initialization_value='10')) + my_class.add_method(JavaFunction(name='getVar', + return_type='int', + is_static=True, + implementation='return myVariable;')) + my_class.render_to_string(java_file) + self.assertTrue(filecmp.cmpfiles('.', 'tests', 'MyClass.java')) + if os.path.exists('MyClass.java'): + os.remove('MyClass.java') + + def test_java_interface(self): + """ + Test Java interface generation + """ + java_file = JavaFile('MyInterface.java') + my_interface = JavaClass(name='MyInterface') + my_interface.add_variable(JavaVariable(name='myVariable', type='int')) + my_interface.add_method(JavaFunction(name='getVar', return_type='int')) + my_interface.render_to_string(java_file) + self.assertTrue(filecmp.cmpfiles('.', 'tests', 'MyInterface.java')) + if os.path.exists('MyInterface.java'): + os.remove('MyInterface.java') + + def test_java_enum(self): + """ + Test Java enum generation + """ + java_file = JavaFile('MyEnum.java') + my_enum = JavaClass(name='MyEnum') + my_enum.add_variable(JavaVariable(name='ITEM1', type='int', initialization_value='1')) + my_enum.add_variable(JavaVariable(name='ITEM2', type='int', initialization_value='2')) + my_enum.add_method(JavaFunction(name='getItem', return_type='int', implementation='return ITEM1;')) + my_enum.render_to_string(java_file) + self.assertTrue(filecmp.cmpfiles('.', 'tests', 'MyEnum.java')) + if os.path.exists('MyEnum.java'): + os.remove('MyEnum.java') + + def test_missing_class_name(self): + """ + Test case for missing class name + """ + java_file = JavaFile('Missing.java') + my_class = JavaClass() + my_class.render_to_string(java_file) + self.assertRaises(RuntimeError, my_class.render_to_string, java_file) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/java/test_java_function_writer.py b/tests/java/test_java_function_writer.py new file mode 100644 index 0000000..c9ccf7e --- /dev/null +++ b/tests/java/test_java_function_writer.py @@ -0,0 +1,62 @@ +import unittest +import io +from textwrap import dedent + +from code_generation.java.file_writer import JavaFile +from code_generation.java.function_generator import JavaFunction + + +class TestJavaFunctionStringIo(unittest.TestCase): + """ + Test Java method generation by writing to StringIO + """ + + def test_java_method(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + method = JavaFunction(name="calculateSum", + return_type="int", + implementation_handle=lambda: "return a + b;") + method.add_argument("int a") + method.add_argument("int b") + method.render_to_string(java) + expected_output = dedent("""\ + public int calculateSum(int a, int b) { + // Method implementation + return a + b; + }""") + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_render_to_string_declaration(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + method = JavaFunction(name="calculateSum", + return_type="int", + implementation_handle=lambda: "return a + b;") + method.add_argument("int a") + method.add_argument("int b") + method.render_to_string(java) + expected_output = dedent("""\ + public int calculateSum(int a, int b);""") + self.assertEqual(expected_output, writer.getvalue().strip()) + + def test_missing_name(self): + method = JavaFunction(name=None, return_type="int") + self.assertRaises(RuntimeError, method.render_to_string, None) + + def test_non_string_name(self): + method = JavaFunction(name=123, return_type="int") + self.assertRaises(RuntimeError, method.render_to_string, None) + + def test_non_string_return_type(self): + method = JavaFunction(name="calculateSum", return_type=123) + self.assertRaises(RuntimeError, method.render_to_string, None) + + def test_non_list_arguments(self): + method = JavaFunction(name="calculateSum", return_type="int") + method.arguments = "int a, int b" + self.assertRaises(RuntimeError, method.render_to_string, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/java/test_java_variable_writer.py b/tests/java/test_java_variable_writer.py new file mode 100644 index 0000000..8a5158e --- /dev/null +++ b/tests/java/test_java_variable_writer.py @@ -0,0 +1,46 @@ +import unittest +import io + +from code_generation.java.file_writer import JavaFile +from code_generation.java.variable_generator import JavaVariable + + +class TestJavaVariableStringIo(unittest.TestCase): + """ + Test Java variable generation by writing to StringIO + """ + + def test_java_var(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + variable = JavaVariable(name="var1", + type="String", + is_class_member=False, + is_static=False, + is_final=True, + initialization_value='"Hello"') + variable.render_to_string(java) + self.assertEqual("final String var1 = \"Hello\";", writer.getvalue().strip()) + + def test_is_static_final_raises(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + variable = JavaVariable(name="var1", type="String", is_static=True, is_final=True) + self.assertRaises(ValueError, variable.render_to_string, java) + + def test_is_static_render_to_string(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + variable = JavaVariable(name="var1", type="String", is_static=True) + variable.render_to_string(java) + self.assertEqual("static String var1;", writer.getvalue().strip()) + + def test_render_to_string_declaration(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + variable = JavaVariable(name="var1", type="String", is_class_member=True) + variable.render_to_string_declaration(java) + self.assertEqual("String var1;", writer.getvalue().strip()) + +if __name__ == "__main__": + unittest.main() From 1e220d14ccd02afe40d95ba6fda6c080524d2210 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Sat, 5 Aug 2023 15:48:49 +0700 Subject: [PATCH 22/51] C++ array testing complete --- setup.cfg | 2 +- src/code_generation/cpp/array_generator.py | 5 +++++ tests/cpp/test_cpp_array_writer.py | 22 ++++++++++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index ccdc184..f97513d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = code_generation -version = 3.0.3 +version = 3.0.4 [options] packages = find: diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index e659afe..c437fe5 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -60,6 +60,8 @@ def _sanity_check(self): """ if not self.type: raise RuntimeError('Array type is not set') + if not self.name: + raise RuntimeError('Array name is not set') if self.is_class_member and not self.name: raise RuntimeError('Class member array name is not set') @@ -137,6 +139,7 @@ def render_to_string(self, cpp): That method is used for generating automatic (non-class members) arrays For class members use render_to_string_declaration/render_to_string_implementation methods """ + self._sanity_check() if self.is_class_member and not (self.is_static and self.is_const): raise RuntimeError('For class member variables use definition() and declaration() methods') @@ -158,6 +161,7 @@ def render_to_string_declaration(self, cpp): Example: static int my_class_member_array[]; """ + self._sanity_check() if not self.is_class_member: raise RuntimeError('For automatic variable use its render_to_string() method') cpp(f'{self._render_static()}{self._render_const()}{self.type} {self.name}[{self._render_size()}];') @@ -175,6 +179,7 @@ def render_to_string_implementation(self, cpp): Non-static arrays-class members do not supported """ + self._sanity_check() if not self.is_class_member: raise RuntimeError('For automatic variable use its render_to_string() method') diff --git a/tests/cpp/test_cpp_array_writer.py b/tests/cpp/test_cpp_array_writer.py index bb0abc6..9c0e89d 100644 --- a/tests/cpp/test_cpp_array_writer.py +++ b/tests/cpp/test_cpp_array_writer.py @@ -1,6 +1,7 @@ import unittest import io +from textwrap import dedent from code_generation.cpp.file_writer import CppFile from code_generation.cpp.array_generator import CppArray @@ -8,6 +9,15 @@ """ +def normalize_lines(text): + """ + Normalize indentation and whitespace for comparison + """ + lines = text.splitlines() + normalized_lines = [line.strip() for line in lines] + return '\n'.join(normalized_lines) + + class TestCppArrayStringIo(unittest.TestCase): """ Test C++ array generation by writing to StringIO @@ -28,8 +38,16 @@ def test_cpp_array_with_newline_align(self): arr = CppArray(name="my_array", type="int", array_size=5, newline_align=True) arr.add_array_items(["1", "2", "0"]) arr.render_to_string(cpp) - expected_output = "int my_array[5] = {\n 1,\n 2,\n 0\n};" - self.assertEqual(expected_output, writer.getvalue().strip()) + generated_output = writer.getvalue().strip() + expected_output = dedent("""int my_array[5] = + { + 1, + 2, + 0 + };""") + expected_output_normalized = normalize_lines(expected_output) + generated_output_normalized = normalize_lines(generated_output) + self.assertEqual(expected_output_normalized, generated_output_normalized) def test_cpp_array_declaration(self): writer = io.StringIO() From cfd86d83553829e445a27cfa56757894b99879f8 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Wed, 6 Dec 2023 20:09:28 +0700 Subject: [PATCH 23/51] Debug C++ class test --- src/code_generation/cpp/class_generator.py | 2 +- src/example.py | 68 ++++++++++++++-------- tests/cpp/test_cpp_class_writer.py | 16 ++++- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 61050f6..e02ad1b 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -218,7 +218,7 @@ def implementation(self, cpp): The method calls Python function that creates C++ method body if handle exists """ if self.implementation_handle is not None: - self.implementation_handle(self, cpp) + self.implementation_handle(cpp) def declaration(self): """ diff --git a/src/example.py b/src/example.py index b019011..dfb5609 100644 --- a/src/example.py +++ b/src/example.py @@ -3,6 +3,7 @@ from code_generation.core.code_file import CodeFile from code_generation.cpp.variable_generator import CppVariable +from code_generation.cpp.class_generator import CppClass from code_generation.java.file_writer import JavaFile from code_generation.java.array_generator import JavaArray @@ -17,24 +18,38 @@ def cpp_example(): static constexpr int const& x = 42; extern char* name; """ - cpp = CodeFile('example.cpp') - cpp('int i = 0;') - - # Create a new variable 'x' - x_variable = CppVariable( - name='x', - type='int const&', - is_static=True, - is_constexpr=True, - initialization_value='42') - x_variable.render_to_string(cpp) - - # Create a new variable 'name' - name_variable = CppVariable( - name='name', - type='char*', - is_extern=True) - name_variable.render_to_string(cpp) + cpp_file = CodeFile('example.cpp') + + # Create a CppClass instance + cpp_class = CppClass(name="MyClass", is_struct=True) + + # Add a CppVariable to the class + cpp_class.add_variable( + CppVariable( + name="m_var", + type="size_t", + is_static=True, + is_const=True, + initialization_value="255" + ) + ) + + # Define a function body for the CppMethod + def handle(cpp): + cpp('return m_var;') + + # Add a CppMethod to the class + cpp_class.add_method( + CppClass.CppMethod( + name="GetVar", + ret_type="size_t", + is_static=True, + implementation_handle=handle + ) + ) + + # Render the class to string + cpp_class.render_to_string(cpp_file) def java_example(): @@ -75,8 +90,12 @@ def html_example3(): html = HtmlFile('example2.html') with html.block('html'): with html.block('head', lang='en'): - HtmlElement(name='meta', self_closing=True, charset='utf-8').render_to_string(html) - HtmlElement(name='meta', self_closing=True, viewport='width=device-width, initial-scale=1').render_to_string(html) + HtmlElement(name='meta', + self_closing=True, + charset='utf-8').render_to_string(html) + HtmlElement(name='meta', + self_closing=True, + viewport='width=device-width, initial-scale=1').render_to_string(html) with html.block('body'): # with semantic with html.block('div', id='container'): @@ -85,8 +104,11 @@ def html_example3(): with html.block('div', id='content'): html('Content') # using content parameter - HtmlElement(name='div', self_closing=False).render_to_string(html, content='Footer 1') - HtmlElement(name='footer', self_closing=False, id='real-footer').render_to_string(html, content='Footer 2') + HtmlElement(name='div', + self_closing=False).render_to_string(html, content='Footer 1') + HtmlElement(name='footer', + self_closing=False, + id='real-footer').render_to_string(html, content='Footer 2') def main(): @@ -94,7 +116,7 @@ def main(): parser.add_argument('--language', help='Programming language to show example for', choices=["C++", "Java", "HTML"], - default="Java", + default="C++", required=False) args = parser.parse_args() diff --git a/tests/cpp/test_cpp_class_writer.py b/tests/cpp/test_cpp_class_writer.py index b0033d1..080a1fe 100644 --- a/tests/cpp/test_cpp_class_writer.py +++ b/tests/cpp/test_cpp_class_writer.py @@ -58,9 +58,23 @@ def handle(cpp): { static const size_t m_var; static size_t GetVar(); - };""") + }; + + static const size_t MyClass::m_var = 255; + + size_t MyClass::GetVar() + { + return m_var; + }""") # Assert the output matches the expected output + # Write expected and actual code to temp files + with open('expected.cpp', 'w') as expected_file: + expected_file.write(expected_output) + + with open('actual.cpp', 'w') as actual_file: + actual_file.write(writer.getvalue().strip()) + self.assertEqual(expected_output, writer.getvalue().strip()) def test_cpp_class_with_inheritance(self): From a639be213689e5357d2102b86694d4445f50bfa3 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 18 Dec 2023 20:05:27 +0700 Subject: [PATCH 24/51] Fixed C++ unittest --- src/code_generation/cpp/class_generator.py | 26 ++++----- tests/cpp/test_cpp_class_writer.py | 68 +++++++++++++++++++--- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 61050f6..2741364 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -113,7 +113,7 @@ def _render_static(self): Before function name, declaration only Static functions can't be const, virtual or pure virtual """ - return 'static' if self.is_static else '' + return 'static ' if self.is_static else '' def _render_constexpr(self): """ @@ -218,7 +218,7 @@ def implementation(self, cpp): The method calls Python function that creates C++ method body if handle exists """ if self.implementation_handle is not None: - self.implementation_handle(self, cpp) + self.implementation_handle(cpp) def declaration(self): """ @@ -273,6 +273,7 @@ def render_to_string_declaration(self, cpp): self.render_to_string(cpp) else: cpp(f'{self._render_virtual()}{self._render_inline()}' + f'{self._render_static()}' f'{self._render_ret_type()} {self.name}({self.args()})' f'{self._render_const()}' f'{self._render_override()}' @@ -341,7 +342,8 @@ def inherits(self): """ @return: string representation of the inheritance """ - return f' : public {self._parent_class()}' + if self._parent_class(): + return f' : public {self._parent_class()}' ######################################## # ADD CLASS MEMBERS @@ -394,7 +396,6 @@ def _render_internal_classes_declaration(self, cpp): """ for classItem in self.internal_class_elements: classItem.declaration().render_to_string(cpp) - cpp.newline() def _render_enum_section(self, cpp): """ @@ -403,7 +404,6 @@ def _render_enum_section(self, cpp): """ for enumItem in self.internal_enum_elements: enumItem.render_to_string(cpp) - cpp.newline() def _render_variables_declaration(self, cpp): """ @@ -412,7 +412,6 @@ def _render_variables_declaration(self, cpp): """ for varItem in self.internal_variable_elements: varItem.declaration().render_to_string(cpp) - cpp.newline() def _render_array_declaration(self, cpp): """ @@ -421,7 +420,6 @@ def _render_array_declaration(self, cpp): """ for arrItem in self.internal_array_elements: arrItem.declaration().render_to_string(cpp) - cpp.newline() def _render_methods_declaration(self, cpp): """ @@ -431,7 +429,6 @@ def _render_methods_declaration(self, cpp): """ for funcItem in self.internal_method_elements: funcItem.render_to_string_declaration(cpp) - cpp.newline() def render_static_members_implementation(self, cpp): """ @@ -506,18 +503,18 @@ def render_to_string_declaration(self, cpp): if self.documentation: cpp(dedent(self.documentation)) - with cpp.block(f'{self._render_class_type()} {self.name} {self.inherits()}', postfix=';'): + render_str = f'{self._render_class_type()} {self.name}' + if self._parent_class(): + render_str += f" {self.inherits()}" + + with cpp.block(render_str, postfix=';'): # in case of struct all members meant to be public if not self.is_struct: cpp.label('public') self.class_interface(cpp) - cpp.newline() - - # in case of struct all members meant to be public - if not self.is_struct: - cpp.label('private') self.private_class_members(cpp) + cpp.newline() def _render_class_type(self): """ @@ -530,7 +527,6 @@ def render_to_string_implementation(self, cpp): Render to string class definition. Typically handle to *.cpp file should be passed as 'cpp' param """ - cpp.newline(2) self.render_static_members_implementation(cpp) self.render_methods_implementation(cpp) diff --git a/tests/cpp/test_cpp_class_writer.py b/tests/cpp/test_cpp_class_writer.py index b0033d1..ac97175 100644 --- a/tests/cpp/test_cpp_class_writer.py +++ b/tests/cpp/test_cpp_class_writer.py @@ -12,6 +12,37 @@ """ +def normalize_code(code): + """ + Normalize indentation, whitespace and line breaks for comparison + """ + replacements = { + '\t': ' ', + '\r\n': '\n', + '\r': '\n', + '\r\n\r\n': '\n', + '\r\r': '\n', + '\n\n': '\n' + } + for old, new in replacements.items(): + count = code.count(old) + print(f"Replacing {count} occurrences of '{old}' with '{new}'") + code = code.replace(old, new) + + return code + + +def debug_dump(expected, actual): + """ + Dump the actual and expected values to 2 files + """ + with open("actual.cpp", "w") as f: + f.write(actual) + + with open("expected.cpp", "w") as f: + f.write(expected) + + class TestCppClassStringIo(unittest.TestCase): """ Test C++ class generation by writing to StringIO @@ -56,12 +87,24 @@ def handle(cpp): expected_output = dedent("""\ struct MyClass { - static const size_t m_var; static size_t GetVar(); - };""") + static const size_t m_var; + }; + + static const size_t MyClass::m_var = 255; + + size_t MyClass::GetVar() + { + return m_var; + }""") # Assert the output matches the expected output - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + debug_dump(expected_output_normalized, actual_output_normalized) + + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_inheritance(self): writer = io.StringIO() @@ -113,7 +156,12 @@ class ChildClass : public ParentClass };""") # Assert the output matches the expected output - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + debug_dump(expected_output_normalized, actual_output_normalized) + + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_nested_classes(self): writer = io.StringIO() @@ -141,7 +189,9 @@ class NestedClass };""") # Assert the output matches the expected output - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + debug_dump(expected_output, actual_output) + self.assertEqual(expected_output, actual_output) def test_cpp_class_with_enum(self): writer = io.StringIO() @@ -175,7 +225,9 @@ class MyClass };""") # Assert the output matches the expected output - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + debug_dump(expected_output, actual_output) + self.assertEqual(expected_output, actual_output) def test_cpp_class_with_array(self): writer = io.StringIO() @@ -207,7 +259,9 @@ class MyClass };""") # Assert the output matches the expected output - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + debug_dump(expected_output, actual_output) + self.assertEqual(expected_output, actual_output) if __name__ == "__main__": From 5f3d5bed9cbe4132788221306773a4df4b5f4e93 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 18 Dec 2023 20:08:16 +0700 Subject: [PATCH 25/51] Fixed C++ unittest --- tests/cpp/test_cpp_class_writer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/cpp/test_cpp_class_writer.py b/tests/cpp/test_cpp_class_writer.py index ac97175..ffcef82 100644 --- a/tests/cpp/test_cpp_class_writer.py +++ b/tests/cpp/test_cpp_class_writer.py @@ -102,8 +102,6 @@ def handle(cpp): actual_output = writer.getvalue().strip() expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) - debug_dump(expected_output_normalized, actual_output_normalized) - self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_inheritance(self): From 3dda47ceb32aaf97a66d55a050334da45219ace1 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 8 Jan 2024 17:28:32 +0700 Subject: [PATCH 26/51] Fixed inheritance --- src/code_generation/cpp/class_generator.py | 2 +- tests/cpp/test_cpp_class_writer.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 2741364..e74e88d 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -505,7 +505,7 @@ def render_to_string_declaration(self, cpp): render_str = f'{self._render_class_type()} {self.name}' if self._parent_class(): - render_str += f" {self.inherits()}" + render_str += self.inherits() with cpp.block(render_str, postfix=';'): diff --git a/tests/cpp/test_cpp_class_writer.py b/tests/cpp/test_cpp_class_writer.py index ffcef82..989fbc8 100644 --- a/tests/cpp/test_cpp_class_writer.py +++ b/tests/cpp/test_cpp_class_writer.py @@ -54,7 +54,7 @@ def test_cpp_class(self): # Create a CppClass instance cpp_class = CppClass(name="MyClass", is_struct=True) - + # Add a CppVariable to the class cpp_class.add_variable( CppVariable( @@ -145,12 +145,21 @@ def test_cpp_class_with_inheritance(self): expected_output = dedent("""\ class ParentClass { - static const int m_var; + public: static int GetVar(); + static const int m_var; }; - + + static const int ParentClass::m_var = 42; + + int ParentClass::GetVar() + { + return m_var; + } + class ChildClass : public ParentClass { + public: };""") # Assert the output matches the expected output @@ -158,7 +167,7 @@ class ChildClass : public ParentClass expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) debug_dump(expected_output_normalized, actual_output_normalized) - + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_nested_classes(self): From cfc9219590c94665af08b3771139cd8e5bb78489 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 8 Jan 2024 19:49:41 +0700 Subject: [PATCH 27/51] Regression fix --- tests/cpp/test_cpp_class_writer.py | 73 ++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/tests/cpp/test_cpp_class_writer.py b/tests/cpp/test_cpp_class_writer.py index 989fbc8..8c68917 100644 --- a/tests/cpp/test_cpp_class_writer.py +++ b/tests/cpp/test_cpp_class_writer.py @@ -1,6 +1,7 @@ import unittest import io from textwrap import dedent +from collections import OrderedDict from code_generation.cpp.file_writer import CppFile from code_generation.cpp.enum_generator import CppEnum @@ -16,18 +17,22 @@ def normalize_code(code): """ Normalize indentation, whitespace and line breaks for comparison """ - replacements = { - '\t': ' ', - '\r\n': '\n', - '\r': '\n', - '\r\n\r\n': '\n', - '\r\r': '\n', - '\n\n': '\n' - } - for old, new in replacements.items(): - count = code.count(old) - print(f"Replacing {count} occurrences of '{old}' with '{new}'") - code = code.replace(old, new) + + replacements = OrderedDict() + replacements['\r\n'] = '\n' + replacements['\r\n\r\n'] = '\n' + replacements['\r\r'] = '\n' + replacements['\t\n'] = '\n' + replacements['\n\n'] = '\n' + replacements['\t'] = ' ' + replacements['\r'] = '\n' + + count = 1 + while count > 0: + for old, new in replacements.items(): + count = code.count(old) + print(f"Replacing {count} occurrences") + code = code.replace(old, new) return code @@ -48,6 +53,9 @@ class TestCppClassStringIo(unittest.TestCase): Test C++ class generation by writing to StringIO """ + # Set to True to dump the actual and expected output to files + DEBUG_DUMP = True + def test_cpp_class(self): writer = io.StringIO() cpp_file = CppFile(None, writer=writer) @@ -102,6 +110,8 @@ def handle(cpp): actual_output = writer.getvalue().strip() expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) + if self.DEBUG_DUMP: + debug_dump(expected_output_normalized, actual_output_normalized) self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_inheritance(self): @@ -166,7 +176,8 @@ class ChildClass : public ParentClass actual_output = writer.getvalue().strip() expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) - debug_dump(expected_output_normalized, actual_output_normalized) + if self.DEBUG_DUMP: + debug_dump(expected_output_normalized, actual_output_normalized) self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -190,15 +201,20 @@ def test_cpp_class_with_nested_classes(self): expected_output = dedent("""\ class MyClass { + public: class NestedClass { + public: }; + };""") - # Assert the output matches the expected output actual_output = writer.getvalue().strip() - debug_dump(expected_output, actual_output) - self.assertEqual(expected_output, actual_output) + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if self.DEBUG_DUMP: + debug_dump(expected_output_normalized, actual_output_normalized) + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_enum(self): writer = io.StringIO() @@ -223,18 +239,23 @@ def test_cpp_class_with_enum(self): expected_output = dedent("""\ class MyClass { + public: enum Items { - Item1, - Item2, - Item3 + eItem1 = 0, + eItem2 = 1, + eItem3 = 2, + eItemsCount = 3 }; };""") # Assert the output matches the expected output actual_output = writer.getvalue().strip() - debug_dump(expected_output, actual_output) - self.assertEqual(expected_output, actual_output) + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if self.DEBUG_DUMP: + debug_dump(expected_output_normalized, actual_output_normalized) + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_array(self): writer = io.StringIO() @@ -244,7 +265,7 @@ def test_cpp_class_with_array(self): cpp_class = CppClass(name="MyClass") # Create a CppArray instance - cpp_array = CppArray(name="Array") + cpp_array = CppArray(name="Array", type="const char*", is_static=True) cpp_array.add_array_items(["Item1", "Item2", "Item3"]) # Add the array to the class @@ -257,6 +278,7 @@ def test_cpp_class_with_array(self): expected_output = dedent("""\ class MyClass { + public: const char* Array[3] = { Item1, @@ -267,8 +289,11 @@ class MyClass # Assert the output matches the expected output actual_output = writer.getvalue().strip() - debug_dump(expected_output, actual_output) - self.assertEqual(expected_output, actual_output) + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if self.DEBUG_DUMP: + debug_dump(expected_output_normalized, actual_output_normalized) + self.assertEqual(expected_output_normalized, actual_output_normalized) if __name__ == "__main__": From 416fc6a12a5e80cd36d8331ad0aafd1498a472d7 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 9 Jan 2024 22:18:45 +0700 Subject: [PATCH 28/51] Regression fix --- src/code_generation/cpp/array_generator.py | 14 +++++++++----- tests/cpp/test_cpp_array_writer.py | 2 +- tests/cpp/test_cpp_class_writer.py | 11 +++-------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index c437fe5..b60e605 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -164,7 +164,11 @@ def render_to_string_declaration(self, cpp): self._sanity_check() if not self.is_class_member: raise RuntimeError('For automatic variable use its render_to_string() method') - cpp(f'{self._render_static()}{self._render_const()}{self.type} {self.name}[{self._render_size()}];') + cpp(f'{self._render_static()}' + f'{self._render_const()}' + f'{self.type} ' + f'{self.name}' + f'[{self._render_size()}];') def render_to_string_implementation(self, cpp): """ @@ -190,10 +194,10 @@ def render_to_string_implementation(self, cpp): # newline-formatting of array elements makes sense only if array is not empty if self.newline_align and self.items: - with cpp.block(f'{self._render_static()}{self._render_const()}{self.type} ' - f'{self.name}[{self._render_size()}] = ', ';'): + with cpp.block(f'{self._render_const()}{self.type} ' + f'{self.fully_qualified_name()}[{self._render_size()}] = ', ';'): # render array items self._render_value(cpp) else: - cpp(f'{self._render_static()}{self._render_const()}{self.type} ' - f'{self.name}[{self._render_size()}] = {{{self._render_content()}}};') + cpp(f'{self._render_const()}{self.type} ' + f'{self.fully_qualified_name()}[{self._render_size()}] = {{{self._render_content()}}};') diff --git a/tests/cpp/test_cpp_array_writer.py b/tests/cpp/test_cpp_array_writer.py index 9c0e89d..d245bbf 100644 --- a/tests/cpp/test_cpp_array_writer.py +++ b/tests/cpp/test_cpp_array_writer.py @@ -64,7 +64,7 @@ def test_cpp_array_implementation(self): is_class_member=True, is_static=True) arr.add_array_items(["1", "2", "0"]) arr.render_to_string_implementation(cpp) - expected_output = "static int m_my_static_array[] = {1, 2, 0};" + expected_output = "int m_my_static_array[] = {1, 2, 0};" self.assertEqual(expected_output, writer.getvalue().strip()) def test_missing_type(self): diff --git a/tests/cpp/test_cpp_class_writer.py b/tests/cpp/test_cpp_class_writer.py index 8c68917..63429a2 100644 --- a/tests/cpp/test_cpp_class_writer.py +++ b/tests/cpp/test_cpp_class_writer.py @@ -31,7 +31,6 @@ def normalize_code(code): while count > 0: for old, new in replacements.items(): count = code.count(old) - print(f"Replacing {count} occurrences") code = code.replace(old, new) return code @@ -279,13 +278,9 @@ def test_cpp_class_with_array(self): class MyClass { public: - const char* Array[3] = - { - Item1, - Item2, - Item3 - }; - };""") + static const char* Array[]; + }; + const char* MyClass::Array[] = {Item1, Item2, Item3};""") # Assert the output matches the expected output actual_output = writer.getvalue().strip() From bbb20d34513ada84554e9486fadf02c052802e94 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 9 Jan 2024 22:51:24 +0700 Subject: [PATCH 29/51] C++ unit test complete --- tests/cpp/comparing_tools.py | 51 ++++++++++++++++++++++++++++++ tests/cpp/test_cpp_array_writer.py | 10 +----- tests/cpp/test_cpp_class_writer.py | 49 ++++------------------------ tests/cpp/test_cpp_enum_writer.py | 46 ++++++++++++++++++++------- 4 files changed, 92 insertions(+), 64 deletions(-) create mode 100644 tests/cpp/comparing_tools.py diff --git a/tests/cpp/comparing_tools.py b/tests/cpp/comparing_tools.py new file mode 100644 index 0000000..7ac42b4 --- /dev/null +++ b/tests/cpp/comparing_tools.py @@ -0,0 +1,51 @@ +from collections import OrderedDict + + +def normalize_code(code): + """ + Normalize indentation, whitespace and line breaks for comparison + """ + + replacements = OrderedDict() + replacements['\r\n'] = '\n' + replacements['\r\n\r\n'] = '\n' + replacements['\r\r'] = '\n' + replacements['\t\n'] = '\n' + replacements['\n\n'] = '\n' + replacements['\t'] = ' ' + replacements['\r'] = '\n' + + count = 1 + while count > 0: + for old, new in replacements.items(): + count = code.count(old) + code = code.replace(old, new) + + return code + + +def debug_dump(expected, actual): + """ + Dump the actual and expected values to 2 files + """ + with open("actual.cpp", "w") as f: + f.write(actual) + + with open("expected.cpp", "w") as f: + f.write(expected) + + +def normalize_lines(text): + """ + Normalize indentation and whitespace for comparison + """ + lines = text.splitlines() + normalized_lines = [line.strip() for line in lines] + return '\n'.join(normalized_lines) + + +def is_debug(): + """ + Dump the actual and expected values to 2 files + """ + return True diff --git a/tests/cpp/test_cpp_array_writer.py b/tests/cpp/test_cpp_array_writer.py index d245bbf..eb4cb05 100644 --- a/tests/cpp/test_cpp_array_writer.py +++ b/tests/cpp/test_cpp_array_writer.py @@ -4,20 +4,12 @@ from textwrap import dedent from code_generation.cpp.file_writer import CppFile from code_generation.cpp.array_generator import CppArray +from comparing_tools import normalize_lines __doc__ = """Unit tests for C++ code generator """ -def normalize_lines(text): - """ - Normalize indentation and whitespace for comparison - """ - lines = text.splitlines() - normalized_lines = [line.strip() for line in lines] - return '\n'.join(normalized_lines) - - class TestCppArrayStringIo(unittest.TestCase): """ Test C++ array generation by writing to StringIO diff --git a/tests/cpp/test_cpp_class_writer.py b/tests/cpp/test_cpp_class_writer.py index 63429a2..87c3be8 100644 --- a/tests/cpp/test_cpp_class_writer.py +++ b/tests/cpp/test_cpp_class_writer.py @@ -1,60 +1,23 @@ import unittest import io from textwrap import dedent -from collections import OrderedDict from code_generation.cpp.file_writer import CppFile from code_generation.cpp.enum_generator import CppEnum from code_generation.cpp.array_generator import CppArray from code_generation.cpp.variable_generator import CppVariable from code_generation.cpp.class_generator import CppClass +from comparing_tools import normalize_code, debug_dump, is_debug __doc__ = """Unit tests for C++ code generator """ -def normalize_code(code): - """ - Normalize indentation, whitespace and line breaks for comparison - """ - - replacements = OrderedDict() - replacements['\r\n'] = '\n' - replacements['\r\n\r\n'] = '\n' - replacements['\r\r'] = '\n' - replacements['\t\n'] = '\n' - replacements['\n\n'] = '\n' - replacements['\t'] = ' ' - replacements['\r'] = '\n' - - count = 1 - while count > 0: - for old, new in replacements.items(): - count = code.count(old) - code = code.replace(old, new) - - return code - - -def debug_dump(expected, actual): - """ - Dump the actual and expected values to 2 files - """ - with open("actual.cpp", "w") as f: - f.write(actual) - - with open("expected.cpp", "w") as f: - f.write(expected) - - class TestCppClassStringIo(unittest.TestCase): """ Test C++ class generation by writing to StringIO """ - # Set to True to dump the actual and expected output to files - DEBUG_DUMP = True - def test_cpp_class(self): writer = io.StringIO() cpp_file = CppFile(None, writer=writer) @@ -109,7 +72,7 @@ def handle(cpp): actual_output = writer.getvalue().strip() expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) - if self.DEBUG_DUMP: + if is_debug(): debug_dump(expected_output_normalized, actual_output_normalized) self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -175,7 +138,7 @@ class ChildClass : public ParentClass actual_output = writer.getvalue().strip() expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) - if self.DEBUG_DUMP: + if is_debug(): debug_dump(expected_output_normalized, actual_output_normalized) self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -211,7 +174,7 @@ class NestedClass actual_output = writer.getvalue().strip() expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) - if self.DEBUG_DUMP: + if is_debug(): debug_dump(expected_output_normalized, actual_output_normalized) self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -252,7 +215,7 @@ class MyClass actual_output = writer.getvalue().strip() expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) - if self.DEBUG_DUMP: + if is_debug(): debug_dump(expected_output_normalized, actual_output_normalized) self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -286,7 +249,7 @@ class MyClass actual_output = writer.getvalue().strip() expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) - if self.DEBUG_DUMP: + if is_debug(): debug_dump(expected_output_normalized, actual_output_normalized) self.assertEqual(expected_output_normalized, actual_output_normalized) diff --git a/tests/cpp/test_cpp_enum_writer.py b/tests/cpp/test_cpp_enum_writer.py index 00d2e11..15f9104 100644 --- a/tests/cpp/test_cpp_enum_writer.py +++ b/tests/cpp/test_cpp_enum_writer.py @@ -4,6 +4,7 @@ from code_generation.cpp.file_writer import CppFile from code_generation.cpp.enum_generator import CppEnum +from comparing_tools import normalize_code, debug_dump, is_debug __doc__ = """Unit tests for C++ code generator """ @@ -23,11 +24,18 @@ def test_cpp_enum(self): expected_output = dedent("""\ enum Items { - Chair, - Table, - Shelve + eChair = 0, + eTable = 1, + eShelve = 2, + eItemsCount = 3 };""") - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized) + + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_enum_with_prefix(self): writer = io.StringIO() @@ -38,11 +46,18 @@ def test_cpp_enum_with_prefix(self): expected_output = dedent("""\ enum Items { - PrefixA, - PrefixB, - PrefixC + PrefixA = 0, + PrefixB = 1, + PrefixC = 2, + PrefixItemsCount = 3 };""") - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized) + + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_enum_class(self): writer = io.StringIO() @@ -53,11 +68,18 @@ def test_cpp_enum_class(self): expected_output = dedent("""\ enum class Items { - A, - B, - C + eA = 0, + eB = 1, + eC = 2, + eItemsCount = 3 };""") - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized) + + self.assertEqual(expected_output_normalized, actual_output_normalized) if __name__ == "__main__": From 19ec3440e7afbf1efbdb545d57143822b5d491d4 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Wed, 10 Jan 2024 21:50:57 +0700 Subject: [PATCH 30/51] Java array unit test complete --- requirements.txt | 2 + src/code_generation/java/array_generator.py | 26 ++++-- test/__init__.py | 0 {tests/cpp => test}/comparing_tools.py | 7 +- test/cpp/__init__.py | 0 {tests => test}/cpp/test_cpp_array_writer.py | 2 +- {tests => test}/cpp/test_cpp_class_writer.py | 15 ++-- {tests => test}/cpp/test_cpp_enum_writer.py | 8 +- {tests => test}/cpp/test_cpp_file.py | 0 .../cpp/test_cpp_function_writer.py | 0 .../cpp/test_cpp_variable_writer.py | 0 {tests => test}/create_assets.py | 0 test/html/__init__.py | 0 {tests => test}/html/test_html_writer.py | 0 test/java/__init__.py | 0 test/java/test_java_array_writer.py | 86 +++++++++++++++++++ .../java/test_java_class_writer.py | 0 {tests => test}/java/test_java_enum_writer.py | 0 {tests => test}/java/test_java_file.py | 0 .../java/test_java_function_writer.py | 0 .../java/test_java_variable_writer.py | 0 {tests => test}/test_assets/array.cpp | 0 {tests => test}/test_assets/class.cpp | 0 {tests => test}/test_assets/class.h | 0 {tests => test}/test_assets/enum.cpp | 0 {tests => test}/test_assets/factorial.cpp | 0 {tests => test}/test_assets/factorial.h | 0 {tests => test}/test_assets/func.cpp | 0 {tests => test}/test_assets/func.h | 0 {tests => test}/test_assets/var.cpp | 0 tests/java/test_java_array_writer.py | 58 ------------- 31 files changed, 121 insertions(+), 83 deletions(-) create mode 100644 requirements.txt create mode 100644 test/__init__.py rename {tests/cpp => test}/comparing_tools.py (86%) create mode 100644 test/cpp/__init__.py rename {tests => test}/cpp/test_cpp_array_writer.py (98%) rename {tests => test}/cpp/test_cpp_class_writer.py (97%) rename {tests => test}/cpp/test_cpp_enum_writer.py (95%) rename {tests => test}/cpp/test_cpp_file.py (100%) rename {tests => test}/cpp/test_cpp_function_writer.py (100%) rename {tests => test}/cpp/test_cpp_variable_writer.py (100%) rename {tests => test}/create_assets.py (100%) create mode 100644 test/html/__init__.py rename {tests => test}/html/test_html_writer.py (100%) create mode 100644 test/java/__init__.py create mode 100644 test/java/test_java_array_writer.py rename {tests => test}/java/test_java_class_writer.py (100%) rename {tests => test}/java/test_java_enum_writer.py (100%) rename {tests => test}/java/test_java_file.py (100%) rename {tests => test}/java/test_java_function_writer.py (100%) rename {tests => test}/java/test_java_variable_writer.py (100%) rename {tests => test}/test_assets/array.cpp (100%) rename {tests => test}/test_assets/class.cpp (100%) rename {tests => test}/test_assets/class.h (100%) rename {tests => test}/test_assets/enum.cpp (100%) rename {tests => test}/test_assets/factorial.cpp (100%) rename {tests => test}/test_assets/factorial.h (100%) rename {tests => test}/test_assets/func.cpp (100%) rename {tests => test}/test_assets/func.h (100%) rename {tests => test}/test_assets/var.cpp (100%) delete mode 100644 tests/java/test_java_array_writer.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c69b237 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pathlib~=1.0.1 +setuptools~=68.1.2 diff --git a/src/code_generation/java/array_generator.py b/src/code_generation/java/array_generator.py index 08e6575..a03687e 100644 --- a/src/code_generation/java/array_generator.py +++ b/src/code_generation/java/array_generator.py @@ -7,7 +7,9 @@ class JavaArray(JavaLanguageElement): available_properties_names = { 'name', + 'type', 'values', + 'quoted', 'is_class_member', 'add_counter' } | JavaLanguageElement.available_properties_names @@ -20,15 +22,19 @@ def __init__(self, **properties): self.values = [] self.is_class_member = False self.add_counter = True + self.type = None + self.quoted = False input_property_names = set(properties.keys()) - self.check_input_properties_names(input_property_names) super(JavaArray, self).__init__(properties) + self.check_input_properties_names(input_property_names) self.init_class_properties(current_class_properties=self.available_properties_names, input_properties_dict=properties) def _sanity_check(self): if not self.name: raise RuntimeError('Enum name is not set') + if not self.type: + raise RuntimeError('Enum type is not set') if not isinstance(self.name, str): raise RuntimeError(f'Enum name is not a string, but {type(self.name)}') if self.values is not None and not isinstance(self.values, list): @@ -47,6 +53,8 @@ def add_item(self, item): Add a single item to the enum :param item: The item to add """ + if self.values is None: + self.values = [] self.values.append(item) def add_items(self, items): @@ -54,15 +62,15 @@ def add_items(self, items): Add multiple items to the enum :param items: The items to add as a list """ + if self.values is None: + self.values = [] self.values.extend(items) def render_to_string(self, java): self._sanity_check() - with java.block(f'enum {self.name}', postfix=';'): - counter = 0 - for value in self.values: - java(f'{value} = {counter},') - counter += 1 - if self.add_counter: - last_element = f'{self.name}Count = {counter}' - java(last_element) + if len(self.values) == 0: + java(f'{self.type}[] {self.name} = new {self.type}[0];') + else: + # Add quotes around string values if self.quoted is True + values_str = ', '.join(f'"{value}"' if self.quoted else str(value) for value in self.values) + java(f'{self.type}[] {self.name} = {{{values_str}}};') \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cpp/comparing_tools.py b/test/comparing_tools.py similarity index 86% rename from tests/cpp/comparing_tools.py rename to test/comparing_tools.py index 7ac42b4..30150aa 100644 --- a/tests/cpp/comparing_tools.py +++ b/test/comparing_tools.py @@ -24,14 +24,15 @@ def normalize_code(code): return code -def debug_dump(expected, actual): +def debug_dump(expected, actual, extension): """ Dump the actual and expected values to 2 files + :param extension: """ - with open("actual.cpp", "w") as f: + with open(f"actual.{extension}", "w") as f: f.write(actual) - with open("expected.cpp", "w") as f: + with open(f"expected.{extension}", "w") as f: f.write(expected) diff --git a/test/cpp/__init__.py b/test/cpp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cpp/test_cpp_array_writer.py b/test/cpp/test_cpp_array_writer.py similarity index 98% rename from tests/cpp/test_cpp_array_writer.py rename to test/cpp/test_cpp_array_writer.py index eb4cb05..c2cd3c1 100644 --- a/tests/cpp/test_cpp_array_writer.py +++ b/test/cpp/test_cpp_array_writer.py @@ -4,7 +4,7 @@ from textwrap import dedent from code_generation.cpp.file_writer import CppFile from code_generation.cpp.array_generator import CppArray -from comparing_tools import normalize_lines +from test.comparing_tools import normalize_lines __doc__ = """Unit tests for C++ code generator """ diff --git a/tests/cpp/test_cpp_class_writer.py b/test/cpp/test_cpp_class_writer.py similarity index 97% rename from tests/cpp/test_cpp_class_writer.py rename to test/cpp/test_cpp_class_writer.py index 87c3be8..cdc769c 100644 --- a/tests/cpp/test_cpp_class_writer.py +++ b/test/cpp/test_cpp_class_writer.py @@ -7,10 +7,9 @@ from code_generation.cpp.array_generator import CppArray from code_generation.cpp.variable_generator import CppVariable from code_generation.cpp.class_generator import CppClass -from comparing_tools import normalize_code, debug_dump, is_debug +from test.comparing_tools import normalize_code, debug_dump, is_debug -__doc__ = """Unit tests for C++ code generator -""" +__doc__ = """Unit tests for C++ code generator""" class TestCppClassStringIo(unittest.TestCase): @@ -73,7 +72,7 @@ def handle(cpp): expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized) + debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_inheritance(self): @@ -139,7 +138,7 @@ class ChildClass : public ParentClass expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized) + debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -175,7 +174,7 @@ class NestedClass expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized) + debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_enum(self): @@ -216,7 +215,7 @@ class MyClass expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized) + debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) def test_cpp_class_with_array(self): @@ -250,7 +249,7 @@ class MyClass expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized) + debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) diff --git a/tests/cpp/test_cpp_enum_writer.py b/test/cpp/test_cpp_enum_writer.py similarity index 95% rename from tests/cpp/test_cpp_enum_writer.py rename to test/cpp/test_cpp_enum_writer.py index 15f9104..e8b24bf 100644 --- a/tests/cpp/test_cpp_enum_writer.py +++ b/test/cpp/test_cpp_enum_writer.py @@ -4,7 +4,7 @@ from code_generation.cpp.file_writer import CppFile from code_generation.cpp.enum_generator import CppEnum -from comparing_tools import normalize_code, debug_dump, is_debug +from test.comparing_tools import normalize_code, debug_dump, is_debug __doc__ = """Unit tests for C++ code generator """ @@ -33,7 +33,7 @@ def test_cpp_enum(self): expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized) + debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -55,7 +55,7 @@ def test_cpp_enum_with_prefix(self): expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized) + debug_dump(expected_output_normalized, actual_output_normalized, cpp) self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -77,7 +77,7 @@ def test_cpp_enum_class(self): expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized) + debug_dump(expected_output_normalized, actual_output_normalized, cpp) self.assertEqual(expected_output_normalized, actual_output_normalized) diff --git a/tests/cpp/test_cpp_file.py b/test/cpp/test_cpp_file.py similarity index 100% rename from tests/cpp/test_cpp_file.py rename to test/cpp/test_cpp_file.py diff --git a/tests/cpp/test_cpp_function_writer.py b/test/cpp/test_cpp_function_writer.py similarity index 100% rename from tests/cpp/test_cpp_function_writer.py rename to test/cpp/test_cpp_function_writer.py diff --git a/tests/cpp/test_cpp_variable_writer.py b/test/cpp/test_cpp_variable_writer.py similarity index 100% rename from tests/cpp/test_cpp_variable_writer.py rename to test/cpp/test_cpp_variable_writer.py diff --git a/tests/create_assets.py b/test/create_assets.py similarity index 100% rename from tests/create_assets.py rename to test/create_assets.py diff --git a/test/html/__init__.py b/test/html/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/html/test_html_writer.py b/test/html/test_html_writer.py similarity index 100% rename from tests/html/test_html_writer.py rename to test/html/test_html_writer.py diff --git a/test/java/__init__.py b/test/java/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/java/test_java_array_writer.py b/test/java/test_java_array_writer.py new file mode 100644 index 0000000..1c0bfa9 --- /dev/null +++ b/test/java/test_java_array_writer.py @@ -0,0 +1,86 @@ +import unittest +import io + +from code_generation.java.file_writer import JavaFile +from code_generation.java.array_generator import JavaArray +from test.comparing_tools import normalize_code, debug_dump, is_debug + + +class TestJavaArrayStringIo(unittest.TestCase): + """ + Test Java array generation by writing to StringIO + """ + + def test_java_array(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + arr = JavaArray(name="myArray", type="int", values=["1", "2", "0"]) + arr.render_to_string(java) + expected_output = "int[] myArray = {1, 2, 0};" + actual_output = writer.getvalue().strip() + + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) + + def test_java_array_with_empty_values(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + arr = JavaArray(name="emptyArray", type='int', values=[]) + arr.render_to_string(java) + actual_output = writer.getvalue().strip() + expected_output = "int[] emptyArray = new int[0];" + + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) + + def test_java_array_add_item(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + arr = JavaArray(name="myArray", type='String', quoted=True) + arr.add_item("item1") + arr.add_item("item2") + arr.render_to_string(java) + actual_output = writer.getvalue().strip() + expected_output = "String[] myArray = {\"item1\", \"item2\"};" + + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) + + def test_java_array_add_items(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + arr = JavaArray(name="myArray", type='String', quoted=True) + arr.add_items(["item1", "item2", "item3"]) + arr.render_to_string(java) + actual_output = writer.getvalue().strip() + expected_output = "String[] myArray = {\"item1\", \"item2\", \"item3\"};" + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) + + def test_missing_name(self): + arr = JavaArray(values=["1", "2", "3"], type='int') + self.assertRaises(RuntimeError, arr.render_to_string, None) + + def test_missing_type(self): + arr = JavaArray(name="myArray", values=["1", "2", "3"]) + self.assertRaises(RuntimeError, arr.render_to_string, None) + + def test_invalid_values_type(self): + arr = JavaArray(name="myArray", values="invalid") + self.assertRaises(RuntimeError, arr.render_to_string, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/java/test_java_class_writer.py b/test/java/test_java_class_writer.py similarity index 100% rename from tests/java/test_java_class_writer.py rename to test/java/test_java_class_writer.py diff --git a/tests/java/test_java_enum_writer.py b/test/java/test_java_enum_writer.py similarity index 100% rename from tests/java/test_java_enum_writer.py rename to test/java/test_java_enum_writer.py diff --git a/tests/java/test_java_file.py b/test/java/test_java_file.py similarity index 100% rename from tests/java/test_java_file.py rename to test/java/test_java_file.py diff --git a/tests/java/test_java_function_writer.py b/test/java/test_java_function_writer.py similarity index 100% rename from tests/java/test_java_function_writer.py rename to test/java/test_java_function_writer.py diff --git a/tests/java/test_java_variable_writer.py b/test/java/test_java_variable_writer.py similarity index 100% rename from tests/java/test_java_variable_writer.py rename to test/java/test_java_variable_writer.py diff --git a/tests/test_assets/array.cpp b/test/test_assets/array.cpp similarity index 100% rename from tests/test_assets/array.cpp rename to test/test_assets/array.cpp diff --git a/tests/test_assets/class.cpp b/test/test_assets/class.cpp similarity index 100% rename from tests/test_assets/class.cpp rename to test/test_assets/class.cpp diff --git a/tests/test_assets/class.h b/test/test_assets/class.h similarity index 100% rename from tests/test_assets/class.h rename to test/test_assets/class.h diff --git a/tests/test_assets/enum.cpp b/test/test_assets/enum.cpp similarity index 100% rename from tests/test_assets/enum.cpp rename to test/test_assets/enum.cpp diff --git a/tests/test_assets/factorial.cpp b/test/test_assets/factorial.cpp similarity index 100% rename from tests/test_assets/factorial.cpp rename to test/test_assets/factorial.cpp diff --git a/tests/test_assets/factorial.h b/test/test_assets/factorial.h similarity index 100% rename from tests/test_assets/factorial.h rename to test/test_assets/factorial.h diff --git a/tests/test_assets/func.cpp b/test/test_assets/func.cpp similarity index 100% rename from tests/test_assets/func.cpp rename to test/test_assets/func.cpp diff --git a/tests/test_assets/func.h b/test/test_assets/func.h similarity index 100% rename from tests/test_assets/func.h rename to test/test_assets/func.h diff --git a/tests/test_assets/var.cpp b/test/test_assets/var.cpp similarity index 100% rename from tests/test_assets/var.cpp rename to test/test_assets/var.cpp diff --git a/tests/java/test_java_array_writer.py b/tests/java/test_java_array_writer.py deleted file mode 100644 index 92dbd37..0000000 --- a/tests/java/test_java_array_writer.py +++ /dev/null @@ -1,58 +0,0 @@ -import unittest -import io - -from code_generation.java.file_writer import JavaFile -from code_generation.java.array_generator import JavaArray - - -class TestJavaArrayStringIo(unittest.TestCase): - """ - Test Java array generation by writing to StringIO - """ - - def test_java_array(self): - writer = io.StringIO() - java = JavaFile(None, writer=writer) - arr = JavaArray(name="myArray", values=["1", "2", "0"]) - arr.render_to_string(java) - expected_output = "int[] myArray = {1, 2, 0};" - self.assertEqual(expected_output, writer.getvalue().strip()) - - def test_java_array_with_empty_values(self): - writer = io.StringIO() - java = JavaFile(None, writer=writer) - arr = JavaArray(name="emptyArray", values=[]) - arr.render_to_string(java) - expected_output = "int[] emptyArray = new int[0];" - self.assertEqual(expected_output, writer.getvalue().strip()) - - def test_java_array_add_item(self): - writer = io.StringIO() - java = JavaFile(None, writer=writer) - arr = JavaArray(name="myArray") - arr.add_item("item1") - arr.add_item("item2") - arr.render_to_string(java) - expected_output = "String[] myArray = {\"item1\", \"item2\"};" - self.assertEqual(expected_output, writer.getvalue().strip()) - - def test_java_array_add_items(self): - writer = io.StringIO() - java = JavaFile(None, writer=writer) - arr = JavaArray(name="myArray") - arr.add_items(["item1", "item2", "item3"]) - arr.render_to_string(java) - expected_output = "String[] myArray = {\"item1\", \"item2\", \"item3\"};" - self.assertEqual(expected_output, writer.getvalue().strip()) - - def test_missing_name(self): - arr = JavaArray(values=["1", "2", "3"]) - self.assertRaises(RuntimeError, arr.render_to_string, None) - - def test_invalid_values_type(self): - arr = JavaArray(name="myArray", values="invalid") - self.assertRaises(RuntimeError, arr.render_to_string, None) - - -if __name__ == "__main__": - unittest.main() From 691378674a4d3fb7f3e3765323b80af0bbae2a73 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 11 Jan 2024 10:59:55 +0700 Subject: [PATCH 31/51] Java function test complete --- src/code_generation/java/enum_generator.py | 3 +- .../java/function_generator.py | 13 ++++---- src/code_generation/java/language_element.py | 10 +++--- test/java/test_java_enum_writer.py | 10 ++++-- test/java/test_java_function_writer.py | 32 +++++++++---------- 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/code_generation/java/enum_generator.py b/src/code_generation/java/enum_generator.py index db54794..45760ab 100644 --- a/src/code_generation/java/enum_generator.py +++ b/src/code_generation/java/enum_generator.py @@ -1,4 +1,3 @@ -from code_generation.core.code_file import CodeFile from code_generation.java.language_element import JavaLanguageElement @@ -62,7 +61,7 @@ def _render_static(self, java): if self.values is None or not self.values: java(f"enum {self.name} {{}}") else: - java(f"enum {self.name} {{ {self.values_str()} }}") + java(f"enum {self.name} {{ {self.values_str()} }};") def render_to_string(self, java): self._sanity_check() diff --git a/src/code_generation/java/function_generator.py b/src/code_generation/java/function_generator.py index fb4ae18..dff5ec1 100644 --- a/src/code_generation/java/function_generator.py +++ b/src/code_generation/java/function_generator.py @@ -8,7 +8,7 @@ class JavaFunction(JavaLanguageElement): available_properties_names = { 'name', - 'ret_type', + 'return_type', 'arguments', 'is_static', 'is_final', @@ -16,13 +16,14 @@ class JavaFunction(JavaLanguageElement): 'is_synchronized', 'is_native', 'is_strictfp', + 'access_specifier', 'documentation', 'implementation_handle' } | JavaLanguageElement.available_properties_names def __init__(self, **properties): self.name = '' - self.ret_type = '' + self.return_type = '' self.arguments = [] self.is_static = False self.is_final = False @@ -31,8 +32,8 @@ def __init__(self, **properties): self.is_native = False self.is_strictfp = False self.documentation = '' + self.access_specifier = 'public' self.implementation_handle = None - input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(JavaFunction, self).__init__(properties) @@ -44,8 +45,8 @@ def _sanity_check(self): raise RuntimeError('Method name is not set') if not isinstance(self.name, str): raise RuntimeError(f'Method name is not a string, but {type(self.name)}') - if not isinstance(self.ret_type, str): - raise RuntimeError(f'Return type is not a string, but {type(self.ret_type)}') + if not isinstance(self.return_type, str): + raise RuntimeError(f'Return type is not a string, but {type(self.return_type)}') if self.arguments is not None and not isinstance(self.arguments, list): raise RuntimeError(f'Arguments are not a list, but {type(self.arguments)}') @@ -77,6 +78,6 @@ def render_to_string(self, java): java('native', end=' ') if self.is_strictfp: java('strictfp', end=' ') - with java.block(f'{self.ret_type} {self.name}({self.args_str()})'): + with java.block(f'{self.access_specifier} {self.return_type} {self.name}({self.args_str()})'): if self.implementation_handle: self.implementation_handle(java) diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py index 1ce3927..caf9f11 100644 --- a/src/code_generation/java/language_element.py +++ b/src/code_generation/java/language_element.py @@ -82,16 +82,16 @@ def init_class_properties(self, current_class_properties, input_properties_dict, @param: default_property_value - value for properties that are not initialized (None by default, because of the same as False semantic) """ - # Set all available properties to DefaultValue - for property_name in current_class_properties: - if property_name not in JavaLanguageElement.available_properties_names: - setattr(self, property_name, default_property_value) - # Set all defined properties values (all undefined will be left with defaults) for (property_name, property_value) in input_properties_dict.items(): if property_name not in JavaLanguageElement.available_properties_names: setattr(self, property_name, property_value) + # Set all available properties to DefaultValue if they are not already set + for property_name in current_class_properties: + if property_name not in JavaLanguageElement.available_properties_names and not hasattr(self, property_name): + setattr(self, property_name, default_property_value) + def render_to_string(self, java): """ @param: java - handle that supports code generation interface (see code_file.py) diff --git a/test/java/test_java_enum_writer.py b/test/java/test_java_enum_writer.py index d1e0aa6..5dac3fb 100644 --- a/test/java/test_java_enum_writer.py +++ b/test/java/test_java_enum_writer.py @@ -3,6 +3,7 @@ from code_generation.java.file_writer import JavaFile from code_generation.java.enum_generator import JavaEnum +from test.comparing_tools import normalize_code, debug_dump, is_debug class TestJavaEnumStringIo(unittest.TestCase): @@ -15,8 +16,13 @@ def test_java_enum(self): java = JavaFile(None, writer=writer) enum = JavaEnum(name="Color", values=["RED", "GREEN", "BLUE"]) enum.render_to_string(java) - expected_output = "enum Color {\n RED,\n GREEN,\n BLUE\n}" - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + expected_output = "enum Color { RED, GREEN, BLUE };" + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_render_to_string_declaration(self): writer = io.StringIO() diff --git a/test/java/test_java_function_writer.py b/test/java/test_java_function_writer.py index c9ccf7e..4028209 100644 --- a/test/java/test_java_function_writer.py +++ b/test/java/test_java_function_writer.py @@ -4,6 +4,11 @@ from code_generation.java.file_writer import JavaFile from code_generation.java.function_generator import JavaFunction +from test.comparing_tools import normalize_code, debug_dump, is_debug + + +def handle_to_function(j): + j('return a + b;') class TestJavaFunctionStringIo(unittest.TestCase): @@ -14,31 +19,24 @@ class TestJavaFunctionStringIo(unittest.TestCase): def test_java_method(self): writer = io.StringIO() java = JavaFile(None, writer=writer) + method = JavaFunction(name="calculateSum", return_type="int", - implementation_handle=lambda: "return a + b;") + implementation_handle=handle_to_function) method.add_argument("int a") method.add_argument("int b") method.render_to_string(java) expected_output = dedent("""\ - public int calculateSum(int a, int b) { - // Method implementation + public int calculateSum(int a, int b) + { return a + b; }""") - self.assertEqual(expected_output, writer.getvalue().strip()) - - def test_render_to_string_declaration(self): - writer = io.StringIO() - java = JavaFile(None, writer=writer) - method = JavaFunction(name="calculateSum", - return_type="int", - implementation_handle=lambda: "return a + b;") - method.add_argument("int a") - method.add_argument("int b") - method.render_to_string(java) - expected_output = dedent("""\ - public int calculateSum(int a, int b);""") - self.assertEqual(expected_output, writer.getvalue().strip()) + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_missing_name(self): method = JavaFunction(name=None, return_type="int") From 55eaf5091948304f92defd6de639e8f437cf0ead Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 11 Jan 2024 11:38:01 +0700 Subject: [PATCH 32/51] Java enum test complete --- .github/workflows/python-app.yml | 14 ++++++++++---- test/cpp/test_cpp_enum_writer.py | 4 ++-- test/java/test_java_enum_writer.py | 8 -------- test/java/test_java_variable_writer.py | 3 ++- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 41cc5dc..e77a8bd 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -34,7 +34,13 @@ jobs: - name: Run test run: | python release_package.py --mode install - python tests/test_cpp_file.py - python tests/test_cpp_function_writer.py - python tests/test_cpp_variable_writer.py - python tests/test_html_writer.py + python test.cpp.test_cpp_array_writer + python test.cpp.test_cpp_enum_writer + python test.cpp.test_cpp_class_writer + python test.cpp.test_cpp_function_writer + python test.cpp.test_cpp_variable_writer + python test.java.test_java_array_writer + python test.java.test_java_enum_writer + python test.java.test_java_class_writer + python test.java.test_java_function_writer + python test.java.test_java_variable_writer diff --git a/test/cpp/test_cpp_enum_writer.py b/test/cpp/test_cpp_enum_writer.py index e8b24bf..9b5425e 100644 --- a/test/cpp/test_cpp_enum_writer.py +++ b/test/cpp/test_cpp_enum_writer.py @@ -55,7 +55,7 @@ def test_cpp_enum_with_prefix(self): expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized, cpp) + debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) @@ -77,7 +77,7 @@ def test_cpp_enum_class(self): expected_output_normalized = normalize_code(expected_output) actual_output_normalized = normalize_code(actual_output) if is_debug(): - debug_dump(expected_output_normalized, actual_output_normalized, cpp) + debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) diff --git a/test/java/test_java_enum_writer.py b/test/java/test_java_enum_writer.py index 5dac3fb..f2a5803 100644 --- a/test/java/test_java_enum_writer.py +++ b/test/java/test_java_enum_writer.py @@ -24,14 +24,6 @@ def test_java_enum(self): debug_dump(expected_output_normalized, actual_output_normalized, "java") self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_render_to_string_declaration(self): - writer = io.StringIO() - java = JavaFile(None, writer=writer) - enum = JavaEnum(name="Color", values=["RED", "GREEN", "BLUE"]) - enum.render_to_string(java) - expected_output = "enum Color;" - self.assertEqual(expected_output, writer.getvalue().strip()) - def test_missing_name(self): enum = JavaEnum(name=None, values=["RED", "GREEN", "BLUE"]) self.assertRaises(RuntimeError, enum.render_to_string, None) diff --git a/test/java/test_java_variable_writer.py b/test/java/test_java_variable_writer.py index 8a5158e..f794140 100644 --- a/test/java/test_java_variable_writer.py +++ b/test/java/test_java_variable_writer.py @@ -38,9 +38,10 @@ def test_is_static_render_to_string(self): def test_render_to_string_declaration(self): writer = io.StringIO() java = JavaFile(None, writer=writer) - variable = JavaVariable(name="var1", type="String", is_class_member=True) + variable = JavaVariable(name="var1", type="String") variable.render_to_string_declaration(java) self.assertEqual("String var1;", writer.getvalue().strip()) + if __name__ == "__main__": unittest.main() From 9ff1e2943a033526860a469ee7a3a9771e8a6624 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 11 Jan 2024 16:05:19 +0700 Subject: [PATCH 33/51] Java variable test complete --- README.md | 4 +- src/code_generation/cpp/class_generator.py | 2 +- src/code_generation/cpp/language_element.py | 2 +- src/code_generation/cpp/variable_generator.py | 14 +- src/code_generation/java/class_generator.py | 2 +- src/code_generation/java/language_element.py | 2 +- .../java/variable_generator.py | 122 ++++++++++++++---- src/example.py | 2 +- test/cpp/test_cpp_class_writer.py | 4 +- test/cpp/test_cpp_file.py | 8 +- test/cpp/test_cpp_variable_writer.py | 8 +- test/create_assets.py | 10 +- test/java/test_java_file.py | 6 +- test/java/test_java_variable_writer.py | 4 +- 14 files changed, 133 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 571b799..1aaf3ec 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ a legal C++ construction, in two places: declaration and definition. cpp = CodeFile('example.cpp') cpp('int i = 0;') -x_variable = CppVariable(name='x', type='int const&', is_static=True, is_constexpr=True, initialization_value='42') +x_variable = CppVariable(name='x', type='int const&', is_static=True, is_constexpr=True, value='42') x_variable.render_to_string(cpp) name_variable = CppVariable(name='name', type='char*', is_extern=True) @@ -130,7 +130,7 @@ cpp_class.add_variable(CppVariable(name = "m_var", type = 'size_t', is_static = True, is_const = True, - initialization_value = 255)) + value = 255)) ``` ###### Generated C++ declaration diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index e74e88d..a094f46 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -19,7 +19,7 @@ class CppClass(CppLanguageElement): type = 'size_t', is_static = True, is_const = True, - initialization_value = 255)) + value = 255)) def handle(cpp): cpp('return m_var;') diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index 10324b5..7acba7a 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -12,7 +12,7 @@ type = 'size_t', is_static = True, is_const = True, - initialization_value = 255)) + value = 255)) // Generated C++ declaration struct MyClass diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index 0ddfc49..32b5400 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -15,7 +15,7 @@ type = 'size_t', is_static = True, is_const = True, - initialization_value = 255)) + value = 255)) // Generated C++ declaration struct MyClass @@ -72,8 +72,8 @@ class MyClass is_extern - boolean, 'extern' prefix is_const - boolean, 'const' prefix is_constexpr - boolean, 'constexpr' prefix - initialization_value - string, initialization_value to be initialized with. - 'a = initialization_value;' for automatic variables, 'a(initialization_value)' for the class member + value - string, value to be initialized with. + 'a = value;' for automatic variables, 'a(value)' for the class member documentation - string, '/// Example doxygen' is_class_member - boolean, for appropriate definition/declaration rendering """ @@ -82,7 +82,7 @@ class MyClass 'is_extern', 'is_const', 'is_constexpr', - 'initialization_value', + 'value', 'documentation', 'is_class_member'} | CppLanguageElement.availablePropertiesNames @@ -99,7 +99,7 @@ def _sanity_check(self): """ if self.is_const and self.is_constexpr: raise ValueError("Variable object can be either 'const' or 'constexpr', not both") - if self.is_constexpr and not self.initialization_value: + if self.is_constexpr and not self.value: raise ValueError("Variable object must be initialized when 'constexpr'") if self.is_static and self.is_extern: raise ValueError("Variable object can be either 'extern' or 'static', not both") @@ -130,9 +130,9 @@ def _render_constexpr(self): def _render_init_value(self): """ - @return: string, initialization_value to be initialized with + @return: string, value to be initialized with """ - return self.initialization_value if self.initialization_value else '' + return self.value if self.value else '' def assignment(self, value): """ diff --git a/src/code_generation/java/class_generator.py b/src/code_generation/java/class_generator.py index 625ac26..602895f 100644 --- a/src/code_generation/java/class_generator.py +++ b/src/code_generation/java/class_generator.py @@ -16,7 +16,7 @@ class JavaClass(JavaLanguageElement): # Python code java_class = JavaClass(name='MyClass') - java_class.add_variable(JavaVariable(name='myVariable', type='int', is_static=True, is_final=True, initialization_value='10')) + java_class.add_variable(JavaVariable(name='myVariable', type='int', is_static=True, is_final=True, value='10')) java_class.add_method(JavaFunction(name='getVar', return_type='int', is_static=True, implementation='return myVariable;')) # Generated Java code diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py index caf9f11..00e293b 100644 --- a/src/code_generation/java/language_element.py +++ b/src/code_generation/java/language_element.py @@ -11,7 +11,7 @@ type = 'int', is_static = True, is_final = True, - initialization_value = 10)) + value = 10)) java_class.add_method(JavaFunction(name = 'main', return_type = 'void', is_static = True, diff --git a/src/code_generation/java/variable_generator.py b/src/code_generation/java/variable_generator.py index 1498025..a417a3d 100644 --- a/src/code_generation/java/variable_generator.py +++ b/src/code_generation/java/variable_generator.py @@ -1,6 +1,5 @@ from code_generation.java.language_element import JavaLanguageElement - __doc__ = """""" @@ -18,23 +17,44 @@ class MyClass { type - string, variable type is_static - boolean, 'static' prefix is_final - boolean, 'final' prefix - initialization_value - string, initialization value to be assigned + value - string, initialization value to be assigned documentation - string, '/** Example Javadoc */' """ - available_properties_names = { - 'type', - 'is_static', - 'is_final', - 'initialization_value', - 'documentation' - } | JavaLanguageElement.available_properties_names + available_properties_names = ( + { + 'type', + 'name', + 'value', + 'access_modifier', + 'is_static', + 'static', + 'is_final', + 'final', + 'documentation', + 'is_transient', + 'transient', + 'is_volatile', + 'volatile', + 'is_synthetic', + 'synthetic', + 'custom_annotations', + 'custom_modifiers', + 'is_class_member' + } | JavaLanguageElement.available_properties_names) def __init__(self, **properties): self.type = '' + self.name = '' + self.value = '' self.is_static = False self.is_final = False - self.initialization_value = '' + self.is_volatile = False + self.is_transient = False + self.is_synthetic = False + self.custom_annotations = [] + self.custom_modifiers = [] + self.access_modifier = None self.documentation = '' input_property_names = set(properties.keys()) @@ -45,22 +65,78 @@ def __init__(self, **properties): input_properties_dict=properties ) - def render_to_string(self, java): - if self.documentation: - java(f"/**\n{self.documentation}\n*/") - java(f"{self._render_static()}{self._render_final()}{self.type} {self.name};") + def _sanity_check(self): + + # Basic checks + if not self.name: + raise ValueError("Variable name is not set") + if not self.type: + raise ValueError("Variable type is not set") + + # Check for incompatible modifiers + if self.is_static and self.is_transient: + raise ValueError("Cannot use 'static' and 'transient' modifiers together") + if self.is_static and self.is_final: + raise ValueError("Cannot use 'static' and 'final' modifiers together") + if self.is_static and self.is_synthetic: + raise ValueError("Cannot use 'static' and 'synthetic' modifiers together") + if self.is_final and self.is_volatile: + raise ValueError("Cannot use 'final' and 'volatile' modifiers together") - def render_to_string_declaration(self, java): - if self.documentation: - java(f"/**\n{self.documentation}\n*/") - java(f"{self._render_static()}{self._render_final()}{self.type} {self.name};") + # Check for valid access modifiers + valid_access_modifiers = ['public', 'protected', 'private', '', None] + if self.access_modifier not in valid_access_modifiers: + raise ValueError(f"Invalid access modifier: {self.access_modifier}") - def render_to_string_implementation(self, java): - if self.is_static: - java(f"{self._render_static()}final {self.type} {self.name} = {self.initialization_value};") + def _render_type(self): + return f'{self.type} ' + + def _render_value(self): + return f' = {self.value}' if self.value else '' def _render_static(self): - return "static " if self.is_static else "" + return 'static ' if self.is_static else '' def _render_final(self): - return "final " if self.is_final else "" + return 'final ' if self.is_final else '' + + def _render_volatile(self): + return 'volatile ' if self.is_volatile else '' + + def _render_transient(self): + return 'transient ' if self.is_transient else '' + + def _render_synthetic(self): + return 'synthetic ' if self.is_synthetic else '' + + def _render_documentation(self): + return f"/**\n{self.documentation}\n*/" if self.documentation else '' + + def _render_access_modifier(self): + return self.access_modifier if self.access_modifier is not None else '' + + def _render_modifiers(self): + modifiers = [ + self._render_static(), + self._render_final(), + self._render_volatile(), + self._render_transient(), + self._render_synthetic() + ] + return ' '.join(modifier for modifier in modifiers if modifier) + + def _render_custom_annotations(self): + return ' '.join(self.custom_annotations) if self.custom_annotations else '' + + def _render_custom_modifiers(self): + return ' '.join(self.custom_modifiers) if self.custom_modifiers else '' + + def render_to_string(self, java): + self._sanity_check() + java(f'{self._render_custom_annotations()}' + f'{self._render_custom_modifiers()}' + f'{self._render_documentation()}' + f'{self._render_modifiers()}' + f'{self._render_type()}' + f'{self.name}' + f'{self._render_value()};') diff --git a/src/example.py b/src/example.py index b019011..d2a491d 100644 --- a/src/example.py +++ b/src/example.py @@ -26,7 +26,7 @@ def cpp_example(): type='int const&', is_static=True, is_constexpr=True, - initialization_value='42') + value='42') x_variable.render_to_string(cpp) # Create a new variable 'name' diff --git a/test/cpp/test_cpp_class_writer.py b/test/cpp/test_cpp_class_writer.py index cdc769c..913f4d7 100644 --- a/test/cpp/test_cpp_class_writer.py +++ b/test/cpp/test_cpp_class_writer.py @@ -31,7 +31,7 @@ def test_cpp_class(self): type="size_t", is_static=True, is_const=True, - initialization_value="255" + value="255" ) ) @@ -92,7 +92,7 @@ def test_cpp_class_with_inheritance(self): type="int", is_static=True, is_const=True, - initialization_value="42" + value="42" ) ) diff --git a/test/cpp/test_cpp_file.py b/test/cpp/test_cpp_file.py index 27fc3e0..7005366 100644 --- a/test/cpp/test_cpp_file.py +++ b/test/cpp/test_cpp_file.py @@ -64,12 +64,12 @@ def test_cpp_class(self): is_class_member=True, is_static=True, is_const=True, - initialization_value='42')) + value='42')) my_class.add_internal_class(nested_class) my_class.add_variable(CppVariable(name="m_var1", type="int", - initialization_value='1')) + value='1')) my_class.add_variable(CppVariable(name="m_var2", type="int*")) @@ -176,13 +176,13 @@ def test_cpp_variable(self): is_class_member=False, is_static=False, is_const=True, - initialization_value='0'), + value='0'), CppVariable(name="var2", type="int", is_class_member=False, is_static=True, is_const=False, - initialization_value='0'), + value='0'), CppVariable(name="var3", type="std::string", is_class_member=False, diff --git a/test/cpp/test_cpp_variable_writer.py b/test/cpp/test_cpp_variable_writer.py index b6115e0..8f029c8 100644 --- a/test/cpp/test_cpp_variable_writer.py +++ b/test/cpp/test_cpp_variable_writer.py @@ -21,7 +21,7 @@ def test_cpp_var(self): is_class_member=False, is_static=False, is_const=True, - initialization_value='0') + value='0') variables.render_to_string(cpp) print(writer.getvalue()) self.assertEqual('const char* var1 = 0;\n', writer.getvalue()) @@ -30,7 +30,7 @@ def test_is_constexpr_const_raises(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) var = CppVariable(name="COUNT", type="int", is_class_member=True, is_const=True, - is_constexpr=True, initialization_value='0') + is_constexpr=True, value='0') self.assertRaises(ValueError, var.render_to_string, cpp) def test_is_constexpr_no_implementation_raises(self): @@ -46,7 +46,7 @@ def test_is_constexpr_render_to_string(self): type="int", is_class_member=False, is_constexpr=True, - initialization_value='0') + value='0') variables.render_to_string(cpp) self.assertIn('constexpr int COUNT = 0;', writer.getvalue()) @@ -57,7 +57,7 @@ def test_is_constexpr_render_to_string_declaration(self): type="int", is_class_member=True, is_constexpr=True, - initialization_value='0') + value='0') variables.render_to_string_declaration(cpp) self.assertIn('constexpr int COUNT = 0;', writer.getvalue()) diff --git a/test/create_assets.py b/test/create_assets.py index f2382c9..edb33d3 100644 --- a/test/create_assets.py +++ b/test/create_assets.py @@ -59,13 +59,13 @@ def generate_var(output_dir='.'): is_class_member=False, is_static=False, is_const=True, - initialization_value='0'), + value='0'), CppVariable(name="var2", type="int", is_class_member=False, is_static=True, is_const=False, - initialization_value='0'), + value='0'), CppVariable(name="var3", type="std::string", is_class_member=False, @@ -160,12 +160,12 @@ def generate_class(output_dir='.'): is_class_member=True, is_static=True, is_const=True, - initialization_value='42')) + value='42')) my_class.add_internal_class(nested_class) my_class.add_variable(CppVariable(name="m_var1", type="int", - initialization_value='1')) + value='1')) my_class.add_variable(CppVariable(name="m_var2", type="int*")) @@ -175,7 +175,7 @@ def generate_class(output_dir='.'): is_constexpr=True, is_static=True, is_class_member=True, - initialization_value=42)) + value=42)) example_class.add_variable(CppVariable( name="m_var1", diff --git a/test/java/test_java_file.py b/test/java/test_java_file.py index 5f6654f..a3554e9 100644 --- a/test/java/test_java_file.py +++ b/test/java/test_java_file.py @@ -23,7 +23,7 @@ def test_java_class(self): type='int', is_static=True, is_final=True, - initialization_value='10')) + value='10')) my_class.add_method(JavaFunction(name='getVar', return_type='int', is_static=True, @@ -52,8 +52,8 @@ def test_java_enum(self): """ java_file = JavaFile('MyEnum.java') my_enum = JavaClass(name='MyEnum') - my_enum.add_variable(JavaVariable(name='ITEM1', type='int', initialization_value='1')) - my_enum.add_variable(JavaVariable(name='ITEM2', type='int', initialization_value='2')) + my_enum.add_variable(JavaVariable(name='ITEM1', type='int', value='1')) + my_enum.add_variable(JavaVariable(name='ITEM2', type='int', value='2')) my_enum.add_method(JavaFunction(name='getItem', return_type='int', implementation='return ITEM1;')) my_enum.render_to_string(java_file) self.assertTrue(filecmp.cmpfiles('.', 'tests', 'MyEnum.java')) diff --git a/test/java/test_java_variable_writer.py b/test/java/test_java_variable_writer.py index f794140..3bfa337 100644 --- a/test/java/test_java_variable_writer.py +++ b/test/java/test_java_variable_writer.py @@ -18,7 +18,7 @@ def test_java_var(self): is_class_member=False, is_static=False, is_final=True, - initialization_value='"Hello"') + value='"Hello"') variable.render_to_string(java) self.assertEqual("final String var1 = \"Hello\";", writer.getvalue().strip()) @@ -39,7 +39,7 @@ def test_render_to_string_declaration(self): writer = io.StringIO() java = JavaFile(None, writer=writer) variable = JavaVariable(name="var1", type="String") - variable.render_to_string_declaration(java) + variable.render_to_string(java) self.assertEqual("String var1;", writer.getvalue().strip()) From a29c64e46c1daf9dff090a8ec5464a60b3406909 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 11 Jan 2024 17:27:35 +0700 Subject: [PATCH 34/51] Unit test extensive refactoring --- README.md | 2 +- src/code_generation/cpp/class_generator.py | 22 ++--- src/code_generation/cpp/function_generator.py | 18 ++-- src/code_generation/java/class_generator.py | 14 ++- .../java/function_generator.py | 8 +- test/cpp/test_cpp_array_writer.py | 8 +- test/cpp/test_cpp_class_writer.py | 16 ++-- test/cpp/test_cpp_enum_writer.py | 6 +- test/cpp/test_cpp_file.py | 6 +- test/cpp/test_cpp_function_writer.py | 18 +++- test/cpp/test_cpp_variable_writer.py | 2 +- test/create_assets.py | 6 +- test/java/test_java_array_writer.py | 8 +- test/java/test_java_class_writer.py | 87 +++++++++++++++---- test/java/test_java_enum_writer.py | 2 +- test/java/test_java_function_writer.py | 4 +- test/java/test_java_variable_writer.py | 2 +- 17 files changed, 149 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 1aaf3ec..d1cbbba 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ cpp = CodeFile('example.cpp') factorial_function = CppFunction(name='factorial', ret_type='int', is_constexpr=True, - implementation_handle=handle_to_factorial, + implementation=handle_to_factorial, documentation='/// Calculates and returns the factorial of \p n.') factorial_function.add_argument('int n') factorial_function.render_to_string(cpp) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index a094f46..3ebf541 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -26,7 +26,7 @@ def handle(cpp): cpp('return m_var;') cpp_class.add_method(CppFunction(name = "GetVar", ret_type = 'size_t', is_static = True, - implementation_handle = handle)) + implementation = handle)) // Generated C++ declaration struct MyClass @@ -58,7 +58,7 @@ class CppMethod(CppFunction): is_virtual - boolean, virtual method postfix, could not be static is_pure_virtual - boolean, ' = 0' method postfix, could not be static documentation - string, '/// Example doxygen' - implementation_handle - reference to a function that receives 'self' and C++ code generator handle + implementation - reference to a function that receives 'self' and C++ code generator handle (see code_generator.cpp) and generates method body without braces Ex. #Python code @@ -66,7 +66,7 @@ def functionBody(self, cpp): cpp('return 42;') f1 = CppFunction(name = 'GetAnswer', ret_type = 'int', documentation = '// Generated code', - implementation_handle = functionBody) + implementation = functionBody) // Generated code int MyClass::GetAnswer() @@ -83,7 +83,7 @@ def functionBody(self, cpp): cpp('return 42;') 'is_const', 'is_override', 'is_final', - 'implementation_handle', + 'implementation', 'documentation'} | CppLanguageElement.availablePropertiesNames def __init__(self, **properties): @@ -98,7 +98,7 @@ def __init__(self, **properties): self.is_override = False self.is_final = False self.arguments = [] - self.implementation_handle = properties.get('implementation_handle') + self.implementation = properties.get('implementation') self.documentation = properties.get('documentation') # check properties @@ -196,9 +196,9 @@ def _sanity_check(self): raise ValueError(f'Pure virtual method {self.name} is also a virtual method') if not self.ref_to_parent: raise ValueError(f'Method {self.name} object must be a child of CppClass') - if self.is_constexpr and self.implementation_handle is None: + if self.is_constexpr and self.implementation is None: raise ValueError(f'Method {self.name} object must be initialized when "constexpr"') - if self.is_pure_virtual and self.implementation_handle is not None: + if self.is_pure_virtual and self.implementation is not None: raise ValueError(f'Pure virtual method {self.name} could not be implemented') def add_argument(self, argument): @@ -217,8 +217,8 @@ def implementation(self, cpp): """ The method calls Python function that creates C++ method body if handle exists """ - if self.implementation_handle is not None: - self.implementation_handle(cpp) + if self.implementation is not None: + self.implementation(cpp) def declaration(self): """ @@ -289,12 +289,12 @@ def render_to_string_implementation(self, cpp): { ... } - Generates method body if self.implementation_handle property exists + Generates method body if self.implementation property exists """ # check all properties for the consistency self._sanity_check() - if self.implementation_handle is None: + if self.implementation is None: raise RuntimeError(f'No implementation handle for the method {self.name}') if self.documentation and not self.is_constexpr: diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index df407b1..160e9e0 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -10,7 +10,7 @@ class CppFunction(CppLanguageElement): ret_type - string, return value for the method ('void', 'int'). Could not be set for constructors is_constexpr - boolean, const method prefix documentation - string, '/// Example doxygen' - implementation_handle - reference to a function that receives 'self' and C++ code generator handle + implementation - reference to a function that receives 'self' and C++ code generator handle (see code_generator.cpp) and generates method body without braces Ex. #Python code @@ -18,7 +18,7 @@ def functionBody(self, cpp): cpp('return 42;') f1 = CppFunction(name = 'GetAnswer', ret_type = 'int', documentation = '// Generated code', - implementation_handle = functionBody) + implementation = functionBody) // Generated code int GetAnswer() @@ -28,7 +28,7 @@ def functionBody(self, cpp): cpp('return 42;') """ availablePropertiesNames = {'ret_type', 'is_constexpr', - 'implementation_handle', + 'implementation', 'documentation'} | CppLanguageElement.availablePropertiesNames def __init__(self, **properties): @@ -36,7 +36,7 @@ def __init__(self, **properties): # e.g. 'int* a', 'const string& s', 'size_t sz = 10' self.arguments = [] self.ret_type = None - self.implementation_handle = None + self.implementation = None self.documentation = None self.is_constexpr = False @@ -51,7 +51,7 @@ def _sanity_check(self): """ Check whether attributes compose a correct C++ code """ - if self.is_constexpr and self.implementation_handle is None: + if self.is_constexpr and self.implementation is None: raise ValueError(f'Constexpr function {self.name} must have implementation') def _render_constexpr(self): @@ -77,8 +77,8 @@ def implementation(self, cpp): """ The method calls Python function that creates C++ method body if handle exists """ - if self.implementation_handle is not None: - self.implementation_handle(self, cpp) + if self.implementation is not None: + self.implementation(self, cpp) def declaration(self): """ @@ -133,9 +133,9 @@ def render_to_string_implementation(self, cpp): { ... } - Generates method body if self.implementation_handle property exists + Generates method body if self.implementation property exists """ - if self.implementation_handle is None: + if self.implementation is None: raise RuntimeError(f'No implementation handle for the function {self.name}') # check all properties for the consistency diff --git a/src/code_generation/java/class_generator.py b/src/code_generation/java/class_generator.py index 602895f..02ce5c2 100644 --- a/src/code_generation/java/class_generator.py +++ b/src/code_generation/java/class_generator.py @@ -48,14 +48,22 @@ def __init__(self, **properties): self.internal_class_elements = [] self.internal_variable_elements = [] + self.internal_enum_elements = [] self.internal_method_elements = [] def _parent_class(self): return self.parent_class if self.parent_class else "" - def render_to_string(self, java): + def _render_documentation(self, java): if self.documentation: - java(f"/**\n{self.documentation}\n*/") + docstring_lines = ["/**"] + docstring_lines.extend([f" * {line}" for line in self.documentation.splitlines()]) + docstring_lines.append(" */") + for line in docstring_lines: + java(line) + + def render_to_string(self, java): + self._render_documentation(java) with java.block(f"public {self._render_class_type()} {self.name} {self.inherits()}"): self.class_interface(java) self.private_class_members(java) @@ -88,7 +96,7 @@ def _render_variables_declaration(self, java): def _render_methods_declaration(self, java): for method_item in self.internal_method_elements: - method_item.render_to_string_declaration(java) + method_item.render_to_string(java) java.newline() def private_class_members(self, java): diff --git a/src/code_generation/java/function_generator.py b/src/code_generation/java/function_generator.py index dff5ec1..883de3f 100644 --- a/src/code_generation/java/function_generator.py +++ b/src/code_generation/java/function_generator.py @@ -18,7 +18,7 @@ class JavaFunction(JavaLanguageElement): 'is_strictfp', 'access_specifier', 'documentation', - 'implementation_handle' + 'implementation' } | JavaLanguageElement.available_properties_names def __init__(self, **properties): @@ -33,7 +33,7 @@ def __init__(self, **properties): self.is_strictfp = False self.documentation = '' self.access_specifier = 'public' - self.implementation_handle = None + self.implementation = None input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(JavaFunction, self).__init__(properties) @@ -79,5 +79,5 @@ def render_to_string(self, java): if self.is_strictfp: java('strictfp', end=' ') with java.block(f'{self.access_specifier} {self.return_type} {self.name}({self.args_str()})'): - if self.implementation_handle: - self.implementation_handle(java) + if self.implementation: + self.implementation(java) diff --git a/test/cpp/test_cpp_array_writer.py b/test/cpp/test_cpp_array_writer.py index c2cd3c1..12a4df2 100644 --- a/test/cpp/test_cpp_array_writer.py +++ b/test/cpp/test_cpp_array_writer.py @@ -15,7 +15,7 @@ class TestCppArrayStringIo(unittest.TestCase): Test C++ array generation by writing to StringIO """ - def test_cpp_array(self): + def test_simple_case(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) arr = CppArray(name="my_array", type="int", array_size=5) @@ -24,7 +24,7 @@ def test_cpp_array(self): expected_output = "int my_array[5] = {1, 2, 0};" self.assertEqual(expected_output, writer.getvalue().strip()) - def test_cpp_array_with_newline_align(self): + def test_with_newline_align(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) arr = CppArray(name="my_array", type="int", array_size=5, newline_align=True) @@ -41,7 +41,7 @@ def test_cpp_array_with_newline_align(self): generated_output_normalized = normalize_lines(generated_output) self.assertEqual(expected_output_normalized, generated_output_normalized) - def test_cpp_array_declaration(self): + def test_declaration(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) arr = CppArray(name="my_class_member_array", type="int", array_size=None, is_class_member=True) @@ -49,7 +49,7 @@ def test_cpp_array_declaration(self): expected_output = "int my_class_member_array[];" self.assertEqual(expected_output, writer.getvalue().strip()) - def test_cpp_array_implementation(self): + def test_implementation(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) arr = CppArray(name="m_my_static_array", type="int", array_size=None, diff --git a/test/cpp/test_cpp_class_writer.py b/test/cpp/test_cpp_class_writer.py index 913f4d7..ab4e5a3 100644 --- a/test/cpp/test_cpp_class_writer.py +++ b/test/cpp/test_cpp_class_writer.py @@ -17,7 +17,7 @@ class TestCppClassStringIo(unittest.TestCase): Test C++ class generation by writing to StringIO """ - def test_cpp_class(self): + def test_simple_case(self): writer = io.StringIO() cpp_file = CppFile(None, writer=writer) @@ -36,7 +36,7 @@ def test_cpp_class(self): ) # Define a function body for the CppMethod - def handle(cpp): + def body(cpp): cpp('return m_var;') # Add a CppMethod to the class @@ -45,7 +45,7 @@ def handle(cpp): name="GetVar", ret_type="size_t", is_static=True, - implementation_handle=handle + implementation=body ) ) @@ -75,7 +75,7 @@ def handle(cpp): debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_cpp_class_with_inheritance(self): + def test_with_inheritance(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) @@ -102,7 +102,7 @@ def test_cpp_class_with_inheritance(self): name="GetVar", ret_type="int", is_static=True, - implementation_handle=lambda cpp_file: cpp_file("return m_var;") + implementation=lambda cpp_file: cpp_file("return m_var;") ) ) @@ -142,7 +142,7 @@ class ChildClass : public ParentClass self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_cpp_class_with_nested_classes(self): + def test_with_nested_classes(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) @@ -177,7 +177,7 @@ class NestedClass debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_cpp_class_with_enum(self): + def test_with_enum(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) @@ -218,7 +218,7 @@ class MyClass debug_dump(expected_output_normalized, actual_output_normalized, "cpp") self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_cpp_class_with_array(self): + def test_with_array(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) diff --git a/test/cpp/test_cpp_enum_writer.py b/test/cpp/test_cpp_enum_writer.py index 9b5425e..95f3f68 100644 --- a/test/cpp/test_cpp_enum_writer.py +++ b/test/cpp/test_cpp_enum_writer.py @@ -15,7 +15,7 @@ class TestCppEnumStringIo(unittest.TestCase): Test C++ enum generation by writing to StringIO """ - def test_cpp_enum(self): + def test_simple_case(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) enum = CppEnum(name="Items") @@ -37,7 +37,7 @@ def test_cpp_enum(self): self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_cpp_enum_with_prefix(self): + def test_with_prefix(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) enum = CppEnum(name="Items", prefix="Prefix") @@ -59,7 +59,7 @@ def test_cpp_enum_with_prefix(self): self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_cpp_enum_class(self): + def test_class(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) enum = CppEnum(name="Items", enum_class=True) diff --git a/test/cpp/test_cpp_file.py b/test/cpp/test_cpp_file.py index 7005366..5c69206 100644 --- a/test/cpp/test_cpp_file.py +++ b/test/cpp/test_cpp_file.py @@ -93,12 +93,12 @@ def virtual_method_body(_, cpp): my_class.add_method(CppClass.CppMethod(name="GetParam", ret_type="int", is_const=True, - implementation_handle=const_method_body)) + implementation=const_method_body)) my_class.add_method(CppClass.CppMethod(name="VirtualMethod", ret_type="int", is_virtual=True, - implementation_handle=virtual_method_body)) + implementation=virtual_method_body)) my_class.add_method(CppClass.CppMethod(name="PureVirtualMethod", ret_type="void", @@ -149,7 +149,7 @@ def function_body(_, cpp1): functions = [CppFunction(name='GetParam', ret_type='int'), CppFunction(name='Calculate', ret_type='void'), - CppFunction(name='GetAnswer', ret_type='int', implementation_handle=function_body)] + CppFunction(name='GetAnswer', ret_type='int', implementation=function_body)] for func in functions: func.render_to_string(hpp) diff --git a/test/cpp/test_cpp_function_writer.py b/test/cpp/test_cpp_function_writer.py index a7383b2..8929fe7 100644 --- a/test/cpp/test_cpp_function_writer.py +++ b/test/cpp/test_cpp_function_writer.py @@ -19,6 +19,18 @@ class TestCppFunctionStringIo(unittest.TestCase): Test C++ function generation by writing to StringIO """ + def test_simple_case(self): + writer = io.StringIO() + cpp = CppFile(None, writer=writer) + func = CppFunction(name="factorial", ret_type="int", implementation=handle_to_factorial) + func.add_argument('int n') + func.render_to_string(cpp) + self.assertIn(dedent("""\ + int factorial(int n) + { + \treturn n < 1 ? 1 : (n * factorial(n - 1)); + }"""), writer.getvalue()) + def test_is_constexpr_no_implementation_raises(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) @@ -29,7 +41,7 @@ def test_is_constexpr_render_to_string(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) func = CppFunction(name="factorial", ret_type="int", - implementation_handle=handle_to_factorial, is_constexpr=True) + implementation=handle_to_factorial, is_constexpr=True) func.add_argument('int n') func.render_to_string(cpp) self.assertIn(dedent("""\ @@ -42,7 +54,7 @@ def test_is_constexpr_render_to_string_declaration(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) func = CppFunction(name="factorial", ret_type="int", - implementation_handle=handle_to_factorial, is_constexpr=True) + implementation=handle_to_factorial, is_constexpr=True) func.add_argument('int n') func.render_to_string_declaration(cpp) self.assertIn(dedent("""\ @@ -55,7 +67,7 @@ def test_docstring_example(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) factorial_function = CppFunction(name='factorial', ret_type='int', is_constexpr=True, - implementation_handle=handle_to_factorial, + implementation=handle_to_factorial, documentation='/// Calculates and returns the factorial of p @n.') factorial_function.add_argument('int n') factorial_function.render_to_string(cpp) diff --git a/test/cpp/test_cpp_variable_writer.py b/test/cpp/test_cpp_variable_writer.py index 8f029c8..d1f608e 100644 --- a/test/cpp/test_cpp_variable_writer.py +++ b/test/cpp/test_cpp_variable_writer.py @@ -13,7 +13,7 @@ class TestCppVariableStringIo(unittest.TestCase): Test C++ variable generation by writing to StringIO """ - def test_cpp_var(self): + def test_simple_case(self): writer = io.StringIO() cpp = CppFile(None, writer=writer) variables = CppVariable(name="var1", diff --git a/test/create_assets.py b/test/create_assets.py index edb33d3..10e30ee 100644 --- a/test/create_assets.py +++ b/test/create_assets.py @@ -120,7 +120,7 @@ def function_body(_, cpp1): functions = [CppFunction(name='GetParam', ret_type='int'), CppFunction(name='Calculate', ret_type='void'), - CppFunction(name='GetAnswer', ret_type='int', implementation_handle=function_body), + CppFunction(name='GetAnswer', ret_type='int', implementation=function_body), CppFunction(name='Help', ret_type='char *', documentation='/// Returns the help documentation.'), ] for func in functions: @@ -200,7 +200,7 @@ def method_body(_, cpp1): ret_type="int", is_method=True, is_const=True, - implementation_handle=method_body)) + implementation=method_body)) my_class.add_method(CppFunction(name="VirtualMethod", ret_type="int", @@ -238,7 +238,7 @@ def handle_to_factorial(_, cpp_file): cpp_file('return n < 1 ? 1 : (n * factorial(n - 1));') func = CppFunction(name="factorial", ret_type="int", - implementation_handle=handle_to_factorial, + implementation=handle_to_factorial, is_constexpr=True) func.add_argument('int n') func.render_to_string(cpp) diff --git a/test/java/test_java_array_writer.py b/test/java/test_java_array_writer.py index 1c0bfa9..69a5e0a 100644 --- a/test/java/test_java_array_writer.py +++ b/test/java/test_java_array_writer.py @@ -11,7 +11,7 @@ class TestJavaArrayStringIo(unittest.TestCase): Test Java array generation by writing to StringIO """ - def test_java_array(self): + def test_simple_case(self): writer = io.StringIO() java = JavaFile(None, writer=writer) arr = JavaArray(name="myArray", type="int", values=["1", "2", "0"]) @@ -25,7 +25,7 @@ def test_java_array(self): debug_dump(expected_output_normalized, actual_output_normalized, "java") self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_java_array_with_empty_values(self): + def test_with_empty_values(self): writer = io.StringIO() java = JavaFile(None, writer=writer) arr = JavaArray(name="emptyArray", type='int', values=[]) @@ -39,7 +39,7 @@ def test_java_array_with_empty_values(self): debug_dump(expected_output_normalized, actual_output_normalized, "java") self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_java_array_add_item(self): + def test_add_item(self): writer = io.StringIO() java = JavaFile(None, writer=writer) arr = JavaArray(name="myArray", type='String', quoted=True) @@ -55,7 +55,7 @@ def test_java_array_add_item(self): debug_dump(expected_output_normalized, actual_output_normalized, "java") self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_java_array_add_items(self): + def test_add_items(self): writer = io.StringIO() java = JavaFile(None, writer=writer) arr = JavaArray(name="myArray", type='String', quoted=True) diff --git a/test/java/test_java_class_writer.py b/test/java/test_java_class_writer.py index 2124d0c..03ffe25 100644 --- a/test/java/test_java_class_writer.py +++ b/test/java/test_java_class_writer.py @@ -6,6 +6,7 @@ from code_generation.java.class_generator import JavaClass from code_generation.java.variable_generator import JavaVariable from code_generation.java.function_generator import JavaFunction +from test.comparing_tools import normalize_code, debug_dump, is_debug class TestJavaClassStringIo(unittest.TestCase): @@ -13,51 +14,99 @@ class TestJavaClassStringIo(unittest.TestCase): Test Java class generation by writing to StringIO """ - def test_java_class(self): + def test_simple_case(self): writer = io.StringIO() java = JavaFile(None, writer=writer) my_class = JavaClass(name="MyClass") var1 = JavaVariable(name="myVariable", type="int", value=10) - method1 = JavaFunction(name="getVar", return_type="int", implementation="return myVariable;") + + def body(j): + j("return myVariable;") + + method1 = JavaFunction(name="getVar", return_type="int", implementation=body) my_class.add_variable(var1) my_class.add_method(method1) my_class.render_to_string(java) expected_output = dedent("""\ - public class MyClass + public class MyClass { - int myVariable = 10; - int getVar() + public int getVar() { return myVariable; } - } - """) - self.assertEqual(writer.getvalue(), expected_output) + int myVariable = 10; + }""") + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_java_class_with_parent_class(self): + def test_with_parent_class(self): writer = io.StringIO() java = JavaFile(None, writer=writer) my_class = JavaClass(name="MyClass", parent_class="ParentClass") my_class.render_to_string(java) - expected_output = "public class MyClass extends ParentClass {" - self.assertTrue(writer.getvalue().strip().startswith(expected_output)) + expected_line = "public class MyClass extends ParentClass" + self.assertTrue(writer.getvalue().strip().startswith(expected_line)) + expected_output = dedent("""\ + public class MyClass extends ParentClass + { + }""") + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) - def test_java_class_with_documentation(self): + def test_with_documentation(self): writer = io.StringIO() java = JavaFile(None, writer=writer) - my_class = JavaClass(name="MyClass", documentation="/** Example Javadoc */") + my_class = JavaClass(name="MyClass", documentation="Example Javadoc") my_class.render_to_string(java) + self.assertTrue(writer.getvalue().strip().startswith('/**')) + expected_output = dedent("""\ /** - * Example Javadoc - */ - public class MyClass - {""") - self.assertTrue(writer.getvalue().strip().startswith(expected_output)) + * Example Javadoc + */ + public class MyClass + { + }""") + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) + + def test_multiline_documentation(self): + writer = io.StringIO() + java = JavaFile(None, writer=writer) + my_class = JavaClass(name="MyClass", documentation="Example multiline Javadoc\nSecond line") + my_class.render_to_string(java) + self.assertTrue(writer.getvalue().strip().startswith('/**')) + + expected_output = dedent("""\ + /** + * Example multiline Javadoc + * Second line + */ + public class MyClass + { + }""") + actual_output = writer.getvalue().strip() + expected_output_normalized = normalize_code(expected_output) + actual_output_normalized = normalize_code(actual_output) + if is_debug(): + debug_dump(expected_output_normalized, actual_output_normalized, "java") + self.assertEqual(expected_output_normalized, actual_output_normalized) def test_missing_name(self): my_class = JavaClass() - self.assertRaises(RuntimeError, my_class.render_to_string, None) + self.assertRaises(AttributeError, my_class.render_to_string, None) if __name__ == "__main__": diff --git a/test/java/test_java_enum_writer.py b/test/java/test_java_enum_writer.py index f2a5803..10e12e0 100644 --- a/test/java/test_java_enum_writer.py +++ b/test/java/test_java_enum_writer.py @@ -11,7 +11,7 @@ class TestJavaEnumStringIo(unittest.TestCase): Test Java enum generation by writing to StringIO """ - def test_java_enum(self): + def test_simple_case(self): writer = io.StringIO() java = JavaFile(None, writer=writer) enum = JavaEnum(name="Color", values=["RED", "GREEN", "BLUE"]) diff --git a/test/java/test_java_function_writer.py b/test/java/test_java_function_writer.py index 4028209..3ac7c18 100644 --- a/test/java/test_java_function_writer.py +++ b/test/java/test_java_function_writer.py @@ -16,13 +16,13 @@ class TestJavaFunctionStringIo(unittest.TestCase): Test Java method generation by writing to StringIO """ - def test_java_method(self): + def test_simple_case(self): writer = io.StringIO() java = JavaFile(None, writer=writer) method = JavaFunction(name="calculateSum", return_type="int", - implementation_handle=handle_to_function) + implementation=handle_to_function) method.add_argument("int a") method.add_argument("int b") method.render_to_string(java) diff --git a/test/java/test_java_variable_writer.py b/test/java/test_java_variable_writer.py index 3bfa337..04e67c7 100644 --- a/test/java/test_java_variable_writer.py +++ b/test/java/test_java_variable_writer.py @@ -10,7 +10,7 @@ class TestJavaVariableStringIo(unittest.TestCase): Test Java variable generation by writing to StringIO """ - def test_java_var(self): + def test_simple_case(self): writer = io.StringIO() java = JavaFile(None, writer=writer) variable = JavaVariable(name="var1", From 0233b3f8f8ea778377cb3685241806add4921466 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 11 Jan 2024 18:06:06 +0700 Subject: [PATCH 35/51] Reformat code with Black --- .github/workflows/python-app.yml | 20 +- run_tests.py | 58 ++++++ src/code_generation/core/code_file.py | 39 ++-- src/code_generation/core/code_style.py | 13 +- src/code_generation/cpp/array_generator.py | 105 +++++++---- src/code_generation/cpp/class_generator.py | 173 +++++++++++------- src/code_generation/cpp/enum_generator.py | 25 ++- src/code_generation/cpp/file_writer.py | 2 +- src/code_generation/cpp/function_generator.py | 41 +++-- src/code_generation/cpp/language_element.py | 33 ++-- src/code_generation/cpp/variable_generator.py | 85 ++++++--- src/code_generation/html/html_element.py | 26 +-- src/code_generation/html/html_file.py | 17 +- src/code_generation/java/array_generator.py | 41 +++-- src/code_generation/java/class_generator.py | 28 +-- src/code_generation/java/enum_generator.py | 27 +-- src/code_generation/java/file_writer.py | 1 - .../java/function_generator.py | 114 +++++++----- src/code_generation/java/language_element.py | 38 ++-- .../java/variable_generator.py | 96 +++++----- src/example.py | 111 ++++++----- test/cpp/test_cpp_function_writer.py | 2 +- 22 files changed, 673 insertions(+), 422 deletions(-) create mode 100644 run_tests.py diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index e77a8bd..55c6be3 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -34,13 +34,13 @@ jobs: - name: Run test run: | python release_package.py --mode install - python test.cpp.test_cpp_array_writer - python test.cpp.test_cpp_enum_writer - python test.cpp.test_cpp_class_writer - python test.cpp.test_cpp_function_writer - python test.cpp.test_cpp_variable_writer - python test.java.test_java_array_writer - python test.java.test_java_enum_writer - python test.java.test_java_class_writer - python test.java.test_java_function_writer - python test.java.test_java_variable_writer + python -m test.cpp.test_cpp_array_writer + python -m test.cpp.test_cpp_enum_writer + python -m test.cpp.test_cpp_class_writer + python -m test.cpp.test_cpp_function_writer + python -m test.cpp.test_cpp_variable_writer + python -m test.java.test_java_array_writer + python -m test.java.test_java_enum_writer + python -m test.java.test_java_class_writer + python -m test.java.test_java_function_writer + python -m test.java.test_java_variable_writer diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 0000000..e0a46e2 --- /dev/null +++ b/run_tests.py @@ -0,0 +1,58 @@ +__doc__ = """Run following tests: + python test.cpp.test_cpp_array_writer + python test.cpp.test_cpp_enum_writer + python test.cpp.test_cpp_class_writer + python test.cpp.test_cpp_function_writer + python test.cpp.test_cpp_variable_writer + python test.java.test_java_array_writer + python test.java.test_java_enum_writer + python test.java.test_java_class_writer + python test.java.test_java_function_writer + python test.java.test_java_variable_writer +""" + +import subprocess +import sys + + +def run_tests(tests): + for test_file in tests: + print(f"Running test {test_file}") + subprocess.run([sys.executable, '-m', f'{test_file}']) + + +def run_linters(): + linters = ["pylint", "mypy", "flake8"] + for linter in linters: + print(f"Running {linter}") + subprocess.run([linter, "src"]) + + +def run_black(): + print("Running black") + subprocess.run(["black", "src"]) + + +if __name__ == "__main__": + command_line_args = sys.argv[1:] + test_files = [ + "test.cpp.test_cpp_array_writer", + "test.cpp.test_cpp_enum_writer", + "test.cpp.test_cpp_class_writer", + "test.cpp.test_cpp_function_writer", + "test.cpp.test_cpp_variable_writer", + "test.java.test_java_array_writer", + "test.java.test_java_enum_writer", + "test.java.test_java_class_writer", + "test.java.test_java_function_writer", + "test.java.test_java_variable_writer" + ] + + for _ in range(1, 3): + run_tests(test_files) + + if 'black' in command_line_args: + run_black() + + if 'lint' in command_line_args: + run_linters() diff --git a/src/code_generation/core/code_file.py b/src/code_generation/core/code_file.py index b93c36b..0ad1ccc 100644 --- a/src/code_generation/core/code_file.py +++ b/src/code_generation/core/code_file.py @@ -44,38 +44,39 @@ class A class CodeFile: """ The class is a main instrument of code generation - + It can generate plain strings using functional calls Ex: code = CodeFile(python_src_file) code('import os, sys') - + Is supports 'with' semantic for indentation blocks creation Ex: # Python code with code('for i in range(0, 5):'): code('lst.append(i*i)') - + # Generated code: for i in range(0, 5): lst.append(i*i) - + It can append code to the last line: Ex. # Python code cpp = CodeFile('ex.cpp') cpp('class Derived') cpp.append(' : public Base') - + // Generated code class Derived : public Base - + And finally, it can insert a number of empty lines cpp.newline(3) """ + # Current formatting style (assigned as a class attribute to generate all files uniformly) Formatter = ANSICodeStyle - + def __init__(self, filename, writer=None): """ Creates a new source file @@ -89,28 +90,32 @@ def __init__(self, filename, writer=None): self.out = writer else: self.out = open(filename, "w") - + def close(self): """ File created, just close the handle """ self.out.close() self.out = None - + def write(self, text, indent=0, endline=True): """ Write a new line with line ending """ - self.out.write('{0}{1}{2}'.format(self.Formatter.indent * (self.current_indent+indent), - text, - self.Formatter.endline if endline else '')) - + self.out.write( + "{0}{1}{2}".format( + self.Formatter.indent * (self.current_indent + indent), + text, + self.Formatter.endline if endline else "", + ) + ) + def append(self, x): """ Append to the existing line without line ending """ self.out.write(x) - + def __call__(self, text, indent=0, endline=True): """ Supports 'object()' semantic, i.e. @@ -118,8 +123,8 @@ def __call__(self, text, indent=0, endline=True): inserts appropriate line """ self.write(text, indent, endline) - - def block(self, text, postfix=''): + + def block(self, text, postfix=""): """ Returns a stub for C++ {} close Supports 'with' semantic, i.e. @@ -138,4 +143,4 @@ def newline(self, n=1): Insert one or several empty lines """ for _ in range(n): - self.write('') + self.write("") diff --git a/src/code_generation/core/code_style.py b/src/code_generation/core/code_style.py index 3f49a5f..67bc327 100644 --- a/src/code_generation/core/code_style.py +++ b/src/code_generation/core/code_style.py @@ -15,10 +15,10 @@ class ANSICodeStyle: # EOL symbol endline = "\n" - + # Tab (indentation) symbol indent = "\t" - + def __init__(self, owner, text, postfix): """ @param: owner - CodeFile where text is written to @@ -32,7 +32,7 @@ def __init__(self, owner, text, postfix): self.owner.write("".join(text)) self.owner.last = self self.postfix = postfix - + def __enter__(self): """ Open code block @@ -61,6 +61,7 @@ class HTMLStyle: // HTML content """ + # EOL symbol endline = "\n" @@ -82,10 +83,12 @@ def __init__(self, owner, element, *attrs, **kwattrs): attributes = "" if "attributes" in kwattrs: if isinstance(kwattrs["attributes"], dict): - attributes = "".join(f' {key}="{value}"' for key, value in kwattrs["attributes"].items()) + attributes = "".join( + f' {key}="{value}"' for key, value in kwattrs["attributes"].items() + ) del kwattrs["attributes"] else: - attributes = "".join(f' {attr}' for attr in attrs) + attributes = "".join(f" {attr}" for attr in attrs) attributes += "".join(f' {key}="{value}"' for key, value in kwattrs.items()) self.attributes = attributes self.owner.last = self diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index b60e605..8f03f92 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -1,4 +1,8 @@ -from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.language_element import ( + CppLanguageElement, + CppDeclaration, + CppImplementation, +) # noinspection PyUnresolvedReferences @@ -28,15 +32,18 @@ class MyClass NOTE: versions 2.0+ of CodeGenerator support boolean properties without "is_" suffix, but old versions preserved for backward compatibility """ - availablePropertiesNames = {'type', - 'is_static', - 'static', - 'is_const', - 'const', - 'is_class_member', - 'class_member', - 'array_size', - 'newline_align'} | CppLanguageElement.availablePropertiesNames + + availablePropertiesNames = { + "type", + "is_static", + "static", + "is_const", + "const", + "is_class_member", + "class_member", + "array_size", + "newline_align", + } | CppLanguageElement.availablePropertiesNames def __init__(self, **properties): self.is_static = False @@ -51,53 +58,55 @@ def __init__(self, **properties): input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(CppArray, self).__init__(properties) - self.init_class_properties(current_class_properties=self.availablePropertiesNames, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.availablePropertiesNames, + input_properties_dict=properties, + ) def _sanity_check(self): """ Check if all required properties are set """ if not self.type: - raise RuntimeError('Array type is not set') + raise RuntimeError("Array type is not set") if not self.name: - raise RuntimeError('Array name is not set') + raise RuntimeError("Array name is not set") if self.is_class_member and not self.name: - raise RuntimeError('Class member array name is not set') + raise RuntimeError("Class member array name is not set") def _render_static(self): """ @return: 'static' prefix if required """ - return 'static ' if self.is_static else '' + return "static " if self.is_static else "" def _render_const(self): """ @return: 'const' prefix if required """ - return 'const ' if self.is_const else '' + return "const " if self.is_const else "" def _render_size(self): """ @return: array size """ - return self.array_size if self.array_size else '' + return self.array_size if self.array_size else "" def _render_content(self): """ @return: array items if any """ - return ', '.join(self.items) if self.items else 'nullptr' + return ", ".join(self.items) if self.items else "nullptr" def _render_value(self, cpp): """ Render to string array items """ if not self.items: - raise RuntimeError('Empty arrays do not supported') + raise RuntimeError("Empty arrays do not supported") for item in self.items[:-1]: - cpp('{0},'.format(item)) - cpp('{0}'.format(self.items[-1])) + cpp("{0},".format(item)) + cpp("{0}".format(self.items[-1])) def declaration(self): """ @@ -141,17 +150,24 @@ def render_to_string(self, cpp): """ self._sanity_check() if self.is_class_member and not (self.is_static and self.is_const): - raise RuntimeError('For class member variables use definition() and declaration() methods') + raise RuntimeError( + "For class member variables use definition() and declaration() methods" + ) # newline-formatting of array elements makes sense only if array is not empty if self.newline_align and self.items: - with cpp.block(f'{self._render_static()}{self._render_const()}{self.type} ' - f'{self.name}[{self._render_size()}] = ', ';'): + with cpp.block( + f"{self._render_static()}{self._render_const()}{self.type} " + f"{self.name}[{self._render_size()}] = ", + ";", + ): # render array items self._render_value(cpp) else: - cpp(f'{self._render_static()}{self._render_const()}{self.type} ' - f'{self.name}[{self._render_size()}] = {{{self._render_content()}}};') + cpp( + f"{self._render_static()}{self._render_const()}{self.type} " + f"{self.name}[{self._render_size()}] = {{{self._render_content()}}};" + ) def render_to_string_declaration(self, cpp): """ @@ -163,12 +179,16 @@ def render_to_string_declaration(self, cpp): """ self._sanity_check() if not self.is_class_member: - raise RuntimeError('For automatic variable use its render_to_string() method') - cpp(f'{self._render_static()}' - f'{self._render_const()}' - f'{self.type} ' - f'{self.name}' - f'[{self._render_size()}];') + raise RuntimeError( + "For automatic variable use its render_to_string() method" + ) + cpp( + f"{self._render_static()}" + f"{self._render_const()}" + f"{self.type} " + f"{self.name}" + f"[{self._render_size()}];" + ) def render_to_string_implementation(self, cpp): """ @@ -185,19 +205,26 @@ def render_to_string_implementation(self, cpp): """ self._sanity_check() if not self.is_class_member: - raise RuntimeError('For automatic variable use its render_to_string() method') + raise RuntimeError( + "For automatic variable use its render_to_string() method" + ) # generate definition for the static class member arrays only # other types are not supported if not self.is_static: - raise RuntimeError('Only static arrays as class members are supported') + raise RuntimeError("Only static arrays as class members are supported") # newline-formatting of array elements makes sense only if array is not empty if self.newline_align and self.items: - with cpp.block(f'{self._render_const()}{self.type} ' - f'{self.fully_qualified_name()}[{self._render_size()}] = ', ';'): + with cpp.block( + f"{self._render_const()}{self.type} " + f"{self.fully_qualified_name()}[{self._render_size()}] = ", + ";", + ): # render array items self._render_value(cpp) else: - cpp(f'{self._render_const()}{self.type} ' - f'{self.fully_qualified_name()}[{self._render_size()}] = {{{self._render_content()}}};') + cpp( + f"{self._render_const()}{self.type} " + f"{self.fully_qualified_name()}[{self._render_size()}] = {{{self._render_content()}}};" + ) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 3ebf541..f9f6509 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -1,4 +1,8 @@ -from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.language_element import ( + CppLanguageElement, + CppDeclaration, + CppImplementation, +) from code_generation.cpp.function_generator import CppFunction from textwrap import dedent @@ -43,9 +47,12 @@ def handle(cpp): cpp('return m_var;') return m_var; } """ - availablePropertiesNames = {'is_struct', - 'documentation', - 'parent_class'} | CppLanguageElement.availablePropertiesNames + + availablePropertiesNames = { + "is_struct", + "documentation", + "parent_class", + } | CppLanguageElement.availablePropertiesNames class CppMethod(CppFunction): """ @@ -74,17 +81,20 @@ def functionBody(self, cpp): cpp('return 42;') return 42; } """ - availablePropertiesNames = {'ret_type', - 'is_static', - 'is_constexpr', - 'is_virtual', - 'is_inline', - 'is_pure_virtual', - 'is_const', - 'is_override', - 'is_final', - 'implementation', - 'documentation'} | CppLanguageElement.availablePropertiesNames + + availablePropertiesNames = { + "ret_type", + "is_static", + "is_constexpr", + "is_virtual", + "is_inline", + "is_pure_virtual", + "is_const", + "is_override", + "is_final", + "implementation", + "documentation", + } | CppLanguageElement.availablePropertiesNames def __init__(self, **properties): # arguments are plain strings @@ -98,108 +108,118 @@ def __init__(self, **properties): self.is_override = False self.is_final = False self.arguments = [] - self.implementation = properties.get('implementation') - self.documentation = properties.get('documentation') + self.implementation = properties.get("implementation") + self.documentation = properties.get("documentation") # check properties input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super().__init__(**properties) - self.init_class_properties(current_class_properties=self.availablePropertiesNames, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.availablePropertiesNames, + input_properties_dict=properties, + ) def _render_static(self): """ Before function name, declaration only Static functions can't be const, virtual or pure virtual """ - return 'static ' if self.is_static else '' + return "static " if self.is_static else "" def _render_constexpr(self): """ Before function name, declaration only Constexpr functions can't be const, virtual or pure virtual """ - return 'constexpr ' if self.is_constexpr else '' + return "constexpr " if self.is_constexpr else "" def _render_virtual(self): """ Before function name, could be in declaration or definition Virtual functions can't be static or constexpr """ - return 'virtual ' if self.is_virtual else '' + return "virtual " if self.is_virtual else "" def _render_inline(self): """ Before function name, could be in declaration or definition Inline functions can't be static, virtual or constexpr """ - return 'inline ' if self.is_inline else '' + return "inline " if self.is_inline else "" def _render_ret_type(self): """ Return type, could be in declaration or definition """ - return self.ret_type if self.ret_type else '' + return self.ret_type if self.ret_type else "" def _render_pure(self): """ After function name, declaration only Pure virtual functions must be virtual """ - return ' = 0' if self.is_pure_virtual else '' + return " = 0" if self.is_pure_virtual else "" def _render_const(self): """ After function name, could be in declaration or definition Const functions can't be static, virtual or constexpr """ - return ' const' if self.is_const else '' + return " const" if self.is_const else "" def _render_override(self): """ After function name, could be in declaration or definition Override functions must be virtual """ - return ' override' if self.is_override else '' + return " override" if self.is_override else "" def _render_final(self): """ After function name, could be in declaration or definition Final functions must be virtual """ - return ' final' if self.is_final else '' + return " final" if self.is_final else "" def _sanity_check(self): """ Check whether attributes compose a correct C++ code """ if self.is_inline and (self.is_virtual or self.is_pure_virtual): - raise ValueError(f'Inline method {self.name} could not be virtual') + raise ValueError(f"Inline method {self.name} could not be virtual") if self.is_constexpr and (self.is_virtual or self.is_pure_virtual): - raise ValueError(f'Constexpr method {self.name} could not be virtual') + raise ValueError(f"Constexpr method {self.name} could not be virtual") if self.is_const and self.is_static: - raise ValueError(f'Static method {self.name} could not be const') + raise ValueError(f"Static method {self.name} could not be const") if self.is_const and self.is_virtual: - raise ValueError(f'Virtual method {self.name} could not be const') + raise ValueError(f"Virtual method {self.name} could not be const") if self.is_const and self.is_pure_virtual: - raise ValueError(f'Pure virtual method {self.name} could not be const') + raise ValueError(f"Pure virtual method {self.name} could not be const") if self.is_override and not self.is_virtual: - raise ValueError(f'Override method {self.name} should be virtual') + raise ValueError(f"Override method {self.name} should be virtual") if self.is_inline and (self.is_virtual or self.is_pure_virtual): - raise ValueError(f'Inline method {self.name} could not be virtual') + raise ValueError(f"Inline method {self.name} could not be virtual") if self.is_final and not self.is_virtual: - raise ValueError(f'Final method {self.name} should be virtual') + raise ValueError(f"Final method {self.name} should be virtual") if self.is_static and self.is_virtual: - raise ValueError(f'Static method {self.name} could not be virtual') + raise ValueError(f"Static method {self.name} could not be virtual") if self.is_pure_virtual and not self.is_virtual: - raise ValueError(f'Pure virtual method {self.name} is also a virtual method') + raise ValueError( + f"Pure virtual method {self.name} is also a virtual method" + ) if not self.ref_to_parent: - raise ValueError(f'Method {self.name} object must be a child of CppClass') + raise ValueError( + f"Method {self.name} object must be a child of CppClass" + ) if self.is_constexpr and self.implementation is None: - raise ValueError(f'Method {self.name} object must be initialized when "constexpr"') + raise ValueError( + f'Method {self.name} object must be initialized when "constexpr"' + ) if self.is_pure_virtual and self.implementation is not None: - raise ValueError(f'Pure virtual method {self.name} could not be implemented') + raise ValueError( + f"Pure virtual method {self.name} could not be implemented" + ) def add_argument(self, argument): """ @@ -250,12 +270,14 @@ class A self._sanity_check() if self.documentation: cpp(dedent(self.documentation)) - with cpp.block(f'{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}' - f'{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})' - f'{self._render_const()}' - f'{self._render_override()}' - f'{self._render_final()}' - f'{self._render_pure()}'): + with cpp.block( + f"{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}" + f"{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})" + f"{self._render_const()}" + f"{self._render_override()}" + f"{self._render_final()}" + f"{self._render_pure()}" + ): self.implementation(cpp) def render_to_string_declaration(self, cpp): @@ -272,13 +294,15 @@ def render_to_string_declaration(self, cpp): cpp(dedent(self.documentation)) self.render_to_string(cpp) else: - cpp(f'{self._render_virtual()}{self._render_inline()}' - f'{self._render_static()}' - f'{self._render_ret_type()} {self.name}({self.args()})' - f'{self._render_const()}' - f'{self._render_override()}' - f'{self._render_final()}' - f'{self._render_pure()};') + cpp( + f"{self._render_virtual()}{self._render_inline()}" + f"{self._render_static()}" + f"{self._render_ret_type()} {self.name}({self.args()})" + f"{self._render_const()}" + f"{self._render_override()}" + f"{self._render_final()}" + f"{self._render_pure()};" + ) def render_to_string_implementation(self, cpp): """ @@ -295,16 +319,20 @@ def render_to_string_implementation(self, cpp): self._sanity_check() if self.implementation is None: - raise RuntimeError(f'No implementation handle for the method {self.name}') + raise RuntimeError( + f"No implementation handle for the method {self.name}" + ) if self.documentation and not self.is_constexpr: cpp(dedent(self.documentation)) - with cpp.block(f'{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}' - f'{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})' - f'{self._render_const()}' - f'{self._render_override()}' - f'{self._render_final()}' - f'{self._render_pure()}'): + with cpp.block( + f"{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}" + f"{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})" + f"{self._render_const()}" + f"{self._render_override()}" + f"{self._render_final()}" + f"{self._render_pure()}" + ): self.implementation(cpp) def __init__(self, **properties): @@ -314,8 +342,10 @@ def __init__(self, **properties): input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(CppClass, self).__init__(properties) - self.init_class_properties(current_class_properties=self.availablePropertiesNames, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.availablePropertiesNames, + input_properties_dict=properties, + ) # aggregated classes self.internal_class_elements = [] @@ -343,7 +373,7 @@ def inherits(self): @return: string representation of the inheritance """ if self._parent_class(): - return f' : public {self._parent_class()}' + return f" : public {self._parent_class()}" ######################################## # ADD CLASS MEMBERS @@ -437,7 +467,11 @@ def render_static_members_implementation(self, cpp): int MyClass::my_static_array[] = {} """ # generate definition for static variables - static_vars = [variable for variable in self.internal_variable_elements if variable.is_static] + static_vars = [ + variable + for variable in self.internal_variable_elements + if variable.is_static + ] for varItem in static_vars: varItem.definition().render_to_string(cpp) cpp.newline() @@ -503,15 +537,14 @@ def render_to_string_declaration(self, cpp): if self.documentation: cpp(dedent(self.documentation)) - render_str = f'{self._render_class_type()} {self.name}' + render_str = f"{self._render_class_type()} {self.name}" if self._parent_class(): render_str += self.inherits() - with cpp.block(render_str, postfix=';'): - + with cpp.block(render_str, postfix=";"): # in case of struct all members meant to be public if not self.is_struct: - cpp.label('public') + cpp.label("public") self.class_interface(cpp) self.private_class_members(cpp) cpp.newline() @@ -520,7 +553,7 @@ def _render_class_type(self): """ @return: 'class' or 'struct' keyword """ - return 'struct' if self.is_struct else 'class' + return "struct" if self.is_struct else "class" def render_to_string_implementation(self, cpp): """ diff --git a/src/code_generation/cpp/enum_generator.py b/src/code_generation/cpp/enum_generator.py index 89a4767..bd44f2a 100644 --- a/src/code_generation/cpp/enum_generator.py +++ b/src/code_generation/cpp/enum_generator.py @@ -25,9 +25,12 @@ class CppEnum(CppLanguageElement): eItemsCount = 3 } """ - availablePropertiesNames = {'prefix', - 'enum_class', - 'add_counter'} | CppLanguageElement.availablePropertiesNames + + availablePropertiesNames = { + "prefix", + "enum_class", + "add_counter", + } | CppLanguageElement.availablePropertiesNames def __init__(self, **properties): """ @@ -39,14 +42,16 @@ def __init__(self, **properties): self.check_input_properties_names(input_property_names) super(CppEnum, self).__init__(properties) - self.init_class_properties(current_class_properties=self.availablePropertiesNames, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.availablePropertiesNames, + input_properties_dict=properties, + ) # place enum items here self.enum_items = [] def _render_class(self): - return 'class ' if self.enum_class else '' + return "class " if self.enum_class else "" def add_item(self, item): """ @@ -73,11 +78,11 @@ def render_to_string(self, cpp): } """ counter = 0 - final_prefix = self.prefix if self.prefix is not None else 'e' - with cpp.block(f'enum {self._render_class()}{self.name}', postfix=';'): + final_prefix = self.prefix if self.prefix is not None else "e" + with cpp.block(f"enum {self._render_class()}{self.name}", postfix=";"): for item in self.enum_items: - cpp(f'{final_prefix}{item} = {counter},') + cpp(f"{final_prefix}{item} = {counter},") counter += 1 if self.add_counter in [None, True]: - last_element = f'{final_prefix}{self.name}Count = {counter}' + last_element = f"{final_prefix}{self.name}Count = {counter}" cpp(last_element) diff --git a/src/code_generation/cpp/file_writer.py b/src/code_generation/cpp/file_writer.py index 88d5afa..a596c1b 100644 --- a/src/code_generation/cpp/file_writer.py +++ b/src/code_generation/cpp/file_writer.py @@ -21,4 +21,4 @@ def label(self, text): private: a: """ - self.write('{0}:'.format(text), -1) + self.write("{0}:".format(text), -1) diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index 160e9e0..cb512f3 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -1,4 +1,8 @@ -from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.language_element import ( + CppLanguageElement, + CppDeclaration, + CppImplementation, +) from textwrap import dedent @@ -26,10 +30,13 @@ def functionBody(self, cpp): cpp('return 42;') return 42; } """ - availablePropertiesNames = {'ret_type', - 'is_constexpr', - 'implementation', - 'documentation'} | CppLanguageElement.availablePropertiesNames + + availablePropertiesNames = { + "ret_type", + "is_constexpr", + "implementation", + "documentation", + } | CppLanguageElement.availablePropertiesNames def __init__(self, **properties): # arguments are plain strings @@ -44,22 +51,24 @@ def __init__(self, **properties): input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(CppFunction, self).__init__(properties) - self.init_class_properties(current_class_properties=self.availablePropertiesNames, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.availablePropertiesNames, + input_properties_dict=properties, + ) def _sanity_check(self): """ Check whether attributes compose a correct C++ code """ if self.is_constexpr and self.implementation is None: - raise ValueError(f'Constexpr function {self.name} must have implementation') + raise ValueError(f"Constexpr function {self.name} must have implementation") def _render_constexpr(self): """ Before function name, declaration only Constexpr functions can't be const, virtual or pure virtual """ - return 'constexpr ' if self.is_constexpr else '' + return "constexpr " if self.is_constexpr else "" def args(self): """ @@ -106,7 +115,9 @@ def render_to_string(self, cpp): self._sanity_check() if self.documentation: cpp(dedent(self.documentation)) - with cpp.block(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})'): + with cpp.block( + f"{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})" + ): self.implementation(cpp) def render_to_string_declaration(self, cpp): @@ -122,7 +133,9 @@ def render_to_string_declaration(self, cpp): cpp(dedent(self.documentation)) self.render_to_string(cpp) else: - cpp(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()});') + cpp( + f"{self._render_constexpr()}{self.ret_type} {self.name}({self.args()});" + ) def render_to_string_implementation(self, cpp): """ @@ -136,10 +149,12 @@ def render_to_string_implementation(self, cpp): Generates method body if self.implementation property exists """ if self.implementation is None: - raise RuntimeError(f'No implementation handle for the function {self.name}') + raise RuntimeError(f"No implementation handle for the function {self.name}") # check all properties for the consistency if self.documentation and not self.is_constexpr: cpp(dedent(self.documentation)) - with cpp.block(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})'): + with cpp.block( + f"{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})" + ): self.implementation(cpp) diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index 7acba7a..d2f090d 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -89,27 +89,36 @@ class CppLanguageElement(object): Contains dynamic storage for element properties (e.g. is_static for the variable is_virtual for the class method etc.) """ - availablePropertiesNames = {'name', 'ref_to_parent'} + + availablePropertiesNames = {"name", "ref_to_parent"} def __init__(self, properties): """ @param: properties - Basic C++ element properties (name, ref_to_parent) class is a parent for method or a member variable """ - self.name = properties.get('name') - self.ref_to_parent = properties.get('ref_to_parent') + self.name = properties.get("name") + self.ref_to_parent = properties.get("ref_to_parent") def check_input_properties_names(self, input_property_names): """ Ensure that all properties that passed to the CppLanguageElement are recognized. Raise an exception otherwise """ - unknown_properties = input_property_names.difference(self.availablePropertiesNames) + unknown_properties = input_property_names.difference( + self.availablePropertiesNames + ) if unknown_properties: raise AttributeError( - f'Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}') - - def init_class_properties(self, current_class_properties, input_properties_dict, default_property_value=None): + f"Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}" + ) + + def init_class_properties( + self, + current_class_properties, + input_properties_dict, + default_property_value=None, + ): """ @param: current_class_properties - all available properties for the C++ element to be generated @param: input_properties_dict - values for the initialized properties (e.g. is_const=True) @@ -122,7 +131,7 @@ def init_class_properties(self, current_class_properties, input_properties_dict, setattr(self, propertyName, default_property_value) # Set all defined properties values (all undefined will be left with defaults) - for (propertyName, propertyValue) in input_properties_dict.items(): + for propertyName, propertyValue in input_properties_dict.items(): if propertyName not in CppLanguageElement.availablePropertiesNames: setattr(self, propertyName, propertyValue) @@ -131,7 +140,7 @@ def render_to_string(self, cpp): @param: cpp - handle that supports code generation interface (see code_file.py) Typically it is passed to all child elements so that render their content """ - raise NotImplementedError('CppLanguageElement is an abstract class') + raise NotImplementedError("CppLanguageElement is an abstract class") def parent_qualifier(self): """ @@ -144,11 +153,11 @@ def parent_qualifier(self): Supports for nested classes, e.g. void MyClass::NestedClass:: """ - full_parent_qualifier = '' + full_parent_qualifier = "" parent = self.ref_to_parent # walk through all existing parents while parent: - full_parent_qualifier = f'{parent.name}::{full_parent_qualifier}' + full_parent_qualifier = f"{parent.name}::{full_parent_qualifier}" parent = parent.ref_to_parent return full_parent_qualifier @@ -158,4 +167,4 @@ def fully_qualified_name(self): Ex. MyClass::NestedClass::Method() """ - return f'{self.parent_qualifier()}{self.name}' + return f"{self.parent_qualifier()}{self.name}" diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index 32b5400..8265aa8 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -1,4 +1,8 @@ -from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation +from code_generation.cpp.language_element import ( + CppLanguageElement, + CppDeclaration, + CppImplementation, +) from textwrap import dedent __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: @@ -77,62 +81,71 @@ class MyClass documentation - string, '/// Example doxygen' is_class_member - boolean, for appropriate definition/declaration rendering """ - availablePropertiesNames = {'type', - 'is_static', - 'is_extern', - 'is_const', - 'is_constexpr', - 'value', - 'documentation', - 'is_class_member'} | CppLanguageElement.availablePropertiesNames + + availablePropertiesNames = { + "type", + "is_static", + "is_extern", + "is_const", + "is_constexpr", + "value", + "documentation", + "is_class_member", + } | CppLanguageElement.availablePropertiesNames def __init__(self, **properties): input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(CppVariable, self).__init__(properties) - self.init_class_properties(current_class_properties=self.availablePropertiesNames, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.availablePropertiesNames, + input_properties_dict=properties, + ) def _sanity_check(self): """ @raise: ValueError, if some properties are not valid """ if self.is_const and self.is_constexpr: - raise ValueError("Variable object can be either 'const' or 'constexpr', not both") + raise ValueError( + "Variable object can be either 'const' or 'constexpr', not both" + ) if self.is_constexpr and not self.value: raise ValueError("Variable object must be initialized when 'constexpr'") if self.is_static and self.is_extern: - raise ValueError("Variable object can be either 'extern' or 'static', not both") + raise ValueError( + "Variable object can be either 'extern' or 'static', not both" + ) def _render_static(self): """ @return: 'static' prefix, can't be used with 'extern' """ - return 'static ' if self.is_static else '' + return "static " if self.is_static else "" def _render_extern(self): """ @return: 'extern' prefix, can't be used with 'static' """ - return 'extern ' if self.is_extern else '' + return "extern " if self.is_extern else "" def _render_const(self): """ @return: 'const' prefix, can't be used with 'constexpr' """ - return 'const ' if self.is_const else '' + return "const " if self.is_const else "" def _render_constexpr(self): """ @return: 'constexpr' prefix, can't be used with 'const' """ - return 'constexpr ' if self.is_constexpr else '' + return "constexpr " if self.is_constexpr else "" def _render_init_value(self): """ @return: string, value to be initialized with """ - return self.value if self.value else '' + return self.value if self.value else "" def assignment(self, value): """ @@ -140,7 +153,7 @@ def assignment(self, value): a = 10; b = 20; """ - return f'{self.name} = {value}' + return f"{self.name} = {value}" def declaration(self): """ @@ -165,14 +178,18 @@ def render_to_string(self, cpp): """ self._sanity_check() if self.is_class_member and not (self.is_static and self.is_const): - raise RuntimeError('For class member variables use definition() and declaration() methods') + raise RuntimeError( + "For class member variables use definition() and declaration() methods" + ) elif self.is_extern: - cpp(f'{self._render_extern()}{self.type} {self.name};') + cpp(f"{self._render_extern()}{self.type} {self.name};") else: if self.documentation: cpp(dedent(self.documentation)) - cpp(f'{self._render_static()}{self._render_const()}{self._render_constexpr()}' - f'{self.type} {self.assignment(self._render_init_value())};') + cpp( + f"{self._render_static()}{self._render_const()}{self._render_constexpr()}" + f"{self.type} {self.assignment(self._render_init_value())};" + ) def render_to_string_declaration(self, cpp): """ @@ -180,12 +197,16 @@ def render_to_string_declaration(self, cpp): int m_var; """ if not self.is_class_member: - raise RuntimeError('For automatic variable use its render_to_string() method') + raise RuntimeError( + "For automatic variable use its render_to_string() method" + ) if self.documentation and self.is_class_member: cpp(dedent(self.documentation)) - cpp(f'{self._render_static()}{self._render_extern()}{self._render_const()}{self._render_constexpr()}' - f'{self.type} {self.name if not self.is_constexpr else self.assignment(self._render_init_value())};') + cpp( + f"{self._render_static()}{self._render_extern()}{self._render_const()}{self._render_constexpr()}" + f"{self.type} {self.name if not self.is_constexpr else self.assignment(self._render_init_value())};" + ) def render_to_string_implementation(self, cpp): """ @@ -201,14 +222,18 @@ def render_to_string_implementation(self, cpp): That string could be used in constructor initialization string """ if not self.is_class_member: - raise RuntimeError('For automatic variable use its render_to_string() method') + raise RuntimeError( + "For automatic variable use its render_to_string() method" + ) # generate definition for the static class member if not self.is_constexpr: if self.is_static: - cpp(f'{self._render_static()}{self._render_const()}{self._render_constexpr()}' - f'{self.type} {self.fully_qualified_name()} = {self._render_init_value()};') + cpp( + f"{self._render_static()}{self._render_const()}{self._render_constexpr()}" + f"{self.type} {self.fully_qualified_name()} = {self._render_init_value()};" + ) # generate definition for non-static static class member, e.g. m_var(0) # (string for the constructor initialization list) else: - cpp(f'{self.name}({self._render_init_value()})') + cpp(f"{self.name}({self._render_init_value()})") diff --git a/src/code_generation/html/html_element.py b/src/code_generation/html/html_element.py index 2060655..dae3227 100644 --- a/src/code_generation/html/html_element.py +++ b/src/code_generation/html/html_element.py @@ -4,24 +4,24 @@ class HtmlElement: - availablePropertiesNames = {'name', 'attributes', 'self_closing'} + availablePropertiesNames = {"name", "attributes", "self_closing"} def __init__(self, **properties): """ :param properties: Basic HTML element properties (name, self-closing, attributes) """ - self.name = properties.get('name') - self.self_closing = properties.get('self_closing', False) - self.attributes = properties.get('attributes', {}) + self.name = properties.get("name") + self.self_closing = properties.get("self_closing", False) + self.attributes = properties.get("attributes", {}) # If 'attributes' is present, set it to self.attributes and remove it from properties - if 'attributes' in properties: - self.attributes = properties['attributes'] - properties.pop('attributes') + if "attributes" in properties: + self.attributes = properties["attributes"] + properties.pop("attributes") # Populate self.attributes with any remaining properties not already set for key, value in properties.items(): - if key not in ('name', 'self_closing'): + if key not in ("name", "self_closing"): self.attributes[key] = value def render_to_string(self, html, content=None): @@ -29,9 +29,9 @@ def render_to_string(self, html, content=None): Generates HTML code for the self-closing element """ if self.self_closing: - html('<{0} {1}/>'.format(self.name, self._render_attributes())) + html("<{0} {1}/>".format(self.name, self._render_attributes())) else: - content = content if content is not None else '' + content = content if content is not None else "" with html.block(element=self.name, **self.attributes): html(content) @@ -39,5 +39,7 @@ def _render_attributes(self): """ Renders attributes to string """ - rendered_attributes = ' '.join('{0}="{1}"'.format(key, value) for key, value in self.attributes.items()) - return f' {rendered_attributes}' if len(self.attributes) else '' + rendered_attributes = " ".join( + '{0}="{1}"'.format(key, value) for key, value in self.attributes.items() + ) + return f" {rendered_attributes}" if len(self.attributes) else "" diff --git a/src/code_generation/html/html_file.py b/src/code_generation/html/html_file.py index 6cba827..dbde33b 100644 --- a/src/code_generation/html/html_file.py +++ b/src/code_generation/html/html_file.py @@ -2,7 +2,6 @@ class HtmlFile: - Formatter = HTMLStyle def __init__(self, filename, writer=None): @@ -13,7 +12,7 @@ def __init__(self, filename, writer=None): self.out = writer else: self.out = open(filename, "w") - self.write('') + self.write("") def close(self): """ @@ -26,10 +25,16 @@ def write(self, text, indent=0, endline=True): """ Write a new line with line ending """ - assert isinstance(indent, int), f'indent {indent} is not an integer, but {type(indent)}' - assert isinstance(text, str), f'text {text} is not a string, but {type(text)}' + assert isinstance( + indent, int + ), f"indent {indent} is not an integer, but {type(indent)}" + assert isinstance(text, str), f"text {text} is not a string, but {type(text)}" indent_str = self.Formatter.indent * (self.current_indent + indent) - self.out.write('{0}{1}{2}'.format(indent_str, text, self.Formatter.endline if endline else '')) + self.out.write( + "{0}{1}{2}".format( + indent_str, text, self.Formatter.endline if endline else "" + ) + ) def append(self, x): """ @@ -64,4 +69,4 @@ def newline(self, n=1): Insert one or several empty lines """ for _ in range(n): - self.write('') + self.write("") diff --git a/src/code_generation/java/array_generator.py b/src/code_generation/java/array_generator.py index a03687e..a9c44fc 100644 --- a/src/code_generation/java/array_generator.py +++ b/src/code_generation/java/array_generator.py @@ -4,21 +4,20 @@ class JavaArray(JavaLanguageElement): - available_properties_names = { - 'name', - 'type', - 'values', - 'quoted', - 'is_class_member', - 'add_counter' + "name", + "type", + "values", + "quoted", + "is_class_member", + "add_counter", } | JavaLanguageElement.available_properties_names def __init__(self, **properties): """ :param properties: """ - self.name = '' + self.name = "" self.values = [] self.is_class_member = False self.add_counter = True @@ -27,23 +26,25 @@ def __init__(self, **properties): input_property_names = set(properties.keys()) super(JavaArray, self).__init__(properties) self.check_input_properties_names(input_property_names) - self.init_class_properties(current_class_properties=self.available_properties_names, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.available_properties_names, + input_properties_dict=properties, + ) def _sanity_check(self): if not self.name: - raise RuntimeError('Enum name is not set') + raise RuntimeError("Enum name is not set") if not self.type: - raise RuntimeError('Enum type is not set') + raise RuntimeError("Enum type is not set") if not isinstance(self.name, str): - raise RuntimeError(f'Enum name is not a string, but {type(self.name)}') + raise RuntimeError(f"Enum name is not a string, but {type(self.name)}") if self.values is not None and not isinstance(self.values, list): - raise RuntimeError(f'Enum values are not a list, but {type(self.values)}') + raise RuntimeError(f"Enum values are not a list, but {type(self.values)}") def values_str(self): if self.values is None or not self.values: - return '' - return ', '.join(str(value) for value in self.values) + return "" + return ", ".join(str(value) for value in self.values) def __str__(self): return self.values_str() @@ -69,8 +70,10 @@ def add_items(self, items): def render_to_string(self, java): self._sanity_check() if len(self.values) == 0: - java(f'{self.type}[] {self.name} = new {self.type}[0];') + java(f"{self.type}[] {self.name} = new {self.type}[0];") else: # Add quotes around string values if self.quoted is True - values_str = ', '.join(f'"{value}"' if self.quoted else str(value) for value in self.values) - java(f'{self.type}[] {self.name} = {{{values_str}}};') \ No newline at end of file + values_str = ", ".join( + f'"{value}"' if self.quoted else str(value) for value in self.values + ) + java(f"{self.type}[] {self.name} = {{{values_str}}};") diff --git a/src/code_generation/java/class_generator.py b/src/code_generation/java/class_generator.py index 02ce5c2..6c0fe99 100644 --- a/src/code_generation/java/class_generator.py +++ b/src/code_generation/java/class_generator.py @@ -30,20 +30,22 @@ class JavaClass(JavaLanguageElement): """ available_properties_names = { - 'documentation', - 'parent_class' + "documentation", + "parent_class", + "is_record", } | JavaLanguageElement.available_properties_names def __init__(self, **properties): self.documentation = None self.parent_class = None + self.is_record = False input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super().__init__(properties) self.init_class_properties( current_class_properties=self.available_properties_names, - input_properties_dict=properties + input_properties_dict=properties, ) self.internal_class_elements = [] @@ -57,19 +59,15 @@ def _parent_class(self): def _render_documentation(self, java): if self.documentation: docstring_lines = ["/**"] - docstring_lines.extend([f" * {line}" for line in self.documentation.splitlines()]) + docstring_lines.extend( + [f" * {line}" for line in self.documentation.splitlines()] + ) docstring_lines.append(" */") for line in docstring_lines: java(line) - def render_to_string(self, java): - self._render_documentation(java) - with java.block(f"public {self._render_class_type()} {self.name} {self.inherits()}"): - self.class_interface(java) - self.private_class_members(java) - def _render_class_type(self): - return "class" + return "class" if not self.is_record else "record" def inherits(self): return f"extends {self._parent_class()}" if self.parent_class else "" @@ -113,3 +111,11 @@ def add_method(self, java_method): def add_internal_class(self, java_class): java_class.ref_to_parent = self self.internal_class_elements.append(java_class) + + def render_to_string(self, java): + self._render_documentation(java) + with java.block( + f"public {self._render_class_type()} {self.name} {self.inherits()}" + ): + self.class_interface(java) + self.private_class_members(java) diff --git a/src/code_generation/java/enum_generator.py b/src/code_generation/java/enum_generator.py index 45760ab..6ebbbd1 100644 --- a/src/code_generation/java/enum_generator.py +++ b/src/code_generation/java/enum_generator.py @@ -5,38 +5,39 @@ class JavaEnum(JavaLanguageElement): - available_properties_names = { - 'name', - 'values', - 'is_class_member' + "name", + "values", + "is_class_member", } | JavaLanguageElement.available_properties_names def __init__(self, **properties): """ :param properties: """ - self.name = '' + self.name = "" self.values = [] self.is_class_member = False input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(JavaEnum, self).__init__(properties) - self.init_class_properties(current_class_properties=self.available_properties_names, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.available_properties_names, + input_properties_dict=properties, + ) def _sanity_check(self): """ Check if all required properties are set """ if not self.name: - raise RuntimeError('Enum name is not set') + raise RuntimeError("Enum name is not set") if not isinstance(self.name, str): - raise RuntimeError(f'Enum name is not a string, but {type(self.name)}') + raise RuntimeError(f"Enum name is not a string, but {type(self.name)}") if self.values is not None and not isinstance(self.values, list): - raise RuntimeError(f'Enum values are not a list, but {type(self.values)}') + raise RuntimeError(f"Enum values are not a list, but {type(self.values)}") if self.is_class_member and not self.name: - raise RuntimeError('Class member enum name is not set') + raise RuntimeError("Class member enum name is not set") def values_str(self): """ @@ -45,8 +46,8 @@ def values_str(self): VALUE1, VALUE2, VALUE3 """ if self.values is None or not self.values: - return '' - return ', '.join(str(value) for value in self.values) + return "" + return ", ".join(str(value) for value in self.values) def __str__(self): """ diff --git a/src/code_generation/java/file_writer.py b/src/code_generation/java/file_writer.py index d871fa0..4f15136 100644 --- a/src/code_generation/java/file_writer.py +++ b/src/code_generation/java/file_writer.py @@ -7,7 +7,6 @@ class JavaFile(CodeFile): - Formatter = ANSICodeStyle def __init__(self, filename, writer=None): diff --git a/src/code_generation/java/function_generator.py b/src/code_generation/java/function_generator.py index 883de3f..5bd8f2f 100644 --- a/src/code_generation/java/function_generator.py +++ b/src/code_generation/java/function_generator.py @@ -1,29 +1,27 @@ from code_generation.java.language_element import JavaLanguageElement - __doc__ = """""" class JavaFunction(JavaLanguageElement): - available_properties_names = { - 'name', - 'return_type', - 'arguments', - 'is_static', - 'is_final', - 'is_abstract', - 'is_synchronized', - 'is_native', - 'is_strictfp', - 'access_specifier', - 'documentation', - 'implementation' + "name", + "return_type", + "arguments", + "is_static", + "is_final", + "is_abstract", + "is_synchronized", + "is_native", + "is_strictfp", + "access_specifier", + "documentation", + "implementation", } | JavaLanguageElement.available_properties_names def __init__(self, **properties): - self.name = '' - self.return_type = '' + self.name = "" + self.return_type = "" self.arguments = [] self.is_static = False self.is_final = False @@ -31,29 +29,33 @@ def __init__(self, **properties): self.is_synchronized = False self.is_native = False self.is_strictfp = False - self.documentation = '' - self.access_specifier = 'public' + self.documentation = "" + self.access_specifier = "public" self.implementation = None input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(JavaFunction, self).__init__(properties) - self.init_class_properties(current_class_properties=self.available_properties_names, - input_properties_dict=properties) + self.init_class_properties( + current_class_properties=self.available_properties_names, + input_properties_dict=properties, + ) def _sanity_check(self): if not self.name: - raise RuntimeError('Method name is not set') + raise RuntimeError("Method name is not set") if not isinstance(self.name, str): - raise RuntimeError(f'Method name is not a string, but {type(self.name)}') + raise RuntimeError(f"Method name is not a string, but {type(self.name)}") if not isinstance(self.return_type, str): - raise RuntimeError(f'Return type is not a string, but {type(self.return_type)}') + raise RuntimeError( + f"Return type is not a string, but {type(self.return_type)}" + ) if self.arguments is not None and not isinstance(self.arguments, list): - raise RuntimeError(f'Arguments are not a list, but {type(self.arguments)}') + raise RuntimeError(f"Arguments are not a list, but {type(self.arguments)}") def args_str(self): if self.arguments is None or not self.arguments: - return '' - return ', '.join(str(arg) for arg in self.arguments) + return "" + return ", ".join(str(arg) for arg in self.arguments) def add_argument(self, argument): """ @@ -62,22 +64,50 @@ def add_argument(self, argument): """ self.arguments.append(argument) + def _render_documentation(self, java): + if self.documentation: + docstring_lines = ["/**"] + docstring_lines.extend( + [f" * {line}" for line in self.documentation.splitlines()] + ) + docstring_lines.append(" */") + for line in docstring_lines: + java(line) + + def _render_access_specifier(self): + return self.access_specifier if self.access_specifier else "" + + def _render_static(self): + return "static" if self.is_static else "" + + def _render_final(self): + return "final" if self.is_final else "" + + def _render_synchronized(self): + return "synchronized" if self.is_synchronized else "" + + def _render_native(self): + return "native" if self.is_native else "" + + def _render_strictfp(self): + return "strictfp" if self.is_strictfp else "" + + def _render_modifiers(self): + modifiers = [ + self._render_access_specifier(), + self._render_static(), + self._render_final(), + self._render_synchronized(), + self._render_native(), + self._render_strictfp(), + ] + return " ".join(modifier for modifier in modifiers if modifier) + def render_to_string(self, java): self._sanity_check() - if self.documentation: - java(f"/**\n{self.documentation}\n*/") - if self.is_abstract: - raise RuntimeError("Cannot generate implementation for abstract method.") - if self.is_static: - java('static', end=' ') - if self.is_final: - java('final', end=' ') - if self.is_synchronized: - java('synchronized', end=' ') - if self.is_native: - java('native', end=' ') - if self.is_strictfp: - java('strictfp', end=' ') - with java.block(f'{self.access_specifier} {self.return_type} {self.name}({self.args_str()})'): - if self.implementation: + self._render_documentation(java) + with java.block( + f"{self._render_modifiers()} {self.return_type} {self.name}({self.args_str()})" + ): + if self.implementation is not None: self.implementation(java) diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py index 00e293b..c86b094 100644 --- a/src/code_generation/java/language_element.py +++ b/src/code_generation/java/language_element.py @@ -46,7 +46,7 @@ def access(self, text): Could be used for Java class access specifiers private: """ - self.write('{0}:'.format(text), -1) + self.write("{0}:".format(text), -1) class JavaLanguageElement: @@ -55,27 +55,36 @@ class JavaLanguageElement: Contains dynamic storage for element properties (e.g. is_static for the variable is_abstract for the class etc.) """ - available_properties_names = {'name', 'ref_to_parent'} + + available_properties_names = {"name", "ref_to_parent"} def __init__(self, properties): """ @param: properties - Basic Java element properties (name, ref_to_parent) class is a parent for method or a member variable """ - self.name = properties.get('name') - self.ref_to_parent = properties.get('ref_to_parent') + self.name = properties.get("name") + self.ref_to_parent = properties.get("ref_to_parent") def check_input_properties_names(self, input_property_names): """ Ensure that all properties that are passed to the JavaLanguageElement are recognized. Raise an exception otherwise """ - unknown_properties = input_property_names.difference(self.available_properties_names) + unknown_properties = input_property_names.difference( + self.available_properties_names + ) if unknown_properties: raise AttributeError( - f'Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}') + f"Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}" + ) - def init_class_properties(self, current_class_properties, input_properties_dict, default_property_value=None): + def init_class_properties( + self, + current_class_properties, + input_properties_dict, + default_property_value=None, + ): """ @param: current_class_properties - all available properties for the Java element to be generated @param: input_properties_dict - values for the initialized properties (e.g. is_abstract=True) @@ -83,13 +92,16 @@ def init_class_properties(self, current_class_properties, input_properties_dict, (None by default, because of the same as False semantic) """ # Set all defined properties values (all undefined will be left with defaults) - for (property_name, property_value) in input_properties_dict.items(): + for property_name, property_value in input_properties_dict.items(): if property_name not in JavaLanguageElement.available_properties_names: setattr(self, property_name, property_value) # Set all available properties to DefaultValue if they are not already set for property_name in current_class_properties: - if property_name not in JavaLanguageElement.available_properties_names and not hasattr(self, property_name): + if ( + property_name not in JavaLanguageElement.available_properties_names + and not hasattr(self, property_name) + ): setattr(self, property_name, default_property_value) def render_to_string(self, java): @@ -97,7 +109,7 @@ def render_to_string(self, java): @param: java - handle that supports code generation interface (see code_file.py) Typically it is passed to all child elements so that they can render their content """ - raise NotImplementedError('JavaLanguageElement is an abstract class') + raise NotImplementedError("JavaLanguageElement is an abstract class") def parent_qualifier(self): """ @@ -110,11 +122,11 @@ def parent_qualifier(self): Supports for nested classes, e.g. void MyClass.NestedClass. """ - full_parent_qualifier = '' + full_parent_qualifier = "" parent = self.ref_to_parent # walk through all existing parents while parent: - full_parent_qualifier = f'{parent.name}.{full_parent_qualifier}' + full_parent_qualifier = f"{parent.name}.{full_parent_qualifier}" parent = parent.ref_to_parent return full_parent_qualifier @@ -124,4 +136,4 @@ def fully_qualified_name(self): Ex. MyClass.NestedClass.Method() """ - return f'{self.parent_qualifier()}{self.name}' + return f"{self.parent_qualifier()}{self.name}" diff --git a/src/code_generation/java/variable_generator.py b/src/code_generation/java/variable_generator.py index a417a3d..750db1f 100644 --- a/src/code_generation/java/variable_generator.py +++ b/src/code_generation/java/variable_generator.py @@ -21,32 +21,31 @@ class MyClass { documentation - string, '/** Example Javadoc */' """ - available_properties_names = ( - { - 'type', - 'name', - 'value', - 'access_modifier', - 'is_static', - 'static', - 'is_final', - 'final', - 'documentation', - 'is_transient', - 'transient', - 'is_volatile', - 'volatile', - 'is_synthetic', - 'synthetic', - 'custom_annotations', - 'custom_modifiers', - 'is_class_member' - } | JavaLanguageElement.available_properties_names) + available_properties_names = { + "type", + "name", + "value", + "access_modifier", + "is_static", + "static", + "is_final", + "final", + "documentation", + "is_transient", + "transient", + "is_volatile", + "volatile", + "is_synthetic", + "synthetic", + "custom_annotations", + "custom_modifiers", + "is_class_member", + } | JavaLanguageElement.available_properties_names def __init__(self, **properties): - self.type = '' - self.name = '' - self.value = '' + self.type = "" + self.name = "" + self.value = "" self.is_static = False self.is_final = False self.is_volatile = False @@ -55,18 +54,17 @@ def __init__(self, **properties): self.custom_annotations = [] self.custom_modifiers = [] self.access_modifier = None - self.documentation = '' + self.documentation = "" input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super().__init__(properties) self.init_class_properties( current_class_properties=self.available_properties_names, - input_properties_dict=properties + input_properties_dict=properties, ) def _sanity_check(self): - # Basic checks if not self.name: raise ValueError("Variable name is not set") @@ -84,36 +82,36 @@ def _sanity_check(self): raise ValueError("Cannot use 'final' and 'volatile' modifiers together") # Check for valid access modifiers - valid_access_modifiers = ['public', 'protected', 'private', '', None] + valid_access_modifiers = ["public", "protected", "private", "", None] if self.access_modifier not in valid_access_modifiers: raise ValueError(f"Invalid access modifier: {self.access_modifier}") def _render_type(self): - return f'{self.type} ' + return f"{self.type} " def _render_value(self): - return f' = {self.value}' if self.value else '' + return f" = {self.value}" if self.value else "" def _render_static(self): - return 'static ' if self.is_static else '' + return "static " if self.is_static else "" def _render_final(self): - return 'final ' if self.is_final else '' + return "final " if self.is_final else "" def _render_volatile(self): - return 'volatile ' if self.is_volatile else '' + return "volatile " if self.is_volatile else "" def _render_transient(self): - return 'transient ' if self.is_transient else '' + return "transient " if self.is_transient else "" def _render_synthetic(self): - return 'synthetic ' if self.is_synthetic else '' + return "synthetic " if self.is_synthetic else "" def _render_documentation(self): - return f"/**\n{self.documentation}\n*/" if self.documentation else '' + return f"/**\n{self.documentation}\n*/" if self.documentation else "" def _render_access_modifier(self): - return self.access_modifier if self.access_modifier is not None else '' + return self.access_modifier if self.access_modifier is not None else "" def _render_modifiers(self): modifiers = [ @@ -121,22 +119,24 @@ def _render_modifiers(self): self._render_final(), self._render_volatile(), self._render_transient(), - self._render_synthetic() + self._render_synthetic(), ] - return ' '.join(modifier for modifier in modifiers if modifier) + return " ".join(modifier for modifier in modifiers if modifier) def _render_custom_annotations(self): - return ' '.join(self.custom_annotations) if self.custom_annotations else '' + return " ".join(self.custom_annotations) if self.custom_annotations else "" def _render_custom_modifiers(self): - return ' '.join(self.custom_modifiers) if self.custom_modifiers else '' + return " ".join(self.custom_modifiers) if self.custom_modifiers else "" def render_to_string(self, java): self._sanity_check() - java(f'{self._render_custom_annotations()}' - f'{self._render_custom_modifiers()}' - f'{self._render_documentation()}' - f'{self._render_modifiers()}' - f'{self._render_type()}' - f'{self.name}' - f'{self._render_value()};') + java( + f"{self._render_custom_annotations()}" + f"{self._render_custom_modifiers()}" + f"{self._render_documentation()}" + f"{self._render_modifiers()}" + f"{self._render_type()}" + f"{self.name}" + f"{self._render_value()};" + ) diff --git a/src/example.py b/src/example.py index d2a491d..ba97731 100644 --- a/src/example.py +++ b/src/example.py @@ -17,23 +17,17 @@ def cpp_example(): static constexpr int const& x = 42; extern char* name; """ - cpp = CodeFile('example.cpp') - cpp('int i = 0;') + cpp = CodeFile("example.cpp") + cpp("int i = 0;") # Create a new variable 'x' x_variable = CppVariable( - name='x', - type='int const&', - is_static=True, - is_constexpr=True, - value='42') + name="x", type="int const&", is_static=True, is_constexpr=True, value="42" + ) x_variable.render_to_string(cpp) # Create a new variable 'name' - name_variable = CppVariable( - name='name', - type='char*', - is_extern=True) + name_variable = CppVariable(name="name", type="char*", is_extern=True) name_variable.render_to_string(cpp) @@ -41,71 +35,90 @@ def java_example(): """ :return: """ - java = JavaFile('example.java') - with java.block('class Main', ';'): - with java.block('public static void main(String[] args)'): + java = JavaFile("example.java") + with java.block("class Main", ";"): + with java.block("public static void main(String[] args)"): java('System.out.println("Hello World!");') - JavaArray(name='dynamicArray', type='String', dynamic=True, array_size=10 - ).render_to_string(java) - array1 = JavaArray(name='arrayWithItems1', type='int', dynamic=False, items=['1', '2', '3']) - array2 = JavaArray(name='arrayWithItems2', type='int', dynamic=False, items=[4, 5, 6]) + JavaArray( + name="dynamicArray", type="String", dynamic=True, array_size=10 + ).render_to_string(java) + array1 = JavaArray( + name="arrayWithItems1", type="int", dynamic=False, items=["1", "2", "3"] + ) + array2 = JavaArray( + name="arrayWithItems2", type="int", dynamic=False, items=[4, 5, 6] + ) array1.render_to_string(java) array2.render_to_string(java) - JavaArray(name='multiArray', type='int', dynamic=False, items=[array1, array2]).render_to_string(java) + JavaArray( + name="multiArray", type="int", dynamic=False, items=[array1, array2] + ).render_to_string(java) def html_example(): - html = HtmlFile('example.html') - with html.block('html'): - with html.block('head', lang='en'): + html = HtmlFile("example.html") + with html.block("html"): + with html.block("head", lang="en"): html('') def html_example2(): - html = HtmlFile('example2.html') - with html.block('a', href='https://www.google.com'): + html = HtmlFile("example2.html") + with html.block("a", href="https://www.google.com"): HtmlElement( - name='i', + name="i", self_closing=False, - attributes={'class': "fab fa-facebook-f", "target": "_blank"} + attributes={"class": "fab fa-facebook-f", "target": "_blank"}, ).render_to_string(html) def html_example3(): - html = HtmlFile('example2.html') - with html.block('html'): - with html.block('head', lang='en'): - HtmlElement(name='meta', self_closing=True, charset='utf-8').render_to_string(html) - HtmlElement(name='meta', self_closing=True, viewport='width=device-width, initial-scale=1').render_to_string(html) - with html.block('body'): + html = HtmlFile("example2.html") + with html.block("html"): + with html.block("head", lang="en"): + HtmlElement( + name="meta", self_closing=True, charset="utf-8" + ).render_to_string(html) + HtmlElement( + name="meta", + self_closing=True, + viewport="width=device-width, initial-scale=1", + ).render_to_string(html) + with html.block("body"): # with semantic - with html.block('div', id='container'): - with html.block('div', id='header'): - html('Header') - with html.block('div', id='content'): - html('Content') + with html.block("div", id="container"): + with html.block("div", id="header"): + html("Header") + with html.block("div", id="content"): + html("Content") # using content parameter - HtmlElement(name='div', self_closing=False).render_to_string(html, content='Footer 1') - HtmlElement(name='footer', self_closing=False, id='real-footer').render_to_string(html, content='Footer 2') + HtmlElement(name="div", self_closing=False).render_to_string( + html, content="Footer 1" + ) + HtmlElement( + name="footer", self_closing=False, id="real-footer" + ).render_to_string(html, content="Footer 2") def main(): - parser = argparse.ArgumentParser(description='Command-line params') - parser.add_argument('--language', - help='Programming language to show example for', - choices=["C++", "Java", "HTML"], - default="Java", - required=False) + parser = argparse.ArgumentParser(description="Command-line params") + parser.add_argument( + "--language", + help="Programming language to show example for", + choices=["C++", "Java", "HTML"], + default="Java", + required=False, + ) args = parser.parse_args() - if args.language == 'C++': + if args.language == "C++": cpp_example() - elif args.language == 'Java': + elif args.language == "Java": java_example() - elif args.language == 'HTML': + elif args.language == "HTML": html_example2() return 0 -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/test/cpp/test_cpp_function_writer.py b/test/cpp/test_cpp_function_writer.py index 8929fe7..6679639 100644 --- a/test/cpp/test_cpp_function_writer.py +++ b/test/cpp/test_cpp_function_writer.py @@ -10,7 +10,7 @@ """ -def handle_to_factorial(_, cpp): +def handle_to_factorial(cpp): cpp('return n < 1 ? 1 : (n * factorial(n - 1));') From 00096210bd64fda866da21df7356f3b7740fa90a Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Thu, 11 Jan 2024 21:35:46 +0700 Subject: [PATCH 36/51] Final refactoring: C++ classes --- src/code_generation/cpp/array_generator.py | 89 +++++----- src/code_generation/cpp/class_generator.py | 192 +++++++++++---------- 2 files changed, 149 insertions(+), 132 deletions(-) diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index 8f03f92..4d9322c 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -1,8 +1,4 @@ -from code_generation.cpp.language_element import ( - CppLanguageElement, - CppDeclaration, - CppImplementation, -) +from code_generation.cpp.language_element import CppLanguageElement # noinspection PyUnresolvedReferences @@ -33,17 +29,18 @@ class MyClass but old versions preserved for backward compatibility """ - availablePropertiesNames = { - "type", - "is_static", - "static", - "is_const", - "const", - "is_class_member", - "class_member", - "array_size", - "newline_align", - } | CppLanguageElement.availablePropertiesNames + availablePropertiesNames = ( + { + "type", + "is_static", + "static", + "is_const", + "const", + "is_class_member", + "class_member", + "array_size", + "newline_align", + } | CppLanguageElement.availablePropertiesNames) def __init__(self, **properties): self.is_static = False @@ -78,13 +75,20 @@ def _render_static(self): """ @return: 'static' prefix if required """ - return "static " if self.is_static else "" + return "static" if self.is_static else "" def _render_const(self): """ @return: 'const' prefix if required """ - return "const " if self.is_const else "" + return "const" if self.is_const else "" + + def _render_modifiers(self): + modifiers = [ + self._render_static(), + self._render_const() + ] + return " ".join(modifiers) def _render_size(self): """ @@ -150,23 +154,28 @@ def render_to_string(self, cpp): """ self._sanity_check() if self.is_class_member and not (self.is_static and self.is_const): - raise RuntimeError( - "For class member variables use definition() and declaration() methods" - ) + raise RuntimeError("For class member variables use definition() and declaration() methods") # newline-formatting of array elements makes sense only if array is not empty if self.newline_align and self.items: with cpp.block( - f"{self._render_static()}{self._render_const()}{self.type} " - f"{self.name}[{self._render_size()}] = ", - ";", + f"{self._render_modifiers()} " + f"{self.type} " + f"{self.name}" + f"[{self._render_size()}]" + f" = ", + postfix=";" ): # render array items self._render_value(cpp) else: cpp( - f"{self._render_static()}{self._render_const()}{self.type} " - f"{self.name}[{self._render_size()}] = {{{self._render_content()}}};" + f"{self._render_modifiers()} " + f"{self.type} " + f"{self.name}" + f"[{self._render_size()}]" + f" = " + f"{{{self._render_content()}}};" ) def render_to_string_declaration(self, cpp): @@ -179,12 +188,9 @@ def render_to_string_declaration(self, cpp): """ self._sanity_check() if not self.is_class_member: - raise RuntimeError( - "For automatic variable use its render_to_string() method" - ) + raise RuntimeError("For automatic variable use its render_to_string() method") cpp( - f"{self._render_static()}" - f"{self._render_const()}" + f"{self._render_modifiers()} " f"{self.type} " f"{self.name}" f"[{self._render_size()}];" @@ -205,9 +211,7 @@ def render_to_string_implementation(self, cpp): """ self._sanity_check() if not self.is_class_member: - raise RuntimeError( - "For automatic variable use its render_to_string() method" - ) + raise RuntimeError("For automatic variable use its render_to_string() method") # generate definition for the static class member arrays only # other types are not supported @@ -217,14 +221,21 @@ def render_to_string_implementation(self, cpp): # newline-formatting of array elements makes sense only if array is not empty if self.newline_align and self.items: with cpp.block( - f"{self._render_const()}{self.type} " - f"{self.fully_qualified_name()}[{self._render_size()}] = ", - ";", + f"{self._render_const()}" + f"{self.type} " + f"{self.fully_qualified_name()}" + f"[{self._render_size()}]" + f" = ", + postfix=";" ): # render array items self._render_value(cpp) else: cpp( - f"{self._render_const()}{self.type} " - f"{self.fully_qualified_name()}[{self._render_size()}] = {{{self._render_content()}}};" + f"{self._render_const()}" + f"{self.type} " + f"{self.fully_qualified_name()}" + f"[{self._render_size()}]" + f" = " + f"{{{self._render_content()}}};" ) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index f9f6509..710b8c1 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -1,8 +1,4 @@ -from code_generation.cpp.language_element import ( - CppLanguageElement, - CppDeclaration, - CppImplementation, -) +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation from code_generation.cpp.function_generator import CppFunction from textwrap import dedent @@ -49,10 +45,10 @@ def handle(cpp): cpp('return m_var;') """ availablePropertiesNames = { - "is_struct", - "documentation", - "parent_class", - } | CppLanguageElement.availablePropertiesNames + "is_struct", + "documentation", + "parent_class", + } | CppLanguageElement.availablePropertiesNames class CppMethod(CppFunction): """ @@ -82,19 +78,20 @@ def functionBody(self, cpp): cpp('return 42;') } """ - availablePropertiesNames = { - "ret_type", - "is_static", - "is_constexpr", - "is_virtual", - "is_inline", - "is_pure_virtual", - "is_const", - "is_override", - "is_final", - "implementation", - "documentation", - } | CppLanguageElement.availablePropertiesNames + availablePropertiesNames = ( + { + "ret_type", + "is_static", + "is_constexpr", + "is_virtual", + "is_inline", + "is_pure_virtual", + "is_const", + "is_override", + "is_final", + "implementation", + "documentation", + } | CppLanguageElement.availablePropertiesNames) def __init__(self, **properties): # arguments are plain strings @@ -125,28 +122,28 @@ def _render_static(self): Before function name, declaration only Static functions can't be const, virtual or pure virtual """ - return "static " if self.is_static else "" + return "static" if self.is_static else "" def _render_constexpr(self): """ Before function name, declaration only Constexpr functions can't be const, virtual or pure virtual """ - return "constexpr " if self.is_constexpr else "" + return "constexpr" if self.is_constexpr else "" def _render_virtual(self): """ Before function name, could be in declaration or definition Virtual functions can't be static or constexpr """ - return "virtual " if self.is_virtual else "" + return "virtual" if self.is_virtual else "" def _render_inline(self): """ Before function name, could be in declaration or definition Inline functions can't be static, virtual or constexpr """ - return "inline " if self.is_inline else "" + return "inline" if self.is_inline else "" def _render_ret_type(self): """ @@ -166,21 +163,21 @@ def _render_const(self): After function name, could be in declaration or definition Const functions can't be static, virtual or constexpr """ - return " const" if self.is_const else "" + return "const" if self.is_const else "" def _render_override(self): """ After function name, could be in declaration or definition Override functions must be virtual """ - return " override" if self.is_override else "" + return "override" if self.is_override else "" def _render_final(self): """ After function name, could be in declaration or definition Final functions must be virtual """ - return " final" if self.is_final else "" + return "final" if self.is_final else "" def _sanity_check(self): """ @@ -205,21 +202,13 @@ def _sanity_check(self): if self.is_static and self.is_virtual: raise ValueError(f"Static method {self.name} could not be virtual") if self.is_pure_virtual and not self.is_virtual: - raise ValueError( - f"Pure virtual method {self.name} is also a virtual method" - ) + raise ValueError(f"Pure virtual method {self.name} is also a virtual method") if not self.ref_to_parent: - raise ValueError( - f"Method {self.name} object must be a child of CppClass" - ) + raise ValueError(f"Method {self.name} object must be a child of CppClass") if self.is_constexpr and self.implementation is None: - raise ValueError( - f'Method {self.name} object must be initialized when "constexpr"' - ) + raise ValueError(f'Method {self.name} object must be initialized when "constexpr"') if self.is_pure_virtual and self.implementation is not None: - raise ValueError( - f"Pure virtual method {self.name} could not be implemented" - ) + raise ValueError(f"Pure virtual method {self.name} could not be implemented") def add_argument(self, argument): """ @@ -254,6 +243,22 @@ def definition(self): """ return CppImplementation(self) + def _render_modifiers_front(self): + modifiers = [ + self._render_constexpr(), + self._render_virtual(), + self._render_inline(), + ] + return " ".join(modifiers) + + def _render_modifiers_back(self): + modifiers = [ + self._render_const(), + self._render_override(), + self._render_final() + ] + return " ".join(modifiers) + def render_to_string(self, cpp): """ By default, method is rendered as a declaration together with implementation, @@ -271,12 +276,13 @@ class A if self.documentation: cpp(dedent(self.documentation)) with cpp.block( - f"{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}" - f"{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})" - f"{self._render_const()}" - f"{self._render_override()}" - f"{self._render_final()}" - f"{self._render_pure()}" + f"{self._render_static()}" + f"{self._render_modifiers_front()}" + f"{self._render_ret_type()} " + f"{self.fully_qualified_name()}" + f"({self.args()})" + f"{self._render_modifiers_back()}" + f"{self._render_pure()}" ): self.implementation(cpp) @@ -295,12 +301,12 @@ def render_to_string_declaration(self, cpp): self.render_to_string(cpp) else: cpp( - f"{self._render_virtual()}{self._render_inline()}" f"{self._render_static()}" - f"{self._render_ret_type()} {self.name}({self.args()})" - f"{self._render_const()}" - f"{self._render_override()}" - f"{self._render_final()}" + f"{self._render_modifiers_front()}" + f"{self._render_ret_type()} " + f"{self.name}" + f"({self.args()})" + f"{self._render_modifiers_back()}" f"{self._render_pure()};" ) @@ -313,25 +319,25 @@ def render_to_string_implementation(self, cpp): { ... } - Generates method body if self.implementation property exists + Generates method body if `self.implementation` property exists """ # check all properties for the consistency self._sanity_check() if self.implementation is None: - raise RuntimeError( - f"No implementation handle for the method {self.name}" - ) + raise RuntimeError(f"No implementation handle for the method {self.name}") + + if self.is_pure_virtual: + raise RuntimeError(f"Pure virtual method {self.name} could not be implemented") if self.documentation and not self.is_constexpr: cpp(dedent(self.documentation)) with cpp.block( - f"{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}" - f"{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})" - f"{self._render_const()}" - f"{self._render_override()}" - f"{self._render_final()}" - f"{self._render_pure()}" + f"{self._render_modifiers_front()}" + f"{self._render_ret_type()} " + f"{self.fully_qualified_name()}" + f"({self.args()})" + f"{self._render_modifiers_back()}" ): self.implementation(cpp) @@ -351,16 +357,16 @@ def __init__(self, **properties): self.internal_class_elements = [] # class members - self.internal_variable_elements = [] + self.variable_members = [] # array class members - self.internal_array_elements = [] + self.array_members = [] # class methods - self.internal_method_elements = [] + self.methods = [] - # class enums - self.internal_enum_elements = [] + # class scoped enums + self.scoped_enums = [] def _parent_class(self): """ @@ -382,7 +388,7 @@ def add_enum(self, enum): @param: enum CppEnum instance """ enum.ref_to_parent = self - self.internal_enum_elements.append(enum) + self.scoped_enums.append(enum) def add_variable(self, cpp_variable): """ @@ -390,7 +396,7 @@ def add_variable(self, cpp_variable): """ cpp_variable.ref_to_parent = self cpp_variable.is_class_member = True - self.internal_variable_elements.append(cpp_variable) + self.variable_members.append(cpp_variable) def add_array(self, cpp_variable): """ @@ -398,7 +404,7 @@ def add_array(self, cpp_variable): """ cpp_variable.ref_to_parent = self cpp_variable.is_class_member = True - self.internal_array_elements.append(cpp_variable) + self.array_members.append(cpp_variable) def add_internal_class(self, cpp_class): """ @@ -414,7 +420,7 @@ def add_method(self, method): """ method.ref_to_parent = self method.is_method = True - self.internal_method_elements.append(method) + self.methods.append(method) ######################################## # RENDER CLASS MEMBERS @@ -424,32 +430,32 @@ def _render_internal_classes_declaration(self, cpp): Could be placed both in 'private:' or 'public:' sections Method is protected as it is used by CppClass only """ - for classItem in self.internal_class_elements: - classItem.declaration().render_to_string(cpp) + for class_item in self.internal_class_elements: + class_item.declaration().render_to_string(cpp) def _render_enum_section(self, cpp): """ Render to string all contained enums Method is protected as it is used by CppClass only """ - for enumItem in self.internal_enum_elements: - enumItem.render_to_string(cpp) + for enum_item in self.scoped_enums: + enum_item.render_to_string(cpp) def _render_variables_declaration(self, cpp): """ Render to string all contained variable class members Method is protected as it is used by CppClass only """ - for varItem in self.internal_variable_elements: - varItem.declaration().render_to_string(cpp) + for var_item in self.variable_members: + var_item.declaration().render_to_string(cpp) def _render_array_declaration(self, cpp): """ Render to string all contained array class members Method is protected as it is used by CppClass only """ - for arrItem in self.internal_array_elements: - arrItem.declaration().render_to_string(cpp) + for arr_item in self.array_members: + arr_item.declaration().render_to_string(cpp) def _render_methods_declaration(self, cpp): """ @@ -457,8 +463,8 @@ def _render_methods_declaration(self, cpp): Should be placed in 'public:' section Method is protected as it is used by CppClass only """ - for funcItem in self.internal_method_elements: - funcItem.render_to_string_declaration(cpp) + for func_item in self.methods: + func_item.render_to_string_declaration(cpp) def render_static_members_implementation(self, cpp): """ @@ -469,19 +475,19 @@ def render_static_members_implementation(self, cpp): # generate definition for static variables static_vars = [ variable - for variable in self.internal_variable_elements + for variable in self.variable_members if variable.is_static ] - for varItem in static_vars: - varItem.definition().render_to_string(cpp) + for var_item in static_vars: + var_item.definition().render_to_string(cpp) cpp.newline() - for arrItem in self.internal_array_elements: - arrItem.definition().render_to_string(cpp) + for arr_item in self.array_members: + arr_item.definition().render_to_string(cpp) cpp.newline() # do the same for nested classes - for classItem in self.internal_class_elements: - classItem.render_static_members_implementation(cpp) + for class_item in self.internal_class_elements: + class_item.render_static_members_implementation(cpp) cpp.newline() def render_methods_implementation(self, cpp): @@ -491,13 +497,13 @@ def render_methods_implementation(self, cpp): Method is public, as it could be used for nested classes """ # generate methods implementation section - for funcItem in self.internal_method_elements: - if not funcItem.is_pure_virtual: - funcItem.render_to_string_implementation(cpp) + for func_item in self.methods: + if not func_item.is_pure_virtual: + func_item.render_to_string_implementation(cpp) cpp.newline() # do the same for nested classes - for classItem in self.internal_class_elements: - classItem.render_static_members_implementation(cpp) + for class_item in self.internal_class_elements: + class_item.render_static_members_implementation(cpp) cpp.newline() ######################################## From 097a8680ee0e2ebe9a61154f069aa966f819f869 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 15 Jan 2024 17:16:26 +0700 Subject: [PATCH 37/51] Unit and C++ examples --- src/code_generation/java/class_generator.py | 6 +- src/example.py | 124 -------------------- src/examples/cpp_array.py | 14 +++ src/examples/cpp_class.py | 50 ++++++++ src/examples/cpp_enum.py | 32 +++++ src/examples/cpp_function.py | 21 ++++ src/examples/html_example.py | 67 +++++++++++ src/examples/java_array.py | 27 +++++ src/examples/java_class.py | 53 +++++++++ src/examples/java_enum.py | 18 +++ 10 files changed, 286 insertions(+), 126 deletions(-) delete mode 100644 src/example.py create mode 100644 src/examples/cpp_array.py create mode 100644 src/examples/cpp_class.py create mode 100644 src/examples/cpp_enum.py create mode 100644 src/examples/cpp_function.py create mode 100644 src/examples/html_example.py create mode 100644 src/examples/java_array.py create mode 100644 src/examples/java_class.py create mode 100644 src/examples/java_enum.py diff --git a/src/code_generation/java/class_generator.py b/src/code_generation/java/class_generator.py index 6c0fe99..8fc90c2 100644 --- a/src/code_generation/java/class_generator.py +++ b/src/code_generation/java/class_generator.py @@ -20,10 +20,12 @@ class JavaClass(JavaLanguageElement): java_class.add_method(JavaFunction(name='getVar', return_type='int', is_static=True, implementation='return myVariable;')) # Generated Java code - public class MyClass { + public class MyClass + { static final int myVariable = 10; - public static int getVar() { + public static int getVar() + { return myVariable; } } diff --git a/src/example.py b/src/example.py deleted file mode 100644 index ba97731..0000000 --- a/src/example.py +++ /dev/null @@ -1,124 +0,0 @@ -import argparse -import sys - -from code_generation.core.code_file import CodeFile -from code_generation.cpp.variable_generator import CppVariable -from code_generation.java.file_writer import JavaFile -from code_generation.java.array_generator import JavaArray - -from code_generation.html.html_file import HtmlFile -from code_generation.html.html_element import HtmlElement - - -def cpp_example(): - """ - Generated C++ code: - int i = 0; - static constexpr int const& x = 42; - extern char* name; - """ - cpp = CodeFile("example.cpp") - cpp("int i = 0;") - - # Create a new variable 'x' - x_variable = CppVariable( - name="x", type="int const&", is_static=True, is_constexpr=True, value="42" - ) - x_variable.render_to_string(cpp) - - # Create a new variable 'name' - name_variable = CppVariable(name="name", type="char*", is_extern=True) - name_variable.render_to_string(cpp) - - -def java_example(): - """ - :return: - """ - java = JavaFile("example.java") - with java.block("class Main", ";"): - with java.block("public static void main(String[] args)"): - java('System.out.println("Hello World!");') - JavaArray( - name="dynamicArray", type="String", dynamic=True, array_size=10 - ).render_to_string(java) - array1 = JavaArray( - name="arrayWithItems1", type="int", dynamic=False, items=["1", "2", "3"] - ) - array2 = JavaArray( - name="arrayWithItems2", type="int", dynamic=False, items=[4, 5, 6] - ) - array1.render_to_string(java) - array2.render_to_string(java) - JavaArray( - name="multiArray", type="int", dynamic=False, items=[array1, array2] - ).render_to_string(java) - - -def html_example(): - html = HtmlFile("example.html") - with html.block("html"): - with html.block("head", lang="en"): - html('') - - -def html_example2(): - html = HtmlFile("example2.html") - with html.block("a", href="https://www.google.com"): - HtmlElement( - name="i", - self_closing=False, - attributes={"class": "fab fa-facebook-f", "target": "_blank"}, - ).render_to_string(html) - - -def html_example3(): - html = HtmlFile("example2.html") - with html.block("html"): - with html.block("head", lang="en"): - HtmlElement( - name="meta", self_closing=True, charset="utf-8" - ).render_to_string(html) - HtmlElement( - name="meta", - self_closing=True, - viewport="width=device-width, initial-scale=1", - ).render_to_string(html) - with html.block("body"): - # with semantic - with html.block("div", id="container"): - with html.block("div", id="header"): - html("Header") - with html.block("div", id="content"): - html("Content") - # using content parameter - HtmlElement(name="div", self_closing=False).render_to_string( - html, content="Footer 1" - ) - HtmlElement( - name="footer", self_closing=False, id="real-footer" - ).render_to_string(html, content="Footer 2") - - -def main(): - parser = argparse.ArgumentParser(description="Command-line params") - parser.add_argument( - "--language", - help="Programming language to show example for", - choices=["C++", "Java", "HTML"], - default="Java", - required=False, - ) - args = parser.parse_args() - - if args.language == "C++": - cpp_example() - elif args.language == "Java": - java_example() - elif args.language == "HTML": - html_example2() - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/src/examples/cpp_array.py b/src/examples/cpp_array.py new file mode 100644 index 0000000..b25765a --- /dev/null +++ b/src/examples/cpp_array.py @@ -0,0 +1,14 @@ +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.array_generator import CppArray + +__doc__ = """Example of generating C++ array + +Expected output: +int my_array[5] = {1, 2, 0}; + +""" + +cpp = CppFile('array.cpp') +arr = CppArray(name="my_array", type="int", array_size=5) +arr.add_array_items(["1", "2", "0"]) +arr.render_to_string(cpp) diff --git a/src/examples/cpp_class.py b/src/examples/cpp_class.py new file mode 100644 index 0000000..b8fc146 --- /dev/null +++ b/src/examples/cpp_class.py @@ -0,0 +1,50 @@ +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.class_generator import CppClass +from code_generation.cpp.variable_generator import CppVariable + +__doc__ = """Example of generating C++ class and struct + +Expected output: +struct MyClass +{ + static size_t GetVar(); + static const size_t m_var; +}; + +static const size_t MyClass::m_var = 255; + +size_t MyClass::GetVar() +{ + return m_var; +} + +""" + +cpp = CppFile('class.cpp') +cpp_class = CppClass(name="MyClass", is_struct=True) + +cpp_class.add_variable( + CppVariable( + name="m_var", + type="size_t", + is_static=True, + is_const=True, + value="255" + ) +) + + +def method_body(c): + c('return m_var;') + + +cpp_class.add_method( + CppClass.CppMethod( + name="GetVar", + ret_type="size_t", + is_static=True, + implementation=method_body + ) +) + +cpp_class.render_to_string(cpp) diff --git a/src/examples/cpp_enum.py b/src/examples/cpp_enum.py new file mode 100644 index 0000000..f4d5c11 --- /dev/null +++ b/src/examples/cpp_enum.py @@ -0,0 +1,32 @@ +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.enum_generator import CppEnum + +__doc__ = """Example of generating C++ enum and enum class + +Expected output: + enum MyEnum + { + eItem1 = 0, + eItem2 = 1, + eItem3 = 2, + eMyEnumCount = 3 + }; + + enum class MyEnumClass + { + eItem1 = 0, + eItem2 = 1, + eItem3 = 2, + eMyEnumCount = 3 + }; + +""" + +cpp = CppFile('array.cpp') +enum = CppEnum(name="MyEnum", prefix="e") +enum.add_items(["Item1", "Item2", "Item3"]) +enum.render_to_string(cpp) + +enum = CppEnum(name="MyEnumClass", prefix="e", enum_class=True) +enum.add_items(["Item1", "Item2", "Item3"]) +enum.render_to_string(cpp) diff --git a/src/examples/cpp_function.py b/src/examples/cpp_function.py new file mode 100644 index 0000000..3c4b72d --- /dev/null +++ b/src/examples/cpp_function.py @@ -0,0 +1,21 @@ +from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.function_generator import CppFunction + +__doc__ = """Example of generating C++ function + +Expected output: +int factorial(int n) +{ + return n < 1 ? 1 : (n * factorial(n - 1)); +} +""" + + +def handle_to_factorial(cpp): + cpp('return n < 1 ? 1 : (n * factorial(n - 1));') + + +cpp = CppFile('function.cpp') +func = CppFunction(name="factorial", ret_type="int", implementation=handle_to_factorial) +func.add_argument('int n') +func.render_to_string(cpp) diff --git a/src/examples/html_example.py b/src/examples/html_example.py new file mode 100644 index 0000000..79439de --- /dev/null +++ b/src/examples/html_example.py @@ -0,0 +1,67 @@ +from code_generation.html.html_file import HtmlFile +from code_generation.html.html_element import HtmlElement + +__doc__ = """Example of generating HTML file + +Expected output example.html: + + + + + + + +Expected output example2.html: + + + + + + + +
+ +
+ Content +
+
+
+
+
+ Footer 2 +
+ +""" + +html = HtmlFile("example.html") +with html.block("html"): + with html.block("head", lang="en"): + html('') + +html = HtmlFile("example2.html") +with html.block("html"): + with html.block("head", lang="en"): + HtmlElement( + name="meta", self_closing=True, charset="utf-8" + ).render_to_string(html) + HtmlElement( + name="meta", + self_closing=True, + viewport="width=device-width, initial-scale=1", + ).render_to_string(html) + with html.block("body"): + # with semantic + with html.block("div", id="container"): + with html.block("div", id="header"): + html("Header") + with html.block("div", id="content"): + html("Content") + # using content parameter + HtmlElement(name="div", self_closing=False).render_to_string( + html, content="Footer 1" + ) + HtmlElement( + name="footer", self_closing=False, id="real-footer" + ).render_to_string(html, content="Footer 2") diff --git a/src/examples/java_array.py b/src/examples/java_array.py new file mode 100644 index 0000000..8ae8dcc --- /dev/null +++ b/src/examples/java_array.py @@ -0,0 +1,27 @@ +from code_generation.java.file_writer import JavaFile +from code_generation.java.class_generator import JavaClass +from code_generation.java.array_generator import JavaArray + +__doc__ = """Example of generating Java array + +Expected output: +public class MyClass +{ + public static int[] my_array = {1, 2, 0}; + public static int[] empty_array = new int[0]; +} + +""" + +java = JavaFile('java_array.java') + +java_class = JavaClass(name="MyClass") + +java_array = JavaArray(name="my_array", type="int") +java_array.add_items(["1", "2", "0"]) +java_class.add_variable(java_array) + +java_array_empty = JavaArray(name="empty_array", type="int", values=[]) +java_class.add_variable(java_array_empty) + +java_class.render_to_string(java) diff --git a/src/examples/java_class.py b/src/examples/java_class.py new file mode 100644 index 0000000..df49c87 --- /dev/null +++ b/src/examples/java_class.py @@ -0,0 +1,53 @@ +from code_generation.java.file_writer import JavaFile +from code_generation.java.class_generator import JavaClass +from code_generation.java.function_generator import JavaFunction +from code_generation.java.variable_generator import JavaVariable + +__doc__ = """Example of generating Java class + +Expected output: +public class MyClass { + /** + * Example Javadoc class member + */ + private int m_my_member_variable; + + /** + * Example Javadoc method + */ + public final synchronized MyClassMethod() + { + m_my_member_variable = 10; + } +} + +""" + +java = JavaFile('java_class.java') + +java_class = JavaClass(name="MyClass") + +java_class.add_variable( + JavaVariable( + name="m_my_member_variable", + type="int", + is_class_member=True, + access_modifier="private", + documentation="/** Example Javadoc class member */" + ) +) + +def method_implementation(c): + c("m_my_member_variable = 10;") + +java_class.add_method( + JavaFunction( + name="MyClassMethod", + is_final=True, + is_synchronized=True, + implementation=method_implementation, + documentation="/** Example Javadoc method */" + ) +) + +java_class.render_to_string(java) diff --git a/src/examples/java_enum.py b/src/examples/java_enum.py new file mode 100644 index 0000000..777cf28 --- /dev/null +++ b/src/examples/java_enum.py @@ -0,0 +1,18 @@ +from code_generation.java.file_writer import JavaFile +from code_generation.java.class_generator import JavaClass +from code_generation.java.enum_generator import JavaEnum + +__doc__ = """Example of generating Java enum + +Expected output: + +""" + +java = JavaFile('java_enum.java') + +java_class = JavaClass(name="MyClass") + +java_enum = JavaEnum(name="MyEnum", values=["1", "2", "0"], is_class_member=True) +java_class.add_variable(java_enum) + +java_class.render_to_string(java) From b99ee7d782fa7a8ff002ab3b3dd3c2847e8f8468 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 15 Jan 2024 17:32:32 +0700 Subject: [PATCH 38/51] Java and C++ examples --- src/code_generation/java/function_generator.py | 2 ++ src/code_generation/java/variable_generator.py | 3 +++ src/examples/java_class.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/code_generation/java/function_generator.py b/src/code_generation/java/function_generator.py index 5bd8f2f..b530ce4 100644 --- a/src/code_generation/java/function_generator.py +++ b/src/code_generation/java/function_generator.py @@ -39,6 +39,8 @@ def __init__(self, **properties): current_class_properties=self.available_properties_names, input_properties_dict=properties, ) + # strip documentation of /** and */ because it's added in _render_documentation + self.documentation = self.documentation.strip("/**").strip("*/") def _sanity_check(self): if not self.name: diff --git a/src/code_generation/java/variable_generator.py b/src/code_generation/java/variable_generator.py index 750db1f..a22784e 100644 --- a/src/code_generation/java/variable_generator.py +++ b/src/code_generation/java/variable_generator.py @@ -64,6 +64,9 @@ def __init__(self, **properties): input_properties_dict=properties, ) + # strip documentation of /** and */ because it's added in _render_documentation + self.documentation = self.documentation.strip("/**").strip("*/") + def _sanity_check(self): # Basic checks if not self.name: diff --git a/src/examples/java_class.py b/src/examples/java_class.py index df49c87..0118183 100644 --- a/src/examples/java_class.py +++ b/src/examples/java_class.py @@ -37,9 +37,11 @@ ) ) + def method_implementation(c): c("m_my_member_variable = 10;") + java_class.add_method( JavaFunction( name="MyClassMethod", From 95e38eabbe9c36b4bd05dd148e5e17255b55e678 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 15 Jan 2024 17:32:41 +0700 Subject: [PATCH 39/51] Java and C++ examples --- src/examples/java_method.py | 55 +++++++++++++++++++++++++++++++++++ src/examples/java_variable.py | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/examples/java_method.py create mode 100644 src/examples/java_variable.py diff --git a/src/examples/java_method.py b/src/examples/java_method.py new file mode 100644 index 0000000..0118183 --- /dev/null +++ b/src/examples/java_method.py @@ -0,0 +1,55 @@ +from code_generation.java.file_writer import JavaFile +from code_generation.java.class_generator import JavaClass +from code_generation.java.function_generator import JavaFunction +from code_generation.java.variable_generator import JavaVariable + +__doc__ = """Example of generating Java class + +Expected output: +public class MyClass { + /** + * Example Javadoc class member + */ + private int m_my_member_variable; + + /** + * Example Javadoc method + */ + public final synchronized MyClassMethod() + { + m_my_member_variable = 10; + } +} + +""" + +java = JavaFile('java_class.java') + +java_class = JavaClass(name="MyClass") + +java_class.add_variable( + JavaVariable( + name="m_my_member_variable", + type="int", + is_class_member=True, + access_modifier="private", + documentation="/** Example Javadoc class member */" + ) +) + + +def method_implementation(c): + c("m_my_member_variable = 10;") + + +java_class.add_method( + JavaFunction( + name="MyClassMethod", + is_final=True, + is_synchronized=True, + implementation=method_implementation, + documentation="/** Example Javadoc method */" + ) +) + +java_class.render_to_string(java) diff --git a/src/examples/java_variable.py b/src/examples/java_variable.py new file mode 100644 index 0000000..0118183 --- /dev/null +++ b/src/examples/java_variable.py @@ -0,0 +1,55 @@ +from code_generation.java.file_writer import JavaFile +from code_generation.java.class_generator import JavaClass +from code_generation.java.function_generator import JavaFunction +from code_generation.java.variable_generator import JavaVariable + +__doc__ = """Example of generating Java class + +Expected output: +public class MyClass { + /** + * Example Javadoc class member + */ + private int m_my_member_variable; + + /** + * Example Javadoc method + */ + public final synchronized MyClassMethod() + { + m_my_member_variable = 10; + } +} + +""" + +java = JavaFile('java_class.java') + +java_class = JavaClass(name="MyClass") + +java_class.add_variable( + JavaVariable( + name="m_my_member_variable", + type="int", + is_class_member=True, + access_modifier="private", + documentation="/** Example Javadoc class member */" + ) +) + + +def method_implementation(c): + c("m_my_member_variable = 10;") + + +java_class.add_method( + JavaFunction( + name="MyClassMethod", + is_final=True, + is_synchronized=True, + implementation=method_implementation, + documentation="/** Example Javadoc method */" + ) +) + +java_class.render_to_string(java) From 2d1fe63231c14b5415ef332b5d68c628aa1c9a14 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 15 Jan 2024 17:34:25 +0700 Subject: [PATCH 40/51] Java and C++ examples --- src/examples/java_method.py | 21 +++------------------ src/examples/java_variable.py | 30 +----------------------------- 2 files changed, 4 insertions(+), 47 deletions(-) diff --git a/src/examples/java_method.py b/src/examples/java_method.py index 0118183..ba4d3f1 100644 --- a/src/examples/java_method.py +++ b/src/examples/java_method.py @@ -7,17 +7,13 @@ Expected output: public class MyClass { - /** - * Example Javadoc class member - */ - private int m_my_member_variable; /** * Example Javadoc method */ public final synchronized MyClassMethod() { - m_my_member_variable = 10; + return 42; } } @@ -27,19 +23,8 @@ java_class = JavaClass(name="MyClass") -java_class.add_variable( - JavaVariable( - name="m_my_member_variable", - type="int", - is_class_member=True, - access_modifier="private", - documentation="/** Example Javadoc class member */" - ) -) - - def method_implementation(c): - c("m_my_member_variable = 10;") + c("return 42;") java_class.add_method( @@ -48,7 +33,7 @@ def method_implementation(c): is_final=True, is_synchronized=True, implementation=method_implementation, - documentation="/** Example Javadoc method */" + documentation="Example Javadoc method" ) ) diff --git a/src/examples/java_variable.py b/src/examples/java_variable.py index 0118183..e00d757 100644 --- a/src/examples/java_variable.py +++ b/src/examples/java_variable.py @@ -6,20 +6,7 @@ __doc__ = """Example of generating Java class Expected output: -public class MyClass { - /** - * Example Javadoc class member - */ - private int m_my_member_variable; - - /** - * Example Javadoc method - */ - public final synchronized MyClassMethod() - { - m_my_member_variable = 10; - } -} + """ @@ -37,19 +24,4 @@ ) ) - -def method_implementation(c): - c("m_my_member_variable = 10;") - - -java_class.add_method( - JavaFunction( - name="MyClassMethod", - is_final=True, - is_synchronized=True, - implementation=method_implementation, - documentation="/** Example Javadoc method */" - ) -) - java_class.render_to_string(java) From bfa9aaad09b21f46ee2aeeb39e8b634f7205aef5 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 15 Jan 2024 17:46:39 +0700 Subject: [PATCH 41/51] Fix Javadoc generator --- .../java/function_generator.py | 63 ++++++++++------- .../java/variable_generator.py | 68 +++++++++++-------- src/examples/java_class.py | 4 +- src/examples/java_method.py | 4 +- src/examples/java_variable.py | 4 +- 5 files changed, 82 insertions(+), 61 deletions(-) diff --git a/src/code_generation/java/function_generator.py b/src/code_generation/java/function_generator.py index b530ce4..c4f2825 100644 --- a/src/code_generation/java/function_generator.py +++ b/src/code_generation/java/function_generator.py @@ -4,20 +4,23 @@ class JavaFunction(JavaLanguageElement): - available_properties_names = { - "name", - "return_type", - "arguments", - "is_static", - "is_final", - "is_abstract", - "is_synchronized", - "is_native", - "is_strictfp", - "access_specifier", - "documentation", - "implementation", - } | JavaLanguageElement.available_properties_names + available_properties_names = ( + { + "name", + "return_type", + "arguments", + "is_static", + "is_final", + "is_abstract", + "is_synchronized", + "is_native", + "is_strictfp", + "access_specifier", + "documentation", + "custom_annotations", + "custom_modifiers", + "implementation" + } | JavaLanguageElement.available_properties_names) def __init__(self, **properties): self.name = "" @@ -32,6 +35,8 @@ def __init__(self, **properties): self.documentation = "" self.access_specifier = "public" self.implementation = None + self.custom_annotations = [] + self.custom_modifiers = [] input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) super(JavaFunction, self).__init__(properties) @@ -66,16 +71,6 @@ def add_argument(self, argument): """ self.arguments.append(argument) - def _render_documentation(self, java): - if self.documentation: - docstring_lines = ["/**"] - docstring_lines.extend( - [f" * {line}" for line in self.documentation.splitlines()] - ) - docstring_lines.append(" */") - for line in docstring_lines: - java(line) - def _render_access_specifier(self): return self.access_specifier if self.access_specifier else "" @@ -105,11 +100,29 @@ def _render_modifiers(self): ] return " ".join(modifier for modifier in modifiers if modifier) + def _render_custom_annotations(self, java): + if self.custom_annotations: + for annotation in self.custom_annotations: + java(f"@{annotation}") + + def _render_custom_modifiers(self, java): + if self.custom_modifiers: + for modifier in self.custom_modifiers: + java(f"{modifier} ") + + def _render_documentation(self, java): + if self.documentation: + java("/**") + java(f" * {self.documentation}") + java(" */") + def render_to_string(self, java): self._sanity_check() self._render_documentation(java) + self._render_custom_annotations(java) + self._render_custom_modifiers(java) with java.block( - f"{self._render_modifiers()} {self.return_type} {self.name}({self.args_str()})" + f"{self._render_modifiers()} {self.return_type} {self.name}({self.args_str()})" ): if self.implementation is not None: self.implementation(java) diff --git a/src/code_generation/java/variable_generator.py b/src/code_generation/java/variable_generator.py index a22784e..e2caa69 100644 --- a/src/code_generation/java/variable_generator.py +++ b/src/code_generation/java/variable_generator.py @@ -21,26 +21,27 @@ class MyClass { documentation - string, '/** Example Javadoc */' """ - available_properties_names = { - "type", - "name", - "value", - "access_modifier", - "is_static", - "static", - "is_final", - "final", - "documentation", - "is_transient", - "transient", - "is_volatile", - "volatile", - "is_synthetic", - "synthetic", - "custom_annotations", - "custom_modifiers", - "is_class_member", - } | JavaLanguageElement.available_properties_names + available_properties_names = ( + { + "type", + "name", + "value", + "access_modifier", + "is_static", + "static", + "is_final", + "final", + "documentation", + "is_transient", + "transient", + "is_volatile", + "volatile", + "is_synthetic", + "synthetic", + "custom_annotations", + "custom_modifiers", + "is_class_member", + } | JavaLanguageElement.available_properties_names) def __init__(self, **properties): self.type = "" @@ -110,9 +111,6 @@ def _render_transient(self): def _render_synthetic(self): return "synthetic " if self.is_synthetic else "" - def _render_documentation(self): - return f"/**\n{self.documentation}\n*/" if self.documentation else "" - def _render_access_modifier(self): return self.access_modifier if self.access_modifier is not None else "" @@ -126,18 +124,28 @@ def _render_modifiers(self): ] return " ".join(modifier for modifier in modifiers if modifier) - def _render_custom_annotations(self): - return " ".join(self.custom_annotations) if self.custom_annotations else "" + def _render_custom_annotations(self, java): + if self.custom_annotations: + for annotation in self.custom_annotations: + java(f"@{annotation}") + + def _render_custom_modifiers(self, java): + if self.custom_modifiers: + for modifier in self.custom_modifiers: + java(f"{modifier} ") - def _render_custom_modifiers(self): - return " ".join(self.custom_modifiers) if self.custom_modifiers else "" + def _render_documentation(self, java): + if self.documentation: + java("/**") + java(f" * {self.documentation}") + java(" */") def render_to_string(self, java): self._sanity_check() + self._render_custom_annotations(java) + self._render_custom_modifiers(java) + self._render_documentation(java) java( - f"{self._render_custom_annotations()}" - f"{self._render_custom_modifiers()}" - f"{self._render_documentation()}" f"{self._render_modifiers()}" f"{self._render_type()}" f"{self.name}" diff --git a/src/examples/java_class.py b/src/examples/java_class.py index 0118183..5dc9461 100644 --- a/src/examples/java_class.py +++ b/src/examples/java_class.py @@ -33,7 +33,7 @@ type="int", is_class_member=True, access_modifier="private", - documentation="/** Example Javadoc class member */" + documentation="Example Javadoc class member" ) ) @@ -48,7 +48,7 @@ def method_implementation(c): is_final=True, is_synchronized=True, implementation=method_implementation, - documentation="/** Example Javadoc method */" + documentation="Example Javadoc method" ) ) diff --git a/src/examples/java_method.py b/src/examples/java_method.py index ba4d3f1..7c9f06e 100644 --- a/src/examples/java_method.py +++ b/src/examples/java_method.py @@ -19,10 +19,10 @@ """ -java = JavaFile('java_class.java') - +java = JavaFile('java_method.java') java_class = JavaClass(name="MyClass") + def method_implementation(c): c("return 42;") diff --git a/src/examples/java_variable.py b/src/examples/java_variable.py index e00d757..431414a 100644 --- a/src/examples/java_variable.py +++ b/src/examples/java_variable.py @@ -10,7 +10,7 @@ """ -java = JavaFile('java_class.java') +java = JavaFile('java_variable.java') java_class = JavaClass(name="MyClass") @@ -20,7 +20,7 @@ type="int", is_class_member=True, access_modifier="private", - documentation="/** Example Javadoc class member */" + documentation="Example Javadoc class member" ) ) From adc410de16bd6884a097a7dfd65e68f637d15d24 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 15 Jan 2024 17:58:53 +0700 Subject: [PATCH 42/51] Major naming refactoring --- setup.py | 2 +- src/code_generation/cpp/array_generator.py | 38 +++++++++---------- src/code_generation/cpp/class_generator.py | 38 +++++++++---------- src/code_generation/cpp/enum_generator.py | 4 +- src/code_generation/cpp/function_generator.py | 8 ++-- src/code_generation/cpp/variable_generator.py | 26 ++++++------- src/code_generation/java/class_generator.py | 4 +- src/code_generation/java/enum_generator.py | 4 +- .../java/function_generator.py | 30 +++++++-------- .../java/variable_generator.py | 34 ++++++++--------- 10 files changed, 94 insertions(+), 94 deletions(-) diff --git a/setup.py b/setup.py index b98dca0..5ca09d6 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ author_email="strategarius@protonmail.com", description="Provides functionality of generating source code programmatically", long_description=README, - long_description_content_type="text/markdown", + long_description_size_type="text/markdown", url="https://github.com/yuchdev/code_generator", project_urls={ "Bug Tracker": "https://github.com/yuchdev/code_generator/issues", diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index 4d9322c..539b1a4 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -71,32 +71,32 @@ def _sanity_check(self): if self.is_class_member and not self.name: raise RuntimeError("Class member array name is not set") - def _render_static(self): + def _static(self): """ @return: 'static' prefix if required """ return "static" if self.is_static else "" - def _render_const(self): + def _const(self): """ @return: 'const' prefix if required """ return "const" if self.is_const else "" - def _render_modifiers(self): + def _modifiers(self): modifiers = [ - self._render_static(), - self._render_const() + self._static(), + self._const() ] return " ".join(modifiers) - def _render_size(self): + def _size(self): """ @return: array size """ return self.array_size if self.array_size else "" - def _render_content(self): + def _render_size(self): """ @return: array items if any """ @@ -159,10 +159,10 @@ def render_to_string(self, cpp): # newline-formatting of array elements makes sense only if array is not empty if self.newline_align and self.items: with cpp.block( - f"{self._render_modifiers()} " + f"{self._modifiers()} " f"{self.type} " f"{self.name}" - f"[{self._render_size()}]" + f"[{self._size()}]" f" = ", postfix=";" ): @@ -170,12 +170,12 @@ def render_to_string(self, cpp): self._render_value(cpp) else: cpp( - f"{self._render_modifiers()} " + f"{self._modifiers()} " f"{self.type} " f"{self.name}" - f"[{self._render_size()}]" + f"[{self._size()}]" f" = " - f"{{{self._render_content()}}};" + f"{{{self._render_size()}}};" ) def render_to_string_declaration(self, cpp): @@ -190,10 +190,10 @@ def render_to_string_declaration(self, cpp): if not self.is_class_member: raise RuntimeError("For automatic variable use its render_to_string() method") cpp( - f"{self._render_modifiers()} " + f"{self._modifiers()} " f"{self.type} " f"{self.name}" - f"[{self._render_size()}];" + f"[{self._size()}];" ) def render_to_string_implementation(self, cpp): @@ -221,10 +221,10 @@ def render_to_string_implementation(self, cpp): # newline-formatting of array elements makes sense only if array is not empty if self.newline_align and self.items: with cpp.block( - f"{self._render_const()}" + f"{self._const()}" f"{self.type} " f"{self.fully_qualified_name()}" - f"[{self._render_size()}]" + f"[{self._size()}]" f" = ", postfix=";" ): @@ -232,10 +232,10 @@ def render_to_string_implementation(self, cpp): self._render_value(cpp) else: cpp( - f"{self._render_const()}" + f"{self._const()}" f"{self.type} " f"{self.fully_qualified_name()}" - f"[{self._render_size()}]" + f"[{self._size()}]" f" = " - f"{{{self._render_content()}}};" + f"{{{self._render_size()}}};" ) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 710b8c1..4d3db0a 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -117,14 +117,14 @@ def __init__(self, **properties): input_properties_dict=properties, ) - def _render_static(self): + def _static(self): """ Before function name, declaration only Static functions can't be const, virtual or pure virtual """ return "static" if self.is_static else "" - def _render_constexpr(self): + def _constexpr(self): """ Before function name, declaration only Constexpr functions can't be const, virtual or pure virtual @@ -158,7 +158,7 @@ def _render_pure(self): """ return " = 0" if self.is_pure_virtual else "" - def _render_const(self): + def _const(self): """ After function name, could be in declaration or definition Const functions can't be static, virtual or constexpr @@ -172,7 +172,7 @@ def _render_override(self): """ return "override" if self.is_override else "" - def _render_final(self): + def _final(self): """ After function name, could be in declaration or definition Final functions must be virtual @@ -243,19 +243,19 @@ def definition(self): """ return CppImplementation(self) - def _render_modifiers_front(self): + def _modifiers_front(self): modifiers = [ - self._render_constexpr(), + self._constexpr(), self._render_virtual(), self._render_inline(), ] return " ".join(modifiers) - def _render_modifiers_back(self): + def _modifiers_back(self): modifiers = [ - self._render_const(), + self._const(), self._render_override(), - self._render_final() + self._final() ] return " ".join(modifiers) @@ -276,12 +276,12 @@ class A if self.documentation: cpp(dedent(self.documentation)) with cpp.block( - f"{self._render_static()}" - f"{self._render_modifiers_front()}" + f"{self._static()}" + f"{self._modifiers_front()}" f"{self._render_ret_type()} " f"{self.fully_qualified_name()}" f"({self.args()})" - f"{self._render_modifiers_back()}" + f"{self._modifiers_back()}" f"{self._render_pure()}" ): self.implementation(cpp) @@ -301,12 +301,12 @@ def render_to_string_declaration(self, cpp): self.render_to_string(cpp) else: cpp( - f"{self._render_static()}" - f"{self._render_modifiers_front()}" + f"{self._static()}" + f"{self._modifiers_front()}" f"{self._render_ret_type()} " f"{self.name}" f"({self.args()})" - f"{self._render_modifiers_back()}" + f"{self._modifiers_back()}" f"{self._render_pure()};" ) @@ -333,11 +333,11 @@ def render_to_string_implementation(self, cpp): if self.documentation and not self.is_constexpr: cpp(dedent(self.documentation)) with cpp.block( - f"{self._render_modifiers_front()}" + f"{self._modifiers_front()}" f"{self._render_ret_type()} " f"{self.fully_qualified_name()}" f"({self.args()})" - f"{self._render_modifiers_back()}" + f"{self._modifiers_back()}" ): self.implementation(cpp) @@ -543,7 +543,7 @@ def render_to_string_declaration(self, cpp): if self.documentation: cpp(dedent(self.documentation)) - render_str = f"{self._render_class_type()} {self.name}" + render_str = f"{self._class_type()} {self.name}" if self._parent_class(): render_str += self.inherits() @@ -555,7 +555,7 @@ def render_to_string_declaration(self, cpp): self.private_class_members(cpp) cpp.newline() - def _render_class_type(self): + def _class_type(self): """ @return: 'class' or 'struct' keyword """ diff --git a/src/code_generation/cpp/enum_generator.py b/src/code_generation/cpp/enum_generator.py index bd44f2a..04293a5 100644 --- a/src/code_generation/cpp/enum_generator.py +++ b/src/code_generation/cpp/enum_generator.py @@ -50,7 +50,7 @@ def __init__(self, **properties): # place enum items here self.enum_items = [] - def _render_class(self): + def _enum_class(self): return "class " if self.enum_class else "" def add_item(self, item): @@ -79,7 +79,7 @@ def render_to_string(self, cpp): """ counter = 0 final_prefix = self.prefix if self.prefix is not None else "e" - with cpp.block(f"enum {self._render_class()}{self.name}", postfix=";"): + with cpp.block(f"enum {self._enum_class()}{self.name}", postfix=";"): for item in self.enum_items: cpp(f"{final_prefix}{item} = {counter},") counter += 1 diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index cb512f3..fbddd2a 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -63,7 +63,7 @@ def _sanity_check(self): if self.is_constexpr and self.implementation is None: raise ValueError(f"Constexpr function {self.name} must have implementation") - def _render_constexpr(self): + def _constexpr(self): """ Before function name, declaration only Constexpr functions can't be const, virtual or pure virtual @@ -116,7 +116,7 @@ def render_to_string(self, cpp): if self.documentation: cpp(dedent(self.documentation)) with cpp.block( - f"{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})" + f"{self._constexpr()}{self.ret_type} {self.name}({self.args()})" ): self.implementation(cpp) @@ -134,7 +134,7 @@ def render_to_string_declaration(self, cpp): self.render_to_string(cpp) else: cpp( - f"{self._render_constexpr()}{self.ret_type} {self.name}({self.args()});" + f"{self._constexpr()}{self.ret_type} {self.name}({self.args()});" ) def render_to_string_implementation(self, cpp): @@ -155,6 +155,6 @@ def render_to_string_implementation(self, cpp): if self.documentation and not self.is_constexpr: cpp(dedent(self.documentation)) with cpp.block( - f"{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})" + f"{self._constexpr()}{self.ret_type} {self.name}({self.args()})" ): self.implementation(cpp) diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index 8265aa8..7e8534d 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -117,31 +117,31 @@ def _sanity_check(self): "Variable object can be either 'extern' or 'static', not both" ) - def _render_static(self): + def _static(self): """ @return: 'static' prefix, can't be used with 'extern' """ return "static " if self.is_static else "" - def _render_extern(self): + def _extern(self): """ @return: 'extern' prefix, can't be used with 'static' """ return "extern " if self.is_extern else "" - def _render_const(self): + def _const(self): """ @return: 'const' prefix, can't be used with 'constexpr' """ return "const " if self.is_const else "" - def _render_constexpr(self): + def _constexpr(self): """ @return: 'constexpr' prefix, can't be used with 'const' """ return "constexpr " if self.is_constexpr else "" - def _render_init_value(self): + def _init_value(self): """ @return: string, value to be initialized with """ @@ -182,13 +182,13 @@ def render_to_string(self, cpp): "For class member variables use definition() and declaration() methods" ) elif self.is_extern: - cpp(f"{self._render_extern()}{self.type} {self.name};") + cpp(f"{self._extern()}{self.type} {self.name};") else: if self.documentation: cpp(dedent(self.documentation)) cpp( - f"{self._render_static()}{self._render_const()}{self._render_constexpr()}" - f"{self.type} {self.assignment(self._render_init_value())};" + f"{self._static()}{self._const()}{self._constexpr()}" + f"{self.type} {self.assignment(self._init_value())};" ) def render_to_string_declaration(self, cpp): @@ -204,8 +204,8 @@ def render_to_string_declaration(self, cpp): if self.documentation and self.is_class_member: cpp(dedent(self.documentation)) cpp( - f"{self._render_static()}{self._render_extern()}{self._render_const()}{self._render_constexpr()}" - f"{self.type} {self.name if not self.is_constexpr else self.assignment(self._render_init_value())};" + f"{self._static()}{self._extern()}{self._const()}{self._constexpr()}" + f"{self.type} {self.name if not self.is_constexpr else self.assignment(self._init_value())};" ) def render_to_string_implementation(self, cpp): @@ -230,10 +230,10 @@ def render_to_string_implementation(self, cpp): if not self.is_constexpr: if self.is_static: cpp( - f"{self._render_static()}{self._render_const()}{self._render_constexpr()}" - f"{self.type} {self.fully_qualified_name()} = {self._render_init_value()};" + f"{self._static()}{self._const()}{self._constexpr()}" + f"{self.type} {self.fully_qualified_name()} = {self._init_value()};" ) # generate definition for non-static static class member, e.g. m_var(0) # (string for the constructor initialization list) else: - cpp(f"{self.name}({self._render_init_value()})") + cpp(f"{self.name}({self._init_value()})") diff --git a/src/code_generation/java/class_generator.py b/src/code_generation/java/class_generator.py index 8fc90c2..6515522 100644 --- a/src/code_generation/java/class_generator.py +++ b/src/code_generation/java/class_generator.py @@ -68,7 +68,7 @@ def _render_documentation(self, java): for line in docstring_lines: java(line) - def _render_class_type(self): + def _class_type(self): return "class" if not self.is_record else "record" def inherits(self): @@ -117,7 +117,7 @@ def add_internal_class(self, java_class): def render_to_string(self, java): self._render_documentation(java) with java.block( - f"public {self._render_class_type()} {self.name} {self.inherits()}" + f"public {self._class_type()} {self.name} {self.inherits()}" ): self.class_interface(java) self.private_class_members(java) diff --git a/src/code_generation/java/enum_generator.py b/src/code_generation/java/enum_generator.py index 6ebbbd1..5e7ac9c 100644 --- a/src/code_generation/java/enum_generator.py +++ b/src/code_generation/java/enum_generator.py @@ -55,7 +55,7 @@ def __str__(self): """ return self.values_str() - def _render_static(self, java): + def _static(self, java): """ Render static enums """ @@ -66,4 +66,4 @@ def _render_static(self, java): def render_to_string(self, java): self._sanity_check() - self._render_static(java) + self._static(java) diff --git a/src/code_generation/java/function_generator.py b/src/code_generation/java/function_generator.py index c4f2825..c0be130 100644 --- a/src/code_generation/java/function_generator.py +++ b/src/code_generation/java/function_generator.py @@ -18,7 +18,7 @@ class JavaFunction(JavaLanguageElement): "access_specifier", "documentation", "custom_annotations", - "custom_modifiers", + "custom_size", "implementation" } | JavaLanguageElement.available_properties_names) @@ -71,32 +71,32 @@ def add_argument(self, argument): """ self.arguments.append(argument) - def _render_access_specifier(self): + def _access_specifier(self): return self.access_specifier if self.access_specifier else "" - def _render_static(self): + def _static(self): return "static" if self.is_static else "" - def _render_final(self): + def _final(self): return "final" if self.is_final else "" - def _render_synchronized(self): + def _synchronized(self): return "synchronized" if self.is_synchronized else "" - def _render_native(self): + def _native(self): return "native" if self.is_native else "" - def _render_strictfp(self): + def _strictfp(self): return "strictfp" if self.is_strictfp else "" - def _render_modifiers(self): + def _modifiers(self): modifiers = [ - self._render_access_specifier(), - self._render_static(), - self._render_final(), - self._render_synchronized(), - self._render_native(), - self._render_strictfp(), + self._access_specifier(), + self._static(), + self._final(), + self._synchronized(), + self._native(), + self._strictfp(), ] return " ".join(modifier for modifier in modifiers if modifier) @@ -122,7 +122,7 @@ def render_to_string(self, java): self._render_custom_annotations(java) self._render_custom_modifiers(java) with java.block( - f"{self._render_modifiers()} {self.return_type} {self.name}({self.args_str()})" + f"{self._modifiers()} {self.return_type} {self.name}({self.args_str()})" ): if self.implementation is not None: self.implementation(java) diff --git a/src/code_generation/java/variable_generator.py b/src/code_generation/java/variable_generator.py index e2caa69..3f117c0 100644 --- a/src/code_generation/java/variable_generator.py +++ b/src/code_generation/java/variable_generator.py @@ -90,37 +90,37 @@ def _sanity_check(self): if self.access_modifier not in valid_access_modifiers: raise ValueError(f"Invalid access modifier: {self.access_modifier}") - def _render_type(self): + def _type(self): return f"{self.type} " - def _render_value(self): + def _value(self): return f" = {self.value}" if self.value else "" - def _render_static(self): + def _static(self): return "static " if self.is_static else "" - def _render_final(self): + def _final(self): return "final " if self.is_final else "" - def _render_volatile(self): + def _volatile(self): return "volatile " if self.is_volatile else "" - def _render_transient(self): + def _transient(self): return "transient " if self.is_transient else "" - def _render_synthetic(self): + def _synthetic(self): return "synthetic " if self.is_synthetic else "" - def _render_access_modifier(self): + def _access_modifier(self): return self.access_modifier if self.access_modifier is not None else "" - def _render_modifiers(self): + def _modifiers(self): modifiers = [ - self._render_static(), - self._render_final(), - self._render_volatile(), - self._render_transient(), - self._render_synthetic(), + self._static(), + self._final(), + self._volatile(), + self._transient(), + self._synthetic(), ] return " ".join(modifier for modifier in modifiers if modifier) @@ -146,8 +146,8 @@ def render_to_string(self, java): self._render_custom_modifiers(java) self._render_documentation(java) java( - f"{self._render_modifiers()}" - f"{self._render_type()}" + f"{self._modifiers()}" + f"{self._type()}" f"{self.name}" - f"{self._render_value()};" + f"{self._value()};" ) From 087a80a1014b1bad76fecc79a0b786bccac829d9 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 15 Jan 2024 20:36:07 +0700 Subject: [PATCH 43/51] Major naming refactoring --- src/code_generation/cpp/array_generator.py | 112 +++---- src/code_generation/cpp/class_generator.py | 306 +++++++++--------- src/code_generation/cpp/enum_generator.py | 30 +- src/code_generation/cpp/function_generator.py | 32 +- src/code_generation/cpp/variable_generator.py | 135 ++++---- src/code_generation/java/array_generator.py | 17 +- src/code_generation/java/class_generator.py | 86 +++-- src/code_generation/java/enum_generator.py | 46 +-- .../java/function_generator.py | 79 ++--- .../java/variable_generator.py | 56 ++-- 10 files changed, 467 insertions(+), 432 deletions(-) diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index 539b1a4..4ff9aa0 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -27,6 +27,10 @@ class MyClass NOTE: versions 2.0+ of CodeGenerator support boolean properties without "is_" suffix, but old versions preserved for backward compatibility + + NOTE: methods responsible for rendering of any element to string start from 'render_*' + (e.g. render_value, render_to_string) + Methods simply returning string representation of the element start from '_' """ availablePropertiesNames = ( @@ -60,58 +64,6 @@ def __init__(self, **properties): input_properties_dict=properties, ) - def _sanity_check(self): - """ - Check if all required properties are set - """ - if not self.type: - raise RuntimeError("Array type is not set") - if not self.name: - raise RuntimeError("Array name is not set") - if self.is_class_member and not self.name: - raise RuntimeError("Class member array name is not set") - - def _static(self): - """ - @return: 'static' prefix if required - """ - return "static" if self.is_static else "" - - def _const(self): - """ - @return: 'const' prefix if required - """ - return "const" if self.is_const else "" - - def _modifiers(self): - modifiers = [ - self._static(), - self._const() - ] - return " ".join(modifiers) - - def _size(self): - """ - @return: array size - """ - return self.array_size if self.array_size else "" - - def _render_size(self): - """ - @return: array items if any - """ - return ", ".join(self.items) if self.items else "nullptr" - - def _render_value(self, cpp): - """ - Render to string array items - """ - if not self.items: - raise RuntimeError("Empty arrays do not supported") - for item in self.items[:-1]: - cpp("{0},".format(item)) - cpp("{0}".format(self.items[-1])) - def declaration(self): """ @return: CppDeclaration wrapper, that could be used @@ -175,7 +127,7 @@ def render_to_string(self, cpp): f"{self.name}" f"[{self._size()}]" f" = " - f"{{{self._render_size()}}};" + f"{{{self._content()}}};" ) def render_to_string_declaration(self, cpp): @@ -237,5 +189,57 @@ def render_to_string_implementation(self, cpp): f"{self.fully_qualified_name()}" f"[{self._size()}]" f" = " - f"{{{self._render_size()}}};" + f"{{{self._content()}}};" ) + + def _sanity_check(self): + """ + Check if all required properties are set + """ + if not self.type: + raise RuntimeError("Array type is not set") + if not self.name: + raise RuntimeError("Array name is not set") + if self.is_class_member and not self.name: + raise RuntimeError("Class member array name is not set") + + def _static(self): + """ + @return: 'static' prefix if required + """ + return "static" if self.is_static else "" + + def _const(self): + """ + @return: 'const' prefix if required + """ + return "const" if self.is_const else "" + + def _modifiers(self): + modifiers = [ + self._static(), + self._const() + ] + return " ".join(modifiers) + + def _size(self): + """ + @return: array size + """ + return self.array_size if self.array_size else "" + + def _content(self): + """ + @return: array items if any + """ + return ", ".join(self.items) if self.items else "nullptr" + + def _render_value(self, cpp): + """ + Render to string array items + """ + if not self.items: + raise RuntimeError("Empty arrays do not supported") + for item in self.items[:-1]: + cpp("{0},".format(item)) + cpp("{0}".format(self.items[-1])) diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index 4d3db0a..c451e15 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -117,99 +117,6 @@ def __init__(self, **properties): input_properties_dict=properties, ) - def _static(self): - """ - Before function name, declaration only - Static functions can't be const, virtual or pure virtual - """ - return "static" if self.is_static else "" - - def _constexpr(self): - """ - Before function name, declaration only - Constexpr functions can't be const, virtual or pure virtual - """ - return "constexpr" if self.is_constexpr else "" - - def _render_virtual(self): - """ - Before function name, could be in declaration or definition - Virtual functions can't be static or constexpr - """ - return "virtual" if self.is_virtual else "" - - def _render_inline(self): - """ - Before function name, could be in declaration or definition - Inline functions can't be static, virtual or constexpr - """ - return "inline" if self.is_inline else "" - - def _render_ret_type(self): - """ - Return type, could be in declaration or definition - """ - return self.ret_type if self.ret_type else "" - - def _render_pure(self): - """ - After function name, declaration only - Pure virtual functions must be virtual - """ - return " = 0" if self.is_pure_virtual else "" - - def _const(self): - """ - After function name, could be in declaration or definition - Const functions can't be static, virtual or constexpr - """ - return "const" if self.is_const else "" - - def _render_override(self): - """ - After function name, could be in declaration or definition - Override functions must be virtual - """ - return "override" if self.is_override else "" - - def _final(self): - """ - After function name, could be in declaration or definition - Final functions must be virtual - """ - return "final" if self.is_final else "" - - def _sanity_check(self): - """ - Check whether attributes compose a correct C++ code - """ - if self.is_inline and (self.is_virtual or self.is_pure_virtual): - raise ValueError(f"Inline method {self.name} could not be virtual") - if self.is_constexpr and (self.is_virtual or self.is_pure_virtual): - raise ValueError(f"Constexpr method {self.name} could not be virtual") - if self.is_const and self.is_static: - raise ValueError(f"Static method {self.name} could not be const") - if self.is_const and self.is_virtual: - raise ValueError(f"Virtual method {self.name} could not be const") - if self.is_const and self.is_pure_virtual: - raise ValueError(f"Pure virtual method {self.name} could not be const") - if self.is_override and not self.is_virtual: - raise ValueError(f"Override method {self.name} should be virtual") - if self.is_inline and (self.is_virtual or self.is_pure_virtual): - raise ValueError(f"Inline method {self.name} could not be virtual") - if self.is_final and not self.is_virtual: - raise ValueError(f"Final method {self.name} should be virtual") - if self.is_static and self.is_virtual: - raise ValueError(f"Static method {self.name} could not be virtual") - if self.is_pure_virtual and not self.is_virtual: - raise ValueError(f"Pure virtual method {self.name} is also a virtual method") - if not self.ref_to_parent: - raise ValueError(f"Method {self.name} object must be a child of CppClass") - if self.is_constexpr and self.implementation is None: - raise ValueError(f'Method {self.name} object must be initialized when "constexpr"') - if self.is_pure_virtual and self.implementation is not None: - raise ValueError(f"Pure virtual method {self.name} could not be implemented") - def add_argument(self, argument): """ @param: argument string representation of the C++ function argument ('int a', 'void p = NULL' etc) @@ -222,7 +129,7 @@ def args(self): """ return ", ".join(self.arguments) - def implementation(self, cpp): + def body(self, cpp): """ The method calls Python function that creates C++ method body if handle exists """ @@ -243,22 +150,6 @@ def definition(self): """ return CppImplementation(self) - def _modifiers_front(self): - modifiers = [ - self._constexpr(), - self._render_virtual(), - self._render_inline(), - ] - return " ".join(modifiers) - - def _modifiers_back(self): - modifiers = [ - self._const(), - self._render_override(), - self._final() - ] - return " ".join(modifiers) - def render_to_string(self, cpp): """ By default, method is rendered as a declaration together with implementation, @@ -278,11 +169,11 @@ class A with cpp.block( f"{self._static()}" f"{self._modifiers_front()}" - f"{self._render_ret_type()} " + f"{self._ret_type()} " f"{self.fully_qualified_name()}" f"({self.args()})" f"{self._modifiers_back()}" - f"{self._render_pure()}" + f"{self._pure()}" ): self.implementation(cpp) @@ -303,11 +194,11 @@ def render_to_string_declaration(self, cpp): cpp( f"{self._static()}" f"{self._modifiers_front()}" - f"{self._render_ret_type()} " + f"{self._ret_type()} " f"{self.name}" f"({self.args()})" f"{self._modifiers_back()}" - f"{self._render_pure()};" + f"{self._pure()};" ) def render_to_string_implementation(self, cpp): @@ -334,13 +225,122 @@ def render_to_string_implementation(self, cpp): cpp(dedent(self.documentation)) with cpp.block( f"{self._modifiers_front()}" - f"{self._render_ret_type()} " + f"{self._ret_type()} " f"{self.fully_qualified_name()}" f"({self.args()})" f"{self._modifiers_back()}" ): self.implementation(cpp) + def _sanity_check(self): + """ + Check whether attributes compose a correct C++ code + """ + if self.is_inline and (self.is_virtual or self.is_pure_virtual): + raise ValueError(f"Inline method {self.name} could not be virtual") + if self.is_constexpr and (self.is_virtual or self.is_pure_virtual): + raise ValueError(f"Constexpr method {self.name} could not be virtual") + if self.is_const and self.is_static: + raise ValueError(f"Static method {self.name} could not be const") + if self.is_const and self.is_virtual: + raise ValueError(f"Virtual method {self.name} could not be const") + if self.is_const and self.is_pure_virtual: + raise ValueError(f"Pure virtual method {self.name} could not be const") + if self.is_override and not self.is_virtual: + raise ValueError(f"Override method {self.name} should be virtual") + if self.is_inline and (self.is_virtual or self.is_pure_virtual): + raise ValueError(f"Inline method {self.name} could not be virtual") + if self.is_final and not self.is_virtual: + raise ValueError(f"Final method {self.name} should be virtual") + if self.is_static and self.is_virtual: + raise ValueError(f"Static method {self.name} could not be virtual") + if self.is_pure_virtual and not self.is_virtual: + raise ValueError(f"Pure virtual method {self.name} is also a virtual method") + if not self.ref_to_parent: + raise ValueError(f"Method {self.name} object must be a child of CppClass") + if self.is_constexpr and self.implementation is None: + raise ValueError(f'Method {self.name} object must be initialized when "constexpr"') + if self.is_pure_virtual and self.implementation is not None: + raise ValueError(f"Pure virtual method {self.name} could not be implemented") + + def _modifiers_front(self): + modifiers = [ + self._constexpr(), + self._virtual(), + self._inline(), + ] + return " ".join(modifiers) + + def _modifiers_back(self): + modifiers = [ + self._const(), + self._override(), + self._final() + ] + return " ".join(modifiers) + + def _static(self): + """ + Before function name, declaration only + Static functions can't be const, virtual or pure virtual + """ + return "static" if self.is_static else "" + + def _constexpr(self): + """ + Before function name, declaration only + Constexpr functions can't be const, virtual or pure virtual + """ + return "constexpr" if self.is_constexpr else "" + + def _virtual(self): + """ + Before function name, could be in declaration or definition + Virtual functions can't be static or constexpr + """ + return "virtual" if self.is_virtual else "" + + def _inline(self): + """ + Before function name, could be in declaration or definition + Inline functions can't be static, virtual or constexpr + """ + return "inline" if self.is_inline else "" + + def _ret_type(self): + """ + Return type, could be in declaration or definition + """ + return self.ret_type if self.ret_type else "" + + def _pure(self): + """ + After function name, declaration only + Pure virtual functions must be virtual + """ + return " = 0" if self.is_pure_virtual else "" + + def _const(self): + """ + After function name, could be in declaration or definition + Const functions can't be static, virtual or constexpr + """ + return "const" if self.is_const else "" + + def _override(self): + """ + After function name, could be in declaration or definition + Override functions must be virtual + """ + return "override" if self.is_override else "" + + def _final(self): + """ + After function name, could be in declaration or definition + Final functions must be virtual + """ + return "final" if self.is_final else "" + def __init__(self, **properties): self.is_struct = False self.documentation = None @@ -368,12 +368,6 @@ def __init__(self, **properties): # class scoped enums self.scoped_enums = [] - def _parent_class(self): - """ - @return: parent class object - """ - return self.parent_class if self.parent_class else "" - def inherits(self): """ @return: string representation of the inheritance @@ -422,9 +416,29 @@ def add_method(self, method): method.is_method = True self.methods.append(method) + ######################################## + # GROUP GENERATED SECTIONS + def class_interface(self, cpp): + """ + Generates section that generally used as an 'open interface' + Generates string representation for enums, internal classes and methods + Should be placed in 'public:' section + """ + self.render_enum_section(cpp) + self.render_internal_classes_declaration(cpp) + self.render_methods_declaration(cpp) + + def private_class_members(self, cpp): + """ + Generates section of class member variables. + Should be placed in 'private:' section + """ + self.render_variables_declaration(cpp) + self.render_array_declaration(cpp) + ######################################## # RENDER CLASS MEMBERS - def _render_internal_classes_declaration(self, cpp): + def render_internal_classes_declaration(self, cpp): """ Generates section of nested classes Could be placed both in 'private:' or 'public:' sections @@ -433,7 +447,7 @@ def _render_internal_classes_declaration(self, cpp): for class_item in self.internal_class_elements: class_item.declaration().render_to_string(cpp) - def _render_enum_section(self, cpp): + def render_enum_section(self, cpp): """ Render to string all contained enums Method is protected as it is used by CppClass only @@ -441,7 +455,7 @@ def _render_enum_section(self, cpp): for enum_item in self.scoped_enums: enum_item.render_to_string(cpp) - def _render_variables_declaration(self, cpp): + def render_variables_declaration(self, cpp): """ Render to string all contained variable class members Method is protected as it is used by CppClass only @@ -449,7 +463,7 @@ def _render_variables_declaration(self, cpp): for var_item in self.variable_members: var_item.declaration().render_to_string(cpp) - def _render_array_declaration(self, cpp): + def render_array_declaration(self, cpp): """ Render to string all contained array class members Method is protected as it is used by CppClass only @@ -457,7 +471,7 @@ def _render_array_declaration(self, cpp): for arr_item in self.array_members: arr_item.declaration().render_to_string(cpp) - def _render_methods_declaration(self, cpp): + def render_methods_declaration(self, cpp): """ Generates all class methods declaration Should be placed in 'public:' section @@ -506,26 +520,6 @@ def render_methods_implementation(self, cpp): class_item.render_static_members_implementation(cpp) cpp.newline() - ######################################## - # GROUP GENERATED SECTIONS - def class_interface(self, cpp): - """ - Generates section that generally used as an 'open interface' - Generates string representation for enums, internal classes and methods - Should be placed in 'public:' section - """ - self._render_enum_section(cpp) - self._render_internal_classes_declaration(cpp) - self._render_methods_declaration(cpp) - - def private_class_members(self, cpp): - """ - Generates section of class member variables. - Should be placed in 'private:' section - """ - self._render_variables_declaration(cpp) - self._render_array_declaration(cpp) - def render_to_string(self, cpp): """ Render to string both declaration and definition. @@ -555,12 +549,6 @@ def render_to_string_declaration(self, cpp): self.private_class_members(cpp) cpp.newline() - def _class_type(self): - """ - @return: 'class' or 'struct' keyword - """ - return "struct" if self.is_struct else "class" - def render_to_string_implementation(self, cpp): """ Render to string class definition. @@ -582,3 +570,17 @@ def definition(self): for definition rendering using render_to_string(cpp) interface """ return CppImplementation(self) + + ######################################## + # PRIVATE METHODS + def _class_type(self): + """ + @return: 'class' or 'struct' keyword + """ + return "struct" if self.is_struct else "class" + + def _parent_class(self): + """ + @return: parent class object + """ + return self.parent_class if self.parent_class else "" diff --git a/src/code_generation/cpp/enum_generator.py b/src/code_generation/cpp/enum_generator.py index 04293a5..5cad474 100644 --- a/src/code_generation/cpp/enum_generator.py +++ b/src/code_generation/cpp/enum_generator.py @@ -24,19 +24,29 @@ class CppEnum(CppLanguageElement): eShelve = 2, eItemsCount = 3 } + + NOTE: methods responsible for rendering of any element to string start from 'render_*' + (e.g. render_value, render_to_string) + Methods simply returning string representation of the element start from '_' """ - availablePropertiesNames = { - "prefix", - "enum_class", - "add_counter", - } | CppLanguageElement.availablePropertiesNames + availablePropertiesNames = ( + { + "prefix", + "enum_class", + "add_counter", + "enum_items" + } | CppLanguageElement.availablePropertiesNames) def __init__(self, **properties): """ :param properties: """ self.enum_class = False + self.enum_items = [] + self.prefix = None + self.add_counter = True + # check properties input_property_names = set(properties.keys()) self.check_input_properties_names(input_property_names) @@ -47,12 +57,6 @@ def __init__(self, **properties): input_properties_dict=properties, ) - # place enum items here - self.enum_items = [] - - def _enum_class(self): - return "class " if self.enum_class else "" - def add_item(self, item): """ @param: item - string representation for the enum element @@ -65,7 +69,6 @@ def add_items(self, items): """ self.enum_items.extend(items) - # noinspection PyUnresolvedReferences def render_to_string(self, cpp): """ Generates a string representation for the enum @@ -86,3 +89,6 @@ def render_to_string(self, cpp): if self.add_counter in [None, True]: last_element = f"{final_prefix}{self.name}Count = {counter}" cpp(last_element) + + def _enum_class(self): + return "class " if self.enum_class else "" diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index fbddd2a..b4da8d5 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -31,12 +31,13 @@ def functionBody(self, cpp): cpp('return 42;') } """ - availablePropertiesNames = { - "ret_type", - "is_constexpr", - "implementation", - "documentation", - } | CppLanguageElement.availablePropertiesNames + availablePropertiesNames = ( + { + "ret_type", + "is_constexpr", + "implementation", + "documentation", + } | CppLanguageElement.availablePropertiesNames) def __init__(self, **properties): # arguments are plain strings @@ -82,7 +83,7 @@ def add_argument(self, argument): """ self.arguments.append(argument) - def implementation(self, cpp): + def body(self, cpp): """ The method calls Python function that creates C++ method body if handle exists """ @@ -116,7 +117,10 @@ def render_to_string(self, cpp): if self.documentation: cpp(dedent(self.documentation)) with cpp.block( - f"{self._constexpr()}{self.ret_type} {self.name}({self.args()})" + f"{self._constexpr()}" + f"{self.ret_type} " + f"{self.name}" + f"({self.args()})" ): self.implementation(cpp) @@ -134,7 +138,10 @@ def render_to_string_declaration(self, cpp): self.render_to_string(cpp) else: cpp( - f"{self._constexpr()}{self.ret_type} {self.name}({self.args()});" + f"{self._constexpr()}" + f"{self.ret_type} " + f"{self.name}" + f"({self.args()});" ) def render_to_string_implementation(self, cpp): @@ -146,7 +153,7 @@ def render_to_string_implementation(self, cpp): { ... } - Generates method body if self.implementation property exists + Generates method body if `self.implementation` property exists """ if self.implementation is None: raise RuntimeError(f"No implementation handle for the function {self.name}") @@ -155,6 +162,9 @@ def render_to_string_implementation(self, cpp): if self.documentation and not self.is_constexpr: cpp(dedent(self.documentation)) with cpp.block( - f"{self._constexpr()}{self.ret_type} {self.name}({self.args()})" + f"{self._constexpr()}" + f"{self.ret_type} " + f"{self.name}" + f"({self.args()})" ): self.implementation(cpp) diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index 7e8534d..0962e0e 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -82,16 +82,17 @@ class MyClass is_class_member - boolean, for appropriate definition/declaration rendering """ - availablePropertiesNames = { - "type", - "is_static", - "is_extern", - "is_const", - "is_constexpr", - "value", - "documentation", - "is_class_member", - } | CppLanguageElement.availablePropertiesNames + availablePropertiesNames = ( + { + "type", + "is_static", + "is_extern", + "is_const", + "is_constexpr", + "value", + "documentation", + "is_class_member", + } | CppLanguageElement.availablePropertiesNames) def __init__(self, **properties): input_property_names = set(properties.keys()) @@ -102,51 +103,6 @@ def __init__(self, **properties): input_properties_dict=properties, ) - def _sanity_check(self): - """ - @raise: ValueError, if some properties are not valid - """ - if self.is_const and self.is_constexpr: - raise ValueError( - "Variable object can be either 'const' or 'constexpr', not both" - ) - if self.is_constexpr and not self.value: - raise ValueError("Variable object must be initialized when 'constexpr'") - if self.is_static and self.is_extern: - raise ValueError( - "Variable object can be either 'extern' or 'static', not both" - ) - - def _static(self): - """ - @return: 'static' prefix, can't be used with 'extern' - """ - return "static " if self.is_static else "" - - def _extern(self): - """ - @return: 'extern' prefix, can't be used with 'static' - """ - return "extern " if self.is_extern else "" - - def _const(self): - """ - @return: 'const' prefix, can't be used with 'constexpr' - """ - return "const " if self.is_const else "" - - def _constexpr(self): - """ - @return: 'constexpr' prefix, can't be used with 'const' - """ - return "constexpr " if self.is_constexpr else "" - - def _init_value(self): - """ - @return: string, value to be initialized with - """ - return self.value if self.value else "" - def assignment(self, value): """ Generates assignment statement for the variable, e.g. @@ -187,8 +143,11 @@ def render_to_string(self, cpp): if self.documentation: cpp(dedent(self.documentation)) cpp( - f"{self._static()}{self._const()}{self._constexpr()}" - f"{self.type} {self.assignment(self._init_value())};" + f"{self._static()}" + f"{self._const()}" + f"{self._constexpr()}" + f"{self.type} " + f"{self.assignment(self._init_value())};" ) def render_to_string_declaration(self, cpp): @@ -204,8 +163,12 @@ def render_to_string_declaration(self, cpp): if self.documentation and self.is_class_member: cpp(dedent(self.documentation)) cpp( - f"{self._static()}{self._extern()}{self._const()}{self._constexpr()}" - f"{self.type} {self.name if not self.is_constexpr else self.assignment(self._init_value())};" + f"{self._static()}" + f"{self._extern()}" + f"{self._const()}" + f"{self._constexpr()}" + f"{self.type} " + f"{self.name if not self.is_constexpr else self.assignment(self._init_value())};" ) def render_to_string_implementation(self, cpp): @@ -230,10 +193,60 @@ def render_to_string_implementation(self, cpp): if not self.is_constexpr: if self.is_static: cpp( - f"{self._static()}{self._const()}{self._constexpr()}" - f"{self.type} {self.fully_qualified_name()} = {self._init_value()};" + f"{self._static()}" + f"{self._const()}" + f"{self._constexpr()}" + f"{self.type} " + f"{self.fully_qualified_name()}" + f" = " + f"{self._init_value()};" ) # generate definition for non-static static class member, e.g. m_var(0) # (string for the constructor initialization list) else: cpp(f"{self.name}({self._init_value()})") + + def _sanity_check(self): + """ + @raise: ValueError, if some properties are not valid + """ + if self.is_const and self.is_constexpr: + raise ValueError( + "Variable object can be either 'const' or 'constexpr', not both" + ) + if self.is_constexpr and not self.value: + raise ValueError("Variable object must be initialized when 'constexpr'") + if self.is_static and self.is_extern: + raise ValueError( + "Variable object can be either 'extern' or 'static', not both" + ) + + def _static(self): + """ + @return: 'static' prefix, can't be used with 'extern' + """ + return "static " if self.is_static else "" + + def _extern(self): + """ + @return: 'extern' prefix, can't be used with 'static' + """ + return "extern " if self.is_extern else "" + + def _const(self): + """ + @return: 'const' prefix, can't be used with 'constexpr' + """ + return "const " if self.is_const else "" + + def _constexpr(self): + """ + @return: 'constexpr' prefix, can't be used with 'const' + """ + return "constexpr " if self.is_constexpr else "" + + def _init_value(self): + """ + @return: string, value to be initialized with + """ + return self.value if self.value else "" diff --git a/src/code_generation/java/array_generator.py b/src/code_generation/java/array_generator.py index a9c44fc..df66bfc 100644 --- a/src/code_generation/java/array_generator.py +++ b/src/code_generation/java/array_generator.py @@ -4,14 +4,15 @@ class JavaArray(JavaLanguageElement): - available_properties_names = { - "name", - "type", - "values", - "quoted", - "is_class_member", - "add_counter", - } | JavaLanguageElement.available_properties_names + available_properties_names = ( + { + "name", + "type", + "values", + "quoted", + "is_class_member", + "add_counter", + } | JavaLanguageElement.available_properties_names) def __init__(self, **properties): """ diff --git a/src/code_generation/java/class_generator.py b/src/code_generation/java/class_generator.py index 6515522..71e209d 100644 --- a/src/code_generation/java/class_generator.py +++ b/src/code_generation/java/class_generator.py @@ -1,6 +1,5 @@ from code_generation.java.language_element import JavaLanguageElement - __doc__ = """""" @@ -31,11 +30,12 @@ class JavaClass(JavaLanguageElement): } """ - available_properties_names = { - "documentation", - "parent_class", - "is_record", - } | JavaLanguageElement.available_properties_names + available_properties_names = ( + { + "documentation", + "parent_class", + "is_record", + } | JavaLanguageElement.available_properties_names) def __init__(self, **properties): self.documentation = None @@ -55,69 +55,67 @@ def __init__(self, **properties): self.internal_enum_elements = [] self.internal_method_elements = [] - def _parent_class(self): - return self.parent_class if self.parent_class else "" + def add_variable(self, java_variable): + java_variable.ref_to_parent = self + self.internal_variable_elements.append(java_variable) - def _render_documentation(self, java): - if self.documentation: - docstring_lines = ["/**"] - docstring_lines.extend( - [f" * {line}" for line in self.documentation.splitlines()] - ) - docstring_lines.append(" */") - for line in docstring_lines: - java(line) + def add_method(self, java_method): + java_method.ref_to_parent = self + self.internal_method_elements.append(java_method) - def _class_type(self): - return "class" if not self.is_record else "record" + def add_internal_class(self, java_class): + java_class.ref_to_parent = self + self.internal_class_elements.append(java_class) def inherits(self): return f"extends {self._parent_class()}" if self.parent_class else "" def class_interface(self, java): - self._render_internal_classes_declaration(java) - self._render_enum_section(java) - self._render_methods_declaration(java) + self.render_internal_classes_declaration(java) + self.render_enum_section(java) + self.render_methods_declaration(java) + + def private_class_members(self, java): + self.render_variables_declaration(java) - def _render_internal_classes_declaration(self, java): + def render_internal_classes_declaration(self, java): for class_item in self.internal_class_elements: class_item.declaration().render_to_string(java) java.newline() - def _render_enum_section(self, java): + def render_enum_section(self, java): for enum_item in self.internal_enum_elements: enum_item.render_to_string(java) java.newline() - def _render_variables_declaration(self, java): + def render_variables_declaration(self, java): for var_item in self.internal_variable_elements: var_item.render_to_string(java) java.newline() - def _render_methods_declaration(self, java): + def render_methods_declaration(self, java): for method_item in self.internal_method_elements: method_item.render_to_string(java) java.newline() - def private_class_members(self, java): - self._render_variables_declaration(java) - - def add_variable(self, java_variable): - java_variable.ref_to_parent = self - self.internal_variable_elements.append(java_variable) - - def add_method(self, java_method): - java_method.ref_to_parent = self - self.internal_method_elements.append(java_method) - - def add_internal_class(self, java_class): - java_class.ref_to_parent = self - self.internal_class_elements.append(java_class) - def render_to_string(self, java): self._render_documentation(java) - with java.block( - f"public {self._class_type()} {self.name} {self.inherits()}" - ): + with java.block(f"public {self._class_type()} {self.name} {self.inherits()}"): self.class_interface(java) self.private_class_members(java) + + def _parent_class(self): + return self.parent_class if self.parent_class else "" + + def _render_documentation(self, java): + if self.documentation: + docstring_lines = ["/**"] + docstring_lines.extend( + [f" * {line}" for line in self.documentation.splitlines()] + ) + docstring_lines.append(" */") + for line in docstring_lines: + java(line) + + def _class_type(self): + return "class" if not self.is_record else "record" diff --git a/src/code_generation/java/enum_generator.py b/src/code_generation/java/enum_generator.py index 5e7ac9c..0f9ada9 100644 --- a/src/code_generation/java/enum_generator.py +++ b/src/code_generation/java/enum_generator.py @@ -1,15 +1,15 @@ from code_generation.java.language_element import JavaLanguageElement - __doc__ = """""" class JavaEnum(JavaLanguageElement): - available_properties_names = { - "name", - "values", - "is_class_member", - } | JavaLanguageElement.available_properties_names + available_properties_names = ( + { + "name", + "values", + "is_class_member", + } | JavaLanguageElement.available_properties_names) def __init__(self, **properties): """ @@ -26,18 +26,11 @@ def __init__(self, **properties): input_properties_dict=properties, ) - def _sanity_check(self): + def __str__(self): """ - Check if all required properties are set + String representation of enum """ - if not self.name: - raise RuntimeError("Enum name is not set") - if not isinstance(self.name, str): - raise RuntimeError(f"Enum name is not a string, but {type(self.name)}") - if self.values is not None and not isinstance(self.values, list): - raise RuntimeError(f"Enum values are not a list, but {type(self.values)}") - if self.is_class_member and not self.name: - raise RuntimeError("Class member enum name is not set") + return self.values_str() def values_str(self): """ @@ -49,11 +42,22 @@ def values_str(self): return "" return ", ".join(str(value) for value in self.values) - def __str__(self): + def render_to_string(self, java): + self._sanity_check() + self._static(java) + + def _sanity_check(self): """ - String representation of enum + Check if all required properties are set """ - return self.values_str() + if not self.name: + raise RuntimeError("Enum name is not set") + if not isinstance(self.name, str): + raise RuntimeError(f"Enum name is not a string, but {type(self.name)}") + if self.values is not None and not isinstance(self.values, list): + raise RuntimeError(f"Enum values are not a list, but {type(self.values)}") + if self.is_class_member and not self.name: + raise RuntimeError("Class member enum name is not set") def _static(self, java): """ @@ -63,7 +67,3 @@ def _static(self, java): java(f"enum {self.name} {{}}") else: java(f"enum {self.name} {{ {self.values_str()} }};") - - def render_to_string(self, java): - self._sanity_check() - self._static(java) diff --git a/src/code_generation/java/function_generator.py b/src/code_generation/java/function_generator.py index c0be130..c3d6473 100644 --- a/src/code_generation/java/function_generator.py +++ b/src/code_generation/java/function_generator.py @@ -47,18 +47,6 @@ def __init__(self, **properties): # strip documentation of /** and */ because it's added in _render_documentation self.documentation = self.documentation.strip("/**").strip("*/") - def _sanity_check(self): - if not self.name: - raise RuntimeError("Method name is not set") - if not isinstance(self.name, str): - raise RuntimeError(f"Method name is not a string, but {type(self.name)}") - if not isinstance(self.return_type, str): - raise RuntimeError( - f"Return type is not a string, but {type(self.return_type)}" - ) - if self.arguments is not None and not isinstance(self.arguments, list): - raise RuntimeError(f"Arguments are not a list, but {type(self.arguments)}") - def args_str(self): if self.arguments is None or not self.arguments: return "" @@ -71,6 +59,46 @@ def add_argument(self, argument): """ self.arguments.append(argument) + def render_custom_annotations(self, java): + if self.custom_annotations: + for annotation in self.custom_annotations: + java(f"@{annotation}") + + def render_custom_modifiers(self, java): + if self.custom_modifiers: + for modifier in self.custom_modifiers: + java(f"{modifier} ") + + def render_documentation(self, java): + if self.documentation: + java("/**") + java(f" * {self.documentation}") + java(" */") + + def render_to_string(self, java): + self._sanity_check() + self.render_documentation(java) + self.render_custom_annotations(java) + self.render_custom_modifiers(java) + with java.block( + f"{self._modifiers()} " + f"{self.return_type} " + f"{self.name}" + f"({self.args_str()})" + ): + if self.implementation is not None: + self.implementation(java) + + def _sanity_check(self): + if not self.name: + raise RuntimeError("Method name is not set") + if not isinstance(self.name, str): + raise RuntimeError(f"Method name is not a string, but {type(self.name)}") + if not isinstance(self.return_type, str): + raise RuntimeError(f"Return type is not a string, but {type(self.return_type)}") + if self.arguments is not None and not isinstance(self.arguments, list): + raise RuntimeError(f"Arguments are not a list, but {type(self.arguments)}") + def _access_specifier(self): return self.access_specifier if self.access_specifier else "" @@ -99,30 +127,3 @@ def _modifiers(self): self._strictfp(), ] return " ".join(modifier for modifier in modifiers if modifier) - - def _render_custom_annotations(self, java): - if self.custom_annotations: - for annotation in self.custom_annotations: - java(f"@{annotation}") - - def _render_custom_modifiers(self, java): - if self.custom_modifiers: - for modifier in self.custom_modifiers: - java(f"{modifier} ") - - def _render_documentation(self, java): - if self.documentation: - java("/**") - java(f" * {self.documentation}") - java(" */") - - def render_to_string(self, java): - self._sanity_check() - self._render_documentation(java) - self._render_custom_annotations(java) - self._render_custom_modifiers(java) - with java.block( - f"{self._modifiers()} {self.return_type} {self.name}({self.args_str()})" - ): - if self.implementation is not None: - self.implementation(java) diff --git a/src/code_generation/java/variable_generator.py b/src/code_generation/java/variable_generator.py index 3f117c0..b2685b6 100644 --- a/src/code_generation/java/variable_generator.py +++ b/src/code_generation/java/variable_generator.py @@ -68,6 +68,34 @@ def __init__(self, **properties): # strip documentation of /** and */ because it's added in _render_documentation self.documentation = self.documentation.strip("/**").strip("*/") + def render_custom_annotations(self, java): + if self.custom_annotations: + for annotation in self.custom_annotations: + java(f"@{annotation}") + + def render_custom_modifiers(self, java): + if self.custom_modifiers: + for modifier in self.custom_modifiers: + java(f"{modifier} ") + + def render_documentation(self, java): + if self.documentation: + java("/**") + java(f" * {self.documentation}") + java(" */") + + def render_to_string(self, java): + self._sanity_check() + self.render_custom_annotations(java) + self.render_custom_modifiers(java) + self.render_documentation(java) + java( + f"{self._modifiers()}" + f"{self._type()}" + f"{self.name}" + f"{self._value()};" + ) + def _sanity_check(self): # Basic checks if not self.name: @@ -123,31 +151,3 @@ def _modifiers(self): self._synthetic(), ] return " ".join(modifier for modifier in modifiers if modifier) - - def _render_custom_annotations(self, java): - if self.custom_annotations: - for annotation in self.custom_annotations: - java(f"@{annotation}") - - def _render_custom_modifiers(self, java): - if self.custom_modifiers: - for modifier in self.custom_modifiers: - java(f"{modifier} ") - - def _render_documentation(self, java): - if self.documentation: - java("/**") - java(f" * {self.documentation}") - java(" */") - - def render_to_string(self, java): - self._sanity_check() - self._render_custom_annotations(java) - self._render_custom_modifiers(java) - self._render_documentation(java) - java( - f"{self._modifiers()}" - f"{self._type()}" - f"{self.name}" - f"{self._value()};" - ) From dd407b1036501fbe524365ae86cb3703a02d6e15 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 23 Jan 2024 13:28:03 +0700 Subject: [PATCH 44/51] Fix last unit test --- src/code_generation/cpp/array_generator.py | 4 +++- src/code_generation/cpp/class_generator.py | 4 +++- src/code_generation/cpp/function_generator.py | 6 +---- src/code_generation/cpp/language_element.py | 24 +++++++++---------- src/code_generation/cpp/variable_generator.py | 6 +---- .../java/variable_generator.py | 2 ++ 6 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/code_generation/cpp/array_generator.py b/src/code_generation/cpp/array_generator.py index 4ff9aa0..b19158f 100644 --- a/src/code_generation/cpp/array_generator.py +++ b/src/code_generation/cpp/array_generator.py @@ -1,4 +1,4 @@ -from code_generation.cpp.language_element import CppLanguageElement +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation # noinspection PyUnresolvedReferences @@ -220,6 +220,8 @@ def _modifiers(self): self._static(), self._const() ] + # leave only non-empty elements + modifiers = [mod for mod in modifiers if mod] return " ".join(modifiers) def _size(self): diff --git a/src/code_generation/cpp/class_generator.py b/src/code_generation/cpp/class_generator.py index c451e15..a547ad3 100644 --- a/src/code_generation/cpp/class_generator.py +++ b/src/code_generation/cpp/class_generator.py @@ -193,7 +193,7 @@ def render_to_string_declaration(self, cpp): else: cpp( f"{self._static()}" - f"{self._modifiers_front()}" + f"{self._modifiers_front()} " f"{self._ret_type()} " f"{self.name}" f"({self.args()})" @@ -269,6 +269,7 @@ def _modifiers_front(self): self._virtual(), self._inline(), ] + modifiers = [mod for mod in modifiers if mod] return " ".join(modifiers) def _modifiers_back(self): @@ -277,6 +278,7 @@ def _modifiers_back(self): self._override(), self._final() ] + modifiers = [mod for mod in modifiers if mod] return " ".join(modifiers) def _static(self): diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index b4da8d5..484f1e7 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -1,8 +1,4 @@ -from code_generation.cpp.language_element import ( - CppLanguageElement, - CppDeclaration, - CppImplementation, -) +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation from textwrap import dedent diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index d2f090d..c8fada1 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -113,27 +113,25 @@ def check_input_properties_names(self, input_property_names): f"Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}" ) - def init_class_properties( - self, - current_class_properties, - input_properties_dict, - default_property_value=None, - ): + def init_class_properties(self, current_class_properties, input_properties_dict, default_property_value=None): """ + Set default values for all properties not listed in availablePropertiesNames + Set values for all properties that are listed in input_properties_dict @param: current_class_properties - all available properties for the C++ element to be generated @param: input_properties_dict - values for the initialized properties (e.g. is_const=True) @param: default_property_value - value for properties that are not initialized (None by default, because of same as False semantic) """ - # Set all available properties to DefaultValue - for propertyName in current_class_properties: - if propertyName not in CppLanguageElement.availablePropertiesNames: - setattr(self, propertyName, default_property_value) + # Set all properties not listed in availablePropertiesNames to default_property_value + for property_name in current_class_properties: + attribute_value = getattr(self, property_name, None) + if property_name not in CppLanguageElement.availablePropertiesNames and attribute_value is None: + setattr(self, property_name, default_property_value) # Set all defined properties values (all undefined will be left with defaults) - for propertyName, propertyValue in input_properties_dict.items(): - if propertyName not in CppLanguageElement.availablePropertiesNames: - setattr(self, propertyName, propertyValue) + for property_name, propertyValue in input_properties_dict.items(): + if property_name not in CppLanguageElement.availablePropertiesNames: + setattr(self, property_name, propertyValue) def render_to_string(self, cpp): """ diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index 0962e0e..a989e73 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -1,8 +1,4 @@ -from code_generation.cpp.language_element import ( - CppLanguageElement, - CppDeclaration, - CppImplementation, -) +from code_generation.cpp.language_element import CppLanguageElement, CppDeclaration, CppImplementation from textwrap import dedent __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives: diff --git a/src/code_generation/java/variable_generator.py b/src/code_generation/java/variable_generator.py index b2685b6..755a435 100644 --- a/src/code_generation/java/variable_generator.py +++ b/src/code_generation/java/variable_generator.py @@ -150,4 +150,6 @@ def _modifiers(self): self._transient(), self._synthetic(), ] + # leave only non-empty elements + modifiers = [mod for mod in modifiers if mod] return " ".join(modifier for modifier in modifiers if modifier) From c7efbbeec87e5474c1a5596a6cffc29c1461f864 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 23 Jan 2024 13:33:00 +0700 Subject: [PATCH 45/51] Fix indent --- src/code_generation/cpp/function_generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/code_generation/cpp/function_generator.py b/src/code_generation/cpp/function_generator.py index 484f1e7..1fff252 100644 --- a/src/code_generation/cpp/function_generator.py +++ b/src/code_generation/cpp/function_generator.py @@ -65,7 +65,7 @@ def _constexpr(self): Before function name, declaration only Constexpr functions can't be const, virtual or pure virtual """ - return "constexpr " if self.is_constexpr else "" + return "constexpr" if self.is_constexpr else "" def args(self): """ @@ -134,7 +134,7 @@ def render_to_string_declaration(self, cpp): self.render_to_string(cpp) else: cpp( - f"{self._constexpr()}" + f"{self._constexpr()} " f"{self.ret_type} " f"{self.name}" f"({self.args()});" @@ -158,7 +158,7 @@ def render_to_string_implementation(self, cpp): if self.documentation and not self.is_constexpr: cpp(dedent(self.documentation)) with cpp.block( - f"{self._constexpr()}" + f"{self._constexpr()} " f"{self.ret_type} " f"{self.name}" f"({self.args()})" From 884cf8352d5de350332460011b9ae8b0337b0c3b Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 23 Jan 2024 14:55:06 +0700 Subject: [PATCH 46/51] Start refactoring formatters --- src/code_generation/core/__init__.py | 2 +- src/code_generation/core/code_file.py | 17 +++---- .../core/{code_style.py => code_formatter.py} | 48 ++++++++++++++----- src/code_generation/cpp/file_writer.py | 3 ++ src/code_generation/html/html_file.py | 4 +- src/code_generation/java/file_writer.py | 4 +- 6 files changed, 51 insertions(+), 27 deletions(-) rename src/code_generation/core/{code_style.py => code_formatter.py} (71%) diff --git a/src/code_generation/core/__init__.py b/src/code_generation/core/__init__.py index 5999c70..029ec6b 100644 --- a/src/code_generation/core/__init__.py +++ b/src/code_generation/core/__init__.py @@ -1,2 +1,2 @@ from . import code_file -from . import code_style +from . import code_formatter diff --git a/src/code_generation/core/code_file.py b/src/code_generation/core/code_file.py index 0ad1ccc..ce80e58 100644 --- a/src/code_generation/core/code_file.py +++ b/src/code_generation/core/code_file.py @@ -1,4 +1,4 @@ -from code_generation.core.code_style import ANSICodeStyle +from code_generation.core.code_formatter import CodeFormat __doc__ = """ Simple and straightforward code generator that could be used for generating code @@ -74,10 +74,7 @@ class Derived : public Base cpp.newline(3) """ - # Current formatting style (assigned as a class attribute to generate all files uniformly) - Formatter = ANSICodeStyle - - def __init__(self, filename, writer=None): + def __init__(self, filename, code_style=None, writer=None): """ Creates a new source file @param: filename source file to create (rewrite if exists) @@ -86,10 +83,8 @@ def __init__(self, filename, writer=None): self.current_indent = 0 self.last = None self.filename = filename - if writer: - self.out = writer - else: - self.out = open(filename, "w") + self.code_style = CodeFormat.DEFAULT if code_style is None else code_style + self.out = writer if writer is not None else open(filename, "w") def close(self): """ @@ -128,7 +123,7 @@ def block(self, text, postfix=""): """ Returns a stub for C++ {} close Supports 'with' semantic, i.e. - cpp.block(class_name, ';'): + with cpp.block(class_name, ';'): """ return CodeFile.Formatter(self, text, postfix) @@ -136,7 +131,7 @@ def endline(self, count=1): """ Insert an endline """ - self.write(CodeFile.Formatter.endline * count, endline=False) + self.write(CodeFile.Formatter.DEFAULT_ENDLINE * count, endline=False) def newline(self, n=1): """ diff --git a/src/code_generation/core/code_style.py b/src/code_generation/core/code_formatter.py similarity index 71% rename from src/code_generation/core/code_style.py rename to src/code_generation/core/code_formatter.py index 67bc327..53f3e1f 100644 --- a/src/code_generation/core/code_style.py +++ b/src/code_generation/core/code_formatter.py @@ -1,8 +1,27 @@ __doc__ = """Formatters for different styles of code generation """ +import inspect +from enum import Enum, auto -class ANSICodeStyle: + +class CodeFormat(Enum): + DEFAULT = auto() + ANSI_CPP = auto() + HTML = auto() + CUSTOM = auto() + + +class CodeFormatter: + """ + Base class for code styles + """ + default_endline = "\n" + default_indent = " " * 4 # Default indentation is 4 spaces + default_postfix = "" + + +class ANSICodeFormatter(CodeFormatter): """ Class represents C++ {} close and its formatting style. It supports ANSI C style with braces on the new lines, like that: @@ -12,17 +31,12 @@ class ANSICodeStyle: }; finishing postfix is optional (e.g. necessary for classes, unnecessary for namespaces) """ - - # EOL symbol - endline = "\n" - - # Tab (indentation) symbol - indent = "\t" - - def __init__(self, owner, text, postfix): + def __init__(self, owner, text, indent=None, endline=None, postfix=None): """ @param: owner - CodeFile where text is written to @param: text - text opening C++ close + @param indent: code indentation + @param endline: custom endline sequence @param: postfix - optional terminating symbol (e.g. ; for classes) """ self.owner = owner @@ -31,7 +45,19 @@ def __init__(self, owner, text, postfix): pass self.owner.write("".join(text)) self.owner.last = self - self.postfix = postfix + self.indent = self.set_option(indent) + self.endline = self.set_option(endline) + self.postfix = self.set_option(postfix) + + def set_option(self, option): + """ + Set option to default value if it is None + Helps to make sure that reasonable default value provided for the option + :param option: usually, it is a string class attribute + :return: option if it is not None, otherwise default value + """ + attr_name = f"default_{inspect.currentframe().f_code.co_name}" + return option if option is not None else getattr(self, attr_name) def __enter__(self): """ @@ -52,7 +78,7 @@ def __exit__(self, *_): self.owner.write("}" + self.postfix) -class HTMLStyle: +class HTMLCodeFormatter(CodeFormatter): """ Class representing HTML close and its formatting style. It supports HTML DOM-tree style, like that: diff --git a/src/code_generation/cpp/file_writer.py b/src/code_generation/cpp/file_writer.py index a596c1b..ace46f7 100644 --- a/src/code_generation/cpp/file_writer.py +++ b/src/code_generation/cpp/file_writer.py @@ -1,3 +1,4 @@ +from code_generation.core.code_formatter import CodeFormat from code_generation.core.code_file import CodeFile __doc__ = """ @@ -9,6 +10,8 @@ class CppFile(CodeFile): This class extends CodeFile class with some specific C++ constructions """ + default_formatter = CodeFormat.ANSI_CPP + def __init__(self, filename, writer=None): """ Create C++ source file diff --git a/src/code_generation/html/html_file.py b/src/code_generation/html/html_file.py index dbde33b..d95966b 100644 --- a/src/code_generation/html/html_file.py +++ b/src/code_generation/html/html_file.py @@ -1,8 +1,8 @@ -from code_generation.core.code_style import HTMLStyle +from code_generation.core.code_formatter import HTMLCodeFormatter class HtmlFile: - Formatter = HTMLStyle + Formatter = HTMLCodeFormatter def __init__(self, filename, writer=None): self.current_indent = 0 diff --git a/src/code_generation/java/file_writer.py b/src/code_generation/java/file_writer.py index 4f15136..815794a 100644 --- a/src/code_generation/java/file_writer.py +++ b/src/code_generation/java/file_writer.py @@ -3,11 +3,11 @@ """ from code_generation.core.code_file import CodeFile -from code_generation.core.code_style import ANSICodeStyle +from code_generation.core.code_formatter import ANSICodeFormatter class JavaFile(CodeFile): - Formatter = ANSICodeStyle + Formatter = ANSICodeFormatter def __init__(self, filename, writer=None): CodeFile.__init__(self, filename, writer) From 373b72a6ae4715f654ac351b9fa3925f916e1802 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 23 Jan 2024 15:07:12 +0700 Subject: [PATCH 47/51] Refactoring formatters --- src/code_generation/core/code_file.py | 6 ++-- src/code_generation/core/code_formatter.py | 41 ++++++++++++++-------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/code_generation/core/code_file.py b/src/code_generation/core/code_file.py index ce80e58..e7f48ab 100644 --- a/src/code_generation/core/code_file.py +++ b/src/code_generation/core/code_file.py @@ -74,7 +74,7 @@ class Derived : public Base cpp.newline(3) """ - def __init__(self, filename, code_style=None, writer=None): + def __init__(self, filename, code_formatter=None, writer=None): """ Creates a new source file @param: filename source file to create (rewrite if exists) @@ -83,7 +83,9 @@ def __init__(self, filename, code_style=None, writer=None): self.current_indent = 0 self.last = None self.filename = filename - self.code_style = CodeFormat.DEFAULT if code_style is None else code_style + self.code_formatter = code_formatter if code_formatter is not None else CodeFormat.DEFAULT + if not isinstance(self.code_formatter, CodeFormat): + raise TypeError(f"code_format must be an instance of {CodeFormat.__name__}") self.out = writer if writer is not None else open(filename, "w") def close(self): diff --git a/src/code_generation/core/code_formatter.py b/src/code_generation/core/code_formatter.py index 53f3e1f..0c6b916 100644 --- a/src/code_generation/core/code_formatter.py +++ b/src/code_generation/core/code_formatter.py @@ -31,6 +31,7 @@ class ANSICodeFormatter(CodeFormatter): }; finishing postfix is optional (e.g. necessary for classes, unnecessary for namespaces) """ + def __init__(self, owner, text, indent=None, endline=None, postfix=None): """ @param: owner - CodeFile where text is written to @@ -59,23 +60,35 @@ def set_option(self, option): attr_name = f"default_{inspect.currentframe().f_code.co_name}" return option if option is not None else getattr(self, attr_name) - def __enter__(self): + def write(self, text, indent_level=0, endline=None): """ - Open code block + Write a new line with line ending """ - self.owner.write("{") - self.owner.current_indent += 1 - self.owner.last = None + return ( + f"{self.indent * indent_level}" + f"{text}" + f"{self.endline if endline is None else endline}" + ) - def __exit__(self, *_): - """ - Close code block - """ - if self.owner.last is not None: - with self.owner.last: - pass - self.owner.current_indent -= 1 - self.owner.write("}" + self.postfix) + +def __enter__(self): + """ + Open code block + """ + self.owner.write("{") + self.owner.current_indent += 1 + self.owner.last = None + + +def __exit__(self, *_): + """ + Close code block + """ + if self.owner.last is not None: + with self.owner.last: + pass + self.owner.current_indent -= 1 + self.owner.write("}" + self.postfix) class HTMLCodeFormatter(CodeFormatter): From 1b098bfbb8e6b0ca845ea2dbded6cea51ba828df Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 29 Jan 2024 17:40:42 +0700 Subject: [PATCH 48/51] Refactor CodeFormatter: rename classes --- src/code_generation/core/__init__.py | 2 +- src/code_generation/core/code_formatter.py | 108 +++++++++++------- .../core/{code_file.py => source_file.py} | 34 +++--- src/code_generation/cpp/file_writer.py | 8 +- src/code_generation/cpp/language_element.py | 4 +- src/code_generation/cpp/variable_generator.py | 6 +- src/code_generation/java/file_writer.py | 6 +- src/code_generation/java/language_element.py | 10 +- src/examples/low_level.py | 12 ++ test/create_assets.py | 2 +- 10 files changed, 112 insertions(+), 80 deletions(-) rename src/code_generation/core/{code_file.py => source_file.py} (78%) create mode 100644 src/examples/low_level.py diff --git a/src/code_generation/core/__init__.py b/src/code_generation/core/__init__.py index 029ec6b..31c92ca 100644 --- a/src/code_generation/core/__init__.py +++ b/src/code_generation/core/__init__.py @@ -1,2 +1,2 @@ -from . import code_file +from . import source_file from . import code_formatter diff --git a/src/code_generation/core/code_formatter.py b/src/code_generation/core/code_formatter.py index 0c6b916..998c3f4 100644 --- a/src/code_generation/core/code_formatter.py +++ b/src/code_generation/core/code_formatter.py @@ -1,9 +1,8 @@ +from enum import Enum, auto + __doc__ = """Formatters for different styles of code generation """ -import inspect -from enum import Enum, auto - class CodeFormat(Enum): DEFAULT = auto() @@ -17,9 +16,19 @@ class CodeFormatter: Base class for code styles """ default_endline = "\n" - default_indent = " " * 4 # Default indentation is 4 spaces + default_indent = " " * 4 default_postfix = "" + def __init__(self, indent=None, endline=None, postfix=None): + """ + :param indent: sequence of symbols used for indentation (4 spaces, tab, etc.) + :param endline: symbol used for line ending + :param postfix: optional terminating symbol (e.g. ; for classes) + """ + self.indent = self.default_indent if indent is None else indent + self.endline = self.default_endline if endline is None else endline + self.postfix = self.default_postfix if postfix is None else postfix + class ANSICodeFormatter(CodeFormatter): """ @@ -34,31 +43,19 @@ class ANSICodeFormatter(CodeFormatter): def __init__(self, owner, text, indent=None, endline=None, postfix=None): """ - @param: owner - CodeFile where text is written to + @param: owner - SourceFile where text is written to @param: text - text opening C++ close @param indent: code indentation @param endline: custom endline sequence @param: postfix - optional terminating symbol (e.g. ; for classes) """ + super().__init__(indent, endline, postfix) self.owner = owner if self.owner.last is not None: with self.owner.last: pass self.owner.write("".join(text)) self.owner.last = self - self.indent = self.set_option(indent) - self.endline = self.set_option(endline) - self.postfix = self.set_option(postfix) - - def set_option(self, option): - """ - Set option to default value if it is None - Helps to make sure that reasonable default value provided for the option - :param option: usually, it is a string class attribute - :return: option if it is not None, otherwise default value - """ - attr_name = f"default_{inspect.currentframe().f_code.co_name}" - return option if option is not None else getattr(self, attr_name) def write(self, text, indent_level=0, endline=None): """ @@ -70,25 +67,23 @@ def write(self, text, indent_level=0, endline=None): f"{self.endline if endline is None else endline}" ) + def __enter__(self): + """ + Open code block + """ + self.owner.write("{") + self.owner.current_indent += 1 + self.owner.last = None -def __enter__(self): - """ - Open code block - """ - self.owner.write("{") - self.owner.current_indent += 1 - self.owner.last = None - - -def __exit__(self, *_): - """ - Close code block - """ - if self.owner.last is not None: - with self.owner.last: - pass - self.owner.current_indent -= 1 - self.owner.write("}" + self.postfix) + def __exit__(self, *_): + """ + Close code block + """ + if self.owner.last is not None: + with self.owner.last: + pass + self.owner.current_indent -= 1 + self.owner.write("}" + self.postfix) class HTMLCodeFormatter(CodeFormatter): @@ -101,19 +96,21 @@ class HTMLCodeFormatter(CodeFormatter): """ - # EOL symbol - endline = "\n" - # Tab (indentation) symbol is 2 spaces - indent = " " + html_indent = " " def __init__(self, owner, element, *attrs, **kwattrs): """ - @param: owner - CodeFile where text is written to + HTML code line + Note that some attributes like 'class' are conflicting with Python's 'class' keyword. + For such attributes prefer passing by list of strings `attrs`, e.g. `['class="class1"', 'id="id1"']` + For other attributes prefer passing by dictionary `kwattrs`, e.g. `{id="id1"}` + @param: owner - SourceFile where text is written to @param: element - HTML element name - @param: attrs - optional opening tag content, like attributes ['class="class1"', 'id="id1"'] - @param: kwattrs - optional opening tag attributes, like class="class1", id="id1" + @param: attrs - optional opening tag content, e.g. attributes ['class="class1"', 'id="id1"'] + @param: kwattrs - optional opening tag attributes, e.g. {id="id1"} """ + super().__init__(indent=self.html_indent) self.owner = owner if self.owner.last is not None: with self.owner.last: @@ -149,3 +146,28 @@ def __exit__(self, *_): pass self.owner.current_indent -= 1 self.owner.write(f"") + + +class CodeFormatterFactory: + """ + Factory class for code formatters + """ + + @staticmethod + def create(code_format, owner, *args, **kwargs): + """ + Create a new code formatter + :param code_format: code format to create + :param owner: source file where formatter is created + :param args: formatter arguments + :param kwargs: formatter keyword arguments + :return: new code formatter + """ + if code_format == CodeFormat.ANSI_CPP: + return ANSICodeFormatter(owner, *args, **kwargs) + elif code_format == CodeFormat.HTML: + return HTMLCodeFormatter(owner, *args, **kwargs) + elif code_format == CodeFormat.CUSTOM: + return CodeFormatter(*args, **kwargs) + else: + raise ValueError(f"Unknown code format: {code_format}") diff --git a/src/code_generation/core/code_file.py b/src/code_generation/core/source_file.py similarity index 78% rename from src/code_generation/core/code_file.py rename to src/code_generation/core/source_file.py index e7f48ab..e1b9b11 100644 --- a/src/code_generation/core/code_file.py +++ b/src/code_generation/core/source_file.py @@ -1,4 +1,4 @@ -from code_generation.core.code_formatter import CodeFormat +from code_generation.core.code_formatter import CodeFormat, CodeFormatterFactory __doc__ = """ Simple and straightforward code generator that could be used for generating code @@ -14,7 +14,7 @@ 1. # Python code -cpp = CodeFile('example.cpp') +cpp = SourceFile('example.cpp') cpp('int i = 0;') // Generated C++ code @@ -41,13 +41,13 @@ class A """ -class CodeFile: +class SourceFile: """ The class is a main instrument of code generation It can generate plain strings using functional calls Ex: - code = CodeFile(python_src_file) + code = SourceFile(python_src_file) code('import os, sys') Is supports 'with' semantic for indentation blocks creation @@ -55,15 +55,17 @@ class CodeFile: # Python code with code('for i in range(0, 5):'): code('lst.append(i*i)') + code('print(lst)') # Generated code: for i in range(0, 5): lst.append(i*i) + print(lst) - It can append code to the last line: + It can append code without line ending: Ex. # Python code - cpp = CodeFile('ex.cpp') + cpp = SourceFile('ex.cpp') cpp('class Derived') cpp.append(' : public Base') @@ -83,7 +85,9 @@ def __init__(self, filename, code_formatter=None, writer=None): self.current_indent = 0 self.last = None self.filename = filename - self.code_formatter = code_formatter if code_formatter is not None else CodeFormat.DEFAULT + if not isinstance(code_formatter, CodeFormat) and code_formatter is not None: + raise TypeError(f"code_format must be an instance of {CodeFormat.__name__}") + self.code_formatter = code_formatter if code_formatter is not None else CodeFormatterFactory.create(CodeFormat.DEFAULT) if not isinstance(self.code_formatter, CodeFormat): raise TypeError(f"code_format must be an instance of {CodeFormat.__name__}") self.out = writer if writer is not None else open(filename, "w") @@ -95,17 +99,11 @@ def close(self): self.out.close() self.out = None - def write(self, text, indent=0, endline=True): + def write(self, text, indent, endline=True): """ Write a new line with line ending """ - self.out.write( - "{0}{1}{2}".format( - self.Formatter.indent * (self.current_indent + indent), - text, - self.Formatter.endline if endline else "", - ) - ) + self.out.write(self.code_formatter.write(text, indent, endline)) def append(self, x): """ @@ -127,17 +125,17 @@ def block(self, text, postfix=""): Supports 'with' semantic, i.e. with cpp.block(class_name, ';'): """ - return CodeFile.Formatter(self, text, postfix) + return self.code_formatter(self, text, postfix) def endline(self, count=1): """ Insert an endline """ - self.write(CodeFile.Formatter.DEFAULT_ENDLINE * count, endline=False) + self.write(text=self.code_formatter.endline * count, indent=0, endline=False) def newline(self, n=1): """ Insert one or several empty lines """ for _ in range(n): - self.write("") + self.write(text="", indent=0, endline=True) diff --git a/src/code_generation/cpp/file_writer.py b/src/code_generation/cpp/file_writer.py index ace46f7..5ac0692 100644 --- a/src/code_generation/cpp/file_writer.py +++ b/src/code_generation/cpp/file_writer.py @@ -1,13 +1,13 @@ from code_generation.core.code_formatter import CodeFormat -from code_generation.core.code_file import CodeFile +from code_generation.core.source_file import SourceFile __doc__ = """ """ -class CppFile(CodeFile): +class CppFile(SourceFile): """ - This class extends CodeFile class with some specific C++ constructions + This class extends SourceFile class with some specific C++ constructions """ default_formatter = CodeFormat.ANSI_CPP @@ -16,7 +16,7 @@ def __init__(self, filename, writer=None): """ Create C++ source file """ - CodeFile.__init__(self, filename, writer) + SourceFile.__init__(self, filename, writer) def label(self, text): """ diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index c8fada1..f90e7a8 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -41,7 +41,7 @@ - empty lines: cpp.newline(2) -For more detailed information see CodeFile and CppFile documentation. +For more detailed information see SourceFile and CppFile documentation. """ @@ -135,7 +135,7 @@ def init_class_properties(self, current_class_properties, input_properties_dict, def render_to_string(self, cpp): """ - @param: cpp - handle that supports code generation interface (see code_file.py) + @param: cpp - handle that supports code generation interface (see source_file.py) Typically it is passed to all child elements so that render their content """ raise NotImplementedError("CppLanguageElement is an abstract class") diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index a989e73..b854459 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -27,10 +27,10 @@ const size_t MyClass::m_var = 255; -That module uses and highly depends on code_file.py as it uses +That module uses and highly depends on source_file.py as it uses code generating and formatting primitives implemented there. -The main object referenced from code_file.py is CppFile, +The main object referenced from source_file.py is CppFile, which is passed as a parameter to render_to_string(cpp) Python method It could also be used for composing more complicated C++ code, @@ -51,7 +51,7 @@ - empty lines: cpp.newline(2) -For detailed information see code_file.py documentation. +For detailed information see source_file.py documentation. """ diff --git a/src/code_generation/java/file_writer.py b/src/code_generation/java/file_writer.py index 815794a..e0e404f 100644 --- a/src/code_generation/java/file_writer.py +++ b/src/code_generation/java/file_writer.py @@ -2,12 +2,12 @@ classes, methods, variables, enums. """ -from code_generation.core.code_file import CodeFile +from code_generation.core.source_file import SourceFile from code_generation.core.code_formatter import ANSICodeFormatter -class JavaFile(CodeFile): +class JavaFile(SourceFile): Formatter = ANSICodeFormatter def __init__(self, filename, writer=None): - CodeFile.__init__(self, filename, writer) + SourceFile.__init__(self, filename, writer) diff --git a/src/code_generation/java/language_element.py b/src/code_generation/java/language_element.py index c86b094..8db2ce0 100644 --- a/src/code_generation/java/language_element.py +++ b/src/code_generation/java/language_element.py @@ -1,4 +1,4 @@ -from code_generation.core.code_file import CodeFile +from code_generation.core.source_file import SourceFile __doc__ = """This module encapsulates Java code generation logic for main Java language primitives: classes, methods and functions, variables, enums. Every Java element can render its current state to a string @@ -30,16 +30,16 @@ """ -class JavaFile(CodeFile): +class JavaFile(SourceFile): """ - This class extends CodeFile class with some specific Java constructions + This class extends SourceFile class with some specific Java constructions """ def __init__(self, filename, writer=None): """ Create Java source file """ - CodeFile.__init__(self, filename, writer) + SourceFile.__init__(self, filename, writer) def access(self, text): """ @@ -106,7 +106,7 @@ def init_class_properties( def render_to_string(self, java): """ - @param: java - handle that supports code generation interface (see code_file.py) + @param: java - handle that supports code generation interface (see source_file.py) Typically it is passed to all child elements so that they can render their content """ raise NotImplementedError("JavaLanguageElement is an abstract class") diff --git a/src/examples/low_level.py b/src/examples/low_level.py new file mode 100644 index 0000000..c7db948 --- /dev/null +++ b/src/examples/low_level.py @@ -0,0 +1,12 @@ +from code_generation.core.source_file import SourceFile + +__doc__ = """ +""" + + +code = SourceFile('example.java') +code('import java.util.*;') + +with code.block('public class Application'): + with code.block('public static void main(String[] args)'): + code('System.out.println("Hello world!");') diff --git a/test/create_assets.py b/test/create_assets.py index 10e30ee..09d1282 100644 --- a/test/create_assets.py +++ b/test/create_assets.py @@ -1,7 +1,7 @@ import os import argparse -from code_generation.core.code_file import CppFile +from code_generation.core.source_file import CppFile from code_generation.cpp.cpp_variable import CppVariable from code_generation.cpp.cpp_enum import CppEnum from code_generation.cpp.cpp_array import CppArray From 50f2c61bcd8e5fa16d5eed03c3f648341d0ffd3b Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 30 Jan 2024 23:43:38 +0700 Subject: [PATCH 49/51] Refactor CodeFormatter: rename classes --- src/code_generation/core/code_formatter.py | 26 +++++++++++-------- src/code_generation/core/source_file.py | 25 ++++++++++-------- src/code_generation/cpp/language_element.py | 4 +-- .../cpp/{file_writer.py => source_file.py} | 2 +- src/code_generation/cpp/variable_generator.py | 2 +- src/code_generation/java/__init__.py | 2 +- .../java/{file_writer.py => source_file.py} | 3 ++- src/examples/cpp_array.py | 4 +-- src/examples/cpp_class.py | 4 +-- src/examples/cpp_enum.py | 4 +-- src/examples/cpp_function.py | 4 +-- src/examples/java_array.py | 4 +-- src/examples/java_class.py | 4 +-- src/examples/java_enum.py | 4 +-- src/examples/java_method.py | 4 +-- src/examples/java_variable.py | 4 +-- src/examples/low_level.py | 3 ++- test/cpp/test_cpp_array_writer.py | 10 +++---- test/cpp/test_cpp_class_writer.py | 12 ++++----- test/cpp/test_cpp_enum_writer.py | 8 +++--- test/cpp/test_cpp_file.py | 16 ++++++------ test/cpp/test_cpp_function_writer.py | 12 ++++----- test/cpp/test_cpp_variable_writer.py | 16 ++++++------ test/java/test_java_array_writer.py | 10 +++---- test/java/test_java_class_writer.py | 10 +++---- test/java/test_java_enum_writer.py | 4 +-- test/java/test_java_file.py | 10 +++---- test/java/test_java_function_writer.py | 4 +-- test/java/test_java_variable_writer.py | 10 +++---- 29 files changed, 117 insertions(+), 108 deletions(-) rename src/code_generation/cpp/{file_writer.py => source_file.py} (94%) rename src/code_generation/java/{file_writer.py => source_file.py} (91%) diff --git a/src/code_generation/core/code_formatter.py b/src/code_generation/core/code_formatter.py index 998c3f4..a63f0f3 100644 --- a/src/code_generation/core/code_formatter.py +++ b/src/code_generation/core/code_formatter.py @@ -13,7 +13,7 @@ class CodeFormat(Enum): class CodeFormatter: """ - Base class for code styles + Base class for code close of different styles """ default_endline = "\n" default_indent = " " * 4 @@ -54,10 +54,10 @@ def __init__(self, owner, text, indent=None, endline=None, postfix=None): if self.owner.last is not None: with self.owner.last: pass - self.owner.write("".join(text)) + self.owner.line("".join(text)) self.owner.last = self - def write(self, text, indent_level=0, endline=None): + def line(self, text, indent_level=0, endline=None): """ Write a new line with line ending """ @@ -71,7 +71,7 @@ def __enter__(self): """ Open code block """ - self.owner.write("{") + self.owner.line("{") self.owner.current_indent += 1 self.owner.last = None @@ -83,7 +83,7 @@ def __exit__(self, *_): with self.owner.last: pass self.owner.current_indent -= 1 - self.owner.write("}" + self.postfix) + self.owner.line("}" + self.postfix) class HTMLCodeFormatter(CodeFormatter): @@ -133,7 +133,7 @@ def __enter__(self): """ Open code block """ - self.owner.write(f"<{self.element}{self.attributes}>") + self.owner.line(f"<{self.element}{self.attributes}>") self.owner.current_indent += 1 self.owner.last = None @@ -145,7 +145,7 @@ def __exit__(self, *_): with self.owner.last: pass self.owner.current_indent -= 1 - self.owner.write(f"") + self.owner.line(f"") class CodeFormatterFactory: @@ -154,20 +154,24 @@ class CodeFormatterFactory: """ @staticmethod - def create(code_format, owner, *args, **kwargs): + def create(code_format, owner, text, *args, **kwargs): """ Create a new code formatter - :param code_format: code format to create :param owner: source file where formatter is created + :param text: code to format + :param code_format: code formatter type :param args: formatter arguments :param kwargs: formatter keyword arguments :return: new code formatter """ if code_format == CodeFormat.ANSI_CPP: - return ANSICodeFormatter(owner, *args, **kwargs) + return ANSICodeFormatter(owner=owner, text=text, *args, **kwargs) elif code_format == CodeFormat.HTML: - return HTMLCodeFormatter(owner, *args, **kwargs) + return HTMLCodeFormatter(owner=owner, text=text, *args, **kwargs) elif code_format == CodeFormat.CUSTOM: return CodeFormatter(*args, **kwargs) + elif code_format == CodeFormat.DEFAULT: + # TODO: leave default formatter for respective source file + return CodeFormatter(*args, **kwargs) else: raise ValueError(f"Unknown code format: {code_format}") diff --git a/src/code_generation/core/source_file.py b/src/code_generation/core/source_file.py index e1b9b11..3353d1a 100644 --- a/src/code_generation/core/source_file.py +++ b/src/code_generation/core/source_file.py @@ -22,7 +22,7 @@ 2. # Python code -cpp = CppFile('example.cpp') +cpp = CppSourceFile('example.cpp') with cpp.block('class A', ';'): cpp.label('public') cpp('int m_classMember1;') @@ -69,27 +69,28 @@ class SourceFile: cpp('class Derived') cpp.append(' : public Base') - // Generated code + // Generated C++ code class Derived : public Base And finally, it can insert a number of empty lines cpp.newline(3) """ - def __init__(self, filename, code_formatter=None, writer=None): + def __init__(self, filename, formatter=None, writer=None): """ Creates a new source file @param: filename source file to create (rewrite if exists) + @param: formatter code formatter to define rules of code indentation and line ending @param: writer optional writer to write output to """ self.current_indent = 0 self.last = None self.filename = filename - if not isinstance(code_formatter, CodeFormat) and code_formatter is not None: - raise TypeError(f"code_format must be an instance of {CodeFormat.__name__}") - self.code_formatter = code_formatter if code_formatter is not None else CodeFormatterFactory.create(CodeFormat.DEFAULT) - if not isinstance(self.code_formatter, CodeFormat): + if not isinstance(formatter, CodeFormat) and formatter is not None: raise TypeError(f"code_format must be an instance of {CodeFormat.__name__}") + formatter = formatter if formatter is not None else CodeFormat.DEFAULT + # Problem... + self.code_formatter = CodeFormatterFactory.create(formatter, owner=self) self.out = writer if writer is not None else open(filename, "w") def close(self): @@ -99,11 +100,11 @@ def close(self): self.out.close() self.out = None - def write(self, text, indent, endline=True): + def write(self, text, indent=0, endline=True): """ Write a new line with line ending """ - self.out.write(self.code_formatter.write(text, indent, endline)) + self.out.write(self.code_formatter.line(text, indent, endline)) def append(self, x): """ @@ -119,12 +120,14 @@ def __call__(self, text, indent=0, endline=True): """ self.write(text, indent, endline) - def block(self, text, postfix=""): + def block(self, text, postfix=None): """ Returns a stub for C++ {} close Supports 'with' semantic, i.e. with cpp.block(class_name, ';'): """ + if postfix is None: + postfix = self.code_formatter.postfix return self.code_formatter(self, text, postfix) def endline(self, count=1): @@ -138,4 +141,4 @@ def newline(self, n=1): Insert one or several empty lines """ for _ in range(n): - self.write(text="", indent=0, endline=True) + self.write(text="", indent=0) diff --git a/src/code_generation/cpp/language_element.py b/src/code_generation/cpp/language_element.py index f90e7a8..3309271 100644 --- a/src/code_generation/cpp/language_element.py +++ b/src/code_generation/cpp/language_element.py @@ -23,7 +23,7 @@ // Generated C++ definition const size_t MyClass::m_var = 255; -You can use CppFile for composing more complicated C++ code, +You can use CppSourceFile for composing more complicated C++ code, which is not supported by CppLanguageElement It supports: @@ -41,7 +41,7 @@ - empty lines: cpp.newline(2) -For more detailed information see SourceFile and CppFile documentation. +For more detailed information see SourceFile and CppSourceFile documentation. """ diff --git a/src/code_generation/cpp/file_writer.py b/src/code_generation/cpp/source_file.py similarity index 94% rename from src/code_generation/cpp/file_writer.py rename to src/code_generation/cpp/source_file.py index 5ac0692..be2bc51 100644 --- a/src/code_generation/cpp/file_writer.py +++ b/src/code_generation/cpp/source_file.py @@ -5,7 +5,7 @@ """ -class CppFile(SourceFile): +class CppSourceFile(SourceFile): """ This class extends SourceFile class with some specific C++ constructions """ diff --git a/src/code_generation/cpp/variable_generator.py b/src/code_generation/cpp/variable_generator.py index b854459..0fe88cd 100644 --- a/src/code_generation/cpp/variable_generator.py +++ b/src/code_generation/cpp/variable_generator.py @@ -30,7 +30,7 @@ That module uses and highly depends on source_file.py as it uses code generating and formatting primitives implemented there. -The main object referenced from source_file.py is CppFile, +The main object referenced from source_file.py is CppSourceFile, which is passed as a parameter to render_to_string(cpp) Python method It could also be used for composing more complicated C++ code, diff --git a/src/code_generation/java/__init__.py b/src/code_generation/java/__init__.py index 122925e..8e46f76 100644 --- a/src/code_generation/java/__init__.py +++ b/src/code_generation/java/__init__.py @@ -1,2 +1,2 @@ -from . import file_writer +from . import source_file from . import language_element diff --git a/src/code_generation/java/file_writer.py b/src/code_generation/java/source_file.py similarity index 91% rename from src/code_generation/java/file_writer.py rename to src/code_generation/java/source_file.py index e0e404f..f6b5112 100644 --- a/src/code_generation/java/file_writer.py +++ b/src/code_generation/java/source_file.py @@ -6,7 +6,8 @@ from code_generation.core.code_formatter import ANSICodeFormatter -class JavaFile(SourceFile): +class JavaSourceFile(SourceFile): + Formatter = ANSICodeFormatter def __init__(self, filename, writer=None): diff --git a/src/examples/cpp_array.py b/src/examples/cpp_array.py index b25765a..4a184b9 100644 --- a/src/examples/cpp_array.py +++ b/src/examples/cpp_array.py @@ -1,4 +1,4 @@ -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.array_generator import CppArray __doc__ = """Example of generating C++ array @@ -8,7 +8,7 @@ """ -cpp = CppFile('array.cpp') +cpp = CppSourceFile('array.cpp') arr = CppArray(name="my_array", type="int", array_size=5) arr.add_array_items(["1", "2", "0"]) arr.render_to_string(cpp) diff --git a/src/examples/cpp_class.py b/src/examples/cpp_class.py index b8fc146..85cb8e4 100644 --- a/src/examples/cpp_class.py +++ b/src/examples/cpp_class.py @@ -1,4 +1,4 @@ -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.class_generator import CppClass from code_generation.cpp.variable_generator import CppVariable @@ -20,7 +20,7 @@ """ -cpp = CppFile('class.cpp') +cpp = CppSourceFile('class.cpp') cpp_class = CppClass(name="MyClass", is_struct=True) cpp_class.add_variable( diff --git a/src/examples/cpp_enum.py b/src/examples/cpp_enum.py index f4d5c11..93eba2f 100644 --- a/src/examples/cpp_enum.py +++ b/src/examples/cpp_enum.py @@ -1,4 +1,4 @@ -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.enum_generator import CppEnum __doc__ = """Example of generating C++ enum and enum class @@ -22,7 +22,7 @@ """ -cpp = CppFile('array.cpp') +cpp = CppSourceFile('array.cpp') enum = CppEnum(name="MyEnum", prefix="e") enum.add_items(["Item1", "Item2", "Item3"]) enum.render_to_string(cpp) diff --git a/src/examples/cpp_function.py b/src/examples/cpp_function.py index 3c4b72d..c90276f 100644 --- a/src/examples/cpp_function.py +++ b/src/examples/cpp_function.py @@ -1,4 +1,4 @@ -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.function_generator import CppFunction __doc__ = """Example of generating C++ function @@ -15,7 +15,7 @@ def handle_to_factorial(cpp): cpp('return n < 1 ? 1 : (n * factorial(n - 1));') -cpp = CppFile('function.cpp') +cpp = CppSourceFile('function.cpp') func = CppFunction(name="factorial", ret_type="int", implementation=handle_to_factorial) func.add_argument('int n') func.render_to_string(cpp) diff --git a/src/examples/java_array.py b/src/examples/java_array.py index 8ae8dcc..e29504b 100644 --- a/src/examples/java_array.py +++ b/src/examples/java_array.py @@ -1,4 +1,4 @@ -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.class_generator import JavaClass from code_generation.java.array_generator import JavaArray @@ -13,7 +13,7 @@ """ -java = JavaFile('java_array.java') +java = JavaSourceFile('java_array.java') java_class = JavaClass(name="MyClass") diff --git a/src/examples/java_class.py b/src/examples/java_class.py index 5dc9461..8373681 100644 --- a/src/examples/java_class.py +++ b/src/examples/java_class.py @@ -1,4 +1,4 @@ -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.class_generator import JavaClass from code_generation.java.function_generator import JavaFunction from code_generation.java.variable_generator import JavaVariable @@ -23,7 +23,7 @@ """ -java = JavaFile('java_class.java') +java = JavaSourceFile('java_class.java') java_class = JavaClass(name="MyClass") diff --git a/src/examples/java_enum.py b/src/examples/java_enum.py index 777cf28..6dbaefb 100644 --- a/src/examples/java_enum.py +++ b/src/examples/java_enum.py @@ -1,4 +1,4 @@ -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.class_generator import JavaClass from code_generation.java.enum_generator import JavaEnum @@ -8,7 +8,7 @@ """ -java = JavaFile('java_enum.java') +java = JavaSourceFile('java_enum.java') java_class = JavaClass(name="MyClass") diff --git a/src/examples/java_method.py b/src/examples/java_method.py index 7c9f06e..3240d3c 100644 --- a/src/examples/java_method.py +++ b/src/examples/java_method.py @@ -1,4 +1,4 @@ -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.class_generator import JavaClass from code_generation.java.function_generator import JavaFunction from code_generation.java.variable_generator import JavaVariable @@ -19,7 +19,7 @@ """ -java = JavaFile('java_method.java') +java = JavaSourceFile('java_method.java') java_class = JavaClass(name="MyClass") diff --git a/src/examples/java_variable.py b/src/examples/java_variable.py index 431414a..2f52c6d 100644 --- a/src/examples/java_variable.py +++ b/src/examples/java_variable.py @@ -1,4 +1,4 @@ -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.class_generator import JavaClass from code_generation.java.function_generator import JavaFunction from code_generation.java.variable_generator import JavaVariable @@ -10,7 +10,7 @@ """ -java = JavaFile('java_variable.java') +java = JavaSourceFile('java_variable.java') java_class = JavaClass(name="MyClass") diff --git a/src/examples/low_level.py b/src/examples/low_level.py index c7db948..20afdc3 100644 --- a/src/examples/low_level.py +++ b/src/examples/low_level.py @@ -1,10 +1,11 @@ from code_generation.core.source_file import SourceFile +from code_generation.core.code_formatter import CodeFormatterFactory, CodeFormat __doc__ = """ """ -code = SourceFile('example.java') +code = SourceFile('example.cpp', formatter=CodeFormat.ANSI_CPP) code('import java.util.*;') with code.block('public class Application'): diff --git a/test/cpp/test_cpp_array_writer.py b/test/cpp/test_cpp_array_writer.py index 12a4df2..ee2aa1b 100644 --- a/test/cpp/test_cpp_array_writer.py +++ b/test/cpp/test_cpp_array_writer.py @@ -2,7 +2,7 @@ import io from textwrap import dedent -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.array_generator import CppArray from test.comparing_tools import normalize_lines @@ -17,7 +17,7 @@ class TestCppArrayStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) arr = CppArray(name="my_array", type="int", array_size=5) arr.add_array_items(["1", "2", "0"]) arr.render_to_string(cpp) @@ -26,7 +26,7 @@ def test_simple_case(self): def test_with_newline_align(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) arr = CppArray(name="my_array", type="int", array_size=5, newline_align=True) arr.add_array_items(["1", "2", "0"]) arr.render_to_string(cpp) @@ -43,7 +43,7 @@ def test_with_newline_align(self): def test_declaration(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) arr = CppArray(name="my_class_member_array", type="int", array_size=None, is_class_member=True) arr.render_to_string_declaration(cpp) expected_output = "int my_class_member_array[];" @@ -51,7 +51,7 @@ def test_declaration(self): def test_implementation(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) arr = CppArray(name="m_my_static_array", type="int", array_size=None, is_class_member=True, is_static=True) arr.add_array_items(["1", "2", "0"]) diff --git a/test/cpp/test_cpp_class_writer.py b/test/cpp/test_cpp_class_writer.py index ab4e5a3..ed0e5a9 100644 --- a/test/cpp/test_cpp_class_writer.py +++ b/test/cpp/test_cpp_class_writer.py @@ -2,7 +2,7 @@ import io from textwrap import dedent -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.enum_generator import CppEnum from code_generation.cpp.array_generator import CppArray from code_generation.cpp.variable_generator import CppVariable @@ -19,7 +19,7 @@ class TestCppClassStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - cpp_file = CppFile(None, writer=writer) + cpp_file = CppSourceFile(None, writer=writer) # Create a CppClass instance cpp_class = CppClass(name="MyClass", is_struct=True) @@ -77,7 +77,7 @@ def body(cpp): def test_with_inheritance(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) # Create a parent class parent_class = CppClass(name="ParentClass") @@ -144,7 +144,7 @@ class ChildClass : public ParentClass def test_with_nested_classes(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) # Create a CppClass instance cpp_class = CppClass(name="MyClass") @@ -179,7 +179,7 @@ class NestedClass def test_with_enum(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) # Create a CppClass instance cpp_class = CppClass(name="MyClass") @@ -220,7 +220,7 @@ class MyClass def test_with_array(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) # Create a CppClass instance cpp_class = CppClass(name="MyClass") diff --git a/test/cpp/test_cpp_enum_writer.py b/test/cpp/test_cpp_enum_writer.py index 95f3f68..5e0bc3c 100644 --- a/test/cpp/test_cpp_enum_writer.py +++ b/test/cpp/test_cpp_enum_writer.py @@ -2,7 +2,7 @@ import io from textwrap import dedent -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.enum_generator import CppEnum from test.comparing_tools import normalize_code, debug_dump, is_debug @@ -17,7 +17,7 @@ class TestCppEnumStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) enum = CppEnum(name="Items") enum.add_items(["Chair", "Table", "Shelve"]) enum.render_to_string(cpp) @@ -39,7 +39,7 @@ def test_simple_case(self): def test_with_prefix(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) enum = CppEnum(name="Items", prefix="Prefix") enum.add_items(["A", "B", "C"]) enum.render_to_string(cpp) @@ -61,7 +61,7 @@ def test_with_prefix(self): def test_class(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) enum = CppEnum(name="Items", enum_class=True) enum.add_items(["A", "B", "C"]) enum.render_to_string(cpp) diff --git a/test/cpp/test_cpp_file.py b/test/cpp/test_cpp_file.py index 5c69206..1e9f051 100644 --- a/test/cpp/test_cpp_file.py +++ b/test/cpp/test_cpp_file.py @@ -2,7 +2,7 @@ import unittest import filecmp -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.variable_generator import CppVariable from code_generation.cpp.enum_generator import CppEnum from code_generation.cpp.array_generator import CppArray @@ -23,7 +23,7 @@ def test_cpp_array(self): """ Test C++ variables generation """ - cpp = CppFile('array.cpp') + cpp = CppSourceFile('array.cpp') arrays = [] a1 = CppArray(name='array1', type='int', is_const=True, array_size=5) a1.add_array_items(['1', '2', '3']) @@ -49,8 +49,8 @@ def test_cpp_class(self): """ Test C++ classes generation """ - my_class_cpp = CppFile('class.cpp') - my_class_h = CppFile('class.h') + my_class_cpp = CppSourceFile('class.cpp') + my_class_h = CppSourceFile('class.h') my_class = CppClass(name='MyClass') enum_elements = CppEnum(name='Items', prefix='wd') @@ -121,7 +121,7 @@ def test_cpp_enum(self): """ Test C++ enums generation """ - cpp = CppFile('enum.cpp') + cpp = CppSourceFile('enum.cpp') enum_elements = CppEnum(name='Items') for item in ['Chair', 'Table', 'Shelve']: enum_elements.add_item(item) @@ -141,8 +141,8 @@ def test_cpp_function(self): """ Test C++ functions generation """ - cpp = CppFile('func.cpp') - hpp = CppFile('func.h') + cpp = CppSourceFile('func.cpp') + hpp = CppSourceFile('func.h') def function_body(_, cpp1): cpp1('return 42;') @@ -170,7 +170,7 @@ def test_cpp_variable(self): """ Test C++ variables generation """ - cpp = CppFile('var.cpp') + cpp = CppSourceFile('var.cpp') variables = [CppVariable(name="var1", type="char*", is_class_member=False, diff --git a/test/cpp/test_cpp_function_writer.py b/test/cpp/test_cpp_function_writer.py index 6679639..1f918a6 100644 --- a/test/cpp/test_cpp_function_writer.py +++ b/test/cpp/test_cpp_function_writer.py @@ -2,7 +2,7 @@ import io from textwrap import dedent -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.function_generator import CppFunction __doc__ = """ @@ -21,7 +21,7 @@ class TestCppFunctionStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) func = CppFunction(name="factorial", ret_type="int", implementation=handle_to_factorial) func.add_argument('int n') func.render_to_string(cpp) @@ -33,13 +33,13 @@ def test_simple_case(self): def test_is_constexpr_no_implementation_raises(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) func = CppFunction(name="factorial", ret_type="int", is_constexpr=True) self.assertRaises(ValueError, func.render_to_string, cpp) def test_is_constexpr_render_to_string(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) func = CppFunction(name="factorial", ret_type="int", implementation=handle_to_factorial, is_constexpr=True) func.add_argument('int n') @@ -52,7 +52,7 @@ def test_is_constexpr_render_to_string(self): def test_is_constexpr_render_to_string_declaration(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) func = CppFunction(name="factorial", ret_type="int", implementation=handle_to_factorial, is_constexpr=True) func.add_argument('int n') @@ -65,7 +65,7 @@ def test_is_constexpr_render_to_string_declaration(self): def test_docstring_example(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) factorial_function = CppFunction(name='factorial', ret_type='int', is_constexpr=True, implementation=handle_to_factorial, documentation='/// Calculates and returns the factorial of p @n.') diff --git a/test/cpp/test_cpp_variable_writer.py b/test/cpp/test_cpp_variable_writer.py index d1f608e..e7eee8e 100644 --- a/test/cpp/test_cpp_variable_writer.py +++ b/test/cpp/test_cpp_variable_writer.py @@ -1,7 +1,7 @@ import unittest import io -from code_generation.cpp.file_writer import CppFile +from code_generation.cpp.source_file import CppSourceFile from code_generation.cpp.variable_generator import CppVariable __doc__ = """Unit tests for C++ code generator @@ -15,7 +15,7 @@ class TestCppVariableStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) variables = CppVariable(name="var1", type="char*", is_class_member=False, @@ -28,20 +28,20 @@ def test_simple_case(self): def test_is_constexpr_const_raises(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) var = CppVariable(name="COUNT", type="int", is_class_member=True, is_const=True, is_constexpr=True, value='0') self.assertRaises(ValueError, var.render_to_string, cpp) def test_is_constexpr_no_implementation_raises(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) var = CppVariable(name="COUNT", type="int", is_class_member=True, is_constexpr=True) self.assertRaises(ValueError, var.render_to_string, cpp) def test_is_constexpr_render_to_string(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) variables = CppVariable(name="COUNT", type="int", is_class_member=False, @@ -52,7 +52,7 @@ def test_is_constexpr_render_to_string(self): def test_is_constexpr_render_to_string_declaration(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) variables = CppVariable(name="COUNT", type="int", is_class_member=True, @@ -63,13 +63,13 @@ def test_is_constexpr_render_to_string_declaration(self): def test_is_extern_static_raises(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) var = CppVariable(name="var1", type="char*", is_static=True, is_extern=True) self.assertRaises(ValueError, var.render_to_string, cpp) def test_is_extern_render_to_string(self): writer = io.StringIO() - cpp = CppFile(None, writer=writer) + cpp = CppSourceFile(None, writer=writer) v = CppVariable(name="var1", type="char*", is_extern=True) v.render_to_string(cpp) self.assertIn('extern char* var1;', writer.getvalue()) diff --git a/test/java/test_java_array_writer.py b/test/java/test_java_array_writer.py index 69a5e0a..0d4c71b 100644 --- a/test/java/test_java_array_writer.py +++ b/test/java/test_java_array_writer.py @@ -1,7 +1,7 @@ import unittest import io -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.array_generator import JavaArray from test.comparing_tools import normalize_code, debug_dump, is_debug @@ -13,7 +13,7 @@ class TestJavaArrayStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) arr = JavaArray(name="myArray", type="int", values=["1", "2", "0"]) arr.render_to_string(java) expected_output = "int[] myArray = {1, 2, 0};" @@ -27,7 +27,7 @@ def test_simple_case(self): def test_with_empty_values(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) arr = JavaArray(name="emptyArray", type='int', values=[]) arr.render_to_string(java) actual_output = writer.getvalue().strip() @@ -41,7 +41,7 @@ def test_with_empty_values(self): def test_add_item(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) arr = JavaArray(name="myArray", type='String', quoted=True) arr.add_item("item1") arr.add_item("item2") @@ -57,7 +57,7 @@ def test_add_item(self): def test_add_items(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) arr = JavaArray(name="myArray", type='String', quoted=True) arr.add_items(["item1", "item2", "item3"]) arr.render_to_string(java) diff --git a/test/java/test_java_class_writer.py b/test/java/test_java_class_writer.py index 03ffe25..ded2600 100644 --- a/test/java/test_java_class_writer.py +++ b/test/java/test_java_class_writer.py @@ -2,7 +2,7 @@ import io from textwrap import dedent -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.class_generator import JavaClass from code_generation.java.variable_generator import JavaVariable from code_generation.java.function_generator import JavaFunction @@ -16,7 +16,7 @@ class TestJavaClassStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) my_class = JavaClass(name="MyClass") var1 = JavaVariable(name="myVariable", type="int", value=10) @@ -45,7 +45,7 @@ def body(j): def test_with_parent_class(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) my_class = JavaClass(name="MyClass", parent_class="ParentClass") my_class.render_to_string(java) expected_line = "public class MyClass extends ParentClass" @@ -63,7 +63,7 @@ def test_with_parent_class(self): def test_with_documentation(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) my_class = JavaClass(name="MyClass", documentation="Example Javadoc") my_class.render_to_string(java) self.assertTrue(writer.getvalue().strip().startswith('/**')) @@ -84,7 +84,7 @@ def test_with_documentation(self): def test_multiline_documentation(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) my_class = JavaClass(name="MyClass", documentation="Example multiline Javadoc\nSecond line") my_class.render_to_string(java) self.assertTrue(writer.getvalue().strip().startswith('/**')) diff --git a/test/java/test_java_enum_writer.py b/test/java/test_java_enum_writer.py index 10e12e0..61802be 100644 --- a/test/java/test_java_enum_writer.py +++ b/test/java/test_java_enum_writer.py @@ -1,7 +1,7 @@ import unittest import io -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.enum_generator import JavaEnum from test.comparing_tools import normalize_code, debug_dump, is_debug @@ -13,7 +13,7 @@ class TestJavaEnumStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) enum = JavaEnum(name="Color", values=["RED", "GREEN", "BLUE"]) enum.render_to_string(java) actual_output = writer.getvalue().strip() diff --git a/test/java/test_java_file.py b/test/java/test_java_file.py index a3554e9..90d9c24 100644 --- a/test/java/test_java_file.py +++ b/test/java/test_java_file.py @@ -2,7 +2,7 @@ import filecmp import os -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.class_generator import JavaClass from code_generation.java.variable_generator import JavaVariable from code_generation.java.function_generator import JavaFunction @@ -17,7 +17,7 @@ def test_java_class(self): """ Test Java class generation """ - java_file = JavaFile('MyClass.java') + java_file = JavaSourceFile('MyClass.java') my_class = JavaClass(name='MyClass') my_class.add_variable(JavaVariable(name='myVariable', type='int', @@ -37,7 +37,7 @@ def test_java_interface(self): """ Test Java interface generation """ - java_file = JavaFile('MyInterface.java') + java_file = JavaSourceFile('MyInterface.java') my_interface = JavaClass(name='MyInterface') my_interface.add_variable(JavaVariable(name='myVariable', type='int')) my_interface.add_method(JavaFunction(name='getVar', return_type='int')) @@ -50,7 +50,7 @@ def test_java_enum(self): """ Test Java enum generation """ - java_file = JavaFile('MyEnum.java') + java_file = JavaSourceFile('MyEnum.java') my_enum = JavaClass(name='MyEnum') my_enum.add_variable(JavaVariable(name='ITEM1', type='int', value='1')) my_enum.add_variable(JavaVariable(name='ITEM2', type='int', value='2')) @@ -64,7 +64,7 @@ def test_missing_class_name(self): """ Test case for missing class name """ - java_file = JavaFile('Missing.java') + java_file = JavaSourceFile('Missing.java') my_class = JavaClass() my_class.render_to_string(java_file) self.assertRaises(RuntimeError, my_class.render_to_string, java_file) diff --git a/test/java/test_java_function_writer.py b/test/java/test_java_function_writer.py index 3ac7c18..e5e0bea 100644 --- a/test/java/test_java_function_writer.py +++ b/test/java/test_java_function_writer.py @@ -2,7 +2,7 @@ import io from textwrap import dedent -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.function_generator import JavaFunction from test.comparing_tools import normalize_code, debug_dump, is_debug @@ -18,7 +18,7 @@ class TestJavaFunctionStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) method = JavaFunction(name="calculateSum", return_type="int", diff --git a/test/java/test_java_variable_writer.py b/test/java/test_java_variable_writer.py index 04e67c7..8122203 100644 --- a/test/java/test_java_variable_writer.py +++ b/test/java/test_java_variable_writer.py @@ -1,7 +1,7 @@ import unittest import io -from code_generation.java.file_writer import JavaFile +from code_generation.java.source_file import JavaSourceFile from code_generation.java.variable_generator import JavaVariable @@ -12,7 +12,7 @@ class TestJavaVariableStringIo(unittest.TestCase): def test_simple_case(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) variable = JavaVariable(name="var1", type="String", is_class_member=False, @@ -24,20 +24,20 @@ def test_simple_case(self): def test_is_static_final_raises(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) variable = JavaVariable(name="var1", type="String", is_static=True, is_final=True) self.assertRaises(ValueError, variable.render_to_string, java) def test_is_static_render_to_string(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) variable = JavaVariable(name="var1", type="String", is_static=True) variable.render_to_string(java) self.assertEqual("static String var1;", writer.getvalue().strip()) def test_render_to_string_declaration(self): writer = io.StringIO() - java = JavaFile(None, writer=writer) + java = JavaSourceFile(None, writer=writer) variable = JavaVariable(name="var1", type="String") variable.render_to_string(java) self.assertEqual("String var1;", writer.getvalue().strip()) From d633a3b022331c1a13cb2e28fe29b1f87405b2c3 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Tue, 30 Jan 2024 23:49:56 +0700 Subject: [PATCH 50/51] Acknolegments --- README.md | 10 +++++++--- src/code_generation/core/source_file.py | 8 -------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d1cbbba..ee8bf75 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,11 @@ python create_assets.py --assets test_assets After executing that command, the fixed data under `tests/test_assets` will be updated and will need to be committed to git. -### Special thanks +### Acknowledgements + +Thanks to Eric Reynolds, the idea based on his +[article](https://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python) +published on Code Project. + +Used under the [Code Project Open License](https://www.codeproject.com/info/cpol10.aspx) -Thanks to Eric Reynolds, for the initial idea of code generation in Python. -http://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python diff --git a/src/code_generation/core/source_file.py b/src/code_generation/core/source_file.py index 3353d1a..410356e 100644 --- a/src/code_generation/core/source_file.py +++ b/src/code_generation/core/source_file.py @@ -4,11 +4,6 @@ Simple and straightforward code generator that could be used for generating code on any programming language and to be a 'building block' for creating more complicated code generators. - -Thanks to Eric Reynolds, the code mainly based on his article published on -https://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python -Used under the Code Project Open License -https://www.codeproject.com/info/cpol10.aspx Examples of usage: @@ -35,9 +30,6 @@ class A int m_classMember1; double m_classMember2; }; - -Class `ANSICodeStyle` is responsible for code formatting. -Re-implement it if you wish to apply any other formatting style. """ From 68bf0242ffb1a16a48fbec4db34261355b364d8d Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Sat, 3 Feb 2024 23:25:45 +0700 Subject: [PATCH 51/51] Add questions section --- src/code_generation/core/code_formatter.py | 20 ++++++++++++-------- src/code_generation/core/source_file.py | 4 +--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/code_generation/core/code_formatter.py b/src/code_generation/core/code_formatter.py index a63f0f3..d00fa8a 100644 --- a/src/code_generation/core/code_formatter.py +++ b/src/code_generation/core/code_formatter.py @@ -11,9 +11,9 @@ class CodeFormat(Enum): CUSTOM = auto() -class CodeFormatter: +class CodeLayout: """ - Base class for code close of different styles + Class defining code layout rules, such as indentation, line ending, etc. """ default_endline = "\n" default_indent = " " * 4 @@ -23,13 +23,20 @@ def __init__(self, indent=None, endline=None, postfix=None): """ :param indent: sequence of symbols used for indentation (4 spaces, tab, etc.) :param endline: symbol used for line ending - :param postfix: optional terminating symbol (e.g. ; for classes) + :param postfix: optional line-terminating symbol """ self.indent = self.default_indent if indent is None else indent self.endline = self.default_endline if endline is None else endline self.postfix = self.default_postfix if postfix is None else postfix +class CodeFormatter: + """ + Base class for code close of different styles + """ + pass + + class ANSICodeFormatter(CodeFormatter): """ Class represents C++ {} close and its formatting style. @@ -41,15 +48,12 @@ class ANSICodeFormatter(CodeFormatter): finishing postfix is optional (e.g. necessary for classes, unnecessary for namespaces) """ - def __init__(self, owner, text, indent=None, endline=None, postfix=None): + def __init__(self, owner, text, code_layout=None): """ @param: owner - SourceFile where text is written to @param: text - text opening C++ close - @param indent: code indentation - @param endline: custom endline sequence - @param: postfix - optional terminating symbol (e.g. ; for classes) """ - super().__init__(indent, endline, postfix) + super().__init__() self.owner = owner if self.owner.last is not None: with self.owner.last: diff --git a/src/code_generation/core/source_file.py b/src/code_generation/core/source_file.py index 410356e..33ec9d8 100644 --- a/src/code_generation/core/source_file.py +++ b/src/code_generation/core/source_file.py @@ -80,9 +80,7 @@ def __init__(self, filename, formatter=None, writer=None): self.filename = filename if not isinstance(formatter, CodeFormat) and formatter is not None: raise TypeError(f"code_format must be an instance of {CodeFormat.__name__}") - formatter = formatter if formatter is not None else CodeFormat.DEFAULT - # Problem... - self.code_formatter = CodeFormatterFactory.create(formatter, owner=self) + self.formatter = formatter if formatter is not None else CodeFormat.DEFAULT self.out = writer if writer is not None else open(filename, "w") def close(self):