První vydání scan, knihovny pro analýzu textu v C++ s konstrukcí konečného automatu při kompilaci

Byl zveřejněn první vydání knihovny scan (0.1.0), která analyzuje text na hodnoty podle šablony známé v době kompilace. Šablona nebo formát se zapisuje jako argument šablony C++ a při kompilaci se přetváří na označený deterministický konečný automat (tagged deterministic finite automaton, TDFA), jehož procházení se při kompilaci rozbaluje do stavů v kódu pomocí šablon C++. Výsledkem je hodnota požadovaného typu. Kód projektu je napsán v C++23 a šíří se pod licencí GPLv3. import scan; // Odpovídá-li celému řetězci. Každý vstup je také adaptér rozsahu. scan::match<«[a-z]+@[a-z.]+»>(address); address | scan::match<«[a-z]+@[a-z.]+»>; // S skupinami. const auto found = scan::match<«([0-9]+)-([a-z]+)»>(«42-abc»); found.get<1>().to_view(); // «42» // Začátek řetězce obsazený šablonou a první shoda kdekoliv. scan::starts_with<«[a-z]+»>(«abc123»).whole().to_view(); // «abc» scan::search<«[0-9]+»>(«id=4210x»).to_view(); // «4210» // Všechny shody a kousky mezi nimi — lenivé reprezentace. for (const auto& one : text | scan::search_all<«[a-z]+»>) { … } const auto fields = «a,bb,,ccc» | scan::split<«,»> | std::ranges::to<std::vector>(); // Hodnoty, nikoliv text. struct row { int id; std::string_view name; }; const row one = scan::scan<«{},{[a-z]+}»>(line); // Seznam, suma typů, vnořená forma. struct all { std::vector<int> values; std::variant<int, std::string_view> tail; }; const all got = scan::scan<«{{}{*,?}} {{[0-9]+}|{[a-z]+}}»>(«1,2,3 abc»); // Začátek vstupu a to, co od něj zůstalo. const auto [value, rest] = scan::scan_prefix<«{},{}»>(line).take<point>(); // Po záznamu najednou, z čehokoliv. for (const row& one : scan::each<«{},{[a-z]+}\\n»>(text).of<row>()) { … }

Jednorázově čitelný rozsah se čte bez vyrovnávací paměti, pole jsou shromažďována v průběhu příjmu znaků. Kolik znaků bude třeba udržet, určuje šablona při kompilaci; tam, kde toto číslo není, je čtení odmítnuto při kompilaci. std::istringstream source(«set speed 42\\nset gain 7\\n»); source > std::noskipws; struct command { scan::held<16> name; int value; }; for (const command& one : scan::each<«set {[a-z]+} {[0-9]+}\\n»>(std::views::istream<char>(source)) .of<command>()) { … } Výkon.

Měření byla provedena na Ryzen 9 9950X, clang 22.1.8 s libc++, -O3 -march=native,LTO. Třicet dva záznamů na průchod, medián sedmi průchodů. «([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 stejných pět polí, po dvě stě písmen každé scan::scan<f>.sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns «first.last@subdomain.example.com» rozpoznávání, nic není extrahováno scan::match<p>.sentinel().scalar() 436 ns scan::match<p>.scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Seskupení skupin při analýze

Typu se oznamuje, do které ze svých skupin patří daný znak, přičemž výpočet probíhá na místě: žádná iterace cyklu není uchovávána, žádný podřetězec není vytvářen. 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() napsáno ručně 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

Jakýkoli objekt volající strany — alokátor, pool, aréna, cokoliv jiného — může být předán do toho volání, které vytváří hodnotu. Typ kontextu při tom neztrácí: stav skeneru a každý jeho hook může být šablonou podle něj, takže ve skeneru není zmíněn žádný typ volající 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 pro oba místa scan::scan(text).of(mine, scan::default_context); // po jednom na místo scan::scan(text).of({mine, scan::default_context}); // to samé v závorkách const both got = scan::scan(text).with(mine); // Kontext nemusí být svého typu: alokátor dosahuje toho, // co buduje čtení. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan(text).of<( std::pmr::polymorphic_allocator(&bytes));

Hook přijímá samotný objekt volající strany, nikoliv kopii, takže stav může uchovávat jeho adresu: kontext žije tak dlouho, jak se volání, a veškeré čtení probíhá uvnitř tohoto volání. Další vlastnosti

  • Dva vrstvy: vrstva šablon (match, starts_with, search, search_all, split) a vrstva formátů, kde „{}“ — pole, a hodnoty místa určují pole samotného typu.
  • Pravidlo rozlišení nejednoznačnosti — leftmost-first, jako v Perl, RE2 a CTRE.
  • Všechny uvedené funkce fungují také v konstantních výrazech.
  • Žádné lookaround, zpětné odkazy a vlastnosti Unicode; šablony pracují s bajty.
  • Nebudou podporovány šablony, které jsou známy pouze během běhu.

Sestavení

Sestavuje se v clang a GCC. Moduly C++ nejsou povinné: k dispozici jsou hlavičkové soubory (include/), které generuje z těchto modulů utilita demodulizer — každý push do main je znovu sestavuje, linkuje a commituje zpět. Jediná závislost, Boost.PFR, není potřebná při 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

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster