Den første udgivelse af scan, et bibliotek til tekstparsing for C++ med opbygning af en endelig automat ved kompilering

Den første version af biblioteket scan (0.1.0) er offentliggjort, som analyserer tekst til værdier baseret på et mønster kendt ved kompileringstidspunktet. Mønsteret eller formatet skrives som et C++ skabelonargument og omdannes under kompileringen til en markeret deterministisk endelig automat (tagged deterministic finite automaton, TDFA), hvis tilstande udrulles til kode på kompileringstidspunktet ved hjælp af C++ skabeloner. Resultatet er en værdi af den ønskede type. Projektkoden er skrevet i C++23 og distribueres under GPLv3-licensen. import scan; // Matcher hele strengen. Hver indgang er også en rækkeadapter. scan::match(address); address | scan::match; // Med grupper. const auto found = scan::match("42-abc"); found.get().to_view(); // "42" // Begyndelsen af strengen, dækket af mønsteret, og den første match hvor som helst. scan::starts_with("abc123").whole().to_view(); // "abc" scan::search("id=4210x").to_view(); // "4210" // Alle matches og stykker imellem dem - late visninger. for (const auto& one : text | scan::search_all) { … } const auto fields = "a,bb,,ccc" | scan::split | std::ranges::to(); // Værdier, ikke tekst. struct row { int id; std::string_view name; }; const row one = scan::scan(line); // Liste, sumtyper, indlejret form. struct all { std::vector values; std::variant tail; }; const all got = scan::scan("1,2,3 abc"); // Begyndelsen af input og hvad der er tilbage. const auto [value, rest] = scan::scan_prefix(line).take(); // Én post ad gangen, fra hvad som helst. for (const row& one : scan::each(text).of()) { … }

Et interval, der kun kan læses én gang, læses uden buffering, felterne samles løbende som symboler ankommer. Hvor mange symboler der skal holdes, bestemmes af mønsteret under kompilering; hvor der ikke er noget sådant antal, afvises læsningen ved kompilering. 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()) { … } Ydeevne.

Målingerne blev udført på Ryzen 9 9950X, clang 22.1.8 med libc++, -O3 -march=native,LTO. Tredive to optegnelser pr. passage, median af syv gennemløb. «([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 de samme fem felter, hver med to hundrede tegn scan::scan<f>.sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns «first.last@subdomain.example.com» genkendelse, intet udtrækkes scan::match<p>.sentinel().scalar() 436 ns scan::match<p>.scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Sammenlægning af grupper under analysen

Typen får besked om, hvilken gruppe det næste tegn tilhører, og beregningen udføres på stedet: ingen iterationscyklus gemmes, og ingen understreng skabes. 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() skrevet manuelt 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
Kontekster og allocatorer

Enhver objekt af den kaldende part - allokator, pool, arena, hvad som helst - kan videregives til det kald, som skaber værdien. Konteksttypen går ikke tabt: scannerens tilstand og hver hook kan være skabeloner for den, så der nævnes ikke en type kaldende part i scanneren. struct arena { std::pmr::memory_resource* hvor = nullptr; std::pmr::memory_resource* resource() const { return hvor; } }; struct numbers { std::pmr::vector værdier; }; struct both { numbers venstre; numbers højre; }; template struct scan::scanner { static constexpr std::string_view mønster() { return "([0-9]+)(?:,([0-9]+))*"; } template struct tilstand { std::pmr::vector værdier; int kørende = 0; }; static tilstand begynd_grupper() { return {}; } template requires requires(const Told& én) { én.resource(); } static tilstand begynd_grupper(const Told& told) { return {std::pmr::vector(told.resource()), 0}; } template static void push_group(tilstand& én, scan::group_at, char ciffer) { én.kørende = én.kørende * 10 + (ciffer - '0'); } template static void closed_group(tilstand& én, scan::group_at) { én.værdier.push_back(én.kørende); én.kørende = 0; } template static numbers afslut_grupper(tilstand én) { return {std::move(én.værdier)}; } }; std::pmr::monotonic_buffer_resource bytes; const arena min{&bytes}; scan::scan(tekst).of(min); // én til begge steder scan::scan(tekst).of(min, scan::default_context); // én til hvert sted scan::scan(tekst).of({min, scan::default_context}); // det samme i parantes const both fået = scan::scan(tekst).with(min); // Konteksten behøver ikke at være sin egen type: allokatoren strækker sig til at // bygge læsning. struct two { std::pmr::string navn; std::pmr::string hale; }; const two beholdes = scan::scan(tekst).of<( std::pmr::polymorphic_allocator(&bytes));

Hook modtager selve objektet af den kaldende part, ikke en kopi, så tilstanden kan gemme dens adresse: konteksten lever så længe som kaldet, og al læsning sker indenfor dette kald. Andre funktioner

  • To lag: skabelonlaget (match, starts_with, search, search_all, split) og formatlaget, hvor "{}" er feltet, og værdien af pladsen bestemmes af felterne af selve typen.
  • Reglen for opløsning af tvetydighed er leftmost-first, ligesom i Perl, RE2 og CTRE.
  • Alt det nævnte virker også i konstante udtryk.
  • Der er ingen lookaround, tilbagegående referencer og Unicode-egenskaber; mønstre arbejder med bytes.
  • Mønstre, der kun kendes under kørslen, understøttes ikke.

Kompilering

Kompileres i clang og GCC. C++-moduler er ikke obligatoriske: der er tilgængelige headerfiler (include/), som genereres af demodulizer-værktøjet fra de samme moduler — hver push til main genopbygger, linker og comitterer dem tilbage. Den eneste afhængighed, Boost.PFR, er ikke nødvendig, når binding packs fra C++26 er aktiveret. 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)

Kilde: opennet.ru

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster