Numri i parë i scan, bibliotekës për analizën e tekstit në C++ me ndërtimin e automatos finite gjatë kompilimit

Është publikuar numri i parë i bibliotekës scan (0.1.0), e cila interpreton tekst në vlera sipas një shablli të njohur gjatë fazës së kompilimit. Shablli ose formati shkruhet si një argument shablli C++ dhe shndërrohet gjatë kompilimit në një automat të fundëm të shënuar (tagged deterministic finite automaton, TDFA), kalimi i të cilit zhvillohet përmes gjendjeve në kod gjatë fazës së kompilimit duke përdorur shabllonat C++. Rezultati i ekzekutimit është vlera e llojit të kërkuar. Koodi i projektit është shkruar në C++23 dhe shpërndahet nën licencën GPLv3. import scan; // A është i barabartë e gjithë vargu. Çdo pikë hyrëse është gjithashtu një adapteri diapazoni. scan::match(address); address | scan::match<"[a-z]+@[a-z.]+"}; // Me grupe. const auto found = scan::match("42-abc"); found.get().to_view(); // "42" // Fillimi i vargut, i zënë me shabllon, dhe e para përputhje kudo. scan::starts_with("abc123").whole().to_view(); // "abc" scan::search("id=4210x").to_view(); // "4210" // Të gjitha përputhjet dhe copat midis tyre - paraqitje të lenjshme. for (const auto& one : text | scan::search_all) { ... } const auto fields = "a,bb,,ccc" | scan::split | std::ranges::to(); // Vlerat, jo tekst. struct row { int id; std::string_view name; }; const row one = scan::scan(line); // Lista, shuma e llojeve, forma e thelluar. struct all { std::vector values; std::variant tail; }; const all got = scan::scan("1,2,3 abc"); // Fillimi i inputit dhe çfarë ka mbetur. const auto [value, rest] = scan::scan_prefix(line).take(); // Duke regjistruar një herë, nga çfarëdo. for (const row& one : scan::each(text).of()) { ... }

Një diapazon, që mund të lexohet vetëm një herë, lexohet pa buferim, fushat mblidhen gjatë pranisë së simboleve. Sa simbole do të mbahen, shablli e përcakton gjatë kompilimit; aty ku nuk ka një numër të tillë, leximet zbatohen gjatë kompilimit. 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()) { ... } Performanca.

Matjet janë realizuar në Ryzen 9 9950X, clang 22.1.8 me libc++, -O3 -march=native, LTO. Tridhjetë e dy regjistrime për kalim, mediana e shtatë kalimeve. «([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 të njëjtat pesë fusha, nga dyqind shkronja çdo një scan::scan<f>.sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns «first.last@subdomain.example.com» njohja, asgjë nuk nxirret scan::match<p>.sentinel().scalar() 436 ns scan::match<p>.scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Përmbledhja e grupeve gjatë analizës

Tipi informon se cilës grupi i përket karakteri i radhës dhe llogaritja kryhet në vend: asnjë iteracion i ciklit nuk ruhet, asnjë nënkarakter nuk krijohet. 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() shkruar manualisht 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
Kontekste dhe alokatorë

Çdo objekt i thirrjes — alokatori, puli, arena, çfarëdo tjetër — mund të kalojë në atë thirrje që krijon një vlerë. Lloji i kontekstit këtu nuk humbet: gjendja e skanerit dhe çdo thirrje e tij mund të jenë shabllone sipas tij, kështu që asnjë lloj i thirrjes nuk përmendet në skaner. 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); // një për të dy vendet scan::scan(text).of(mine, scan::default_context); // nga një për vend scan::scan(text).of({mine, scan::default_context}); // të njëjtën në kllapa const both got = scan::scan(text).with(mine); // Konteksti nuk është e detyrueshme të jetë tipi i tij: alokatori arrin deri në atë // që ndihmon në ndërtimin e leximit. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan(text).of<( std::pmr::polymorphic_allocator(&bytes));

Huka merr vetë objektin e thirrjes, e jo një kopje, kështu që gjendja mund të ruajë adresën e tij: konteksti jeton aq sa thirrja, dhe të gjithë leximi ndodh brenda kësaj thirrjeje. Veçori të tjera

  • Dy shtresa: shtresa e shablloneve (match, starts_with, search, search_all, split) dhe shtresa e formateve, ku «{}» është fusha, dhe vlera e vendit përcaktohet nga fushat e vetë tipit.
  • Rregulli i zgjidhjes së dyshimeve është leftmost-first, si në Perl, RE2 dhe CTRE.
  • E gjithë kjo funksionon gjithashtu në shprehje konstante.
  • Nuk ka lookaround, referenca prapa dhe tipare Unicode; shabllonet punojnë me byte.
  • Nuk mbështeten shabllonet që janë të njohura vetëm gjatë ekzekutimit.

Ndërtimi

Kompilohet në clang dhe GCC. Modul C++ nuk është i detyrueshëm: janë të disponueshme skedarë titujsh (include/) që gjenerohet nga të njëjtit module me utilitarin demodulizer — çdo push në main e rikompilon, lidhen dhe i komiton mbrapsht. Varësia e vetme, Boost.PFR, nuk është e nevojshme kur janë aktivizuar paketat e lidhjes nga 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)

Burimi: opennet.ru

Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS 🔥 Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS | ProHoster