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"
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
