From b0ee22474a457e86e6013f39f4655118b7dcdb65 Mon Sep 17 00:00:00 2001
From: Frederik Hennig <frederik.hennig@fau.de>
Date: Fri, 15 Dec 2023 20:34:33 +0100
Subject: [PATCH] Vast extensions to documentation

---
 docs/api/composer.md                          |   8 +
 docs/api/{composition.md => context.md}       |   2 -
 docs/api/emission.md                          |  14 ++
 docs/api/generator.md                         |   5 +-
 docs/api/index.md                             |  18 ++
 docs/api/source_components.md                 |  36 ++-
 mkdocs.yml                                    |   6 +-
 src/pystencilssfg/cli.py                      |   4 +-
 src/pystencilssfg/composer/__init__.py        |  10 +
 .../basic_composer.py}                        | 184 ++------------
 src/pystencilssfg/composer/class_composer.py  | 230 ++++++++++++++++++
 src/pystencilssfg/composer/composer.py        |  22 ++
 src/pystencilssfg/configuration.py            |  33 ---
 src/pystencilssfg/context.py                  |  83 ++++++-
 src/pystencilssfg/emission/__init__.py        |   4 +-
 src/pystencilssfg/emission/clang_format.py    |  18 ++
 ...der_source_pair.py => header_impl_pair.py} |   8 +-
 src/pystencilssfg/generator.py                |  54 +++-
 18 files changed, 523 insertions(+), 216 deletions(-)
 create mode 100644 docs/api/composer.md
 rename docs/api/{composition.md => context.md} (57%)
 create mode 100644 docs/api/emission.md
 create mode 100644 src/pystencilssfg/composer/__init__.py
 rename src/pystencilssfg/{composer.py => composer/basic_composer.py} (68%)
 create mode 100644 src/pystencilssfg/composer/class_composer.py
 create mode 100644 src/pystencilssfg/composer/composer.py
 rename src/pystencilssfg/emission/{header_source_pair.py => header_impl_pair.py} (78%)

diff --git a/docs/api/composer.md b/docs/api/composer.md
new file mode 100644
index 0000000..c0542aa
--- /dev/null
+++ b/docs/api/composer.md
@@ -0,0 +1,8 @@
+
+::: pystencilssfg.composer.SfgComposer
+
+::: pystencilssfg.composer.SfgBasicComposer
+
+::: pystencilssfg.composer.SfgClassComposer
+
+::: pystencilssfg.composer.make_sequence
\ No newline at end of file
diff --git a/docs/api/composition.md b/docs/api/context.md
similarity index 57%
rename from docs/api/composition.md
rename to docs/api/context.md
index 4c58610..fcc4948 100644
--- a/docs/api/composition.md
+++ b/docs/api/context.md
@@ -1,4 +1,2 @@
 
 ::: pystencilssfg.context.SfgContext
-
-::: pystencilssfg.composer
diff --git a/docs/api/emission.md b/docs/api/emission.md
new file mode 100644
index 0000000..fa6211e
--- /dev/null
+++ b/docs/api/emission.md
@@ -0,0 +1,14 @@
+
+## Output Configuration
+
+::: pystencilssfg.configuration.SfgOutputSpec
+
+## Header-Implementation-Pair Emission
+
+::: pystencilssfg.emission.HeaderImplPairEmitter
+
+## Code Style and `clang-format`
+
+::: pystencilssfg.configuration.SfgCodeStyle
+
+::: pystencilssfg.emission.clang_format.invoke_clang_format
\ No newline at end of file
diff --git a/docs/api/generator.md b/docs/api/generator.md
index 32a0b8e..07128fd 100644
--- a/docs/api/generator.md
+++ b/docs/api/generator.md
@@ -1,4 +1,5 @@
 
-::: pystencilssfg.configuration
+::: pystencilssfg.generator.SourceFileGenerator
+
+::: pystencilssfg.configuration.SfgConfiguration
 
-::: pystencilssfg.generator
diff --git a/docs/api/index.md b/docs/api/index.md
index e69de29..3dd4689 100644
--- a/docs/api/index.md
+++ b/docs/api/index.md
@@ -0,0 +1,18 @@
+# API Documentation
+
+These pages document the public API of *pystencils-sfg*.
+
+### Front End
+
+ - [Source File Generator](generator.md)
+ - [Code Generation Context](context.md)
+ - [Composer](composer.md)
+
+### Source File Modelling
+
+ - [Source File Components](source_components.md)
+ - [Kernel Call Tree](tree.md)
+
+### Code Generation
+
+ - [Emission and Printing](emission.md)
\ No newline at end of file
diff --git a/docs/api/source_components.md b/docs/api/source_components.md
index a7d1a59..29448be 100644
--- a/docs/api/source_components.md
+++ b/docs/api/source_components.md
@@ -1,2 +1,36 @@
 
-::: pystencilssfg.source_components
+## Kernels and Kernel Namespaces
+
+::: pystencilssfg.source_components.SfgKernelNamespace
+
+::: pystencilssfg.source_components.SfgKernelHandle
+
+## Includes
+
+::: pystencilssfg.source_components.SfgHeaderInclude
+
+## Functions
+
+::: pystencilssfg.source_components.SfgFunction
+
+## Classes
+
+::: pystencilssfg.source_components.SfgClassKeyword
+
+::: pystencilssfg.source_components.SfgClass
+
+### Visibility
+
+::: pystencilssfg.source_components.SfgVisibility
+
+::: pystencilssfg.source_components.SfgVisibilityBlock
+
+### Members
+
+::: pystencilssfg.source_components.SfgClassMember
+
+::: pystencilssfg.source_components.SfgInClassDefinition
+
+::: pystencilssfg.source_components.SfgConstructor
+
+::: pystencilssfg.source_components.SfgMethod
diff --git a/mkdocs.yml b/mkdocs.yml
index c962ad4..e92f517 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -24,7 +24,7 @@ plugins:
             members_order: source
             group_by_category: False
             show_root_heading: True
-            show_root_full_path: False
+            show_root_full_path: True
             show_symbol_type_heading: True
             show_symbol_type_toc: True
             show_source: False
@@ -46,6 +46,8 @@ nav:
   - 'API Documentation':
     - 'Overview': api/index.md
     - 'Source File Generator': api/generator.md
-    - 'Composer': api/composition.md
+    - 'Code Generation Context': api/context.md
+    - 'Composer': api/composer.md
     - 'Source File Components': api/source_components.md
     - 'Kernel Call Tree': api/tree.md
+    - 'Emission and Printing': api/emission.md
diff --git a/src/pystencilssfg/cli.py b/src/pystencilssfg/cli.py
index 933c28a..9cf4d4a 100644
--- a/src/pystencilssfg/cli.py
+++ b/src/pystencilssfg/cli.py
@@ -72,9 +72,9 @@ def list_files(args):
     _, scriptname = path.split(args.codegen_script)
     basename = path.splitext(scriptname)[0]
 
-    from .emission import HeaderSourcePairEmitter
+    from .emission import HeaderImplPairEmitter
 
-    emitter = HeaderSourcePairEmitter(config.get_output_spec(basename))
+    emitter = HeaderImplPairEmitter(config.get_output_spec(basename))
 
     print(args.sep.join(emitter.output_files), end=os.linesep if args.newline else '')
 
diff --git a/src/pystencilssfg/composer/__init__.py b/src/pystencilssfg/composer/__init__.py
new file mode 100644
index 0000000..e895fe6
--- /dev/null
+++ b/src/pystencilssfg/composer/__init__.py
@@ -0,0 +1,10 @@
+from .composer import SfgComposer
+from .basic_composer import SfgBasicComposer, make_sequence
+from .class_composer import SfgClassComposer
+
+__all__ = [
+    'SfgComposer',
+    "make_sequence",
+    "SfgBasicComposer",
+    "SfgClassComposer"
+]
diff --git a/src/pystencilssfg/composer.py b/src/pystencilssfg/composer/basic_composer.py
similarity index 68%
rename from src/pystencilssfg/composer.py
rename to src/pystencilssfg/composer/basic_composer.py
index cb51c56..29b8d96 100644
--- a/src/pystencilssfg/composer.py
+++ b/src/pystencilssfg/composer/basic_composer.py
@@ -6,7 +6,7 @@ import numpy as np
 from pystencils import Field
 from pystencils.astnodes import KernelFunction
 
-from .tree import (
+from ..tree import (
     SfgCallTreeNode,
     SfgKernelCallNode,
     SfgStatements,
@@ -15,45 +15,42 @@ from .tree import (
     SfgSequence,
     SfgBlock,
 )
-from .tree.deferred_nodes import SfgDeferredFieldMapping
-from .tree.conditional import SfgCondition, SfgCustomCondition, SfgBranch, SfgSwitch
-from .source_components import (
+from ..tree.deferred_nodes import SfgDeferredFieldMapping
+from ..tree.conditional import SfgCondition, SfgCustomCondition, SfgBranch, SfgSwitch
+from ..source_components import (
     SfgFunction,
     SfgHeaderInclude,
     SfgKernelNamespace,
     SfgKernelHandle,
     SfgClass,
-    SfgClassMember,
-    SfgInClassDefinition,
     SfgConstructor,
-    SfgMethod,
     SfgMemberVariable,
     SfgClassKeyword,
-    SfgVisibility,
-    SfgVisibilityBlock,
 )
-from .source_concepts import SrcObject, SrcField, TypedSymbolOrObject, SrcVector
-from .types import cpp_typename, SrcType
-from .exceptions import SfgException
+from ..source_concepts import SrcObject, SrcField, TypedSymbolOrObject, SrcVector
+from ..types import cpp_typename, SrcType
+from ..exceptions import SfgException
 
 if TYPE_CHECKING:
-    from .context import SfgContext
+    from ..context import SfgContext
 
 
-class SfgComposer:
-    """Primary interface for constructing source files in pystencils-sfg."""
+class SfgBasicComposer:
+    """Composer for basic source components."""
 
     def __init__(self, ctx: SfgContext):
-        self._ctx = ctx
+        self._ctx: SfgContext = ctx
 
     @property
     def context(self):
         return self._ctx
 
     def prelude(self, content: str):
-        """Add a string to the code file's prelude.
+        """Append a string to the prelude comment, to be printed at the top of both generated files.
 
-        Do not wrap the given string in comment syntax."""
+        The string should not contain C/C++ comment delimiters, since these will be added automatically
+        during code generation.
+        """
         self._ctx.append_to_prelude(content)
 
     def define(self, definition: str):
@@ -83,6 +80,13 @@ class SfgComposer:
         return kns
 
     def include(self, header_file: str, private: bool = False):
+        """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
+                only included in the implementation file.
+        """
         self._ctx.add_include(parse_include(header_file, private))
 
     def numpy_struct(
@@ -367,150 +371,6 @@ def parse_include(incl: str | SfgHeaderInclude, private: bool = False):
     return SfgHeaderInclude(incl, system_header=system_header, private=private)
 
 
-class SfgClassComposer:
-    def __init__(self, ctx: SfgContext):
-        self._ctx = ctx
-
-    class VisibilityContext:
-        def __init__(self, visibility: SfgVisibility):
-            self._vis_block = SfgVisibilityBlock(visibility)
-
-        def members(self):
-            yield from self._vis_block.members()
-
-        def __call__(
-            self,
-            *args: (
-                SfgClassMember | SfgClassComposer.ConstructorBuilder | SrcObject | str
-            ),
-        ):
-            for arg in args:
-                self._vis_block.append_member(SfgClassComposer._resolve_member(arg))
-
-            return self
-
-        def resolve(self, cls: SfgClass) -> None:
-            cls.append_visibility_block(self._vis_block)
-
-    class ConstructorBuilder:
-        def __init__(self, *params: SrcObject):
-            self._params = params
-            self._initializers: list[str] = []
-            self._body = ""
-
-        def init(self, initializer: str) -> SfgClassComposer.ConstructorBuilder:
-            self._initializers.append(initializer)
-            return self
-
-        def body(self, body: str):
-            self._body = body
-            return self
-
-        def resolve(self) -> SfgConstructor:
-            return SfgConstructor(
-                parameters=self._params,
-                initializers=self._initializers,
-                body=self._body,
-            )
-
-    def klass(self, class_name: str, bases: Sequence[str] = ()):
-        return self._class(class_name, SfgClassKeyword.CLASS, bases)
-
-    def struct(self, class_name: str, bases: Sequence[str] = ()):
-        return self._class(class_name, SfgClassKeyword.STRUCT, bases)
-
-    @property
-    def public(self) -> SfgClassComposer.VisibilityContext:
-        return SfgClassComposer.VisibilityContext(SfgVisibility.PUBLIC)
-
-    @property
-    def protected(self) -> SfgClassComposer.VisibilityContext:
-        return SfgClassComposer.VisibilityContext(SfgVisibility.PROTECTED)
-
-    @property
-    def private(self) -> SfgClassComposer.VisibilityContext:
-        return SfgClassComposer.VisibilityContext(SfgVisibility.PRIVATE)
-
-    def var(self, name: str, dtype: SrcType):
-        return SfgMemberVariable(name, dtype)
-
-    def constructor(self, *params):
-        return SfgClassComposer.ConstructorBuilder(*params)
-
-    def method(
-        self,
-        name: str,
-        returns: SrcType = SrcType("void"),
-        inline: bool = False,
-        const: bool = False,
-    ):
-        def sequencer(*args: str | tuple | SfgCallTreeNode | SfgNodeBuilder):
-            tree = make_sequence(*args)
-            return SfgMethod(
-                name, tree, return_type=returns, inline=inline, const=const
-            )
-
-        return sequencer
-
-    #   INTERNALS
-
-    def _class(self, class_name: str, keyword: SfgClassKeyword, bases: Sequence[str]):
-        if self._ctx.get_class(class_name) is not None:
-            raise ValueError(f"Class or struct {class_name} already exists.")
-
-        cls = SfgClass(class_name, class_keyword=keyword, bases=bases)
-        self._ctx.add_class(cls)
-
-        def sequencer(
-            *args: (
-                SfgClassComposer.VisibilityContext
-                | SfgClassMember
-                | SfgClassComposer.ConstructorBuilder
-                | SrcObject
-                | str
-            ),
-        ):
-            default_ended = False
-
-            for arg in args:
-                if isinstance(arg, SfgClassComposer.VisibilityContext):
-                    default_ended = True
-                    arg.resolve(cls)
-                elif isinstance(
-                    arg,
-                    (
-                        SfgClassMember,
-                        SfgClassComposer.ConstructorBuilder,
-                        SrcObject,
-                        str,
-                    ),
-                ):
-                    if default_ended:
-                        raise SfgException(
-                            "Composer Syntax Error: "
-                            "Cannot add members with default visibility after a visibility block."
-                        )
-                    else:
-                        cls.default.append_member(self._resolve_member(arg))
-                else:
-                    raise SfgException(f"{arg} is not a valid class member.")
-
-        return sequencer
-
-    @staticmethod
-    def _resolve_member(
-        arg: (SfgClassMember | SfgClassComposer.ConstructorBuilder | SrcObject | str),
-    ):
-        if isinstance(arg, SrcObject):
-            return SfgMemberVariable(arg.name, arg.dtype)
-        elif isinstance(arg, str):
-            return SfgInClassDefinition(arg)
-        elif isinstance(arg, SfgClassComposer.ConstructorBuilder):
-            return arg.resolve()
-        else:
-            return arg
-
-
 def struct_from_numpy_dtype(
     struct_name: str, dtype: np.dtype, add_constructor: bool = True
 ):
diff --git a/src/pystencilssfg/composer/class_composer.py b/src/pystencilssfg/composer/class_composer.py
new file mode 100644
index 0000000..e326292
--- /dev/null
+++ b/src/pystencilssfg/composer/class_composer.py
@@ -0,0 +1,230 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING, Sequence
+
+from ..tree import SfgCallTreeNode
+from ..source_components import (
+    SfgClass,
+    SfgClassMember,
+    SfgInClassDefinition,
+    SfgConstructor,
+    SfgMethod,
+    SfgMemberVariable,
+    SfgClassKeyword,
+    SfgVisibility,
+    SfgVisibilityBlock,
+)
+from ..source_concepts import SrcObject
+from ..types import SrcType
+from ..exceptions import SfgException
+
+from .basic_composer import SfgBasicComposer, SfgNodeBuilder, make_sequence
+
+if TYPE_CHECKING:
+    from ..context import SfgContext
+
+
+class SfgClassComposer:
+    """Composer for classes and structs.
+
+
+    This class cannot be instantiated on its own but must be mixed in with
+    [SfgBasicComposer][pystencilssfg.composer.SfgBasicComposer].
+    Use through [SfgComposer][pystencilssfg.SfgComposer].
+    """
+
+    def __init__(self, ctx: SfgContext):
+        if not isinstance(self, SfgBasicComposer):
+            raise Exception("SfgClassComposer must be mixed-in with SfgBasicComposer.")
+        self._ctx: SfgContext = ctx
+
+    class VisibilityContext:
+        """Represent a visibility block in the composer syntax.
+
+        Returned by
+        [private][pystencilssfg.composer.SfgClassComposer.private],
+        [public][pystencilssfg.composer.SfgClassComposer.public] and
+        [protected][pystencilssfg.composer.SfgClassComposer.protected].
+        """
+
+        def __init__(self, visibility: SfgVisibility):
+            self._vis_block = SfgVisibilityBlock(visibility)
+
+        def members(self):
+            yield from self._vis_block.members()
+
+        def __call__(
+            self,
+            *args: (
+                SfgClassMember | SfgClassComposer.ConstructorBuilder | SrcObject | str
+            ),
+        ):
+            for arg in args:
+                self._vis_block.append_member(SfgClassComposer._resolve_member(arg))
+
+            return self
+
+        def resolve(self, cls: SfgClass) -> None:
+            cls.append_visibility_block(self._vis_block)
+
+    class ConstructorBuilder:
+        """Composer syntax for constructor building.
+
+        Returned by [constructor][pystencilssfg.composer.SfgClassComposer.constructor].
+        """
+
+        def __init__(self, *params: SrcObject):
+            self._params = params
+            self._initializers: list[str] = []
+            self._body: str | None = None
+
+        def init(self, initializer: str) -> SfgClassComposer.ConstructorBuilder:
+            """Add an initialization expression to the constructor's initializer list."""
+            self._initializers.append(initializer)
+            return self
+
+        def body(self, body: str):
+            """Define the constructor body"""
+            if self._body is not None:
+                raise SfgException("Multiple definitions of constructor body.")
+            self._body = body
+            return self
+
+        def resolve(self) -> SfgConstructor:
+            return SfgConstructor(
+                parameters=self._params,
+                initializers=self._initializers,
+                body=self._body if self._body is not None else "",
+            )
+
+    def klass(self, class_name: str, bases: Sequence[str] = ()):
+        """Create a class and add it to the underlying context.
+
+        Args:
+            class_name: Name of the class
+            bases: List of base classes
+        """
+        return self._class(class_name, SfgClassKeyword.CLASS, bases)
+
+    def struct(self, class_name: str, bases: Sequence[str] = ()):
+        """Create a struct and add it to the underlying context.
+
+        Args:
+            class_name: Name of the struct
+            bases: List of base classes
+        """
+        return self._class(class_name, SfgClassKeyword.STRUCT, bases)
+
+    @property
+    def public(self) -> SfgClassComposer.VisibilityContext:
+        """Create a `public` visibility block in a class body"""
+        return SfgClassComposer.VisibilityContext(SfgVisibility.PUBLIC)
+
+    @property
+    def protected(self) -> SfgClassComposer.VisibilityContext:
+        """Create a `protected` visibility block in a class or struct body"""
+        return SfgClassComposer.VisibilityContext(SfgVisibility.PROTECTED)
+
+    @property
+    def private(self) -> SfgClassComposer.VisibilityContext:
+        """Create a `private` visibility block in a class or struct body"""
+        return SfgClassComposer.VisibilityContext(SfgVisibility.PRIVATE)
+
+    def var(self, name: str, dtype: SrcType):
+        """In a class or struct body or visibility block, and a member variable.
+
+        Args:
+            name: The variable's name
+            dtype: The variable's data type
+        """
+        return SfgMemberVariable(name, dtype)
+
+    def constructor(self, *params: SrcObject):
+        """In a class or struct body or visibility block, add a constructor.
+
+        Args:
+            params: List of constructor parameters
+        """
+        return SfgClassComposer.ConstructorBuilder(*params)
+
+    def method(
+        self,
+        name: str,
+        returns: SrcType = SrcType("void"),
+        inline: bool = False,
+        const: bool = False,
+    ):
+        """In a class or struct body or visibility block, add a method.
+        The usage is similar to [SfgBasicComposer.function][pystencilssfg.composer.SfgBasicComposer.function].
+
+        Args:
+            name: The method name
+            returns: The method's return type
+            inline: Whether or not the method should be defined in-line.
+            const: Whether or not the method is const-qualified.
+        """
+
+        def sequencer(*args: str | tuple | SfgCallTreeNode | SfgNodeBuilder):
+            tree = make_sequence(*args)
+            return SfgMethod(
+                name, tree, return_type=returns, inline=inline, const=const
+            )
+
+        return sequencer
+
+    #   INTERNALS
+
+    def _class(self, class_name: str, keyword: SfgClassKeyword, bases: Sequence[str]):
+        if self._ctx.get_class(class_name) is not None:
+            raise ValueError(f"Class or struct {class_name} already exists.")
+
+        cls = SfgClass(class_name, class_keyword=keyword, bases=bases)
+        self._ctx.add_class(cls)
+
+        def sequencer(
+            *args: (
+                SfgClassComposer.VisibilityContext
+                | SfgClassMember
+                | SfgClassComposer.ConstructorBuilder
+                | SrcObject
+                | str
+            ),
+        ):
+            default_ended = False
+
+            for arg in args:
+                if isinstance(arg, SfgClassComposer.VisibilityContext):
+                    default_ended = True
+                    arg.resolve(cls)
+                elif isinstance(
+                    arg,
+                    (
+                        SfgClassMember,
+                        SfgClassComposer.ConstructorBuilder,
+                        SrcObject,
+                        str,
+                    ),
+                ):
+                    if default_ended:
+                        raise SfgException(
+                            "Composer Syntax Error: "
+                            "Cannot add members with default visibility after a visibility block."
+                        )
+                    else:
+                        cls.default.append_member(self._resolve_member(arg))
+                else:
+                    raise SfgException(f"{arg} is not a valid class member.")
+
+        return sequencer
+
+    @staticmethod
+    def _resolve_member(
+        arg: (SfgClassMember | SfgClassComposer.ConstructorBuilder | SrcObject | str),
+    ):
+        if isinstance(arg, SrcObject):
+            return SfgMemberVariable(arg.name, arg.dtype)
+        elif isinstance(arg, str):
+            return SfgInClassDefinition(arg)
+        elif isinstance(arg, SfgClassComposer.ConstructorBuilder):
+            return arg.resolve()
+        else:
+            return arg
diff --git a/src/pystencilssfg/composer/composer.py b/src/pystencilssfg/composer/composer.py
new file mode 100644
index 0000000..3ed79c5
--- /dev/null
+++ b/src/pystencilssfg/composer/composer.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING
+
+from .basic_composer import SfgBasicComposer
+from .class_composer import SfgClassComposer
+
+if TYPE_CHECKING:
+    from ..context import SfgContext
+
+
+class SfgComposer(SfgBasicComposer, SfgClassComposer):
+    """Primary interface for constructing source files in pystencils-sfg.
+
+    The SfgComposer combines the [SfgBasicComposer][pystencilssfg.composer.SfgBasicComposer]
+    for the basic components (kernel namespaces, includes, definitions, and functions)
+    and the [SfgClassComposer][pystencilssfg.composer.SfgClassComposer] for constructing
+    `struct`s and `class`es.
+    """
+
+    def __init__(self, ctx: SfgContext):
+        SfgBasicComposer.__init__(self, ctx)
+        SfgClassComposer.__init__(self, ctx)
diff --git a/src/pystencilssfg/configuration.py b/src/pystencilssfg/configuration.py
index a7bced4..c6c7422 100644
--- a/src/pystencilssfg/configuration.py
+++ b/src/pystencilssfg/configuration.py
@@ -1,36 +1,3 @@
-"""
-The [source file generator][pystencilssfg.SourceFileGenerator] draws configuration from a total of four sources:
-
- - The [default configuration][pystencilssfg.configuration.DEFAULT_CONFIG];
- - The project configuration;
- - Command-line arguments;
- - The user configuration passed to the constructor of `SourceFileGenerator`.
-
-They take precedence in the following way:
-
- - Project configuration overrides the default configuration
- - Command line arguments override the project configuration
- - User configuration overrides default and project configuration,
-   and must not conflict with command-line arguments; otherwise, an error is thrown.
-
-### Project Configuration via Configurator Script
-
-Currently, the only way to define the project configuration is via a configuration module.
-A configurator module is a Python file defining the following function at the top-level:
-
-```Python
-from pystencilssfg import SfgConfiguration
-
-def sfg_config() -> SfgConfiguration:
-    # ...
-    return SfgConfiguration(
-        # ...
-    )
-```
-
-The configuration module is passed to the code generation script via the command-line argument
-`--sfg-config-module`.
-"""
 # mypy: strict_optional=False
 
 from __future__ import annotations
diff --git a/src/pystencilssfg/context.py b/src/pystencilssfg/context.py
index 222b313..968be89 100644
--- a/src/pystencilssfg/context.py
+++ b/src/pystencilssfg/context.py
@@ -11,12 +11,51 @@ from .exceptions import SfgException
 
 
 class SfgContext:
+    """Represents a header/implementation file pair in the code generator.
+
+    ## Source File Properties and Components
+
+    The SfgContext collects all properties and components of a header/implementation
+    file pair (or just the header file, if header-only generation is used).
+    These are:
+
+     - The code namespace, which is combined from the [outer_namespace][pystencilssfg.SfgContext.outer_namespace]
+       and the [inner_namespace][pystencilssfg.SfgContext.inner_namespace]. The outer namespace is meant to be set
+       externally e.g. by the project configuration, while the inner namespace is meant to be set by the generator
+       script.
+     - The [prelude comment][pystencilssfg.SfgContext.prelude_comment] is a block of text printed as a comment block
+       at the top of both generated files. Typically, it contains authorship and licence information.
+     - The set of [Included header files][pystencilssfg.SfgContext.includes].
+     - Custom [definitions][pystencilssfg.SfgContext.definitions], which are just arbitrary code strings.
+     - Any number of [kernel namespaces][pystencilssfg.SfgContext.kernel_namespaces], within which *pystencils*
+       kernels are managed.
+     - Any number of [functions][pystencilssfg.SfgContext.functions], which are meant to serve as wrappers
+       around kernel calls.
+     - Any number of [classes][pystencilssfg.SfgContext.classes], which can be used to build more extensive wrappers
+       around kernels.
+
+    ## Order of Definitions
+
+    To honor C/C++ use-after-declare rules, the context preserves the order in which definitions, functions and classes
+    are added to it.
+    The header file printers implemented in *pystencils-sfg* will print the declarations accordingly.
+    The declarations can retrieved in order of definition via
+    [declarations_ordered][pystencilssfg.SfgContext.declarations_ordered].
+    """
+
     def __init__(
         self,
         outer_namespace: str | None = None,
         codestyle: SfgCodeStyle = SfgCodeStyle(),
         argv: Sequence[str] | None = None,
     ):
+        """
+        Args:
+            outer_namespace: Qualified name of the outer code namespace
+            codestyle: Code style that should be used by the code emitter
+            argv: The generator script's command line arguments;
+                reserved for internal use by the [SourceFileGenerator][pystencilssfg.SourceFileGenerator].
+        """
         self._argv = argv
         self._default_kernel_namespace = SfgKernelNamespace(self, "kernels")
 
@@ -54,14 +93,17 @@ class SfgContext:
 
     @property
     def outer_namespace(self) -> str | None:
+        """Outer code namespace. Set by constructor argument `outer_namespace`."""
         return self._outer_namespace
 
     @property
     def inner_namespace(self) -> str | None:
+        """Inner code namespace. Set by `set_namespace`."""
         return self._inner_namespace
 
     @property
     def fully_qualified_namespace(self) -> str | None:
+        """Combined outer and inner namespaces, as `outer_namespace::inner_namespace`."""
         match (self.outer_namespace, self.inner_namespace):
             case None, None:
                 return None
@@ -76,6 +118,7 @@ class SfgContext:
 
     @property
     def codestyle(self) -> SfgCodeStyle:
+        """The code style object for this generation context."""
         return self._codestyle
 
     # ----------------------------------------------------------------------------------------------
@@ -88,6 +131,12 @@ class SfgContext:
         return self._prelude
 
     def append_to_prelude(self, code_str: str):
+        """Append a string to the prelude comment.
+
+        The string should not contain
+        C/C++ comment delimiters, since these will be added automatically during
+        code generation.
+        """
         if self._prelude:
             self._prelude += "\n"
 
@@ -105,14 +154,19 @@ class SfgContext:
         self._includes.add(include)
 
     def definitions(self) -> Generator[str, None, None]:
-        """Definitions are code lines printed at the top of the header file, after the includes."""
+        """Definitions are arbitrary custom lines of code."""
         yield from self._definitions
 
     def add_definition(self, definition: str):
+        """Add a custom code string to the header file."""
         self._definitions.append(definition)
         self._declarations_ordered.append(definition)
 
     def set_namespace(self, namespace: str):
+        """Set the inner code namespace.
+
+        Throws an exception if the namespace was already set.
+        """
         if self._inner_namespace is not None:
             raise SfgException("The code namespace was already set.")
 
@@ -124,15 +178,22 @@ class SfgContext:
 
     @property
     def default_kernel_namespace(self) -> SfgKernelNamespace:
+        """The default kernel namespace."""
         return self._default_kernel_namespace
 
     def kernel_namespaces(self) -> Generator[SfgKernelNamespace, None, None]:
+        """Iterator over all registered kernel namespaces."""
         yield from self._kernel_namespaces.values()
 
     def get_kernel_namespace(self, str) -> SfgKernelNamespace | None:
+        """Retrieve a kernel namespace by name, or `None` if it does not exist."""
         return self._kernel_namespaces.get(str)
 
     def add_kernel_namespace(self, namespace: SfgKernelNamespace):
+        """Adds a new kernel namespace.
+
+        If a kernel namespace of the same name already exists, throws an exception.
+        """
         if namespace.name in self._kernel_namespaces:
             raise ValueError(f"Duplicate kernel namespace: {namespace.name}")
 
@@ -143,13 +204,19 @@ class SfgContext:
     # ----------------------------------------------------------------------------------------------
 
     def functions(self) -> Generator[SfgFunction, None, None]:
+        """Iterator over all registered functions."""
         yield from self._functions.values()
 
     def get_function(self, name: str) -> SfgFunction | None:
+        """Retrieve a function by name. Returns `None` if no function of the given name exists."""
         return self._functions.get(name, None)
 
     def add_function(self, func: SfgFunction):
-        if func.name in self._functions:
+        """Adds a new function.
+
+        If a function or class with the same name exists already, throws an exception.
+        """
+        if func.name in self._functions or func.name in self._classes:
             raise SfgException(f"Duplicate function: {func.name}")
 
         self._functions[func.name] = func
@@ -160,13 +227,19 @@ class SfgContext:
     # ----------------------------------------------------------------------------------------------
 
     def classes(self) -> Generator[SfgClass, None, None]:
+        """Iterator over all registered classes."""
         yield from self._classes.values()
 
     def get_class(self, name: str) -> SfgClass | None:
+        """Retrieve a class by name, or `None` if the class does not exist."""
         return self._classes.get(name, None)
 
     def add_class(self, cls: SfgClass):
-        if cls.class_name in self._classes:
+        """Add a class.
+
+        Throws an exception if a class or function of the same name exists already.
+        """
+        if cls.class_name in self._classes or cls.class_name in self._functions:
             raise SfgException(f"Duplicate class: {cls.class_name}")
 
         self._classes[cls.class_name] = cls
@@ -176,7 +249,9 @@ class SfgContext:
     #   Declarations in order of addition
     # ----------------------------------------------------------------------------------------------
 
-    def declarations_ordered(self) -> Generator[str | SfgFunction | SfgClass, None, None]:
+    def declarations_ordered(
+        self,
+    ) -> Generator[str | SfgFunction | SfgClass, None, None]:
         """All declared definitions, classes and functions in the order they were added.
 
         Awareness about order is necessary due to the C++ declare-before-use rules."""
diff --git a/src/pystencilssfg/emission/__init__.py b/src/pystencilssfg/emission/__init__.py
index fc3c4f6..74314be 100644
--- a/src/pystencilssfg/emission/__init__.py
+++ b/src/pystencilssfg/emission/__init__.py
@@ -1,5 +1,5 @@
-from .header_source_pair import HeaderSourcePairEmitter
+from .header_impl_pair import HeaderImplPairEmitter
 
 __all__ = [
-    "HeaderSourcePairEmitter"
+    "HeaderImplPairEmitter"
 ]
diff --git a/src/pystencilssfg/emission/clang_format.py b/src/pystencilssfg/emission/clang_format.py
index f0970ae..5c5084c 100644
--- a/src/pystencilssfg/emission/clang_format.py
+++ b/src/pystencilssfg/emission/clang_format.py
@@ -6,6 +6,24 @@ from ..exceptions import SfgException
 
 
 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.
+
+    Args:
+        code: Code string to format
+        codestyle: [SfgCodeStyle][pystencilssfg.configuration.SfgCodeStyle] object
+            defining the `clang-format` binary and the desired code style.
+
+    Returns:
+        The formatted code, if `clang-format` was run sucessfully.
+        Otherwise, the original unformatted code, unless `codestyle.force_clang_format`
+            was set to true. In the latter case, an exception is thrown.
+
+    Forced Formatting:
+        If `codestyle.force_clang_format` was set to true but the formatter could not
+        be executed (binary not found, or error during exection), the function will
+        throw an exception.
+    """
     args = [codestyle.clang_format_binary, f"--style={codestyle.code_style}"]
 
     if not shutil.which(codestyle.clang_format_binary):
diff --git a/src/pystencilssfg/emission/header_source_pair.py b/src/pystencilssfg/emission/header_impl_pair.py
similarity index 78%
rename from src/pystencilssfg/emission/header_source_pair.py
rename to src/pystencilssfg/emission/header_impl_pair.py
index 8eaef4e..53b6857 100644
--- a/src/pystencilssfg/emission/header_source_pair.py
+++ b/src/pystencilssfg/emission/header_impl_pair.py
@@ -7,8 +7,11 @@ from .printers import SfgHeaderPrinter, SfgImplPrinter
 from .clang_format import invoke_clang_format
 
 
-class HeaderSourcePairEmitter:
+class HeaderImplPairEmitter:
+    """Emits a header-implementation file pair."""
+
     def __init__(self, output_spec: SfgOutputSpec):
+        """Create a `HeaderImplPairEmitter` from an [SfgOutputSpec][pystencilssfg.configuration.SfgOutputSpec]."""
         self._basename = output_spec.basename
         self._output_directory = output_spec.output_directory
         self._header_filename = output_spec.get_header_filename()
@@ -18,12 +21,15 @@ class HeaderSourcePairEmitter:
 
     @property
     def output_files(self) -> tuple[str, str]:
+        """The files that will be written by `write_files`."""
         return (
             path.join(self._output_directory, self._header_filename),
             path.join(self._output_directory, self._impl_filename),
         )
 
     def write_files(self, ctx: SfgContext):
+        """Write the code represented by the given [SfgContext][pystencilssfg.SfgContext] to the files
+        specified by the output specification."""
         ctx = prepare_context(ctx)
 
         header_printer = SfgHeaderPrinter(ctx, self._ospec)
diff --git a/src/pystencilssfg/generator.py b/src/pystencilssfg/generator.py
index 1cd56bd..99339ff 100644
--- a/src/pystencilssfg/generator.py
+++ b/src/pystencilssfg/generator.py
@@ -5,17 +5,58 @@ import sys
 import os
 from os import path
 
-from .configuration import SfgConfiguration, config_from_commandline, merge_configurations
+from .configuration import (
+    SfgConfiguration,
+    config_from_commandline,
+    merge_configurations,
+)
 from .context import SfgContext
 
 
 class SourceFileGenerator:
-    """Context manager that controls the code generation process in generator scripts."""
+    """Context manager that controls the code generation process in generator scripts.
+
+    ## Configuration
+
+    The [source file generator][pystencilssfg.SourceFileGenerator] draws configuration from a total of four sources:
+
+     - The [default configuration][pystencilssfg.configuration.DEFAULT_CONFIG];
+     - The project configuration;
+     - Command-line arguments;
+     - The user configuration passed to the constructor of `SourceFileGenerator`.
+
+    They take precedence in the following way:
+
+     - Project configuration overrides the default configuration
+     - Command line arguments override the project configuration
+     - User configuration overrides default and project configuration,
+       and must not conflict with command-line arguments; otherwise, an error is thrown.
+
+    ### Project Configuration via Configurator Script
+
+    Currently, the only way to define the project configuration is via a configuration module.
+    A configurator module is a Python file defining the following function at the top-level:
+
+    ```Python
+    from pystencilssfg import SfgConfiguration
+
+    def sfg_config() -> SfgConfiguration:
+        # ...
+        return SfgConfiguration(
+            # ...
+        )
+    ```
+
+    The configuration module is passed to the code generation script via the command-line argument
+    `--sfg-config-module`.
+    """
+
     def __init__(self, sfg_config: SfgConfiguration | None = None):
         if sfg_config and not isinstance(sfg_config, SfgConfiguration):
             raise TypeError("sfg_config is not an SfgConfiguration.")
 
         import __main__
+
         scriptpath = __main__.__file__
         scriptname = path.split(scriptpath)[1]
         basename = path.splitext(scriptname)[0]
@@ -24,10 +65,13 @@ class SourceFileGenerator:
 
         config = merge_configurations(project_config, cmdline_config, sfg_config)
 
-        self._context = SfgContext(config.outer_namespace, config.codestyle, argv=script_args)
+        self._context = SfgContext(
+            config.outer_namespace, config.codestyle, argv=script_args
+        )
+
+        from .emission import HeaderImplPairEmitter
 
-        from .emission import HeaderSourcePairEmitter
-        self._emitter = HeaderSourcePairEmitter(config.get_output_spec(basename))
+        self._emitter = HeaderImplPairEmitter(config.get_output_spec(basename))
 
     def clean_files(self):
         for file in self._emitter.output_files:
-- 
GitLab