Першы выпуск scan, бібліятэкі разбору тэксту для C++ з пабудовай канчатковага аўтамата пры кампіляцыі

Апублікаваны першы выпуск бібліятэкі scan (0.1.0), якая разбірае тэкст у значэнні па шаблоне, вядомаму на этапе кампіляцыі. Шаблон або фармат запісваецца як аргумент шаблону C++ і ператвараецца пры кампіляцыі ў размечаны дэтэрмінаваны канчатковы аўтамат (tagged deterministic finite automaton, TDFA), абыход якога разгортваецца па станах у код на этапе кампіляцыі з выкарыстаннем шаблонаў C++. Вынікам выканання з'яўляецца значэнне запрошанага тыпу. Код праекту напісаны на C++23 і распаўсюджваецца пад ліцэнзіяй GPLv3. import scan; // Ці супадае ўвесь радок. Кожная кропка ўваходу - яшчэ і адаптар дыяпазону. scan::match<«[az]+@[az.]+»>(address); address | scan::match<«[az]+@[az.]+»>; // З групамі. const auto found = scan::match<«([0-9]+)-([az]+)»>(«42-abc»); found.get<1>().to_view(); // «42» // Пачатак радка, занятае шаблонам, і першае супадзенне дзе заўгодна. scan::starts_with<«[az]+»>(«abc123»).whole().to_view(); // "abc" scan::search<"[0-9]+">("id=4210x").to_view(); // «4210» // Усе супадзенні і кавалкі паміж імі - лянівыя ўяўленні. for (const auto& one : text | scan::search_all<«[az]+»>) { … } const auto fields = "a,bb,,ccc" | scan::split<«,»> | std::ranges::to (); // Значэнні, а не тэкст. struct row { int id; std::string_view name; }; const row one = scan::scan<«{},{[az]+}»>(line); // Спіс, сума тыпаў, укладзеная форма. struct all { std::vector values; std::variant tail; }; const all got = scan::scan<«{{}{*,?}} {{[0-9]+}|{[az]+}}»>(«1,2,3 abc»); // Пачатак уводу і тое, што ад яго засталося. const auto [value, rest] = scan::scan_prefix<«{},{}»>(line).take (); // Па запісе за раз, з чаго заўгодна. for (const row& one : scan::each<«{},{[az]+}\\n»>(text).of ()) { … }

Дыяпазон, які можна прачытаць толькі аднойчы, чытаецца без буферызацыі, палі збіраюцца па меры паступлення сімвалаў. Колькі сімвалаў давядзецца ўтрымаць, шаблон задае пры кампіляцыі; там, дзе такога ліку няма, чытанне адхіляецца пры кампіляцыі. 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 {[az]+} {[0-9]+}\\n»>(std::views::istream (source)) .of ()) { … } Прадукцыйнасць.

Замеры праведзены на Ryzen 9 9950X, clang 22.1.8 з libc++, -O3 -march = native, LTO. Трыццаць два запісы за праход, медыяна сямі праходаў. "([az]+),([az]+),([az]+),([az]+),([az]+)" "alpha, bravo,charlie,delta,echo" .sentinel() 477 нс re2c 554 нс scan::scan 659 нс CTRE 717 нс RE2 15986 нс тыя ж пяць палёў, па дзвесце літар кожнае scan::scan .sentinel() 57.8 нс re2c 637 нс CTRE 692 нс RE2 11751 нс «first.last@subdomain.example.com» распазнанне, нічога не здабываецца scan::match .sentinel().scalar() 436 нс scan::match .scalar() 545 нс re2c 1186 нс RE2 2616 нс CTRE 14339 нс Згортка груп па ходзе разбору

Тыпу паведамляецца, да якой з яго груп ставіцца чарговы знак, і вылічэнне выконваецца на месцы: ні адна ітэрацыя цыклу не захоўваецца, ніводнай падрадкі не ствараецца. 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<0>) { one.place = 0; one.marks = 0; } static constexpr void closed_group(state_type& one, scan::group_at<0>) { 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<1>, char) { ++one.place; } static constexpr void push_group(state_type& one, scan::group_at<2>, 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={}{[az]*}">("value=(__X_XX)abcdefgh").of (); // got.number.value == 12, got.tail == «abcdefgh»

heap scan::scan .scalar() напісанае ўручную scan::scan 10141 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
Кантэксты і алакатары

Любы аб'ект выклікалага боку - алакатар, пул, арэну, што заўгодна яшчэ - можна перадаць у той выклік, які стварае значэнне. Тып кантэксту пры гэтым не губляецца: стан сканара і кожны яго хук могуць быць шаблонамі па ім, так што ў сканары не згаданы ніводны тып выклікалага боку. 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); // адзін на абодва месцы scan::scan<«{} {}»>(text).of (mine, scan::default_context); // па адным на месца scan::scan<«{} {}»>(text).of ({mine, scan::default_context}); // тое ж у дужках const both got = scan::scan<«{} {}»>(text).with(mine); // Кантэксту не абавязкова быць сваім тыпам: алакатар даходзіць да таго, // што будуе чытанне. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan<«{[az]+} {[az]+}»>(text).of<( std::pmr::polymorphic_allocator<>(&bytes));

Хук атрымлівае сам аб'ект задзірлівага боку, а не копію, таму стан можа захоўваць яго адрас: кантэкст жыве столькі ж, колькі выклік, а ўсё чытанне адбываецца ўнутры гэтага выкліку. Іншыя асаблівасці

  • Два пласта: пласт шаблонаў (match, starts_with, search, search_all, split) і пласт фарматаў, дзе "{}" - поле, а значэнне месца вызначаюць палі самага тыпу.
  • Правіла дазволу неадназначнасці - leftmost-first, як у Perl, RE2 і CTRE.
  • Усё пералічанае працуе і ў канстантных выразах.
  • Няма lookaround, зваротных спасылак і ўласцівасцяў Unicode; шаблоны працуюць з байтамі.
  • Не падтрымліваюцца шаблоны, вядомыя толькі падчас выканання.

зборка

Збіраецца ў clang і GCC. Модулі C++ не абавязковыя: даступныя загалоўкавыя файлы (include/), якія генеруе з тых жа модуляў утыліта demodulizer - кожны push у main перазбірае, лінкуе і камітіт іх зваротна. Адзіная залежнасць, Boost.PFR, не патрэбна пры ўключаных binding packs з 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)

Крыніца: opennet.ru

Купіць надзейны хостынг для сайтаў з абаронай ад DDoS, VPS VDS серверы 🔥 Купіць надзейны хостынг для сайтаў з абаронай ад DDoS, VPS VDS серверы | ProHoster