From fa347fe5934e9adcf0b7404f4de50cd742306f86 Mon Sep 17 00:00:00 2001
From: Frederik Hennig <frederik.hennig@fau.de>
Date: Thu, 6 Feb 2025 17:22:30 +0100
Subject: [PATCH] updating the composer WIP

---
 src/pystencilssfg/composer/basic_composer.py | 210 +++++++++------
 src/pystencilssfg/context.py                 | 261 +++++--------------
 src/pystencilssfg/ir/source_components.py    |  21 +-
 3 files changed, 215 insertions(+), 277 deletions(-)

diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py
index db671b9..c0b420f 100644
--- a/src/pystencilssfg/composer/basic_composer.py
+++ b/src/pystencilssfg/composer/basic_composer.py
@@ -6,7 +6,7 @@ import sympy as sp
 from functools import reduce
 from warnings import warn
 
-from pystencils import Field
+from pystencils import Field, CreateKernelConfig, create_kernel
 from pystencils.codegen import Kernel
 from pystencils.types import create_type, UserTypeSpec
 
@@ -31,13 +31,15 @@ from ..ir.postprocessing import (
 )
 from ..ir.source_components import (
     SfgFunction,
-    SfgHeaderInclude,
     SfgKernelNamespace,
     SfgKernelHandle,
     SfgClass,
     SfgConstructor,
     SfgMemberVariable,
     SfgClassKeyword,
+    SfgEntityDecl,
+    SfgEntityDef,
+    SfgNamespaceBlock,
 )
 from ..lang import (
     VarLike,
@@ -61,6 +63,7 @@ from ..exceptions import SfgException
 class SfgIComposer(ABC):
     def __init__(self, ctx: SfgContext):
         self._ctx = ctx
+        self._cursor = ctx.cursor
 
     @property
     def context(self):
@@ -80,6 +83,66 @@ SequencerArg: TypeAlias = tuple | ExprLike | SfgCallTreeNode | SfgNodeBuilder
 """Valid arguments to `make_sequence` and any sequencer that uses it."""
 
 
+class KernelsAdder:
+    def __init__(self, ctx: SfgContext, loc: SfgNamespaceBlock):
+        self._ctx = ctx
+        self._loc = SfgNamespaceBlock
+        assert isinstance(loc.namespace, SfgKernelNamespace)
+        self._kernel_namespace = loc.namespace
+
+    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 self._kernel_namespace.find_kernel(kernel_name) is not None:
+            raise ValueError(
+                f"Duplicate kernels: A kernel called {kernel_name} already exists "
+                f"in namespace {self._kernel_namespace.fqname}"
+            )
+
+        if name is not None:
+            kernel.name = kernel_name
+
+        khandle = SfgKernelHandle(kernel_name, self._kernel_namespace, kernel)
+        self._kernel_namespace.add_kernel(khandle)
+
+        for header in kernel.required_headers:
+            #   TODO: Find current source file by traversing namespace blocks upward?
+            self._ctx.impl_file.includes.append(HeaderFile.parse(header))
+
+        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 self._kernel_namespace.find_kernel(name) is not None:
+                raise ValueError(
+                    f"Duplicate kernels: A kernel called {name} already exists "
+                    f"in namespace {self._kernel_namespace.fqname}"
+                )
+
+            config.function_name = name
+
+        # type: ignore
+        kernel = create_kernel(assignments, config=config)
+        return self.add(kernel)
+
+
 class SfgBasicComposer(SfgIComposer):
     """Composer for basic source components, and base class for all composer mix-ins."""
 
@@ -87,7 +150,7 @@ class SfgBasicComposer(SfgIComposer):
         ctx: SfgContext = sfg if isinstance(sfg, SfgContext) else sfg.context
         super().__init__(ctx)
 
-    def prelude(self, content: str):
+    def prelude(self, content: str, end: str = "\n"):
         """Append a string to the prelude comment, to be printed at the top of both generated files.
 
         The string should not contain C/C++ comment delimiters, since these will be added automatically
@@ -105,7 +168,11 @@ class SfgBasicComposer(SfgIComposer):
                  */
 
         """
-        self._ctx.append_to_prelude(content)
+        for f in self._ctx.files:
+            if f.prelude is None:
+                f.prelude = content + end
+            else:
+                f.prelude += content + end
 
     def code(self, *code: str):
         """Add arbitrary lines of code to the generated header file.
@@ -126,7 +193,7 @@ class SfgBasicComposer(SfgIComposer):
 
         """
         for c in code:
-            self._ctx.add_definition(c)
+            self._cursor.write_header(c)
 
     def define(self, *definitions: str):
         from warnings import warn
@@ -139,34 +206,9 @@ class SfgBasicComposer(SfgIComposer):
 
         self.code(*definitions)
 
-    def define_once(self, *definitions: str):
-        """Add unique definitions to the header file.
-
-        Each code string given to `define_once` will only be added if the exact same string
-        was not already added before.
-        """
-        for definition in definitions:
-            if all(d != definition for d in self._ctx.definitions()):
-                self._ctx.add_definition(definition)
-
     def namespace(self, namespace: str):
-        """Set the inner code namespace. Throws an exception if a namespace was already set.
-
-        :Example:
-
-            After adding the following to your generator script:
-
-            >>> sfg.namespace("codegen_is_awesome")
-
-            All generated code will be placed within that namespace:
-
-            .. code-block:: C++
-
-                namespace codegen_is_awesome {
-                    /* all generated code */
-                }
-        """
-        self._ctx.set_namespace(namespace)
+        #   TODO: Enter into a new namespace context
+        raise NotImplementedError()
 
     def generate(self, generator: CustomGenerator):
         """Invoke a custom code generator with the underlying context."""
@@ -183,18 +225,16 @@ class SfgBasicComposer(SfgIComposer):
             sfg.kernels.add(ast, "kernel_name")
             sfg.kernels.create(assignments, "kernel_name", config)
         """
-        return self._ctx._default_kernel_namespace
+        return self.kernel_namespace("kernels")
 
     def kernel_namespace(self, name: str) -> SfgKernelNamespace:
         """Return the kernel namespace of the given name, creating it if it does not exist yet."""
-        kns = self._ctx.get_kernel_namespace(name)
-        if kns is None:
-            kns = SfgKernelNamespace(self._ctx, name)
-            self._ctx.add_kernel_namespace(kns)
+        #   TODO: Find the default kernel namespace as a child entity of the current
+        #   namespace, or create it if it does not exist
+        #   Then create a new namespace block, place it at the cursor position, and expose
+        #   it to the user via an adder
 
-        return kns
-
-    def include(self, header_file: str, private: bool = False):
+    def include(self, header_file: str | HeaderFile, private: bool = False):
         """Include a header file.
 
         Args:
@@ -214,7 +254,14 @@ class SfgBasicComposer(SfgIComposer):
                 #include <vector>
                 #include "custom.h"
         """
-        self._ctx.add_include(SfgHeaderInclude(HeaderFile.parse(header_file), private))
+        header_file = HeaderFile.parse(header_file)
+
+        if private:
+            if self._ctx.impl_file is None:
+                raise ValueError("Cannot emit a private include since no implementation file is being generated")
+            self._ctx.impl_file.includes.append(header_file)
+        else:
+            self._ctx.header_file.includes.append(header_file)
 
     def numpy_struct(
         self, name: str, dtype: np.dtype, add_constructor: bool = True
@@ -224,11 +271,9 @@ class SfgBasicComposer(SfgIComposer):
         Returns:
             The created class object
         """
-        if self._ctx.get_class(name) is not None:
-            raise SfgException(f"Class with name {name} already exists.")
-
-        cls = _struct_from_numpy_dtype(name, dtype, add_constructor=add_constructor)
-        self._ctx.add_class(cls)
+        cls = self._struct_from_numpy_dtype(name, dtype, add_constructor=add_constructor)
+        self._ctx.add_entity(cls)
+        self._cursor.write_header(SfgEntityDecl(cls))
         return cls
 
     def kernel_function(
@@ -281,15 +326,18 @@ class SfgBasicComposer(SfgIComposer):
             )
             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(returns), inline=inline
+                name, self._cursor.current_namespace, tree, return_type=create_type(returns), inline=inline
             )
-            self._ctx.add_function(func)
+            self._ctx.add_entity(func)
+            
+            if inline:
+                self._cursor.write_header(SfgEntityDef(func))
+            else:
+                self._cursor.write_header(SfgEntityDecl(func))
+                self._cursor.write_impl(SfgEntityDef(func))
 
         return sequencer
 
@@ -482,6 +530,36 @@ class SfgBasicComposer(SfgIComposer):
             (asvar(c) if isinstance(c, _VarLike) else c) for c in lhs_components
         ]
         return SfgDeferredVectorMapping(components, rhs)
+    
+    def _struct_from_numpy_dtype(
+        self, struct_name: str, dtype: np.dtype, add_constructor: bool = True
+    ):
+        cls = SfgClass(struct_name, self._cursor.current_namespace, class_keyword=SfgClassKeyword.STRUCT)
+
+        fields = dtype.fields
+        if fields is None:
+            raise SfgException(f"Numpy dtype {dtype} is not a structured type.")
+
+        constr_params = []
+        constr_inits = []
+
+        for member_name, type_info in fields.items():
+            member_type = create_type(type_info[0])
+
+            member = SfgMemberVariable(member_name, member_type)
+
+            arg = SfgVar(f"{member_name}_", member_type)
+
+            cls.default.append_member(member)
+
+            constr_params.append(arg)
+            constr_inits.append(f"{member}({arg})")
+
+        if add_constructor:
+            cls.default.append_member(SfgEntityDef(SfgConstructor(constr_params, constr_inits)))
+
+        return cls
+
 
 
 def make_statements(arg: ExprLike) -> SfgStatements:
@@ -628,33 +706,3 @@ class SfgSwitchBuilder(SfgNodeBuilder):
 
     def resolve(self) -> SfgCallTreeNode:
         return SfgSwitch(make_statements(self._switch_arg), self._cases, self._default)
-
-
-def _struct_from_numpy_dtype(
-    struct_name: str, dtype: np.dtype, add_constructor: bool = True
-):
-    cls = SfgClass(struct_name, class_keyword=SfgClassKeyword.STRUCT)
-
-    fields = dtype.fields
-    if fields is None:
-        raise SfgException(f"Numpy dtype {dtype} is not a structured type.")
-
-    constr_params = []
-    constr_inits = []
-
-    for member_name, type_info in fields.items():
-        member_type = create_type(type_info[0])
-
-        member = SfgMemberVariable(member_name, member_type)
-
-        arg = SfgVar(f"{member_name}_", member_type)
-
-        cls.default.append_member(member)
-
-        constr_params.append(arg)
-        constr_inits.append(f"{member}({arg})")
-
-    if add_constructor:
-        cls.default.append_member(SfgConstructor(constr_params, constr_inits))
-
-    return cls
diff --git a/src/pystencilssfg/context.py b/src/pystencilssfg/context.py
index 17537a2..577dcbd 100644
--- a/src/pystencilssfg/context.py
+++ b/src/pystencilssfg/context.py
@@ -1,83 +1,45 @@
-from typing import Generator, Sequence, Any
+from __future__ import annotations
+from typing import Sequence, Any, Generator
 
 from .config import CodeStyle
 from .ir.source_components import (
-    SfgHeaderInclude,
+    SfgSourceFile,
+    SfgNamespace,
     SfgKernelNamespace,
-    SfgFunction,
+    SfgNamespaceBlock,
+    SfgNamespaceElement,
+    SfgCodeEntity,
     SfgClass,
 )
 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`
-      and the `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` 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`, 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`.
-    """
+    """Manages context information during the execution of a generator script."""
 
     def __init__(
         self,
+        header_file: SfgSourceFile,
+        impl_file: SfgSourceFile,
         outer_namespace: str | None = None,
         codestyle: CodeStyle | None = None,
         argv: Sequence[str] | None = None,
         project_info: Any = 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].
-            project_info: Project-specific information provided by a build system.
-                Reserved for internal use by the [SourceFileGenerator][pystencilssfg.SourceFileGenerator].
-        """
         self._argv = argv
         self._project_info = project_info
-        self._default_kernel_namespace = SfgKernelNamespace(self, "kernels")
 
         self._outer_namespace = outer_namespace
         self._inner_namespace: str | None = None
 
         self._codestyle = codestyle if codestyle is not None else CodeStyle()
 
-        #   Source Components
-        self._prelude: str = ""
-        self._includes: list[SfgHeaderInclude] = []
-        self._definitions: list[str] = []
-        self._kernel_namespaces = {
-            self._default_kernel_namespace.name: self._default_kernel_namespace
-        }
-        self._functions: dict[str, SfgFunction] = dict()
-        self._classes: dict[str, SfgClass] = dict()
+        self._header_file = header_file
+        self._impl_file = impl_file
 
-        self._declarations_ordered: list[str | SfgFunction | SfgClass] = list()
+        self._entities: dict[str, SfgCodeEntity] = dict()
+
+        self._cursor: SfgCursor
 
     @property
     def argv(self) -> Sequence[str]:
@@ -100,163 +62,74 @@ class SfgContext:
         """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
-            case outer, None:
-                return outer
-            case None, inner:
-                return inner
-            case outer, inner:
-                return f"{outer}::{inner}"
-            case _:
-                assert False
-
     @property
     def codestyle(self) -> CodeStyle:
         """The code style object for this generation context."""
         return self._codestyle
 
-    # ----------------------------------------------------------------------------------------------
-    #   Prelude, Includes, Definitions, Namespace
-    # ----------------------------------------------------------------------------------------------
-
     @property
-    def prelude_comment(self) -> str:
-        """The prelude is a comment block printed at the top of both generated files."""
-        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"
-
-        self._prelude += code_str
-
-        if not code_str.endswith("\n"):
-            self._prelude += "\n"
-
-    def includes(self) -> Generator[SfgHeaderInclude, None, None]:
-        """Includes of headers. Public includes are added to the header file, private includes
-        are added to the implementation file."""
-        yield from self._includes
-
-    def add_include(self, include: SfgHeaderInclude):
-        self._includes.append(include)
-
-    def definitions(self) -> Generator[str, None, None]:
-        """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.")
-
-        self._inner_namespace = namespace
-
-    # ----------------------------------------------------------------------------------------------
-    #   Kernel Namespaces
-    # ----------------------------------------------------------------------------------------------
+    def header_file(self) -> SfgSourceFile:
+        return self._header_file
 
     @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}")
+    def impl_file(self) -> SfgSourceFile | None:
+        return self._impl_file
 
-        self._kernel_namespaces[namespace.name] = namespace
-
-    # ----------------------------------------------------------------------------------------------
-    #   Functions
-    # ----------------------------------------------------------------------------------------------
-
-    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):
-        """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}")
+    @property
+    def cursor(self) -> SfgCursor:
+        return self._cursor
 
-        self._functions[func.name] = func
-        self._declarations_ordered.append(func)
+    @property
+    def files(self) -> Generator[SfgSourceFile, None, None]:
+        yield self._header_file
+        if self._impl_file is not None:
+            yield self._impl_file
 
-    # ----------------------------------------------------------------------------------------------
-    #   Classes
-    # ----------------------------------------------------------------------------------------------
+    def get_entity(self, fqname: str) -> SfgCodeEntity | None:
+        #   TODO: Only track top-level entities here, traverse namespaces to find qualified entities
+        return self._entities.get(fqname, None)
 
-    def classes(self) -> Generator[SfgClass, None, None]:
-        """Iterator over all registered classes."""
-        yield from self._classes.values()
+    def add_entity(self, entity: SfgCodeEntity) -> None:
+        fqname = entity.fqname
+        if fqname in self._entities:
+            raise ValueError(f"Another entity with name {fqname} already exists")
+        self._entities[fqname] = entity
 
-    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):
-        """Add a class.
+class SfgCursor:
+    """Cursor that tracks the current location in the source file(s) during execution of the generator script."""
 
-        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}")
+    def __init__(self, ctx: SfgContext, namespace: str | None = None) -> None:
+        self._ctx = ctx
 
-        self._classes[cls.class_name] = cls
-        self._declarations_ordered.append(cls)
+        self._cur_namespace: SfgNamespace | None
+        if namespace is not None:
+            self._cur_namespace = ctx.get_namespace(namespace)
+        else:
+            self._cur_namespace = None
 
-    # ----------------------------------------------------------------------------------------------
-    #   Declarations in order of addition
-    # ----------------------------------------------------------------------------------------------
+        self._loc: dict[SfgSourceFile, list[SfgNamespaceElement]]
+        for f in self._ctx.files:
+            if self._cur_namespace is not None:
+                block = SfgNamespaceBlock(self._cur_namespace)
+                f.elements.append(block)
+                self._loc[f] = block.elements
+            else:
+                self._loc[f] = f.elements
 
-    def declarations_ordered(
-        self,
-    ) -> Generator[str | SfgFunction | SfgClass, None, None]:
-        """All declared definitions, classes and functions in the order they were added.
+    #   TODO: Enter and exit namespace blocks
 
-        Awareness about order is necessary due to the C++ declare-before-use rules."""
-        yield from self._declarations_ordered
+    @property
+    def current_namespace(self) -> SfgNamespace | None:
+        return self._cur_namespace
+
+    def write_header(self, elem: SfgNamespaceElement) -> None:
+        self._loc[self._ctx.header_file].append(elem)
+
+    def write_impl(self, elem: SfgNamespaceElement) -> None:
+        impl_file = self._ctx.impl_file
+        if impl_file is None:
+            raise SfgException(
+                f"Cannot write element {elem} to implemenation file since no implementation file is being generated."
+            )
+        self._loc[impl_file].append(elem)
diff --git a/src/pystencilssfg/ir/source_components.py b/src/pystencilssfg/ir/source_components.py
index d98e2d8..d8fb0f5 100644
--- a/src/pystencilssfg/ir/source_components.py
+++ b/src/pystencilssfg/ir/source_components.py
@@ -73,6 +73,8 @@ class SfgNamespace(SfgCodeEntity):
         parent: Parent namespace enclosing this namespace
     """
 
+    #   TODO: Namespaces must keep track of their child entities
+
 
 class SfgKernelHandle(SfgCodeEntity):
     """Handle to a pystencils kernel."""
@@ -127,6 +129,17 @@ class SfgKernelNamespace(SfgNamespace):
     def kernels(self) -> tuple[SfgKernelHandle, ...]:
         return tuple(self._kernels.values())
 
+    def find_kernel(self, name: str) -> SfgKernelHandle | None:
+        return self._kernels.get(name, None)
+
+    def add_kernel(self, kernel: SfgKernelHandle):
+        if kernel.name in self._kernels:
+            raise ValueError(
+                f"Duplicate kernels: A kernel called {kernel.name} already exists "
+                f"in namespace {self.fqname}"
+            )
+        self._kernels[kernel.name] = kernel
+
     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."""
@@ -623,7 +636,7 @@ class SfgVisibilityBlock:
         self._cls = cls
 
 
-class SfgNamespaceDef:
+class SfgNamespaceBlock:
     """A C++ namespace.
 
     Each namespace has a `name` and a `parent`; its fully qualified name is given as
@@ -638,6 +651,10 @@ class SfgNamespaceDef:
         self._namespace = namespace
         self._elements: list[SfgNamespaceElement] = []
 
+    @property
+    def namespace(self) -> SfgNamespace:
+        return self._namespace
+
     @property
     def elements(self) -> list[SfgNamespaceElement]:
         """Sequence of source elements that make up the body of this namespace"""
@@ -648,7 +665,7 @@ class SfgNamespaceDef:
         self._elements = list(elems)
 
 
-SfgNamespaceElement = str | SfgNamespaceDef | SfgEntityDecl | SfgEntityDef
+SfgNamespaceElement = str | SfgNamespaceBlock | SfgEntityDecl | SfgEntityDef
 """Elements that may be placed inside a namespace, including the global namespace."""
 
 
-- 
GitLab