【问题标题】:Boost Spirit Qi track line and parse unicode提升灵气轨迹线并解析unicode
【发布时间】:2013-12-04 23:08:16
【问题描述】:

我想跟踪 unicode 字符串的输入位置和输入行。

对于该位置,我存储了一个迭代器以开始并在所需位置使用std::distance。只要输入不是 unicode,它就可以很好地工作。使用 unicode 符号,位置会发生变化,即ä 在输入流中占用两个空格,并且位置关闭 1。所以,我切换到 boost::u8_to_u32_iterator,这工作正常。

对于我使用boost::spirit::line_pos_iterator 的行,它也很有效。

我的问题是结合这两个概念来使用行迭代器和 unicode 迭代器。当然,另一种允许 pos 和 line 使用 unicode 字符串的解决方案也是受欢迎的。

这是一个 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;
  }
}

【问题讨论】:

    标签: c++ boost unicode boost-spirit boost-spirit-qi


    【解决方案1】:

    更新 添加演示Live on Coliru

    当您尝试将iterator_type 包装在line_pos_iterator 中时,我发现了同样的问题。

    经过一番思考,我不太清楚是什么原因造成的(可以通过将 u8_to_u32 转换迭代器适配器包装在 boost::spirit::multi_pass&lt;&gt; 迭代器适配器中来解决这个问题,但是......这听起来太笨拙了,我没有'甚至没有尝试过)。

    相反,我认为换行的本质是它(大部分?)与字符集无关。所以你可以在编码转换之前先用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的实现可能存在问题;如果您发现类似情况,您可以尝试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) 处理 bytes (因为它不知道字符集/编码)。在 u32string 中,您可以/几乎/假设单个位置大致是一个代码点(尽管我认为(?)对于 >L2 UNICODE 支持,您仍然必须支持从多个代码单元组合的代码点)。

    [2] 这意味着当前的属性转换和语义动作是多余的,但你会明白的:)

    【讨论】:

    • 再次感谢您。我的错误出现在firstlast 的定义中。尽管没有您的帮助,我可能无法找到如何与u8_to_u32 共舞的方法。我在直接解析 ast 时存储行号,但在这里调整您的解决方案没有问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多