From 2b4b01d72837e1c67da83e3a22231be5ffb46861 Mon Sep 17 00:00:00 2001 From: Frederik Hennig <frederik.hennig@fau.de> Date: Wed, 15 Nov 2023 14:30:49 +0900 Subject: [PATCH] Added configuration module --- pystencilssfg/configuration.py | 152 +++++++++++++++++++++++++++++++++ pystencilssfg/context.py | 44 +++------- 2 files changed, 165 insertions(+), 31 deletions(-) create mode 100644 pystencilssfg/configuration.py diff --git a/pystencilssfg/configuration.py b/pystencilssfg/configuration.py new file mode 100644 index 0000000..18eabe8 --- /dev/null +++ b/pystencilssfg/configuration.py @@ -0,0 +1,152 @@ +from typing import List, Sequence +from enum import Enum, auto +from dataclasses import dataclass, replace +from argparse import ArgumentParser + +from jinja2.filters import do_indent + +from .exceptions import SfgException + +HEADER_FILE_EXTENSIONS = {'h', 'hpp'} +SOURCE_FILE_EXTENSIONS = {'c', 'cpp'} + + +@dataclass +class SfgCodeStyle: + indent_width: int = 2 + + def indent(self, s: str): + return do_indent(s, self.indent_width, first=True) + + +@dataclass +class SfgConfiguration: + header_extension: str = None + """File extension for generated header files.""" + + source_extension: str = None + """File extension for generated source files.""" + + header_only: bool = None + """If set to `True`, generate only a header file without accompaning source file.""" + + base_namespace: str = None + """The outermost namespace in the generated file. May be a valid C++ nested namespace qualifier + (like `a::b::c`) or `None` if no outer namespace should be generated.""" + + codestyle: SfgCodeStyle = None + """Code style that should be used by the code generator.""" + + output_directory: str = None + """Directory to which the generated files should be written.""" + + def __post_init__(self): + if self.header_only: + raise SfgException( + "Header-only code generation is not implemented yet.") + + if self.header_extension[0] == '.': + self.header_extension = self.header_extension[1:] + + if self.source_extension[0] == '.': + self.source_extension = self.source_extension[1:] + + +DEFAULT_CONFIG = SfgConfiguration( + header_extension='h', + source_extension='cpp', + header_only=False, + base_namespace=None, + codestyle=SfgCodeStyle(), + output_directory="" +) + + +def get_file_extensions(self, extensions: Sequence[str]): + h_ext = None + src_ext = None + + extensions = ((ext[1:] if ext[0] == '.' else ext) for ext in extensions) + + for ext in extensions: + if ext in HEADER_FILE_EXTENSIONS: + if h_ext is not None: + raise ValueError("Multiple header file extensions found.") + h_ext = ext + elif ext in SOURCE_FILE_EXTENSIONS: + if src_ext is not None: + raise ValueError("Multiple source file extensions found.") + src_ext = ext + else: + raise ValueError(f"Don't know how to interpret extension '.{ext}'") + + return h_ext, src_ext + + +def run_configurator(configurator_script: str): + raise NotImplementedError() + + +def config_from_commandline(self, argv: List[str]): + parser = ArgumentParser("pystencilssfg", + description="pystencils Source File Generator", + allow_abbrev=False) + + parser.add_argument("-sfg-d", "--sfg-output-dir", + type=str, default='.', dest='output_directory') + parser.add_argument("-sfg-e", "--sfg-file-extensions", + type=str, default=None, nargs='*', dest='file_extensions') + parser.add_argument("--sfg-header-only", + type=str, default=None, nargs='*', dest='header_only') + parser.add_argument("--sfg-configurator", type=str, + default=None, nargs='*', dest='configurator_script') + + args, script_args = parser.parse_known_args(argv) + + if args.configurator_script is not None: + project_config = run_configurator(args.configurator_script) + else: + project_config = None + + if args.file_extensions is not None: + h_ext, src_ext = get_file_extensions(args.file_extensions) + else: + h_ext, src_ext = None, None + + cmdline_config = SfgConfiguration( + header_extension=h_ext, + source_extension=src_ext, + header_only=args.header_only, + output_directory=args.output_directory + ) + + return project_config, cmdline_config, script_args + + +def merge_configurations(project_config: SfgConfiguration, + cmdline_config: SfgConfiguration, + script_config: SfgConfiguration): + # Project config completely overrides default config + config = DEFAULT_CONFIG + + if project_config is not None: + config = replace(DEFAULT_CONFIG, **(project_config.asdict())) + + if cmdline_config is not None: + cmdline_dict = cmdline_config.asdict() + # Commandline config completely overrides project and default config + config = replace(config, **cmdline_dict) + else: + cmdline_dict = {} + + if script_config is not None: + # User config may only set values not specified on the command line + script_dict = script_config.asdict() + for key, cmdline_value in cmdline_dict.items(): + if cmdline_value is not None and script_dict[key] is not None: + raise SfgException( + f"Conflicting configuration: Parameter {key} was specified both in the script and on the command line.") + + config = replace(config, **script_dict) + + return config diff --git a/pystencilssfg/context.py b/pystencilssfg/context.py index f2d968d..feece85 100644 --- a/pystencilssfg/context.py +++ b/pystencilssfg/context.py @@ -5,13 +5,11 @@ import sys import os from os import path -from argparse import ArgumentParser - -from jinja2.filters import do_indent from pystencils import Field from pystencils.astnodes import KernelFunction +from .configuration import SfgConfiguration, config_from_commandline, merge_configurations, SfgCodeStyle from .kernel_namespace import SfgKernelNamespace, SfgKernelHandle from .tree import SfgCallTreeNode, SfgSequence, SfgKernelCallNode, SfgStatements from .tree.deferred_nodes import SfgDeferredFieldMapping @@ -21,37 +19,22 @@ from .source_concepts import SrcField, TypedSymbolOrObject from .source_components import SfgFunction, SfgHeaderInclude -@dataclass -class SfgCodeStyle: - indent_width: int = 2 - - def indent(self, s: str): - return do_indent(s, self.indent_width, first=True) - class SourceFileGenerator: - def __init__(self, - namespace: str = "pystencils", - codestyle: SfgCodeStyle = SfgCodeStyle()): - - parser = ArgumentParser( - "pystencilssfg", - description="pystencils Source File Generator", - allow_abbrev=False) - - parser.add_argument("-d", "--sfg-output-dir", type=str, default='.', dest='output_directory') - - generator_args, script_args = parser.parse_known_args(sys.argv) - + def __init__(self, sfg_config: SfgConfiguration): import __main__ scriptpath = __main__.__file__ scriptname = path.split(scriptpath)[1] - basename = path.splitext(scriptname)[0] + basename = path.splitext(scriptname)[0] + + project_config, cmdline_config, script_args = config_from_commandline(sys.argv) + + config = merge_configurations(project_config, cmdline_config, sfg_config) - self._context = SfgContext(script_args, namespace, codestyle) + self._context = SfgContext(script_args, config) from .emitters.cpu.basic_cpu import BasicCpuEmitter - self._emitter = BasicCpuEmitter(self._context, basename, generator_args.output_directory) + self._emitter = BasicCpuEmitter(self._context, basename, config.output_directory) def clean_files(self): for file in self._emitter.output_files: @@ -68,10 +51,9 @@ class SourceFileGenerator: class SfgContext: - def __init__(self, argv, root_namespace: str, codestyle: SfgCodeStyle): + def __init__(self, argv, config: SfgConfiguration): self._argv = argv - self._root_namespace = root_namespace - self._codestyle = codestyle + self._config = config self._default_kernel_namespace = SfgKernelNamespace(self, "kernels") # Source Components @@ -85,11 +67,11 @@ class SfgContext: @property def root_namespace(self) -> str: - return self._root_namespace + return self._config.base_namespace @property def codestyle(self) -> SfgCodeStyle: - return self._codestyle + return self._config.codestyle #---------------------------------------------------------------------------------------------- # Source Component Getters -- GitLab