diff --git a/src/pystencilssfg/__init__.py b/src/pystencilssfg/__init__.py index 247800ca5c095a808b9e0f03ccab66e40a134035..1ee4eaee9df05fc447c990bde3a889066675d850 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 6a2472067ddb746a4d0700599c1541491d86e4a9..09384892fb5f0975daadbbfd1fa77498c3f3bd80 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 2eb3efe7855af24cfe21e2815d7a40d6eea60686..a76251a04cde0d875b0ba41908a0fadc5fc90553 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 69ad83f8e2ab8ddf9436e1ebc3505b819d6a5cf2..bd3591889cbed6ab2a4bc023787944857585a601 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 5c5084ce340d17cd0b11f3b58091df99db4d46c9..eea152a062474a6761fdbbdecfaaeb88bb63c4d3 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 1b3a805cdb3efbac12e81005b7747859d0e4e2cb..93371619e302932d97441ff069190ba956b0f04d 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 22db7e2cc54d0670bd4a378dae88c95559d04e18..aa8396c4ecae7471c333a14587435ac3f35eec3a 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 9d27ebeddd488ac23d3b87d126f3d17b312e936a..93e93cabdc7bc80331e2f5c7efc3a988f87e841a 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 0000000000000000000000000000000000000000..45ffcf60d4c878203fb8f9335e97081c1d4461d8 --- /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 a729d1f125e69c5ef3d178f2ed8be1d65cd423a8..2c7f2273cb2ccdb71df502cae780eb12187ecc93 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 0000000000000000000000000000000000000000..754e1a30e33d75bc9b6e4b0fd71117cbb1f455e9 --- /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 466dbf34ca6f622442090d7e7001e437a8260156..7fd86720ab715d6062050a247440492bf99fcbc4 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