diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index d5692bf5..a36233f1 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -18,6 +18,13 @@ jobs: compiler_version: [g++-10, g++-11] cxx_std: [17, 20] os: [ubuntu-22.04] + disable_trace: [false] + include: + # Test with trace disabled + - compiler_version: g++-11 + cxx_std: 20 + os: ubuntu-22.04 + disable_trace: true runs-on: ${{ matrix.os }} @@ -30,7 +37,11 @@ jobs: run: | mkdir build cd build - cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler_version }} -DCXX_STD=${{ matrix.cxx_std }} + if [ "${{ matrix.disable_trace }}" = "true" ]; then + cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler_version }} -DCXX_STD=${{ matrix.cxx_std }} -DDISABLE_TRACE=true + else + cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler_version }} -DCXX_STD=${{ matrix.cxx_std }} + fi - name: Build run: cd build ; make -j4 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d40a5f8e..1a202bdc 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -17,6 +17,12 @@ jobs: matrix: cxx_std: [17, 20, 23] os: [windows-2022] + disable_trace: [false] + include: + # Test with trace disabled + - cxx_std: 20 + os: windows-2022 + disable_trace: true runs-on: ${{ matrix.os }} @@ -26,7 +32,13 @@ jobs: - name: Configure CMake # Configure CMake in a 'build' subdirectory. Visual Studio is a multi-config generator, so we don't use CMAKE_BUILD_TYPE. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B build -DCXX_STD=${{ matrix.cxx_std }} + run: | + if ("${{ matrix.disable_trace }}" -eq "true") { + cmake -B build -DCXX_STD=${{ matrix.cxx_std }} -DDISABLE_TRACE=true + } else { + cmake -B build -DCXX_STD=${{ matrix.cxx_std }} + } + shell: pwsh - name: Build working-directory: build diff --git a/CMakeLists.txt b/CMakeLists.txt index c9d78d7a..115d0b30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,12 @@ if (BUILD_WITH_HANA) add_definitions(-DBOOST_PARSER_USE_HANA_TUPLE) endif() +set(DISABLE_TRACE false CACHE BOOL + "Disable parser trace functionality (defines BOOST_PARSER_DISABLE_TRACE).") +if (DISABLE_TRACE) + add_definitions(-DBOOST_PARSER_DISABLE_TRACE) +endif() + ################################################## # Dependencies diff --git a/README.md b/README.md index 9f69eaa9..b1a8ba96 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,6 @@ Develop status: [![Windows MSVC](https://github.com/tzlaine/parser/actions/workflows/windows.yml/badge.svg?branch=develop)](https://github.com/tzlaine/parser/actions/workflows/windows.yml) -[![macos-13 - Clang 14](https://github.com/tzlaine/parser/actions/workflows/macos-13.yml/badge.svg?branch=develop)](https://github.com/tzlaine/parser/actions/workflows/macos-13.yml) +[![macos-15 - Clang](https://github.com/tzlaine/parser/actions/workflows/macos-15.yml/badge.svg?branch=develop)](https://github.com/tzlaine/parser/actions/workflows/macos-15.yml) [![License](https://img.shields.io/badge/license-boost-brightgreen.svg)](LICENSE_1_0.txt) diff --git a/doc/parser.qbk b/doc/parser.qbk index b6ab37d7..509ccf49 100644 --- a/doc/parser.qbk +++ b/doc/parser.qbk @@ -148,6 +148,7 @@ [def _RULES_ [macroref BOOST_PARSER_DEFINE_RULES `BOOST_PARSER_DEFINE_RULES`]] [def _AGGR_SIZE_ [macroref BOOST_PARSER_MAX_AGGREGATE_SIZE `BOOST_PARSER_MAX_AGGREGATE_SIZE`]] [def _SUBRNG_ [macroref BOOST_PARSER_SUBRANGE `BOOST_PARSER_SUBRANGE`]] +[def _DISABLE_TRACE_ [macroref BOOST_PARSER_DISABLE_TRACE `BOOST_PARSER_DISABLE_TRACE`]] [def __p_ [globalref boost::parser::_p `_p`]] diff --git a/doc/tables.qbk b/doc/tables.qbk index a3bea4ea..a70c846e 100644 --- a/doc/tables.qbk +++ b/doc/tables.qbk @@ -317,6 +317,16 @@ the input they match unless otherwise stated in the table below.] [ `std::string` if `_ATTR_np_(p)` is `char` or `char32_t`, otherwise `std::vector<_ATTR_np_(p)>` ] [ The special value _inf_ may be used for the upper bound; it indicates unlimited repetition. `decltype(_RES_np_(arg0))` and `decltype(_RES_np_(arg1))` each must be implicitly convertible to `int64_t`. Matching _e_ an unlimited number of times creates an infinite loop, which is undefined behavior in C++. _Parser_ will assert in debug mode when it encounters `_rpt_np_(n, _inf_)[_e_]` (this applies to unconditional _e_ only). ]] + [[ `_rpt_np_(arg0, p1)[p2]` ] + [ Matches iff `p2` matches exactly `_RES_np_(arg0)` times, matching `p1` in between each pair of consecutive matches of `p2`. Equivalent to: _e_ for `_RES_np_(arg0) <= 0`, and `_rpt_np_(_RES_np_(arg0) - 1)[p2 >> p1] >> p2` otherwise. ] + [ `std::string` if `_ATTR_np_(p2)` is `char` or `char32_t`, otherwise `std::vector<_ATTR_np_(p2)>` ] + [ The special value _inf_ may be used; it indicates unlimited repetition. `decltype(_RES_np_(arg0))` must be implicitly convertible to `int64_t`. Matching _e_ an unlimited number of times creates an infinite loop, which is undefined behavior in C++. _Parser_ will assert in debug mode when it encounters `_rpt_np_(_inf_)[_e_]` (this applies to unconditional _e_ only). ]] + + [[ `_rpt_np_(arg0, arg1, p1)[p2]` ] + [ Matches iff `p2` matches between `_RES_np_(arg0)` and `_RES_np_(arg1)` times, inclusively, matching `p1` in between each pair of consecutive matches of `p2`. Equivalent to: _e_ for `_RES_np_(arg1) <= 0`, and `_rpt_np_(_RES_np_(arg0) - 1, _RES_np_(arg1) - 1)[p2 >> p1] >> p2` otherwise. ] + [ `std::string` if `_ATTR_np_(p2)` is `char` or `char32_t`, otherwise `std::vector<_ATTR_np_(p2)>` ] + [ The special value _inf_ may be used for the upper bound; it indicates unlimited repetition. `decltype(_RES_np_(arg0))` and `decltype(_RES_np_(arg1))` each must be implicitly convertible to `int64_t`. Matching _e_ an unlimited number of times creates an infinite loop, which is undefined behavior in C++. _Parser_ will assert in debug mode when it encounters `_rpt_np_(n, _inf_)[_e_]` (this applies to unconditional _e_ only). ]] + [[ `_if_np_(pred)[p]` ] [ Equivalent to `_e_(pred) >> p`. ] [ `_ATTR_np_(p)` ] diff --git a/doc/tutorial.qbk b/doc/tutorial.qbk index aefc7cc0..1d24510c 100644 --- a/doc/tutorial.qbk +++ b/doc/tutorial.qbk @@ -1514,9 +1514,10 @@ attribute, simply returns `bool`; this indicates the success or failure of the parse.] [warning _Parser_ assumes that all attributes are semi-regular (see -`std::semiregular`). Within the _Parser_ code, attributes are assigned, -moved, copy, and default constructed. There is no support for move-only or -non-default-constructible types.] +`std::semiregular`). Move-only types that are nearly `std::semiregular`, +other than their copy operations, are also supported. Within the _Parser_ +code, attributes are move assigned, moved, and default constructed. There is +no support for non-default-constructible types.] [heading The attribute type trait, _attr_] @@ -3766,6 +3767,22 @@ Some things to be aware of when looking at _Parser_ trace output: produces that value. In these cases, you'll see the resolved value of the parse argument. +[heading Compile-time trace disabling] + +While trace mode is very useful for debugging, it does have some overhead. +Most parser templates are instantiated twice: Once with tracing enabled, +once with tracing disabled. +If you want to completely disable trace functionality at compile time, you can define +`BOOST_PARSER_DISABLE_TRACE` before including any Boost.Parser headers: + +[teletype]`` +#define BOOST_PARSER_DISABLE_TRACE +#include +`` + +When this define is set, all trace-related code is compiled out, +reducing the compile time. + [endsect] [section Memory Allocation] @@ -3983,7 +4000,8 @@ This defines a RAII trace object that will produce the verbose trace requested by the user if they passed `_trace_::on` to the top-level parse. It only has effect if `detail::enable_trace(flags)` is `true`. If trace is enabled, it will show the state of the parse at the point at which it is defined, and then -again when it goes out of scope. +again when it goes out of scope. By defining `BOOST_PARSER_DISABLE_TRACE`, +the trace feature can be disabled globally at compile time. [important For the tracing code to work, you must define an overload of `detail::print_parser` for your new parser type/template. See diff --git a/include/boost/parser/config.hpp b/include/boost/parser/config.hpp index 94067249..ff31295a 100644 --- a/include/boost/parser/config.hpp +++ b/include/boost/parser/config.hpp @@ -36,6 +36,10 @@ disable the use of concepts, define this macro. */ # define BOOST_PARSER_DISABLE_CONCEPTS +/** Boost.Parser will generate code to trace the execution of each and every + parser by default. To disable all trace code, define this macro. */ +# define BOOST_PARSER_DISABLE_TRACE + /** Define this macro to use `boost::hana::tuple` instead of `std::tuple` throughout Boost.Parser. */ # define BOOST_PARSER_USE_HANA_TUPLE @@ -92,6 +96,12 @@ # define BOOST_PARSER_USE_CONCEPTS 0 #endif +#if defined(BOOST_PARSER_DISABLE_TRACE) +# define BOOST_PARSER_DO_TRACE 0 +#else +# define BOOST_PARSER_DO_TRACE 1 +#endif + #if defined(__cpp_lib_ranges) && BOOST_PARSER_USE_CONCEPTS # define BOOST_PARSER_SUBRANGE std::ranges::subrange #else diff --git a/include/boost/parser/detail/printing.hpp b/include/boost/parser/detail/printing.hpp index 52facf7b..b8866f52 100644 --- a/include/boost/parser/detail/printing.hpp +++ b/include/boost/parser/detail/printing.hpp @@ -593,11 +593,14 @@ namespace boost { namespace parser { namespace detail { } template - auto resolve(Context const & context, T const & x); + decltype(auto) resolve(Context const & context, T const & x); template auto resolve(Context const &, nope n); + template + constexpr bool trace_disabled = !DoTraceMacro; + template< bool DoTrace, typename Iter, @@ -606,6 +609,8 @@ namespace boost { namespace parser { namespace detail { typename Attribute> struct scoped_trace_t { + static_assert(!trace_disabled); + scoped_trace_t( std::ostream & os, Iter & first, @@ -681,6 +686,9 @@ namespace boost { namespace parser { namespace detail { flags f, Attribute const & attr) { + if constexpr (!BOOST_PARSER_DO_TRACE) + return; + if constexpr (Context::do_trace) { std::stringstream oss; detail::print_parser(context, parser, oss); @@ -695,6 +703,9 @@ namespace boost { namespace parser { namespace detail { template auto final_trace(Context const & context, flags f, Attribute const & attr) { + if constexpr (!BOOST_PARSER_DO_TRACE) + return; + if (!detail::do_trace(f)) return; diff --git a/include/boost/parser/parser.hpp b/include/boost/parser/parser.hpp index caf72e85..1c5585f3 100644 --- a/include/boost/parser/parser.hpp +++ b/include/boost/parser/parser.hpp @@ -1020,7 +1020,7 @@ namespace boost { namespace parser { bool Callable = is_detected_v> struct resolve_impl { - static auto call(Context const &, T const & x) { return x; } + static auto& call(Context const &, T const & x) { return x; } }; template @@ -1033,7 +1033,7 @@ namespace boost { namespace parser { }; template - auto resolve(Context const & context, T const & x) + decltype(auto) resolve(Context const & context, T const & x) { return resolve_impl::call(context, x); } @@ -2168,7 +2168,10 @@ namespace boost { namespace parser { { if (!gen_attrs || !x) return; - c.insert(c.end(), x->begin(), x->end()); + c.insert(c.end(), + std::make_move_iterator(x->begin()), + std::make_move_iterator(x->end()) + ); } template @@ -3177,6 +3180,7 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, @@ -3185,6 +3189,7 @@ namespace boost { namespace parser { detail::in_apply_parser(flags) ? detail::disable_trace(flags) : flags, retval); +#endif if constexpr (detail::is_optional_v) { detail::optional_type attr; @@ -3207,18 +3212,44 @@ namespace boost { namespace parser { int64_t count = 0; - for (int64_t end = detail::resolve(context, min_); count != end; - ++count) { + auto const iteration = [&](auto prev_first, auto on_fail) { + if constexpr (!detail::is_nope_v) { + if (count) { + detail::skip(first, last, skip, flags); + delimiter_parser_.call( + first, + last, + context, + skip, + detail::disable_attrs(flags), + success); + if (!success) { + on_fail(prev_first); + return false; + } + } + } + detail::skip(first, last, skip, flags); attr_t attr{}; parser_.call( first, last, context, skip, flags, success, attr); if (!success) { - detail::assign(retval, Attribute()); - return; + on_fail(prev_first); + return false; } detail::move_back( retval, std::move(attr), detail::gen_attrs(flags)); + return true; + }; + + for (int64_t end = detail::resolve(context, min_); count != end; + ++count) { + if (!iteration(first, [&](auto prev_first) { + detail::assign(retval, Attribute()); + })) { + return; + } } int64_t const end = detail::resolve(context, max_); @@ -3229,37 +3260,12 @@ namespace boost { namespace parser { !detail::is_unconditional_eps{} || end < Inf); for (; count != end; ++count) { - auto const prev_first = first; - // This is only ever used in delimited_parser, which - // always has a min=1; we therefore know we're after a - // previous element when this executes. - if constexpr (!detail::is_nope_v) { - detail::skip(first, last, skip, flags); - delimiter_parser_.call( - first, - last, - context, - skip, - detail::disable_attrs(flags), - success); - if (!success) { + if (!iteration(first, [&](auto prev_first) { success = true; first = prev_first; - break; - } - } - - detail::skip(first, last, skip, flags); - attr_t attr{}; - parser_.call( - first, last, context, skip, flags, success, attr); - if (!success) { - success = true; - first = prev_first; - break; + })) { + return; } - detail::move_back( - retval, std::move(attr), detail::gen_attrs(flags)); } } } @@ -3341,8 +3347,10 @@ namespace boost { namespace parser { Attribute & retval) const { //[ opt_parser_trace +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif //] //[ opt_parser_skip @@ -3505,8 +3513,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif use_parser_t const use_parser{ first, last, context, skip, flags, success}; @@ -3629,8 +3639,10 @@ namespace boost { namespace parser { decltype(detail::hl::transform(parsers_, use_parser)); result_t retval{}; +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first_, last, context, flags, retval); +#endif call_impl( first, @@ -3665,8 +3677,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first_, last, context, flags, retval); +#endif Iter first = first_; use_parser_t const use_parser{ @@ -3713,7 +3727,7 @@ namespace boost { namespace parser { if constexpr (detail::is_struct_compatible_v< Attribute, result_t>) { - detail::assign(retval, temp_retval); + detail::assign(retval, std::move(temp_retval)); } else { detail::assign( retval, @@ -4292,12 +4306,13 @@ namespace boost { namespace parser { { Iter first = first_; - auto temp_result = - make_temp_result(first, last, context, skip, flags, success); + using temp_result_t = + decltype(make_temp_result(first, last, context, skip, flags, success)); - std::decay_t{}))> + std::decay_t(), llong<0>{}))> retval{}; +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first_, @@ -4306,10 +4321,11 @@ namespace boost { namespace parser { detail::in_apply_parser(flags) ? detail::disable_trace(flags) : flags, retval); +#endif - std::decay_t{}))> + std::decay_t(), llong<1>{}))> indices; - std::decay_t{}))> + std::decay_t(), llong<2>{}))> merged; call_impl( first, @@ -4328,7 +4344,7 @@ namespace boost { namespace parser { // A 1-tuple is converted to a scalar. if constexpr (detail::hl::size(retval) == llong<1>{}) { using namespace literals; - return parser::get(retval, 0_c); + return parser::get(std::move(retval), 0_c); } else { return retval; } @@ -4349,6 +4365,7 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first_, @@ -4357,16 +4374,17 @@ namespace boost { namespace parser { detail::in_apply_parser(flags) ? detail::disable_trace(flags) : flags, retval); +#endif Iter first = first_; - auto temp_result = - make_temp_result(first, last, context, skip, flags, success); + using temp_result_t = + decltype(make_temp_result(first, last, context, skip, flags, success)); using temp_result_attr_t = - std::decay_t{}))>; - std::decay_t{}))> + std::decay_t(), llong<0>{}))>; + std::decay_t(), llong<1>{}))> indices; - std::decay_t{}))> merged; + std::decay_t(), llong<2>{}))> merged; auto max_ = [](auto result, auto x) { if constexpr (decltype(result)::value < decltype(x)::value) { @@ -4703,8 +4721,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif auto const initial_first = first; auto attr = parser_.call( @@ -4769,8 +4789,10 @@ namespace boost { namespace parser { detail::flags flags, bool & success) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, detail::global_nope); +#endif auto attr = parser_.call(first, last, context, skip, flags, success); if (success && detail::gen_attrs(flags)) @@ -4794,8 +4816,11 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif + auto attr = parser_.call(first, last, context, skip, flags, success); if (success && detail::gen_attrs(flags)) @@ -4822,8 +4847,10 @@ namespace boost { namespace parser { detail::flags flags, bool & success) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, detail::global_nope); +#endif parser_.call( first, @@ -4850,8 +4877,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif parser_.call( first, @@ -4901,8 +4930,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif auto const initial_first = first; parser_.call( @@ -4966,8 +4997,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif auto const initial_first = first; parser_.call( @@ -5042,8 +5075,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif parser_.call( first, @@ -5102,8 +5137,10 @@ namespace boost { namespace parser { auto context = context_; ++context.no_case_depth_; +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif parser_.call(first, last, context, skip, flags, success, retval); } @@ -5149,8 +5186,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if constexpr (detail::is_nope_v) { parser_.call( @@ -5213,8 +5252,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif auto first_copy = first; parser_.call( @@ -5380,8 +5421,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif auto [trie, _0] = detail::get_trie(context, ref()); auto const lookup = context.no_case_depth_ @@ -5455,8 +5498,10 @@ namespace boost { namespace parser { tag_type * const tag_ptr = nullptr; auto const rule_context = detail::make_rule_context( context, tag_ptr, retval, locals, params); +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, rule_context, flags, retval); +#endif bool dont_assign = false; if constexpr (in_recursion) { @@ -5485,7 +5530,7 @@ namespace boost { namespace parser { dont_assign); if (success && !dont_assign) { if constexpr (!detail::is_nope_v) - detail::assign(retval, attr); + detail::assign(retval, std::move(attr)); } } @@ -5553,8 +5598,10 @@ namespace boost { namespace parser { auto const rule_context = detail::make_rule_context( context, tag_ptr, attr, locals, params); +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, rule_context, flags, retval); +#endif bool dont_assign = false; parse_rule( @@ -5580,7 +5627,7 @@ namespace boost { namespace parser { container && container) { detail::move_back(retval, attr, detail::gen_attrs(flags)); } else { - detail::assign(retval, attr); + detail::assign(retval, std::move(attr)); } } } @@ -6542,20 +6589,24 @@ namespace boost { namespace parser { /** Represents a `repeat_parser` as a directive (e.g. `repeat[other_parser]`). */ - template + template< + typename MinType, + typename MaxType, + typename DelimiterParser = detail::nope> struct repeat_directive { template constexpr auto operator[](parser_interface rhs) const noexcept { using repeat_parser_type = - repeat_parser; + repeat_parser; return parser_interface{ - repeat_parser_type{rhs.parser_, min_, max_}}; + repeat_parser_type{rhs.parser_, min_, max_, delimiter_}}; } MinType min_; MaxType max_; + DelimiterParser delimiter_; }; /** Returns a `repeat_directive` that repeats exactly `n` times, and whose @@ -6567,6 +6618,18 @@ namespace boost { namespace parser { return repeat_directive{n, n}; } + /** Returns a `repeat_directive` that repeats exactly `n` times, where the + items parsed are delimited by `DelimiterParser`. The value returned + has an `operator[]` that returns a + `parser_interface>` from a given parser of type + `parser_interface

`. */ + template + inline repeat_directive + repeat(T n, parser_interface sep) noexcept + { + return repeat_directive{n, n, sep.parser_}; + } + /** Returns a `repeat_directive` that repeats between `min_` and `max_` times, inclusive, and whose `operator[]` returns a `parser_interface>` from a given parser of type @@ -6578,6 +6641,21 @@ namespace boost { namespace parser { return repeat_directive{min_, max_}; } + /** Returns a `repeat_directive` that repeats between `min_` and `max_` + times, inclusive, where the items parsed are delimited by + `DelimiterParser`. The value returned has an `operator[]` that + returns a `parser_interface>` from a given parser of + type `parser_interface

`. */ + template + inline repeat_directive repeat( + MinType min_, + MaxType max_, + parser_interface sep) noexcept + { + return repeat_directive{ + min_, max_, sep.parser_}; + } + /** A directive that represents a `perm_parser`, where the items parsed are delimited by `DelimiterParser` (e.g. `delimiter(delimter_parser)[some_perm_parser]`). This directive @@ -6744,8 +6822,10 @@ namespace boost { namespace parser { detail::flags flags, bool & success) const noexcept { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, detail::global_nope); +#endif BOOST_PARSER_SUBRANGE const where(first, first); auto const predicate_context = detail::make_action_context( context, detail::global_nope, where); @@ -6772,8 +6852,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif BOOST_PARSER_SUBRANGE const where(first, first); auto const predicate_context = detail::make_action_context( context, detail::global_nope, where); @@ -6823,8 +6905,10 @@ namespace boost { namespace parser { detail::flags flags, bool & success) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, detail::global_nope); +#endif if (first != last) success = false; return {}; @@ -6845,8 +6929,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (first != last) success = false; } @@ -6875,8 +6961,10 @@ namespace boost { namespace parser { detail::flags flags, bool &) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, detail::global_nope); +#endif return detail::resolve(context, attr_); } @@ -6895,8 +6983,10 @@ namespace boost { namespace parser { bool & success, Attribute_ & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (detail::gen_attrs(flags)) detail::assign_copy(retval, detail::resolve(context, attr_)); } @@ -6961,8 +7051,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (first == last) { success = false; @@ -7126,8 +7218,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (first == last) { success = false; @@ -7277,8 +7371,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (first == last) { success = false; @@ -7357,8 +7453,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (first == last) { success = false; @@ -7479,8 +7577,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (first == last) { success = false; @@ -7643,8 +7743,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (first == last) { success = false; @@ -7964,8 +8066,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif if (first == last) { success = false; @@ -8131,8 +8235,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif auto compare = [no_case = context.no_case_depth_](char32_t a, char32_t b) { @@ -8220,8 +8326,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif T attr = 0; auto const initial = first; success = @@ -8361,8 +8469,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif T attr = 0; auto const initial = first; success = @@ -8476,8 +8586,10 @@ namespace boost { namespace parser { bool & success, Attribute & retval) const { +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif T attr = 0; auto const initial = first; success = detail::numeric::parse_real(first, last, attr); @@ -8570,8 +8682,10 @@ namespace boost { namespace parser { using attr_t = decltype(or_parser_.call( first, last, context, skip, flags, success)); attr_t attr{}; +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace(*this, first, last, context, flags, attr); +#endif attr = or_parser_.call(first, last, context, skip, flags, success); return attr; } @@ -8596,8 +8710,10 @@ namespace boost { namespace parser { "It looks like you tried to write switch_(val). You need at " "least one alternative, like: switch_(val)(value_1, " "parser_1)(value_2, parser_2)...")); +#if BOOST_PARSER_DO_TRACE [[maybe_unused]] auto _ = detail::scoped_trace( *this, first, last, context, flags, retval); +#endif or_parser_.call(first, last, context, skip, flags, success, retval); } @@ -9056,8 +9172,13 @@ namespace boost { namespace parser { "fill in attr above, using the attribute generated by parser. " "However, parser does not generate an attribute."); if (trace_mode == trace::on) { - return reset = detail::parse_impl( - first, last, parser, parser.error_handler_, attr); + return reset = + detail::parse_impl<(true && BOOST_PARSER_DO_TRACE)>( + first, + last, + parser, + parser.error_handler_, + attr); } else { return reset = detail::parse_impl( first, last, parser, parser.error_handler_, attr); @@ -9074,8 +9195,9 @@ namespace boost { namespace parser { "fill in attr above, using the attribute generated by parser. " "However, parser does not generate an attribute."); if (trace_mode == trace::on) { - return reset = detail::parse_impl( - f, l, parser, parser.error_handler_, attr); + return reset = + detail::parse_impl<(true && BOOST_PARSER_DO_TRACE)>( + f, l, parser, parser.error_handler_, attr); } else { return reset = detail::parse_impl( f, l, parser, parser.error_handler_, attr); @@ -9178,7 +9300,7 @@ namespace boost { namespace parser { { if constexpr (!detail::is_char8_iter_v) { if (trace_mode == trace::on) { - return detail::parse_impl( + return detail::parse_impl<(true && BOOST_PARSER_DO_TRACE)>( first, last, parser, parser.error_handler_); } else { return detail::parse_impl( @@ -9191,7 +9313,7 @@ namespace boost { namespace parser { auto const l = r.end(); auto _ = detail::scoped_base_assign(first, f); if (trace_mode == trace::on) { - return detail::parse_impl( + return detail::parse_impl<(true && BOOST_PARSER_DO_TRACE)>( f, l, parser, parser.error_handler_); } else { return detail::parse_impl( @@ -9297,7 +9419,8 @@ namespace boost { namespace parser { "fill in attr above, using the attribute generated by parser. " "However, parser does not generate an attribute."); if (trace_mode == trace::on) { - return reset = detail::skip_parse_impl( + return reset = detail::skip_parse_impl<( + true && BOOST_PARSER_DO_TRACE)>( first, last, parser, @@ -9325,7 +9448,8 @@ namespace boost { namespace parser { "fill in attr above, using the attribute generated by parser. " "However, parser does not generate an attribute."); if (trace_mode == trace::on) { - return reset = detail::skip_parse_impl( + return reset = detail::skip_parse_impl<( + true && BOOST_PARSER_DO_TRACE)>( f, l, parser, skip, parser.error_handler_, attr); } else { return reset = detail::skip_parse_impl( @@ -9430,7 +9554,7 @@ namespace boost { namespace parser { { if constexpr (!detail::is_char8_iter_v) { if (trace_mode == trace::on) { - return detail::skip_parse_impl( + return detail::skip_parse_impl<(true && BOOST_PARSER_DO_TRACE)>( first, last, parser, skip, parser.error_handler_); } else { return detail::skip_parse_impl( @@ -9443,7 +9567,7 @@ namespace boost { namespace parser { auto const l = r.end(); auto _ = detail::scoped_base_assign(first, f); if (trace_mode == trace::on) { - return detail::skip_parse_impl( + return detail::skip_parse_impl<(true && BOOST_PARSER_DO_TRACE)>( f, l, parser, skip, parser.error_handler_); } else { return detail::skip_parse_impl( @@ -9547,7 +9671,8 @@ namespace boost { namespace parser { { if constexpr (!detail::is_char8_iter_v) { if (trace_mode == trace::on) { - return detail::callback_parse_impl( + return detail::callback_parse_impl<( + true && BOOST_PARSER_DO_TRACE)>( first, last, parser, parser.error_handler_, callbacks); } else { return detail::callback_parse_impl( @@ -9560,7 +9685,8 @@ namespace boost { namespace parser { auto const l = r.end(); auto _ = detail::scoped_base_assign(first, f); if (trace_mode == trace::on) { - return detail::callback_parse_impl( + return detail::callback_parse_impl<( + true && BOOST_PARSER_DO_TRACE)>( f, l, parser, parser.error_handler_, callbacks); } else { return detail::callback_parse_impl( @@ -9672,7 +9798,8 @@ namespace boost { namespace parser { { if constexpr (!detail::is_char8_iter_v) { if (trace_mode == trace::on) { - return detail::callback_skip_parse_impl( + return detail::callback_skip_parse_impl<( + true && BOOST_PARSER_DO_TRACE)>( first, last, parser, @@ -9695,7 +9822,8 @@ namespace boost { namespace parser { auto const l = r.end(); auto _ = detail::scoped_base_assign(first, f); if (trace_mode == trace::on) { - return detail::callback_skip_parse_impl( + return detail::callback_skip_parse_impl<( + true && BOOST_PARSER_DO_TRACE)>( f, l, parser, skip, parser.error_handler_, callbacks); } else { return detail::callback_skip_parse_impl( diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index eebcf19d..75b074f1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -82,6 +82,7 @@ add_test_executable(parser_seq_permutations_1) add_test_executable(parser_seq_permutations_2) add_test_executable(parser_or_permutations_1) add_test_executable(parser_or_permutations_2) +add_test_executable(disable_trace) if (MSVC) add_executable(vs_output_tracing tracing.cpp) diff --git a/test/disable_trace.cpp b/test/disable_trace.cpp new file mode 100644 index 00000000..dd12a4c3 --- /dev/null +++ b/test/disable_trace.cpp @@ -0,0 +1,27 @@ +/** +* Copyright (C) 2025 + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ + +#define BOOST_PARSER_DISABLE_TRACE + +#include +#include + +int main() +{ + namespace bp = boost::parser; + { + auto const parser = + bp::string("FOO") >> -(bp::string("bar") | bp::string("foo")); + + auto result = bp::parse("FOOfoo", parser); + BOOST_TEST(result); + BOOST_TEST(bp::get(*result, bp::llong<0>{}) == std::string("FOO")); + BOOST_TEST(bp::get(*result, bp::llong<1>{}) == std::string("foo")); + } + return boost::report_errors(); +} \ No newline at end of file diff --git a/test/github_issues.cpp b/test/github_issues.cpp index b36d872c..03dd3099 100644 --- a/test/github_issues.cpp +++ b/test/github_issues.cpp @@ -597,6 +597,186 @@ void github_pr_297() } } +namespace github_issue_312_ { + /* + * Recursive descent parser for expressions. + * Supports addition (+), multiplication (*) and + * parethesized expressions, nothing else. + * + * Creates a tree of "evaluatable" objects which + * own their downstream objects in a unique_ptr + */ + + // base class for all tree nodes + struct evaluatable + { + virtual double evaluate() = 0; + virtual ~evaluatable() = default; + }; + + namespace bp = boost::parser; + + // top level parser + constexpr bp::rule> expression_parser = "expression_parser"; + + /* + * LITERAL EXPRESSION + */ + struct literal_evaluatable : evaluatable + { + explicit literal_evaluatable(double v) : value_(v) {} + double evaluate() override + { + return value_; + } + double value_; + }; + constexpr bp::rule> literal_parser = "literal_parser"; + constexpr auto literal_parser_action = [](auto& ctx) { + std::unique_ptr& val = _val(ctx); + double& parsed_value = _attr(ctx); + val = std::make_unique(parsed_value); + }; + constexpr auto literal_parser_def = + bp::double_[literal_parser_action]; + + /* + * PARENTHESIZED EXPRESSION + */ + struct parenthesized_evaluatable : evaluatable + { + explicit parenthesized_evaluatable(std::unique_ptr&& e) : evaluatable_(std::move(e)) {} + double evaluate() override + { + return evaluatable_->evaluate(); + } + std::unique_ptr evaluatable_; + }; + constexpr bp::rule> parenthesized_parser = "parenthesized_parser"; + constexpr auto parenthesized_action = [](auto& ctx) { + std::unique_ptr& val = _val(ctx); + std::unique_ptr& attr = _attr(ctx); + val = std::make_unique(std::move(attr)); + }; + constexpr auto parenthesized_parser_def = + ( + bp::lit('(') > expression_parser > bp::lit(')') + )[parenthesized_action]; + + /* + * ATOM EXPRESSION + */ + struct atom_evaluatable : evaluatable + { + explicit atom_evaluatable(std::unique_ptr&& e) : evaluatable_(std::move(e)) {} + double evaluate() override + { + return evaluatable_->evaluate(); + } + std::unique_ptr evaluatable_; + }; + constexpr bp::rule> atom_parser = "atom_parser"; + constexpr auto atom_action = [](auto& ctx) { + std::unique_ptr& val = _val(ctx); + std::unique_ptr& attr = _attr(ctx); + val = std::make_unique(std::move(attr)); + }; + constexpr auto atom_parser_def = + ( + parenthesized_parser + | + literal_parser + )[atom_action]; + + /* + * MULTIPLICATION EXPRESSION + */ + struct multiplication_evaluatable : evaluatable + { + multiplication_evaluatable(std::vector>&& e) + : evaluatables_(std::move(e)) + {} + double evaluate() override + { + double result = 1; + for (const auto& e : evaluatables_) { + result *= e->evaluate(); + } + return result; + } + std::vector> evaluatables_; + }; + constexpr bp::rule> mult_parser = "mult_parser"; + constexpr auto mult_parser_action = [](auto& ctx) { + std::unique_ptr& val = _val(ctx); + std::vector>& operands = _attr(ctx); + val = std::make_unique(std::move(operands)); + }; + constexpr auto mult_parser_def = + (atom_parser % bp::lit('*'))[mult_parser_action]; + + /* + * ADDITION EXPRESSION + */ + struct addition_evaluatable : evaluatable + { + addition_evaluatable(std::vector>&& e) + : evaluatables_(std::move(e)) + {} + double evaluate() override + { + double result = 0; + for (const auto& e : evaluatables_) { + result += e->evaluate(); + } + return result; + } + std::vector> evaluatables_; + }; + constexpr bp::rule> add_parser = "add_parser"; + constexpr auto add_parser_action = [](auto& ctx) { + std::unique_ptr& val = _val(ctx); + std::vector>& operands = _attr(ctx); + val = std::make_unique(std::move(operands)); + }; + constexpr auto add_parser_def = + (mult_parser % bp::lit('+'))[add_parser_action]; + + constexpr auto expression_parser_action = [](auto& ctx) { + std::unique_ptr& val = _val(ctx); + std::unique_ptr& attr = _attr(ctx); + val = std::move(attr); + }; + + /* + * EXPRESSION + */ + constexpr auto expression_parser_def = + add_parser[expression_parser_action]; + + BOOST_PARSER_DEFINE_RULES( + literal_parser, + mult_parser, + add_parser, + expression_parser, + parenthesized_parser, + atom_parser); +} + +void github_issue_312() +{ + namespace bp = boost::parser; + using namespace github_issue_312_; + + auto result = bp::parse("(2 + 3) + 3.1415 * 2", expression_parser, bp::blank); + BOOST_TEST(result); + BOOST_TEST(result.value()->evaluate() == (2 + 3) + 3.1415 * 2); + + result = bp::parse("((2*0.1) + 33.) * (2 + 3) + 3.1415 * 2", expression_parser, bp::blank); + BOOST_TEST(result); + BOOST_TEST(result.value()->evaluate() == ((2*0.1) + 33.) * (2 + 3) + 3.1415 * 2); +} + int main() { @@ -615,5 +795,6 @@ int main() github_pr_290(); github_issue_294(); github_pr_297(); + github_issue_312(); return boost::report_errors(); } diff --git a/test/parser.cpp b/test/parser.cpp index 9ea882e2..ff87d47e 100644 --- a/test/parser.cpp +++ b/test/parser.cpp @@ -1092,6 +1092,72 @@ int main() // repeat { + { + constexpr auto parser = repeat(2)[string("zs")]; + + { + std::string str = ""; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "z"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zs"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zszs"; + std::vector chars; + BOOST_TEST(parse(str, parser, chars)); + BOOST_TEST(chars == std::vector({"zs", "zs"})); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(chars); + BOOST_TEST( + *chars == std::vector({"zs", "zs"})); + } + } + { + std::string str = "zszszs"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + } { constexpr auto parser = repeat(2, 3)[string("zs")]; @@ -1145,6 +1211,228 @@ int main() *chars == std::vector({"zs", "zs"})); } } + { + std::string str = "zszszs"; + std::vector chars; + BOOST_TEST(parse(str, parser, chars)); + BOOST_TEST( + chars == std::vector({"zs", "zs", "zs"})); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(chars); + BOOST_TEST( + *chars == std::vector({"zs", "zs", "zs"})); + } + } + { + std::string str = "zszszszs"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + } + { + auto parser = repeat(2, char_(','))[string("zs")]; + + { + std::string str = ""; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "z"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zs"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zszs"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector()); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zs,zs"; + std::vector chars; + BOOST_TEST(parse(str, parser, chars)); + BOOST_TEST(chars == std::vector({"zs", "zs"})); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(chars); + BOOST_TEST( + *chars == std::vector({"zs", "zs"})); + } + } + { + std::string str = "zs,zs,"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector()); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zs,zs,zs"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector()); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + } + { + auto parser = repeat(2, 3, char_(','))[string("zs")]; + + { + std::string str = ""; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "z"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zs"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zs,"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zs,zs,"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } + { + std::string str = "zs,zs"; + std::vector chars; + BOOST_TEST(parse(str, parser, chars)); + BOOST_TEST(chars == std::vector({"zs", "zs"})); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(chars); + BOOST_TEST( + *chars == std::vector({"zs", "zs"})); + } + } + { + std::string str = "zs,zs,zs"; + std::vector chars; + BOOST_TEST(parse(str, parser, chars)); + BOOST_TEST( + chars == std::vector({"zs", "zs", "zs"})); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(chars); + BOOST_TEST( + *chars == std::vector({"zs", "zs", "zs"})); + } + } + { + std::string str = "zs,zs,zs,zs"; + std::vector chars; + BOOST_TEST(!parse(str, parser, chars)); + BOOST_TEST(chars == std::vector{}); + + { + std::optional> const chars = + parse(str, parser); + BOOST_TEST(!chars); + } + } } { constexpr auto parser = *char_ >> eps >> *string("str");