From bd8b02d7dce8f49a58165629e3ff00ab9f78cfd9 Mon Sep 17 00:00:00 2001
From: Frederik Hennig <frederik.hennig@fau.de>
Date: Thu, 9 Nov 2023 10:55:51 +0900
Subject: [PATCH] renamed package. Started building test case for mdspans.

---
 .gitignore                                    |  6 +-
 .../emitters/cpu/templates/BasicCpu.tmpl.h    | 11 ---
 pystencils_sfg/source_concepts/containers.py  | 20 ------
 .../source_concepts/cpp/std_mdspan.py         | 17 -----
 .../source_concepts/source_concepts.py        | 27 -------
 pystencils_sfg/tree/__init__.py               |  7 --
 {pystencils_sfg => pystencilssfg}/__init__.py |  2 +-
 {pystencils_sfg => pystencilssfg}/context.py  | 70 ++++++++++++-------
 .../emitters/__init__.py                      |  0
 .../emitters/cpu/__init__.py                  |  0
 .../emitters/cpu/basic_cpu.py                 | 19 +++--
 .../emitters/cpu/jinja_filters.py             |  3 +-
 .../emitters/cpu/templates/BasicCpu.tmpl.cpp  |  2 -
 .../emitters/cpu/templates/BasicCpu.tmpl.h    | 18 +++++
 .../kernel_namespace.py                       |  3 +
 .../source_components.py                      |  2 +-
 .../source_concepts/__init__.py               |  0
 pystencilssfg/source_concepts/containers.py   | 36 ++++++++++
 .../source_concepts/cpp/std_mdspan.py         | 43 ++++++++++++
 .../source_concepts/source_concepts.py        | 43 ++++++++++++
 pystencilssfg/tree/__init__.py                |  9 +++
 .../tree/basic_nodes.py                       | 70 ++++++++++++-------
 .../tree/builders.py                          | 59 ++++++----------
 .../tree/conditional.py                       |  0
 .../tree/visitors.py                          |  4 +-
 tests/TestSequencing.py                       |  2 +-
 tests/mdspan/Makefile                         | 34 +++++++++
 tests/mdspan/kernels.py                       |  8 +++
 tests/mdspan/main.cpp                         |  6 ++
 29 files changed, 337 insertions(+), 184 deletions(-)
 delete mode 100644 pystencils_sfg/emitters/cpu/templates/BasicCpu.tmpl.h
 delete mode 100644 pystencils_sfg/source_concepts/containers.py
 delete mode 100644 pystencils_sfg/source_concepts/cpp/std_mdspan.py
 delete mode 100644 pystencils_sfg/source_concepts/source_concepts.py
 delete mode 100644 pystencils_sfg/tree/__init__.py
 rename {pystencils_sfg => pystencilssfg}/__init__.py (99%)
 rename {pystencils_sfg => pystencilssfg}/context.py (65%)
 rename {pystencils_sfg => pystencilssfg}/emitters/__init__.py (100%)
 rename {pystencils_sfg => pystencilssfg}/emitters/cpu/__init__.py (100%)
 rename {pystencils_sfg => pystencilssfg}/emitters/cpu/basic_cpu.py (57%)
 rename {pystencils_sfg => pystencilssfg}/emitters/cpu/jinja_filters.py (88%)
 rename {pystencils_sfg => pystencilssfg}/emitters/cpu/templates/BasicCpu.tmpl.cpp (99%)
 create mode 100644 pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.h
 rename {pystencils_sfg => pystencilssfg}/kernel_namespace.py (98%)
 rename {pystencils_sfg => pystencilssfg}/source_components.py (95%)
 rename {pystencils_sfg => pystencilssfg}/source_concepts/__init__.py (100%)
 create mode 100644 pystencilssfg/source_concepts/containers.py
 create mode 100644 pystencilssfg/source_concepts/cpp/std_mdspan.py
 create mode 100644 pystencilssfg/source_concepts/source_concepts.py
 create mode 100644 pystencilssfg/tree/__init__.py
 rename {pystencils_sfg => pystencilssfg}/tree/basic_nodes.py (53%)
 rename {pystencils_sfg => pystencilssfg}/tree/builders.py (54%)
 rename {pystencils_sfg => pystencilssfg}/tree/conditional.py (100%)
 rename {pystencils_sfg => pystencilssfg}/tree/visitors.py (96%)
 create mode 100644 tests/mdspan/Makefile
 create mode 100644 tests/mdspan/kernels.py
 create mode 100644 tests/mdspan/main.cpp

diff --git a/.gitignore b/.gitignore
index 136ac3c..0788a35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,6 @@
 **/.vscode
-**/__pycache__
\ No newline at end of file
+**/__pycache__
+**/bin
+**/obj
+**/out
+**/generated_src
\ No newline at end of file
diff --git a/pystencils_sfg/emitters/cpu/templates/BasicCpu.tmpl.h b/pystencils_sfg/emitters/cpu/templates/BasicCpu.tmpl.h
deleted file mode 100644
index 9a496ff..0000000
--- a/pystencils_sfg/emitters/cpu/templates/BasicCpu.tmpl.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-#define RESTRICT __restrict__
-
-#include <cstdint>
-
-
-namespace {{root_namespace}} {
-
-
-} // namespace {{root_namespace}}
diff --git a/pystencils_sfg/source_concepts/containers.py b/pystencils_sfg/source_concepts/containers.py
deleted file mode 100644
index 7c6f80c..0000000
--- a/pystencils_sfg/source_concepts/containers.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from abc import ABC, abstractmethod
-
-from .source_concepts import SrcObject, SrcMemberAccess
-
-class SrcContiguousContainer(SrcObject):
-    def __init__(self, src_type, identifier: Optional[str]):
-        super().__init__(src_type, identifier)
-
-    @abstractmethod
-    def ptr(self) -> SrcMemberAccess:
-        pass
-
-    @abstractmethod
-    def size(self, dimension: int) -> SrcMemberAccess:
-        pass
-
-    @abstractmethod
-    def stride(self, dimension: int) -> SrcMemberAccess:
-        pass
-
diff --git a/pystencils_sfg/source_concepts/cpp/std_mdspan.py b/pystencils_sfg/source_concepts/cpp/std_mdspan.py
deleted file mode 100644
index e92480a..0000000
--- a/pystencils_sfg/source_concepts/cpp/std_mdspan.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from typing import Optional
-
-from ..source_concepts import SrcMemberAccess
-from ..containers import SrcContiguousContainer
-
-class std_mdspan(SrcContiguousContainer):
-    def __init__(self, identifer: str):
-        super().__init__("std::mdspan", identifier)
-
-    def ptr(self):
-        return SrcMemberAccess(self, f"{self._identifier}.data_handle()")
-
-    def size(self, dimension: int):
-        return SrcMemberAccess(self, f"{self._identifier}.extents().extent({dimension})")
-
-    def stride(self, dimension: int):
-        return SrcMemberAccess(self, f"{self._identifier}.stride({dimension})")
diff --git a/pystencils_sfg/source_concepts/source_concepts.py b/pystencils_sfg/source_concepts/source_concepts.py
deleted file mode 100644
index 62463b7..0000000
--- a/pystencils_sfg/source_concepts/source_concepts.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from typing import Optional
-from abc import ABC, abstractmethod
-from pystencils import TypedSymbol
-
-class SrcClass:
-    def __init__(self):
-        pass
-
-
-class SrcObject(ABC):
-    def __init__(self, src_type, identifier: Optional[str]):
-        self._src_type = src_type
-        self._identifier = identifier
-
-    @property
-    def _sfg_symbol(self):
-        return TypedSymbol(self._identifier, self._src_type)
-    
-
-class SrcMemberAccess():
-    def __init__(self, obj: SrcObject, code_string: str):
-        self._obj = obj
-        self._code_string = code_string
-
-    def _sfg_code_string():
-        return self._code_string
-
diff --git a/pystencils_sfg/tree/__init__.py b/pystencils_sfg/tree/__init__.py
deleted file mode 100644
index 9615188..0000000
--- a/pystencils_sfg/tree/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from .basic_nodes import SfgCallTreeNode, SfgKernelCallNode, SfgBlock, SfgSequence
-from .conditional import SfgBranch, SfgCondition
-
-__all__ = [
-    SfgCallTreeNode, SfgKernelCallNode, SfgSequence, SfgBlock,
-    SfgCondition, SfgBranch
-]
\ No newline at end of file
diff --git a/pystencils_sfg/__init__.py b/pystencilssfg/__init__.py
similarity index 99%
rename from pystencils_sfg/__init__.py
rename to pystencilssfg/__init__.py
index 88406b1..04329f1 100644
--- a/pystencils_sfg/__init__.py
+++ b/pystencilssfg/__init__.py
@@ -4,4 +4,4 @@ from .kernel_namespace import SfgKernelNamespace, SfgKernelHandle
 __all__ = [
     'SourceFileGenerator', 'SfgContext',
     'SfgKernelNamespace', 'SfgKernelHandle'
-]
\ No newline at end of file
+]
diff --git a/pystencils_sfg/context.py b/pystencilssfg/context.py
similarity index 65%
rename from pystencils_sfg/context.py
rename to pystencilssfg/context.py
index 7fc0176..572d843 100644
--- a/pystencils_sfg/context.py
+++ b/pystencilssfg/context.py
@@ -1,16 +1,21 @@
-from typing import Callable, Sequence, Generator, Union, Optional
+from typing import Generator, Union, Optional, Sequence
 from dataclasses import dataclass
 
+import sys
 import os
 from os import path
 
+from argparse import ArgumentParser
+
 from jinja2.filters import do_indent
 
+from pystencils import Field
 from pystencils.astnodes import KernelFunction
 
 from .kernel_namespace import SfgKernelNamespace, SfgKernelHandle
 from .tree import SfgCallTreeNode, SfgSequence, SfgKernelCallNode, SfgCondition, SfgBranch
-from .tree.builders import SfgBranchBuilder, SfgSequencer
+from .tree.builders import SfgBranchBuilder, make_sequence
+from .source_concepts.containers import SrcField
 from .source_components import SfgFunction
 
 
@@ -25,23 +30,29 @@ class SfgCodeStyle:
 class SourceFileGenerator:
     def __init__(self,
                  namespace: str = "pystencils",
-                 basename: str = None,
                  codestyle: SfgCodeStyle = SfgCodeStyle()):
         
-        if basename is None:
-            import __main__
-            scriptpath = __main__.__file__
-            scriptname = path.split(scriptpath)[1]
-            basename = path.splitext(scriptname)[0]
+        parser = ArgumentParser(
+            "pystencilssfg",
+            description="pystencils Source File Generator")
+        
+        parser.add_argument("script_args", nargs='*')
+        parser.add_argument("-d", "--output-dir", type=str, default='.', dest='output_directory')
+
+        args = parser.parse_args(sys.argv)
 
-        self.basename = basename
-        self.header_filename = basename + ".h"
-        self.cpp_filename = basename + ".cpp"
+        import __main__
+        scriptpath = __main__.__file__
+        scriptname = path.split(scriptpath)[1]
+        basename = path.splitext(scriptname)[0]        
 
-        self._context = SfgContext(namespace, codestyle)
+        self._context = SfgContext(args.script_args, namespace, codestyle)
+
+        from .emitters.cpu.basic_cpu import BasicCpuEmitter
+        self._emitter = BasicCpuEmitter(self._context, basename, args.output_directory)
 
     def clean_files(self):
-        for file in (self.header_filename, self.cpp_filename):
+        for file in self._emitter.output_files:
             if path.exists(file):
                 os.remove(file)
 
@@ -51,12 +62,12 @@ class SourceFileGenerator:
 
     def __exit__(self, exc_type, exc_value, traceback):
         if exc_type is None:
-            from .emitters.cpu.basic_cpu import BasicCpuEmitter
-            BasicCpuEmitter(self._context, self.basename).write_files()
+            self._emitter.write_files()
 
 
 class SfgContext:
-    def __init__(self, root_namespace: str, codestyle: SfgCodeStyle):
+    def __init__(self, argv, root_namespace: str, codestyle: SfgCodeStyle):
+        self._argv = argv
         self._root_namespace = root_namespace
         self._codestyle = codestyle
         self._default_kernel_namespace = SfgKernelNamespace(self, "kernels")
@@ -66,8 +77,9 @@ class SfgContext:
         self._kernel_namespaces = { self._default_kernel_namespace.name : self._default_kernel_namespace }
         self._functions = dict()
 
-        #   Builder Components
-        self._sequencer = SfgSequencer(self)
+    @property
+    def argv(self) -> Sequence[str]:
+        return self._argv
 
     @property
     def root_namespace(self) -> str:
@@ -88,6 +100,9 @@ class SfgContext:
         kns = SfgKernelNamespace(self, name)
         self._kernel_namespaces[name] = kns
         return kns
+    
+    def includes(self) -> Generator[str, None, None]:
+        yield from self._includes
 
     def kernel_namespaces(self) -> Generator[SfgKernelNamespace, None, None]:
         yield from self._kernel_namespaces.values()
@@ -96,7 +111,12 @@ class SfgContext:
         yield from self._functions.values()
 
     def include(self, header_file: str):
+        if not (header_file.startswith("<") and header_file.endswith(">")):
+            if not (header_file.startswith('"') and header_file.endswith('"')):
+                header_file = f'"{header_file}"'
+        
         self._includes.append(header_file)
+    
 
     def function(self, 
                  name: str,
@@ -114,7 +134,7 @@ class SfgContext:
                 raise TypeError(f"Invalid type of argument `ast_or_kernel_handle`!")
         else:
             def sequencer(*args: SfgCallTreeNode):
-                tree = self.seq(*args)
+                tree = make_sequence(*args)
                 func = SfgFunction(self, name, tree)
                 self._functions[name] = func
 
@@ -125,14 +145,16 @@ class SfgContext:
     #   Call Tree Node Factory
     #----------------------------------------------------------------------------------------------
 
-    @property
-    def seq(self) -> SfgSequencer:
-        return self._sequencer
-
     def call(self, kernel_handle: SfgKernelHandle) -> SfgKernelCallNode:
         return SfgKernelCallNode(kernel_handle)
     
     @property
     def branch(self) -> SfgBranchBuilder:
-        return SfgBranchBuilder(self)
+        return SfgBranchBuilder()
+    
+    def map_field(self, field: Field, src_object: Optional[SrcField] = None) -> SfgSequence:
+        if src_object is None:
+            raise NotImplementedError("Automatic field extraction is not implemented yet.")
+        else:
+            return src_object.extract_parameters(field)
     
\ No newline at end of file
diff --git a/pystencils_sfg/emitters/__init__.py b/pystencilssfg/emitters/__init__.py
similarity index 100%
rename from pystencils_sfg/emitters/__init__.py
rename to pystencilssfg/emitters/__init__.py
diff --git a/pystencils_sfg/emitters/cpu/__init__.py b/pystencilssfg/emitters/cpu/__init__.py
similarity index 100%
rename from pystencils_sfg/emitters/cpu/__init__.py
rename to pystencilssfg/emitters/cpu/__init__.py
diff --git a/pystencils_sfg/emitters/cpu/basic_cpu.py b/pystencilssfg/emitters/cpu/basic_cpu.py
similarity index 57%
rename from pystencils_sfg/emitters/cpu/basic_cpu.py
rename to pystencilssfg/emitters/cpu/basic_cpu.py
index 2181681..d36c98e 100644
--- a/pystencils_sfg/emitters/cpu/basic_cpu.py
+++ b/pystencilssfg/emitters/cpu/basic_cpu.py
@@ -1,26 +1,37 @@
 from jinja2 import Environment, PackageLoader, StrictUndefined
 
+from os import path
+
 from ...context import SfgContext
 
 class BasicCpuEmitter:
-    def __init__(self, ctx: SfgContext, basename: str):
+    def __init__(self, ctx: SfgContext, basename: str, output_directory: str):
         self._ctx = ctx
         self._basename = basename
+        self._output_directory = output_directory
         self._header_filename = basename + ".h"
         self._cpp_filename = basename + ".cpp"
 
+    @property
+    def output_files(self) -> str:
+        return (
+            path.join(self._output_directory, self._header_filename),
+            path.join(self._output_directory, self._cpp_filename)
+        )
+
     def write_files(self):
         jinja_context = {
             'ctx': self._ctx,
             'basename': self._basename,
             'root_namespace': self._ctx.root_namespace,
+            'includes': list(self._ctx.includes()),
             'kernel_namespaces': list(self._ctx.kernel_namespaces()),
             'functions': list(self._ctx.functions())
         }
 
         template_name = "BasicCpu"
 
-        env = Environment(loader=PackageLoader('pystencils_sfg.emitters.cpu'), undefined=StrictUndefined)
+        env = Environment(loader=PackageLoader('pystencilssfg.emitters.cpu'), undefined=StrictUndefined)
 
         from .jinja_filters import add_filters_to_jinja
         add_filters_to_jinja(env)
@@ -28,8 +39,8 @@ class BasicCpuEmitter:
         header = env.get_template(f"{template_name}.tmpl.h").render(**jinja_context)
         source = env.get_template(f"{template_name}.tmpl.cpp").render(**jinja_context)
 
-        with open(self._header_filename, 'w') as headerfile:
+        with open(path.join(self._output_directory, self._header_filename), 'w') as headerfile:
             headerfile.write(header)
 
-        with open(self._cpp_filename, 'w') as cppfile:
+        with open(path.join(self._output_directory, self._cpp_filename), 'w') as cppfile:
             cppfile.write(source)
diff --git a/pystencils_sfg/emitters/cpu/jinja_filters.py b/pystencilssfg/emitters/cpu/jinja_filters.py
similarity index 88%
rename from pystencils_sfg/emitters/cpu/jinja_filters.py
rename to pystencilssfg/emitters/cpu/jinja_filters.py
index 0abbdf3..d3602da 100644
--- a/pystencils_sfg/emitters/cpu/jinja_filters.py
+++ b/pystencilssfg/emitters/cpu/jinja_filters.py
@@ -4,8 +4,7 @@ from pystencils.astnodes import KernelFunction
 from pystencils import Backend
 from pystencils.backends import generate_c
 
-from pystencils_sfg.tree import SfgCallTreeNode
-from pystencils_sfg.source_components import SfgFunction
+from pystencilssfg.source_components import SfgFunction
 
 @pass_context
 def generate_kernel_definition(ctx, ast: KernelFunction):
diff --git a/pystencils_sfg/emitters/cpu/templates/BasicCpu.tmpl.cpp b/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.cpp
similarity index 99%
rename from pystencils_sfg/emitters/cpu/templates/BasicCpu.tmpl.cpp
rename to pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.cpp
index b9782a3..c5d368a 100644
--- a/pystencils_sfg/emitters/cpu/templates/BasicCpu.tmpl.cpp
+++ b/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.cpp
@@ -23,11 +23,9 @@ namespace {{ kns.name }}{
 *************************************************************************************/
 
 {% for function in functions %}
-
 void {{ function.name }} ( {{ function | generate_function_parameter_list }} ) { 
   {{ function | generate_function_body | indent(2) }}
 }
-
 {% endfor %}
 
 } // namespace {{root_namespace}}
diff --git a/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.h b/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.h
new file mode 100644
index 0000000..8301a21
--- /dev/null
+++ b/pystencilssfg/emitters/cpu/templates/BasicCpu.tmpl.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#define RESTRICT __restrict__
+
+#include <cstdint>
+
+{% for header in includes %}
+#include {{header}}
+{% endfor %}
+
+
+namespace {{root_namespace}} {
+
+{% for function in functions %}
+void {{ function.name }} ( {{ function | generate_function_parameter_list }} );
+{% endfor %}
+
+} // namespace {{root_namespace}}
diff --git a/pystencils_sfg/kernel_namespace.py b/pystencilssfg/kernel_namespace.py
similarity index 98%
rename from pystencils_sfg/kernel_namespace.py
rename to pystencilssfg/kernel_namespace.py
index 8545299..4af5da4 100644
--- a/pystencils_sfg/kernel_namespace.py
+++ b/pystencilssfg/kernel_namespace.py
@@ -1,4 +1,7 @@
 # from .context import SfgContext
+
+from typing import Sequence
+
 from pystencils import CreateKernelConfig, create_kernel
 from pystencils.astnodes import KernelFunction
 
diff --git a/pystencils_sfg/source_components.py b/pystencilssfg/source_components.py
similarity index 95%
rename from pystencils_sfg/source_components.py
rename to pystencilssfg/source_components.py
index 68e8be3..c296e97 100644
--- a/pystencils_sfg/source_components.py
+++ b/pystencilssfg/source_components.py
@@ -1,5 +1,5 @@
 from __future__ import annotations
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
     from .context import SfgContext
diff --git a/pystencils_sfg/source_concepts/__init__.py b/pystencilssfg/source_concepts/__init__.py
similarity index 100%
rename from pystencils_sfg/source_concepts/__init__.py
rename to pystencilssfg/source_concepts/__init__.py
diff --git a/pystencilssfg/source_concepts/containers.py b/pystencilssfg/source_concepts/containers.py
new file mode 100644
index 0000000..c634310
--- /dev/null
+++ b/pystencilssfg/source_concepts/containers.py
@@ -0,0 +1,36 @@
+from typing import Optional, Union
+from abc import ABC, abstractmethod
+
+from pystencils import Field
+from pystencils.typing import FieldPointerSymbol, FieldStrideSymbol, FieldShapeSymbol
+
+from .source_concepts import SrcObject
+from ..tree import SfgStatements, SfgSequence
+
+class SrcField(SrcObject):
+    def __init__(self, src_type, identifier: Optional[str]):
+        super().__init__(src_type, identifier)
+
+    @abstractmethod
+    def extract_ptr(self, ptr_symbol: FieldPointerSymbol) -> SfgStatements:
+        pass
+
+    @abstractmethod
+    def extract_size(self, coordinate: int, size: Union[int, FieldShapeSymbol]) -> SfgStatements:
+        pass
+
+    @abstractmethod
+    def extract_stride(self, coordinate: int, stride: Union[int, FieldStrideSymbol]) -> SfgStatements:
+        pass
+
+    def extract_parameters(self, field: Field) -> SfgSequence:
+        ptr = FieldPointerSymbol(field.name, field.dtype, False)
+
+        from ..tree import make_sequence
+
+        return make_sequence(
+            self.extract_ptr(ptr),
+            *(self.extract_size(c, s) for c, s in enumerate(field.shape)),
+            *(self.extract_stride(c, s) for c, s in enumerate(field.strides))
+        )
+
diff --git a/pystencilssfg/source_concepts/cpp/std_mdspan.py b/pystencilssfg/source_concepts/cpp/std_mdspan.py
new file mode 100644
index 0000000..93c8811
--- /dev/null
+++ b/pystencilssfg/source_concepts/cpp/std_mdspan.py
@@ -0,0 +1,43 @@
+from typing import Union
+
+from pystencils.typing import FieldPointerSymbol, FieldStrideSymbol, FieldShapeSymbol
+
+from ...tree import SfgStatements
+from ..containers import SrcField
+
+class std_mdspan(SrcField):
+    def __init__(self, identifer: str):
+        super().__init__("std::mdspan", identifer)
+
+    def extract_ptr(self, ptr_symbol: FieldPointerSymbol):
+        return SfgStatements(
+            f"{ptr_symbol.dtype} {ptr_symbol.name} = {self._identifier}.data_handle();",
+            (ptr_symbol, ),
+            (self, )
+        )
+
+    def extract_size(self, coordinate: int, size: Union[int, FieldShapeSymbol]) -> SfgStatements:
+        if isinstance(size, FieldShapeSymbol):
+            return SfgStatements(
+                    f"{size.dtype} {size.name} = {self._identifier}.extents().extent({coordinate});",
+                    (size, ),
+                    (self, )
+                )
+        else:
+            return SfgStatements(
+                f"assert( {self._identifier}.extents().extent({coordinate}) == {size} );",
+                (), (self, )
+            )
+        
+    def extract_stride(self, coordinate: int, stride: Union[int, FieldShapeSymbol]) -> SfgStatements:
+        if isinstance(stride, FieldShapeSymbol):
+            return SfgStatements(
+                    f"{stride.dtype} {stride.name} = {self._identifier}.stride({coordinate});",
+                    (stride, ),
+                    (self, )
+                )
+        else:
+            return SfgStatements(
+                f"assert( {self._identifier}.stride({coordinate}) == {stride} );",
+                (), (self, )
+            )
diff --git a/pystencilssfg/source_concepts/source_concepts.py b/pystencilssfg/source_concepts/source_concepts.py
new file mode 100644
index 0000000..4e663f4
--- /dev/null
+++ b/pystencilssfg/source_concepts/source_concepts.py
@@ -0,0 +1,43 @@
+from __future__ import annotations
+
+from typing import Optional, Sequence, Union, Set
+from abc import ABC
+from pystencils import TypedSymbol
+
+class SrcObject(ABC):
+    def __init__(self, src_type, identifier: Optional[str]):
+        self._src_type = src_type
+        self._identifier = identifier
+
+    @property
+    def src_type(self):
+        return self._src_type
+    
+    @property
+    def identifier(self):
+        return self._identifier
+
+    @property
+    def typed_symbol(self):
+        return TypedSymbol(self._identifier, self._src_type)
+
+
+
+    
+
+# class SrcExpression(SrcStatements):
+#     """Represents a single expression of the source language, e.g. a C++ expression 
+#     (c.f. https://en.cppreference.com/w/cpp/language/expressions).
+
+#     It is the user's responsibility to ensure that the code string is valid code in the output language,
+#     and that the list of required  objects is complete.
+
+#     Args:
+#         code_string: Code to be printed out.
+#         required_objects: Objects (as `SrcObject` or `TypedSymbol`) that are required as input to this expression.
+#     """
+
+#     def __init__(self,
+#                  code_string: str,
+#                  required_objects: Sequence[Union[SrcObject, TypedSymbol]]):
+#         super().__init__(code_string, (), required_objects)
diff --git a/pystencilssfg/tree/__init__.py b/pystencilssfg/tree/__init__.py
new file mode 100644
index 0000000..44603b7
--- /dev/null
+++ b/pystencilssfg/tree/__init__.py
@@ -0,0 +1,9 @@
+from .basic_nodes import SfgCallTreeNode, SfgKernelCallNode, SfgBlock, SfgSequence, SfgStatements
+from .conditional import SfgBranch, SfgCondition
+from .builders import make_sequence
+
+__all__ = [
+    SfgCallTreeNode, SfgKernelCallNode, SfgSequence, SfgBlock, SfgStatements,
+    SfgCondition, SfgBranch,
+    make_sequence
+]
\ No newline at end of file
diff --git a/pystencils_sfg/tree/basic_nodes.py b/pystencilssfg/tree/basic_nodes.py
similarity index 53%
rename from pystencils_sfg/tree/basic_nodes.py
rename to pystencilssfg/tree/basic_nodes.py
index bff3d00..3750bd5 100644
--- a/pystencils_sfg/tree/basic_nodes.py
+++ b/pystencilssfg/tree/basic_nodes.py
@@ -1,5 +1,5 @@
 from __future__ import annotations
-from typing import TYPE_CHECKING, Any, Sequence
+from typing import TYPE_CHECKING, Any, Sequence, Set, Union, Iterable
 
 if TYPE_CHECKING:
     from ..context import SfgContext
@@ -10,6 +10,7 @@ from functools import reduce
 from jinja2.filters import do_indent
 
 from ..kernel_namespace import SfgKernelHandle
+from ..source_concepts.source_concepts import SrcObject
 
 from pystencils.typing import TypedSymbol
 
@@ -38,37 +39,54 @@ class SfgCallTreeLeaf(SfgCallTreeNode, ABC):
 
     @property
     @abstractmethod
-    def required_symbols(self) -> set(TypedSymbol):
+    def required_symbols(self) -> Set[TypedSymbol]:
         pass
 
 
-class SfgParameterDefinition(SfgCallTreeLeaf):
-    def __init__(self, defined_param: TypedSymbol, required_params: Set[TypedSymbol], code_string: str):
-        self._defined_param = defined_param
-        self._required_params = required_params
+class SfgStatements(SfgCallTreeLeaf):
+    """Represents (a sequence of) statements in the source language.
+    
+    This class groups together arbitrary code strings
+    (e.g. sequences of C++ statements, cf. https://en.cppreference.com/w/cpp/language/statements),
+    and annotates them with the set of symbols read and written by these statements.
+
+    It is the user's responsibility to ensure that the code string is valid code in the output language,
+    and that the lists of required and defined objects are correct and complete.
+
+    Args:
+        code_string: Code to be printed out.
+        defined_objects: Objects (as `SrcObject` or `TypedSymbol`) that will be newly defined and visible to
+            code in sequence after these statements.
+        required_objects: Objects (as `SrcObject` or `TypedSymbol`) that are required as input to these statements.
+    """
+
+    def __init__(self, 
+                 code_string: str,
+                 defined_objects: Sequence[Union[SrcObject, TypedSymbol]],
+                 required_objects: Sequence[Union[SrcObject, TypedSymbol]]):
         self._code_string = code_string
-
-    @property
-    def defined_symbol(self) -> TypedSymbol:
-        return self._defined_param
-
+        
+        def to_symbol(obj: Union[SrcObject, TypedSymbol]):
+            if isinstance(obj, SrcObject):
+                self._required_symbols.add(obj.typed_symbol)
+            elif isinstance(obj, TypedSymbol):
+                self._required_symbols.add(obj)
+            else:
+                raise ValueError(f"Required object in expression is neither TypedSymbol nor SrcObject: {obj}")
+        
+        self._defined_symbols = set(map(to_symbol, defined_objects))
+        self._required_symbols = set(map(to_symbol, required_objects))
+            
     @property
-    def required_symbols(self) -> set(TypedSymbol):
-        return self._required_params
-
-    def get_code(self):
-        return self._code_string
-
-
-class SfgCustomStatement(SfgCallTreeLeaf):
-    def __init__(self, statement: str):
-        self._statement = statement
-
-    def required_symbols(self) -> set(TypedSymbol):
-        return set()
+    def required_symbols(self) -> Set[TypedSymbol]:
+        return self._required_symbols
     
+    @property
+    def defined_symbols(self) -> Set[TypedSymbol]:
+        return self._defined_symbols
+            
     def get_code(self, ctx: SfgContext) -> str:
-        return self._statement
+        return self._code_string
 
 
 class SfgSequence(SfgCallTreeNode):
@@ -103,7 +121,7 @@ class SfgKernelCallNode(SfgCallTreeLeaf):
         self._kernel_handle = kernel_handle
 
     @property
-    def required_symbols(self) -> set(TypedSymbol):
+    def required_symbols(self) -> Set[TypedSymbol]:
         return set(p.symbol for p in self._kernel_handle.parameters)
     
     def get_code(self, ctx: SfgContext) -> str:
diff --git a/pystencils_sfg/tree/builders.py b/pystencilssfg/tree/builders.py
similarity index 54%
rename from pystencils_sfg/tree/builders.py
rename to pystencilssfg/tree/builders.py
index 5f617e3..5c35c56 100644
--- a/pystencils_sfg/tree/builders.py
+++ b/pystencilssfg/tree/builders.py
@@ -8,41 +8,35 @@ from abc import ABC, abstractmethod
 
 from pystencils import Field
 
-from .basic_nodes import SfgCallTreeNode, SfgSequence, SfgBlock, SfgCustomStatement
+from .basic_nodes import SfgCallTreeNode, SfgSequence, SfgBlock, SfgStatements
 from .conditional import SfgCondition, SfgCustomCondition, SfgBranch
-from ..source_concepts.containers import SrcContiguousContainer
     
 class SfgNodeBuilder(ABC):
     @abstractmethod
     def resolve(self) -> SfgCallTreeNode:
         pass
 
-class SfgSequencer:
-    def __init__(self, ctx: SfgContext) -> None:
-        self._ctx = ctx
-
-    def __call__(self, *args) -> SfgSequence:
-        children = []
-        for i, arg in enumerate(args):
-            if isinstance(arg, SfgNodeBuilder):
-                children.append(arg.resolve())
-            elif isinstance(arg, SfgCallTreeNode):
-                children.append(arg)
-            elif isinstance(arg, str):
-                children.append(SfgCustomStatement(arg))
-            elif isinstance(arg, tuple):
-                #   Tuples are treated as blocks
-                subseq = self(*arg)
-                children.append(SfgBlock(subseq))
-            else:
-                raise TypeError(f"Sequence argument {i} has invalid type.")
-        
-        return SfgSequence(children)
+def make_sequence(*args) -> SfgSequence:
+    children = []
+    for i, arg in enumerate(args):
+        if isinstance(arg, SfgNodeBuilder):
+            children.append(arg.resolve())
+        elif isinstance(arg, SfgCallTreeNode):
+            children.append(arg)
+        elif isinstance(arg, str):
+            children.append(SfgStatements(arg, (), ()))
+        elif isinstance(arg, tuple):
+            #   Tuples are treated as blocks
+            subseq = self(*arg)
+            children.append(SfgBlock(subseq))
+        else:
+            raise TypeError(f"Sequence argument {i} has invalid type.")
+    
+    return SfgSequence(children)
     
 
 class SfgBranchBuilder(SfgNodeBuilder):
-    def __init__(self, ctx: SfgContext):
-        self._ctx = ctx
+    def __init__(self):
         self._phase = 0
 
         self._cond = None
@@ -65,9 +59,9 @@ class SfgBranchBuilder(SfgNodeBuilder):
                 self._cond = cond
 
             case 1: # Then-branch
-                self._branch_true = self._ctx.seq(*args)
+                self._branch_true = make_sequence(*args)
             case 2: # Else-branch
-                self._branch_false = self._ctx.seq(*args)
+                self._branch_false = make_sequence(*args)
             case _: # There's no third branch!
                 raise TypeError("Branch construct already complete.")
 
@@ -78,14 +72,3 @@ class SfgBranchBuilder(SfgNodeBuilder):
     def resolve(self) -> SfgCallTreeNode:
         return SfgBranch(self._cond, self._branch_true, self._branch_false)
     
-    
-class SfgFieldMappingBuilder(SfgNodeBuilder):
-    def __init__(self, ctx: SfgContext):
-        super().__init__(ctx)
-
-        self._field = None
-        self._container = None
-
-    def __call__(self, field: Field, container: SrcContiguousContainer):
-        self._field = field
-        self._container = container
\ No newline at end of file
diff --git a/pystencils_sfg/tree/conditional.py b/pystencilssfg/tree/conditional.py
similarity index 100%
rename from pystencils_sfg/tree/conditional.py
rename to pystencilssfg/tree/conditional.py
diff --git a/pystencils_sfg/tree/visitors.py b/pystencilssfg/tree/visitors.py
similarity index 96%
rename from pystencils_sfg/tree/visitors.py
rename to pystencilssfg/tree/visitors.py
index 816abe9..c94ee53 100644
--- a/pystencils_sfg/tree/visitors.py
+++ b/pystencilssfg/tree/visitors.py
@@ -3,7 +3,7 @@ from functools import reduce
 
 from pystencils.typing import TypedSymbol
 
-from .basic_nodes import SfgCallTreeNode, SfgCallTreeLeaf, SfgSequence, SfgParameterDefinition
+from .basic_nodes import SfgCallTreeNode, SfgCallTreeLeaf, SfgSequence, SfgStatements
 
 
 class FlattenSequences():
@@ -56,7 +56,7 @@ class ParameterCollector():
         
         params = set()
         for c in sequence.children[::-1]:
-            if isinstance(c, SfgParameterDefinitionNode):
+            if isinstance(c, SfgStatements):
                 params -= c.defined_symbols
             
             assert not isinstance(c, SfgSequence), "Sequence not flattened."
diff --git a/tests/TestSequencing.py b/tests/TestSequencing.py
index 1303bd3..ad9c522 100644
--- a/tests/TestSequencing.py
+++ b/tests/TestSequencing.py
@@ -1,4 +1,4 @@
-from pystencils_sfg import SourceFileGenerator
+from pystencilssfg import SourceFileGenerator
 
 from lbmpy.advanced_streaming import Timestep
 from lbmpy import LBMConfig, create_lb_ast
diff --git a/tests/mdspan/Makefile b/tests/mdspan/Makefile
new file mode 100644
index 0000000..cf51bde
--- /dev/null
+++ b/tests/mdspan/Makefile
@@ -0,0 +1,34 @@
+
+CXX := clang++
+CXX_FLAGS := '-DDEBUG -g'
+
+PYTHON := python
+
+MKDIR := mkdir -p
+dir_guard = $(MKDIR) $(@D)	
+
+OBJ := obj
+BIN := bin
+GEN_SRC := generated_src
+
+.PHONY: all clean
+
+all: $(BIN)/mdspan_test
+
+clean: 
+	rm -rf $(BIN) $(OUT) $(GEN_SRC)
+
+$(GEN_SRC)/kernels.cpp $(GEN_SRC)/kernels.h &: kernels.py
+	@$(dir_guard)
+	$(PYTHON) $< -d $(@D)
+
+$(OBJ)/kernels.o: $(GEN_SRC)/kernels.cpp $(GEN_SRC)/kernels.h
+	@$(dir_guard)
+	$(CXX) $(CXX_FLAGS) -c -o $@ $<
+
+$(OBJ)/main.o: main.cpp $(GEN_SRC)/kernels.h
+	$(CXX) $(CXX_FLAGS) -c -o $@ $<
+
+$(BIN)/mdspan_test: $(OBJ)/kernels.o $(OBJ)/main.o
+	$(CXX) $(CXX_FLAGS) -o $@ $^
+
diff --git a/tests/mdspan/kernels.py b/tests/mdspan/kernels.py
new file mode 100644
index 0000000..11942a3
--- /dev/null
+++ b/tests/mdspan/kernels.py
@@ -0,0 +1,8 @@
+from pystencilssfg import SourceFileGenerator
+
+with SourceFileGenerator() as sfg:
+    sfg.include("<iostream>")
+
+    sfg.function("myFunction")(
+        r'std::cout << "mdspans!\n";'
+    )
diff --git a/tests/mdspan/main.cpp b/tests/mdspan/main.cpp
new file mode 100644
index 0000000..f44ca21
--- /dev/null
+++ b/tests/mdspan/main.cpp
@@ -0,0 +1,6 @@
+#include "generated_src/kernels.h"
+
+int main(int argc, char ** argv){
+    pystencils::myFunction();
+    return 0;
+}
-- 
GitLab