From 89d01c91f362fc9e2bf40420967b7434342d4ab9 Mon Sep 17 00:00:00 2001
From: Frederik Hennig <frederik.hennig@fau.de>
Date: Mon, 17 Feb 2025 16:31:10 +0100
Subject: [PATCH] extend docs

 - Write separate installation guide
 - Extend Getting Started guide
 - Rewrite landing page
 - Update code generator monkeypatch to use advanced features of myst-nb
---
 docs/source/_util/sfg_mockup.py        |  19 --
 docs/source/_util/sfg_monkeypatch.py   |  68 ++++++
 docs/source/conf.py                    |   1 +
 docs/source/getting_started.md         | 194 +++++++++++-----
 docs/source/index.md                   | 263 +++-------------------
 docs/source/installation.md            |  45 ++++
 docs/source/usage/generator_scripts.md | 294 +------------------------
 src/pystencilssfg/emission/emitter.py  |   6 +-
 src/pystencilssfg/generator.py         |  43 ++--
 9 files changed, 310 insertions(+), 623 deletions(-)
 delete mode 100644 docs/source/_util/sfg_mockup.py
 create mode 100644 docs/source/_util/sfg_monkeypatch.py
 create mode 100644 docs/source/installation.md

diff --git a/docs/source/_util/sfg_mockup.py b/docs/source/_util/sfg_mockup.py
deleted file mode 100644
index 4bcaad1..0000000
--- a/docs/source/_util/sfg_mockup.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from pystencilssfg import SourceFileGenerator
-from pystencilssfg.config import SfgConfig
-
-
-class DocsMockupGenerator(SourceFileGenerator):
-    scriptname: str = "script"
-
-    def _scriptname(self) -> str:
-        return f"{DocsMockupGenerator.scriptname}.py"
-
-    def __init__(
-        self, sfg_config: SfgConfig | None = None, keep_unknown_argv: bool = False
-    ):
-        if sfg_config is None:
-            sfg_config = SfgConfig()
-
-        sfg_config.output_directory = "_sfg_out"
-
-        super().__init__(sfg_config, keep_unknown_argv=True)
diff --git a/docs/source/_util/sfg_monkeypatch.py b/docs/source/_util/sfg_monkeypatch.py
new file mode 100644
index 0000000..0269d40
--- /dev/null
+++ b/docs/source/_util/sfg_monkeypatch.py
@@ -0,0 +1,68 @@
+import pystencilssfg
+from pystencilssfg.config import SfgConfig
+
+
+class DocsPatchedGenerator(pystencilssfg.SourceFileGenerator):
+    """Mockup wrapper around SourceFileGenerator for use in documentation
+    notebooks to print the generated code directly to the HTML
+    instead of writing it to file."""
+
+    scriptname: str = "demo"
+    glue: bool = False
+    display: bool = True
+
+    @classmethod
+    def setup(cls, scriptname: str, glue: bool = False, display: bool = True):
+        cls.scriptname = scriptname
+        cls.glue = glue
+        cls.display = display
+
+    def _scriptname(self) -> str:
+        return f"{DocsPatchedGenerator.scriptname}.py"
+
+    def __init__(
+        self, sfg_config: SfgConfig | None = None, keep_unknown_argv: bool = False
+    ):
+        super().__init__(sfg_config, keep_unknown_argv=True)
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        if exc_type is None:
+            self._finish_files()
+
+            header_code = self._emitter.dumps(self._header_file)
+            impl_code = (
+                None
+                if self._impl_file is None
+                else self._emitter.dumps(self._impl_file)
+            )
+
+            mdcode = ":::::{tab-set}\n"
+
+            mdcode += "::::{tab-item} Generated Header (.hpp)\n"
+            mdcode += ":::{code-block} C++\n\n"
+            mdcode += header_code
+            mdcode += "\n:::\n::::\n"
+
+            if impl_code:
+                mdcode += "::::{tab-item} Generated Implementation (.cpp)\n"
+                mdcode += ":::{code-block} C++\n\n"
+                mdcode += impl_code
+                mdcode += "\n:::\n::::\n"
+
+            mdcode += ":::::"
+            from IPython.display import Markdown
+
+            mdobj = Markdown(mdcode)
+
+            if self.glue:
+                from myst_nb import glue
+
+                glue(f"sfg_out_{self.scriptname}", mdobj, display=False)
+
+            if self.display:
+                from IPython.display import display
+
+                display(mdobj)
+
+
+pystencilssfg.SourceFileGenerator = DocsPatchedGenerator
diff --git a/docs/source/conf.py b/docs/source/conf.py
index db8f782..9b6dcf4 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -89,3 +89,4 @@ myst_enable_extensions = [
     "dollarmath",
     "colon_fence",
 ]
+nb_render_markdown_format = "myst"
diff --git a/docs/source/getting_started.md b/docs/source/getting_started.md
index 822ea0b..008585a 100644
--- a/docs/source/getting_started.md
+++ b/docs/source/getting_started.md
@@ -4,47 +4,26 @@ kernelspec:
   name: python3
 ---
 
+(getting_started)=
 # Getting Started
 
-## Prequesites
-
-To use pystencils-sfg, you will need at least Python 3.10.
-You will also need the appropriate compilers for building the generated code,
-such as
- - a modern C++ compiler (e.g. GCC, clang)
- - `nvcc` for CUDA or `hipcc` for HIP
- - Intel OneAPI or AdaptiveCpp for SYCL
-
-Furthermore, an installation of clang-format for automatic code formatting is strongly recommended. 
-
-## Install the Latest Development Revision
+```{code-cell} ipython3
+:tags: [remove-cell]
 
-As pystencils-sfg is still unreleased, it can at this time only be obtained directly
-from its Git repository.
+import sys
+from pathlib import Path
 
-Create a fresh [virtual environment](https://docs.python.org/3/library/venv.html) or activate
-an existing one. Install both the pystencils 2.0 and pystencils-sfg development revisions from Git:
+mockup_path = Path("_util").resolve()
+sys.path.append(str(mockup_path))
 
-```{code-block} bash
-pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
-pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils-sfg.git"
+from sfg_monkeypatch import DocsPatchedGenerator  # monkeypatch SFG for docs
 ```
 
-````{caution}
-
-*pystencils-sfg* is not compatible with the *pystencils 1.3.x* releases available from PyPI;
-at the moment, you will still have to manually install the latest version of pystencils 2.0.
-````
 
-## Check your Installation
-
-To verify that the SFG was successfully installed, execute the following command:
-
-```{code-block} bash
-sfg-cli version
-```
-
-You should see an output like `0.1a4+...`.
+This guide will explain the basics of using pystencils-sfg through generator scripts.
+Generator scripts are the primary way to run code generation with pystencils-sfg.
+A generator script is a Python script that, when executed, produces one or more
+C++ source files with the same base name, but different file extensions.
 
 ## Writing a Basic Generator Script
 
@@ -63,8 +42,7 @@ When executed, the above will produce two (nearly) empty C++ files
 in the current folder, both with the same name as your Python script
 but with `.hpp` and `.cpp` file extensions instead.
 
-Generator scripts are the primary mode of using pystencils-sfg;
-in them, code generation is orchestrated by the `SourceFileGenerator` context manager.
+In the generator script, code generation is orchestrated by the `SourceFileGenerator` context manager.
 When entering into the region controlled by the `SourceFileGenerator`,
 it supplies us with a *composer object*, customarily called `sfg`.
 Through the composer, we can declaratively populate the generated files with code.
@@ -73,33 +51,52 @@ Through the composer, we can declaratively populate the generated files with cod
 
 One of the core applications of pystencils-sfg is to generate and wrap pystencils-kernels
 for usage within C++ applications.
-To register a kernel, pass its assignments to `sfg.kernels.create`.
-This gives you a *kernel handle*, through which you can call the kernel from a function:
+To register a kernel, pass its assignments to `sfg.kernels.create`, which returns a *kernel handle* object:
 
-```{code-cell} ipython3
-:tags: [remove-cell]
+```{code-block} python
+src, dst = ps.fields("src, dst: double[1D]")
+c = sp.Symbol("c")
 
-import sys
-from pathlib import Path
+@ps.kernel
+def scale():
+    dst.center @= c * src.center()
 
-mockup_path = Path("_util").resolve()
-sys.path.append(str(mockup_path))
+#   Register the kernel for code generation
+scale_kernel = sfg.kernels.create(scale, "scale_kernel")
+```
 
-from sfg_mockup import DocsMockupGenerator as SourceFileGenerator
+In order to call the kernel, and expose it to the outside world,
+we have to create a wrapper function for it, using `sfg.function`.
+In its body, we use `sfg.call` to invoke the kernel:
+
+```{code-block} python
+sfg.function("scale")(
+    sfg.call(scale_kernel)
+)
 ```
 
+The `function` composer has a special syntax that mimics the generated C++ code.
+We call it twice in sequence,
+first providing the name of the function, and then populating its body.
+
+Here's our full first generator script:
+
 ```{code-cell} ipython3
 :tags: [remove-cell]
-SourceFileGenerator.scriptname = "demo1"
+
+DocsPatchedGenerator.scriptname = "add_kernel_demo"
+DocsPatchedGenerator.glue = True
+DocsPatchedGenerator.display = False
 ```
 
 ```{code-cell} ipython3
+from pystencilssfg import SourceFileGenerator
 import pystencils as ps
 import sympy as sp
 
 with SourceFileGenerator() as sfg:
     #   Define a copy kernel
-    src, dst = ps.fields("src, dst: [1D]")
+    src, dst = ps.fields("src, dst: double[1D]")
     c = sp.Symbol("c")
 
     @ps.kernel
@@ -116,21 +113,108 @@ with SourceFileGenerator() as sfg:
 
 ```
 
-::::{tab-set}
+When executing the above script, two files will be generated: a C++ header and implementation file containing
+the `scale_kernel` and its wrapper function:
 
-:::{tab-item} Generated Header
-```{literalinclude} _sfg_out/demo1.hpp
-:language: C++
-```
+:::{glue:md} sfg_out_add_kernel_demo
+:format: myst
 :::
 
-:::{tab-item} Generated Implementation
-```{literalinclude} _sfg_out/demo1.cpp
-:language: C++
+As you can see, the header file contains a declaration `void scale(...)` of a function
+which is defined in the associated implementation file,
+and there calls our generated numerical kernel.
+As of now, it forwards the entire set of low-level kernel arguments -- array pointers and indexing information --
+to the outside.
+In numerical applications, this information is most of the time hidden from the user by encapsulating
+it in high-level C++ data structures.
+Pystencils-sfg offers means of representing such data structures in the code generator, and supports the
+automatic extraction of the low-level indexing information from them.
+
+## Mapping Fields to Data Structures
+
+Since C++23 there exists the archetypical [std::mdspan][mdspan], which represents a non-owning n-dimensional view
+on a contiguous data array.
+Pystencils-sfg offers native support for mapping pystencils fields onto `mdspan` instances in order to
+hide their memory layout details.
+
+Import `std` from `pystencilssfg.lang.cpp` and use `std.mdspan.from_field` to create representations
+of your pystencils fields as `std::mdspan` objects:
+
+```{code-block} python
+from pystencilssfg.lang.cpp import std
+
+...
+
+src_mdspan = std.mdspan.from_field(src)
+dst_mdspan = std.mdspan.from_field(dst)
+```
+
+Then, inside the wrapper function, instruct the SFG to map the fields onto their corresponding mdspans:
+
+```{code-block} python
+sfg.function("scale")(
+    sfg.map_field(src, src_mdspan),
+    sfg.map_field(dst, dst_mdspan),
+    sfg.call(scale_kernel)
+)
+```
+
+Here's the full script and its output:
+
+```{code-cell} ipython3
+:tags: [remove-cell]
+
+DocsPatchedGenerator.setup("mdspan_demo", False, True)
+```
+
+
+```{code-cell} ipython3
+:tags: [hide-input]
+
+from pystencilssfg import SourceFileGenerator
+import pystencils as ps
+import sympy as sp
+
+from pystencilssfg.lang.cpp import std
+
+with SourceFileGenerator() as sfg:
+    #   Define a copy kernel
+    src, dst = ps.fields("src, dst: double[1D]")
+    c = sp.Symbol("c")
+
+    @ps.kernel
+    def scale():
+        dst.center @= c * src.center()
+
+    #   Register the kernel for code generation
+    scale_kernel = sfg.kernels.create(scale, "scale_kernel")
+
+    #   Create mdspan objects
+    src_mdspan = std.mdspan.from_field(src)
+    dst_mdspan = std.mdspan.from_field(dst)
+
+    #   Wrap it in a function
+    sfg.function("scale")(
+        sfg.map_field(src, src_mdspan),
+        sfg.map_field(dst, dst_mdspan),
+        sfg.call(scale_kernel)
+    )
+
 ```
+
+:::{note}
+
+As of early 2025, `std::mdspan` is still not fully adopted by standard library implementors
+(see [cppreference.com][cppreference_compiler_support]);
+most importantly, the GNU libstdc++ does not yet ship an implementation of it.
+However, a reference implementation is available at https://github.com/kokkos/mdspan.
+If you are using the reference implementation, refer to the documentation of {any}`StdMdspan`
+for advice on how to configure the header file and namespace where the class is defined.
 :::
 
-::::
 
 ## Next Steps
 
+
+[mdspan]: https://en.cppreference.com/w/cpp/container/mdspan
+[cppreference_compiler_support]: https://en.cppreference.com/w/cpp/compiler_support
diff --git a/docs/source/index.md b/docs/source/index.md
index b11cb53..4b60903 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -1,15 +1,38 @@
 # The pystencils Source File Generator
 
+
+[![pipeline](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/badges/master/pipeline.svg)](https://i10git.cs.fau.de/pycodegen-/pystencils-sfg/commits/master)
+[![coverage](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/badges/master/coverage.svg)](https://i10git.cs.fau.de/pycodegen-/pystencils-sfg/commits/master)
+[![licence](https://img.shields.io/gitlab/license/pycodegen%2Fpystencils-sfg?gitlab_url=https%3A%2F%2Fi10git.cs.fau.de)](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/-/blob/master/LICENSE)
+
+*A bridge over the semantic gap between [pystencils](https://pypi.org/project/pystencils/) and C++ HPC frameworks.*
+
+The pystencils Source File Generator is a code generation tool that allows you to
+declaratively describe and automatically generate C++ code using its Python API.
+It is part of the wider [pycodegen][pycodegen] family of packages for scientific code generation.
+
+The primary purpose of pystencils-sfg is to embed the [pystencils][pystencils] code generator for
+high-performance stencil computations into C++ HPC applications and frameworks of all scales.
+Its features include:
+
+ - Exporting pystencils kernels to C++ source files for use in larger projects
+ - Mapping of symbolic pystencils fields onto a wide variety of n-dimensional array data structures
+ - Orchestration of code generation as part of a Makefile or CMake project
+ - Declarative description of C++ code structure including functions and classes using the versatile composer API
+ - Reflection of C++ APIs in the code generator, including automatic tracking of variables and `#include`s
+
+
+## Table of Contents
+
 ```{toctree}
 :maxdepth: 1
-:hidden:
 
+installation
 getting_started
 ```
 
 ```{toctree}
 :maxdepth: 1
-:hidden:
 :caption: User Guide
 
 usage/generator_scripts
@@ -21,7 +44,6 @@ usage/tips_n_tricks
 
 ```{toctree}
 :maxdepth: 1
-:hidden:
 :caption: API Reference
 
 api/generation
@@ -31,236 +53,5 @@ api/ir
 api/errors
 ```
 
-[![pipeline](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/badges/master/pipeline.svg)](https://i10git.cs.fau.de/pycodegen-/pystencils-sfg/commits/master)
-[![coverage](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/badges/master/coverage.svg)](https://i10git.cs.fau.de/pycodegen-/pystencils-sfg/commits/master)
-[![licence](https://img.shields.io/gitlab/license/pycodegen%2Fpystencils-sfg?gitlab_url=https%3A%2F%2Fi10git.cs.fau.de)](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/-/blob/master/LICENSE)
-
-A bridge over the semantic gap between code emitted by [pystencils](https://pypi.org/project/pystencils/)
-and your C/C++/Cuda/HIP framework.
-
-## Installation
-
-### From Git
-
-Install the package into your current Python environment from the git repository using pip
-(usage of virtual environments is strongly encouraged!):
-
-```bash
-pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils-sfg.git"
-```
-
-````{caution}
-
-*pystencils-sfg* requires *pystencils 2.0* and is not compatible with *pystencils 1.3.x*.
-However, *pystencils 2.0* is still under development and only available as a pre-release version.
-To use *pystencils-sfg*, explicitly install *pystencils* from the v2.0 development branch:
-   
-```bash
-pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
-```
-````
-
-### From PyPI
-
-Not yet available.
-
-## Primer
-
-With *pystencils-sfg*, including your *pystencils*-generated kernels with handwritten code becomes straightforward
-and intuitive. To illustrate, generating a Jacobi smoother for the two-dimensional Poisson equation
-and mapping it onto C++23 `std::mdspan`s takes just a few lines of code:
-
-```python
-import sympy as sp
-
-from pystencils import fields, kernel
-
-from pystencilssfg import SourceFileGenerator
-from pystencilssfg.lang.cpp import std
-
-with SourceFileGenerator() as sfg:
-    u_src, u_dst, f = fields("u_src, u_dst, f(1) : double[2D]", layout="fzyx")
-    h = sp.Symbol("h")
-
-    @kernel
-    def poisson_jacobi():
-        u_dst[0,0] @= (h**2 * f[0, 0] + u_src[1, 0] + u_src[-1, 0] + u_src[0, 1] + u_src[0, -1]) / 4
-
-    poisson_kernel = sfg.kernels.create(poisson_jacobi)
-
-    sfg.function("jacobi_smooth")(
-        sfg.map_field(u_src, std.mdspan.from_field(u_src)),
-        sfg.map_field(u_dst, std.mdspan.from_field(u_dst)),
-        sfg.map_field(f, std.mdspan.from_field(f)),
-        sfg.call(poisson_kernel)
-    )
-```
-
-The script above, and the code within the region controlled by the `SourceFileGenerator`,
-constructs a C++ header/implementation file pair by describing its contents.
-We first describe our Jacobi smoother symbolically using *pystencils*
-and then pass it to the `sfg` to add it to the output file.
-Then, a wrapper function `jacobi_smooth` is defined which maps the symbolic fields onto `std::mdspan`
-objects and then executes the kernel.
-
-Take this code, store it into a file `poisson_smoother.py`, and execute the script from a terminal:
-
-```shell
-python poisson_smoother.py
-```
-
-During execution, *pystencils-sfg* assembles the above constructs into an internal representation of the C++ files.
-It then takes the name of your Python script, replaces `.py` with `.cpp` and `.hpp`,
-and exports the constructed code to the files 
-`poisson_smoother.cpp` and `poisson_smoother.hpp` into the current directory, ready to be `#include`d.
-
-````{dropdown} poisson_smoother.hpp
-
-```C++
-#pragma once
-
-#include <cstdint>
-#include <experimental/mdspan>
-
-#define RESTRICT __restrict__
-
-void jacobi_smooth(
-    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent, 1>> &f,
-    const double h,
-    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent>> &u_dst,
-    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent>> &u_src
-);
-```
-
-````
-
-````{dropdown} poisson_smoother.cpp
-
-```C++
-#include "poisson_smoother.hpp"
-
-#include <math.h>
-
-#define FUNC_PREFIX inline
-
-/*************************************************************************************
- *                                Kernels
- *************************************************************************************/
-
-namespace kernels {
-
-FUNC_PREFIX void kernel(const int64_t _size_f_0, const int64_t _size_f_1,
-                        const int64_t _stride_f_0, const int64_t _stride_f_1,
-                        const int64_t _stride_u_dst_0,
-                        const int64_t _stride_u_dst_1,
-                        const int64_t _stride_u_src_0,
-                        const int64_t _stride_u_src_1, double *const f_data,
-                        const double h, double *const u_dst_data,
-                        double *const u_src_data) {
-  const double __c_1_0o4_0 = 1.0 / 4.0;
-  for (int64_t ctr_1 = 1LL; ctr_1 < _size_f_1 - 1LL; ctr_1 += 1LL) {
-    for (int64_t ctr_0 = 1LL; ctr_0 < _size_f_0 - 1LL; ctr_0 += 1LL) {
-      u_dst_data[ctr_0 * _stride_u_dst_0 + ctr_1 * _stride_u_dst_1] =
-          __c_1_0o4_0 * u_src_data[(ctr_0 + 1LL) * _stride_u_src_0 +
-                                   ctr_1 * _stride_u_src_1] +
-          __c_1_0o4_0 * u_src_data[ctr_0 * _stride_u_src_0 +
-                                   (ctr_1 + 1LL) * _stride_u_src_1] +
-          __c_1_0o4_0 * u_src_data[ctr_0 * _stride_u_src_0 +
-                                   (ctr_1 + -1LL) * _stride_u_src_1] +
-          __c_1_0o4_0 * u_src_data[(ctr_0 + -1LL) * _stride_u_src_0 +
-                                   ctr_1 * _stride_u_src_1] +
-          __c_1_0o4_0 * (h * h) *
-              f_data[ctr_0 * _stride_f_0 + ctr_1 * _stride_f_1];
-    }
-  }
-}
-
-} // namespace kernels
-
-/*************************************************************************************
- *                                Functions
- *************************************************************************************/
-
-void jacobi_smooth(
-    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent, 1>> &f,
-    const double h,
-    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent>> &u_dst,
-    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent>> &u_src) 
-{
-  double *const u_src_data{u_src.data_handle()};
-  const int64_t _stride_u_src_0{u_src.stride(0)};
-  const int64_t _stride_u_src_1{u_src.stride(1)};
-  double *const u_dst_data{u_dst.data_handle()};
-  const int64_t _stride_u_dst_0{u_dst.stride(0)};
-  const int64_t _stride_u_dst_1{u_dst.stride(1)};
-  double *const f_data{f.data_handle()};
-  const int64_t _size_f_0{f.extents().extent(0)};
-  const int64_t _size_f_1{f.extents().extent(1)};
-  /* f.extents().extent(2) == 1 */
-  const int64_t _stride_f_0{f.stride(0)};
-  const int64_t _stride_f_1{f.stride(1)};
-  kernels::kernel(_size_f_0, _size_f_1, _stride_f_0, _stride_f_1,
-                  _stride_u_dst_0, _stride_u_dst_1, _stride_u_src_0,
-                  _stride_u_src_1, f_data, h, u_dst_data, u_src_data);
-}
-```
-
-````
-
-The above is what we call a *generator script*; a Python script that, when executed, produces a pair
-of source files of the same name, but with different extensions.
-Generator scripts are the primary front-end pattern of *pystencils-sfg*; to learn more about them,
-read the [Usage Guide](usage/generator_scripts.md).
-
-## CMake Integration
-
-*Pystencils-sfg* comes with a CMake module to register generator scripts for on-the-fly code generation.
-With the module loaded, use the function `pystencilssfg_generate_target_sources` inside your `CMakeLists.txt`
-to register one or multiple generator scripts; their outputs will automatically be added to the specified target.
-
-```CMake
-pystencilssfg_generate_target_sources( <target name> 
-    SCRIPTS kernels.py ...
-    FILE_EXTENSIONS .h .cpp
-)
-```
-
-*Pystencils-sfg* makes sure that all generated files are on the project's include path.
-To `#include` them, add the prefix `gen/<target name>`:
-
-```C++
-#include "gen/<target name>/kernels.h"
-```
-
-For details on how to add *pystencils-sfg* to your CMake project, refer to
-[the project integration guide](#guide_project_integration).
-
-## Learn To Use pystencils-sfg
-
-Here is an overview of user guides for pystencils-sfg available on this site.
-A basic understanding of [pystencils](https://pycodegen.pages.i10git.cs.fau.de/pystencils/index.html)
-is required.
-
-```{card} Writing Generator Scripts
-:link: guide:generator_scripts
-:link-type: ref
-
-Learn about *generator scripts*, the primary usage idiom of *pystencils-sfg*:
-Embedd *pystencils*-generated kernels into C++ source files and augment them with
-arbitrary C++ glue code.
-```
-
-```{card} CLI and Build System Integration
-:link: guide_project_integration
-:link-type: ref
-
-Learn how to control code generation from the command line
-and how to embedd *pystencils-sfg* into your build system.
-```
-
-```{card} Tips and Tricks
-:link: guide:tips_n_tricks
-:link-type: ref
-
-A collection of various tricks that might come in handy when working with *pystencils-sfg*.
-```
+[pycodegen]: https://pycodegen.pages.i10git.cs.fau.de
+[pystencils]: https://pycodegen.pages.i10git.cs.fau.de/docs/pystencils/2.0dev
diff --git a/docs/source/installation.md b/docs/source/installation.md
new file mode 100644
index 0000000..77c379b
--- /dev/null
+++ b/docs/source/installation.md
@@ -0,0 +1,45 @@
+# Installation and Setup
+
+## Prequesites
+
+To use pystencils-sfg, you will need at least Python 3.10.
+You will also need the appropriate compilers for building the generated code,
+such as
+ - a modern C++ compiler (e.g. GCC, clang)
+ - `nvcc` for CUDA or `hipcc` for HIP
+ - Intel OneAPI or AdaptiveCpp for SYCL
+
+Furthermore, an installation of clang-format for automatic code formatting is strongly recommended. 
+
+## Install the Latest Development Revision
+
+As pystencils-sfg is still unreleased, it can at this time only be obtained directly
+from its Git repository.
+
+Create a fresh [virtual environment](https://docs.python.org/3/library/venv.html) or activate
+an existing one. Install both the pystencils 2.0 and pystencils-sfg development revisions from Git:
+
+```{code-block} bash
+pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
+pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils-sfg.git"
+```
+
+````{caution}
+
+*pystencils-sfg* is not compatible with the *pystencils 1.3.x* releases available from PyPI;
+at the moment, you will still have to manually install the latest version of pystencils 2.0.
+````
+
+## Check your Installation
+
+To verify that the SFG was successfully installed, execute the following command:
+
+```{code-block} bash
+sfg-cli version
+```
+
+You should see an output like `0.1a4+...`.
+
+## Next Steps
+
+Move on to [](#getting_started) for a guide on how to author simple generator scripts.
diff --git a/docs/source/usage/generator_scripts.md b/docs/source/usage/generator_scripts.md
index 343fd74..59f376d 100644
--- a/docs/source/usage/generator_scripts.md
+++ b/docs/source/usage/generator_scripts.md
@@ -2,296 +2,6 @@
 (guide:generator_scripts)=
 # Generator Script Configuration and Command-Line Interface
 
-Generator scripts are the primary mode of using pystencils-sfg.
-A generator script is a Python script, say `kernels.py`, which uses the pystencils-sfg API
-at the top level that, when executed, emits source code to a pair of files `kernels.hpp`
-and `kernels.cpp`.
-This guide describes the basic structure of generator scripts, their execution from the command line,
-the configuration of the code generator both inline and from the shell,
-as well as customization of their command-line options.
-
-## Anatomy
-
-At minimum, each generator script must contain the following code:
-
-```{code-block} python
-from pystencilssfg import SourceFileGenerator
-
-with SourceFileGenerator() as sfg:
-    ...
-```
-
-The code generation process begins as the above code enters the region controlled by the
-`SourceFileGenerator`.
-The object `sfg` in the above snippet is the *composer*, which exposes the primary API
-for interacting with the code generator.
-
-
-The code generation process in a generator script is controlled by the `SourceFileGenerator` context manager.
-It configures the code generator by combining configuration options from the 
-environment (e.g. a CMake build system) with options specified in the script,
-and infers the names of the output files from the script's name.
-It then returns a {py:class}`composer <pystencilssfg.composer.SfgComposer>` to the user,
-which provides a convenient interface for constructing the source files.
-
-To start, place the following code in a Python script, e.g. `kernels.py`:
-
-```{literalinclude} examples/guide_generator_scripts/01/kernels.py
-```
-
-The source file is constructed within the context manager's managed region.
-During execution of the script, when the region ends, a header/source file pair
-`kernels.hpp` and `kernels.cpp` will be written to disk next to your script.
-Execute the script as-is and inspect the generated files, which will of course still be empty:
-
-``````{dropdown} Generated Files
-`````{tab-set}
-
-````{tab-item} kernels.hpp
-```{literalinclude} examples/guide_generator_scripts/01/kernels.hpp
-```
-````
-
-````{tab-item} kernels.cpp
-```{literalinclude} examples/guide_generator_scripts/01/kernels.cpp
-```
-````
-`````
-``````
-
-## Using the Composer
-
-The object `sfg` constructed in above snippet is an instance of [SfgComposer](#pystencilssfg.composer.SfgComposer).
-The composer is the central part of the user front-end of *pystencils-sfg*.
-It provides an interface for constructing source files that closely mimics
-C++ syntactic structures within Python.
-
-::::{dropdown} Composer API Overview
-```{eval-rst}
-.. currentmodule:: pystencilssfg.composer
-```
-
-Structure and Verbatim Code:
-
-```{eval-rst}
-
-.. autosummary::
-  :nosignatures:
-
-  SfgBasicComposer.prelude
-  SfgBasicComposer.include
-  SfgBasicComposer.namespace
-  SfgBasicComposer.code
-```
-
-Kernels and Kernel Namespaces:
-
-```{eval-rst}
-
-.. autosummary::
-  :nosignatures:
-
-  SfgBasicComposer.kernels
-  SfgBasicComposer.kernel_namespace
-  SfgBasicComposer.kernel_function
-```
-
-Function definition, parameters, and header inclusion:
-
-```{eval-rst}
-
-.. autosummary::
-  :nosignatures:
-
-  SfgBasicComposer.function
-  SfgBasicComposer.params
-  SfgBasicComposer.require
-```
-
-Variables, expressions, and variable initialization:
-
-```{eval-rst}
-
-.. autosummary::
-  :nosignatures:
-
-  SfgBasicComposer.var
-  SfgBasicComposer.vars
-  SfgBasicComposer.expr
-  SfgBasicComposer.init
-  
-  SfgBasicComposer.map_field
-  SfgBasicComposer.set_param
-```
-
-Parameter mappings:
-
-```{eval-rst}
-
-.. autosummary::
-  :nosignatures:
-
-  SfgBasicComposer.set_param
-  SfgBasicComposer.map_field
-  SfgBasicComposer.map_vector
-```
-
-Control Flow:
-
-```{eval-rst}
-
-.. autosummary::
-  :nosignatures:
-
-  SfgBasicComposer.branch
-  SfgBasicComposer.switch
-```
-
-Kernel Invocation:
-
-```{eval-rst}
-
-.. autosummary::
-  :nosignatures:
-
-  SfgBasicComposer.call
-  SfgBasicComposer.cuda_invoke
-```
-::::
-
-### Includes and Definitions
-
-With {any}`include <SfgBasicComposer.include>`, the code generator can be instructed to include header files.
-As in C++, you can use the `<>` delimiters for system headers, and omit them for project headers.
-
-`````{tab-set}
-
-````{tab-item} kernels.py
-```{literalinclude} examples/guide_generator_scripts/02/kernels.py
-```
-````
-
-````{tab-item} kernels.hpp
-```{literalinclude} examples/guide_generator_scripts/02/kernels.hpp
-```
-````
-
-````{tab-item} kernels.cpp
-```{literalinclude} examples/guide_generator_scripts/02/kernels.cpp
-```
-````
-`````
-
-### Adding Kernels
-
-[pystencils](https://pycodegen.pages.i10git.cs.fau.de/pystencils/)-generated kernels are managed in *kernel namespaces*.
-The default kernel namespace is called `kernels` and is available via
-[`sfg.kernels`](#pystencilssfg.composer.SfgBasicComposer.kernels).
-Adding an existing *pystencils* AST, or creating one from a list of assignments, is possible through 
-[`kernels.add`](#pystencilssfg.ir.SfgKernelNamespace.add)
-and
-[`kernels.create`](#pystencilssfg.ir.SfgKernelNamespace.create).
-The latter is a wrapper around
-[`pystencils.create_kernel`](
-https://pycodegen.pages.i10git.cs.fau.de/pystencils/sphinx/kernel_compile_and_call.html#pystencils.create_kernel
-).
-Both functions return a [kernel handle](#pystencilssfg.ir.SfgKernelHandle)
-through which the kernel can be accessed, e.g. for calling it in a function.
-
-To access other kernel namespaces than the default one,
-the [`sfg.kernel_namespace`](#pystencilssfg.composer.SfgBasicComposer.kernel_namespace) method can be used.
-
-`````{tab-set}
-
-````{tab-item} kernels.py
-```{literalinclude} examples/guide_generator_scripts/03/kernels.py
-```
-````
-
-````{tab-item} kernels.hpp
-```{literalinclude} examples/guide_generator_scripts/03/kernels.hpp
-```
-````
-
-````{tab-item} kernels.cpp
-```{literalinclude} examples/guide_generator_scripts/03/kernels.cpp
-```
-````
-`````
-
-### Building Functions
-
-Through the composer, you can define free functions in your generated C++ file.
-These may contain arbitrary code;
-their primary intended task however is to wrap kernel calls with the necessary boilerplate code
-to integrate them into a framework.
-The composer provides an interface for constructing functions that tries to mimic the look of the generated C++ code.
-Use `sfg.function` to create a function, and `sfg.call` to call a kernel:
-
-`````{tab-set}
-
-````{tab-item} kernels.py
-```{literalinclude} examples/guide_generator_scripts/04/kernels.py
-:start-after: start
-:end-before: end
-```
-````
-
-````{tab-item} kernels.hpp
-```{literalinclude} examples/guide_generator_scripts/04/kernels.hpp
-```
-````
-
-````{tab-item} kernels.cpp
-```{literalinclude} examples/guide_generator_scripts/04/kernels.cpp
-```
-````
-`````
-
-Note the special syntax: To mimic the look of a C++ function, the composer uses a sequence of two calls
-to construct the function.
-
-The function body can furthermore be populated with code to embedd the generated kernel into
-the target C++ application.
-If you examine the generated files of the previous example, you will notice that your
-function `scale_kernel` has lots of raw pointers and integer indices in its interface.
-We can wrap those up into proper C++ data structures,
-such as, for example, `std::span` or `std::vector`, like this:
-
-`````{tab-set}
-
-````{tab-item} kernels.py
-```{literalinclude} examples/guide_generator_scripts/05/kernels.py
-:start-after: start
-:end-before: end
-```
-````
-
-````{tab-item} kernels.hpp
-```{literalinclude} examples/guide_generator_scripts/05/kernels.hpp
-```
-````
-
-````{tab-item} kernels.cpp
-```{literalinclude} examples/guide_generator_scripts/05/kernels.cpp
-```
-````
-`````
-
-If you now inspect the generated code, you will see that the interface of your function is
-considerably simplified.
-Also, all the necessary code was added to its body to extract the low-level information required
-by the actual kernel from the data structures.
-
-The `sfg.map_field` API can be used to map pystencils fields to a variety of different data structures.
-The pystencils-sfg provides modelling support for a number of C++ standard library classes
-(see {any}`pystencilssfg.lang.cpp.std`).
-It also provides the necessary infrastructure for modelling the data structures of any C++ framework
-in a similar manner.
-
-
-## Configuration and Invocation
-
 There are several ways to affect the behavior and output of a generator script.
 For one, the `SourceFileGenerator` itself may be configured from the combination of three
 different configuration sources:
@@ -306,7 +16,7 @@ different configuration sources:
   For details on configuration modules, refer to the guide on [Project and Build System Integration](#guide_project_integration).
 
 (inline_config)=
-### Inline Configuration
+## Inline Configuration
 
 To configure the source file generator within your generator script, import the {any}`SfgConfig` from `pystencilssfg`.
 You may then set up the configuration object before passing it to the `SourceFileGenerator` constructor.
@@ -317,7 +27,7 @@ of the generator script to `gen_src`:
 ```
 
 (cmdline_options)=
-### Command-Line Options
+## Command-Line Options
 
 The `SourceFileGenerator` consumes a number of command-line parameters that may be passed to the script
 on invocation. These include:
diff --git a/src/pystencilssfg/emission/emitter.py b/src/pystencilssfg/emission/emitter.py
index 7f8870b..909ae16 100644
--- a/src/pystencilssfg/emission/emitter.py
+++ b/src/pystencilssfg/emission/emitter.py
@@ -21,7 +21,7 @@ class SfgCodeEmitter:
         self._clang_format_opts = clang_format
         self._printer = SfgFilePrinter(code_style)
 
-    def emit(self, file: SfgSourceFile):
+    def dumps(self, file: SfgSourceFile) -> str:
         code = self._printer(file)
 
         if self._code_style.get_option("includes_sorting_key") is not None:
@@ -33,6 +33,10 @@ class SfgCodeEmitter:
             code, self._clang_format_opts, sort_includes=sort_includes
         )
 
+        return code
+
+    def emit(self, file: SfgSourceFile):
+        code = self.dumps(file)
         self._output_dir.mkdir(parents=True, exist_ok=True)
         fpath = self._output_dir / file.name
         fpath.write_text(code)
diff --git a/src/pystencilssfg/generator.py b/src/pystencilssfg/generator.py
index 379b24d..ebb84b3 100644
--- a/src/pystencilssfg/generator.py
+++ b/src/pystencilssfg/generator.py
@@ -149,32 +149,35 @@ class SourceFileGenerator:
             if impl_path.exists():
                 impl_path.unlink()
 
+    def _finish_files(self) -> None:
+        if self._output_mode == OutputMode.INLINE:
+            assert self._impl_file is not None
+            self._header_file.elements.append(f'#include "{self._impl_file.name}"')
+
+        from .ir import collect_includes
+
+        header_includes = collect_includes(self._header_file)
+        self._header_file.includes = list(
+            set(self._header_file.includes) | header_includes
+        )
+        self._header_file.includes.sort(key=self._include_sort_key)
+
+        if self._impl_file is not None:
+            impl_includes = collect_includes(self._impl_file)
+            #   If some header is already included by the generated header file, do not duplicate that inclusion
+            impl_includes -= header_includes
+            self._impl_file.includes = list(
+                set(self._impl_file.includes) | impl_includes
+            )
+            self._impl_file.includes.sort(key=self._include_sort_key)
+
     def __enter__(self) -> SfgComposer:
         self.clean_files()
         return SfgComposer(self._context)
 
     def __exit__(self, exc_type, exc_value, traceback):
         if exc_type is None:
-            if self._output_mode == OutputMode.INLINE:
-                assert self._impl_file is not None
-                self._header_file.elements.append(f'#include "{self._impl_file.name}"')
-
-            from .ir import collect_includes
-
-            header_includes = collect_includes(self._header_file)
-            self._header_file.includes = list(
-                set(self._header_file.includes) | header_includes
-            )
-            self._header_file.includes.sort(key=self._include_sort_key)
-
-            if self._impl_file is not None:
-                impl_includes = collect_includes(self._impl_file)
-                #   If some header is already included by the generated header file, do not duplicate that inclusion
-                impl_includes -= header_includes
-                self._impl_file.includes = list(
-                    set(self._impl_file.includes) | impl_includes
-                )
-                self._impl_file.includes.sort(key=self._include_sort_key)
+            self._finish_files()
 
             self._emitter.emit(self._header_file)
             if self._impl_file is not None:
-- 
GitLab