Avaldati scan teegi esimene väljalase (0.1.0), mis analüüsib teksti malli alusel, mida tuntakse kompilatsioonietapis. Mall või formaat on C++ template'i argumendina ja muudetakse kompilatsiooni käigus tähistatud määramatuks lõppautomaatideks (tagged deterministic finite automaton, TDFA), mille kaudu liikumine rakendatakse olekutes koodis kompilatsioonietapis, kasutades C++ malle. Tulemuseks on soovitud tüübi väärtus. Projekti kood on kirjutatud C++23-s ja levitatakse GPLv3 litsentsi all. import scan; // Kas kogu rida vastab. Iga sisenemispunkt on ka vahemiku adapter. scan::match(address); address | scan::match; // Grupid. const auto found = scan::match("42-abc"); found.get().to_view(); // "42" // Rida, mille mall on hõivatud, ja esimene vaste kuskil. scan::starts_with("abc123").whole().to_view(); // "abc" scan::search("id=4210x").to_view(); // "4210" // Kõik vasted ja tükid nende vahel — laisk esitus. for (const auto& one : text | scan::search_all) { … } const auto fields = "a,bb,,ccc" | scan::split | std::ranges::to(); // Väärtused, mitte tekst. struct row { int id; std::string_view name; }; const row one = scan::scan(line); // Loend, tüüpide summa, sisemine vorm. struct all { std::vector values; std::variant tail; }; const all got = scan::scan("1,2,3 abc"); // Sisendi algus ja see, mis sellest jäi. const auto [value, rest] = scan::scan_prefix(line).take(); // Üks kirje korraga, millest iganes. for (const row& one : scan::each(text).of()) { … }
Ühtlasi, mida saab lugeda ainult üks kord, loetakse ilma vahemälu, väljad kogutakse sümbolite sissevoolu käigus. Kui palju sümboleid tuleb hoida, määrab mall kompileerimisel; seal, kus sellist arvu ei ole, lükatakse lugemine tagasi kompileerimisel. 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()) { … } Tõhusus.
Mõõtmised viidi läbi Ryzen 9 9950X, clang 22.1.8 koos libc++, -O3 -march=native, LTO. Kolmkümmend kaks salvestust jooksu kohta, seitsme jooksu mediaan. «([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 samad viis väli, igaühes kaks sada tähte scan::scan<f>.sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns «first.last@subdomain.example.com» tuvastamine, midagi ei ekstraheerita scan::match<p>.sentinel().scalar() 436 ns scan::match<p>.scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Grupi kokkuvõte analüüsi käigus
Tüübile edastatakse, millisesse tema rühma järgnev märk kuulub, ja arvutamine toimub kohapeal: ühtegi tsükli iteratsiooni ei salvestata, ühtegi alamstringi ei loo. 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"
Mistah liitub kõiki objekti väljapoole - allokaatorite, basseinide, areenide või mis iganes muude jaotustega - saame edastada selle funktsiooni, mis loob väärtuse. Kontseptsiooni tüüp ei kao: skanneri olek ja iga tema haak võivad olla selle põhjal malli loodud, nii et skanneris ei mainita ühtegi objekti tüüpi. struct arena { std::pmr::memory_resource* where = nullptr; std::pmr::memory_resource* resource() const { return where; } }; struct numbers { std::pmr::vector<int> values; }; struct both { numbers left; numbers right; }; template <> struct scan::scanner<numbers> { static constexpr std::string_view pattern() { return «([0-9]+)(?:,([0-9]+))*»; } template <class Told> struct state { std::pmr::vector<int> values; int running = 0; }; static state<scan::default_context_t> begin_groups() { return {}; } template <class Told> requires requires(const Told& one) { one.resource(); } static state<Told> begin_groups(const Told& told) { return {std::pmr::vector<int>(told.resource()), 0}; } template <class Told, std::size_t Which> static void push_group(state<Told>& one, scan::group_at<Which>, char digit) { one.running = one.running * 10 + (digit — ‘0’); } template <class Told, std::size_t Which> static void closed_group(state<Told>& one, scan::group_at<Which>) { one.values.push_back(one.running); one.running = 0; } template <class Told> static numbers finish_groups(state<Told> one) { return {std::move(one.values)}; } }; std::pmr::monotonic_buffer_resource bytes; const arena mine{&bytes}; scan::scan<«{} {}»>(text).of<both>(mine); // üks mõlemale kohale scan::scan<«{} {}»>(text).of<both>(mine, scan::default_context); // ükshaaval kohal scan::scan<«{} {}»>(text).of<both>({mine, scan::default_context}); // sama sulgudes const both got = scan::scan<«{} {}»>(text).with(mine); // Konteksti ei pea olema enda tüüp: allokaator suudab täiesti lugeda. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan<«{[a-z]+} {[a-z]+}»>(text).of<( std::pmr::polymorphic_allocator<>(&bytes));
Haak saab objektist, mitte kopeerib, seega võib olek salvestada selle aadressi: konteksti eluiga on sama kui kutsumise aeg, ja kogu lugemine toimub selle kutsumise ajal. Muud omadused
- Kaks kihte: mallide kiht (match, starts_with, search, search_all, split) ja formaatide kiht, kus «{}» on väli ja väärtuse kohti määravad ise tüübi väljad.
- Äraoleku lahendamise reegel on leftmost-first, nagu Perl, RE2 ja CTRE.
- Kõik ülaltoodud toimib ka konstantsetes väljendites.
- Ei ole lookaround, tagasiviidete ega Unicode omadusi; mallid töötavad baitidega.
- Ei toetata malle, mis on teada ainult käitamise ajal.
Kogumine
Kogutatakse clang'is ja GCC's. C++ moodulid pole vajalikud: on saadaval päisefailid (include/), mille genereerib sama moodulite utiliit demodulizer — iga push main'i uuendab, linkib ja commitib need tagasi. Ainus sõltuvus, Boost.PFR, ei ole vajalik, kui C++26 binding pack'id on lubatud. 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)
Allikas: opennet.ru
