From 2edd363e0829ad170ba9f3b679a3794873528bcf Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Thu, 17 Oct 2024 15:47:13 +0200 Subject: [PATCH] More examples for composer. Fix generator script tests ground-truth comparison. --- src/pystencilssfg/__init__.py | 3 +- src/pystencilssfg/composer/basic_composer.py | 63 +++++++++++++++++-- src/pystencilssfg/configuration.py | 5 +- src/pystencilssfg/context.py | 8 +-- src/pystencilssfg/emission/clang_format.py | 3 + src/pystencilssfg/emission/printers.py | 11 ---- src/pystencilssfg/generator.py | 5 ++ .../{SimpleClasses.cpp => SimpleClasses.h} | 4 ++ tests/generator_scripts/expected/Structural.h | 18 ++++++ .../scripts/SimpleClasses.py | 2 +- tests/generator_scripts/scripts/Structural.py | 16 +++++ .../test_generator_scripts.py | 22 ++++--- 12 files changed, 128 insertions(+), 32 deletions(-) rename tests/generator_scripts/expected/{SimpleClasses.cpp => SimpleClasses.h} (76%) create mode 100644 tests/generator_scripts/expected/Structural.h create mode 100644 tests/generator_scripts/scripts/Structural.py diff --git a/src/pystencilssfg/__init__.py b/src/pystencilssfg/__init__.py index 247800c..1ee4eae 100644 --- a/src/pystencilssfg/__init__.py +++ b/src/pystencilssfg/__init__.py @@ -1,4 +1,4 @@ -from .configuration import SfgConfiguration, SfgOutputMode +from .configuration import SfgConfiguration, SfgOutputMode, SfgCodeStyle from .generator import SourceFileGenerator from .composer import SfgComposer from .context import SfgContext @@ -9,6 +9,7 @@ __all__ = [ "SfgComposer", "SfgConfiguration", "SfgOutputMode", + "SfgCodeStyle", "SfgContext", "AugExpr", ] diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py index 6a24720..0938489 100644 --- a/src/pystencilssfg/composer/basic_composer.py +++ b/src/pystencilssfg/composer/basic_composer.py @@ -96,22 +96,63 @@ class SfgBasicComposer(SfgIComposer): The string should not contain C/C++ comment delimiters, since these will be added automatically during code generation. + + :Example: + >>> sfg.prelude("This file was generated using pystencils-sfg; do not modify it directly!") + + will appear in the generated files as + + .. code-block:: C++ + + /* + * This file was generated using pystencils-sfg; do not modify it directly! + */ + """ self._ctx.append_to_prelude(content) def define(self, *definitions: str): - """Add custom definitions to the generated header file.""" + """Add custom definitions to the generated header file. + + Each string passed to this method will be printed out directly into the generated header file. + + :Example: + + >>> sfg.define("#define PI 3.14 // more than enough for engineers") + + will appear as + + .. code-block:: C++ + + #define PI 3.14 // more than enough for engineers + + """ for d in definitions: self._ctx.add_definition(d) def define_once(self, *definitions: str): - """Same as `define`, but only adds definitions only if the same code string was not already added.""" + """Same as `define`, but only adds definitions if the same code string was not already added.""" for definition in definitions: if all(d != definition for d in self._ctx.definitions()): self._ctx.add_definition(definition) def namespace(self, namespace: str): - """Set the inner code namespace. Throws an exception if a namespace was already set.""" + """Set the inner code namespace. Throws an exception if a namespace was already set. + + :Example: + + After adding the following to your generator script: + + >>> sfg.namespace("codegen_is_awesome") + + All generated code will be placed within that namespace: + + .. code-block:: C++ + + namespace codegen_is_awesome { + /* all generated code */ + } + """ self._ctx.set_namespace(namespace) def generate(self, generator: CustomGenerator): @@ -142,9 +183,21 @@ class SfgBasicComposer(SfgIComposer): """Include a header file. Args: - header_file: Path to the header file. Enclose in `<>` for a system header. - private: If `True`, in header-implementation code generation, the header file is + header_file: Path to the header file. Enclose in ``<>`` for a system header. + private: If ``True``, in header-implementation code generation, the header file is only included in the implementation file. + + :Example: + + >>> sfg.include("<vector>") + >>> sfg.include("custom.h") + + will be printed as + + .. code-block:: C++ + + #include <vector> + #include "custom.h" """ self._ctx.add_include(SfgHeaderInclude.parse(header_file, private)) diff --git a/src/pystencilssfg/configuration.py b/src/pystencilssfg/configuration.py index 2eb3efe..a76251a 100644 --- a/src/pystencilssfg/configuration.py +++ b/src/pystencilssfg/configuration.py @@ -40,7 +40,10 @@ class SfgCodeStyle: """ force_clang_format: bool = False - """If set to True, abort code generation if `clang-format` binary cannot be found.""" + """If set to True, abort code generation if ``clang-format`` binary cannot be found.""" + + skip_clang_format: bool = False + """If set to True, skip formatting using ``clang-format``.""" clang_format_binary: str = "clang-format" """Path to the clang-format executable""" diff --git a/src/pystencilssfg/context.py b/src/pystencilssfg/context.py index 69ad83f..bd35918 100644 --- a/src/pystencilssfg/context.py +++ b/src/pystencilssfg/context.py @@ -69,7 +69,7 @@ class SfgContext: # Source Components self._prelude: str = "" - self._includes: set[SfgHeaderInclude] = set() + self._includes: list[SfgHeaderInclude] = [] self._definitions: list[str] = [] self._kernel_namespaces = { self._default_kernel_namespace.name: self._default_kernel_namespace @@ -79,10 +79,6 @@ class SfgContext: self._declarations_ordered: list[str | SfgFunction | SfgClass] = list() - # Standard stuff - self.add_include(SfgHeaderInclude("cstdint", system_header=True)) - self.add_definition("#define RESTRICT __restrict__") - @property def argv(self) -> Sequence[str]: """If this context was created by a `pystencilssfg.SourceFileGenerator`, provides the command @@ -159,7 +155,7 @@ class SfgContext: yield from self._includes def add_include(self, include: SfgHeaderInclude): - self._includes.add(include) + self._includes.append(include) def definitions(self) -> Generator[str, None, None]: """Definitions are arbitrary custom lines of code.""" diff --git a/src/pystencilssfg/emission/clang_format.py b/src/pystencilssfg/emission/clang_format.py index 5c5084c..eea152a 100644 --- a/src/pystencilssfg/emission/clang_format.py +++ b/src/pystencilssfg/emission/clang_format.py @@ -24,6 +24,9 @@ def invoke_clang_format(code: str, codestyle: SfgCodeStyle) -> str: be executed (binary not found, or error during exection), the function will throw an exception. """ + if codestyle.skip_clang_format: + return code + args = [codestyle.clang_format_binary, f"--style={codestyle.code_style}"] if not shutil.which(codestyle.clang_format_binary): diff --git a/src/pystencilssfg/emission/printers.py b/src/pystencilssfg/emission/printers.py index 1b3a805..9337161 100644 --- a/src/pystencilssfg/emission/printers.py +++ b/src/pystencilssfg/emission/printers.py @@ -178,14 +178,6 @@ class SfgHeaderPrinter(SfgGeneralPrinter): return code -def delimiter(content): - return f"""\ -/************************************************************************************* - * {content} -*************************************************************************************/ -""" - - class SfgImplPrinter(SfgGeneralPrinter): def __init__( self, ctx: SfgContext, output_spec: SfgOutputSpec, inline_impl: bool = False @@ -219,11 +211,8 @@ class SfgImplPrinter(SfgGeneralPrinter): parts = interleave( chain( - [delimiter("Kernels")], ctx.kernel_namespaces(), - [delimiter("Functions")], ctx.functions(), - [delimiter("Class Methods")], ctx.classes(), ), repeat(SfgEmptyLines(1)), diff --git a/src/pystencilssfg/generator.py b/src/pystencilssfg/generator.py index 22db7e2..aa8396c 100644 --- a/src/pystencilssfg/generator.py +++ b/src/pystencilssfg/generator.py @@ -58,6 +58,11 @@ class SourceFileGenerator: project_info=config.project_info, ) + from pystencilssfg.ir import SfgHeaderInclude + + self._context.add_include(SfgHeaderInclude("cstdint", system_header=True)) + self._context.add_definition("#define RESTRICT __restrict__") + self._emitter: AbstractEmitter match config.output_mode: case SfgOutputMode.HEADER_ONLY: diff --git a/tests/generator_scripts/expected/SimpleClasses.cpp b/tests/generator_scripts/expected/SimpleClasses.h similarity index 76% rename from tests/generator_scripts/expected/SimpleClasses.cpp rename to tests/generator_scripts/expected/SimpleClasses.h index 9d27ebe..93e93ca 100644 --- a/tests/generator_scripts/expected/SimpleClasses.cpp +++ b/tests/generator_scripts/expected/SimpleClasses.h @@ -1,5 +1,9 @@ +#pragma once + #include <cstdint> +#define RESTRICT __restrict__ + class Point { public: const int64_t & getX() const { diff --git a/tests/generator_scripts/expected/Structural.h b/tests/generator_scripts/expected/Structural.h new file mode 100644 index 0000000..45ffcf6 --- /dev/null +++ b/tests/generator_scripts/expected/Structural.h @@ -0,0 +1,18 @@ +/* + * Expect the unexpected, and you shall never be surprised. + */ + +#pragma once + +#include <cstdint> + +#include <iostream> +#include "config.h" + +namespace awesome { + +#define RESTRICT __restrict__ +#define PI 3.1415 +using namespace std; + +} diff --git a/tests/generator_scripts/scripts/SimpleClasses.py b/tests/generator_scripts/scripts/SimpleClasses.py index a729d1f..2c7f227 100644 --- a/tests/generator_scripts/scripts/SimpleClasses.py +++ b/tests/generator_scripts/scripts/SimpleClasses.py @@ -6,7 +6,7 @@ with SourceFileGenerator() as sfg: sfg.klass("Point")( sfg.public( - sfg.method("getX", returns="const int64_t &", const=True)( + sfg.method("getX", returns="const int64_t &", const=True, inline=True)( "return this->x;" ) ), diff --git a/tests/generator_scripts/scripts/Structural.py b/tests/generator_scripts/scripts/Structural.py new file mode 100644 index 0000000..754e1a3 --- /dev/null +++ b/tests/generator_scripts/scripts/Structural.py @@ -0,0 +1,16 @@ +from pystencilssfg import SourceFileGenerator, SfgConfiguration, SfgCodeStyle + +# Do not use clang-format, since it reorders headers +cfg = SfgConfiguration( + codestyle=SfgCodeStyle(skip_clang_format=True) +) + +with SourceFileGenerator(cfg) as sfg: + sfg.prelude("Expect the unexpected, and you shall never be surprised.") + sfg.include("<iostream>") + sfg.include("config.h") + + sfg.namespace("awesome") + + sfg.define("#define PI 3.1415") + sfg.define("using namespace std;") diff --git a/tests/generator_scripts/test_generator_scripts.py b/tests/generator_scripts/test_generator_scripts.py index 466dbf3..7fd8672 100644 --- a/tests/generator_scripts/test_generator_scripts.py +++ b/tests/generator_scripts/test_generator_scripts.py @@ -14,21 +14,25 @@ EXPECTED_DIR = path.join(THIS_DIR, "expected") @dataclass class ScriptInfo: + @staticmethod + def make(name, *args, **kwargs): + return pytest.param(ScriptInfo(name, *args, **kwargs), id=f"{name}.py") + script_name: str """Name of the generator script, without .py-extension. - + Generator scripts must be located in the ``scripts`` folder. """ expected_outputs: tuple[str, ...] """List of file extensions expected to be emitted by the generator script. - + Output files will all be placed in the ``out`` folder. """ compilable_output: str | None = None """File extension of the output file that can be compiled. - + If this is set, and the expected file exists, the ``compile_cmd`` will be executed to check for error-free compilation of the output. """ @@ -36,11 +40,15 @@ class ScriptInfo: compile_cmd: str = f"g++ --std=c++17 -I {THIS_DIR}/deps/mdspan/include" """Command to be invoked to compile the generated source file.""" + def __repr__(self) -> str: + return self.script_name + SCRIPTS = [ - ScriptInfo("SimpleJacobi", ("h", "cpp"), compilable_output="cpp"), - ScriptInfo("SimpleClasses", ("h", "cpp")), - ScriptInfo("Variables", ("h", "cpp"), compilable_output="cpp"), + ScriptInfo.make("Structural", ("h", "cpp")), + ScriptInfo.make("SimpleJacobi", ("h", "cpp"), compilable_output="cpp"), + ScriptInfo.make("SimpleClasses", ("h", "cpp")), + ScriptInfo.make("Variables", ("h", "cpp"), compilable_output="cpp"), ] @@ -92,7 +100,7 @@ def test_generator_script(script_info: ScriptInfo): # Strip whitespace expected = "".join(expected.split()) - actual = "".join(expected.split()) + actual = "".join(actual.split()) assert expected == actual -- GitLab