From 6a3187c1dd1bdbe6f03690b4f8b99f7cf0921c6e Mon Sep 17 00:00:00 2001 From: Elias Kosunen Date: Sun, 29 Oct 2023 00:08:56 +0300 Subject: [PATCH] wip --- .github/workflows/docs.yml | 41 ++ CMakeLists.txt | 2 +- {docs => docs-old}/CMakeLists.txt | 0 {docs => docs-old}/Doxyfile.in | 0 {docs => docs-old}/Pipfile | 0 {docs => docs-old}/Pipfile.lock | 0 {docs => docs-old}/_static/css/custom.css | 0 {docs => docs-old}/api.rst | 0 {docs => docs-old}/conf.py | 0 {docs => docs-old}/guide.rst | 2 +- {docs => docs-old}/index.rst | 0 .../migration/migration_2_0.rst | 0 {docs => docs-old}/requirements.txt | 0 docs/pages/guide.md | 365 +++++++++++++++++ docs/pages/mainpage.md | 19 + docs/pages/migration-2-0.md | 381 ++++++++++++++++++ docs/poxy.toml | 53 +++ include/scn/detail/args.h | 100 +++-- include/scn/detail/caching_view.h | 39 +- include/scn/detail/context.h | 34 +- include/scn/detail/erased_range.h | 26 +- include/scn/detail/error.h | 7 + include/scn/detail/format_string.h | 6 +- include/scn/detail/input_map.h | 1 + include/scn/detail/istream_range.h | 9 + include/scn/detail/parse_context.h | 6 + include/scn/detail/pp_detect.h | 4 + include/scn/detail/ranges.h | 7 +- include/scn/detail/result.h | 19 +- include/scn/detail/scan.h | 42 +- include/scn/detail/scanner.h | 16 +- include/scn/detail/scanner_range.h | 6 +- include/scn/detail/visitor.h | 10 +- include/scn/detail/vscan.h | 31 +- include/scn/detail/xchar.h | 13 + include/scn/fwd.h | 27 ++ include/scn/util/expected.h | 9 +- include/scn/util/expected_impl.h | 13 +- include/scn/util/optional.h | 31 -- include/scn/util/span.h | 31 -- src/scn/impl/reader/common.h | 4 +- 41 files changed, 1220 insertions(+), 134 deletions(-) create mode 100644 .github/workflows/docs.yml rename {docs => docs-old}/CMakeLists.txt (100%) rename {docs => docs-old}/Doxyfile.in (100%) rename {docs => docs-old}/Pipfile (100%) rename {docs => docs-old}/Pipfile.lock (100%) rename {docs => docs-old}/_static/css/custom.css (100%) rename {docs => docs-old}/api.rst (100%) rename {docs => docs-old}/conf.py (100%) rename {docs => docs-old}/guide.rst (99%) rename {docs => docs-old}/index.rst (100%) rename {docs => docs-old}/migration/migration_2_0.rst (100%) rename {docs => docs-old}/requirements.txt (100%) create mode 100644 docs/pages/guide.md create mode 100644 docs/pages/mainpage.md create mode 100644 docs/pages/migration-2-0.md create mode 100644 docs/poxy.toml delete mode 100644 include/scn/util/optional.h diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..20cfe27b --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,41 @@ +name: Documentation + +on: + push: + branches: + - master + - dev + - poxy + paths: + - "**.h" + - "**.hpp" + - "**.dox" + - "**.md" + - "docs/" + - "**/docs.yml" + workflow_dispatch: + +jobs: + docs: + runs-on: ubuntu-latest + + steps: + - name: Install dependencies + run: | + sudo apt -y update + sudo apt -y install --no-istall-recommends git python3 python3-pip doxygen + sudo -H pip3 install --upgrade poxy + + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate docs + run: | + cd docs + poxy --verbose + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/html diff --git a/CMakeLists.txt b/CMakeLists.txt index a476b490..06ed4a38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,7 +153,7 @@ set_interface_flags(scn_internal) add_subdirectory(scripts) add_subdirectory(benchmark) -add_subdirectory(docs) +#add_subdirectory(docs) add_subdirectory(examples) enable_testing() diff --git a/docs/CMakeLists.txt b/docs-old/CMakeLists.txt similarity index 100% rename from docs/CMakeLists.txt rename to docs-old/CMakeLists.txt diff --git a/docs/Doxyfile.in b/docs-old/Doxyfile.in similarity index 100% rename from docs/Doxyfile.in rename to docs-old/Doxyfile.in diff --git a/docs/Pipfile b/docs-old/Pipfile similarity index 100% rename from docs/Pipfile rename to docs-old/Pipfile diff --git a/docs/Pipfile.lock b/docs-old/Pipfile.lock similarity index 100% rename from docs/Pipfile.lock rename to docs-old/Pipfile.lock diff --git a/docs/_static/css/custom.css b/docs-old/_static/css/custom.css similarity index 100% rename from docs/_static/css/custom.css rename to docs-old/_static/css/custom.css diff --git a/docs/api.rst b/docs-old/api.rst similarity index 100% rename from docs/api.rst rename to docs-old/api.rst diff --git a/docs/conf.py b/docs-old/conf.py similarity index 100% rename from docs/conf.py rename to docs-old/conf.py diff --git a/docs/guide.rst b/docs-old/guide.rst similarity index 99% rename from docs/guide.rst rename to docs-old/guide.rst index ba4430ee..2089f773 100644 --- a/docs/guide.rst +++ b/docs-old/guide.rst @@ -97,7 +97,7 @@ so `--std=c++17` (or equivalent, or newer) needs to be included in the build fla Basic usage ----------- -``scn::scan`` can be used to parse various values from a source range. +``scn::scan`` can be used to scan various values from a source range. A range is an object that has a beginning and an end. Examples of ranges are string literals, ``std::string`` and ``std::vector``. diff --git a/docs/index.rst b/docs-old/index.rst similarity index 100% rename from docs/index.rst rename to docs-old/index.rst diff --git a/docs/migration/migration_2_0.rst b/docs-old/migration/migration_2_0.rst similarity index 100% rename from docs/migration/migration_2_0.rst rename to docs-old/migration/migration_2_0.rst diff --git a/docs/requirements.txt b/docs-old/requirements.txt similarity index 100% rename from docs/requirements.txt rename to docs-old/requirements.txt diff --git a/docs/pages/guide.md b/docs/pages/guide.md new file mode 100644 index 00000000..69cf1117 --- /dev/null +++ b/docs/pages/guide.md @@ -0,0 +1,365 @@ +\page guide Guide +\tableofcontents + +\section basic Basic usage + +`scn::scan` can be used to scan various values from a source range. + +A range is an object that has a beginning and an end. +Examples of ranges are string literals, `std::string` and `std::vector`. +Objects of these types, and more, can be passed to `scn::scan`. +To learn more about the requirements on these ranges, see the API documentation on source ranges. + +After the source range, `scn::scan` is passed a format string. +This is similar in nature to `std::scanf`, and has virtually the same syntax as `std::format` and {fmt}. +In the format string, arguments are marked with curly braces `{}`. +Each `{}` means that a single value is to be scanned from the source range. +Because scnlib uses templates, type information is not required in the format string, +like it is with `std::scanf` (e.g. `%d`). + +The list of the types of the values of the scan are given as template parameters to `scn::scan`. +`scn::scan` returns an object, which contains the read value. +If only a single value is read, it can be accessed through the member function `value()`, +otherwise all the read values can be accessed through a `std::tuple` with `values()`. + +\code{.cpp} +// Scanning an int +auto result = scn::scan("123", "{}"): +auto i = result->value(); +// i == 123 + +// Scanning a double +auto result = scn::scan("3.14", "{}"); +auto& [d] = result->values(); +// d == 3.14 + +// Scanning multiple values +auto result = scn::scan("0 1 2", "{} {}"); +auto& [a, b] = result->values(); +// a == 0 +// b == 1 +// Note, that " 2" was not scanned, +// because only two integers were requested + +// Scanning a string means scanning a "word" -- +// that is, until the next whitespace character +// this is the same behavior as with iostreams +auto result = scn::scan("hello world", "{}"); +// result->value() == "hello" +\endcode + +Compare the above example to the same implemented with `std::istringstream`: + +\code{.cpp} +int i; +std::istringstream{"123"} >> i; + +double d; +std::istringstream{"3.14"} >> d; + +int a, b; +std::istringstream{"0 1 2"} >> a >> b; + +std::string str; +std::istringstream{"hello world"} >> str; +\endcode + +Or with `std::sscanf`: + +\code{.cpp} +int i; +std::sscanf("123", "%d", &i); + +double d; +std::sscanf("3.14", "%lf", &d); + +int a, b; +std::sscanf("0 1 2", "%d %d", &a, &b); + +// Not really possible with scanf! +char buf[16] = {0}; +std::sscanf("hello world", "%15s", buf); +// buf == "hello" +\endcode + +\section errors Error handling and return values + +scnlib does not use exceptions. +The library compiles with `-fno-exceptions -fno-rtti` and is perfectly usable without them. + +Instead, it uses return values to signal errors: `scn::scan` returns an `scan_expected`. +This return value is truthy if the operation succeeded. +If there was an error, the `.error()` member function can be used to gather more details about the error. + +The actual read values are accessed with either +`operator->` or member function `.value()` of the returned `scan_expected`. +This ensures, that if an error occurred, the values are not accidentally accessed. + +\code{.cpp} +// "foo" is not an integer +auto result = scn::scan("foo", "{}"); +// fails, result->value() would be UB, result.value().value() would throw +if (!result) { + std::cout << result.error().msg() << '\n'; +} +\endcode + +Unlike with `std::scanf`, partial successes are not supported. +Either the entire scanning operation succeeds, or a failure is returned. + +\code{.cpp} +// "foo" is still not an integer +auto result = scn::scan("123 foo", "{} {}"); +// fails -- result == false +\endcode + +Oftentimes, the entire source range is not scanned, and the remainder of the range may be useful later. +The unparsed input can be accessed with `->range()`, which returns a `subrange`. +An iterator pointing to the first unparsed element can be retrieved with `->begin()`. + +\code{.cpp} +auto result = scn::scan("123 456"sv, "{}"); +// result == true +// result->value() == 123 +// result->range() == " 456" + +auto [other_result, i] = scn::scan(result->range(), "{}"); +// other_result == true +// i == 456 +// other_result-> == "" +\endcode + +The return type of `->range()` is a view into the range `scn::scan` was given. +Its type may not be the same as the source range, but its iterator and sentinel types are the same. +If the range given to `scn::scan` does not model `ranges::borrowed_range` +(essentially, the returned range would dangle), the returned range is of type `ranges::dangling`. + +Because the range type returned by `scn::scan` is always a `subrange` over its input, +it's easy to use `scn::scan` in loops, as long as the input type is a `subrange` to begin with. +If it's not, consider making it one with `scn::ranges::subrange{your-input-range}`. + +\code{.cpp} +auto input = scn::ranges::subrange{...}; +while (auto result = scn::scan<...>(input, ...)) { + // use result + input = result->range(); +} +\endcode + +\section stdin Standard streams and `stdin` + +To read from `stdin`, use `scn::input` or `scn::prompt`. +They work similarly to `scn::scan`, except they do not take an input range as a parameter: `stdin` is implied. +They take care of synchronization with `std::cin` and C stdio `stdin` buffers, +so `scn::input` usage can be mixed with both `std::cin` and `std::scanf`. +`scn::input` does not return a leftover range type. + +\code{.cpp} +if (auto result = scn::input("{}")) { + // ... +} +// scn::input, std::cin, and std::scanf can be used immediately, +// without explicit synchronization +if (auto result = scn::prompt("Provide a number: ", "{}"); result) { + // ... +} +\endcode + +`scn::input` is internally implemented by wrapping `std::cin` inside an `scn::istreambuf_view`. +`scn::istreambuf_view` is a view, that wraps a `std::streambuf`, and provides a range-like interface for it. +`scn::istreambuf_view` has a member function, `sync`, +that can be used to synchronize its state with the underlying `std::streambuf`, +so that it can be used again. + +\code{.cpp} +std::istringstream ss{"123 456"}; +auto ssview = scn::istreambuf_view{ss}; +auto result = scn::scan(ssview, "{}"); +// result->value() == 123 + +result->begin().sync(); +// ss can now be used again +int j{}; +ss >> j; +\endcode + +\section format Format string + +Parsing of a given value can be customized with the format string. +The format string syntax is based on the one used by {fmt} and `std::format`. + +In short, in the format string, `{}` represents a value to be parsed. +The type of the value is determined by the list of types given to `scn::scan`. + +Any whitespace character in the format string is an instruction to skip all whitespace. +Some types may do that automatically. +This behavior is identical to `scanf`. + +\code{.cpp} +// scanning a char doesn't automatically skip whitespace, +// int does +auto result = scn::scan("x 123", "{}{}{}"); +auto& [a, b, i] = result->values(); +// a == 'x' +// b == ' ' +// i == 123 + +// Whitespace in format string, skip all whitespace +auto result = scn::scan("x y", "{} {}"); +auto& [a, b] = result->values(); +// a == 'x' +// b == 'y' +\endcode + +Any other character in the format string is expected to be found in the source range, and is then discarded. + +\code{.cpp} +auto result = scn::scan("abc", "ab{}"); +// result->value() == 'c' +\endcode + +Inside the curly braces `{}`, flags can be specified, that govern the way the value is parsed. +The flags start with a colon `:` character. +See the API Documentation for full reference on format string flags. + +\code{.cpp} +// accept only hex floats +auto result = scn::scan(..., "{:a}"); + +// interpret the parsed number as hex +auto result = scn::scan(..., "{:x}"); +\endcode + +\section scan_value `scn::scan_value` + +For simple cases, there's `scn::scan_value`. +It can be used to scan a single value from a source range, as if by using the default format string `"{}"`. + +\code{.cpp} +auto result = scn::scan_value("123"); +// result->value() == 123 +// result->range() is empty +\endcode + +\section unicode Unicode and wide source ranges + +scnlib expects all input given to it to be Unicode. +All input with the character/value type of `char` is always assumed to be UTF-8. +Encoding errors are checked for, and may cause scanning to fail. + +This guide has so far only used narrow (`char`) ranges as input. +scnlib also supports wide (`wchar_t`) ranges to be used as source ranges, +including wide string literals and `std::wstring` s. +Wide strings are expected to be encoded in UTF-16 (with platform endianness), or UTF-32, +depending on the width of `wchar_t` (2 byte `wchar_t` -> UTF-16, 4 byte `wchar_t` -> UTF-32). + +\code{.cpp} +auto result = scn::scan(L"foo bar", L"{}"); +// result->value() == L"foo" + +// narrow strings can be scanned from wide sources, and vice versa +// in these cases, Unicode transcoding (UTF-8 <-> UTF-16/32) is performed +auto result2 = scn::scan(result->range(), L"{}"); +// result2->value() == "bar" +\endcode + +\section usertypes User types + +To scan a value of a user-defined type, specialize `scn::scanner` +with two member functions, `parse` and `scan`. + +\code{.cpp} +struct mytype { + int i; + double d; +}; + +template <> +struct scn::scanner { + template + auto parse(ParseContext& pctx) + -> scan_expected; + + template + auto scan(mytype& val, Context& ctx) + -> scan_expected; +}; +\endcode + +`parse` parses the format string, and extracts scanning options from it. +The easiest ways to implement it are to inherit it from another type, or to just accept no options: + +\code{.cpp} +// Inherit +template <> +struct scn::scanner : scn::scanner {}; + +// Accept only empty +template +auto parse(ParseContext& pctx) -> scan_expected { + return pctx.begin(); +} +\endcode + +`scan` parses the actual value, using the supplied `Context`. +The context has a member function, `current`, to get an iterator pointing to the next character in the source range, +and `range`, to get the entire source range that's still left to scan. +These values can be then passed to `scn::scan`. +Alternatively, scanning can be delegated to another `scn::scanner`. + +\code{.cpp} +template +auto scan(mytype& val, Context& ctx) -> scan_expected { + auto result = scn::scan(ctx.range(), "{} {}"); + if (!result) { + return unexpected(result.error()); + } + + val = {i, d}; + return result->begin(); + + // or, delegate to other scanners (more advanced): + + return scn::scanner{}.scan(val.i, ctx) + .and_then([&](auto it) { + ctx.advance_to(it); + return scn::scanner{}.scan(val.d, ctx); + }); +} +\endcode + +If your type has an `std::istream` compatible `operator>>` overload, that can also be used for scanning. +Include the header ``, and specialize `scn::scanner` by inheriting from `scn::istream_scanner`. + +\code{.cpp} +std::istream& operator>>(std::istream&, const mytype&); + +template <> +struct scn::scanner : scn::istream_scanner {}; +\endcode + +\section locale Localization + +By default, scnlib isn't affected by changes to the global C or C++ locale. +All functions behave as if the global locale were set to `"C"`. + +A `std::locale` can be passed as the first argument to `scn::scan`, to scan using that locale. +This is mostly used with floats, to get locale-specific decimal separators. + +Because of the way `std::locale` and the facilities around it work, +parsing using a locale is significantly slower compared to not using one. +This is, because the library effectively has to fall back on iostreams for parsing. + +Just passing a locale isn't enough, but you'll need to opt-in to locale-specific parsing, +by using the `L` flag in the format string. Not every type supports localized parsing. + +\code{.cpp} +auto result = scn::scan(std::locale{"fi_FI.UTF-8"}, "2,73", "{:L}"); +// result->value() == 2.73 +\endcode + +Because localized scanning uses iostreams under the hood, +the results may not be entirely the same when no locale is used, +even if `std::locale::classic()` was passed. +This is due to limitations of the design of iostreams, +and platform-specific differences in locales and iostreams. diff --git a/docs/pages/mainpage.md b/docs/pages/mainpage.md new file mode 100644 index 00000000..8db982a1 --- /dev/null +++ b/docs/pages/mainpage.md @@ -0,0 +1,19 @@ +\mainpage scnlib +\tableofcontents + +`scnlib` is a modern C++ library for scanning values. +Think of it as a more C++-y `scanf`, or the inverse of +{fmt} / `std::format`. + +\section about About this documentation + +This documentation is for the version 2.0 of the library. +This version is currently experimental, and under development, with no stability guarantees. +For a stable release, see version 1.1. +Its documentation is hosted over at Read the Docs: +https://scnlib.readthedocs.io/. + +An introductory guide to the library can be found at \ref guide "Guide". +Instructions for migrating to v2.0 from v1.1 can be found at \ref migration-2-0 "Migration Guide v1.1 -> v2.0". +The API documentation is organized into modules, that can be found under Modules, behind the link at the top of the page. +It can be searched directly using the search function in the navbar, or by pressing the TAB key. diff --git a/docs/pages/migration-2-0.md b/docs/pages/migration-2-0.md new file mode 100644 index 00000000..80495598 --- /dev/null +++ b/docs/pages/migration-2-0.md @@ -0,0 +1,381 @@ +\page migration-2-0 Migration Guide v1.1 -> v2.0 +\tableofcontents + +For v2.0, the library was rewritten and redesigned in its entirety. +The new design is more focused and powerful, and closer to `std::format` than previously. + +This guide isn't exhaustive, because the changes are very extensive, but should be enough to get you started. + +\section cpp17 C++17 required + +v1 required C++11 in order to compile. v2, at least at this point, requires C++17. + +\section headers Header files changed + +The base header is renamed from `` in v1 to `` in v2. + +To get support for `wchar_t` input, include ``. This is done to ease compile times. + +\section scan_prefix `scan_` prefix added to many names inside the `scn` namespace + +To prepare for standardization, in v2, many names have the prefix `scan_`, +or otherwise indicate being related to scanning. + +Changes include: + +| v1 | v2 | +|:------------------------------------------------------|:---------------------------------------------------------------------| +| `scn::error` | `scn::scan_error` | +| `scn::basic_arg`, `scn::basic_args`, `scn::arg_store` | `scn::basic_scan_arg`, `scn::basic_scan_args`, `scn::scan_arg_store` | +| `scn::basic_context` | `scn::basic_scan_context` | +| `scn::basic_parse_context`, `scn::parse_context` | `scn::basic_scan_parse_context`, `scn::scan_parse_context` | + +\section scan_arg_passing `scn::scan` argument passing and return value + +The largest change is in how values are returned from `scn::scan` and other scanning functions. + +In v1, values were passed to `scn::scan` by lvalue reference as out parameters. +The return value was used to get information about the leftover input data, and about possible errors. + +\code{.cpp} +int i; +std::string str; +auto result = scn::scan("123 input", "{} {}", i, str); +\endcode + +In v2, the values are returned from `scn::scan`, wrapped in an ``scn::expected``. +The types of the arguments are given in an explicit template parameter list, +instead of being deduced from the given arguments. + +\code{.cpp} +auto result = scn::scan("123 input", "{} {}"); +if (result) +auto& [i, str] = result->values(); +\endcode + +The `result` value above is truthy when the operation was successful. +Use `result->range()` to get a `subrange` over the unparsed input, +`result->begin()` and `result->end()` to get the beginning and the end of that range, respectively, and +`result->values()` to access the parsed values through a `std::tuple`. +If only a single value is read, `result->value()` can be used to access it directly. +If `result` contains an error, use `result.error()` to access it. + +\section indirect No more "indirect" ranges: revamped source range error handling + +The notion of "indirect" ranges from v1 is removed in v2. +Indirect ranges were source ranges, the value type of which was `scn::expected`, instead of `CharT`. +This was to enable source ranges to report their own errors to the library, +and for it to pass them forward to the user. +In v2, the value type of the source range must either be `char` or `wchar_t`. + +This approach was arguably against the principles of Ranges, +and made a lot of things more complicated than they needed to be. + +In v2, the separation of I/O and input parsing is more clearly separated. +scnlib is not intended to be an I/O library, and that it shan't try to be. +In the optimal case, if I/O needs to be performed to fetch the data to be passed to scnlib, +that is done by the user, to ensure proper behavior and error recovery. +Also, when scnlib is given plain contiguous strings as input, instead of more complicated ranges, +a number of optimizations are enabled. + +\code{.cpp} +std::string input; +std::getline(file, input); + +auto result = scn::scan(input, "{}"); +\endcode + +If doing your own I/O isn't possible, or is for some reason unfeasible, a number of other options are available: + + 1) A `scn::basic_istreambuf_view`/`scn::basic_istreambuf_subrange` can be given as a source range to `scn::scan`. + These types wrap an arbitrary `std::basic_istream`/`std::basic_streambuf`. + This can be useful, if you already have a `std::basic_istream`, + and don't want to accidentally read anything extra from the stream, like with `std::cin`. + + \code{.cpp} + auto range = scn::istreambuf_view{std::cin}; + auto result = scn::scan(range, "{}"); + \endcode + + 2) Signal errors like any other range signals them: by reaching end prematurely, or with exceptions (discouraged). + If using a custom user-provided range, this is likely the only option. + + \code{.cpp} + auto result = scn::scan(custom_source_range, "{} {}"); + // result can be true, if both the int and the double could be scanned, + // but the given range reached an error condition. + // We need to do the checking ourselves through custom_source_range, through whatever mechanism it provides + if (result && custom_source_range.good()) { + auto& [i, d] = result->values(); + } + + // Alternatively, if custom_source_range throws on error + try { + auto result = scn::scan(custom_source_range, "{} {}"); + if (result) { + auto& [i, d] = result->values(); + } + } catch (const custom_source_range_error& e) { + // ... + } + \endcode + +\section range-requirements Relaxed source range requirements + +The set of allowed source ranges to be given to `scn::scan` is increased in v2, compared to v1. + +In v1, a range was scannable, if it was bidirectional, and default and move constructible. + +In v2, the range needs to just be a `forward_range`, and movable. + +\section ownership Returned ranges do not take ownership (may return `dangling`) + +In v1, the lifetime semantics of the range returned from `scn::scan` were complicated. +Usually, the returned range was a view over the given range, i.e. reference semantics were used. +But, sometimes, if the range was an rvalue container (or anything else that didn't model `borrowed_range`), +the return value contained that range, i.e. ownership was taken. + +\code{.cpp} +// v1: reference semantics +int i{}; +auto result = scn::scan("123 456", "{}", i); +// result contains a string_view over the given string literal + +// v1: reference semantics +std::string source{"123 456"}; +int i{}; +auto result = scn::scan(source, "{}", i); +// result contains a string_view over source + +// v1: ownership semantics +int i{}; +auto result = scn::scan(std::string{"123 456"}, "{}", i); +// result contains a std::string +\endcode + +In v2, the semantics are clearer: a view (`subrange`) over the given range is always returned. +If that view would dangle, `ranges::dangling` is returned instead. + +\code{.cpp} +// v2: reference semantics (no change) +auto result = scn::scan("123 456", "{}"); +// result->begin() points to the given string literal + +// v2: reference semantics (no change) +std::string source{}; +auto result = scn::scan(source, "{}"); +// result->begin() points to source + +// v2: dangling +auto result = scn::scan(std::string{"123 456"}, "{}"); +// result->begin() is of type scn::ranges::dangling, the given std::string has gone out of scope and been destroyed +\endcode + +In other words, in v2, `scn::scan` always returns an iterator pointing to the given range. +If that's not possible without dangling, it returns `scn::ranges::dangling` instead. + +\section files Files removed + +In v1, scnlib provided support for reading files with `scn::file`, `scn::owning_file`, +and `scn::mapped_file`. These caused the library to grow in size, blurred its focus, and were the source of many bugs. + +In v2, these have been removed. +If you need to read from a file, either do your own I/O and give `scn::scan` a string, +or use `scn::basic_istreambuf_view`. +If you need to use memory mapped files, do the mapping yourself, and give `scn::scan` a view into the mapped memory. + +In v2, `scn::cstdin()` and `scn::wcstdin()` have been removed. +For reading from stdin, use `scn::input` and `scn::prompt`, +or create your own `scn::basic_istreambuf_view` from `std::(w)cin`, +remembering to sync the range afterward with `std::(w)cin`. + +\code{.cpp} +// v1: +int i; +auto result = scn::input("{}", i); +// or +auto result = scn::scan(scn::cstdin(), "{}", i); + +// v2: +auto result = scn::input("{}"); +// or +auto in = scn::istreambuf_view{std::cin}; +auto result = scn::scan(in, "{}"); +in.sync(result->begin()); +\endcode + +\section scanner-specialize Specializing `scn::scanner` changed + +In v1, `scn::scanner` took the type it was used for as a template parameter. +Inside it, `parse()` and `scan()` returned a `scn::error`. + +\code{.cpp} +struct int_and_double { + int i; + double d; +}; + +template <> +struct scn::scanner { + template + error parse(ParseCtx& pctx); + + template + error scan(int_and_double& val, Context& ctx) const; + +}; +\endcode + +In v2, `scn::scanner` also takes in the character type of the source range. +This is consistent with `std::formatter`. +The character type defaults to `char`. + +`parse()` and `scan()` return a `scn::scan_expected`. + +`parse()` should be `constexpr`, to support compile-time format string checking. + +\code{.cpp} +struct int_and_double { + int i; + double d; +}; + +template +struct scn::scanner { + template + constexpr auto parse(ParseCtx& pctx) -> scan_expected; + + template + auto scan(int_and_double& val, Context& ctx) const -> scan_expected; + +}; +\endcode + +\section scan_usertype `scn::scan_usertype` removed + +In v1, `scn::scan_usertype` could be used to make scanning values of custom types easier. +This helper function was necessary, because the scanning context had complex logic concerning the source range. +In v2, this has been removed, because of the new tuple-return API, +and because the context no longer deals with complicated ranges. + +\code{.cpp} +// v1 +template +error scan(int_and_double& val, Context& ctx) const { + return scn::scan_usertype(ctx.range(), "[{}, {}]", val.i, val.d); +} + +// v2 +template +auto scan(int_and_double& val, Context& ctx) const + -> expected { + auto result = scn::scan(ctx.range(), "[{}, {}]"); + if (!result) { + return unexpected(result.error()); + } + + std::tie(val.i, val.d) = result->values(); + return result->begin(); +} +\endcode + +\section parser `scn::*_parser` removed + +In v1, there were helper base classes for creating `scanner::parse`, +including `scn::empty_parser` and `scn::common_parser`. + +In v2, these are removed. Create your own `parse` member functions, or reuse already existing `scanner`s. + +\section istream-operator Including `` no longer enables custom scanning for types with `operator>>` by default + +In v1, just by including ``, any type with an `operator>>` would be automatically `scn::scan`able. + +In v2, you'll need to explicitly opt-in to this behavior for your own types, by creating a `scn::scanner`, +and inheriting from the `scn::basic_istream_scanner` class template. + +This is done to avoid potentially surprising behavior. + +\code{.cpp} +#include + +struct mytype { + int i, j; + + friend std::istream& operator>>(std::istream& is, const mytype& val) { + return is >> val.i >> val.j; + } +}; + +// v1 would work out of the box: +mytype val{}; +auto result = scn::scan("123 456", "{}", val); + +// v2 requires a scanner definition +template +struct scn::scanner : public scn::basic_istream_scanner {}; + +auto result = scn::scan("123 456", "{}"); +\endcode + +\section scan_localized `scn::scan_localized` renamed to `scn::scan` + +In v1, to use a `std::locale` in scanning, the function `scn::scan_localized` had to be used. + +In v2, this function is part of the `scn::scan` overload set. + +\code{.cpp} +// v1 +int i; +auto ret = scn::scan_localized(locale, "42", "{}", i); + +// v2; +auto result = scn::scan(locale, "42", "{}"); +\endcode + +\section lists List operations removed + +In v1, there were `scn::scan_list` and `scn::scan_list_ex`, +that could be used to scan multiple values of the same type into a container. + +In v2, these have been removed. +Either scan each value manually, or use the new (experimental) range scanning functionality, in ``. + +\code{.cpp} +// v1 +std::vector vec{}; +auto result = scn::scan_list("123 456 abc", vec); +// vec == [123, 456] +// result.range() == " abc" +// NOTE: result.error() == invalid_scanner_value (because of "abc") + +// v2 +std::vector vec{}; +auto input = scn::ranges::subrange{std::string_view{"123 456 abc"}}; + +while (auto result = scn::scan(input, "{}")) { + vec.push_back(result->value()); + input = result->range(); +} +// vec == [123, 456] +// input == " abc" + +// or, if the source range is in the correct format +// (how std::format would output it) +auto result = scn::scan>("[123, 456]", "{}"); +// result->value() == [123, 456] +\endcode + +\section ignore-getline `scn::ignore` and `scn::getline` removed + +In v2, `scn::ignore` can be replaced with simple range operations, like `scn::ranges::views::drop_while`. + +`scn::getline` can be replaced with `scn::scan(..., "{:[^\n]}")`. + +\section encoding Encoding is always Unicode + +In v1, when scanning in non-localized mode, the input was assumed to be Unicode +(UTF-8, UTF-16, or UTF-32, based on the character type), +and whatever the locale specified in localized mode. +Because of the limited character encoding handling support provided by the standard library, this was buggy. + +In v2, all input is assumed to be Unicode, despite what has been set in a possibly supplied locale. diff --git a/docs/poxy.toml b/docs/poxy.toml new file mode 100644 index 00000000..2208c971 --- /dev/null +++ b/docs/poxy.toml @@ -0,0 +1,53 @@ +name = "scnlib" +author = "Elias Kosunen" +description = "scanf for modern C++" +cpp = 17 +github = "eliaskosunen/scnlib" +license = ['Apache-2.0', 'https://github.com/eliaskosunen/scnlib/blob/dev/LICENSE'] +theme = "dark" +show_includes = false +navbar = ['namespaces', 'classes', 'modules', 'pages', 'repo'] + +[warnings] +enabled = true +treat_as_errors = false +undocumented = true + +[sources] +paths = ['pages', '../include/scn'] +recursive_paths = ['../include/scn/detail', '../include/scn/util'] +patterns = ['*.h', '*.hpp', '*.md', '*.dox'] +strip_paths = ['../include', 'pages'] +#extract_all = true + +[macros] +'SCN_DOXYGEN' = '1' +'SCN_USE_IOSTREAMS' = '1' +'SCN_NOEXCEPT' = 'noexcept' +'SCN_NOEXCEPT_P(a)' = 'noexcept(a)' +'SCN_CONSTEVAL' = 'consteval' +'SCN_NODISCARD' = '[[nodiscard]]' +'SCN_MAYBE_UNUSED' = '[[maybe_unused]]' +'SCN_NO_UNIQUE_ADDRESS' = '[[no_unique_address]]' +'SCN_MOVE(x)' = 'std::move(x)' +'SCN_FWD(x)' = 'std::forward(x)' +'SCN_DECLVAL(x)' = 'std::declval()' +'SCN_BEGIN_NAMESPACE' = '' +'SCN_END_NAMESPACE' = '' +'SCN_GCC_PUSH' = '' +'SCN_GCC_IGNORE(...)' = '' +'SCN_GCC_POP' = '' +'SCN_CLANG_PUSH' = '' +'SCN_CLANG_IGNORE(...)' = '' +'SCN_CLANG_POP' = '' +'SCN_GCC_COMPAT_PUSH' = '' +'SCN_GCC_COMPAT_IGNORE(...)' = '' +'SCN_GCC_COMPAT_POP' = '' +'SCN_MSVC_PUSH' = '' +'SCN_MSVC_IGNORE(...)' = '' +'SCN_MSVC_POP' = '' +'NANO_BEGIN_NAMESPACE' = '' +'NANO_END_NAMESPACE' = '' + +[code_blocks] +macros = ['SCN_[A-Z0-9_]+?'] diff --git a/include/scn/detail/args.h b/include/scn/detail/args.h index c46054ce..d17b1dbe 100644 --- a/include/scn/detail/args.h +++ b/include/scn/detail/args.h @@ -39,8 +39,6 @@ namespace scn { SCN_BEGIN_NAMESPACE namespace detail { - struct monostate {}; - enum class arg_type { none_type, schar_type, @@ -363,17 +361,30 @@ namespace scn { constexpr decltype(auto) visit_scan_arg(Visitor&& vis, basic_scan_arg& arg); - /// Type-erased scanning argument + /** + * Type-erased scanning argument. + * + * Contains a pointer to the value contained in a `scan_arg_store`. + */ template class basic_scan_arg { public: + /** + * Enables scanning of a user-defined type. + * + * Contains a pointer to the value contained in a `scan_arg_store`, and + * a callback for parsing the format string, and scanning the value. + * + * \see scn::visit_scan_arg + */ class handle { public: - explicit handle(detail::custom_value_type custom) - : m_custom(custom) - { - } - + /** + * Parse the format string in `parse_ctx`, and scan the value from + * `ctx`. + * + * \return Any error returned by the scanner + */ scan_error scan(typename Context::parse_context_type& parse_ctx, Context& ctx) const { @@ -381,26 +392,40 @@ namespace scn { } private: + explicit handle(detail::custom_value_type custom) + : m_custom(custom) + { + } + + template + friend constexpr decltype(auto) visit_scan_arg( + Visitor&& vis, + basic_scan_arg& arg); + detail::custom_value_type m_custom; }; + /// Construct a `basic_scan_arg` which doesn't contain an argument. constexpr basic_scan_arg() = default; + /** + * @return `true` if `*this` contains an argument + */ constexpr explicit operator bool() const SCN_NOEXCEPT { return m_type != detail::arg_type::none_type; } - constexpr detail::arg_type type() const + SCN_NODISCARD constexpr detail::arg_type type() const { return m_type; } - constexpr detail::arg_value& value() + SCN_NODISCARD constexpr detail::arg_value& value() { return m_value; } - constexpr const detail::arg_value& value() const + SCN_NODISCARD constexpr const detail::arg_value& value() const { return m_value; } @@ -446,11 +471,11 @@ namespace scn { } // namespace detail /** - * A tuple of scanning arguments, by value. + * A tuple of scanning arguments, stored by value. * * Implicitly convertible to `basic_scan_args`, * to be passed to type-erased along to type-erased scanning functions, - * like `vscan`. + * like `scn::vscan`. */ template class scan_arg_store @@ -458,19 +483,25 @@ namespace scn { using base = detail::scan_arg_store_base; public: + std::tuple& args() + { + return m_args; + } + + private: constexpr scan_arg_store() : scan_arg_store(std::tuple{}) {} + constexpr explicit scan_arg_store(std::tuple&& a) : m_args{std::move(a)}, m_data{std::apply(make_data_array, m_args)} { } - std::tuple& args() - { - return m_args; - } + template + friend constexpr auto make_scan_args(); + template + friend constexpr auto make_scan_args(std::tuple&& values); - private: template static constexpr typename base::value_array_type make_data_array( A&... args) @@ -535,19 +566,33 @@ namespace scn { /** * A view over a collection of scanning arguments (`scan_arg_store`). + * + * Passed to `scn::vscan`, where it's automatically constructed from a + * `scan_arg_store`. */ template class basic_scan_args { public: + /// Construct a view over no arguments constexpr basic_scan_args() = default; + /** + * Construct a view over `store`. + * + * Intentionally not `explicit`. + */ template - constexpr basic_scan_args(scan_arg_store& store) + constexpr /*implicit*/ basic_scan_args( + scan_arg_store& store) : basic_scan_args{scan_arg_store::desc, store.m_data.data()} { } + /** + * \return `basic_scan_arg` at index `id`. Empty `basic_scan_arg` if + * there's no argument at index `id`. + */ SCN_NODISCARD constexpr basic_scan_arg get( std::size_t id) const { @@ -573,13 +618,9 @@ namespace scn { return arg; } - SCN_NODISCARD constexpr std::size_t max_size() const - { - return SCN_LIKELY(is_packed()) - ? detail::max_packed_args - : (m_desc & ~detail::is_unpacked_bit); - } - + /** + * \return Number of arguments in `*this`. + */ SCN_NODISCARD constexpr std::size_t size() const { if (SCN_UNLIKELY(!is_packed())) { @@ -614,6 +655,13 @@ namespace scn { return static_cast((m_desc >> shift) & mask); } + SCN_NODISCARD constexpr std::size_t max_size() const + { + return SCN_LIKELY(is_packed()) + ? detail::max_packed_args + : (m_desc & ~detail::is_unpacked_bit); + } + size_t m_desc{0}; union { detail::arg_value* m_values; diff --git a/include/scn/detail/caching_view.h b/include/scn/detail/caching_view.h index c97440f5..ac2ca93e 100644 --- a/include/scn/detail/caching_view.h +++ b/include/scn/detail/caching_view.h @@ -78,24 +78,35 @@ namespace scn { }; } // namespace detail + /** + * \defgroup scannable Scannable Ranges + */ + /** * A range adaptor over `Range`, that caches the contents of the range, * and allows the user to go back and see these contents. - * Turns an `input_range` into a `bidirectional_range`. + * Effectively, turns an `input_range` into a `bidirectional_range`. * * Move-only. + * + * \ingroup scannable */ template class basic_caching_view : public detail::basic_caching_view_base>, public ranges::view_interface> { public: + /// Underlying range type using range_type = Range; + /// Range character (value) type using char_type = detail::char_t; using difference_type = std::ptrdiff_t; class iterator; + /** + * Construct a `basic_caching_view` from a range + */ template < typename R, std::enable_if_t>* = nullptr> @@ -104,19 +115,36 @@ namespace scn { { } + /** + * \return An `iterator` pointing to the beginning of `base()`. + */ iterator begin() const SCN_NOEXCEPT; + /** + * \return A sentinel corresponding to the end of `base()`. + */ + using detail::basic_caching_view_base>::end; + + /** + * Clears the cache. + * + * After calling `clear()`, `ranges::begin(base())` and `begin()` return + * an iterator pointing to the same element (for the extent for which + * that makes sense for `input_range`s). + */ void clear() { this->m_buffer_begin_offset = this->buffer_size(); this->m_buffer.clear(); } - auto& underlying() + /// Access the underlying range. + auto& base() { return m_range; } - const auto& underlying() const + /// Access the underlying range. + const auto& base() const { return m_range; } @@ -327,6 +355,11 @@ namespace scn { return {*this}; } + /** + * A subrange into a `basic_caching_view`. + * + * Not a type alias to limit template name length. + */ template class basic_caching_subrange : public ranges::subrange>, diff --git a/include/scn/detail/context.h b/include/scn/detail/context.h index c481fdd0..d251c6aa 100644 --- a/include/scn/detail/context.h +++ b/include/scn/detail/context.h @@ -36,18 +36,39 @@ namespace scn { : std::true_type {}; } // namespace detail - /// Scanning context + /** + * \defgroup ctx Contexts and scanners + */ + + /** + * Scanning context. + * + * \ingroup ctx + */ template class basic_scan_context { public: + /// Character type of the input using char_type = CharT; + + /** + * Contained range type. + * In normal operation, one of: + * - `std::basic_string_view` + * - `scn::basic_istreambuf_subrange` + * - `scn::basic_erased_subrange` + */ using range_type = Range; + using iterator = ranges::iterator_t; using sentinel = ranges::sentinel_t; using parse_context_type = basic_scan_parse_context; using arg_type = basic_scan_arg; + /** + * The scanner type associated with this scanning context. + */ template using scanner_type = scanner; @@ -81,7 +102,7 @@ namespace scn { return m_args; } - /// Returns a view over the input range, starting at `current()` + /// \return A view over the input range, starting at `current()` constexpr range_type range() const { if constexpr (detail::is_string_view::value) { @@ -92,8 +113,11 @@ namespace scn { return {current(), ranges::end(m_range)}; } } - /// Returns an iterator pointing to - /// the beginning of the current input range + + /** + * \return An iterator pointing to + * the beginning of the current input range + */ constexpr iterator current() const { return m_current; @@ -110,7 +134,7 @@ namespace scn { m_current = SCN_MOVE(it); } - constexpr detail::locale_ref locale() const + SCN_NODISCARD constexpr detail::locale_ref locale() const { return m_locale; } diff --git a/include/scn/detail/erased_range.h b/include/scn/detail/erased_range.h index eb89a7a7..dc8fb00b 100644 --- a/include/scn/detail/erased_range.h +++ b/include/scn/detail/erased_range.h @@ -149,7 +149,7 @@ namespace scn { } // namespace detail /** - * Type-erased forward_range. + * Type-erased `forward_range`. */ template class basic_erased_range { @@ -160,16 +160,24 @@ namespace scn { class iterator; using sentinel = ranges_std::default_sentinel_t; - basic_erased_range() = default; - - template - explicit basic_erased_range(R&& r) - : m_impl(detail::make_unique_erased_range_impl(SCN_FWD(r))) + /** + * Construct a `basic_erased_range` containing `range`. + */ + template + explicit basic_erased_range(Range&& range) + : m_impl( + detail::make_unique_erased_range_impl(SCN_FWD(range))) { } + /** + * \return An `iterator` pointing to the beginning of `*this`. + */ iterator begin() const SCN_NOEXCEPT; + /** + * \return A `sentinel` corresponding to the end of `*this`. + */ SCN_NODISCARD sentinel end() const SCN_NOEXCEPT { return ranges_std::default_sentinel; @@ -179,7 +187,6 @@ namespace scn { using impl_ptr_type = std::unique_ptr>; - // TODO: Small object optimization? impl_ptr_type m_impl{nullptr}; }; @@ -341,6 +348,11 @@ namespace scn { return {m_impl.get()}; } + /** + * A subrange into a `basic_erased_range`. + * + * Not a type alias to avoid long template names. + */ template struct basic_erased_subrange : public ranges::subrange>, diff --git a/include/scn/detail/error.h b/include/scn/detail/error.h index 1b10bc10..55393f0b 100644 --- a/include/scn/detail/error.h +++ b/include/scn/detail/error.h @@ -29,6 +29,8 @@ namespace scn { /** * Error class. * Used as a return value for functions without a success value. + * + * \ingroup result */ class SCN_TRIVIAL_ABI scan_error { public: @@ -50,10 +52,12 @@ namespace scn { /// from. The library can't use the source range in this state. /// Can only happen when using an istream as the input. bad_source_error, +#if 0 /// This operation is only possible with exceptions enabled // exceptions_required, // currently unused /// This operation is only possible with the heap enabled // heap_required, // currently unused +#endif max_error }; @@ -68,8 +72,11 @@ namespace scn { return {}; } + /// Constructs an error with `code::good` and no message. constexpr scan_error() SCN_NOEXCEPT = default; constexpr scan_error(success_tag_t) SCN_NOEXCEPT : scan_error() {} + + /// Constructs an error with `c` and `m` constexpr scan_error(code_t c, const char* m) SCN_NOEXCEPT : m_msg(m), m_code(c) { diff --git a/include/scn/detail/format_string.h b/include/scn/detail/format_string.h index 43eab3f1..32ea9e1e 100644 --- a/include/scn/detail/format_string.h +++ b/include/scn/detail/format_string.h @@ -28,7 +28,11 @@ namespace scn { std::basic_string_view str; }; - /// Create a runtime format string + /** + * Create a runtime format string + * + * Can be used to avoid compile-time format string checking + */ inline basic_runtime_format_string runtime(std::string_view s) { return {{s}}; diff --git a/include/scn/detail/input_map.h b/include/scn/detail/input_map.h index 705498bf..db90c97f 100644 --- a/include/scn/detail/input_map.h +++ b/include/scn/detail/input_map.h @@ -24,6 +24,7 @@ namespace scn { SCN_BEGIN_NAMESPACE + /// Tag type to indicate an invalid range given to `scn::scan` struct invalid_input_range {}; namespace detail::_scan_map_input_range_impl { diff --git a/include/scn/detail/istream_range.h b/include/scn/detail/istream_range.h index 716fd950..1f184c1d 100644 --- a/include/scn/detail/istream_range.h +++ b/include/scn/detail/istream_range.h @@ -213,7 +213,11 @@ namespace scn { using istream_type = std::basic_istream; using iterator = typename base::iterator; + /// Construct a `basic_istreambuf_view` over a `std::basic_istream` basic_istreambuf_view(istream_type& is) : base(istreambuf_view{is}) {} + + /// Construct a `basic_istreambuf_view` over a `std::basic_streambuf`. + /// Requires `s != nullptr`. basic_istreambuf_view(streambuf_type* s) : base(istreambuf_view{s}) {} /** @@ -238,6 +242,11 @@ namespace scn { void sync(iterator it); }; + /** + * A subrange into a `basic_istreambuf_range`. + * + * Not a type alias to avoid long template names. + */ template class basic_istreambuf_subrange : public ranges::subrange< diff --git a/include/scn/detail/parse_context.h b/include/scn/detail/parse_context.h index d27f78ae..8b646c54 100644 --- a/include/scn/detail/parse_context.h +++ b/include/scn/detail/parse_context.h @@ -25,6 +25,8 @@ namespace scn { /** * Format string parsing context, wrapping the format string being parsed, * and a counter for argument indexing. + * + * \ingroup ctx */ template class basic_scan_parse_context { @@ -32,6 +34,9 @@ namespace scn { using char_type = CharT; using iterator = typename std::basic_string_view::const_pointer; + /** + * Construct a `basic_scan_parse_context` over a format string `format`. + */ explicit constexpr basic_scan_parse_context( std::basic_string_view format, int next_arg_id = 0) @@ -44,6 +49,7 @@ namespace scn { { return m_format.data(); } + /// Returns an iterator pointing to the end of the format string constexpr auto end() const SCN_NOEXCEPT { return m_format.data() + m_format.size(); diff --git a/include/scn/detail/pp_detect.h b/include/scn/detail/pp_detect.h index 79d1da9a..ad65498e 100644 --- a/include/scn/detail/pp_detect.h +++ b/include/scn/detail/pp_detect.h @@ -17,6 +17,10 @@ #pragma once +#ifndef __cplusplus +#error "scnlib is a C++ library" +#endif + #ifdef __has_include #define SCN_HAS_INCLUDE(x) __has_include(x) #else diff --git a/include/scn/detail/ranges.h b/include/scn/detail/ranges.h index a533d459..0146dcf7 100644 --- a/include/scn/detail/ranges.h +++ b/include/scn/detail/ranges.h @@ -431,17 +431,18 @@ namespace scn { typename simple_borrowed_subrange::type; template > - struct borrowed_ssubrange { + struct borrowed_subrange_with_sentinel { using type = ranges::subrange, ranges::sentinel_t>; }; template - struct borrowed_ssubrange { + struct borrowed_subrange_with_sentinel { using type = ranges::dangling; }; template - using borrowed_ssubrange_t = typename borrowed_ssubrange::type; + using borrowed_subrange_with_sentinel_t = + typename borrowed_subrange_with_sentinel::type; namespace r_pf { diff --git a/include/scn/detail/result.h b/include/scn/detail/result.h index 72bb54b9..5a591b26 100644 --- a/include/scn/detail/result.h +++ b/include/scn/detail/result.h @@ -46,9 +46,15 @@ namespace scn { }; } // namespace detail + /** + * \defgroup result Result types + */ + /** * Type returned by `scan`, contains the unused input as a `subrange`, and * the scanned values in a `tuple`. + * + * \ingroup result */ template class scan_result { @@ -76,6 +82,7 @@ namespace scn { { } + /// Converting constructor from a range and a tuple template >> @@ -150,52 +157,54 @@ namespace scn { } /// Access the scanned values - /// @{ tuple_type& values() & { return m_values; } + /// Access the scanned values const tuple_type& values() const& { return m_values; } + /// Access the scanned values tuple_type&& values() && { return SCN_MOVE(m_values); } + /// Access the scanned values const tuple_type&& values() const&& { return SCN_MOVE(m_values); } - /// @} /// Access the single scanned value - /// @{ template > decltype(auto) value() & { return std::get<0>(m_values); } + /// Access the single scanned value template > decltype(auto) value() const& { return std::get<0>(m_values); } + /// Access the single scanned value template > decltype(auto) value() && { return SCN_MOVE(std::get<0>(m_values)); } + /// Access the single scanned value template > decltype(auto) value() const&& { return SCN_MOVE(std::get<0>(m_values)); } - /// @} private: range_type m_range{}; @@ -244,7 +253,7 @@ namespace scn { auto map_scan_result_range(SourceRange&& source, const ResultIterator& mapped_begin, const ResultIterator& result) - -> borrowed_ssubrange_t + -> borrowed_subrange_with_sentinel_t { auto end = map_scan_result_end(source); return { diff --git a/include/scn/detail/scan.h b/include/scn/detail/scan.h index d3c83dce..557338d9 100644 --- a/include/scn/detail/scan.h +++ b/include/scn/detail/scan.h @@ -44,6 +44,8 @@ namespace scn { * * return scn::make_scan_result(std::move(result), std::move(args)); * \endcode + * + * \ingroup result */ template auto make_scan_result(scan_expected&& result, @@ -61,8 +63,8 @@ namespace scn { * types of the scanned arguments. */ template - using scan_result_type = - scan_expected, Args...>>; + using scan_result_type = scan_expected< + scan_result, Args...>>; namespace detail { // Boilerplate for scan() @@ -79,6 +81,10 @@ namespace scn { } } // namespace detail + /** + * \defgroup scan Basic scanning API + */ + /** * Scans `Args...` from the range given to it (`source`), according to the * specifications given in the format string (`format`). @@ -90,6 +96,8 @@ namespace scn { * if (auto result = scn::scan("123", "{}")) * int value = result->value(); * \endcode + * + * \ingroup scan */ template value() * \endcode + * + * \ingroup scan */ template value() == 3.14 * \endcode + * + * \ingroup locale */ template (source, "{}")`, * except it can skip parsing the format string, gaining performance. + * + * \ingroup scan */ template SCN_NODISCARD auto scan_value(Source&& source) @@ -228,7 +250,11 @@ namespace scn { return detail::scan_value_impl(SCN_FWD(source), T{}); } - /// `scan` a single value, with default options, and a default value. + /** + * `scan` a single value, with default options, and a default value. + * + * \ingroup scan + */ template SCN_NODISCARD auto scan_value(Source&& source, T default_value) -> scan_result_type @@ -262,6 +288,8 @@ namespace scn { * \code{.cpp} * auto result = scn::input("{}"); * \endcode + * + * \ingroup scan */ template SCN_NODISCARD auto input(format_string format) @@ -271,7 +299,11 @@ namespace scn { format); } - /// Write msg to stdout, and call `input(format)` + /** + * Write msg to stdout, and call `input(format)` + * + * \ingroup scan + */ template SCN_NODISCARD auto prompt(const char* msg, format_string format) -> scan_result_type diff --git a/include/scn/detail/scanner.h b/include/scn/detail/scanner.h index cec4cbf6..e6c2ef34 100644 --- a/include/scn/detail/scanner.h +++ b/include/scn/detail/scanner.h @@ -37,7 +37,11 @@ namespace scn { const basic_format_specs& specs); } // namespace detail - // scanner specialization for all built-in types + /** + * `scanner` specialization for all built-in types + * + * \ingroup ctx + */ template struct scanner< T, @@ -154,6 +158,16 @@ namespace scn { } } // namespace detail + /** + * Type for discarding any scanned value. + * Example: + * + * \code{.cpp} + * auto r = scn::scan>("42", "{}"); + * // r.has_value() == true + * // decltype(r->value()) is scn::discard + * \endcode + */ template struct discard { constexpr discard() = default; diff --git a/include/scn/detail/scanner_range.h b/include/scn/detail/scanner_range.h index 50624d26..0cd7de42 100644 --- a/include/scn/detail/scanner_range.h +++ b/include/scn/detail/scanner_range.h @@ -420,7 +420,7 @@ namespace scn { public: constexpr range_scanner() = default; - constexpr detail::range_scanner_type& underlying() + constexpr detail::range_scanner_type& base() { return m_underlying; } @@ -525,8 +525,8 @@ namespace scn { } else if constexpr (Kind == range_format::map) { m_underlying.set_brackets("{", "}"); - m_underlying.underlying().set_brackets({}, {}); - m_underlying.underlying().set_separator(":"); + m_underlying.base().set_brackets({}, {}); + m_underlying.base().set_separator(":"); } } diff --git a/include/scn/detail/visitor.h b/include/scn/detail/visitor.h index 17a54837..ad43d49b 100644 --- a/include/scn/detail/visitor.h +++ b/include/scn/detail/visitor.h @@ -22,6 +22,14 @@ namespace scn { SCN_BEGIN_NAMESPACE + /** + * Visit a `basic_scan_arg` with `Visitor`. + * Calls `vis` with the value stored in `arg`. + * If no value is contained in `arg`, calls `vis` with a `monostate`. + * + * \return `vis(x)`, where `x` is either a reference to the value contained + * in `arg`, or a `basic_scan_arg::handle`. + */ template constexpr decltype(auto) visit_scan_arg(Visitor&& vis, basic_scan_arg& arg) @@ -83,7 +91,7 @@ namespace scn { SCN_UNLIKELY_ATTR case detail::arg_type::none_type: default: { - detail::monostate val{}; + monostate val{}; return vis(val); } diff --git a/include/scn/detail/vscan.h b/include/scn/detail/vscan.h index 33d9fd1b..f4e0e550 100644 --- a/include/scn/detail/vscan.h +++ b/include/scn/detail/vscan.h @@ -147,8 +147,18 @@ namespace scn { #endif } // namespace detail + /** + * \defgroup vscan Type-erased scanning API + */ + + /** + * Result type returned by `vscan`. + * + * \ingroup vscan + */ template - using vscan_result = scan_expected>; + using vscan_result = + scan_expected>; namespace detail { template @@ -220,6 +230,12 @@ namespace scn { SCN_GCC_PUSH SCN_GCC_IGNORE("-Wnoexcept") + /** + * Perform actual scanning from `range`, according to `format`, into the + * type-erased arguments at `args`. Called by `scan`. + * + * \ingroup vscan + */ template auto vscan(Range&& range, std::string_view format, @@ -228,6 +244,13 @@ namespace scn { return detail::vscan_generic(SCN_FWD(range), format, args); } + /** + * Perform actual scanning from `range`, according to `format`, into the + * type-erased arguments at `args`, using `loc`, if requested. Called by + * `scan`. + * + * \ingroup locale + */ template > @@ -240,6 +263,12 @@ namespace scn { args); } + /** + * Perform actual scanning from `range` into the type-erased argument at + * `arg`. Called by `scan_value`. + * + * \ingroup vscan + */ template auto vscan_value(Range&& range, scan_arg_for arg) -> vscan_result diff --git a/include/scn/detail/xchar.h b/include/scn/detail/xchar.h index b8639cf8..07a16cce 100644 --- a/include/scn/detail/xchar.h +++ b/include/scn/detail/xchar.h @@ -23,8 +23,13 @@ namespace scn { SCN_BEGIN_NAMESPACE + /** + * \defgroup xchar Wide character APIs + */ + // vscan + /// \ingroup xchar template auto vscan(Range&& range, std::wstring_view format, @@ -33,6 +38,7 @@ namespace scn { return detail::vscan_generic(SCN_FWD(range), format, args); } + /// \ingroup xchar template > @@ -45,6 +51,7 @@ namespace scn { args); } + /// \ingroup xchar template auto vscan_value(Range&& range, scan_arg_for arg) -> vscan_result @@ -67,6 +74,7 @@ namespace scn { // scan + /// \ingroup xchar template (SCN_FWD(source), format, {}); } + /// \ingroup xchar template SCN_NODISCARD auto input(wformat_string format) -> scan_result_type @@ -134,6 +146,7 @@ namespace scn { format); } + /// \ingroup xchar template SCN_NODISCARD auto prompt(const wchar_t* msg, wformat_string format) diff --git a/include/scn/fwd.h b/include/scn/fwd.h index b7308851..7c1573da 100644 --- a/include/scn/fwd.h +++ b/include/scn/fwd.h @@ -30,9 +30,15 @@ SCN_GCC_IGNORE("-Wrestrict") SCN_GCC_POP +/** + * scnlib namespace, containing the library interface + */ namespace scn { SCN_BEGIN_NAMESPACE + /// Placeholder monostate type + struct monostate {}; + // detail/args.h template @@ -59,13 +65,17 @@ namespace scn { template class basic_erased_range; + /// using erased_range = basic_erased_range; + /// using werased_range = basic_erased_range; template struct basic_erased_subrange; + /// using erased_subrange = basic_erased_subrange; + /// using werased_subrange = basic_erased_subrange; // detail/error.h @@ -113,13 +123,17 @@ namespace scn { template class basic_istreambuf_view; + /// using istreambuf_view = basic_istreambuf_view; + /// using wistreambuf_view = basic_istreambuf_view; template class basic_istreambuf_subrange; + /// using istreambuf_subrange = basic_istreambuf_subrange; + /// using wistreambuf_subrange = basic_istreambuf_subrange; // detail/istream_scanner.h @@ -127,7 +141,9 @@ namespace scn { template struct basic_istream_scanner; + /// using istream_scanner = basic_istream_scanner; + /// using wistream_scanner = basic_istream_scanner; #endif // SCN_USE_IOSTREAMS @@ -139,7 +155,9 @@ namespace scn { template class basic_scan_parse_context; + /// using scan_parse_context = basic_scan_parse_context; + /// using wscan_parse_context = basic_scan_parse_context; namespace detail { @@ -166,6 +184,11 @@ namespace scn { // detail/scanner.h + /** + * Scanner type, can be customized to enable scanning of user-defined types + * + * \ingroup ctx + */ template struct scanner { scanner() = delete; @@ -238,5 +261,9 @@ namespace scn { // util/string_view.h: empty + /// Private implementation namespace + namespace detail { + } + SCN_END_NAMESPACE } // namespace scn diff --git a/include/scn/util/expected.h b/include/scn/util/expected.h index 5c254d95..233ac29e 100644 --- a/include/scn/util/expected.h +++ b/include/scn/util/expected.h @@ -23,8 +23,13 @@ namespace scn { SCN_BEGIN_NAMESPACE - // Doing this instead of a simple using-declaration - // to shorten template names + /** + * An `expected`. + * + * Not a type alias to shorten template names +* +* \ingroup result + */ template struct scan_expected : public expected { using expected::expected; diff --git a/include/scn/util/expected_impl.h b/include/scn/util/expected_impl.h index a3934ab8..7d3cf280 100644 --- a/include/scn/util/expected_impl.h +++ b/include/scn/util/expected_impl.h @@ -33,8 +33,11 @@ namespace scn { SCN_BEGIN_NAMESPACE - struct monostate {}; - + /** + * A C++23-like `expected`. + * + * \ingroup result + */ template class expected; @@ -682,7 +685,7 @@ namespace scn { } }; - /** + /* * Base class trickery to conditionally mark copy and move * constructors of an expected as =deleted. * @@ -758,7 +761,7 @@ namespace scn { "constructible"); }; - /// Same as above, but for assignment + // Same as above, but for assignment template ::value && @@ -825,7 +828,7 @@ namespace scn { struct non_default_ctor_tag_t {}; - /** + /* * Same as above, but for the default constructor * * The constructor taking a non_default_ctor_tag_t is needed, to diff --git a/include/scn/util/optional.h b/include/scn/util/optional.h deleted file mode 100644 index 9182742a..00000000 --- a/include/scn/util/optional.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2017 Elias Kosunen -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is a part of scnlib: -// https://github.com/eliaskosunen/scnlib - -#pragma once - -#include - -#include - -namespace scn { - SCN_BEGIN_NAMESPACE - - template - using optional = std::optional; - - SCN_END_NAMESPACE -} // namespace scn diff --git a/include/scn/util/span.h b/include/scn/util/span.h index 0401d52a..626b6a1c 100644 --- a/include/scn/util/span.h +++ b/include/scn/util/span.h @@ -188,36 +188,5 @@ namespace scn { pointer m_end{nullptr}; }; - template < - typename I, - typename S, - typename Ptr = decltype(detail::to_address(SCN_DECLVAL(I))), - typename SPtr = decltype(detail::to_address(SCN_DECLVAL(S))), - typename ValueT = std::remove_reference_t>> - constexpr auto make_span(I first, S last) SCN_NOEXCEPT->span - { - return {first, last}; - } - template < - typename I, - typename Ptr = decltype(detail::to_address(SCN_DECLVAL(I))), - typename ValueT = std::remove_reference_t>> - constexpr auto make_span(I first, - std::size_t len) SCN_NOEXCEPT->span - { - return {first, len}; - } - - template - constexpr span make_span(T& container) SCN_NOEXCEPT - { - using std::begin; - using std::end; - return span( - detail::to_address(begin(container)), - static_cast( - std::distance(begin(container), end(container)))); - } - SCN_END_NAMESPACE } // namespace scn diff --git a/src/scn/impl/reader/common.h b/src/scn/impl/reader/common.h index 5205b932..6a1ea40d 100644 --- a/src/scn/impl/reader/common.h +++ b/src/scn/impl/reader/common.h @@ -136,7 +136,7 @@ namespace scn { template scan_expected> - read_default(Range&&, detail::monostate&, detail::locale_ref) + read_default(Range&&, monostate&, detail::locale_ref) { SCN_EXPECT(false); SCN_UNREACHABLE; @@ -146,7 +146,7 @@ namespace scn { scan_expected> read_specs( Range&&, const detail::basic_format_specs&, - detail::monostate&, + monostate&, detail::locale_ref) { SCN_EXPECT(false);