Prvý release scan, knižnice na analýzu textu pre C++ s konštrukciou konečného automatu pri kompilácii

Bol spustený prvý výstup knižnice scan (0.1.0), ktorá analyzuje text na hodnoty podľa šablóny známej už v čase kompilácie. Šablóna alebo formát je zadaný ako argument šablóny C++ a pri kompilácii sa mení na označený deterministický konečný automat (tagged deterministic finite automaton, TDFA), ktorého prechádzanie sa rozvinie do stavov v kóde v čase kompilácie pomocou šablón C++. Výsledkom vykonávania je hodnota požadovaného typu. Kód projektu je napísaný v C++23 a je distribuovaný pod licenciou GPLv3. import scan; // Zhoduje sa celá reťazec. Každý vstup — tiež adaptér rozsahu. scan::match(address); address | scan::match; // S skupinami. const auto found = scan::match("42-abc"); found.get().to_view(); // "42" // Začiatok reťazca zaplnený šablónou a prvé zhodnotenie kdekoľvek. scan::starts_with("abc123").whole().to_view(); // "abc" scan::search("id=4210x").to_view(); // "4210" // Všetky zhody a časti medzi nimi — lenivá zobrazenia. for (const auto& one : text | scan::search_all) { … } const auto fields = "a,bb,,ccc" | scan::split | std::ranges::to(); // Hodnoty, nie text. struct row { int id; std::string_view name; }; const row one = scan::scan(line); // Zoznam, suma typov, vnorená forma. struct all { std::vector values; std::variant tail; }; const all got = scan::scan ("1,2,3 abc"); // Začiatok vstupu a to, čo z neho zostalo. const auto [value, rest] = scan::scan_prefix(line).take(); // Po zázname za raz, z čohokoľvek. for (const row& one : scan::each(text).of()) { … }

Rozsah, ktorý možno prečítať iba raz, sa číta bez vyrovnávania, polia sa zhromažďujú podľa prichádzajúcich znakov. Koľko znakov sa má uchovávať, je určené šablónou pri kompilácii; tam, kde také číslo nie je, je čítanie odmietnuté pri kompilácii. 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()) { … } Výkon.

Merania boli vykonané na Ryzen 9 9950X, clang 22.1.8 s libc++, -O3 -march=native, LTO. Tridsať dva zápisov za prechod, mediána siedmich prechodov. «([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 tie isté piatich polí, po dvesto znakov každé scan::scan<f>.sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns «first.last@subdomain.example.com» rozpoznávanie, nič nevyťažuje scan::match<p>.sentinel().scalar() 436 ns scan::match<p>.scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Zúženie skupín počas analýzy

Typu sa oznamuje, do ktorej skupiny patrí aktuálny symbol, a výpočet sa vykonáva na mieste: žiadna iterácia cyklu sa neukladá, žiadny podreťazec sa nevytvára. 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=(__X_XX)abcdefgh").of(); // got.number.value == 12, got.tail == "abcdefgh"

heap scan::scan.scalar() napísané manuálne 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
Kontexty a alokátory

Akýkoľvek objekt volajúcej strany — alokátor, pool, aréna, čokoľvek iné — môže byť odovzdaný do toho volania, ktoré vytvára hodnotu. Typ kontextu pritom nezaniká: stav skenera a každý jeho hák môžu byť šablónami podľa neho, takže v skeneri nie je uvedený žiadny typ volajúcej strany. 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); // jeden na obe miesta scan::scan(text).of(mine, scan::default_context); // po jednom na miesto scan::scan(text).of({mine, scan::default_context}); // to isté v zátvorkách const both got = scan::scan(text).with(mine); // Kontext nemusí byť svojím typom: alokátor sa dostáva až k // tomu, že vytvára čítanie. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan(text).of<( std::pmr::polymorphic_allocator(&bytes));

Hák dostáva samotný objekt volajúcej strany, a nie kópiu, preto môže stav uchovávať jeho adresu: kontext žije tak dlho, ako volanie, a všetko čítanie sa deje v rámci tohto volania. Ďalšie vlastnosti

  • Dva vrstvy: vrstva šablón (match, starts_with, search, search_all, split) a vrstva formátov, kde «{}» je pole a hodnoty miest určujú pole samotného typu.
  • Pravidlo rozlíšenia nejednoznačnosti — leftmost-first, ako v Perl, RE2 a CTRE.
  • Všetko uvedené funguje aj v konštantných výrazoch.
  • Žiadne lookaround, spätné odkazy a vlastnosti Unicode; šablóny fungujú s bajtmi.
  • Nepodporujú sa šablóny, ktoré sú známe iba počas behu.

Zostavenie

Skladá sa v clang a GCC. Moduly C++ nie sú povinné: sú k dispozícii hlavičkové súbory (include/), ktoré generuje z týchto modulov nástroj demodulizer — každý push do main prekompiluje, linkuje a znovu ich commitne. Jediná závislosť, Boost.PFR, nie je potrebná pri zapnutých binding packs z 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)

Zdroj: opennet.ru

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster