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

7.1 KiB

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:

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:

#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:

#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:

/* ---- 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:

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. 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_CASEs 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.