C++ Boost Spirit attr_cast не вызывает ожидаемый tranform_attribute
Я работаю над синтаксическим анализатором строковых литералов C++ с бодрым духом.
Это то, что я до сих пор:
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/home/support/iterators/line_pos_iterator.hpp>
#include <boost/spirit/repository/include/qi_confix.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
using namespace boost::spirit;
#include <boost/fusion/include/adapt_struct.hpp>
////////////////////////////////
// extra facilities
struct get_line_f
{
template <typename> struct result { typedef size_t type; };
template <typename It> size_t operator()(It const& pos_iter) const
{
return get_line(pos_iter);
}
};
namespace boost { namespace spirit { namespace traits
{
template <>
struct transform_attribute<uint16_t, std::string, qi::domain>
{
typedef std::string& type;
static std::string pre(uint16_t& d) { return "pre16"; }
static void post(uint16_t& val, std::string& attr) { attr = "unicode16"; }
static void fail(uint16_t&) {}
};
}}}
namespace boost { namespace spirit { namespace traits
{
template <>
struct transform_attribute<uint32_t, std::string, qi::domain>
{
typedef std::string& type;
static std::string pre(uint32_t& d) { return "pre32"; }
static void post(uint32_t& val, std::string& attr) { attr = "unicode32"; }
static void fail(uint32_t&) {}
};
}}}
//
////////////////////////////////
struct RangePosition
{
RangePosition()
: beginLine(-1)
, endLine(-1)
{
}
size_t beginLine;
size_t endLine;
};
struct String : public RangePosition
{
String()
: RangePosition()
, value()
, source()
{
}
std::string value;
std::string source;
};
BOOST_FUSION_ADAPT_STRUCT(String,
(std::string, value)
(std::string, source)
(size_t, beginLine)
(size_t, endLine)
)
template <typename Iterator>
struct source_string : qi::grammar<Iterator, String(), qi::space_type>
{
struct escape_symbols : qi::symbols<char, char>
{
escape_symbols()
{
add
("\\\'" , '\'')
("\\\"" , '\"')
("\\\?" , '\?')
("\\\\" , '\\')
("\\0" , '\0')
("\\a" , '\a')
("\\b" , '\b')
("\\f" , '\f')
("\\n" , '\n')
("\\r" , '\r')
("\\t" , '\t')
("\\v" , '\v')
;
}
} escape_symbol;
source_string() : source_string::base_type(start)
{
using qi::raw;
using qi::_val;
using qi::_1;
using qi::space;
using qi::omit;
using qi::no_case;
using qi::attr_cast;
using qi::print;
namespace phx = boost::phoenix;
using phx::at_c;
using phx::begin;
using phx::end;
using phx::construct;
using phx::ref;
escape %= escape_symbol;
character %= (no_case["\\x"] >> hex12)
| ("\\" >> oct123)
| escape
| (print - (lit('"') | '\\'));
unicode %= ("\\u" >> attr_cast(hex4))
| ("\\U" >> attr_cast(hex8));
string_section %= '"' >> *(unicode | character) >> '"';
string %= string_section % omit[*space];
start = raw[
string[at_c<0>(_val) = _1]
]
[
at_c<1>(_val) = construct<std::string>(begin(_1), end(_1)),
at_c<2>(_val) = get_line_(begin(_1)),
at_c<3>(_val) = get_line_(end(_1))
]
;
}
boost::phoenix::function<get_line_f> get_line_;
qi::rule<Iterator, String(), qi::space_type> start;
qi::rule<Iterator, std::string()> escape;
qi::uint_parser<char, 16, 1, 2> hex12;
qi::uint_parser<uint16_t, 16, 4, 4> hex4;
qi::uint_parser<uint32_t, 16, 8, 8> hex8;
qi::uint_parser<char, 8, 1, 3> oct123;
qi::rule<Iterator, std::string()> character;
qi::rule<Iterator, std::string()> unicode;
qi::rule<Iterator, std::string()> string_section;
qi::rule<Iterator, std::string()> string;
};
и мой тестовый код
std::string str[] =
{
"\"\\u1234\\U12345678\"",
"\"te\"\"st\"",
"\"te\" \"st\"",
"\"te\" \n \"st\"",
"\"\"",
"\"\\\"\"",
"\"test\"",
"\"test\" something",
"\"\\\'\\\"\\\?\\\\\\a\\b\\f\\n\\r\\t\\v\"",
"\"\\x61cd\\X3012\\x7z\"",
"\"\\141cd\\06012\\78\\778\"",
"\"te",
"\"te\nst\"",
"\"test\\\"",
"\"te\\st\"",
//
};
typedef line_pos_iterator<std::string::const_iterator> Iterator;
std::ostringstream result;
for (size_t i = 0; i < sizeof(str) / sizeof(str[0]); ++i)
{
source_string<Iterator> g;
Iterator iter(str[i].begin());
Iterator end(str[i].end());
String string;
bool r = phrase_parse(iter, end, g, qi::space, string);
if (r)
result << string.beginLine << "-" << string.endLine << ": " << string.value << " === " << string.source << "\n";
else
result << "Parsing failed\n";
}
Может кто-нибудь помочь мне, почему в этом правиле:
unicode %= ("\\u" >> attr_cast(hex4))
| ("\\U" >> attr_cast(hex8));
attr_cast не вызывает transform_attribute, который я определил?
namespace boost { namespace spirit { namespace traits
{
template <>
struct transform_attribute<uint16_t, std::string, qi::domain>
{
typedef std::string& type;
static std::string pre(uint16_t& d) { return "pre16"; }
static void post(uint16_t& val, std::string& attr) { attr = "unicode16"; }
static void fail(uint16_t&) {}
};
}}}
namespace boost { namespace spirit { namespace traits
{
template <>
struct transform_attribute<uint32_t, std::string, qi::domain>
{
typedef std::string& type;
static std::string pre(uint32_t& d) { return "pre32"; }
static void post(uint32_t& val, std::string& attr) { attr = "unicode32"; }
static void fail(uint32_t&) {}
};
}}}
1 ответ
То, что встроенные типы примитивов ведут себя "странно", похоже на VeryBadIdea ™.
Предполагая, что вы просто хотите декодировать, я предлагаю более простой подход с использованием семантических действий, например
https://github.com/sehe/spirit-v2-json/blob/master/JSON.cpp#L102
char_ = +( ~encoding::char_(L"\"\\")) [ qi::_val += qi::_1 ] | qi::lit(L"\x5C") >> ( // \ (reverse solidus) qi::lit(L"\x22") [ qi::_val += L'"' ] | // " quotation mark U+0022 qi::lit(L"\x5C") [ qi::_val += L'\\' ] | // \ reverse solidus U+005C qi::lit(L"\x2F") [ qi::_val += L'/' ] | // / solidus U+002F qi::lit(L"\x62") [ qi::_val += L'\b' ] | // b backspace U+0008 qi::lit(L"\x66") [ qi::_val += L'\f' ] | // f form feed U+000C qi::lit(L"\x6E") [ qi::_val += L'\n' ] | // n line feed U+000A qi::lit(L"\x72") [ qi::_val += L'\r' ] | // r carriage return U+000D qi::lit(L"\x74") [ qi::_val += L'\t' ] | // t tab U+0009 qi::lit(L"\x75") // uXXXX U+XXXX >> _4HEXDIG [ qi::_val += qi::_1 ]
Это легко адаптируется к вашему варианту использования.
Теперь, если вы настаиваете, во-первых, оберните типы (чтобы вы не "переопределяли" основные типы для духа), а во-вторых, настройте container insertion
черты, так как std::string
(или скорее std::vector<char>
?) это тип контейнера.
Я не рекомендовал бы это все же. Мне нравится держать вещи "простыми" и логику в одном месте. Очевидно, что это "забавно" говорить при использовании генератора синтаксического анализатора, такого как Spirit, потому что так много всего происходит "волшебным образом" за кулисами. Тем не менее, это природа абстракции. Я не думаю, что я бы хотел "абстрагировать" декодирование от Unicode: здесь они чувствуют, что принадлежат проблемной области, а не инструментам.