2
0
mirror of https://github.com/boostorg/parser.git synced 2026-01-20 04:42:22 +00:00

7 Commits

Author SHA1 Message Date
Zach Laine
e3fbd6c849 WIP_quoted_string 2024-02-22 00:58:00 -06:00
Zach Laine
2b4243151a Add Phil Endecott's example latlong parser from the Boost mailing list review
as a test.
2024-02-22 00:56:54 -06:00
Zach Laine
63884d2efa Fix an error in seq_parser when a non-attribute parser fails internally, after
the successful parse of an adjacent attribute-generating parser.  Since the
non-attribute parser points to the same spot in the output tuple, if the
non-attribute parser fails internally, it is likely to do "out_attr =
decltype(out_attr)()", which erases the previous seuccessfully generated
attribute.
2024-02-22 00:13:07 -06:00
Zach Laine
3487211426 Use a more compelling example in the example code for replace().
Fixes #96.
2024-02-19 21:50:36 -06:00
Zach Laine
c12c512a94 Call out the constraint on the ReplacementV template parameter to replace().
Partially addresses #97.
2024-02-19 21:33:52 -06:00
Zach Laine
8ce60a8e53 Remove superfluous utf_pointer concept.
Partially addresses #97.
2024-02-19 18:33:13 -06:00
Zach Laine
4287c9b7b3 Fix transform_replace* in C++20 mode on GCC < 12. 2024-02-11 18:56:59 -06:00
16 changed files with 702 additions and 44 deletions

View File

@@ -3014,6 +3014,10 @@ similar.
[section Algorithms and Views That Use Parsers]
Unless otherwise noted, all the algorithms and views are constrained very much
like the way the _p_ overloads are. The kinds of ranges, parsers, etc., that
they accept are the same.
[heading _search_]
As shown in _p_api_, the two patterns of parsing in _Parser_ are whole-parse
@@ -3141,15 +3145,16 @@ Unlike _split_v_, _replace_v_ does not produce empty subranges, unless
`replacement` is empty.
namespace bp = boost::parser;
auto rng = "XYZaaXYZbaabaXYZXYZ" | bp::replace(bp::lit("XYZ"), "foo");
auto card_number = bp::int_ >> bp::repeat(3)['-' >> bp::int_];
auto rng = "My credit card number is 1234-5678-9012-3456." | bp::replace(card_number, "XXXX-XXXX-XXXX-XXXX");
int count = 0;
// Prints foo aa foo baaba foo foo.
// Prints My credit card number is XXXX-XXXX-XXXX-XXXX.
for (auto subrange : rng) {
std::cout << std::string_view(subrange.begin(), subrange.end() - subrange.begin()) << " ";
std::cout << std::string_view(subrange.begin(), subrange.end() - subrange.begin());
++count;
}
std::cout << "\n";
assert(count == 6);
assert(count == 3);
If the iterator types `Ir` and `Ireplacement` for the `r` and `replacement`
@@ -3164,13 +3169,13 @@ subranges represented by _replace_v_ is easily joined back into a single
range.
namespace bp = boost::parser;
char const str[] = "XYZaaXYZbaabaXYZXYZ";
auto rng = str | bp::replace(bp::lit("XYZ"), "foo") | std::views::join;
auto card_number = bp::int_ >> bp::repeat(3)['-' >> bp::int_];
auto rng = "My credit card number is 1234-5678-9012-3456." | bp::replace(card_number, "XXXX-XXXX-XXXX-XXXX") | std::views::join;
std::string replace_result;
for (auto ch : rng) {
replace_result.push_back(ch);
}
assert(replace_result == "fooaafoobaabafoofoo");
assert(replace_result == "My credit card number is XXXX-XXXX-XXXX-XXXX.");
Note that we could *not* have written `std::string replace_result(r.begin(),
r.end())`. This is ill-formed because the `std::string` range constructor
@@ -3184,6 +3189,11 @@ is because the subranges produced are all common ranges, and so if
pass very long C-style strings to _replace_ and not pay to see the end until
the range is used, don't.
`ReplacementV` is constrained almost exactly the same as `V`. `V` must model
`parsable_range` and `std::ranges::viewable_range`. `ReplacementV` is the
same, except that it can also be a `std::ranges::input_range`, whereas `V`
must be a `std::ranges::forward_range`.
You may wonder what happens when you pass a UTF-N range for `r`, and a UTF-M
range for `replacement`. What happens in this case is silent transcoding of
`replacement` from UTF-M to UTF-N by the _replace_ range adaptor. This
@@ -3216,9 +3226,6 @@ overloads.
[important _trans_replace_ and _trans_replace_v_ are not available on MSVC in
C++17 mode.]
[important _trans_replace_ and _trans_replace_v_ are not available on GCC in
C++20 mode before GCC 12.]
_trans_replace_ creates _trans_replace_vs_. _trans_replace_v_ is a
`std::views`-style view. It produces a range of subranges from the parsed
range `r` and the given invocable `f`. Wherever in the parsed range a match

View File

@@ -44,11 +44,7 @@ namespace boost { namespace parser {
//]
template<typename T>
concept utf_pointer =
std::is_pointer_v<T> && code_unit<std::iter_value_t<T>>;
template<typename T>
concept range_like =
std::ranges::range<T> || utf_pointer<std::remove_cvref_t<T>>;
concept range_like = std::ranges::range<T> || parsable_pointer<T>;
template<
typename I,

View File

@@ -264,6 +264,13 @@ namespace boost { namespace parser { namespace detail {
std::ostream & os,
int components = 0);
template<typename Context, typename I, typename S>
void print_parser(
Context const & context,
quoted_string_parser<I, S> const & parser,
std::ostream & os,
int components = 0);
template<typename Context, bool NewlinesOnly, bool NoNewlines>
void print_parser(
Context const & context,

View File

@@ -439,8 +439,7 @@ namespace boost { namespace parser { namespace detail {
template<
typename Context,
typename ResolvedExpected,
bool Integral = std::is_integral<ResolvedExpected>{},
int SizeofExpected = sizeof(ResolvedExpected)>
bool Integral = std::is_integral<ResolvedExpected>{}>
struct print_expected_char_impl
{
static void call(
@@ -452,13 +451,17 @@ namespace boost { namespace parser { namespace detail {
}
};
template<typename Context, typename Expected>
struct print_expected_char_impl<Context, Expected, true, 4>
template<typename Context>
struct print_expected_char_impl<Context, char32_t, true>
{
static void
call(Context const & context, std::ostream & os, Expected expected)
call(Context const & context, std::ostream & os, char32_t expected)
{
std::array<char32_t, 1> cps = {{(char32_t)expected}};
if (expected == '\'') {
os << "'\\''";
return;
}
std::array<char32_t, 1> cps = {{expected}};
auto const r = cps | text::as_utf8;
os << "'";
for (auto c : r) {
@@ -646,6 +649,27 @@ namespace boost { namespace parser { namespace detail {
os << "\"";
}
template<typename Context, typename I, typename S>
void print_parser(
Context const & context,
quoted_string_parser<I, S> const & parser,
std::ostream & os,
int components)
{
os << "quoted_string(";
if (parser.chs_.empty()) {
detail::print_expected_char_impl<Context, char32_t>::call(
context, os, parser.ch_);
} else {
os << '"';
for (auto c : parser.chs_ | text::as_utf8) {
detail::print_char(os, c);
}
os << '"';
}
os << ')';
}
template<typename Context, bool NewlinesOnly, bool NoNewlines>
void print_parser(
Context const & context,

View File

@@ -131,7 +131,8 @@ namespace boost::parser::detail::text::detail {
constexpr all_impl all;
#if BOOST_PARSER_DETAIL_TEXT_USE_CONCEPTS
#if BOOST_PARSER_DETAIL_TEXT_USE_CONCEPTS && \
(!defined(__GNUC__) || 12 <= __GNUC__)
template<typename R>
using all_t = std::views::all_t<R>;
#else

View File

@@ -44,13 +44,27 @@ namespace boost::parser::detail { namespace text {
using iterator_to_tag_t = decltype(iterator_to_tag<I>());
#if BOOST_PARSER_DETAIL_TEXT_USE_CONCEPTS
#if defined(__GNUC__) && __GNUC__ < 12
// This uses the final version of viewable_range. Older GCCs use a
// different one that breaks, so we use this on instead of the one
// from std::ranges::. It's missing the !initializer_list part.
template<typename T>
concept viewable_range = std::ranges::range<T> &&
((std::ranges::view<std::remove_cvref_t<T>> &&
std::constructible_from<std::remove_cvref_t<T>, T>) ||
(!std::ranges::view<std::remove_cvref_t<T>> &&
(std::is_lvalue_reference_v<T> ||
std::movable<std::remove_reference_t<T>>)));
#else
template<typename T>
concept viewable_range = std::ranges::viewable_range<T>;
#endif
template<class T>
using with_reference = T &;
template<typename T>
concept can_reference = requires { typename with_reference<T>; };
#endif
#if BOOST_PARSER_DETAIL_TEXT_USE_CONCEPTS
template<class Char>
struct cast_to_charn {
constexpr Char operator()(Char c) const { return c; }
@@ -265,7 +279,7 @@ namespace boost::parser::detail { namespace text {
#if BOOST_PARSER_DETAIL_TEXT_USE_ALIAS_CTAD
template<class R, auto F>
project_view(R &&) -> project_view<std::views::all_t<R>, F>;
project_view(R &&) -> project_view<detail::all_t<R>, F>;
#endif
namespace detail {
@@ -281,7 +295,7 @@ namespace boost::parser::detail { namespace text {
#if BOOST_PARSER_DETAIL_TEXT_USE_CONCEPTS
template<class R>
requires std::ranges::viewable_range<R> &&
requires viewable_range<R> &&
std::ranges::input_range<R> &&
std::regular_invocable<decltype(F)&, std::ranges::range_reference_t<R>> &&
detail::can_reference<std::invoke_result_t<decltype(F)&, std::ranges::range_reference_t<R>>>
@@ -402,7 +416,7 @@ namespace boost::parser::detail { namespace text {
{
#if BOOST_PARSER_DETAIL_TEXT_USE_CONCEPTS
template<class R>
requires (std::ranges::viewable_range<R> &&
requires (viewable_range<R> &&
std::ranges::input_range<R> &&
std::convertible_to<std::ranges::range_reference_t<R>, format_to_type_t<Format>>) ||
utf_pointer<std::remove_cvref_t<R>>
@@ -606,7 +620,7 @@ namespace boost::parser::detail { namespace text {
#if BOOST_PARSER_DETAIL_TEXT_USE_ALIAS_CTAD
template<format Format, class R>
utf_view(R &&) -> utf_view<Format, std::views::all_t<R>>;
utf_view(R &&) -> utf_view<Format, detail::all_t<R>>;
template<class V>
using utf8_view = utf_view<format::utf8, V>;
@@ -757,7 +771,7 @@ namespace boost::parser::detail { namespace text {
#if BOOST_PARSER_DETAIL_TEXT_USE_CONCEPTS
template<class R>
requires is_utf_view<std::remove_cvref_t<R>> ||
(std::ranges::viewable_range<R> &&
(viewable_range<R> &&
can_utf_view<unpacked_range<R>, View>) ||
utf_pointer<std::remove_cvref_t<R>>
#else

View File

@@ -3914,10 +3914,11 @@ namespace boost { namespace parser {
last,
&context,
&skip,
flags,
flags_ = flags,
&success,
&retval](auto const &
parser_index_merged_and_backtrack) {
auto flags = flags_;
using namespace literals;
detail::skip(first, last, skip, flags);
if (!success) // Someone earlier already failed...
@@ -3969,6 +3970,9 @@ namespace boost { namespace parser {
container<std::decay_t<decltype(out)>>;
constexpr bool attr_container = container<attr_t>;
if constexpr (detail::is_nope_v<attr_t>)
flags = detail::disable_attrs(flags);
if constexpr (
(out_container == attr_container &&
!was_merged_into_adjacent_container) ||
@@ -6325,7 +6329,7 @@ namespace boost { namespace parser {
}
};
/** The literal code point parser. The produced attribute is the type of
/** The single code point parser. The produced attribute is the type of
the matched code point. This parser can be used to create code point
parsers that match one or more specific code point values, by calling
it with: a single value comparable to a code point; a set of code
@@ -6333,7 +6337,7 @@ namespace boost { namespace parser {
hi]`, or a set of code point values passed as a range. */
inline constexpr parser_interface<char_parser<detail::nope>> char_;
/** The literal code point parser. It produces a `char32_t` attribute.
/** The single code point parser. It produces a `char32_t` attribute.
This parser can be used to create code point parsers that match one or
more specific code point values, by calling it with: a single value
comparable to a code point; a set of code point values in a string; a
@@ -6341,7 +6345,7 @@ namespace boost { namespace parser {
values passed as a range. */
inline constexpr parser_interface<char_parser<detail::nope, char32_t>> cp;
/** The literal code unit parser. It produces a `char` attribute. This
/** The single code unit parser. It produces a `char` attribute. This
parser can be used to create code unit parsers that match one or more
specific code unit values, by calling it with: a single value
comparable to a code unit; a set of code unit values in a string; a
@@ -6489,6 +6493,251 @@ namespace boost { namespace parser {
return parser_interface{string_parser(str)};
}
template<typename I, typename S>
struct quoted_string_parser
{
constexpr quoted_string_parser() : chs_(), ch_('"') {}
#if BOOST_PARSER_USE_CONCEPTS
template<parsable_range_like R>
#else
template<
typename R,
typename Enable =
std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
constexpr quoted_string_parser(R && r) :
chs_(detail::make_view_begin(r), detail::make_view_end(r)), ch_(0)
{}
constexpr quoted_string_parser(char32_t cp) : chs_(), ch_(cp)
{}
template<
bool UseCallbacks,
typename Iter,
typename Sentinel,
typename Context,
typename SkipParser>
std::string call(
std::bool_constant<UseCallbacks> use_cbs,
Iter & first,
Sentinel last,
Context const & context,
SkipParser const & skip,
detail::flags flags,
bool & success) const
{
std::string retval;
call(use_cbs, first, last, context, skip, flags, success, retval);
return retval;
}
template<
bool UseCallbacks,
typename Iter,
typename Sentinel,
typename Context,
typename SkipParser,
typename Attribute>
void call(
std::bool_constant<UseCallbacks> use_cbs,
Iter & first,
Sentinel last,
Context const & context,
SkipParser const & skip,
detail::flags flags,
bool & success,
Attribute & retval) const
{
auto _ = detail::scoped_trace(
*this, first, last, context, flags, retval);
if (first == last) {
success = false;
return;
}
auto const prev_first = first;
auto append = [&retval,
gen_attrs = detail::gen_attrs(flags)](auto & ctx) {
detail::move_back(retval, _attr(ctx), gen_attrs);
};
if (chs_.empty()) {
auto const p =
ch_ >> *((lit('\\') >> (char_('\\') | char_(ch_)) | char_) -
ch_)[append] > ch_;
call_impl(
p.parser_,
use_cbs,
first,
last,
context,
skip,
flags,
success,
retval);
} else {
detail::remove_cv_ref_t<decltype(*first)> const ch = *first;
bool found = false;
if constexpr (std::is_same_v<decltype(ch), char32_t const>) {
auto r = chs_ | detail::text::as_utf32;
found =
detail::text::find(r.begin(), r.end(), ch) == r.end();
} else {
found = detail::text::find(chs_.begin(), chs_.end(), ch) ==
chs_.end();
}
if (!found) {
success = false;
return;
}
++first;
auto const p =
*((lit('\\') >> (char_('\\') | char_(ch)) | char_) -
ch)[append] > ch;
call_impl(
p.parser_,
use_cbs,
first,
last,
context,
skip,
flags,
success,
retval);
}
if (!success) {
retval = Attribute();
first = prev_first;
}
}
template<
typename Parser,
bool UseCallbacks,
typename Iter,
typename Sentinel,
typename Context,
typename SkipParser,
typename Attribute>
void call_impl(
Parser & parser,
std::bool_constant<UseCallbacks> use_cbs,
Iter & first,
Sentinel last,
Context const & context,
SkipParser const & skip,
detail::flags flags,
bool & success,
Attribute & retval) const
{
parser.call(
use_cbs,
first,
last,
context,
skip,
detail::disable_skip(flags),
success,
retval);
}
/** Returns a `parser_interface` containing a `quoted_string_parser`
that uses `x` as its quotation marks. */
#if BOOST_PARSER_USE_CONCEPTS
template<typename T>
// clang-format off
requires (!parsable_range_like<T>)
#else
template<
typename T,
typename Enable =
std::enable_if_t<!detail::is_parsable_range_like_v<T>>>
#endif
constexpr auto operator()(T x) const noexcept
// clang-format on
{
BOOST_PARSER_ASSERT(
(chs_.empty() && ch_ == '"' &&
"If you're seeing this, you tried to chain calls on "
"quoted_string, like 'quoted_string('\"')('\\'')'. Quit "
"it!'"));
return parser_interface(quoted_string_parser(std::move(x)));
}
/** Returns a `parser_interface` containing a `quoted_string_parser`
that accepts any of the values in `r` as its quotation marks. If
the input being matched during the parse is a a sequence of
`char32_t`, the elements of `r` are transcoded from their presumed
encoding to UTF-32 during the comparison. Otherwise, the
character begin matched is directly compared to the elements of
`r`. */
#if BOOST_PARSER_USE_CONCEPTS
template<parsable_range_like R>
#else
template<
typename R,
typename Enable =
std::enable_if_t<detail::is_parsable_range_like_v<R>>>
#endif
constexpr auto operator()(R && r) const noexcept
{
BOOST_PARSER_ASSERT(((
!std::is_rvalue_reference_v<R &&> ||
!detail::is_range<detail::remove_cv_ref_t<
R>>)&&"It looks like you tried to pass an rvalue range to "
"quoted_string(). Don't do that, or you'll end up "
"with dangling references."));
BOOST_PARSER_ASSERT(
(chs_.empty() && ch_ == '"' &&
"If you're seeing this, you tried to chain calls on "
"quoted_string, like 'quoted_string(char-range)(char-range)'. "
" Quit it!'"));
return parser_interface(
quoted_string_parser<
decltype(detail::make_view_begin(r)),
decltype(detail::make_view_end(r))>((R &&) r));
}
BOOST_PARSER_SUBRANGE<I, S> chs_;
char32_t ch_;
};
/** Parses a string delimited by quotation marks. This parser can be used
to create parsers that accept one or more specific quotation mark
characters. By default, the quotation marks are `'"'`; an alternate
quotation mark can be specified by calling this parser with a single
character, or a range of characters. If a range is specified, the
opening quote must be one of the characters specified, and the closing
quote must match the opening quote. Quotation marks may appear within
the string if escaped with a backslash, and a pair of backslashes is
treated as a single escaped backslash; all other backslashes cuase the
parse to fail. Skipping is disabled during parsing of the entire
quoted string, including the quotation marks. There is an expectation
point before the closing qutation mark. Produces a `std::string`
attribute. */
inline constexpr parser_interface<
quoted_string_parser<char const *, char const *>>
quoted_string;
#if BOOST_PARSER_USE_CONCEPTS
template<parsable_range_like R>
#else
template<typename R>
#endif
quoted_string_parser(R r) -> quoted_string_parser<
decltype(detail::make_view_begin(r)),
decltype(detail::make_view_end(r))>;
quoted_string_parser(char32_t)
-> quoted_string_parser<char const *, char const *>;
/** Returns a parser that matches `str` that produces no attribute. */
#if BOOST_PARSER_USE_CONCEPTS
template<parsable_range_like R>

View File

@@ -364,12 +364,17 @@ namespace boost { namespace parser {
character being matched. */
struct digit_parser;
/** Maches a particular string, delimited by an iterator sentinel pair;
/** Matches a particular string, delimited by an iterator sentinel pair;
produces no attribute. */
template<typename StrIter, typename StrSentinel>
struct string_parser;
/** Maches an end-of-line (`NewlinesOnly == true`), whitespace
/** Matches a string delimited by quotation marks; produces a
`std::string` attribute. */
template<typename I, typename S>
struct quoted_string_parser;
/** Matches an end-of-line (`NewlinesOnly == true`), whitespace
(`NewlinesOnly == false`), or (`NoNewlines == true`) blank (whitespace
but not newline) code point, based on the Unicode definitions of each
(also matches the two code points `"\r\n"`). Produces no
@@ -377,7 +382,7 @@ namespace boost { namespace parser {
template<bool NewlinesOnly, bool NoNewlines>
struct ws_parser;
/** Maches the strings "true" and "false", producing an attribute of
/** Matches the strings "true" and "false", producing an attribute of
`true` or `false`, respectively, and fails on any other input. */
struct bool_parser;

View File

@@ -3,8 +3,7 @@
#include <boost/parser/replace.hpp>
#if (!defined(_MSC_VER) || BOOST_PARSER_USE_CONCEPTS) && \
(!defined(__GNUC__) || 12 <= __GNUC__ || !BOOST_PARSER_USE_CONCEPTS)
#if (!defined(_MSC_VER) || BOOST_PARSER_USE_CONCEPTS)
namespace boost::parser {

View File

@@ -46,6 +46,7 @@ macro(add_test_executable name)
add_test(NAME ${name} COMMAND ${name} --gtest_catch_exceptions=1)
endmacro()
#add_test_executable(quoted_string)
add_test_executable(all_t)
add_test_executable(search)
add_test_executable(split)
@@ -67,3 +68,4 @@ add_test_executable(class_type)
add_test_executable(case_fold_generated)
add_test_executable(no_case)
add_test_executable(merge_separate)
add_test_executable(parse_coords_new)

194
test/parse_coords_new.cpp Normal file
View File

@@ -0,0 +1,194 @@
/**
* Copyright (C) 2024 Phil Endecott
* Copyright (C) 2024 T. Zachary Laine
*
* 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)
*/
#include "boost/parser/parser.hpp"
#include <cassert>
#include <iostream>
#include <optional>
#include <string>
#include <gtest/gtest.h>
namespace bp = boost::parser;
using namespace boost::hana::literals;
namespace g2d {
struct Vector
{
double x, y;
};
std::ostream & operator<<(std::ostream & os, Vector vec)
{
os << "vec{" << vec.x << ", " << vec.y << "}";
return os;
}
};
bp::rule<struct uint_0_60, unsigned int> uint_0_60 = "uint_0_60";
bp::rule<struct double_0_60, double> double_0_60 = "double_0_60";
bp::rule<struct degrees_decimal_minutes, double> degrees_decimal_minutes = "degrees_decimal_minutes";
bp::rule<struct degrees_minutes_seconds, double> degrees_minutes_seconds = "degrees_minutes_seconds";
bp::rule<struct degrees, double> degrees = "degrees";
bp::rule<struct latitude, double> latitude = "latitude";
bp::rule<struct longitude, double> longitude = "longitude";
bp::rule<struct signed_latitude, double> signed_latitude = "signed_latitude";
bp::rule<struct signed_longitude, double> signed_longitude = "signed_longitude";
bp::rule<struct latlon, g2d::Vector> latlon = "latlon";
const auto degrees_symbol = bp::no_case[ bp::lit("degrees") | bp::lit("deg") | bp::lit('d') ];
const auto minutes_symbol = bp::no_case[ bp::lit('\'') | bp::lit("minutes") | bp::lit("min") | bp::lit('m') ];
const auto seconds_symbol = bp::no_case[ bp::lit('"') | bp::lit("seconds") | bp::lit("sec") | bp::lit('s') ];
const auto uint_0_60_def = bp::uint_ [( [](auto ctx) { _pass(ctx) = _attr(ctx) < 60U; _val(ctx) = _attr(ctx); } )];
const auto double_0_60_def = bp::double_ [( [](auto ctx) { _pass(ctx) = _attr(ctx) < 60; _val(ctx) = _attr(ctx); } )];
const auto decimal_degrees = bp::double_ >> -degrees_symbol;
const auto degrees_decimal_minutes_def = (bp::uint_ >> -degrees_symbol
>> (double_0_60 - '.') >> -minutes_symbol) [( [](auto ctx) {
auto d = _attr(ctx)[0_c];
auto m = _attr(ctx)[1_c];
_val(ctx) = d + m/60.0;
} )];
const auto degrees_minutes_seconds_def = (bp::uint_ >> -degrees_symbol
>> uint_0_60 >> -minutes_symbol
>> (double_0_60 - '.') >> -seconds_symbol) [( [](auto ctx) {
auto d = _attr(ctx)[0_c];
auto m = _attr(ctx)[1_c];
auto s = _attr(ctx)[2_c];
_val(ctx) = d + m/60.0 + s/3600.0;
} )];
const auto degrees_def = degrees_minutes_seconds
| degrees_decimal_minutes
| decimal_degrees;
const auto northsouth = bp::no_case[ bp::char_("ns") ];
const auto eastwest = bp::no_case[ bp::char_("ew") ];
const auto latitude_def = (degrees >> northsouth) [( [](auto ctx) {
auto d = _attr(ctx)[0_c];
auto ns = _attr(ctx)[1_c];
_pass(ctx) = d <= 90;
_val(ctx) = ns=='S' || ns=='s' ? -d : d;
} )];
const auto longitude_def = (degrees >> eastwest) [( [](auto ctx) {
auto d = _attr(ctx)[0_c];
auto ew = _attr(ctx)[1_c];
_pass(ctx) = d <= 180;
_val(ctx) = ew=='W' || ew=='w' ? -d : d;
} )];
const auto signed_degrees = bp::double_ >> -degrees_symbol;
const auto signed_latitude_def = signed_degrees [( [](auto ctx) { auto d = _attr(ctx); _pass(ctx) = -90 <= d && d <= 90; _val(ctx) = _attr(ctx); } )];
const auto signed_longitude_def = signed_degrees [( [](auto ctx) { auto d = _attr(ctx); _pass(ctx) = -180 <= d && d <= 180; _val(ctx) = _attr(ctx); } )];
const auto latlon_def = ((latitude >> longitude) [( [](auto ctx) { _val(ctx) = g2d::Vector{_attr(ctx)[1_c], _attr(ctx)[0_c]}; } )] )
| ((longitude >> latitude) [( [](auto ctx) { _val(ctx) = g2d::Vector{_attr(ctx)[0_c], _attr(ctx)[1_c]}; } )] )
| ((signed_longitude >> signed_latitude)
[( [](auto ctx) { _val(ctx) = g2d::Vector{_attr(ctx)[0_c], _attr(ctx)[1_c]}; } )] );
BOOST_PARSER_DEFINE_RULES(uint_0_60);
BOOST_PARSER_DEFINE_RULES(double_0_60);
BOOST_PARSER_DEFINE_RULES(degrees_decimal_minutes);
BOOST_PARSER_DEFINE_RULES(degrees_minutes_seconds);
BOOST_PARSER_DEFINE_RULES(degrees);
BOOST_PARSER_DEFINE_RULES(latitude);
BOOST_PARSER_DEFINE_RULES(longitude);
BOOST_PARSER_DEFINE_RULES(signed_latitude);
BOOST_PARSER_DEFINE_RULES(signed_longitude);
BOOST_PARSER_DEFINE_RULES(latlon);
#if 0
static std::optional<g2d::Vector> try_parse_latlon(std::string_view s)
{
auto input = latlon >> bp::eoi;
return parse(s,input, bp::ws|bp::lit(','));
}
int main(int argc, const char* argv[])
{
assert(argc==2);
auto opt_coords = try_parse_latlon(argv[1]);
if (!opt_coords) {
std::cerr << "parse error\n";
return 1;
} else {
std::cout << opt_coords->x << " " << opt_coords->y << "\n";
return 0;
}
}
#endif
TEST(parse_coords, all_examples)
{
std::vector<std::string> test_coords = {
"12.34 N, 56.78 E",
"56.78,-12.34",
"12d 30' n 45d 15' 7\" w",
"12 30 45 N, 45 15 7 W",
"12d 30m 15s S 50d 59m 59s W",
"50d 0.5m n 50d 59m 59s W"};
{
auto result = bp::parse(test_coords[0], latlon, bp::ws | bp::lit(','));
EXPECT_TRUE(result);
EXPECT_LT(std::abs(result->x - 56.78), 0.001);
EXPECT_LT(std::abs(result->y - 12.34), 0.001);
}
{
auto result = bp::parse(test_coords[1], latlon, bp::ws | bp::lit(','));
EXPECT_TRUE(result);
EXPECT_LT(std::abs(result->x - 56.78), 0.001);
EXPECT_LT(std::abs(result->y - -12.34), 0.001);
}
{
auto result = bp::parse(test_coords[2], latlon, bp::ws | bp::lit(','));
EXPECT_TRUE(result);
EXPECT_LT(std::abs(result->x - -45.2519), 0.001);
EXPECT_LT(std::abs(result->y - 12.5), 0.001);
}
{
auto result = bp::parse(test_coords[3], latlon, bp::ws | bp::lit(','));
EXPECT_TRUE(result);
EXPECT_LT(std::abs(result->x - -45.2519), 0.001);
EXPECT_LT(std::abs(result->y - 12.5125), 0.001);
}
{
auto result = bp::parse(test_coords[4], latlon, bp::ws | bp::lit(','));
EXPECT_TRUE(result);
EXPECT_LT(std::abs(result->x - -50.9997), 0.001);
EXPECT_LT(std::abs(result->y - -12.5042), 0.001);
}
{
auto result = bp::parse(test_coords[5], latlon, bp::ws | bp::lit(','));
EXPECT_TRUE(result);
EXPECT_LT(std::abs(result->x - -50.9997), 0.001);
EXPECT_LT(std::abs(result->y - 50.0083), 0.001);
}
}

View File

@@ -2167,6 +2167,13 @@ TEST(parser, combined_seq_and_or)
EXPECT_EQ(chars, "xyz");
}
}
{
constexpr auto parser = int_ >> -(lit('a') | 'b');
auto result = parse("34b", parser);
EXPECT_TRUE(result);
EXPECT_EQ(*result, 34);
}
}
TEST(parser, eol_)

127
test/quoted_string.cpp Normal file
View File

@@ -0,0 +1,127 @@
/**
* Copyright (C) 2024 T. Zachary Laine
*
* 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)
*/
#include <boost/parser/parser.hpp>
#include <boost/parser/transcode_view.hpp>
#include <gtest/gtest.h>
namespace bp = boost::parser;
TEST(quoted_string, basic)
{
constexpr auto parser = bp::quoted_string;
{
auto result = bp::parse("", parser, bp::ws);
EXPECT_FALSE(result);
}
{
auto result = bp::parse(R"("foo")", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "foo");
}
{
auto result = bp::parse(R"("foo\\")", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "foo\\");
}
{
auto result = bp::parse(R"("\"foo\"")", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "\"foo\"");
}
}
TEST(quoted_string, different_char)
{
constexpr auto parser = bp::quoted_string('\'');
{
auto result = bp::parse("", parser, bp::ws);
EXPECT_FALSE(result);
}
{
auto result = bp::parse(R"('foo')", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "foo");
}
{
auto result = bp::parse(R"('foo\\')", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "foo\\");
}
{
auto result = bp::parse(R"('\'foo\'')", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "'foo'");
}
}
#if 0
TEST(quoted_string, char_set)
{
constexpr auto parser = bp::quoted_string("'\"");
{
auto result = bp::parse("", parser, bp::ws);
EXPECT_FALSE(result);
}
{
EXPECT_FALSE(bp::parse(R"('foo")", parser, bp::ws));
EXPECT_FALSE(bp::parse(R"("foo')", parser, bp::ws));
}
{
auto result = bp::parse(R"('foo')", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "foo");
}
{
auto result = bp::parse(R"("foo")", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "foo");
}
{
auto result = bp::parse(R"('foo\\')", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "foo\\");
}
{
auto result = bp::parse(R"("foo\\")", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "foo\\");
}
{
auto result = bp::parse(R"('\'foo\'')", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "'foo'");
}
{
auto result = bp::parse(R"("\"foo\"")", parser, bp::ws);
EXPECT_TRUE(result);
EXPECT_EQ(*result, "'foo'");
}
{
// Can't escape arbitrary characters, only backslash and the quote
// character.
EXPECT_FALSE(bp::parse(R"("\'foo")", parser, bp::ws));
}
}
#endif

View File

@@ -437,17 +437,35 @@ TEST(replace, join_compat)
TEST(replace, doc_examples)
{
// clang-format off
{
auto rng = "XYZaaXYZbaabaXYZXYZ" | bp::replace(bp::lit("XYZ"), "foo");
namespace bp = boost::parser;
auto card_number = bp::int_ >> bp::repeat(3)['-' >> bp::int_];
auto rng = "My credit card number is 1234-5678-9012-3456." | bp::replace(card_number, "XXXX-XXXX-XXXX-XXXX");
int count = 0;
// Prints foo aa foo baaba foo foo.
// Prints My credit card number is XXXX-XXXX-XXXX-XXXX.
for (auto subrange : rng) {
std::cout << std::string_view(subrange.begin(), subrange.end() - subrange.begin()) << " ";
std::cout << std::string_view(subrange.begin(), subrange.end() - subrange.begin());
++count;
}
std::cout << "\n";
assert(count == 6);
assert(count == 3);
}
#if BOOST_PARSER_USE_CONCEPTS && (!defined(__GNUC__) || 12 <= __GNUC__)
{
namespace bp = boost::parser;
auto card_number = bp::int_ >> bp::repeat(3)['-' >> bp::int_];
auto rng = "My credit card number is 1234-5678-9012-3456." |
bp::replace(card_number, "XXXX-XXXX-XXXX-XXXX") |
std::views::join;
std::string replace_result;
for (auto ch : rng) {
replace_result.push_back(ch);
}
assert(replace_result == "My credit card number is XXXX-XXXX-XXXX-XXXX.");
#endif
}
// clang-format on
}
#endif

View File

@@ -297,6 +297,15 @@ int main()
PARSE(string("h"));
std::cout << "\n\n"
<< "----------------------------------------\n"
<< "| quoted_string() |\n"
<< "----------------------------------------\n";
PARSE(quoted_string);
PARSE(quoted_string('\''));
PARSE(quoted_string("'\""));
std::cout << "\n\n"
<< "----------------------------------------\n"
<< "| eol |\n"

View File

@@ -14,8 +14,7 @@
#include <list>
#if (!defined(_MSC_VER) || BOOST_PARSER_USE_CONCEPTS) && \
(!defined(__GNUC__) || 12 <= __GNUC__ || !BOOST_PARSER_USE_CONCEPTS)
#if (!defined(_MSC_VER) || BOOST_PARSER_USE_CONCEPTS)
namespace bp = boost::parser;