Files
paulhorn db41258d16 feat: PDA als libpda + pda nach P1204R0
Ersetzt das Geruest (counter/notebook) durch ein echtes Beispiel und teilt
es in zwei eigenstaendige Projekte, wie P1204R0 es fuer Bibliothek plus
Programm verlangt.

libpda: textfile (C, Datei-I/O und Feld-Escaping), calculator
(Recursive-Descent-Parser mit std::expected), contact, editor, explorer.
Exportiert pda::pda ueber install(EXPORT) und ist per find_package(pda)
benutzbar.

pda: Shell ohne Ein-/Ausgabe (execute() gibt Text zurueck, deshalb ohne
Terminal testbar), REPL und Stapelbetrieb in main.cpp.

examples/consumer und tools/check-install.sh pruefen die Export-Kette
Ende-zu-Ende: installieren, dann ein fremdes Projekt dagegen bauen.

docs/CMAKE.md erklaert das Target-Modell, PUBLIC/PRIVATE/INTERFACE,
Generator-Ausdruecke, install/export und Symbolsichtbarkeit an diesem
Projekt.

71 Tests gruen unter Homebrew-clang 22, Apple clang und GCC 16, statisch
und shared, mit und ohne ASan/UBSan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 16:39:35 +02:00

190 lines
7.1 KiB
Markdown

# Konventionen
Was `clang-format` und `clang-tidy` nicht erzwingen können, steht hier.
Mechanisch erzwungen wird bisher nur, was `clang-format` und `clang-tidy`
abdecken. Ein `check_style.sh` gibt es in diesem Repo **nicht** (die
CONVENTIONS.md von mydb verweist darauf, aber die Datei existiert dort
ebenfalls nicht) — die folgenden Regeln gelten also per Konvention.
## Projektstruktur (P1204R0)
Zwei eigenständige Projekte unter einem Superprojekt:
```
playground/ Superprojekt, kein eigener Quellcode
├── libpda/ project(libpda) -> libpda.a
│ ├── libpda/ Quellcode UND Header zusammen
│ │ ├── textfile.h public API (C)
│ │ ├── textfile.c
│ │ ├── textfile.test.c Unit-Test, direkt neben dem Modul
│ │ ├── contact.hpp public API (C++)
│ │ ├── contact.cpp
│ │ ├── contact.test.cpp
│ │ └── details/ Implementation Details, nicht public API
│ └── tests/basics/ Integrationstests gegen die öffentliche API
├── pda/ project(pda) -> bin/pda
│ ├── pda/ shell.hpp/.cpp, main.cpp
│ └── tests/session/
├── examples/consumer/ fremdes Projekt, find_package(pda)
├── third_party/ vendorter Fremdcode, nie editieren
├── cmake/ ProjectDefaults, Warnings, Sanitizers, pdaConfig.cmake.in
└── docs/ CMAKE.md, diese Datei, ADRs
```
**Kein `include/` + `src/`.** Header und Implementierung liegen nebeneinander.
Bei Templates, `inline` und Modulen ist die Trennung ohnehin nicht sauber zu
ziehen, und der Verzeichnisname übernimmt die Rolle des Namespace im
Dateisystem.
**Bibliothek und Programm sind getrennte Projekte.** P1204R0 verlangt das; der
praktische Grund ist, dass `libpda` ohne `pda` baubar sein muss. Die Probe:
```sh
cmake -S libpda -B build/nur-lib && cmake --build build/nur-lib
```
**Ein Namespace, zwei Include-Wurzeln.** Beide Projekte benutzen
`namespace pda` — auseinandergehalten wird über den Pfad:
```cpp
#include <libpda/contact.hpp> // Bibliothek
#include <pda/shell.hpp> // Anwendung
```
Das ist genau das Schema aus P1204R0 (`libhello` / `hello`): das `lib`-Präfix
steht im Projekt- und Verzeichnisnamen, nicht im Namespace.
## Includes
**Eigene Header immer mit spitzen Klammern und Projektpräfix:**
```c
#include <libpda/textfile.h> /* richtig */
#include "textfile.h" /* falsch */
```
Das ist P1204R0s wichtigste Einzelregel. Spitze Klammern durchsuchen nur die
`-I`-Pfade, niemals das Verzeichnis der inkludierenden Datei — ein fehlender
oder falsch installierter Header fällt damit sofort auf, statt dass zufällig
ein gleichnamiger Nachbar gefunden wird.
Reihenfolge macht `clang-format` (`IncludeBlocks: Regroup`): eigener Header,
Projekt-Header, Test-Framework, C-Standardbibliothek, C++-Standardbibliothek,
Rest.
## Extensions
| Extension | Bedeutung |
|---|---|
| `.h` / `.c` | C |
| `.hpp` / `.cpp` | C++ |
| `.test.c` / `.test.cpp` | Unit-Test neben dem getesteten Modul |
Die Extension sagt die Sprache. In einem gemischten Projekt ist das der Punkt —
ein `.h` ist aus C **und** C++ benutzbar (der `extern "C"`-Block im Header),
ein `.hpp` nur aus C++.
## Namen
| Was | Form | Beispiel |
|---|---|---|
| Typen | `PascalCase` | `ContactBook`, `TextBuffer`, `Explorer` |
| Funktionen, Variablen | `snake_case` | `human_size`, `line_count` |
| Makros | `UPPER_CASE` mit Projektpräfix | `LIBPDA_TEXTFILE_H`, `PDA_EXPORT` |
| C-API | Modulpräfix | `textfile_read`, `textfile_write`, `textfile_escape` |
| Out-Parameter | `out_`-Präfix | `size_t* out_len` |
| Member | blank, kein `m_`, kein `_` | `entries`, `rows`, `here` |
| Namespace | Projektname ohne `lib` | `namespace pda` |
## Kommentare
Ausschließlich `/* */`, niemals `//` — auch nicht einzeilig. Abschnitte werden
so getrennt:
```c
/* ---- API ---- */
```
Kommentare sind auf Deutsch, **Umlaute transliteriert** (`ae oe ue ss`), damit
die Quellen reines ASCII bleiben. README und öffentliche Header auf Englisch.
Prüfen lässt sich das mit:
```sh
grep -rlP '[^\x00-\x7F]' playground tests
```
## Header
`#ifndef LIBPDA_TEXTFILE_H` — Include-Guards, kein `#pragma once`.
Öffentliche C-Header bekommen einen `extern "C"`-Block, damit die C++-Seite sie
benutzen kann.
Structs, deren Layout niemanden angeht, sind opak: `typedef struct Page Page;`
im Header, die Definition in der `.c`.
## Tests
**Unit-Test** (`<modul>.test.c` / `.test.cpp`) liegt neben dem Modul, kennt
dessen Interna und ist eine eigenständige Executable.
- C → Unity, registriert als CTest `unity.<modul>` (hier: `unity.textfile`)
- C++ → doctest, `doctest_discover_tests()` legt **einen CTest-Eintrag pro
`TEST_CASE`** an
**Integrationstest** (`tests/<name>/driver.c` oder `.cpp`) benutzt nur die öffentliche API,
kein Framework, Ergebnis über den Exit-Code. Er würde genauso gegen eine
installierte Bibliothek laufen.
TDD-Zyklus: RED (Test schlägt aus dem richtigen Grund fehl) → GREEN (kleinste
Implementierung) → REFACTOR.
## Build
Die ausführliche Erklärung steht in [CMAKE.md](CMAKE.md). Das Wichtigste:
- Öffentliche Deklarationen der Bibliothek brauchen `PDA_EXPORT`, sonst
exportiert der Shared-Bau sie nicht.
- Neue Dateien immer von Hand in `LIBPDA_SOURCES` / `LIBPDA_HEADERS`.
Kein `file(GLOB)`. Neue Dateien werden in `CMakeLists.txt` eingetragen — CMake
merkt sonst nicht, dass eine Datei dazugekommen ist, der Build bleibt grün und
die Datei fehlt einfach.
| Preset | Zweck |
|---|---|
| `debug` | Alltag |
| `release` | RelWithDebInfo |
| `asan-ubsan` | AddressSanitizer + UBSan |
| `apple-clang` | Portabilitätsprobe (macOS-System-Compiler) |
| `homebrew-gcc` | Portabilitätsprobe (GCC-Frontend) |
| `linux-gcc` | nur auf Linux sichtbar |
`build/<preset>/` — die Tiefe ist bindend: der neotest-CTest-Adapter sucht nur
drei Ebenen tief, und der Neovim-Target-Picker leitet den File-API-Pfad daraus ab.
## Commits
Conventional Commits: `feat:`, `fix:`, `test:`, `refactor:`, `chore:`, `docs:`.
Aktuell ohne Hook — es gibt kein `check_style.sh`, das einen installieren
könnte.
## Zwei Dinge, die wie Fehler aussehen, aber keine sind
**Nach einem frischen Clone zeigt clangd nichts.** Es gibt noch kein
`compile_commands.json`. Einmal `<leader>oc` (configure), dann ist alles da.
Neovim weist beim Öffnen einer C/C++-Datei darauf hin.
**Der Testbaum (`<leader>ts`) ist nach einem frischen Clone leer.**
`doctest_discover_tests()` ruft die Test-Executable beim **Build** auf, um die
`TEST_CASE`s aufzulisten. Vor dem ersten Build kennt CTest sie also nicht. Einmal
`<leader>b`, dann füllt sich der Baum.
## Rückfallebene für die C-Tests
Der neotest-Adapter für Unity (`nvim-local/neotest-unity`) ist selbst
geschrieben, weil `neotest-ctest` `.c`-Dateien grundsätzlich nicht sieht. Wenn er
je zur Last wird: aus der `adapters`-Liste in `nvim/lua/plugins/neotest.lua`
entfernen, neotest bleibt für C++ zuständig, und die C-Tests laufen über
`<leader>ot` (overseer → ctest → Quickfix). Dann fehlt der Rot/Grün-Baum für C,
aber ein Tastendruck und Sprung zur Fehlerstelle bleiben.