提升灵气轨迹线并解析unicode

Boost Spirit Qi track line and parse unicode

本文关键字:unicode 轨迹线      更新时间:2023-10-16

我想跟踪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;
  }
}

更新 添加了演示在Coliru上直播

当你试图将iterator_type封装在line_pos_iterator中时,我看到了同样的问题。

经过一番思考,我不太清楚是什么原因导致了这种情况(通过将u8_to_u32转换迭代器适配器封装在boost::spirit::multi_pass<>迭代器转接器中可能会解决这个问题,但是……这听起来太难了,我甚至还没有尝试过)。

相反,我认为换行的本质是(主要是?)不可知字符集。因此,在编码转换之前,可以先用line_pos_iterator包装源迭代器。

这是可编译的。当然,您将获得源迭代器的位置信息,而不是"逻辑字符"[1]

让我在下面演示一下。它将空格分隔的单词解析为字符串向量。显示位置信息的最简单方法是使用iterator_ranges的矢量,而不是仅使用strings。我使用qi::raw[]来公开迭代器[2]

因此,在成功解析后,我循环浏览匹配的范围并打印它们的位置信息。首先,我打印line_pos_iterators报告的实际位置。请记住,这些是"原始"字节偏移量,因为源迭代器是面向字节的。

接下来,我将使用get_current_line和u8_to_u32转换来将行内的偏移转换为(更多)逻辑计数。你会看到的范围

注意我目前认为范围不会越界(这对语法来说是真的)。否则,将需要提取并转换2行。我现在做这件事的方式相当昂贵。考虑通过例如使用Boost字符串算法的find_all设施进行优化。您可以构建一个行结束列表,并使用std::lower_bound来稍微更有效地定位当前行。

注意get_line_startget_current_line的实现可能存在问题;如果你注意到类似的情况,在[spirit general]用户列表上有一个10行的补丁,你可以尝试

事不宜迟,代码和输出:

#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)处理字节就足够了(因为它不知道字符集/编码)。在u32string中,您可以/几乎/假设单个位置大致是一个代码点(尽管我认为(?)对于>L2 UNICODE支持,您仍然必须支持由多个代码单元组合而成的代码点)。

[2]这意味着当前的属性转换和语义操作是多余的,但您会得到:)