Skannauskirjaston ensimmäinen versio (0.1.0) on julkaistu. Se jäsentää tekstin arvoiksi käännösaikaisen mallin perusteella. Malli tai muoto kirjoitetaan C++-templaattiargumenttina ja käännetään tägätyksi deterministiseksi äärelliseksi automaatiksi (TDFA), jonka läpikulku avataan tila kerrallaan käännösaikaiseksi koodiksi C++-templaattien avulla. Suorituksen tulos on pyydetyn tyyppinen arvo. Projektikoodi on kirjoitettu C++23:lla ja se jaetaan GPLv3-lisenssillä. import scan; // Täsmääkö koko merkkijono? Jokainen aloituspiste on myös aluesovitin. scan::match<«[az]+@[az.]+»>(address); address | scan::match<«[az]+@[az.]+»>; // Ryhmillä. const auto found = scan::match<«([0-9]+)-([az]+)»>(«42-abc»); found.get<1>().to_view(); // "42" // Hahmon käyttämän merkkijonon alku ja ensimmäinen osuma missä tahansa. scan::starts_with<«[a-z]+»>(«abc123»).whole().to_view(); // "abc" scan::search<«[0-9]+»>(«id=4210x»).to_view(); // "4210" // Kaikki osumat ja niiden väliset osat ovat laiskoja näkymiä. for (const auto& one : text | scan::search_all<«[a-z]+»>) { … } const auto fields = "a,bb,,ccc" | scan::split<«,»> | std::ranges::to (); // Arvoja, ei tekstiä. struct row { int id; std::string_view name; }; const row one = scan::scan<«{},{[a-z]+}»>(line); // Lista, tyyppien summa, sisäkkäinen muoto. struct all { std::vector arvot; std::variantti tail; }; const all got = scan::scan<«{{}{*,?}} {{[0-9]+}|{[a-z]+}}»>(«1,2,3 abc»); // Syötteen alku ja siitä jäljellä oleva osa. const auto [value, rest] = scan::scan_prefix<«{},{}»>(line).take (); // Yksi tietue kerrallaan, mistä tahansa. for (const row& one : scan::each<«{},{[a-z]+}\\n»>(text).of ()) { … }
Kerran luettavissa oleva alue luetaan ilman puskurointia, ja kentät kerätään merkkien saapuessa. Säilytettävien merkkien lukumäärä määritetään mallin mukaan käännösaikana; jos tätä lukumäärää ei ole saatavilla, lukeminen hylätään käännösaikana. 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 (lähde)) ./ ()) { … } Suorituskykyä.
Mittaukset tehtiin Ryzen 9 9950X -prosessorilla, clang 22.1.8 ja libc++, -O3 -march=native,LTO. 2 kirjoituskertaa per läpimeno, keskimäärin seitsemän läpimenokertaa. "([az]+),([az]+),([az]+),([az]+),([az]+)" "alpha,bravo,charlie,delta,echo" scan::scan .sentinel() 477 ns uudelleentarkastelu 554 ns scan::scan 659 ns CTRE 717 ns RE2 15986 ns samat viisi kenttää, kaksisataa kirjainta kussakin scan::scan .sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns "first.last@aliverkkotunnus.esimerkki.com" tunnistus, ei mitään noudettu scan::match .sentinel().scalar() 436 ns scan::match .scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Ryhmien kutistaminen analysoitaessa
Tyypille kerrotaan, mihin sen ryhmistä seuraava merkki kuuluu, ja arviointi suoritetaan paikan päällä: silmukan iteraatioita ei tallenneta eikä alimerkkijonoja luoda. 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 += kirjain == '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 (); // sai.luku.arvo == 12, sai.häntä == "abcdefgh"
Mikä tahansa kutsujaobjekti – allokaattori, pooli, areena tai mikä tahansa – voidaan välittää arvon luovalle kutsulle. Kontekstin tyyppiä ei menetetä: skannerin tila ja jokainen sen hookseista voidaan mallintaa siihen, joten skannerissa ei mainita kutsujatyyppiä. struct arena { std::pmr::memory_resource* where = nullptr; std::pmr::memory_resource* resource() const { return where; } }; struct numbers { std::pmr::vector arvot; }; struct molemmat { numerot vasemmalla; numerot oikealla; }; template <> struct scan::scanner { static constexpr std::string_view pattern() { return "([0-9]+)(?:,([0-9]+))*"; } malline rakennetila { std::pmr::vector arvot; int käynnissä = 0; }; staattinen tila begin_groups() { return {}; } malline vaatii vaatii(const Told& one) { yksi.resurssi(); } staattisessa tilassa begin_groups(const Told& told) { return {std::pmr::vector} (told.resource()), 0}; } malline static void push_group(tila & yksi, skannaus::group_at , char numero) { one.running = one.running * 10 + (numero - '0'); } malline static void closed_group(tila & yksi, skannaus::group_at ) { one.values.push_back(one.running); one.running = 0; } malline staattiset luvut finish_groups(tila one) { return {std::move(one.values)}; } }; std::pmr::monotonic_buffer_resource bytes; const arena mine{&bytes}; scan::scan<“{} {}”>(text).of (mine); // yksi molemmille paikoille scan::scan<«{} {}»>(text).of (mine, scan::default_context); // yksi paikkaa kohden scan::scan<“{} {}”>(text).of ({mine, scan::default_context}); // sama suluissa const both got = scan::scan<«{} {}»>(text).with(mine); // Kontekstin ei tarvitse olla tyyppiään: allokaattori menee niin pitkälle kuin // konstruoi luetun. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan<«{[az]+} {[az]+}»>(text).of<( std::pmr::polymorphic_allocator<>(&bytes));
Koukku vastaanottaa kutsujan objektin itse, ei sen kopiota, joten tila voi tallentaa sen osoitteen: konteksti on olemassa yhtä kauan kuin kutsu, ja kaikki lukeminen tapahtuu kyseisen kutsun aikana. Muut ominaisuudet
- Kaksi tasoa: mallitaso (match, starts_with, search, search_all, split) ja muotoilutaso, jossa "{}" on kenttä ja paikkamerkintä määräytyy itse tyypin kenttien perusteella.
- Täsmennyssääntö on vasemmanpuoleisin ensin, kuten Perlissä, RE2:ssa ja CTRE:ssä.
- Kaikki yllä oleva toimii myös vakiolausekkeissa.
- Siinä ei ole ympäriottoja, takaviittauksia tai Unicode-ominaisuuksia; mallit toimivat tavujen perusteella.
- Vain suorituksen aikana tunnettuja malleja ei tueta.
kokoonpano
Kääntyy clangin ja GCC:n avulla. C++-moduulit ovat valinnaisia: demodulointityökalun samoista moduuleista luomat otsikkotiedostot (include/) ovat käytettävissä – jokainen push-tiedosto pääuudelleenrakennuksiin, linkittää ne ja tekee commit-tiedoston. Ainoa riippuvuus, Boost.PFR, ei ole tarpeen, kun C++26-sidontapaketit ovat käytössä. 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)
Lähde: opennet.ru
