我想跟踪unicode字符串的输入位置和输入行。
对于位置,我存储一个迭代器以开始并在所需位置使用std::distance
。只要输入不是unicode,这就可以正常工作。使用unicode符号,位置会发生偏移,即ä
在输入流中占用两个空格,并且位置偏离1.因此,我切换到boost::u8_to_u32_iterator
,这样可以正常工作。
对于我使用boost::spirit::line_pos_iterator
的行也很有用。
我的问题是将两个概念结合起来使用行迭代器和unicode迭代器。另一种允许在unicode字符串上使用pos和line的解决方案当然也是受欢迎的。
这是unicode解析器的一个小例子;如上所述我想用boost::spirit::line_pos_iterator
另外包装迭代器但是甚至不能编译。
#define BOOST_SPIRIT_USE_PHOENIX_V3
#define BOOST_SPIRIT_UNICODE
#include <boost/regex/pending/unicode_iterator.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
namespace phx = boost::phoenix;
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
#include <boost/spirit/repository/include/qi_iter_pos.hpp>
#include <boost/spirit/include/support_line_pos_iterator.hpp>
#include <iostream>
#include <string>
//==============================================================================
std::string to_utf8(const std::u32string& input) {
return std::string(
boost::u32_to_u8_iterator<std::u32string::const_iterator>(input.begin()),
boost::u32_to_u8_iterator<std::u32string::const_iterator>(input.end()));
}
//==============================================================================
int main() {
std::string input(u8"Hallo äöüß");
typedef boost::u8_to_u32_iterator<std::string::const_iterator> iterator_type;
iterator_type first(input.begin()), last(input.end());
qi::rule<iterator_type, std::u32string()> string_u32 = *(qi::char_ - qi::eoi);
qi::rule<iterator_type, std::string()> string =
string_u32[qi::_val = phx::bind(&to_utf8, qi::_1)];
qi::rule<iterator_type, std::string()> rule = string;
std::string ast;
bool result = qi::parse(first, last, rule, ast);
if (result) {
result = first == last;
}
if (result) {
std::cout << "Parsed: " << ast << std::endl;
} else {
std::cout << "Failure" << std::endl;
}
}
答案 0 :(得分:6)
更新 已添加演示 Live on Coliru
当您尝试将iterator_type
包裹在line_pos_iterator
中时,我发现了同样的问题。
经过一番思考后,我不太清楚它是什么原因造成的(可能通过在boost::spirit::multi_pass<>
迭代器适配器中包装u8_to_u32转换迭代器适配器来解决这个问题,但是...听起来......如此笨拙,我甚至没有尝试过。)
line_pos_iterator
包装源迭代器。
这确实可以编译。当然,那么你将根据源迭代器获得位置信息,而不是逻辑字符&#39; [1] 。
让我在下面展示一个示例。它将空格分隔的单词解析为字符串向量。显示位置信息的最简单方法是使用iterator_range
s而不是strings
的向量。我使用qi::raw[]
来公开迭代器 [2] 。
因此,在成功解析后,我遍历匹配的范围并打印其位置信息。首先,我打印line_pos_iterators
报告的实际位置。请记住,这些都是“原始的”。字节偏移,因为源迭代器是面向字节的。
接下来,我与get_current_line
和u8_to_u32转换进行一点跳舞,将线内的偏移量转换为(更多)逻辑计数。你会看到例如
注意我目前假设范围不会跨越线边界(对于此语法也是如此)。否则,需要提取和转换2行。我现在这样做的方式相当昂贵。考虑通过例如优化使用Boost字符串算法find_all
设施。您可以构建一个行尾列表,并使用std::lower_bound
更有效地定位当前行。
注意 get_line_start
和get_current_line
的实施可能存在问题;如果您发现类似这样的内容,可以尝试 10-line patch over at the [spirit-general] user list
没有进一步的麻烦,代码和输出:
#define BOOST_SPIRIT_USE_PHOENIX_V3
#define BOOST_SPIRIT_UNICODE
#include <boost/regex/pending/unicode_iterator.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/phoenix/function/adapt_function.hpp>
namespace phx = boost::phoenix;
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
namespace encoding = boost::spirit::unicode;
#include <boost/spirit/repository/include/qi_iter_pos.hpp>
#include <boost/spirit/include/support_line_pos_iterator.hpp>
#include <iostream>
#include <string>
//==============================================================================
std::string to_utf8(const std::u32string& input) {
return std::string(
boost::u32_to_u8_iterator<std::u32string::const_iterator>(input.begin()),
boost::u32_to_u8_iterator<std::u32string::const_iterator>(input.end()));
}
BOOST_PHOENIX_ADAPT_FUNCTION(std::string, to_utf8_, to_utf8, 1)
//==============================================================================
int main() {
std::string input(u8"Hallo äöüß\n¡Bye! ✿➂➿♫");
typedef boost::spirit::line_pos_iterator<std::string::const_iterator> source_iterator;
typedef boost::u8_to_u32_iterator<source_iterator> iterator_type;
source_iterator soi(input.begin()),
eoi(input.end());
iterator_type first(soi),
last(eoi);
qi::rule<iterator_type, std::u32string()> string_u32 = +encoding::graph;
qi::rule<iterator_type, std::string()> string = string_u32 [qi::_val = to_utf8_(qi::_1)];
std::vector<boost::iterator_range<iterator_type> > ast;
// note the trick with `raw` to expose the iterators
bool result = qi::phrase_parse(first, last, *qi::raw[ string ], encoding::space, ast);
if (result) {
for (auto const& range : ast)
{
source_iterator
base_b(range.begin().base()),
base_e(range.end().base());
auto lbound = get_line_start(soi, base_b);
// RAW access to the base iterators:
std::cout << "Fragment: '" << std::string(base_b, base_e) << "'\t"
<< "raw: L" << get_line(base_b) << ":" << get_column(lbound, base_b, /*tabs:*/4)
<< "-L" << get_line(base_e) << ":" << get_column(lbound, base_e, /*tabs:*/4);
// "cooked" access:
auto line = get_current_line(lbound, base_b, eoi);
// std::cout << "Line: '" << line << "'\n";
// iterator_type is an alias for u8_to_u32_iterator<...>
size_t cur_pos = 0, start_pos = 0, end_pos = 0;
for(iterator_type it = line.begin(), _eol = line.end(); ; ++it, ++cur_pos)
{
if (it.base() == base_b) start_pos = cur_pos;
if (it.base() == base_e) end_pos = cur_pos;
if (it == _eol)
break;
}
std::cout << "\t// in u32 code _units_: positions " << start_pos << "-" << end_pos << "\n";
}
std::cout << "\n";
} else {
std::cout << "Failure" << std::endl;
}
if (first!=last)
{
std::cout << "Remaining: '" << std::string(first, last) << "'\n";
}
}
输出:
clang++ -std=c++11 -Os main.cpp && ./a.out
Fragment: 'Hallo' raw: L1:1-L1:6 // in u32 code _units_: positions 0-5
Fragment: 'äöüß' raw: L1:7-L1:15 // in u32 code _units_: positions 6-10
Fragment: '¡Bye!' raw: L2:2-L2:8 // in u32 code _units_: positions 1-6
Fragment: '✿➂➿♫' raw: L2:9-L2:21 // in u32 code _units_: positions 7-11
[1] 我认为在这种情况下,对于角色的定义并不是一个有用的定义。有字节,代码单元,代码点,字形集群,可能更多。可以说源迭代器(std::string::const_iterator
)处理字节(因为它是charset / encoding unaware)。在u32string中,您可以/几乎/假设单个位置大致是一个代码点(尽管我认为(?)对于&gt; L2 UNICODE支持,您仍然必须支持从多个代码单元组合的代码点)。
[2] 这意味着当前属性转换和语义动作是多余的,但是你会得到它:)