Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (18)
......@@ -10,7 +10,7 @@ linter:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
script:
- flake8 src/pystencilssfg
- python3 -m flake8 src/pystencilssfg
tags:
- docker
......@@ -21,11 +21,23 @@ typechecker:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
script:
- pip install mypy
- python3 -m pip install mypy
- mypy src/pystencilssfg
tags:
- docker
unit-tests:
stage: test
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
script:
- env
- python3 -m pip list
- python3 -m pip install --upgrade setuptools>=69
- python3 -m pip install -e .
- python3 -m pytest .
tags:
- docker
build-documentation:
stage: test
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
......
File moved
......@@ -2,10 +2,10 @@
# It is not intended for manual editing.
[metadata]
groups = ["default", "code_quality", "docs", "interactive"]
groups = ["default", "docs", "interactive", "testing"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.1"
content_hash = "sha256:2c854f8da4b29c3080cd89c774409f95c47d3532c953cf10ecaa67d0b77ff9cf"
content_hash = "sha256:b08920a7c4d7bb2b1d192c73869276ee2de05ca774ef39536403a7dd38ceca62"
[[package]]
name = "appdirs"
......@@ -127,7 +127,7 @@ name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
groups = ["docs", "interactive"]
groups = ["docs", "interactive", "testing"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
......@@ -149,7 +149,7 @@ name = "exceptiongroup"
version = "1.2.0"
requires_python = ">=3.7"
summary = "Backport of PEP 654 (exception groups)"
groups = ["interactive"]
groups = ["interactive", "testing"]
marker = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
......@@ -172,7 +172,7 @@ name = "flake8"
version = "7.0.0"
requires_python = ">=3.8.1"
summary = "the modular source code checker: pep8 pyflakes and co"
groups = ["code_quality"]
groups = ["testing"]
dependencies = [
"mccabe<0.8.0,>=0.7.0",
"pycodestyle<2.12.0,>=2.11.0",
......@@ -221,9 +221,20 @@ files = [
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
]
[[package]]
name = "iniconfig"
version = "2.0.0"
requires_python = ">=3.7"
summary = "brain-dead simple config-ini parsing"
groups = ["testing"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "ipython"
version = "8.19.0"
version = "8.20.0"
requires_python = ">=3.10"
summary = "IPython: Productive Interactive Computing"
groups = ["interactive"]
......@@ -240,8 +251,8 @@ dependencies = [
"traitlets>=5",
]
files = [
{file = "ipython-8.19.0-py3-none-any.whl", hash = "sha256:2f55d59370f59d0d2b2212109fe0e6035cfea436b1c0e6150ad2244746272ec5"},
{file = "ipython-8.19.0.tar.gz", hash = "sha256:ac4da4ecf0042fb4e0ce57c60430c2db3c719fa8bdf92f8631d6bd8a5785d1f0"},
{file = "ipython-8.20.0-py3-none-any.whl", hash = "sha256:bc9716aad6f29f36c449e30821c9dd0c1c1a7b59ddcc26931685b87b4c569619"},
{file = "ipython-8.20.0.tar.gz", hash = "sha256:2f21bd3fc1d51550c89ee3944ae04bbc7bc79e129ea0937da6e6c68bfdbf117a"},
]
[[package]]
......@@ -353,7 +364,7 @@ name = "mccabe"
version = "0.7.0"
requires_python = ">=3.6"
summary = "McCabe checker, plugin for flake8"
groups = ["code_quality"]
groups = ["testing"]
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
......@@ -469,7 +480,7 @@ files = [
[[package]]
name = "mkdocstrings-python"
version = "1.7.5"
version = "1.8.0"
requires_python = ">=3.8"
summary = "A Python handler for mkdocstrings."
groups = ["docs"]
......@@ -478,8 +489,8 @@ dependencies = [
"mkdocstrings>=0.20",
]
files = [
{file = "mkdocstrings_python-1.7.5-py3-none-any.whl", hash = "sha256:5f6246026353f0c0785135db70c3fe9a5d9318990fc7ceb11d62097b8ffdd704"},
{file = "mkdocstrings_python-1.7.5.tar.gz", hash = "sha256:c7d143728257dbf1aa550446555a554b760dcd40a763f077189d298502b800be"},
{file = "mkdocstrings_python-1.8.0-py3-none-any.whl", hash = "sha256:4209970cc90bec194568682a535848a8d8489516c6ed4adbe58bbc67b699ca9d"},
{file = "mkdocstrings_python-1.8.0.tar.gz", hash = "sha256:1488bddf50ee42c07d9a488dddc197f8e8999c2899687043ec5dd1643d057192"},
]
[[package]]
......@@ -513,7 +524,7 @@ name = "mypy"
version = "1.8.0"
requires_python = ">=3.8"
summary = "Optional static typing for Python"
groups = ["code_quality"]
groups = ["testing"]
dependencies = [
"mypy-extensions>=1.0.0",
"tomli>=1.1.0; python_version < \"3.11\"",
......@@ -544,7 +555,7 @@ name = "mypy-extensions"
version = "1.0.0"
requires_python = ">=3.5"
summary = "Type system extensions for programs checked with the mypy type checker."
groups = ["code_quality"]
groups = ["testing"]
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
......@@ -592,7 +603,7 @@ name = "packaging"
version = "23.2"
requires_python = ">=3.7"
summary = "Core utilities for Python packages"
groups = ["docs"]
groups = ["docs", "testing"]
files = [
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
......@@ -654,6 +665,17 @@ files = [
{file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
]
[[package]]
name = "pluggy"
version = "1.3.0"
requires_python = ">=3.8"
summary = "plugin and hook calling mechanisms for python"
groups = ["testing"]
files = [
{file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
{file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
]
[[package]]
name = "prompt-toolkit"
version = "3.0.43"
......@@ -694,7 +716,7 @@ name = "pycodestyle"
version = "2.11.1"
requires_python = ">=3.8"
summary = "Python style guide checker"
groups = ["code_quality"]
groups = ["testing"]
files = [
{file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
{file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
......@@ -705,7 +727,7 @@ name = "pyflakes"
version = "3.2.0"
requires_python = ">=3.8"
summary = "passive checker of Python programs"
groups = ["code_quality"]
groups = ["testing"]
files = [
{file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
......@@ -753,6 +775,25 @@ files = [
{file = "pystencils-1.3.3.tar.gz", hash = "sha256:d066d8dd6eee355671dd6e93454f9a1bf143ab2528f76076ce15f0c15b5d29c7"},
]
[[package]]
name = "pytest"
version = "7.4.4"
requires_python = ">=3.7"
summary = "pytest: simple powerful testing with Python"
groups = ["testing"]
dependencies = [
"colorama; sys_platform == \"win32\"",
"exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
"iniconfig",
"packaging",
"pluggy<2.0,>=0.12",
"tomli>=1.0.0; python_version < \"3.11\"",
]
files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
]
[[package]]
name = "python-dateutil"
version = "2.8.2"
......@@ -931,7 +972,7 @@ name = "tomli"
version = "2.0.1"
requires_python = ">=3.7"
summary = "A lil' TOML parser"
groups = ["code_quality"]
groups = ["testing"]
marker = "python_version < \"3.11\""
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
......@@ -954,7 +995,7 @@ name = "typing-extensions"
version = "4.9.0"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
groups = ["code_quality"]
groups = ["testing"]
files = [
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
......
......@@ -27,9 +27,10 @@ build-backend = "setuptools.build_meta"
interactive = [
"ipython>=8.17.2",
]
code_quality = [
testing = [
"flake8>=6.1.0",
"mypy>=1.7.0",
"pytest>=7.4.3",
]
docs = [
"mkdocs>=1.5.3",
......@@ -44,3 +45,9 @@ versionfile_source = "src/pystencilssfg/_version.py"
versionfile_build = "pystencilssfg/_version.py"
tag_prefix = ""
parentdir_prefix = "pystencilssfg-"
[tool.pytest.ini_options]
testpaths = [
"tests",
"integration"
]
......@@ -42,8 +42,12 @@ class SfgCodeStyle:
tree will automatically be used.
"""
run_clang_format: bool = True
"""If set to True, use clang-format to beautify the generated code."""
force_clang_format: bool = False
"""If set to True, abort code generation if `clang-format` binary cannot be found."""
"""If `run_clang_format` and `force_clang_format` are both set to True,
abort code generation if `clang-format` binary cannot be found."""
clang_format_binary: str = "clang-format"
"""Path to the clang-format executable"""
......
from .header_impl_pair import HeaderImplPairEmitter
from .string_emitter import StringEmitter
__all__ = [
"HeaderImplPairEmitter"
"HeaderImplPairEmitter",
"StringEmitter"
]
......@@ -9,6 +9,9 @@ def invoke_clang_format(code: str, codestyle: SfgCodeStyle) -> str:
"""Call the `clang-format` command-line tool to format the given code string
according to the given style arguments.
If `codestyle.run_clang_format == False`, `clang-format` is not executed and the
code string is returned as-is.
Args:
code: Code string to format
codestyle: [SfgCodeStyle][pystencilssfg.configuration.SfgCodeStyle] object
......@@ -24,6 +27,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 not codestyle.run_clang_format:
return code
args = [codestyle.clang_format_binary, f"--style={codestyle.code_style}"]
if not shutil.which(codestyle.clang_format_binary):
......
......@@ -32,8 +32,8 @@ class HeaderImplPairEmitter:
specified by the output specification."""
ctx = prepare_context(ctx)
header_printer = SfgHeaderPrinter(ctx, self._ospec)
impl_printer = SfgImplPrinter(ctx, self._ospec)
header_printer = SfgHeaderPrinter(ctx)
impl_printer = SfgImplPrinter(ctx, self._ospec.get_header_filename())
header = header_printer.get_code()
impl = impl_printer.get_code()
......
......@@ -8,7 +8,6 @@ from pystencils import Backend
from pystencils.backends import generate_c
from ..context import SfgContext
from ..configuration import SfgOutputSpec
from ..visitors import visitor
from ..exceptions import SfgException
......@@ -23,7 +22,7 @@ from ..source_components import (
SfgMemberVariable,
SfgMethod,
SfgVisibility,
SfgVisibilityBlock
SfgVisibilityBlock,
)
......@@ -71,8 +70,7 @@ class SfgGeneralPrinter:
class SfgHeaderPrinter(SfgGeneralPrinter):
def __init__(self, ctx: SfgContext, output_spec: SfgOutputSpec):
self._output_spec = output_spec
def __init__(self, ctx: SfgContext):
self._ctx = ctx
def get_code(self) -> str:
......@@ -109,7 +107,7 @@ class SfgHeaderPrinter(SfgGeneralPrinter):
def function(self, func: SfgFunction):
params = sorted(list(func.parameters), key=lambda p: p.name)
param_list = ", ".join(f"{param.dtype} {param.name}" for param in params)
return f"{func.return_type} {func.name} ( {param_list} );"
return f"{func.return_type} {func.name}( {param_list} );"
@visit.case(SfgClass)
def sfg_class(self, cls: SfgClass):
......@@ -143,7 +141,7 @@ class SfgHeaderPrinter(SfgGeneralPrinter):
@visit.case(SfgConstructor)
def sfg_constructor(self, constr: SfgConstructor):
code = f"{constr.owning_class.class_name} ("
code = f"{constr.owning_class.class_name}("
code += ", ".join(f"{param.dtype} {param.name}" for param in constr.parameters)
code += ")\n"
if constr.initializers:
......@@ -160,13 +158,13 @@ class SfgHeaderPrinter(SfgGeneralPrinter):
@visit.case(SfgMethod)
def sfg_method(self, method: SfgMethod):
code = f"{method.return_type} {method.name} ({self.param_list(method)})"
code += "const" if method.const else ""
code = f"{method.return_type} {method.name}({self.param_list(method)})"
code += " const" if method.const else ""
if method.inline:
code += (
" {\n"
+ self._ctx.codestyle.indent(method.tree.get_code(self._ctx))
+ "}\n"
+ "\n}\n"
)
else:
code += ";"
......@@ -182,8 +180,20 @@ def delimiter(content):
class SfgImplPrinter(SfgGeneralPrinter):
def __init__(self, ctx: SfgContext, output_spec: SfgOutputSpec):
self._output_spec = output_spec
"""Printer for the implementation file.
This printer prints the implementation file of a header/impl pair desribed by an
[SfgContext][pystencilssfg.SfgContext].
"""
def __init__(self, ctx: SfgContext, assoc_header: str):
"""
Args:
ctx: The context describing the files
assoc_header: Name of the associated header file (including extension);
`#include "<assoc_header>"` will be the first non-comment line of the printed code.
"""
self._assoc_header = assoc_header
self._ctx = ctx
def get_code(self) -> str:
......@@ -197,7 +207,7 @@ class SfgImplPrinter(SfgGeneralPrinter):
def frame(self, ctx: SfgContext) -> str:
code = super().prelude(ctx)
code += f'\n#include "{self._output_spec.get_header_filename()}"\n\n'
code += f'\n#include "{self._assoc_header}"\n\n'
includes = filter(lambda incl: incl.private, ctx.includes())
code += "\n".join(self.visit(incl) for incl in includes)
......@@ -242,7 +252,7 @@ class SfgImplPrinter(SfgGeneralPrinter):
def function(self, func: SfgFunction) -> str:
code = f"{func.return_type} {func.name} ({self.param_list(func)})"
code += (
"{\n" + self._ctx.codestyle.indent(func.tree.get_code(self._ctx)) + "}\n"
"{\n" + self._ctx.codestyle.indent(func.tree.get_code(self._ctx)) + "\n}\n"
)
return code
......@@ -257,6 +267,6 @@ class SfgImplPrinter(SfgGeneralPrinter):
code = f"{method.return_type} {method.owning_class.class_name}::{method.name}"
code += f"({self.param_list(method)}) {const_qual}"
code += (
" {\n" + self._ctx.codestyle.indent(method.tree.get_code(self._ctx)) + "}\n"
" {\n" + self._ctx.codestyle.indent(method.tree.get_code(self._ctx)) + "\n}\n"
)
return code
from ..context import SfgContext
from .prepare import prepare_context
from .printers import SfgHeaderPrinter, SfgImplPrinter
from .clang_format import invoke_clang_format
class StringEmitter:
def __init__(self, ctx: SfgContext):
self._ctx = prepare_context(ctx)
def get_header_code(self):
printer = SfgHeaderPrinter(self._ctx)
code = printer.get_code()
code = invoke_clang_format(code, self._ctx.codestyle)
return code
def get_impl_code(self, assoc_header: str):
printer = SfgImplPrinter(self._ctx, assoc_header)
code = printer.get_code()
code = invoke_clang_format(code, self._ctx.codestyle)
return code
# type: ignore
import re
from pystencilssfg import SfgContext, SfgComposer
from pystencilssfg.configuration import SfgCodeStyle
from pystencilssfg.types import SrcType
from pystencilssfg.source_concepts import SrcObject
from pystencilssfg.source_components import (
SfgClass,
SfgMemberVariable,
SfgConstructor,
SfgMethod,
SfgVisibility,
)
from pystencilssfg.emission import StringEmitter
from pystencils import fields, kernel
from util import normalize_whitespace as normalws
codestyle = SfgCodeStyle(run_clang_format=False)
def test_classes_explicit():
ctx = SfgContext(codestyle=codestyle)
sfg = SfgComposer(ctx)
f, g = fields("f, g(1): double[2D]")
@kernel
def assignments():
f[0, 0] @= 3 * g[0, 0]
khandle = sfg.kernels.create(assignments)
cls = SfgClass("MyClass")
cls.default.append_member(SfgMethod("callKernel", sfg.call(khandle), const=True))
cls.default.append_member(
SfgMethod(
"inlineConst",
sfg.seq("return -1.0;"),
return_type=SrcType("double"),
inline=True,
const=True,
)
)
cls.default.append_member(
SfgMethod(
"awesomeMethod",
sfg.seq("return 2.0f;"),
return_type=SrcType("float"),
inline=False,
const=True,
)
)
cls.default.append_member(SfgMemberVariable("stuff", "std::vector< int >"))
cls.default.append_member(
SfgConstructor([SrcObject("stuff", "std::vector< int > &")], ["stuff_(stuff)"])
)
ctx.add_class(cls)
emitter = StringEmitter(ctx)
header_code = normalws(emitter.get_header_code())
structure_re = re.compile(
r"class MyClass\s*\{\s*void callKernel\s*\(.*\)\s*const;\s*"
r"double inlineConst\s*\(\)\s*const\s*\{\s*return -1\.0;\s*\}\s*"
r"float awesomeMethod\s*\(\)\s*const;\s*"
r"std::vector<\s*int\s*> stuff;\s*"
r"MyClass\s*\(std::vector<\s*int\s*>\s*&\s*stuff\)\s*:\s*stuff_\(stuff\)\s*\{\s*\}\s*"
r"\};"
)
assert structure_re.search(header_code) is not None
"""Utility functions for pystencils-sfg unit tests."""
def normalize_whitespace(s: str):
return " ".join(s.split())
def normalized_streq(a: str, b: str):
return normalize_whitespace(a) == normalize_whitespace(b)