Skip to content

Instantly share code, notes, and snippets.

@cppljevans
Last active February 16, 2020 10:06
Show Gist options
  • Save cppljevans/0fede2c9c2e57f25712528dab4e6aec7 to your computer and use it in GitHub Desktop.
Save cppljevans/0fede2c9c2e57f25712528dab4e6aec7 to your computer and use it in GitHub Desktop.
date_parser solution to stackoverflow problem
//Following code modifies:
// https://gist.github.com/cppljevans/0fede2c9c2e57f25712528dab4e6aec7#file-date_parser-rm_as_define-cpp
//by:
// 1) adding a macro, USE_ALTERNATIVES
// 2) using BOOST_FUSION_ADAPT_STRUCT
// 3) using rule
// 4) using force propagation flag in rule.
//to:
// show that even with attribute propagation, the attributes will not
// propage when there's any ALTERNATIVE's.
//Result:
// When !defined(USE_ALTERNATIVES), runs OK.
// when defined(USE_ALTERNATRIVES), fails at compile time.
//=====================
//#define USE_ALTERNATIVES
#include <iostream>
#include <vector>
template <typename Element>
std::ostream& operator<<(std::ostream& os, std::vector<Element>const& seq)
{
for(int elm:seq){ os<<elm<<" ";}
return os;
}
namespace boost {
namespace fusion { namespace detail {
using ::operator<<;
}}
namespace detail { namespace variant {
using ::operator<<;
}}
}//namespace boost
#include <boost/optional/optional_io.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/utility/unrefcv.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/view/iterator_range/iterator_range.hpp>
#include <boost/fusion/iterator/deref.hpp>
using TimeType = unsigned;
using Day = TimeType;
using Month = TimeType;
using Year = TimeType;
struct Date
{
Day _d;
Month _m;
Year _y;
TimeType _hours;
TimeType _minutes;
TimeType _seconds;
TimeType _millisecs;
Date(Day d=0, Month m=0, Year y=0, TimeType hours=0, TimeType minutes=0, TimeType seconds=0, TimeType millisecs=0)
: _d(d),_m(m),_y(y),_hours(hours),_minutes(minutes),_seconds(seconds),_millisecs(millisecs)
{}
friend std::ostream& operator<<(std::ostream&sout, Date const& date) {
sout
<<"{ yr:"<<date._y
<<", mo:"<<date._m
<<", day:"<<date._d
<<", hr:"<<date._hours
<<", min:"<<date._minutes
<<", sec:"<<date._seconds
<<", mil:"<<date._millisecs
<<"}";
return sout;
}
};
BOOST_FUSION_ADAPT_STRUCT(Date,_d,_m,_y,_hours,_minutes,_seconds,_millisecs)
namespace x3 = boost::spirit::x3;
namespace parsers {
static const struct months : x3::symbols<TimeType> {
months() {
add
("Jan" , 1)
("Feb" , 2)
("Mar" , 3)
("Apr" , 4)
("May" , 5)
("Jun" , 6)
("Jul" , 7)
("Aug" , 8)
("Sep" , 9)
("Oct" , 10)
("Nov" , 11)
("Dec" , 12)
("January" , 1)
("February" , 2)
("March" , 3)
("April" , 4)
("May" , 5)
("June" , 6)
("July" , 7)
("August" , 8)
("September" , 9)
("October" , 10)
("November" , 11)
("December" , 12)
;
}
} months;
static const x3::uint_parser<TimeType, 10, 4, 4> yyyy;
static const x3::uint_parser<TimeType, 10, 1, 2> MM, dd;
static const x3::uint_parser<TimeType, 10, 2, 2> H_mm_ss;
static const x3::uint_parser<TimeType, 10, 3, 3> zzz;
auto validate_H = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x >= 0 && x < 24);
};
auto validate_mm_ss = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x >= 0 && x < 60);
};
auto validate_yyyy = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x > 1900 && x < 2200);
};
auto validate_MM = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x >= 1 && x <= 12);
};
auto validate_dd = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x >= 1 && x <= 31);
};
static const auto year_ = yyyy[validate_yyyy];
static const auto month_ = months | MM[validate_MM];
static const auto day_ = dd[validate_dd];
static const auto hours_ = H_mm_ss[validate_H];
static const auto minutes_ = H_mm_ss[validate_mm_ss];
static const auto seconds_ = H_mm_ss[validate_mm_ss];
static const auto milliseconds_ = zzz;
auto swap_day_year=
[](auto& ctx)
{ auto& date_ymd = x3::_attr(ctx);
//assert: date_ymd has int's stored in year month day order.
using boost::fusion::at_c;
std::swap //swap so that day is 1st and year is 3ird:
( at_c<0>(date_ymd)
, at_c<2>(date_ymd)
);
};
static const auto date_parser =
#ifdef USE_ALTERNATIVES
( (year_ > '-' > month_ > '-' > day_ )[swap_day_year]
| (day_ > '-' > month_ > '-' > year_)
)
#else
day_ > '-' > month_ > '-' > year_
#endif
;
static const auto time_parser
=
#ifdef USE_ALTERNATIVES
( hours_ >
( ( ':' > minutes_ > ':' > seconds_ > '.' > milliseconds_ )
| ( minutes_
> ( ( seconds_
> ( milliseconds_
| x3::attr(TimeType(0))
)
)
| x3::repeat(2)[x3::attr(TimeType(0))]
)
)
)
)
| x3::repeat(4)[x3::attr(TimeType(0))]
#else
hours_ > ':' > minutes_ > ':' > seconds_ > '.' > milliseconds_
#endif
;
static const auto date_time_parser =
date_parser > time_parser
;
using date_time_parser_type = x3::unrefcv_t<decltype(date_time_parser)>;
using date_time_attr_type = x3::traits::attribute_of<date_time_parser_type,x3::unused_type,void>::type;
struct attr2date
//Purpose:
// copy attr from parser to the user attribute of type, Date.
: public boost::static_visitor<>
{
Date& _date;
unsigned _index;
attr2date(Date&date):_date(date),_index(0)
{
}
void put_then_next(TimeType time_value)
{
switch(_index)
{
case 0:
_date._d=time_value;
break;
case 1:
_date._m=time_value;
break;
case 2:
_date._y=time_value;
break;
case 3:
_date._hours=time_value;
break;
case 4:
_date._minutes=time_value;
break;
case 5:
_date._seconds=time_value;
break;
case 6:
_date._millisecs=time_value;
break;
default:
std::cout<<"invalid index="<<_index<<"!\n";
}
++_index;
}
template<typename... T>
void put_then_next(boost::variant<T...>const&nxt)
{
(*this)(nxt);
}
template<typename Seq>
auto make_iter_range(Seq const&seq)
{
auto beg_iter=boost::fusion::begin(seq);
auto end_iter=boost::fusion::end(seq);
using beg_type = x3::unrefcv_t<decltype(beg_iter)>;
using end_type = x3::unrefcv_t<decltype(end_iter)>;
return boost::fusion::iterator_range<beg_type,end_type>(beg_iter,end_iter);
}
template<typename Beg, typename End>
auto next_iter_range(boost::fusion::iterator_range<Beg,End>const&iter_range)
{
auto beg_iter=boost::fusion::advance_c<1>(iter_range.first);
using beg_type = x3::unrefcv_t<decltype(beg_iter)>;
auto end_iter=iter_range.last;
return boost::fusion::iterator_range<beg_type,End>(beg_iter,end_iter);
}
void operator()(TimeType time_value)
{
this->put_then_next(time_value);
}
void operator()(int time_value)
{
this->put_then_next(time_value);
}
template<typename... T>
void operator()(boost::variant<T...>const&nxt)
{
boost::apply_visitor(*this,nxt);
}
template <typename Element>
void operator()(std::vector<Element>const &vec)
{ for(Element val:vec) (*this)(val);
}
template <typename EndIter>
void operator()(boost::fusion::iterator_range<EndIter,EndIter>const& iter_range)
{}
template <typename BegIter, typename EndIter>
void operator()(boost::fusion::iterator_range<BegIter,EndIter>const& iter_range)
{
//std::cout<<"iter_range="<<iter_range<<";\n";
using range_type = boost::fusion::iterator_range<BegIter,EndIter>;
auto deref_val = boost::fusion::deref(iter_range.first);
//std::cout<<"deref_val="<<deref_val<<";\n";
this->put_then_next(deref_val);
auto iter_nxt = this->next_iter_range(iter_range);
//std::cout<<"iter_nxt="<<iter_nxt<<";\n";
(*this)(iter_nxt);
}
template <typename... T>
void operator()(boost::fusion::deque<T...>const& seq)
{ //std::cout<<"seq="<<seq<<";\n";
auto iter_range_seq=make_iter_range(seq);
//std::cout<<"make_iter_range="<<iter_range_seq<<";\n";
(*this)(iter_range_seq);
}
};
Date parse(const std::string& date_str)
{
auto first = date_str.begin();
const auto last = date_str.end();
boost::spirit::x3::ascii::space_type space;
Date date_attr{};
x3::rule<struct date_time_id, Date, true> const date_time_rule{"date_time_rule"};
bool r =
phrase_parse
( first
, last
, date_time_rule%=date_time_parser
, space
, date_attr
);
if ( not(r && first == last) )
{ std::cout
<< "Parsing of " << date_str
<< " failed at " << std::string(first, last)
<< ";\n";
}
return date_attr;
};
} // namespace parsers
void print_parse(std::string date_str)
{
std::cout<<"date_str="<<date_str<<";\n";
auto date_ast = parsers::parse(date_str);
std::cout<<"date_ast="<<date_ast<<";\n";
}
int main()
{
std::string date_strs[]=
{ "03-04-2001 10:11:12.123"
#ifdef USE_ALTERNATIVES
, "03-04-2001 10 11 12 123"
, "03-04-2001 10 11 12"
, "03-04-2001 10 11 "
, "2000-01-02"
#endif
};
for(std::string date_str: date_strs) print_parse(date_str);
return 0;
}
//Following code copy&pasted from:
// https://stackoverflow.com/questions/60045742/boost-spirit-performance-considerations-date-parsing
//On:
// 2020-02-03.0824.CST
//and then modified to use rm all BOOST_SPIRIT_DEFINE and use of x3::rule
//and then use default attributes and as last step, copy from default
//attributes to the desired user attribute, Date.
//=====================
#include <iostream>
#include <vector>
template <typename Element>
std::ostream& operator<<(std::ostream& os, std::vector<Element>const& seq)
{
for(int elm:seq){ os<<elm<<" ";}
return os;
}
namespace boost {
namespace fusion { namespace detail {
using ::operator<<;
}}
namespace detail { namespace variant {
using ::operator<<;
}}
}//namespace boost
#include <boost/optional/optional_io.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/utility/unrefcv.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/fusion/view/iterator_range/iterator_range.hpp>
#include <boost/fusion/iterator/deref.hpp>
using TimeType = unsigned;
using Day = TimeType;
using Month = TimeType;
using Year = TimeType;
struct Date
{
Day _d;
Month _m;
Year _y;
TimeType _hours;
TimeType _minutes;
TimeType _seconds;
TimeType _millisecs;
Date(Day d=0, Month m=0, Year y=0, TimeType hours=0, TimeType minutes=0, TimeType seconds=0, TimeType millisecs=0)
: _d(d),_m(m),_y(y),_hours(hours),_minutes(minutes),_seconds(seconds),_millisecs(millisecs)
{}
friend std::ostream& operator<<(std::ostream&sout, Date const& date) {
sout
<<"{ yr:"<<date._y
<<", mo:"<<date._m
<<", day:"<<date._d
<<", hr:"<<date._hours
<<", min:"<<date._minutes
<<", sec:"<<date._seconds
<<", mil:"<<date._millisecs
<<"}";
return sout;
}
};
namespace x3 = boost::spirit::x3;
namespace parsers {
static const struct months : x3::symbols<TimeType> {
months() {
add
("Jan" , 1)
("Feb" , 2)
("Mar" , 3)
("Apr" , 4)
("May" , 5)
("Jun" , 6)
("Jul" , 7)
("Aug" , 8)
("Sep" , 9)
("Oct" , 10)
("Nov" , 11)
("Dec" , 12)
("January" , 1)
("February" , 2)
("March" , 3)
("April" , 4)
("May" , 5)
("June" , 6)
("July" , 7)
("August" , 8)
("September" , 9)
("October" , 10)
("November" , 11)
("December" , 12)
;
}
} months;
static const x3::uint_parser<TimeType, 10, 4, 4> yyyy;
static const x3::uint_parser<TimeType, 10, 1, 2> MM, dd;
static const x3::uint_parser<TimeType, 10, 2, 2> H_mm_ss;
static const x3::uint_parser<TimeType, 10, 3, 3> zzz;
auto validate_H = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x >= 0 && x < 24);
};
auto validate_mm_ss = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x >= 0 && x < 60);
};
auto validate_yyyy = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x > 1900 && x < 2200);
};
auto validate_MM = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x >= 1 && x <= 12);
};
auto validate_dd = [](auto& ctx) {
const auto& x = x3::_attr(ctx);
x3::_pass(ctx) = (x >= 1 && x <= 31);
};
static const auto year_ = yyyy[validate_yyyy];
static const auto month_ = months | MM[validate_MM];
static const auto day_ = dd[validate_dd];
static const auto hours_ = H_mm_ss[validate_H];
static const auto minutes_ = H_mm_ss[validate_mm_ss];
static const auto seconds_ = H_mm_ss[validate_mm_ss];
static const auto milliseconds_ = zzz;
auto swap_day_year=
[](auto& ctx)
{ auto& date_ymd = x3::_attr(ctx);
//assert: date_ymd has int's stored in year month day order.
using boost::fusion::at_c;
std::swap //swap so that day is 1st and year is 3ird:
( at_c<0>(date_ymd)
, at_c<2>(date_ymd)
);
};
static const auto date_parser =
( (year_ > '-' > month_ > '-' > day_ )[swap_day_year]
| (day_ > '-' > month_ > '-' > year_)
)
;
static const auto time_parser
=
( hours_ >
( ( ':' > minutes_ > ':' > seconds_ > '.' > milliseconds_ )
| ( minutes_
> ( ( seconds_
> ( milliseconds_
| x3::attr(TimeType(0))
)
)
| x3::repeat(2)[x3::attr(TimeType(0))]
)
)
)
)
| x3::repeat(4)[x3::attr(TimeType(0))]
;
static const auto date_time_parser =
date_parser > time_parser
;
using date_time_parser_type = x3::unrefcv_t<decltype(date_time_parser)>;
using date_time_attr_type = x3::traits::attribute_of<date_time_parser_type,x3::unused_type,void>::type;
struct attr2date
//Purpose:
// copy attr from parser to the user attribute of type, Date.
: public boost::static_visitor<>
{
Date& _date;
unsigned _index;
attr2date(Date&date):_date(date),_index(0)
{
}
void put_then_next(TimeType time_value)
{
switch(_index)
{
case 0:
_date._d=time_value;
break;
case 1:
_date._m=time_value;
break;
case 2:
_date._y=time_value;
break;
case 3:
_date._hours=time_value;
break;
case 4:
_date._minutes=time_value;
break;
case 5:
_date._seconds=time_value;
break;
case 6:
_date._millisecs=time_value;
break;
default:
std::cout<<"invalid index="<<_index<<"!\n";
}
++_index;
}
template<typename... T>
void put_then_next(boost::variant<T...>const&nxt)
{
(*this)(nxt);
}
template<typename Seq>
auto make_iter_range(Seq const&seq)
{
auto beg_iter=boost::fusion::begin(seq);
auto end_iter=boost::fusion::end(seq);
using beg_type = x3::unrefcv_t<decltype(beg_iter)>;
using end_type = x3::unrefcv_t<decltype(end_iter)>;
return boost::fusion::iterator_range<beg_type,end_type>(beg_iter,end_iter);
}
template<typename Beg, typename End>
auto next_iter_range(boost::fusion::iterator_range<Beg,End>const&iter_range)
{
auto beg_iter=boost::fusion::advance_c<1>(iter_range.first);
using beg_type = x3::unrefcv_t<decltype(beg_iter)>;
auto end_iter=iter_range.last;
return boost::fusion::iterator_range<beg_type,End>(beg_iter,end_iter);
}
void operator()(TimeType time_value)
{
this->put_then_next(time_value);
}
void operator()(int time_value)
{
this->put_then_next(time_value);
}
template<typename... T>
void operator()(boost::variant<T...>const&nxt)
{
boost::apply_visitor(*this,nxt);
}
template <typename Element>
void operator()(std::vector<Element>const &vec)
{ for(Element val:vec) (*this)(val);
}
template <typename EndIter>
void operator()(boost::fusion::iterator_range<EndIter,EndIter>const& iter_range)
{}
template <typename BegIter, typename EndIter>
void operator()(boost::fusion::iterator_range<BegIter,EndIter>const& iter_range)
{
//std::cout<<"iter_range="<<iter_range<<";\n";
using range_type = boost::fusion::iterator_range<BegIter,EndIter>;
auto deref_val = boost::fusion::deref(iter_range.first);
//std::cout<<"deref_val="<<deref_val<<";\n";
this->put_then_next(deref_val);
auto iter_nxt = this->next_iter_range(iter_range);
//std::cout<<"iter_nxt="<<iter_nxt<<";\n";
(*this)(iter_nxt);
}
template <typename... T>
void operator()(boost::fusion::deque<T...>const& seq)
{ //std::cout<<"seq="<<seq<<";\n";
auto iter_range_seq=make_iter_range(seq);
//std::cout<<"make_iter_range="<<iter_range_seq<<";\n";
(*this)(iter_range_seq);
}
};
Date parse(const std::string& date_str)
{
auto first = date_str.begin();
const auto last = date_str.end();
boost::spirit::x3::ascii::space_type space;
date_time_attr_type date_time_attr_valu;
bool r =
phrase_parse
( first
, last
, date_time_parser
, space
, date_time_attr_valu
);
Date date_attr{};
if ( not(r && first == last) )
{ std::cout
<< "Parsing of " << date_str
<< " failed at " << std::string(first, last)
<< ";\n";
}
else
{
attr2date viz{date_attr};
viz(date_time_attr_valu);
}
return date_attr;
};
} // namespace parsers
void print_parse(std::string date_str)
{
std::cout<<"date_str="<<date_str<<";\n";
auto date_ast = parsers::parse(date_str);
std::cout<<"date_ast="<<date_ast<<";\n";
}
int main()
{
print_parse("2000-01-02");
print_parse("03-04-2001 10:11:12.123");
print_parse("03-04-2001 10 11 12 123");
print_parse("03-04-2001 10 11 12");
print_parse("03-04-2001 10 11 ");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment