De eerste release van scan, een bibliotheek voor tekstanalyse in C++ met opbouw van een eindige automaat tijdens compilatie

De eerste editie van de scan-bibliotheek (0.1.0) is gepubliceerd, die tekst omzet naar waarden op basis van een patroon dat bekend is tijdens de compilatiefase. Het patroon of formaat wordt als een sjabloonargument in C++ geschreven en wordt tijdens de compilatie omgezet in een gemarkeerde deterministische eindige automaat (tagged deterministic finite automaton, TDFA), waarvan de doorgang over de toestanden in code in de compilatiefase wordt ontwikkeld met behulp van C++-sjablonen. Het resultaat is een waarde van het opgevraagde type. De projectcode is geschreven in C++23 en wordt verspreid onder de GPLv3-licentie. import scan; // Komt de gehele reeks overeen. Elke ingang is ook een adapter voor het bereik. scan::match(address); address | scan::match; // Met groepen. const auto found = scan::match("42-abc"); found.get().to_view(); // "42" // Begin van de reeks, bezet door het patroon, en de eerste overeenkomst overal. scan::starts_with("abc123").whole().to_view(); // "abc" scan::search("id=4210x").to_view(); // "4210" // Alle overeenkomsten en stukken ertussen zijn luie representaties. for (const auto& one : text | scan::search_all) { … } const auto fields = "a,bb,,ccc" | scan::split | std::ranges::to(); // Waarden, geen tekst. struct row { int id; std::string_view name; }; const row one = scan::scan(line); // Lijst, som van typen, geneste vorm. struct all { std::vector values; std::variant tail; }; const all got = scan::scan("1,2,3 abc"); // Begin van de invoer en wat er van overblijft. const auto [value, rest] = scan::scan_prefix(line).take(); // Per record, uit wat dan ook. for (const row& one : scan::each(text).of()) { … }

Een bereik dat slechts één keer kan worden gelezen, wordt gelezen zonder buffering, velden worden verzameld naarmate de karakters binnenkomen. Het aantal karakters dat moet worden vastgehouden, wordt tijdens de compilatie door het patroon bepaald; waar dit aantal ontbreekt, wordt het lezen tijdens de compilatie afgewezen. 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()) { … } Performance.

Metingen zijn uitgevoerd op Ryzen 9 9950X, clang 22.1.8 met libc++, -O3 -march=native,LTO. Tweeëndertig opnames per doorgang, mediaan van zeven doorgangen. «([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 dezelfde vijf velden, elk met tweehonderd letters scan::scan<f>.sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns «first.last@subdomain.example.com» herkenning, niets wordt geëxtraheerd scan::match<p>.sentinel().scalar() 436 ns scan::match<p>.scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Groepering van groepen tijdens de parsing

Het type wordt geïnformeerd over welke van zijn groepen het huidige teken toebehoort, en de berekening vindt ter plaatse plaats: geen enkele iteratie van de lus wordt opgeslagen, er wordt geen enkele substring gemaakt. 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»

heap scan::scan.scalar() handmatig geschreven scan::scan 10 141 ns 41.4 ns 42.5 ns 170 ns 100 439 ns 341 ns 392 ns 1176 ns 1000 3314 ns 3170 ns 3797 ns 11887 ns
Contexten en allocators

Elk object van de oproeper — een allocator, pool, arena, noem maar op — kan worden doorgegeven aan die aanroep die een waarde creëert. Het type van de context gaat hierbij niet verloren: de toestand van de scanner en elke haak kunnen sjablonen ervan zijn, zodat geen enkel type van de oproepende kant in de scanner wordt genoemd. 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); // één voor beide plaatsen scan::scan(text).of(mine, scan::default_context); // één voor één op plaats scan::scan(text).of({mine, scan::default_context}); // hetzelfde in haakjes const both got = scan::scan(text).with(mine); // De context hoeft niet zijn eigen type te zijn: de allocator komt zover dat hij de lezing opbouwt. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan(text).of<( std::pmr::polymorphic_allocator(&bytes));

De haak ontvangt het object van de oproeper zelf, en niet een kopie, zodat de toestand zijn adres kan opslaan: de context leeft zolang als de oproep, en alle lezing vindt binnen deze oproep plaats. Overige kenmerken

  • Twee lagen: een sjabloonlaag (match, starts_with, search, search_all, split) en een formaatlaag, waar "{}" een veld is en de waarden van de plaats de velden van het type zelf bepalen.
  • De regel voor het oplossen van ambiguïteit is leftmost-first, zoals in Perl, RE2 en CTRE.
  • Alles wat is genoemd, werkt ook in constante uitdrukkingen.
  • Geen lookaround, terugverwijzingen en Unicode-eigenschappen; sjablonen werken met bytes.
  • Sjablonen die alleen tijdens runtime bekend zijn, worden niet ondersteunt.

Bouwen

Gebouwd met clang en GCC. C++-modules zijn niet verplicht: er zijn headerbestanden (include/) beschikbaar, die worden gegenereerd door de demodulizer-tool uit dezelfde modules — elke push naar main bouwt, linkt en commit deze terug. De enige afhankelijkheid, Boost.PFR, is niet nodig bij ingeschakelde binding packs van C++26. 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)

Bron: opennet.ru

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster