如何定义 boost tokenizer 以返回 boost::iterator_range<const char*>

Posted

技术标签:

【中文标题】如何定义 boost tokenizer 以返回 boost::iterator_range<const char*>【英文标题】:How to define boost tokenizer to return boost::iterator_range<const char*> 【发布时间】:2021-12-08 09:41:55 【问题描述】:

我正在尝试解析一个文件,其中每一行由; 分隔的属性组成。每个属性定义为key valuekey=value,其中键和值可以用双引号" 括起来,以允许键和值包含特殊字符,例如空格 、等号= 或半-冒号;.

为此,我首先使用boost::algorithm::make_split_iterator,然后,为了允许双引号,我使用boost::tokenizer。

我需要将每个键和值解析为boost::iterator_range&lt;const char*&gt;。我尝试按照下面的代码进行编码,但我无法构建它。可能是分词器的定义是正确的,但错误来自iterator_range 的打印。 如有需要,我可以提供更多信息。

#include <boost/algorithm/string.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/tokenizer.hpp>

boost::iterator_range<const char*> line;

const auto topDelim = boost::token_finder(
  [](const char c)  return (c == ';'); ,
  boost::token_compress_on);
for (auto attrIt = make_split_iterator(line, topDelim); !attrIt.eof() && !attrIt->empty(); attrIt++) 
  std::string escape("\\");
  std::string delim(" =");
  std::string quote("\"");
  boost::escaped_list_separator<char> els(escape, delim, quote);
  boost::tokenizer<
    boost::escaped_list_separator<char>,
    boost::iterator_range<const char*>::iterator, // how to define iterator for iterator_range?
    boost::iterator_range<const char*>
  > tok(*attrIt, els);

for (auto t : tok) 
  std::cout << t << std::endl;

构建错误:

/third_party/boost/boost-1_58_0/include/boost/token_functions.hpp: In instantiation of 'bool boost::escaped_list_separator<Char, Traits>::operator()(InputIterator&, InputIterator, Token&) [with InputIterator = const char*; Token = boost::iterator_range<const char*>; Char = char; Traits = std::char_traits<char>]':
/third_party/boost/boost-1_58_0/include/boost/token_iterator.hpp:70:36:   required from 'void boost::token_iterator<TokenizerFunc, Iterator, Type>::initialize() [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>]'
/third_party/boost/boost-1_58_0/include/boost/token_iterator.hpp:77:63:   required from 'boost::token_iterator<TokenizerFunc, Iterator, Type>::token_iterator(TokenizerFunc, Iterator, Iterator) [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>]'
/third_party/boost/boost-1_58_0/include/boost/tokenizer.hpp:86:33:   required from 'boost::tokenizer<TokenizerFunc, Iterator, Type>::iter boost::tokenizer<TokenizerFunc, Iterator, Type>::begin() const [with TokenizerFunc = boost::escaped_list_separator<char>; Iterator = const char*; Type = boost::iterator_range<const char*>; boost::tokenizer<TokenizerFunc, Iterator, Type>::iter = boost::token_iterator<boost::escaped_list_separator<char>, const char*, boost::iterator_range<const char*> >]'
test.cpp:21:23:   required from here
/third_party/boost/boost-1_58_0/include/boost/token_functions.hpp:188:19: error: no match for 'operator+=' (operand types are 'boost::iterator_range<const char*>' and 'const char')
  188 |           else tok+=*next;
      |                ~~~^~~~~~~

【问题讨论】:

你要解析,而不是拆分。 @sehe,谢谢,我同意。我通过迭代器实现了我的自定义解析器。我想知道在 boost 上是否有类似的东西(可能更有效)。 看我的回答:) 【参考方案1】:

正如我所说,您想要解析,而不是拆分。具体来说,如果要将输入拆分为迭代器范围,则必须重复解析的工作,例如引用的构造来获取预期的(未引用的)值。

我会按照你的规格使用 Boost Spirit:

using Attribute = std::pair<std::string /*key*/, //
                            std::string /*value*/>;
using Line      = std::vector<Attribute>;
using File      = std::vector<Line>;

语法

现在使用 X3 我们可以编写表达式来定义语法:

auto file      = x3::skip(x3::blank)[ line % x3::eol ];

在文件中,通常会跳过空格 (std::isblank)。

内容由一行或多行组成,由换行符分隔。

auto line      = attribute % ';';

一行由一个或多个以';'分隔的属性组成

auto attribute = field >> -x3::lit('=') >> field;
auto field     = quoted | unquoted;

一个属性是两个字段,可选用= 分隔。请注意,每个字段都是带引号或不带引号的值。

现在,事情变得有点棘手:在定义字段规则时,我们希望它们是“词素”,即任何空格都不能被跳过。

auto unquoted = x3::lexeme[+(x3::graph - ';' - '=')];

注意graph 已经排除了空格(参见 std::isgraph)。此外,我们禁止裸露的';''=',以免遇到下一个属性/字段。

对于可能包含空格和/或那些特殊字符的字段,我们定义带引号的词位:

auto quoted      = x3::lexeme['"' >> *quoted_char >> '"'];

所以,这只是 "" 之间有任意数量的引号字符,其中

auto quoted_char = '\\' >> x3::char_ | ~x3::char_('"');

该字符可以是任何用\ 转义的字符或除右引号以外的任何字符。

测试时间

让我们锻炼一下*Live On Compiler Explorer

for (std::string const& str :
     
         R"(a 1)",
         R"(b    = 2      )",
         R"("c"="3")",
         R"(a=1;two 222;three "3 3 3")",
         R"(b=2;three 333;four "4 4 4"
            c=3;four 444;five "5 5 5")",
         // special cases
         R"("e=" "5")",
         R"("f=""7")",
         R"("g="="8")",
         R"("\"Hello\\ World\\!\"" '8')",
         R"("h=10;i=11;" bogus;yup "nope")",
         // not ok?
         R"(h i j)",
         // allowing empty lines/attributes?
         "",
         "a 1;",
         ";",
         ";;",
         R"(a=1;two 222;three "3 3 3"

            n=1;gjb 222;guerr "3 3 3"
        )",
     ) //

    File contents;
    if (parse(begin(str), end(str), parser::file, contents))
        fmt::print("Parsed:\n\t- \n", fmt::join(contents, "\n\t- "));
    else
        fmt::print("Not Parsed\n");

打印

Parsed:
    - ("a", "1")
Parsed:
    - ("b", "2")
Parsed:
    - ("c", "3")
Parsed:
    - ("a", "1"), ("two", "222"), ("three", "3 3 3")
Parsed:
    - ("b", "2"), ("three", "333"), ("four", "4 4 4")
    - ("c", "3"), ("four", "444"), ("five", "5 5 5")
Parsed:
    - ("e=", "5")
Parsed:
    - ("f=", "7")
Parsed:
    - ("g=", "8")
Parsed:
    - (""Hello\ World\!"", "'8'")
Parsed:
    - ("h=10;i=11;", "bogus"), ("yup", "nope")
Not Parsed
Not Parsed
Not Parsed
Not Parsed
Not Parsed
Not Parsed

允许空元素

就像替换line 一样简单:

auto line = -(attribute % ';');

也允许多余的分隔符:

auto line = -(attribute % +x3::lit(';')) >> *x3::lit(';');

看到Live On Compiler Explorer

坚持迭代器范围

我在上面解释了为什么我认为这是一个坏主意。考虑如何正确解释这一行的键/值:

"\"Hello\\ World\\!\"" '8'

您根本不想在解析器外部处理语法。但是,也许您的数据是一个 10 GB 的内存映射文件:

using Field     = boost::iterator_range<std::string::const_iterator>;
using Attribute = std::pair<Field /*key*/, //
                            Field /*value*/>;

然后将x3::raw[] 添加到词位中:

auto quoted      = x3::lexeme[x3::raw['"' >> *quoted_char >> '"']];

auto unquoted    = x3::lexeme[x3::raw[+(x3::graph - ';' - '=')]];

Live On Compiler Explorer

【讨论】:

哇,太棒了!非常感谢,我会努力的。

以上是关于如何定义 boost tokenizer 以返回 boost::iterator_range<const char*>的主要内容,如果未能解决你的问题,请参考以下文章

Boost::tokenizer 逗号分隔 (c++)

如何使用 boost::tokenizer 作为 C++ 类成员?

在 Boost Property 树库中,我如何以自定义方式处理文件未找到错误(C++)

使用 BOOST Tokenizer 来显示分隔符并且不在引号中标记字符串

字符串迭代器不兼容 boost::tokenizer

Boost.Tokenizer 用于引号和括号