diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py
index b96d559a1733ec69b6f40db04f41437cc80e3ad7..db671b9abeecd2175e05446041fb843e2c547501 100644
--- a/src/pystencilssfg/composer/basic_composer.py
+++ b/src/pystencilssfg/composer/basic_composer.py
@@ -4,6 +4,7 @@ from abc import ABC, abstractmethod
 import numpy as np
 import sympy as sp
 from functools import reduce
+from warnings import warn
 
 from pystencils import Field
 from pystencils.codegen import Kernel
@@ -252,7 +253,13 @@ class SfgBasicComposer(SfgIComposer):
         func = SfgFunction(name, tree)
         self._ctx.add_function(func)
 
-    def function(self, name: str, return_type: UserTypeSpec = void):
+    def function(
+        self,
+        name: str,
+        returns: UserTypeSpec = void,
+        inline: bool = False,
+        return_type: UserTypeSpec | None = None,
+    ):
         """Add a function.
 
         The syntax of this function adder uses a chain of two calls to mimic C++ syntax:
@@ -265,12 +272,23 @@ class SfgBasicComposer(SfgIComposer):
 
         The function body is constructed via sequencing (see `make_sequence`).
         """
+        if return_type is not None:
+            warn(
+                "The parameter `return_type` to `function()` is deprecated and will be removed by version 0.1. "
+                "Setting it will override the value of the `returns` parameter. "
+                "Use `returns` instead.",
+                FutureWarning,
+            )
+            returns = return_type
+
         if self._ctx.get_function(name) is not None:
             raise ValueError(f"Function {name} already exists.")
 
         def sequencer(*args: SequencerArg):
             tree = make_sequence(*args)
-            func = SfgFunction(name, tree, return_type=create_type(return_type))
+            func = SfgFunction(
+                name, tree, return_type=create_type(returns), inline=inline
+            )
             self._ctx.add_function(func)
 
         return sequencer
diff --git a/src/pystencilssfg/ir/postprocessing.py b/src/pystencilssfg/ir/postprocessing.py
index aa3cd2732f62f5b9b50131b4e1ae1b48aa23e4ce..db26a38ba2ffd189edb91156e6f0dc95cf29c616 100644
--- a/src/pystencilssfg/ir/postprocessing.py
+++ b/src/pystencilssfg/ir/postprocessing.py
@@ -29,7 +29,6 @@ from ..lang import (
 
 if TYPE_CHECKING:
     from ..context import SfgContext
-    from .source_components import SfgClass
 
 
 class FlattenSequences:
@@ -65,19 +64,9 @@ class FlattenSequences:
 
 
 class PostProcessingContext:
-    def __init__(self, enclosing_class: SfgClass | None = None) -> None:
-        self.enclosing_class: SfgClass | None = enclosing_class
+    def __init__(self) -> None:
         self._live_variables: dict[str, SfgVar] = dict()
 
-    def is_method(self) -> bool:
-        return self.enclosing_class is not None
-
-    def get_enclosing_class(self) -> SfgClass:
-        if self.enclosing_class is None:
-            raise SfgException("Cannot get the enclosing class of a free function.")
-
-        return self.enclosing_class
-
     @property
     def live_variables(self) -> set[SfgVar]:
         return set(self._live_variables.values())
@@ -144,8 +133,7 @@ class PostProcessingResult:
 
 
 class CallTreePostProcessing:
-    def __init__(self, enclosing_class: SfgClass | None = None):
-        self._enclosing_class = enclosing_class
+    def __init__(self):
         self._flattener = FlattenSequences()
 
     def __call__(self, ast: SfgCallTreeNode) -> PostProcessingResult:
@@ -174,7 +162,7 @@ class CallTreePostProcessing:
     def get_live_variables(self, node: SfgCallTreeNode) -> set[SfgVar]:
         match node:
             case SfgSequence():
-                ppc = self._ppc()
+                ppc = PostProcessingContext()
                 self.handle_sequence(node, ppc)
                 return ppc.live_variables
 
@@ -191,9 +179,6 @@ class CallTreePostProcessing:
                     set(),
                 )
 
-    def _ppc(self) -> PostProcessingContext:
-        return PostProcessingContext(enclosing_class=self._enclosing_class)
-
 
 class SfgDeferredNode(SfgCallTreeNode, ABC):
     """Nodes of this type are inserted as placeholders into the kernel call tree
diff --git a/src/pystencilssfg/ir/source_components.py b/src/pystencilssfg/ir/source_components.py
index ea43ac8e06cd7520c75eb266c8ff9008ca7132a0..07ee848efccf3afd64a62a26db5c5d00eafa4218 100644
--- a/src/pystencilssfg/ir/source_components.py
+++ b/src/pystencilssfg/ir/source_components.py
@@ -2,149 +2,74 @@ from __future__ import annotations
 
 from abc import ABC
 from enum import Enum, auto
-from typing import TYPE_CHECKING, Sequence, Generator, TypeVar
+from typing import (
+    TYPE_CHECKING,
+    Sequence,
+    Generator,
+    Iterable,
+    TypeVar,
+    Generic,
+)
 from dataclasses import replace
 from itertools import chain
 
 from pystencils import CreateKernelConfig, create_kernel, Field
-from pystencils.codegen import Kernel, Parameter
+from pystencils.codegen import Kernel
 from pystencils.types import PsType, PsCustomType
 
-from ..lang import SfgVar, HeaderFile, void
+from ..lang import SfgVar, SfgKernelParamVar, HeaderFile, void
 from ..exceptions import SfgException
 
 if TYPE_CHECKING:
     from . import SfgCallTreeNode
-    from ..context import SfgContext
 
 
-class SfgEmptyLines:
-    def __init__(self, lines: int):
-        self._lines = lines
+#   =========================================================================================================
+#
+#   SEMANTICAL ENTITIES
+#
+#   These classes model *code entities*, which represent *semantic components* of the generated files.
+#
+#   =========================================================================================================
 
-    @property
-    def lines(self) -> int:
-        return self._lines
-
-
-class SfgHeaderInclude:
-    """Represent ``#include``-directives."""
-
-    def __init__(
-        self, header_file: HeaderFile, private: bool = False
-    ):
-        self._header_file = header_file
-        self._private = private
-
-    @property
-    def file(self) -> str:
-        return self._header_file.filepath
-
-    @property
-    def system_header(self):
-        return self._header_file.system_header
-
-    @property
-    def private(self):
-        return self._private
-
-    def __hash__(self) -> int:
-        return hash((self._header_file, self._private))
-
-    def __eq__(self, other: object) -> bool:
-        return (
-            isinstance(other, SfgHeaderInclude)
-            and self._header_file == other._header_file
-            and self._private == other._private
-        )
 
+class SfgCodeEntity:
+    """Base class for code entities.
 
-class SfgKernelNamespace:
-    """A namespace grouping together a number of kernels."""
+    Each code entity has a name and an optional enclosing namespace.
+    """
 
-    def __init__(self, ctx: SfgContext, name: str):
-        self._ctx = ctx
+    def __init__(self, name: str, namespace: SfgNamespace | None) -> None:
         self._name = name
-        self._kernel_functions: dict[str, Kernel] = dict()
+        self._namespace: SfgNamespace | None = namespace
 
     @property
-    def name(self):
+    def name(self) -> str:
+        """Name of this entity"""
         return self._name
 
     @property
-    def kernel_functions(self):
-        yield from self._kernel_functions.values()
-
-    def get_kernel_function(self, khandle: SfgKernelHandle) -> Kernel:
-        if khandle.kernel_namespace is not self:
-            raise ValueError(
-                f"Kernel handle does not belong to this namespace: {khandle}"
-            )
-
-        return self._kernel_functions[khandle.kernel_name]
-
-    def add(self, kernel: Kernel, name: str | None = None):
-        """Adds an existing pystencils AST to this namespace.
-        If a name is specified, the AST's function name is changed."""
-        if name is not None:
-            astname = name
+    def fqname(self) -> str:
+        """Fully qualified name of this entity"""
+        if self._namespace is not None:
+            return self._namespace.fqname + "::" + self._name
         else:
-            astname = kernel.name
+            return self._name
 
-        if astname in self._kernel_functions:
-            raise ValueError(
-                f"Duplicate ASTs: An AST with name {astname} already exists in namespace {self._name}"
-            )
-
-        if name is not None:
-            kernel.name = name
-
-        self._kernel_functions[astname] = kernel
-
-        for header in kernel.required_headers:
-            self._ctx.add_include(SfgHeaderInclude(HeaderFile.parse(header), private=True))
-
-        return SfgKernelHandle(self._ctx, astname, self, kernel.parameters)
-
-    def create(
-        self,
-        assignments,
-        name: str | None = None,
-        config: CreateKernelConfig | None = None,
-    ):
-        """Creates a new pystencils kernel from a list of assignments and a configuration.
-        This is a wrapper around `pystencils.create_kernel`
-        with a subsequent call to `add`.
-        """
-        if config is None:
-            config = CreateKernelConfig()
+    @property
+    def namespace(self) -> SfgNamespace | None:
+        """Parent namespace of this entity"""
+        return self._namespace
 
-        if name is not None:
-            if name in self._kernel_functions:
-                raise ValueError(
-                    f"Duplicate ASTs: An AST with name {name} already exists in namespace {self._name}"
-                )
-            config = replace(config, function_name=name)
 
-        # type: ignore
-        ast = create_kernel(assignments, config=config)
-        return self.add(ast)
+class SfgKernelHandle(SfgCodeEntity):
+    """Handle to a pystencils kernel."""
 
+    def __init__(self, name: str, namespace: SfgKernelNamespace, kernel: Kernel):
+        super().__init__(name, namespace)
 
-class SfgKernelHandle:
-    """A handle that represents a pystencils kernel within a kernel namespace."""
-
-    def __init__(
-        self,
-        ctx: SfgContext,
-        name: str,
-        namespace: SfgKernelNamespace,
-        parameters: Sequence[Parameter],
-    ):
-        self._ctx = ctx
-        self._name = name
-        self._namespace = namespace
-        self._parameters = [SfgKernelParamVar(p) for p in parameters]
+        self._kernel = kernel
+        self._parameters = [SfgKernelParamVar(p) for p in kernel.parameters]
 
         self._scalar_params: set[SfgVar] = set()
         self._fields: set[Field] = set()
@@ -155,82 +80,49 @@ class SfgKernelHandle:
             else:
                 self._scalar_params.add(param)
 
-    @property
-    def kernel_name(self):
-        return self._name
-
-    @property
-    def kernel_namespace(self):
-        return self._namespace
-
-    @property
-    def fully_qualified_name(self):
-        match self._ctx.fully_qualified_namespace:
-            case None:
-                return f"{self.kernel_namespace.name}::{self.kernel_name}"
-            case fqn:
-                return f"{fqn}::{self.kernel_namespace.name}::{self.kernel_name}"
-
     @property
     def parameters(self) -> Sequence[SfgKernelParamVar]:
+        """Parameters to this kernel"""
         return self._parameters
 
     @property
     def scalar_parameters(self) -> set[SfgVar]:
+        """Scalar parameters to this kernel"""
         return self._scalar_params
 
     @property
     def fields(self):
+        """Fields accessed by this kernel"""
         return self._fields
 
-    def get_kernel_function(self) -> Kernel:
-        return self._namespace.get_kernel_function(self)
-
-
-SymbolLike_T = TypeVar("SymbolLike_T", bound=Parameter)
-
+    def get_kernel(self) -> Kernel:
+        """Underlying pystencils kernel object"""
+        return self._kernel
 
-class SfgKernelParamVar(SfgVar):
-    __match_args__ = ("wrapped",)
 
-    """Cast pystencils- or SymPy-native symbol-like objects as a `SfgVar`."""
-
-    def __init__(self, param: Parameter):
-        self._param = param
-        super().__init__(param.name, param.dtype)
-
-    @property
-    def wrapped(self) -> Parameter:
-        return self._param
-
-    def _args(self):
-        return (self._param,)
-
-
-class SfgFunction:
+class SfgFunction(SfgCodeEntity):
     __match_args__ = ("name", "tree", "parameters")
 
     def __init__(
         self,
         name: str,
+        namespace: SfgNamespace | None,
         tree: SfgCallTreeNode,
         return_type: PsType = void,
-        _is_method: bool = False,
+        inline: bool = False,
     ):
-        self._name = name
+        super().__init__(name, namespace)
+
         self._tree = tree
         self._return_type = return_type
+        self._inline = inline
 
         self._parameters: set[SfgVar]
-        if not _is_method:
-            from .postprocessing import CallTreePostProcessing
 
-            param_collector = CallTreePostProcessing()
-            self._parameters = param_collector(self._tree).function_params
+        from .postprocessing import CallTreePostProcessing
 
-    @property
-    def name(self) -> str:
-        return self._name
+        param_collector = CallTreePostProcessing()
+        self._parameters = param_collector(self._tree).function_params
 
     @property
     def parameters(self) -> set[SfgVar]:
@@ -244,6 +136,10 @@ class SfgFunction:
     def return_type(self) -> PsType:
         return self._return_type
 
+    @property
+    def inline(self) -> bool:
+        return self._inline
+
 
 class SfgVisibility(Enum):
     DEFAULT = auto()
@@ -308,50 +204,6 @@ class SfgClassMember(ABC):
         self._vis = vis
 
 
-class SfgVisibilityBlock:
-    def __init__(self, visibility: SfgVisibility) -> None:
-        self._vis = visibility
-        self._members: list[SfgClassMember] = []
-        self._cls: SfgClass | None = None
-
-    @property
-    def visibility(self) -> SfgVisibility:
-        return self._vis
-
-    def append_member(self, member: SfgClassMember):
-        if self._cls is not None:
-            self._cls._add_member(member, self._vis)
-        self._members.append(member)
-
-    def members(self) -> Generator[SfgClassMember, None, None]:
-        yield from self._members
-
-    @property
-    def is_bound(self) -> bool:
-        return self._cls is not None
-
-    def _bind(self, cls: SfgClass):
-        if self._cls is not None:
-            raise SfgException(
-                f"Binding visibility block to class {cls.class_name} failed: "
-                f"was already bound to {self._cls.class_name}"
-            )
-        self._cls = cls
-
-
-class SfgInClassDefinition(SfgClassMember):
-    def __init__(self, text: str):
-        SfgClassMember.__init__(self)
-        self._text = text
-
-    @property
-    def text(self) -> str:
-        return self._text
-
-    def __str__(self) -> str:
-        return self._text
-
-
 class SfgMemberVariable(SfgVar, SfgClassMember):
     def __init__(self, name: str, dtype: PsType):
         SfgVar.__init__(self, name, dtype)
@@ -367,29 +219,16 @@ class SfgMethod(SfgFunction, SfgClassMember):
         inline: bool = False,
         const: bool = False,
     ):
-        SfgFunction.__init__(self, name, tree, return_type=return_type, _is_method=True)
+        SfgFunction.__init__(self, name, tree, return_type=return_type, inline=inline)
         SfgClassMember.__init__(self)
 
-        self._inline = inline
         self._const = const
         self._parameters: set[SfgVar] = set()
 
-    @property
-    def inline(self) -> bool:
-        return self._inline
-
     @property
     def const(self) -> bool:
         return self._const
 
-    def _bind(self, cls: SfgClass, vis: SfgVisibility):
-        super()._bind(cls, vis)
-
-        from .postprocessing import CallTreePostProcessing
-
-        param_collector = CallTreePostProcessing(enclosing_class=cls)
-        self._parameters = param_collector(self._tree).function_params
-
 
 class SfgConstructor(SfgClassMember):
     __match_args__ = ("parameters", "initializers", "body")
@@ -418,7 +257,7 @@ class SfgConstructor(SfgClassMember):
         return self._body
 
 
-class SfgClass:
+class SfgClass(SfgCodeEntity):
     """Models a C++ class.
 
     ### Adding members to classes
@@ -430,23 +269,22 @@ class SfgClass:
     accessible through the `default` property.
     To add members with custom visibility, create a new SfgVisibilityBlock,
     add members to the block, and add the block using `append_visibility_block`.
-
-    A more succinct interface for constructing classes is available through the
-    [SfgClassComposer][pystencilssfg.composer.SfgClassComposer].
     """
 
     __match_args__ = ("class_name",)
 
     def __init__(
         self,
-        class_name: str,
+        name: str,
+        namespace: SfgNamespace | None,
         class_keyword: SfgClassKeyword = SfgClassKeyword.CLASS,
         bases: Sequence[str] = (),
     ):
         if isinstance(bases, str):
             raise ValueError("Base classes must be given as a sequence.")
 
-        self._class_name = class_name
+        super().__init__(name, namespace)
+
         self._class_keyword = class_keyword
         self._bases_classes = tuple(bases)
 
@@ -454,18 +292,14 @@ class SfgClass:
         self._default_block._bind(self)
         self._blocks = [self._default_block]
 
-        self._definitions: list[SfgInClassDefinition] = []
         self._constructors: list[SfgConstructor] = []
         self._methods: list[SfgMethod] = []
         self._member_vars: dict[str, SfgMemberVariable] = dict()
 
-    @property
-    def class_name(self) -> str:
-        return self._class_name
-
     @property
     def src_type(self) -> PsType:
-        return PsCustomType(self._class_name)
+        #   TODO: Use CppTypeFactory instead
+        return PsCustomType(self._name)
 
     @property
     def base_classes(self) -> tuple[str, ...]:
@@ -504,14 +338,6 @@ class SfgClass:
                 for b in filter(lambda b: b.visibility == visibility, self._blocks)
             )
 
-    def definitions(
-        self, visibility: SfgVisibility | None = None
-    ) -> Generator[SfgInClassDefinition, None, None]:
-        if visibility is not None:
-            yield from filter(lambda m: m.visibility == visibility, self._definitions)
-        else:
-            yield from self._definitions
-
     def member_variables(
         self, visibility: SfgVisibility | None = None
     ) -> Generator[SfgMemberVariable, None, None]:
@@ -547,16 +373,11 @@ class SfgClass:
             self._add_member_variable(member)
         elif isinstance(member, SfgMethod):
             self._add_method(member)
-        elif isinstance(member, SfgInClassDefinition):
-            self._add_definition(member)
         else:
             raise SfgException(f"{member} is not a valid class member.")
 
         member._bind(self, vis)
 
-    def _add_definition(self, definition: SfgInClassDefinition):
-        self._definitions.append(definition)
-
     def _add_constructor(self, constr: SfgConstructor):
         self._constructors.append(constr)
 
@@ -566,7 +387,288 @@ class SfgClass:
     def _add_member_variable(self, variable: SfgMemberVariable):
         if variable.name in self._member_vars:
             raise SfgException(
-                f"Duplicate field name {variable.name} in class {self._class_name}"
+                f"Duplicate field name {variable.name} in class {self._name}"
             )
 
         self._member_vars[variable.name] = variable
+
+
+SourceEntity_T = TypeVar(
+    "SourceEntity_T", bound=SfgFunction | SfgClassMember | SfgClass, covariant=True
+)
+"""Source entities that may have declarations and definitions."""
+
+
+#   =========================================================================================================
+#
+#   SYNTACTICAL ELEMENTS
+#
+#   These classes model *code elements*, which represent the actual syntax objects that populate the output
+#   files, their namespaces and class bodies.
+#
+#   =========================================================================================================
+
+
+class SfgEntityDecl(Generic[SourceEntity_T]):
+    """Declaration of a function, class, method, or constructor"""
+
+    __match_args__ = ("entity",)
+
+    def __init__(self, entity: SourceEntity_T) -> None:
+        self._entity = entity
+
+    @property
+    def entity(self) -> SourceEntity_T:
+        return self._entity
+
+
+class SfgEntityDef(Generic[SourceEntity_T]):
+    """Definition of a function, class, method, or constructor"""
+
+    __match_args__ = ("entity",)
+
+    def __init__(self, entity: SourceEntity_T) -> None:
+        self._entity = entity
+
+    @property
+    def entity(self) -> SourceEntity_T:
+        return self._entity
+
+
+SfgClassBodyElement = (
+    str
+    | SfgEntityDecl[SfgClassMember]
+    | SfgEntityDef[SfgClassMember]
+    | SfgMemberVariable
+)
+"""Elements that may be placed in the visibility blocks of a class body."""
+
+
+class SfgVisibilityBlock:
+    """Visibility-qualified block inside a class definition body.
+
+    Visibility blocks host the code elements placed inside a class body:
+    method and constructor declarations,
+    in-class method and constructor definitions,
+    as well as variable declarations and definitions.
+
+    Args:
+        visibility: The visibility qualifier of this block
+    """
+
+    def __init__(self, visibility: SfgVisibility) -> None:
+        self._vis = visibility
+        self._elements: list[SfgClassBodyElement] = []
+        self._cls: SfgClass | None = None
+
+    @property
+    def visibility(self) -> SfgVisibility:
+        return self._vis
+
+    def append_member(self, element: SfgClassBodyElement):
+        if isinstance(element, (SfgEntityDecl, SfgEntityDef)):
+            member = element.entity
+        elif isinstance(element, SfgClassMember):
+            member = element
+        else:
+            member = None
+
+        if self._cls is not None and member is not None:
+            self._cls._add_member(member, self._vis)
+
+        self._elements.append(element)
+
+    @property
+    def elements(self) -> tuple[SfgClassBodyElement, ...]:
+        return tuple(self._elements)
+
+    def members(self) -> Generator[SfgClassMember, None, None]:
+        for elem in self._elements:
+            match elem:
+                case SfgEntityDecl(entity) | SfgEntityDef(entity):
+                    yield entity
+                case SfgMemberVariable():
+                    yield elem
+
+    @property
+    def is_bound(self) -> bool:
+        return self._cls is not None
+
+    def _bind(self, cls: SfgClass):
+        if self._cls is not None:
+            raise SfgException(
+                f"Binding visibility block to class {cls.class_name} failed: "
+                f"was already bound to {self._cls.class_name}"
+            )
+        self._cls = cls
+
+
+class SfgNamespace:
+    """A C++ namespace.
+
+    Each namespace has a `name` and a `parent`; its fully qualified name is given as
+    ``<parent.name>::<name>``.
+
+    Args:
+        name: Local name of this namespace
+        parent: Parent namespace enclosing this namespace
+    """
+
+    def __init__(self, name: str, parent: SfgNamespace | None) -> None:
+        self._name: str = name
+        self._parent: SfgNamespace | None = parent
+        self._elements: list[SfgNamespaceElement] = []
+
+    @property
+    def name(self) -> str:
+        """The name of this namespace"""
+        return self._name
+
+    @property
+    def fqname(self) -> str:
+        """The fully qualified name of this namespace"""
+        if self._parent is not None:
+            return self._parent.fqname + "::" + self._name
+        else:
+            return self._name
+
+    @property
+    def elements(self) -> list[SfgNamespaceElement]:
+        """Sequence of source elements that make up the body of this namespace"""
+        return self._elements
+
+    @elements.setter
+    def elements(self, elems: Iterable[SfgNamespaceElement]):
+        self._elements = list(elems)
+
+
+class SfgKernelNamespace(SfgNamespace):
+    """A namespace grouping together a number of kernels."""
+
+    def __init__(self, name: str, parent: SfgNamespace | None):
+        super().__init__(name, parent)
+        self._kernels: dict[str, SfgKernelHandle] = []
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def kernels(self) -> tuple[SfgKernelHandle, ...]:
+        return tuple(self._kernels.values())
+
+    def add(self, kernel: Kernel, name: str | None = None):
+        """Adds an existing pystencils AST to this namespace.
+        If a name is specified, the AST's function name is changed."""
+        if name is None:
+            kernel_name = kernel.name
+        else:
+            kernel_name = name
+
+        if kernel_name in self._kernels:
+            raise ValueError(
+                f"Duplicate kernels: A kernel called {kernel_name} already exists in namespace {self.fqname}"
+            )
+
+        if name is not None:
+            kernel.name = kernel_name
+
+        khandle = SfgKernelHandle(kernel_name, self, kernel)
+        self._kernels[kernel_name] = khandle
+
+        #   TODO: collect includes later
+        # for header in kernel.required_headers:
+        #     self._ctx.add_include(
+        #         SfgHeaderInclude(HeaderFile.parse(header), private=True)
+        #     )
+
+        return khandle
+
+    def create(
+        self,
+        assignments,
+        name: str | None = None,
+        config: CreateKernelConfig | None = None,
+    ):
+        """Creates a new pystencils kernel from a list of assignments and a configuration.
+        This is a wrapper around `pystencils.create_kernel`
+        with a subsequent call to `add`.
+        """
+        if config is None:
+            config = CreateKernelConfig()
+
+        if name is not None:
+            if name in self._kernels:
+                raise ValueError(
+                    f"Duplicate kernels: A kernel with name {name} already exists in namespace {self.fqname}"
+                )
+            config = replace(config, function_name=name)
+
+        # type: ignore
+        kernel = create_kernel(assignments, config=config)
+        return self.add(kernel)
+
+
+SfgNamespaceElement = str | SfgNamespace | SfgEntityDecl | SfgEntityDef
+"""Elements that may be placed inside a namespace, including the global namespace."""
+
+
+class SfgSourceFileType(Enum):
+    HEADER = auto()
+    TRANSLATION_UNIT = auto()
+
+
+class SfgSourceFile:
+    """A C++ source file.
+
+    Args:
+        name: Name of the file (without parent directories), e.g. ``Algorithms.cpp``
+        file_type: Type of the source file (header or translation unit)
+        prelude: Optionally, text of the prelude comment printed at the top of the file
+    """
+
+    def __init__(
+        self, name: str, file_type: SfgSourceFileType, prelude: str | None = None
+    ) -> None:
+        self._name: str = name
+        self._file_type: SfgSourceFileType = file_type
+        self._prelude: str | None = prelude
+        self._includes: list[HeaderFile] = []
+        self._elements: list[SfgNamespaceElement] = []
+
+    @property
+    def name(self) -> str:
+        """Name of this source file"""
+        return self._name
+
+    @property
+    def file_type(self) -> SfgSourceFileType:
+        """File type of this source file"""
+        return self._file_type
+
+    @property
+    def prelude(self) -> str | None:
+        """Text of the prelude comment"""
+        return self._prelude
+
+    @prelude.setter
+    def prelude(self, text: str | None):
+        self._prelude = text
+
+    @property
+    def includes(self) -> list[HeaderFile]:
+        """Sequence of header files to be included at the top of this file"""
+        return self._includes
+
+    @includes.setter
+    def includes(self, incl: Iterable[HeaderFile]):
+        self._includes = list(incl)
+
+    @property
+    def elements(self) -> list[SfgNamespaceElement]:
+        """Sequence of source elements comprising the body of this file"""
+        return self._elements
+
+    @elements.setter
+    def elements(self, elems: Iterable[SfgNamespaceElement]):
+        self._elements = list(elems)
diff --git a/src/pystencilssfg/lang/__init__.py b/src/pystencilssfg/lang/__init__.py
index 9218ec2b7d7f94517e35a2c9a8e4e4ddaa7c3a2a..a8de86be10ce44c2ac2d49cc3b5fba0e1549de50 100644
--- a/src/pystencilssfg/lang/__init__.py
+++ b/src/pystencilssfg/lang/__init__.py
@@ -2,6 +2,7 @@ from .headers import HeaderFile
 
 from .expressions import (
     SfgVar,
+    SfgKernelParamVar,
     AugExpr,
     VarLike,
     _VarLike,
@@ -21,6 +22,7 @@ from .types import cpptype, void, Ref, strip_ptr_ref
 __all__ = [
     "HeaderFile",
     "SfgVar",
+    "SfgKernelParamVar",
     "AugExpr",
     "VarLike",
     "_VarLike",
diff --git a/src/pystencilssfg/lang/expressions.py b/src/pystencilssfg/lang/expressions.py
index f86140ee7a3775caab19f69c34ef97822975b95e..4a1f7e9e7aa49d0534f4d27e3b038eb7c72d3c25 100644
--- a/src/pystencilssfg/lang/expressions.py
+++ b/src/pystencilssfg/lang/expressions.py
@@ -6,6 +6,7 @@ from abc import ABC, abstractmethod
 import sympy as sp
 
 from pystencils import TypedSymbol
+from pystencils.codegen import Parameter
 from pystencils.types import PsType, UserTypeSpec, create_type
 
 from ..exceptions import SfgException
@@ -74,6 +75,23 @@ class SfgVar:
         return self.name_and_type()
 
 
+class SfgKernelParamVar(SfgVar):
+    __match_args__ = ("wrapped",)
+
+    """Cast pystencils- or SymPy-native symbol-like objects as a `SfgVar`."""
+
+    def __init__(self, param: Parameter):
+        self._param = param
+        super().__init__(param.name, param.dtype)
+
+    @property
+    def wrapped(self) -> Parameter:
+        return self._param
+
+    def _args(self):
+        return (self._param,)
+
+
 class DependentExpression:
     """Wrapper around a C++ expression code string,
     annotated with a set of variables and a set of header files this expression depends on.