Az első scan kiadás, a C++ szövegelemző könyvtár, amely végállapotú automatát állít össze fordítás közben

Megjelent a scan könyvtár első kiadása (0.1.0), amely a fordítási szakaszban ismert sablon szerint értelmezi a szöveget. A sablont vagy formátumot C++ sablonargumentumként adjuk meg, és a fordítás során címkézett determinisztikus véges automata (TDFA) formájává alakul. A TDFA állapotainak bejárása C++ sablonok használatával történik a fordítási szakaszban. A kimenet a kért típusú érték. A projekt kódja C++23-ban íródott és GPLv3 licenc alatt terjesztik. import scan; \/\/ Egész sor egyezik. Minden belépési pont — egy tartományadapter is. scan::match<«[a-z]+@[a-z.]+»>(address); address | scan::match<«[a-z]+@[a-z.]+»>; \/\/ Csoportokkal. const auto found = scan::match<«([0-9]+)-([a-z]+)»>(«42-abc»); found.get<1>().to_view(); \/\/ «42» \/\/ A sablon által elfoglalt sor kezdete, és az első egyezés bárhol. scan::starts_with<«[a-z]+»>(«abc123»).whole().to_view(); \/\/ «abc» scan::search<«[0-9]+»>(«id=4210x»).to_view(); \/\/ «4210» \/\/ Minden egyezés és a köztük lévő darabok - késleltetett nézetek. for (const auto& one : text | scan::search_all<«[a-z]+»>) { … } const auto fields = «a,bb,,ccc» | scan::split<«,»> | std::ranges::to<std::vector>(); \/\/ Értékek, nem szöveg. struct row { int id; std::string_view name; }; const row one = scan::scan<«{},{[a-z]+}»>(line); \/\/ Lista, típussumma, beágyazott forma. struct all { std::vector<int> values; std::variant<int, std::string_view> tail; }; const all got = scan::scan<«{{}{*,?}} {{[0-9]+}|{[a-z]+}}»>(«1,2,3 abc»); \/\/ Bemenet kezdete és ami belőle megmaradt. const auto [value, rest] = scan::scan_prefix<«{},{}»>(line).take<point>(); \/\/ Egy nyilvános adatsorra egyesével, bármiből. for (const row& one : scan::each<«{},{[a-z]+}\\n»>(text).of<row>()) { … }

A tartományt, amelyet csak egyszer lehet olvasni, pufferteljesítmény nélkül olvassuk. A mezők a jellemzők megjelenése során gyűjtődnek össze. Hány karaktert kell majd megtartani, a sablon a fordítás során határozza meg; ahol ilyen szám nem áll rendelkezésre, az olvasás a fordítási fázisban eljut. 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<char>(source)) .of<command>()) { … } Teljesítmény.

Mérések a Ryzen 9 9950X, clang 22.1.8 libc++-val készültek, -O3 -march=native,LTO. Harminc kettő felvétel egy futás során, hét futás mediánja. «([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 az öt mező ugyanúgy, mindegyik kétszáz karakter scan::scan<f>.sentinel() 57.8 ns re2c 637 ns CTRE 692 ns RE2 11751 ns «first.last@subdomain.example.com» azonosítás, semmi nem kerül kiolvasásra scan::match<p>.sentinel().scalar() 436 ns scan::match<p>.scalar() 545 ns re2c 1186 ns RE2 2616 ns CTRE 14339 ns Csoportok összecsomagolása feldolgozás közben

A típus értesítést kap arról, hogy a következő karakter melyik csoporthoz tartozik, és a számítás a helyszínen történik: egyetlen iterációs ciklus sem kerül tárolásra, és egyetlen részszöveg sem jön létre. 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() kézzel írt 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
Környezetek és allokátorok

Bármilyen hívóobjektum — allokátor, medence, aréna, bármi más — átkonvertálható abba a hívásba, ami értéket hoz létre. A kontextus típusa ennek megfelelően nem veszik el: a szkenner állapota és minden egyes akció sablonként szolgálhat vele, így a szkennerben egyetlen hívófél típusa sem kerül említésre. 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); // egy a két helyre scan::scan(text).of(mine, scan::default_context); // egyenként a helyekre scan::scan(text).of({mine, scan::default_context}); // ugyanaz zárójelben const both got = scan::scan(text).with(mine); // A kontextusnak nem feltétlenül a saját típusnak kell lennie: az allokátor odáig jut, // hogy megépíti az olvasást. struct two { std::pmr::string name; std::pmr::string tail; }; const two kept = scan::scan(text).of<( std::pmr::polymorphic_allocator (&bytes));

A hívásnál a hívó fél objektuma, nem a másolata kerül átadásra, ezért az állapot tárolhatja annak címét: a kontextus annyira él, mint a hívás, és minden olvasás ezen a híváson belül történik. Egyéb jellemzők

  • Két réteg: sablonok rétege (match, starts_with, search, search_all, split) és a formátumok rétege, ahol a "{}" — mező, és az értéket a típus mezői határozzák meg.
  • Az egyértelműség feloldásának szabálya — baloldal-első, mint Perl, RE2 és CTRE.
  • Mindez működik konstans kifejezésekben is.
  • Nincs lookaround, visszautalások és Unicode tulajdonságok; a sablonok byte-okkal működnek.
  • A futásidő alatt ismert sablonokat nem támogatják.

Összeállítás

A clang és a GCC fordítja. A C++ modulok nem kötelezőek: az include/ könyvtárban elérhetőek a demodulizer nevű eszköz által generált fejlécfájlok — minden push a main ágba újrafordítja, linkeli és visszacommitálja őket. Az egyetlen függőség, a Boost.PFR, nem szükséges, ha a C++26 osztálycsomagok engedélyezve vannak. 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)

Forrás: opennet.ru

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster