Die erste Ausgabe der scan-Bibliothek (0.1.0) wurde veröffentlicht, die Text in Werte entsprechend einem zur Kompilierungszeit bekannten Muster analysiert. Das Muster oder Format wird als Template-Argument in C++ ĂŒbergeben und wird zur Kompilierungszeit in einen gekennzeichneten deterministischen Endzustand-Automaten (tagged deterministic finite automaton, TDFA) umgewandelt, dessen Durchlauf zu Kompilierungszeit mit Hilfe von C++-Templates in Code umgesetzt wird. Das Ergebnis der AusfĂŒhrung ist ein Wert des angeforderten Typs. Der Projektcode ist in C++23 geschrieben und steht unter der Lizenz GPLv3. import scan; // Entspricht die gesamte Zeile. Jedes Eingangssignal ist zudem ein Bereichsadapter. scan::match(address); address | scan::match; // Mit Gruppen. const auto found = scan::match("42-abc"); found.get().to_view(); // "42" // Anfang der Zeile, belegt durch das Muster, und erstes Ăbereinstimmen wo auch immer. scan::starts_with("abc123").whole().to_view(); // "abc" scan::search("id=4210x").to_view(); // "4210" // Alle Ăbereinstimmungen und StĂŒcke dazwischen â faule Darstellungen. for (const auto& one : text | scan::search_all) { ⊠} const auto fields = "a,bb,,ccc" | scan::split | std::ranges::to(); // Werte, nicht Text. struct row { int id; std::string_view name; }; const row one = scan::scan(line); // Liste, Summe von Typen, verschachtelte Form. struct all { std::vector values; std::variant tail; }; const all got = scan::scan("1,2,3 abc"); // Beginn der Eingabe und das, was von ihr ĂŒbrig bleibt. const auto [value, rest] = scan::scan_prefix(line).take(); // Nach der Aufnahme einmal, aus was auch immer. for (const row& one : scan::each(text).of()) { ⊠}
Der Bereich, der nur einmal gelesen werden kann, wird ohne Pufferung gelesen, Felder werden wĂ€hrend des Eingangs von Zeichen gesammelt. Wie viele Zeichen gehalten werden mĂŒssen, legt das Muster zur Kompilierungszeit fest; wo eine solche Anzahl nicht vorhanden ist, wird das Lesen zur Kompilierungszeit abgelehnt. std::istringstream source("set speed 42\\nset gain 7\\n"); source > std::noskipws; struct command { scan::held name; int value; }; for (const command& one : scan::each(std::views::istream(source)).of()) { ⊠} Leistung.
Die Messungen wurden auf einem Ryzen 9 9950X, clang 22.1.8 mit libc++, -O3 -march=native, LTO durchgeführt. Zweiunddreißig Aufzeichnungen pro Durchgang, Median von sieben Durchgängen. «([a-z]+),([a-z]+),([a-z]+),([a-z]+),([a-z]+)» «alpha,bravo,charlie,delta,echo» scan::scan<f>.sentinel() 477 ns re2c 554 ns scan::scan<f> 659 ns CTRE 717 ns RE2 15986 ns dieselben fünf Felder, jeweils zweihundert Buchstaben scan::scan<f>.sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns «first.last@subdomain.example.com» Erkennung, es wird nichts extrahiert scan::match<p>.sentinel().scalar() 436 ns scan::match<p>.scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Zusammenfassung von Gruppen wĂ€hrend der Analyse
Der Typ erhÀlt die Zugehörigkeit des nÀchsten Zeichens zu einer seiner Gruppen, und die Berechnung erfolgt im Vorfeld: keine Iteration der Schleife wird gespeichert, es wird keine Teilzeichenkette erzeugt. struct tally { unsigned long value = 0; }; struct reading { tally number; std::string_view tail; }; template struct scan::scanner { static constexpr std::string_view pattern() { return R»(\\(((_+)(X|Y)*)*\\))»; } struct state_type { unsigned long total = 0; unsigned place = 0, marks = 0; }; static constexpr state_type begin_groups() { return {}; } static constexpr void opened_group(state_type& one, scan::group_at) { one.place = 0; one.marks = 0; } static constexpr void closed_group(state_type& one, scan::group_at) { unsigned long weight = 1; for (unsigned step = 1; step < one.place; ++step) weight *= 10; one.total += weight * one.marks; } static constexpr void push_group(state_type& one, scan::group_at, char) { ++one.place; } static constexpr void push_group(state_type& one, scan::group_at, char letter) { one.marks += letter == 'Y' ? 2u : 1u; } static constexpr tally finish_groups(state_type one) { return {one.total}; } }; const reading got = scan::scan«value={}{[a-z]*}»(«value=(__X_XX)abcdefgh»).of(); // got.number.value == 12, got.tail == «abcdefgh»
Jedes Objekt der aufrufenden Seite â Allokator, Pool, Arena, was auch immer â kann an den Aufruf ĂŒbergeben werden, der den Wert erstellt. Der Typ des Kontexts bleibt dabei erhalten: Der Zustand des Scanners und jeder seiner Hooks können Vorlagen gemÀà diesem Typ sein, sodass im Scanner kein Typ der aufrufenden Seite erwĂ€hnt wird. struct arena { std::pmr::memory_resource* where = nullptr; std::pmr::memory_resource* resource() const { return where; } }; struct numbers { std::pmr::vector values; }; struct both { numbers left; numbers right; }; template struct scan::scanner { static constexpr std::string_view pattern() { return "([0-9]+)(?:,([0-9]+))*"; } template struct state { std::pmr::vector values; int running = 0; }; static state begin_groups() { return {}; } template requires requires(const Told& one) { one.resource(); } static state begin_groups(const Told& told) { return {std::pmr::vector(told.resource()), 0}; } template static void push_group(state& one, scan::group_at, char digit) { one.running = one.running * 10 + (digit - '0'); } template static void closed_group(state& one, scan::group_at) { one.values.push_back(one.running); one.running = 0; } template static numbers finish_groups(state one) { return {std::move(one.values)}; } }; std::pmr::monotonic_buffer_resource bytes; const arena mine{&bytes}; scan::scan(text).of(mine); // einer fĂŒr beide PlĂ€tze scan::scan(text).of(mine, scan::default_context); // einer pro Platz scan::scan(text).of({mine, scan::default_context}); // das gleiche in Klammern const both got = scan::scan(text).with(mine); // Der Kontext muss nicht unbedingt seinen Typ haben: Der Allokator geht so weit, // dass er das Lesen aufbaut. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan(text).of<( std::pmr::polymorphic_allocator(&bytes));
Der Hook erhÀlt das Objekt der aufrufenden Seite und nicht eine Kopie, sodass der Zustand seine Adresse speichern kann: Der Kontext lebt so lange wie der Aufruf, und das gesamte Lesen findet innerhalb dieses Aufrufs statt. Weitere Merkmale
- Zwei Schichten: die Schicht der Vorlagen (match, starts_with, search, search_all, split) und die Schicht der Formate, wobei "{}" das Feld ist und die Werte des Ortes durch die Felder des Typs selbst bestimmt werden.
- Die Regel zur Auflösung von Mehrdeutigkeiten ist leftmost-first, wie in Perl, RE2 und CTRE.
- Alles AufgezĂ€hlte funktioniert auch in konstanten AusdrĂŒcken.
- Es gibt kein Lookaround, keine RĂŒckverweise und keine Unicode-Eigenschaften; Vorlagen arbeiten mit Bytes.
- Vorlagen, die nur zur Laufzeit bekannt sind, werden nicht unterstĂŒtzt.
Bau
Kompatibel mit Clang und GCC. C++-Module sind nicht erforderlich: Es stehen Header-Dateien (include/) zur VerfĂŒgung, die von dem gleichen Modul-Tool, dem Demodulisierer, erzeugt werden - jeder Push in Main fĂŒhrt zu einer NeuĂŒbersetzung, Verlinkung und RĂŒckcommitierung. Die einzige AbhĂ€ngigkeit, Boost.PFR, ist nicht erforderlich, wenn die Binding-Packs aus C++26 aktiviert sind. FetchContent_Declare(scan GIT_REPOSITORY https://github.com/j4niwzis/scan.git GIT_TAG v0.1.0) FetchContent_MakeAvailable(scan) target_link_libraries(mine PRIVATE scan::scan)
Quelle: opennet.ru
