将 boost-spirit 语法解析嵌入到结构中到另一个语法中会产生编译错误

Embedding a boost-spirit grammar parsing into a struct into another grammar gives compilation errors

提问人:Xoozee 提问时间:2/18/2021 更新时间:2/26/2021 访问量:110

问:

我正在使用 boost spirit 来解析一些文本。为此,我有两个语法。第一个将字符串解析为结构,第二个将语法作为模板参数,并使用它来解析数据序列。第二个解析器应该足够灵活,也可以处理其他语法返回类型。由于原始解析器太大,无法作为最小的示例,因此我尽可能地减少了代码,留下了一些不会解析任何内容的东西,但仍然会导致相同的编译错误:(Coliru 上的代码)

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

#include <vector>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;


struct Struct1
{
  float f;
};
BOOST_FUSION_ADAPT_STRUCT(
    Struct1,
    (float, f))

struct Struct2
{
  float f;
  int i;
};
BOOST_FUSION_ADAPT_STRUCT(
    Struct2,
    (float, f)
    (int, i))


template<typename Iterator,
         typename Result>
class ElementParser : public qi::grammar<Iterator, Result(), ascii::space_type>
{
public:
  using ValueType = Result;
  
  ElementParser() : ElementParser::base_type(element) {}
  
private:
  qi::rule<Iterator, Result(), ascii::space_type> element;
};


template<typename Iterator,
         typename ElementParser,
         typename Element = typename ElementParser::ValueType>
class SP : public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type>
{
public:
  SP()
    : SP::base_type(sequence)
  {
    sequence %= simpleVector % ',';
    // The simpleVector hack is really needed because of some other parsing
    // stuff, that is going on, but has been left out here.
    simpleVector %= qi::repeat(1)[simple];
  }
  
private:
  using Rule = qi::rule<Iterator, std::vector<Element>(), ascii::space_type>;
  Rule sequence;
  Rule simpleVector;
  ElementParser simple;
};


void sequenceTest()
{
  using Iterator = std::string::const_iterator;
  
  SP<Iterator, qi::uint_parser<>, std::size_t> uintParser;                  // OK
  SP<Iterator, ElementParser<Iterator, float>> floatParser;                 // OK
  SP<Iterator, ElementParser<Iterator, std::vector<float>>> vectorParser;   // OK
  
// error: invalid static_cast from type 'const std::vector<Struct1, std::allocator<Struct1> >' to type 'element_type' {aka 'float'}
  SP<Iterator, ElementParser<Iterator, Struct1>> struct1Parser;
  
// error: no matching function for call to 'Struct2::Struct2(const std::vector<Struct2, std::allocator<Struct2> >&)'
  SP<Iterator, ElementParser<Iterator, Struct2>> struct2Parser;
}

只要我使用简单类型或向量作为 的返回类型,一切正常,但是一旦我解析到结构(它本身工作正常),序列解析器似乎会尝试一些 stange 赋值。为什么结构版本会导致编译错误?ElementParserSP

C++ 推-精神-气 助 推融合

评论


答:

1赞 sehe 2/18/2021 #1

我认为你正在绕过古老的单元素序列兼容性规则。尤其是 Struct1,它确实被改编为单元素序列。

但是,在您的代码中,我可以通过删除不必要的装置来轻松使其工作:repeat(1)[]

在 Coliru 上直播

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;

struct Struct1 { float f; };
struct Struct2 { float f; int i; };
BOOST_FUSION_ADAPT_STRUCT(Struct1, f)
BOOST_FUSION_ADAPT_STRUCT(Struct2, f, i)

template <typename Iterator, typename Result>
class ElementParser
    : public qi::grammar<Iterator, Result(), ascii::space_type> {
  public:
    using ValueType = Result;

    ElementParser() : ElementParser::base_type(element) {
    }

  private:
    qi::rule<Iterator, Result(), ascii::space_type> element;
};

template <typename Iterator, typename ElementParser,
          typename Element = typename ElementParser::ValueType>
class SequenceParser
    : public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type> {
  public:
    SequenceParser() : SequenceParser::base_type(sequence) {
        sequence = simple % ',';
    }

  private:
    qi::rule<Iterator, std::vector<Element>(), ascii::space_type> sequence;
    ElementParser simple;
};

void sequenceTest() {
    using It = std::string::const_iterator;

    SequenceParser<It, qi::uint_parser<>, std::size_t> uintParser;  // OK
    SequenceParser<It, ElementParser<It, float>> floatParser; // OK
    SequenceParser<It, ElementParser<It, std::vector<float>>>
        vectorParser; // OK

    SequenceParser<It, ElementParser<It, Struct1>> struct1Parser;
    SequenceParser<It, ElementParser<It, Struct2>> struct2Parser;
}

int main() {
    sequenceTest();
}

奖励:魔术功能JustParseIt

请注意,在某种程度上,您似乎正在重新审视库设计。看一看 qi::auto_

除此之外,还有这里的想法:

例如,通过专门化 trait 来制作逗号分隔的序列解析器:

template <typename... T>
struct create_parser<std::vector<T...>> : comma_separated_sequence {};

struct comma_separated_sequence {
    using type = decltype(qi::copy(qi::auto_ % ','));
    static type call() { return qi::copy(qi::auto_ % ','); }
};

现在,您可以实现一个使用 /the world/ 的函数:JustParseIt

bool JustParseIt(std::string_view input, auto& val) {
    return qi::phrase_parse(input.begin(), input.end(), qi::auto_, qi::space, val);
}

你会惊讶地看到它解析的内容:

Live On 编译器资源管理器

#include <boost/fusion/include/adapted.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/spirit/include/qi.hpp>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
namespace qi = boost::spirit::qi;

namespace MyLib {
    struct Struct1 { float f; };
    struct Struct2 { float f; int i; };
    using boost::fusion::operator<<;
}
BOOST_FUSION_ADAPT_STRUCT(MyLib::Struct1, f)
BOOST_FUSION_ADAPT_STRUCT(MyLib::Struct2, f, i)

namespace {
    struct comma_separated_sequence {
        using type = decltype(qi::copy(qi::auto_ % ','));
        static type call() { return qi::copy(qi::auto_ % ','); }
    };
}

namespace boost::spirit::traits {
    template <typename... T>
    struct create_parser<std::list<T...>> : comma_separated_sequence {};

    template <typename... T>
    struct create_parser<std::vector<T...>> : comma_separated_sequence {};
}

bool JustParseIt(std::string_view input, auto& val) {
#ifdef BOOST_SPIRIT_DEBUG
    using It      = decltype(input.begin());
    using Skipper = qi::space_type;
    using Attr    = std::decay_t<decltype(val)>;
    static qi::rule<It, Attr(), Skipper> parser = qi::auto_;
    BOOST_SPIRIT_DEBUG_NODE(parser);
    return qi::phrase_parse(input.begin(), input.end(), parser, qi::space, val);
#else
    return qi::phrase_parse(input.begin(), input.end(), qi::auto_, qi::space, val);
#endif
}

int main() {
    using namespace MyLib;
    std::cerr << std::boolalpha; // make debug easier to read

    float f;
    JustParseIt("3.1415", f);

    uint64_t u;
    JustParseIt("00897823", u);

    Struct1 s1;
    JustParseIt("3.1415", s1);

    Struct2 s2;
    JustParseIt("3.1415 00897823", s2);

    std::list<float> floats;;
    JustParseIt("1.2,3.4", floats);

    std::list<Struct1> list1;
    JustParseIt("1.2", list1);
    JustParseIt("1.2, -inf, 9e10, NaN", list1);

    std::vector<boost::variant<Struct2, bool> > variants;
    JustParseIt("true, 9e10 123, NaN 234, false, false", variants);

    std::vector<Struct2> vec2;
    JustParseIt("9e10 123, NaN 234", vec2);

    // this is pushing it - for lack of structurual syntax
    std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> insane;
    JustParseIt("true 3.14 1e1 1, 2e2 2, 3e3 3, false +inf 4e4 4", insane);

    fmt::print("float f: {}\n"
            "uint64_t u: {}\n"
            "std::list<float> floats: {}\n"
            "std::list<Struct1> list1: {}\n"
            "std::vector<Struct2> vec2: {}\n"
            "Struct1 s1: {}\n"
            "Struct2 s2: {}\n"
            "std::vector<boost::variant<Struct2, bool> > variants: {}\n"
            "std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> "
            "insane: {}\n",
            f, u, floats, list1, vec2, s1, s2, variants, insane);
}

指纹

float f: 3.1415
uint64_t u: 897823
std::list<float> floats: {1.2, 3.4}
std::list<Struct1> list1: {(1.2), (1.2), (-inf), (9e+10), (nan)}
std::vector<Struct2> vec2: {(9e+10 123), (nan 234)}
Struct1 s1: (3.1415)
Struct2 s2: (3.1415 897823)
std::vector<boost::variant<Struct2, bool> > variants: {1, (9e+10 123), (nan 234), 0, 0}
std::vector<std::tuple<bool, Struct1, std::vector<Struct2>>> insane: {(true, (3.14), {(10 1), (200 2), (3000 3)}), (false, (inf), {(40000 4)})}

请注意,您可以定义BOOST_SPIRIT_DEBUG来将解析器调试到 stderr,例如

<parser>
  <try>true, 9e10 123, NaN </try>
  <success></success>
  <attributes>[[true, [9e+10, 123], [nan, 234], false, false]]</attributes>
</parser>

评论

0赞 Xoozee 2/18/2021
嗨,@sehe,感谢您的回答。不幸的是,这个序列解析器包括一个嵌套的重复解析器,要做到这一点,需要使单个元素与“旧”结果兼容,这些结果已经是向量。这就是为什么我在 singleVector 规则上方添加了此评论。这在我遇到的所有其他情况下都很好用,但结构以某种方式杀死了它。尤其是这种情况。我不明白,为什么它要将整个向量分配给单个元素。repeat(1)[]Struct2
0赞 Xoozee 2/18/2021
该函数看起来很有趣。是否可以在特征中使用预定义的语法?我想重写序列语法,将元素解析器作为模板参数,然后也可以是.但这需要以某种方式将迭代器类型传递给语法,在我看来,这是无法做到的,使用 .有什么提示吗?JustParseItcreate_parserqi::auto_create_parser
0赞 sehe 2/18/2021
我认为将解析器表达式保持在 Qi Domain 原型表达式的领域(即不绑定到静态迭代器类型)严格来说更通用。如果需要 的属性强制/变换能力,请使用 qi::attr_castqi::rule
0赞 sehe 2/19/2021
也就是说,如果你不介意通用性/通用性,或者只是已经有大量的遗留规则,你当然可以通过假设静态迭代器类型来实现create_parser(如果迭代器类型不匹配,则根本不会编译)。
0赞 sehe 2/19/2021
下面是一个演示:不使用 -DUSE_LEGACY_STRUCT2_PARSER 与使用 -DUSE_LEGACY_STRUCT2_PARSER 形成对比。我让 Struct2 遗留规则接受“[float;int]“ 输入
0赞 Xoozee 2/26/2021 #2

下面是一个更短的示例,演示了相同的问题(编译器资源管理器):

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/include/qi.hpp>

#include <vector>
#include <tuple>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;

void test()
{
  using Iterator = std::string::const_iterator;
  
  // OK
  qi::rule<Iterator, std::vector<int>(), ascii::space_type> vecI_src;
  qi::rule<Iterator, std::vector<int>(), ascii::space_type> vecI_dst = *vecI_src;
  
  // error: no matching function for call to 'std::tuple<int, float>::tuple(const std::vector<std::tuple<int, float> >&)'
  qi::rule<Iterator, std::vector<std::tuple<int, float>>(), ascii::space_type> vecT_src;
  qi::rule<Iterator, std::vector<std::tuple<int, float>>(), ascii::space_type> vecT_dst = *vecT_src;
}

我认为,问题在于,向量和元组在底层库中的句柄非常相似,因此当涉及到展平向量时,会超出目标并且赋值失败。(可能是通过某种SFINAE机制。现在,扁平化向量不起作用,右侧解析器的合成属性类型为 ,而不是预期的 。boost::fusionboost::fusiontuplevector<vector<tuple<int, float>>>vector<tuple<int, float>>

知道了这一点,我发现的(不是很漂亮的)解决方案(对于原始示例)是手动为两种预期形式创建赋值函数重载:

  static
  void flattenAndAppend(std::vector<Element>& into,
                        std::vector<std::vector<Element>> const& vector)
  {
    for(auto const& subvector: vector)
    {
      into.insert(into.end(), subvector.begin(), subvector.end());
    }
  }
  
  static
  void flattenAndAppend(std::vector<Element>& into,
                        std::vector<Element> const& vector)
  {
    into.insert(into.end(), vector.begin(), vector.end());
  }

并通过函数在语义操作中调用它们:boost::phoenix

    ph::function append = [](auto& into,
                             auto const& a1)
    {
      flattenAndAppend(into, a1);
    };
    
    sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];

下面是整个工作示例(编译器资源管理器):

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

#include <vector>
#include <tuple>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
namespace ql = qi::labels;
namespace ph = boost::phoenix;


struct Struct1
{
  float f;
};
BOOST_FUSION_ADAPT_STRUCT(
    Struct1,
    (float, f))

struct Struct2
{
  float f;
  int i;
};
BOOST_FUSION_ADAPT_STRUCT(
    Struct2,
    (float, f)
    (int, i))


template<typename Iterator,
         typename Result>
class ElementParser : public qi::grammar<Iterator, Result(), ascii::space_type>
{
public:
  using ValueType = Result;
  
  ElementParser() : ElementParser::base_type(element) {}
  
private:
  qi::rule<Iterator, Result(), ascii::space_type> element;
};


template<typename Iterator>
class Struct2Tuple : public qi::grammar<Iterator, std::tuple<float, int>(), ascii::space_type>
{
public:
  using ValueType = std::tuple<float, int>;
  
  Struct2Tuple() : Struct2Tuple::base_type(tupleElement)
  {
    ph::function convert = [](auto const& s,
                              auto& t)
    {
      t = std::make_tuple(s.f, s.i);
    };
    
    tupleElement = structElement[convert(ql::_1, qi::_val)];
  }
  
private:
  qi::rule<Iterator, ValueType(), ascii::space_type> tupleElement;
  ElementParser<Iterator, Struct2> structElement;
};


template<typename Iterator,
         typename ElementParser,
         typename Element = typename ElementParser::ValueType>
class SP : public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type>
{
private:
  static
  void flattenAndAppend(std::vector<Element>& into,
                        std::vector<std::vector<Element>> const& vector)
  {
    for(auto const& subvector: vector)
    {
      into.insert(into.end(), subvector.begin(), subvector.end());
    }
  }
  
  static
  void flattenAndAppend(std::vector<Element>& into,
                        std::vector<Element> const& vector)
  {
    into.insert(into.end(), vector.begin(), vector.end());
  }
  
public:
  SP()
    : SP::base_type(sequence)
  {
    ph::function append = [](auto& into,
                             auto const& a1)
    {
      flattenAndAppend(into, a1);
    };
    
    sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];
    simpleVector = qi::repeat(1)[simple];
  }
  
private:
  using Rule = qi::rule<Iterator, std::vector<Element>(), ascii::space_type>;
  Rule sequence;
  Rule simpleVector;
  ElementParser simple;
};


void sequenceTest()
{
  using Iterator = std::string::const_iterator;
  
  SP<Iterator, qi::uint_parser<>, std::size_t> uintParser;                  // OK
  SP<Iterator, ElementParser<Iterator, float>> floatParser;                 // OK
  SP<Iterator, ElementParser<Iterator, std::vector<float>>> vectorParser;   // OK
  
  SP<Iterator, Struct2Tuple<Iterator>> struct2tupleParser;                  // OK.
  
  SP<Iterator, ElementParser<Iterator, std::tuple<float, float>>> tupleParser;   // now OK
  SP<Iterator, ElementParser<Iterator, Struct1>> struct1Parser;                  // now OK
  SP<Iterator, ElementParser<Iterator, Struct2>> struct2Parser;                  // now OK
}